This commit is contained in:
zlwu committed 2026-05-24 16:58:51 +08:00
1 parent 9dd7eb56ba
commit 7cec1f2190
112 files changed
+22684 -6952

No files matched your search

File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1 +1 @@
84901
95768
+101 -3
View File
@@ -1,5 +1,5 @@
"""生产管理 API 路由(7 个子模块:耗材、试剂、SOP文档、工艺工序分类、产出物类型、耗材包、工艺库)"""
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, HTTPException
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy.orm import Session
from typing import Any, Optional, List
@@ -34,7 +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.schemas.production_work_order import ProductionWorkOrderListQuery, ProductionWorkOrderComplete
from app.schemas.production_work_order import ProductionWorkOrderListQuery, ProductionWorkOrderComplete, ProductionWorkOrderCreate, ProductionWorkOrderConfigureBody
from app.schemas.production_task import (
ProductionTaskCreate,
ProductionTaskUpdate,
@@ -1543,6 +1543,43 @@ async def update_step_data_collections(
# ---------------------------------------------------------------------------
@router.post("/production-work-orders")
async def create_production_work_order(
body: ProductionWorkOrderCreate,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
wo_type = (body.work_order_type or "inbound").strip()
if wo_type == "temporary":
wo = production_work_order_svc.create_temp_production_work_order(
db,
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
remark=body.remark or "",
)
else:
wo = production_work_order_svc.create_production_work_order_manual(
db,
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
protocol_no=body.protocol_no or "",
sample_code=body.sample_code or "",
reception_source=body.reception_source or "cell_storage",
remark=body.remark or "",
)
db.commit()
return success_response(production_work_order_svc._wo_to_dict(wo), message="创建成功")
except ValueError as e:
return error_response(400, str(e))
except Exception as e:
db.rollback()
return error_response(400, str(e))
@router.get("/production-work-orders")
async def list_production_work_orders(
page: int = Query(1, ge=1),
@@ -1550,6 +1587,7 @@ async def list_production_work_orders(
keyword: Optional[str] = None,
workOrderNo: Optional[str] = None,
workOrderStatus: Optional[str] = None,
workOrderType: Optional[str] = None,
protocolNo: Optional[str] = None,
sampleCode: Optional[str] = None,
user: dict = Depends(require_admin_or_super_admin),
@@ -1560,6 +1598,7 @@ async def list_production_work_orders(
page=page, page_size=pageSize,
keyword=keyword,
work_order_no=workOrderNo, work_order_status=workOrderStatus,
work_order_type=workOrderType,
protocol_no=protocolNo, sample_code=sampleCode,
)
result = production_work_order_svc.list_production_work_orders(
@@ -1602,6 +1641,51 @@ async def complete_production_work_order(
return error_response(400, str(e))
@router.delete("/production-work-orders/{wo_id}")
async def delete_production_work_order_api(
wo_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
production_work_order_svc.delete_production_work_order(db, wo_id, tenant_no, user.get("user_id"))
return success_response(None, message="删除成功")
except ValueError as e:
return error_response(400, str(e))
@router.post("/production-work-orders/{wo_id}/configure")
async def configure_production_work_order(
wo_id: str,
body: ProductionWorkOrderConfigureBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
wo = production_work_order_svc.configure_temp_production_work_order(
db,
wo_id,
task_name=body.task_name or "",
task_type=body.task_type or "cell_storage",
route_id=body.route_id,
description=body.description or "",
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(production_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. 生产任务
# ---------------------------------------------------------------------------
@@ -1948,6 +2032,7 @@ async def list_cell_products(
productTypeId: Optional[str] = None,
productStatus: Optional[str] = None,
passage: Optional[str] = None,
sourceType: Optional[str] = None,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
@@ -1957,7 +2042,7 @@ async def list_cell_products(
keyword=keyword,
product_code=productCode, product_name=productName,
product_type_id=productTypeId, product_status=productStatus,
passage=passage,
passage=passage, source_type=sourceType,
user_info=user,
)
items = result.get("items", [])
@@ -1979,6 +2064,19 @@ async def get_cell_product_detail(
return success_response(cell_product_svc.product_to_response(db, tenant_no, obj))
@router.get("/cell-products/{product_id}/chain")
async def get_cell_product_chain(
product_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
chain = cell_product_svc.get_product_chain(db, product_id, tenant_no)
if not chain:
raise HTTPException(status_code=404, detail="细胞产品不存在")
return success_response(chain)
@router.post("/cell-products")
async def create_cell_product(
body: CellProductCreate,
+133
View File
@@ -23,6 +23,9 @@ from app.crud import qc_material_usage as mu_svc
from app.crud import qc_work_order as wo_svc
from app.crud.collection_protocol import total_pages
from app.api.sample_reception import _get_current_user_id, _get_tenant_no, _page_payload
from app.schemas.qc_work_order import QcWorkOrderCreateBody, QcWorkOrderUpdateBody
from app.schemas.qc_task import QcTaskCreateBody
router = APIRouter(prefix="/web/admin", tags=["质量检测"])
@@ -547,12 +550,52 @@ class WorkOrderConfigureBody(BaseModel):
third_party_item_ids: List[str] = Field(default_factory=list, alias="thirdPartyItemIds")
@router.post("/qc-work-orders")
async def create_qc_work_order(
body: QcWorkOrderCreateBody,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
):
try:
tenant_no = _get_tenant_no(db, user)
wo_type = (body.work_order_type or "inbound").strip()
if wo_type == "temporary":
wo = wo_svc.create_temp_qc_work_order(
db,
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
cell_product_id=body.cell_product_id or "",
remark=body.remark or "",
)
else:
wo = wo_svc.create_qc_work_order_manual(
db,
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
protocol_no=body.protocol_no or "",
sample_code=body.sample_code or "",
reception_source=body.reception_source or "cell_storage",
qc_biz_type=body.qc_biz_type or "inbound",
remark=body.remark or "",
)
db.commit()
return success_response(wo_svc._wo_to_dict(wo), message="创建成功")
except PermissionError as e:
return error_response(403, str(e))
except Exception as e:
db.rollback()
return error_response(400, str(e))
@router.get("/qc-work-orders")
async def list_qc_work_orders(
page: int = Query(1, ge=1),
pageSize: int = Query(10, ge=1, le=100),
keyword: Optional[str] = None,
workOrderStatus: Optional[str] = None,
workOrderType: Optional[str] = None,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
):
@@ -564,6 +607,7 @@ async def list_qc_work_orders(
page_size=pageSize,
keyword=keyword,
work_order_status=workOrderStatus,
work_order_type=workOrderType,
tenant_no=tenant_no,
user_info=user,
)
@@ -592,6 +636,49 @@ async def get_qc_work_order_detail(
return error_response(400, str(e))
@router.put("/qc-work-orders/{work_order_id}")
async def update_qc_work_order(
work_order_id: str,
body: QcWorkOrderUpdateBody,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
):
try:
tenant_no = _get_tenant_no(db, user)
wo = wo_svc.update_temp_qc_work_order(
db,
work_order_id,
tenant_no=tenant_no,
user_id=_get_current_user_id(user),
cell_product_id=body.cell_product_id,
remark=body.remark,
)
db.commit()
return success_response(wo_svc._wo_to_dict(wo), message="保存成功")
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
db.rollback()
return error_response(400, str(e))
except Exception as e:
db.rollback()
return error_response(400, str(e))
@router.delete("/qc-work-orders/{work_order_id}")
async def delete_qc_work_order_api(
work_order_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
wo_svc.delete_qc_work_order(db, work_order_id, tenant_no, user.get("user_id"))
return success_response(None, message="删除成功")
except ValueError as e:
return error_response(400, str(e))
@router.post("/qc-work-orders/{work_order_id}/configure")
async def configure_qc_work_order(
work_order_id: str,
@@ -629,6 +716,38 @@ class TaskAttachmentsBody(BaseModel):
third_party_attachments: str = Field("", alias="thirdPartyAttachments")
@router.post("/qc-tasks")
async def create_qc_task(
body: QcTaskCreateBody,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcTask:list")),
):
try:
tenant_no = _get_tenant_no(db, user)
task = task_svc.create_qc_task_manual(
db,
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
task_type=body.task_type,
item_code=body.item_code,
item_name=body.item_name,
company_id=body.company_id,
company_name=body.company_name,
fee_amount=body.fee_amount,
sample_code=body.sample_code or "",
work_order_id=body.work_order_id or "",
remark=body.remark or "",
)
db.commit()
return success_response(task_svc._task_to_dict(task), message="创建成功")
except PermissionError as e:
return error_response(403, str(e))
except Exception as e:
db.rollback()
return error_response(400, str(e))
@router.get("/qc-tasks")
async def list_qc_tasks(
page: int = Query(1, ge=1),
@@ -838,6 +957,20 @@ async def update_qc_task_attachments(
return error_response(400, str(e))
@router.delete("/qc-tasks/{task_id}")
async def delete_qc_task_api(
task_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
task_svc.delete_qc_task(db, task_id, tenant_no, user.get("user_id"))
return success_response(None, message="删除成功")
except ValueError as e:
return error_response(400, str(e))
# ---------- 质检物料库 ----------
+345
View File
@@ -132,6 +132,42 @@ def _enrich_product_dicts(
else:
slot_map[slot.id] = code or node_path or ""
# --- 来源信息富化 ---
from app.models.models import BizProductionTask, BizProductionWorkOrder
task_ids = {i.get("productionTaskId") for i in items if i.get("productionTaskId")}
task_map: Dict[str, Any] = {}
task_to_wo: Dict[str, str] = {}
wo_map: Dict[str, Any] = {}
if task_ids:
tasks = (
db.query(BizProductionTask)
.filter(
BizProductionTask.id.in_(task_ids),
BizProductionTask.del_flag == 0,
)
.all()
)
for t in tasks:
task_map[t.id] = t
if t.work_order_id:
task_to_wo[t.id] = t.work_order_id
wo_ids = set(task_to_wo.values())
if wo_ids:
wos = (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.id.in_(wo_ids),
BizProductionWorkOrder.del_flag == 0,
)
.all()
)
wo_map = {w.id: w for w in wos}
source_type_labels = {"production": "生产入库", "manual": "手动录入"}
for item in items:
tid = item.get("productTypeId") or ""
sid = item.get("specId") or ""
@@ -139,6 +175,28 @@ def _enrich_product_dicts(
item["productTypeName"] = type_map.get(tid, "") if tid else ""
item["specName"] = spec_map.get(sid, "") if sid else ""
item["storageSlotName"] = slot_map.get(slot_id, "") if slot_id else ""
# 来源信息
src_type = item.get("sourceType", "")
item["sourceTypeLabel"] = source_type_labels.get(src_type, src_type or "")
tid = item.get("productionTaskId") or ""
if tid and tid in task_map:
t_obj = task_map[tid]
item["sourceTaskNo"] = t_obj.task_no or ""
wo_id = task_to_wo.get(tid)
if wo_id and wo_id in wo_map:
wo_obj = wo_map[wo_id]
item["sourceWorkOrderNo"] = wo_obj.work_order_no or ""
item["sourceProtocolNo"] = wo_obj.protocol_no or ""
item["sourceReceptionSource"] = wo_obj.reception_source or ""
else:
item["sourceWorkOrderNo"] = ""
item["sourceProtocolNo"] = ""
item["sourceReceptionSource"] = ""
else:
item["sourceTaskNo"] = ""
item["sourceWorkOrderNo"] = ""
item["sourceProtocolNo"] = ""
item["sourceReceptionSource"] = ""
return items
@@ -159,6 +217,7 @@ def list_cell_products(
product_type_id: Optional[str] = None,
product_status: Optional[str] = None,
passage: Optional[str] = None,
source_type: Optional[str] = None,
user_info=None,
) -> Dict[str, Any]:
def _filtered():
@@ -187,6 +246,8 @@ def list_cell_products(
q0 = q0.filter(BizCellProduct.product_status == product_status)
if passage:
q0 = q0.filter(BizCellProduct.passage == passage)
if source_type:
q0 = q0.filter(BizCellProduct.source_type == source_type)
if user_info:
from app.core.data_scope import apply_data_scope
q0 = apply_data_scope(q0, BizCellProduct, user_info, db, tenant_no)
@@ -600,3 +661,287 @@ def total_pages(total: int, page_size: int) -> int:
if total <= 0:
return 0
return int(math.ceil(total / float(page_size)))
# ---------------------------------------------------------------------------
# 来源链路 API
# ---------------------------------------------------------------------------
_MAX_CHAIN_DEPTH = 10
_SOURCE_TYPE_LABELS = {"production": "生产入库", "manual": "手动录入"}
def _product_node(obj: BizCellProduct, is_current: bool = False) -> Dict[str, Any]:
return {
"id": obj.id,
"productCode": obj.product_code or "",
"productName": obj.product_name or "",
"nodeType": "cell_product",
"productStatus": obj.product_status or "",
"passage": obj.passage or "",
"isCurrent": is_current,
}
def _first_chain_link(chain: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
"""祖先链单链路段:列表仅含一个节点时取首项,供 parent 嵌套为对象而非数组。"""
return chain[0] if chain else None
def _edge_meta(task: Any, wo: Any = None) -> Dict[str, Any]:
edge: Dict[str, Any] = {
"workOrderNo": "",
"workOrderType": "",
"taskNo": "",
"taskName": "",
"taskId": "",
}
if task:
edge["taskNo"] = task.task_no or ""
edge["taskName"] = task.task_name or ""
edge["taskId"] = task.id or ""
if wo:
edge["workOrderNo"] = wo.work_order_no or ""
edge["workOrderType"] = wo.reception_source or ""
return edge
def _sample_customer_dict(db: Session, protocol_id: str, tenant_no: str) -> Dict[str, Any]:
if not protocol_id:
return {}
from app.models.models import BizProtocolPartyCustomer
pc = (
db.query(BizProtocolPartyCustomer)
.filter(
BizProtocolPartyCustomer.collection_protocol_id == protocol_id,
BizProtocolPartyCustomer.tenant_no == tenant_no,
BizProtocolPartyCustomer.del_flag == 0,
)
.first()
)
if not pc:
return {}
return {
"motherName": pc.mother_name or "",
"motherPhone": pc.mother_phone or "",
"motherIdCard": pc.mother_id_card or "",
"newbornName": pc.newborn_name or "",
}
def _build_ancestor_chain(
db: Session, product: BizCellProduct, depth: int, tenant_no: str
) -> List[Dict[str, Any]]:
if depth >= _MAX_CHAIN_DEPTH:
return []
task_id = product.production_task_id
if not task_id:
return []
from app.models.models import BizProductionTask, BizProductionWorkOrder
from app.models.models import BizProductionTaskInput
task = (
db.query(BizProductionTask)
.filter(BizProductionTask.id == task_id, BizProductionTask.del_flag == 0)
.first()
)
if not task:
return []
wo = None
if task.work_order_id:
wo = (
db.query(BizProductionWorkOrder)
.filter(BizProductionWorkOrder.id == task.work_order_id, BizProductionWorkOrder.del_flag == 0)
.first()
)
edge = _edge_meta(task, wo)
# 检查投入物中是否有父细胞产品(按排序取首个)
parent_input = (
db.query(BizProductionTaskInput)
.filter(
BizProductionTaskInput.task_id == task_id,
BizProductionTaskInput.input_type == "cell_product",
BizProductionTaskInput.del_flag == 0,
)
.order_by(
BizProductionTaskInput.order_num.asc(),
BizProductionTaskInput.create_time.asc(),
)
.first()
)
if parent_input and (parent_input.input_id or parent_input.input_code):
parent_product = None
if parent_input.input_id:
parent_product = (
db.query(BizCellProduct)
.filter(BizCellProduct.id == parent_input.input_id, BizCellProduct.del_flag == 0)
.first()
)
if not parent_product and parent_input.input_code:
parent_product = (
db.query(BizCellProduct)
.filter(BizCellProduct.product_code == parent_input.input_code, BizCellProduct.del_flag == 0)
.first()
)
if parent_product:
parent_chain = _build_ancestor_chain(db, parent_product, depth + 1, tenant_no)
return [
{
"node": _product_node(parent_product),
"edge": edge,
"parent": _first_chain_link(parent_chain),
}
]
# 检查投入物中是否有样本(终止节点)
sample_input = (
db.query(BizProductionTaskInput)
.filter(
BizProductionTaskInput.task_id == task_id,
BizProductionTaskInput.input_type == "sample",
BizProductionTaskInput.del_flag == 0,
)
.first()
)
if sample_input and (sample_input.input_id or sample_input.input_code):
from app.models.models import BizProtocolSample
sample = None
if sample_input.input_id:
sample = (
db.query(BizProtocolSample)
.filter(BizProtocolSample.id == sample_input.input_id, BizProtocolSample.del_flag == 0)
.first()
)
if not sample and sample_input.input_code:
sample = (
db.query(BizProtocolSample)
.filter(BizProtocolSample.sample_code == sample_input.input_code, BizProtocolSample.del_flag == 0)
.first()
)
if sample:
reception_source = wo.reception_source if wo else ""
source_labels = {
"cell_storage": "细胞存储",
"research_donation": "科研捐赠",
}
protocol_id = sample.collection_protocol_id or ""
sample_node = {
"id": sample.id,
"productCode": sample.sample_code or "",
"productName": "",
"nodeType": "sample",
"sampleCode": sample.sample_code or "",
"sampleTypeName": sample_input.input_name or "",
"protocolNo": wo.protocol_no if wo else "",
"collectionProtocolId": protocol_id,
"receptionSource": reception_source,
"receptionSourceLabel": source_labels.get(reception_source, reception_source),
"customer": _sample_customer_dict(db, protocol_id, tenant_no),
"isCurrent": False,
}
return [{"node": sample_node, "edge": edge, "parent": None}]
return []
def _build_descendant_chain(
db: Session, product_id: str, product_code: str, depth: int, tenant_no: str
) -> List[Dict[str, Any]]:
if depth >= _MAX_CHAIN_DEPTH:
return []
from app.models.models import BizProductionTaskInput, BizProductionTaskOutput, BizProductionTask, BizProductionWorkOrder
# 找到引用此产品作为投入物的所有任务(id 或编码)
input_match = [BizProductionTaskInput.input_id == product_id]
if product_code:
input_match.append(BizProductionTaskInput.input_code == product_code)
input_rows = (
db.query(BizProductionTaskInput)
.filter(
BizProductionTaskInput.input_type == "cell_product",
BizProductionTaskInput.del_flag == 0,
or_(*input_match),
)
.all()
)
if not input_rows:
return []
task_ids = list({r.task_id for r in input_rows})
tasks = (
db.query(BizProductionTask)
.filter(BizProductionTask.id.in_(task_ids), BizProductionTask.del_flag == 0)
.all()
)
task_by_id = {t.id: t for t in tasks}
wo_ids = list({t.work_order_id for t in tasks if t.work_order_id})
wo_by_id: Dict[str, Any] = {}
if wo_ids:
wos = (
db.query(BizProductionWorkOrder)
.filter(BizProductionWorkOrder.id.in_(wo_ids), BizProductionWorkOrder.del_flag == 0)
.all()
)
wo_by_id = {w.id: w for w in wos}
# 对每个任务,找产出
result: List[Dict[str, Any]] = []
output_rows = (
db.query(BizProductionTaskOutput)
.filter(
BizProductionTaskOutput.task_id.in_(task_ids),
BizProductionTaskOutput.del_flag == 0,
)
.all()
)
child_product_ids = list({o.cell_product_id for o in output_rows if o.cell_product_id})
child_products = {}
if child_product_ids:
cps = (
db.query(BizCellProduct)
.filter(BizCellProduct.id.in_(child_product_ids), BizCellProduct.del_flag == 0)
.all()
)
child_products = {cp.id: cp for cp in cps}
seen_child_ids: set = set()
for o in output_rows:
cp = child_products.get(o.cell_product_id)
if not cp or cp.id in seen_child_ids:
continue
seen_child_ids.add(cp.id)
t = task_by_id.get(o.task_id)
wo_obj = wo_by_id.get(t.work_order_id) if t and t.work_order_id else None
edge = _edge_meta(t, wo_obj)
result.append({
"node": _product_node(cp),
"edge": edge,
"children": _build_descendant_chain(
db, cp.id, cp.product_code or "", depth + 1, tenant_no
),
})
return result
def get_product_chain(db: Session, product_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
product = get_cell_product(db, product_id, tenant_no)
if not product:
return None
return {
"current": _product_node(product, is_current=True),
"ancestors": _build_ancestor_chain(db, product, 0, tenant_no),
"descendants": _build_descendant_chain(
db, product.id, product.product_code or "", 0, tenant_no
),
}
+25 -2
View File
@@ -73,6 +73,7 @@ def _task_to_dict(t: BizProductionTask, *, work_order_no: str = "") -> Dict[str,
"id": t.id,
"taskNo": t.task_no,
"workOrderId": t.work_order_id,
"orderSource": getattr(t, "order_source", "inbound") or "inbound",
"workOrderNo": work_order_no,
"taskType": t.task_type or "cell_storage",
"taskName": t.task_name or "",
@@ -382,13 +383,30 @@ def _clone_route_to_task(
def create_production_task(
db: Session, *, tenant_no: str, user_id: Optional[str] = None,
create_dept_id: str = "", data: Dict[str, Any],
order_source: str = "inbound",
) -> BizProductionTask:
work_order_id = (data.get("work_order_id") or "").strip()
wo = None
if work_order_id:
wo = (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.id == work_order_id,
BizProductionWorkOrder.tenant_no == tenant_no,
BizProductionWorkOrder.del_flag == 0,
)
.first()
)
if wo and (getattr(wo, "work_order_type", None) or "inbound") == "temporary":
order_source = "temporary"
task_no = _generate_task_no(db, tenant_no)
now = datetime.now()
task = BizProductionTask(
id=generate_uuid(),
task_no=task_no,
work_order_id=data.get("work_order_id", ""),
order_source=order_source,
task_type=data.get("task_type") or "cell_storage",
task_name=(data.get("task_name") or "").strip() or None,
description=(data.get("description") or "").strip() or None,
@@ -417,6 +435,13 @@ def create_production_task(
production_work_order_svc.sync_production_work_order_status_from_tasks(
db, task.work_order_id or "", tenant_no, user_id
)
if wo and (getattr(wo, "work_order_type", None) or "inbound") == "temporary":
if not getattr(wo, "configured_time", None):
wo.configured_time = now
wo.configured_by = user_id
wo.update_by = user_id
wo.update_time = now
db.add(wo)
db.flush()
return task
@@ -579,11 +604,9 @@ def list_production_tasks(
if wo:
item["protocolNo"] = wo.protocol_no or ""
item["sampleCode"] = wo.sample_code or ""
# 接收人、接收时间
reception = reception_map.get(wo.reception_id) if wo.reception_id else None
item["receiverName"] = reception.handler_name or "" if reception else ""
item["receptionTime"] = _fmt_dt(reception.reception_time) if reception else ""
# 样本类型
item["sampleTypeName"] = sample_type_map.get(wo.protocol_sample_id, "") if wo.protocol_sample_id else ""
return items, int(total)
+370 -2
View File
@@ -1,21 +1,27 @@
"""入库生产工单 CRUD"""
"""入库生产工单 CRUD(含临时生产工单)"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import func, or_
from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.crud.collection_protocol import urls_to_attachment_list
from app.crud import sample_reception as sample_reception_svc
from app.crud import sample_reception_check_result as check_result_svc
from app.crud import production_task as production_task_svc
from app.models.models import (
BizCollectionProtocol,
BizProductionTask,
BizProductionTaskInput,
BizProductionTaskOutput,
BizProductionTaskStep,
BizProductionTaskStepConsumable,
BizProductionTaskStepDataCollection,
BizProductionTaskStepOutput,
BizProductionTaskStepReagent,
BizProductionWorkOrder,
BizProtocolSample,
BizSampleReception,
@@ -39,6 +45,18 @@ def _fmt_dt(t: Optional[datetime]) -> str:
return t.strftime("%Y-%m-%d %H:%M:%S") if t else ""
def _soft_delete_sub(db: Session, model_class, parent_field: str, parent_id: str, now: datetime) -> None:
rows = db.query(model_class).filter(
and_(
getattr(model_class, parent_field) == parent_id,
model_class.del_flag == 0,
)
).all()
for r in rows:
r.del_flag = 1
r.update_time = now
def _generate_work_order_no(db: Session, tenant_no: str) -> str:
today_str = datetime.now().strftime("%Y%m%d")
prefix = f"SC{today_str}"
@@ -62,6 +80,7 @@ def _wo_to_dict(wo: BizProductionWorkOrder) -> Dict[str, Any]:
return {
"id": wo.id,
"workOrderNo": wo.work_order_no,
"workOrderType": getattr(wo, "work_order_type", None) or "inbound",
"receptionId": wo.reception_id,
"protocolSampleId": wo.protocol_sample_id,
"collectionProtocolId": wo.collection_protocol_id,
@@ -276,6 +295,8 @@ def list_production_work_orders(
q0 = q0.filter(BizProductionWorkOrder.work_order_no.like(like))
if query.work_order_status and query.work_order_status.strip():
q0 = q0.filter(BizProductionWorkOrder.work_order_status == query.work_order_status.strip())
if query.work_order_type and query.work_order_type.strip():
q0 = q0.filter(BizProductionWorkOrder.work_order_type == query.work_order_type.strip())
if query.protocol_no and query.protocol_no.strip():
like = f"%{query.protocol_no.strip()}%"
q0 = q0.filter(BizProductionWorkOrder.protocol_no.like(like))
@@ -516,3 +537,350 @@ def get_production_info_map_for_samples(
if sid:
out[sid] = _production_info_item(db, wo)
return out
def create_production_work_order_manual(
db: Session,
*,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
protocol_no: str = "",
sample_code: str = "",
reception_source: str = "cell_storage",
remark: str = "",
) -> BizProductionWorkOrder:
"""手动创建生产工单(不关联接收记录)"""
wo = BizProductionWorkOrder(
id=generate_uuid(),
work_order_no=_generate_work_order_no(db, tenant_no),
work_order_type="inbound",
reception_id=None,
protocol_sample_id=None,
collection_protocol_id=None,
protocol_no=protocol_no or None,
sample_code=sample_code or None,
reception_source=reception_source,
work_order_status="dpz",
tenant_no=tenant_no,
remark=remark,
create_by=user_id,
create_dept_id=create_dept_id,
)
db.add(wo)
db.flush()
return wo
def delete_production_work_order(db: Session, work_order_id: str, tenant_no: str, user_id: Optional[str]) -> None:
wo = db.query(BizProductionWorkOrder).filter(
and_(
BizProductionWorkOrder.id == work_order_id,
BizProductionWorkOrder.tenant_no == tenant_no,
BizProductionWorkOrder.del_flag == 0,
)
).first()
if not wo:
raise ValueError("工单不存在")
if wo.work_order_status == "ywc":
raise ValueError("已完成的工单不能删除")
now = datetime.now()
tasks = db.query(BizProductionTask).filter(
and_(BizProductionTask.work_order_id == work_order_id, BizProductionTask.del_flag == 0)
).all()
for task in tasks:
steps = db.query(BizProductionTaskStep).filter(
and_(BizProductionTaskStep.task_id == task.id, BizProductionTaskStep.del_flag == 0)
).all()
for step in steps:
_soft_delete_sub(db, BizProductionTaskStepReagent, "step_id", step.id, now)
_soft_delete_sub(db, BizProductionTaskStepConsumable, "step_id", step.id, now)
_soft_delete_sub(db, BizProductionTaskStepOutput, "step_id", step.id, now)
_soft_delete_sub(db, BizProductionTaskStepDataCollection, "step_id", step.id, now)
step.del_flag = 1
step.update_by = user_id
step.update_time = now
_soft_delete_sub(db, BizProductionTaskInput, "task_id", task.id, now)
_soft_delete_sub(db, BizProductionTaskOutput, "task_id", task.id, now)
task.del_flag = 1
task.update_by = user_id
task.update_time = now
wo.del_flag = 1
wo.update_by = user_id
wo.update_time = now
db.commit()
# ---------------------------------------------------------------------------
# 临时生产工单(work_order_type='temporary')
# ---------------------------------------------------------------------------
def _generate_temp_work_order_no(db: Session, tenant_no: str) -> str:
today_str = datetime.now().strftime("%Y%m%d")
prefix = f"LSSC{today_str}"
last = (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.tenant_no == tenant_no,
BizProductionWorkOrder.work_order_no.like(f"{prefix}%"),
BizProductionWorkOrder.del_flag == 0,
)
.order_by(BizProductionWorkOrder.work_order_no.desc())
.first()
)
seq = 1
if last:
seq = int(last.work_order_no[-4:]) + 1
return f"{prefix}{seq:04d}"
def _temp_wo_to_dict(wo: BizProductionWorkOrder) -> Dict[str, Any]:
return {
"id": wo.id,
"workOrderNo": wo.work_order_no,
"workOrderStatus": wo.work_order_status,
"workOrderStatusLabel": _WORK_ORDER_STATUS_LABELS.get(wo.work_order_status, wo.work_order_status),
"configuredTime": _fmt_dt(getattr(wo, "configured_time", None)),
"configuredBy": getattr(wo, "configured_by", None) or "",
"remark": wo.remark or "",
"createTime": _fmt_dt(wo.create_time),
"updateTime": _fmt_dt(wo.update_time),
}
def create_temp_production_work_order(
db: Session,
*,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
remark: str = "",
) -> BizProductionWorkOrder:
wo = BizProductionWorkOrder(
id=generate_uuid(),
work_order_no=_generate_temp_work_order_no(db, tenant_no),
work_order_type="temporary",
work_order_status="dpz",
tenant_no=tenant_no,
remark=remark,
create_by=user_id,
create_dept_id=create_dept_id,
update_by=user_id,
create_time=datetime.now(),
update_time=datetime.now(),
del_flag=0,
)
db.add(wo)
db.flush()
return wo
def list_temp_production_work_orders(
db: Session,
*,
page: int = 1,
page_size: int = 10,
keyword: Optional[str] = None,
work_order_status: Optional[str] = None,
tenant_no: Optional[str] = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
q = db.query(BizProductionWorkOrder).filter(
BizProductionWorkOrder.del_flag == 0,
BizProductionWorkOrder.work_order_type == "temporary",
)
if tenant_no:
q = q.filter(BizProductionWorkOrder.tenant_no == tenant_no)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q = q.filter(BizProductionWorkOrder.work_order_no.like(like))
if work_order_status and work_order_status.strip():
q = q.filter(BizProductionWorkOrder.work_order_status == work_order_status.strip())
if user_info:
from app.core.data_scope import apply_data_scope
q = apply_data_scope(q, BizProductionWorkOrder, user_info, db, tenant_no or "")
total = q.order_by(None).with_entities(func.count(BizProductionWorkOrder.id)).scalar() or 0
page = max(1, page)
page_size = min(max(1, page_size), 100)
rows = (
q.order_by(BizProductionWorkOrder.create_time.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
items = [_temp_wo_to_dict(x) for x in rows]
return items, int(total)
def get_temp_production_work_order(
db: Session, row_id: str, tenant_no: str
) -> Optional[BizProductionWorkOrder]:
return (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.id == row_id,
BizProductionWorkOrder.tenant_no == tenant_no,
BizProductionWorkOrder.del_flag == 0,
BizProductionWorkOrder.work_order_type == "temporary",
)
.first()
)
def get_temp_production_work_order_detail(
db: Session, row_id: str, tenant_no: str
) -> Optional[Dict[str, Any]]:
wo = get_temp_production_work_order(db, row_id, tenant_no)
if not wo:
return None
result = _temp_wo_to_dict(wo)
tasks = (
db.query(BizProductionTask)
.filter(
BizProductionTask.work_order_id == row_id,
BizProductionTask.order_source == "temporary",
BizProductionTask.tenant_no == tenant_no,
BizProductionTask.del_flag == 0,
)
.order_by(BizProductionTask.order_num, BizProductionTask.create_time)
.all()
)
result["tasks"] = [production_task_svc._task_to_dict(t) for t in tasks]
return result
def configure_temp_production_work_order(
db: Session,
work_order_id: str,
*,
task_name: str,
task_type: str,
route_id: Optional[str],
description: str,
remark: str,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
) -> BizProductionWorkOrder:
wo = get_temp_production_work_order(db, work_order_id, tenant_no)
if not wo:
raise ValueError("工单不存在")
if wo.work_order_status != "dpz":
raise ValueError("工单已配置,不能重复配置")
production_task_svc.create_production_task(
db,
tenant_no=tenant_no,
user_id=user_id,
create_dept_id=create_dept_id,
data={
"work_order_id": work_order_id,
"task_name": task_name,
"task_type": task_type,
"route_id": route_id,
"description": description,
"remark": remark,
},
order_source="temporary",
)
now = datetime.now()
wo.work_order_status = "jxz"
wo.configured_time = now
wo.configured_by = user_id
wo.update_by = user_id
wo.update_time = now
db.add(wo)
db.flush()
return wo
def sync_temp_production_work_order_status(
db: Session,
work_order_id: str,
tenant_no: str,
user_id: Optional[str] = None,
) -> None:
if not (work_order_id or "").strip():
return
wo = get_temp_production_work_order(db, work_order_id.strip(), tenant_no)
if not wo:
return
tasks = (
db.query(BizProductionTask.task_status)
.filter(
BizProductionTask.work_order_id == work_order_id.strip(),
BizProductionTask.order_source == "temporary",
BizProductionTask.tenant_no == tenant_no,
BizProductionTask.del_flag == 0,
)
.all()
)
if not tasks:
new_status = "dpz"
elif all(t.task_status == "ywc" for t in tasks):
new_status = "ywc"
else:
new_status = "jxz"
if wo.work_order_status == new_status:
return
now = datetime.now()
wo.work_order_status = new_status
wo.update_by = user_id
wo.update_time = now
db.add(wo)
def delete_temp_production_work_order(
db: Session, work_order_id: str, tenant_no: str, user_id: Optional[str]
) -> None:
wo = get_temp_production_work_order(db, work_order_id, tenant_no)
if not wo:
raise ValueError("工单不存在")
if wo.work_order_status == "ywc":
raise ValueError("已完成的工单不能删除")
now = datetime.now()
tasks = db.query(BizProductionTask).filter(
and_(
BizProductionTask.work_order_id == work_order_id,
BizProductionTask.order_source == "temporary",
BizProductionTask.del_flag == 0,
)
).all()
for task in tasks:
steps = db.query(BizProductionTaskStep).filter(
and_(BizProductionTaskStep.task_id == task.id, BizProductionTaskStep.del_flag == 0)
).all()
for step in steps:
_soft_delete_sub(db, BizProductionTaskStepReagent, "step_id", step.id, now)
_soft_delete_sub(db, BizProductionTaskStepConsumable, "step_id", step.id, now)
_soft_delete_sub(db, BizProductionTaskStepOutput, "step_id", step.id, now)
_soft_delete_sub(db, BizProductionTaskStepDataCollection, "step_id", step.id, now)
step.del_flag = 1
step.update_by = user_id
step.update_time = now
_soft_delete_sub(db, BizProductionTaskInput, "task_id", task.id, now)
_soft_delete_sub(db, BizProductionTaskOutput, "task_id", task.id, now)
task.del_flag = 1
task.update_by = user_id
task.update_time = now
wo.del_flag = 1
wo.update_by = user_id
wo.update_time = now
db.commit()
+123 -9
View File
@@ -38,7 +38,7 @@ def _fmt_dt(t: Optional[datetime]) -> str:
return t.strftime("%Y-%m-%d %H:%M:%S") if t else ""
def _gen_task_no(db: Session, work_order: BizQcWorkOrder, seq: int) -> str:
def _gen_task_no(db: Session, work_order, seq: int) -> str:
return f"{work_order.work_order_no}-{seq:02d}"
@@ -66,6 +66,7 @@ def _task_to_dict(
return {
"id": t.id,
"workOrderId": t.work_order_id,
"orderSource": getattr(t, "order_source", "inbound") or "inbound",
"workOrderNo": work_order_no,
"taskNo": t.task_no,
"taskType": t.task_type,
@@ -90,15 +91,26 @@ def _task_to_dict(
def _task_work_order_context(wo: BizQcWorkOrder, t: BizQcTask) -> Dict[str, Any]:
biz = _work_order_qc_biz_type(wo)
source = getattr(wo, "reception_source", "cell_storage") or "cell_storage"
order_source = getattr(t, "order_source", "inbound") or "inbound"
wo_type = getattr(wo, "work_order_type", None) or "inbound"
if order_source == "temporary" or wo_type == "temporary":
recv_source = ""
else:
recv_source = getattr(wo, "reception_source", "cell_storage") or "cell_storage"
return {
"receptionId": wo.reception_id,
"protocolNo": wo.protocol_no or "",
"sampleCode": wo.sample_code or t.sample_code or "",
"qcBizType": biz,
"qcBizTypeLabel": _qc_biz_type_label(biz),
"receptionSource": source,
"receptionSourceLabel": _reception_source_label(source),
"receptionSource": recv_source,
"receptionSourceLabel": _reception_source_label(recv_source) if recv_source else "",
"cellProductId": getattr(wo, "cell_product_id", None) or "",
"cellProductCode": "",
"cellProductName": "",
"cellProductTypeName": "",
"cellProductPassage": "",
"cellProductSpecName": "",
"receptionTime": "",
"handlerName": "",
"sampleTypeName": "",
@@ -184,6 +196,10 @@ def _enrich_tasks_reception_fields(
):
donation_status_map[d.id] = d.donation_status or ""
for item in items:
if item.get("orderSource") == "temporary":
item["receptionSource"] = ""
item["receptionSourceLabel"] = ""
continue
rec = rec_map.get(item.get("receptionId") or "")
if not rec:
continue
@@ -236,14 +252,16 @@ def create_task_for_line(
company_id: Optional[str] = None,
company_name: Optional[str] = None,
fee_amount: Optional[Decimal] = None,
order_source: str = "inbound",
) -> BizQcTask:
obj = BizQcTask(
id=generate_uuid(),
work_order_id=work_order.id,
order_source=order_source,
task_no=_gen_task_no(db, work_order, seq),
task_type=task_type,
source_line_id=source_line_id,
sample_code=work_order.sample_code or None,
sample_code=getattr(work_order, "sample_code", None) or None,
item_code=item_code,
item_name=item_name,
company_id=company_id,
@@ -313,7 +331,15 @@ def list_tasks(
q0 = q0.filter(BizQcTask.task_status == st)
bt = (qc_biz_type or "").strip()
if bt:
q0 = q0.filter(BizQcWorkOrder.qc_biz_type == bt)
if bt == "temporary":
q0 = q0.filter(
or_(
BizQcWorkOrder.qc_biz_type == "temporary",
BizQcWorkOrder.work_order_type == "temporary",
)
)
else:
q0 = q0.filter(BizQcWorkOrder.qc_biz_type == bt)
if user_info:
from app.core.data_scope import apply_data_scope
q0 = apply_data_scope(q0, BizQcTask, user_info, db, tenant_no)
@@ -380,9 +406,13 @@ def get_task_detail(db: Session, row_id: str, tenant_no: str) -> Dict[str, Any]:
t, wo = row
out = _task_row_to_dict(t, wo, db, tenant_no)
out = _enrich_tasks_reception_fields(db, [out])[0]
reception_detail = sample_reception_svc.get_reception_by_id(db, wo.reception_id, tenant_no)
if reception_detail:
out["reception"] = reception_detail
from app.crud.qc_work_order import _enrich_work_order_cell_products
out = _enrich_work_order_cell_products(db, tenant_no, [out])[0]
if (out.get("qcBizType") or "") != "temporary":
reception_detail = sample_reception_svc.get_reception_by_id(db, wo.reception_id, tenant_no)
if reception_detail:
out["reception"] = reception_detail
return out
@@ -531,3 +561,87 @@ def update_task_attachments(
else:
db.flush()
return t
def create_qc_task_manual(
db: Session,
*,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
task_type: str = "sysz",
item_code: str = "",
item_name: str = "",
company_id: Optional[str] = None,
company_name: Optional[str] = None,
fee_amount: Optional[float] = None,
sample_code: str = "",
work_order_id: str = "",
remark: str = "",
) -> BizQcTask:
"""手动创建质检任务(可独立于工单)"""
# 生成任务号:ZJRW + 日期 + 序号
day = datetime.now().strftime("%Y%m%d")
like = f"ZJRW{day}%"
cnt = db.query(BizQcTask).filter(
BizQcTask.tenant_no == tenant_no,
BizQcTask.task_no.like(like),
).count()
task_no = f"ZJRW{day}{cnt + 1:04d}"
task = BizQcTask(
id=generate_uuid(),
work_order_id=work_order_id or "",
task_no=task_no,
task_type=task_type,
source_line_id="",
sample_code=sample_code,
item_code=item_code,
item_name=item_name,
company_id=company_id,
company_name=company_name,
fee_amount=fee_amount,
task_status="dzx",
tenant_no=tenant_no,
remark=remark,
create_by=user_id,
create_dept_id=create_dept_id,
)
db.add(task)
db.flush()
return task
def delete_qc_task(db: Session, task_id: str, tenant_no: str, user_id: Optional[str]) -> None:
"""软删除质检任务及其关联的检测结果"""
task = db.query(BizQcTask).filter(
and_(
BizQcTask.id == task_id,
BizQcTask.tenant_no == tenant_no,
BizQcTask.del_flag == 0,
)
).first()
if not task:
raise ValueError("任务不存在")
if task.task_status != "dzx":
raise ValueError("仅待执行的任务可以删除")
now = datetime.now()
# 软删除任务结果
from app.models.qc_param import BizQcTaskResult
results = db.query(BizQcTaskResult).filter(
and_(BizQcTaskResult.task_id == task_id, BizQcTaskResult.del_flag == 0)
).all()
for r in results:
r.del_flag = 1
r.update_by = user_id
r.update_time = now
# 软删除任务本体
task.del_flag = 1
task.update_by = user_id
task.update_time = now
db.commit()
+682 -6
View File
@@ -16,7 +16,9 @@ from app.crud.qc_third_party_company import get_company
from app.crud.collection_protocol import urls_to_attachment_list
from app.crud import sample_reception as sample_reception_svc
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.models.models import (
BizCellProduct,
BizCellSampleType,
BizCollectionProtocol,
BizProtocolSample,
@@ -38,6 +40,7 @@ _RECEPTION_SOURCE_LABELS = {
_QC_BIZ_TYPE_LABELS = {
"inbound": "入库质检",
"outbound": "出库质检",
"temporary": "临时质检",
}
_ACCEPT_CONCLUSION_LABELS = {"js": "接收", "bj": "拒收"}
_WORK_ORDER_STATUS_LABELS = {"dpz": "待配置", "ypz": "已配置", "ywc": "已完成"}
@@ -73,9 +76,53 @@ def _qc_biz_type_label(biz_type: Optional[str]) -> str:
def _work_order_qc_biz_type(wo: BizQcWorkOrder) -> str:
wo_type = getattr(wo, "work_order_type", None) or "inbound"
if wo_type == "temporary":
return "temporary"
return getattr(wo, "qc_biz_type", None) or "inbound"
def _validate_cell_product_id(
db: Session, cell_product_id: Optional[str], tenant_no: str
) -> str:
cp_id = (cell_product_id or "").strip()
if not cp_id:
raise ValueError("请选择质检对象(细胞产品)")
cp = get_cell_product(db, cp_id, tenant_no)
if not cp:
raise ValueError("细胞产品不存在")
return cp_id
def _enrich_work_order_cell_products(
db: Session, tenant_no: str, items: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
if not items:
return items
cp_ids = list({x.get("cellProductId") for x in items if x.get("cellProductId")})
if not cp_ids:
return 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.get("cellProductId") or "")
item["cellProductCode"] = cp.get("productCode", "") if cp else ""
item["cellProductName"] = cp.get("productName", "") if cp else ""
item["cellProductTypeName"] = cp.get("productTypeName", "") if cp else ""
item["cellProductPassage"] = cp.get("passage", "") if cp else ""
item["cellProductSpecName"] = cp.get("specName", "") if cp else ""
return items
def _accept_conclusion_label(v: Optional[str]) -> str:
key = (v or "").strip()
return _ACCEPT_CONCLUSION_LABELS.get(key, key)
@@ -188,6 +235,10 @@ def _enrich_work_order_list_items(
):
donation_status_map[d.id] = d.donation_status or ""
for item in items:
if item.get("workOrderType") == "temporary":
item["receptionSource"] = ""
item["receptionSourceLabel"] = ""
continue
rec = rec_map.get(item.get("receptionId") or "")
source = item.get("receptionSource") or (rec.reception_source if rec else "cell_storage")
item["receptionSourceLabel"] = _reception_source_label(source)
@@ -233,24 +284,38 @@ def _gen_daily_no(db: Session, tenant_no: str, prefix: str) -> str:
def _wo_to_dict(wo: BizQcWorkOrder) -> Dict[str, Any]:
wo_type = getattr(wo, "work_order_type", None) or "inbound"
recv_source_raw = getattr(wo, "reception_source", None)
if wo_type == "temporary":
recv_source = (recv_source_raw or "").strip()
else:
recv_source = (recv_source_raw or "cell_storage").strip() or "cell_storage"
return {
"id": wo.id,
"workOrderNo": wo.work_order_no,
"workOrderType": wo_type,
"receptionId": wo.reception_id,
"protocolSampleId": wo.protocol_sample_id,
"collectionProtocolId": wo.collection_protocol_id,
"protocolNo": wo.protocol_no or "",
"sampleCode": wo.sample_code or "",
"receptionSource": getattr(wo, "reception_source", "cell_storage") or "cell_storage",
"receptionSourceLabel": _reception_source_label(
getattr(wo, "reception_source", "cell_storage") or "cell_storage"
),
"receptionSource": recv_source,
"receptionSourceLabel": _reception_source_label(recv_source) if recv_source else "",
"qcBizType": _work_order_qc_biz_type(wo),
"qcBizTypeLabel": _qc_biz_type_label(_work_order_qc_biz_type(wo)),
"cellProductId": getattr(wo, "cell_product_id", None) or "",
"cellProductCode": "",
"cellProductName": "",
"cellProductTypeName": "",
"cellProductPassage": "",
"cellProductSpecName": "",
"receptionTime": "",
"handlerName": "",
"sampleTypeName": "",
"workOrderStatus": wo.work_order_status,
"workOrderStatusLabel": _WORK_ORDER_STATUS_LABELS.get(
wo.work_order_status or "", wo.work_order_status or ""
),
"hasLab": wo.has_lab == "1",
"hasThirdParty": wo.has_third_party == "1",
"configuredTime": _fmt_dt(wo.configured_time),
@@ -384,6 +449,7 @@ def list_work_orders(
page_size: int = 10,
keyword: Optional[str] = None,
work_order_status: Optional[str] = None,
work_order_type: Optional[str] = None,
tenant_no: Optional[str] = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
@@ -391,6 +457,9 @@ def list_work_orders(
q0 = db.query(BizQcWorkOrder).filter(BizQcWorkOrder.del_flag == 0)
if tenant_no:
q0 = q0.filter(BizQcWorkOrder.tenant_no == tenant_no)
wt = (work_order_type or "").strip()
if wt:
q0 = q0.filter(BizQcWorkOrder.work_order_type == wt)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
@@ -420,7 +489,10 @@ def list_work_orders(
.all()
)
items = [_wo_to_dict(x) for x in rows]
return _enrich_work_order_list_items(db, items), int(total)
items = _enrich_work_order_list_items(db, items)
if tenant_no:
items = _enrich_work_order_cell_products(db, tenant_no, items)
return items, int(total)
def get_work_order(
@@ -568,13 +640,21 @@ def get_work_order_detail(
if not wo:
raise ValueError("工单不存在")
out = _wo_to_dict(wo)
enriched = _enrich_work_order_list_items(db, [out])[0]
enriched_list = _enrich_work_order_list_items(db, [out])
if tenant_no:
enriched_list = _enrich_work_order_cell_products(db, tenant_no, enriched_list)
enriched = enriched_list[0]
out.update(
{
"receptionTime": enriched.get("receptionTime", ""),
"handlerName": enriched.get("handlerName", ""),
"sampleTypeName": enriched.get("sampleTypeName", ""),
"receptionSourceLabel": enriched.get("receptionSourceLabel", ""),
"cellProductCode": enriched.get("cellProductCode", ""),
"cellProductName": enriched.get("cellProductName", ""),
"cellProductTypeName": enriched.get("cellProductTypeName", ""),
"cellProductPassage": enriched.get("cellProductPassage", ""),
"cellProductSpecName": enriched.get("cellProductSpecName", ""),
}
)
reception_detail = sample_reception_svc.get_reception_by_id(db, wo.reception_id, tenant_no)
@@ -664,6 +744,7 @@ def configure_work_order(
raise ValueError("工单已完成,无法调整检测项目")
if wo.work_order_status not in ("dpz", "ypz"):
raise ValueError("当前工单状态不可配置检测项目")
order_source = "temporary" if (getattr(wo, "work_order_type", None) or "inbound") == "temporary" else "inbound"
if wo.work_order_status == "ypz":
_soft_delete_pending_work_order_tasks(db, wo.id, tenant_no)
@@ -703,6 +784,7 @@ def configure_work_order(
line = BizQcWorkOrderLabItem(
id=generate_uuid(),
work_order_id=wo.id,
order_source=order_source,
lab_test_item_id=item.id,
item_code=item.item_code,
item_name=item.item_name,
@@ -728,6 +810,7 @@ def configure_work_order(
user_id=user_id,
create_dept_id=create_dept_id,
seq=seq,
order_source=order_source,
)
for tid in third_party_item_ids:
@@ -743,6 +826,7 @@ def configure_work_order(
line = BizQcWorkOrderThirdItem(
id=generate_uuid(),
work_order_id=wo.id,
order_source=order_source,
third_party_item_id=item.id,
company_id=comp.id,
company_name=comp.company_name,
@@ -774,6 +858,7 @@ def configure_work_order(
company_id=comp.id,
company_name=comp.company_name,
fee_amount=fee,
order_source=order_source,
)
wo.has_lab = "1" if has_lab else "0"
@@ -804,8 +889,599 @@ def try_complete_work_order(db: Session, work_order_id: str, tenant_no: str) ->
if pending:
return
wo = get_work_order(db, work_order_id, tenant_no=tenant_no)
if wo and wo.work_order_status == "ypz":
wo.work_order_status = "ywc"
wo.update_time = datetime.now()
db.add(wo)
db.commit()
# ---------- 临时质检工单 ----------
def _temp_lab_lines(db: Session, work_order_id: str) -> List[Dict[str, Any]]:
rows = (
db.query(BizQcWorkOrderLabItem)
.filter(
BizQcWorkOrderLabItem.work_order_id == work_order_id,
BizQcWorkOrderLabItem.order_source == "temporary",
BizQcWorkOrderLabItem.del_flag == 0,
)
.order_by(BizQcWorkOrderLabItem.order_num.asc())
.all()
)
return [
{
"id": r.id,
"labTestItemId": r.lab_test_item_id,
"itemCode": r.item_code,
"itemName": r.item_name,
}
for r in rows
]
def _temp_third_lines(db: Session, work_order_id: str) -> List[Dict[str, Any]]:
rows = (
db.query(BizQcWorkOrderThirdItem)
.filter(
BizQcWorkOrderThirdItem.work_order_id == work_order_id,
BizQcWorkOrderThirdItem.order_source == "temporary",
BizQcWorkOrderThirdItem.del_flag == 0,
)
.order_by(BizQcWorkOrderThirdItem.order_num.asc())
.all()
)
return [
{
"id": r.id,
"thirdPartyItemId": r.third_party_item_id,
"companyId": r.company_id,
"companyName": r.company_name,
"itemCode": r.item_code,
"itemName": r.item_name,
"feeAmount": _dec(r.fee_amount),
}
for r in rows
]
def _temp_tasks(db: Session, work_order_id: str, tenant_no: str) -> List[Dict[str, Any]]:
rows = (
db.query(BizQcTask)
.filter(
BizQcTask.work_order_id == work_order_id,
BizQcTask.order_source == "temporary",
BizQcTask.tenant_no == tenant_no,
BizQcTask.del_flag == 0,
)
.order_by(BizQcTask.order_num.asc())
.all()
)
items = []
for t in rows:
d = task_svc._task_to_dict(t, "", db, tenant_no)
tt = (t.task_type or "").strip()
ts = (t.task_status or "").strip()
tc = (t.conclusion or "").strip()
d["taskTypeLabel"] = _TASK_TYPE_LABELS.get(tt, tt)
d["taskStatusLabel"] = _TASK_STATUS_LABELS.get(ts, ts)
d["conclusionLabel"] = _CONCLUSION_LABELS.get(tc, tc or "—")
items.append(d)
return items
def create_temp_qc_work_order(
db: Session,
*,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
cell_product_id: str = "",
remark: str = "",
) -> BizQcWorkOrder:
"""创建临时质检工单(work_order_type='temporary',LSZJ前缀编号)"""
cp_id = _validate_cell_product_id(db, cell_product_id, tenant_no)
wo = BizQcWorkOrder(
id=generate_uuid(),
work_order_no=_gen_daily_no(db, tenant_no, "LSZJ"),
work_order_type="temporary",
reception_id=None,
protocol_sample_id=None,
collection_protocol_id=None,
protocol_no=None,
sample_code=None,
reception_source=None,
qc_biz_type="temporary",
cell_product_id=cp_id,
work_order_status="dpz",
has_lab="0",
has_third_party="0",
tenant_no=tenant_no,
remark=remark,
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 update_temp_qc_work_order(
db: Session,
work_order_id: str,
*,
tenant_no: str,
user_id: Optional[str] = None,
cell_product_id: Optional[str] = None,
remark: Optional[str] = None,
) -> BizQcWorkOrder:
"""编辑临时质检工单(备注、质检对象)"""
wo = get_work_order(db, work_order_id, tenant_no=tenant_no)
if not wo:
raise ValueError("工单不存在")
if (getattr(wo, "work_order_type", None) or "inbound") != "temporary":
raise ValueError("仅临时质检工单可编辑")
if cell_product_id is not None:
wo.cell_product_id = _validate_cell_product_id(db, cell_product_id, tenant_no)
if remark is not None:
wo.remark = (remark or "").strip() or None
wo.update_by = user_id
wo.update_time = datetime.now()
db.add(wo)
db.flush()
return wo
def list_temp_qc_work_orders(
db: Session,
*,
page: int = 1,
page_size: int = 10,
keyword: Optional[str] = None,
work_order_status: Optional[str] = None,
tenant_no: Optional[str] = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
def _filtered():
q0 = db.query(BizQcWorkOrder).filter(
BizQcWorkOrder.del_flag == 0,
BizQcWorkOrder.work_order_type == "temporary",
)
if tenant_no:
q0 = q0.filter(BizQcWorkOrder.tenant_no == tenant_no)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q0 = q0.filter(BizQcWorkOrder.work_order_no.like(like))
st = (work_order_status or "").strip()
if st:
q0 = q0.filter(BizQcWorkOrder.work_order_status == st)
if user_info:
from app.core.data_scope import apply_data_scope
q0 = apply_data_scope(q0, BizQcWorkOrder, user_info, db, tenant_no)
return q0
total = _filtered().order_by(None).with_entities(func.count(BizQcWorkOrder.id)).scalar() or 0
page = max(1, page)
page_size = min(max(1, page_size), 100)
rows = (
_filtered()
.order_by(BizQcWorkOrder.create_time.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
items = [_wo_to_dict(x) for x in rows]
if tenant_no:
items = _enrich_work_order_cell_products(db, tenant_no, items)
return items, int(total)
def get_temp_qc_work_order(
db: Session, row_id: str, tenant_no: str
) -> Optional[BizQcWorkOrder]:
q = db.query(BizQcWorkOrder).filter(
BizQcWorkOrder.id == row_id,
BizQcWorkOrder.del_flag == 0,
BizQcWorkOrder.work_order_type == "temporary",
)
if tenant_no:
q = q.filter(BizQcWorkOrder.tenant_no == tenant_no)
return q.first()
def get_temp_qc_work_order_detail(
db: Session, row_id: str, tenant_no: str
) -> Optional[Dict[str, Any]]:
wo = get_temp_qc_work_order(db, row_id, tenant_no)
if not wo:
return None
out = _wo_to_dict(wo)
enriched_list = _enrich_work_order_cell_products(db, tenant_no, [out])
out = enriched_list[0]
out["labItems"] = _temp_lab_lines(db, wo.id)
out["thirdPartyItems"] = _temp_third_lines(db, wo.id)
out["tasks"] = _temp_tasks(db, wo.id, tenant_no)
return out
def configure_temp_qc_work_order(
db: Session,
work_order_id: str,
*,
has_lab: bool,
has_third_party: bool,
lab_item_ids: List[str],
third_party_item_ids: List[str],
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
) -> BizQcWorkOrder:
wo = get_temp_qc_work_order(db, work_order_id, tenant_no)
if not wo:
raise ValueError("工单不存在")
if wo.work_order_status == "ywc":
raise ValueError("工单已完成,无法调整检测项目")
if wo.work_order_status not in ("dpz", "ypz"):
raise ValueError("当前工单状态不可配置检测项目")
if wo.work_order_status == "ypz":
tasks = (
db.query(BizQcTask)
.filter(
BizQcTask.work_order_id == wo.id,
BizQcTask.order_source == "temporary",
BizQcTask.tenant_no == tenant_no,
BizQcTask.del_flag == 0,
)
.all()
)
if any(t.task_status in ("jxz", "ywc") for t in tasks):
raise ValueError("存在进行中或已完成的质检任务,无法调整检测项目")
now = datetime.now()
task_ids = [t.id for t in tasks]
if task_ids:
db.query(BizQcTaskResult).filter(
BizQcTaskResult.task_id.in_(task_ids),
BizQcTaskResult.tenant_no == tenant_no,
BizQcTaskResult.del_flag == 0,
).update(
{"del_flag": 1, "update_time": now},
synchronize_session="fetch",
)
for t in tasks:
t.del_flag = 1
t.update_time = now
db.add(t)
if not has_lab and not has_third_party:
raise ValueError("请至少选择一种检测方式")
if has_lab and not lab_item_ids:
raise ValueError("请选择实验室自检项目")
if has_third_party and not third_party_item_ids:
raise ValueError("请选择第三方检测项目")
if not has_lab:
lab_item_ids = []
if not has_third_party:
third_party_item_ids = []
for row in (
db.query(BizQcWorkOrderLabItem)
.filter(
BizQcWorkOrderLabItem.work_order_id == wo.id,
BizQcWorkOrderLabItem.order_source == "temporary",
BizQcWorkOrderLabItem.del_flag == 0,
)
.all()
):
row.del_flag = 1
for row in (
db.query(BizQcWorkOrderThirdItem)
.filter(
BizQcWorkOrderThirdItem.work_order_id == wo.id,
BizQcWorkOrderThirdItem.order_source == "temporary",
BizQcWorkOrderThirdItem.del_flag == 0,
)
.all()
):
row.del_flag = 1
fee_total = Decimal("0")
seq = 0
for lid in lab_item_ids:
item = lab_item_svc.get_lab_item(db, lid, tenant_no=tenant_no)
if not item or item.status != "1":
raise ValueError("实验室项目无效或已停用")
seq += 1
line = BizQcWorkOrderLabItem(
id=generate_uuid(),
work_order_id=wo.id,
order_source="temporary",
lab_test_item_id=item.id,
item_code=item.item_code,
item_name=item.item_name,
tenant_no=tenant_no,
order_num=seq,
create_by=user_id,
create_dept_id=create_dept_id or "",
create_time=datetime.now(),
update_by=user_id,
update_time=datetime.now(),
del_flag=0,
)
db.add(line)
db.flush()
task_svc.create_task_for_line(
db,
work_order=wo,
task_type="sysz",
source_line_id=line.id,
item_code=line.item_code,
item_name=line.item_name,
tenant_no=tenant_no,
user_id=user_id,
create_dept_id=create_dept_id,
seq=seq,
order_source="temporary",
)
for tid in third_party_item_ids:
item = third_item_svc.get_item(db, tid, tenant_no=tenant_no)
if not item or item.status != "1":
raise ValueError("第三方项目无效或已停用")
comp = get_company(db, item.company_id, tenant_no=tenant_no)
if not comp:
raise ValueError("第三方公司不存在")
seq += 1
fee = item.fee_amount or Decimal("0")
fee_total += fee
line = BizQcWorkOrderThirdItem(
id=generate_uuid(),
work_order_id=wo.id,
order_source="temporary",
third_party_item_id=item.id,
company_id=comp.id,
company_name=comp.company_name,
item_code=item.item_code,
item_name=item.item_name,
fee_amount=fee,
tenant_no=tenant_no,
order_num=seq,
create_by=user_id,
create_dept_id=create_dept_id or "",
create_time=datetime.now(),
update_by=user_id,
update_time=datetime.now(),
del_flag=0,
)
db.add(line)
db.flush()
task_svc.create_task_for_line(
db,
work_order=wo,
task_type="dsfjc",
source_line_id=line.id,
item_code=line.item_code,
item_name=line.item_name,
tenant_no=tenant_no,
user_id=user_id,
create_dept_id=create_dept_id,
seq=seq,
company_id=comp.id,
company_name=comp.company_name,
fee_amount=fee,
order_source="temporary",
)
wo.has_lab = "1" if has_lab else "0"
wo.has_third_party = "1" if has_third_party else "0"
wo.work_order_status = "ypz"
wo.configured_time = datetime.now()
wo.configured_by = user_id
wo.third_party_fee_total = fee_total if has_third_party else None
wo.update_by = user_id
wo.update_time = datetime.now()
db.add(wo)
db.commit()
db.refresh(wo)
return wo
def try_complete_temp_qc_work_order(
db: Session, work_order_id: str, tenant_no: str
) -> None:
pending = (
db.query(BizQcTask)
.filter(
BizQcTask.work_order_id == work_order_id,
BizQcTask.order_source == "temporary",
BizQcTask.tenant_no == tenant_no,
BizQcTask.del_flag == 0,
BizQcTask.task_status != "ywc",
)
.count()
)
if pending:
return
wo = get_temp_qc_work_order(db, work_order_id, tenant_no)
if wo and wo.work_order_status == "ypz":
wo.work_order_status = "ywc"
wo.update_time = datetime.now()
db.add(wo)
db.commit()
def delete_temp_qc_work_order(
db: Session, work_order_id: str, tenant_no: str, user_id: Optional[str]
) -> None:
wo = get_temp_qc_work_order(db, work_order_id, tenant_no)
if not wo:
raise ValueError("工单不存在")
if wo.work_order_status == "ywc":
raise ValueError("已完成的工单不能删除")
now = datetime.now()
tasks = (
db.query(BizQcTask)
.filter(
BizQcTask.work_order_id == work_order_id,
BizQcTask.order_source == "temporary",
BizQcTask.del_flag == 0,
)
.all()
)
for task in tasks:
results = (
db.query(BizQcTaskResult)
.filter(
BizQcTaskResult.task_id == task.id,
BizQcTaskResult.del_flag == 0,
)
.all()
)
for r in results:
r.del_flag = 1
r.update_by = user_id
r.update_time = now
task.del_flag = 1
task.update_by = user_id
task.update_time = now
lab_items = (
db.query(BizQcWorkOrderLabItem)
.filter(
BizQcWorkOrderLabItem.work_order_id == work_order_id,
BizQcWorkOrderLabItem.order_source == "temporary",
BizQcWorkOrderLabItem.del_flag == 0,
)
.all()
)
for item in lab_items:
item.del_flag = 1
item.update_by = user_id
item.update_time = now
third_items = (
db.query(BizQcWorkOrderThirdItem)
.filter(
BizQcWorkOrderThirdItem.work_order_id == work_order_id,
BizQcWorkOrderThirdItem.order_source == "temporary",
BizQcWorkOrderThirdItem.del_flag == 0,
)
.all()
)
for item in third_items:
item.del_flag = 1
item.update_by = user_id
item.update_time = now
wo.del_flag = 1
wo.update_by = user_id
wo.update_time = now
db.commit()
def create_qc_work_order_manual(
db: Session,
*,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
protocol_no: str = "",
sample_code: str = "",
reception_source: str = "cell_storage",
qc_biz_type: str = "inbound",
remark: str = "",
) -> BizQcWorkOrder:
"""手动创建质检工单(不关联接收记录)"""
wo = BizQcWorkOrder(
id=generate_uuid(),
work_order_no=_gen_daily_no(db, tenant_no, "ZJ"),
work_order_type="inbound",
reception_id=None,
protocol_sample_id=None,
collection_protocol_id=None,
protocol_no=protocol_no or None,
sample_code=sample_code or None,
reception_source=reception_source,
qc_biz_type=qc_biz_type,
work_order_status="dpz",
has_lab="0",
has_third_party="0",
tenant_no=tenant_no,
remark=remark,
create_by=user_id,
create_dept_id=create_dept_id,
)
db.add(wo)
db.flush()
return wo
def delete_qc_work_order(db: Session, work_order_id: str, tenant_no: str, user_id: Optional[str]) -> None:
"""软删除质检工单及其关联的任务、任务结果、检测项目和第三方检测项目"""
wo = db.query(BizQcWorkOrder).filter(
and_(
BizQcWorkOrder.id == work_order_id,
BizQcWorkOrder.tenant_no == tenant_no,
BizQcWorkOrder.del_flag == 0,
)
).first()
if not wo:
raise ValueError("工单不存在")
if wo.work_order_status == "ywc":
raise ValueError("已完成的工单不能删除")
now = datetime.now()
# 1. 软删除所有关联任务及其结果
tasks = db.query(BizQcTask).filter(
and_(BizQcTask.work_order_id == work_order_id, BizQcTask.del_flag == 0)
).all()
from app.models.qc_param import BizQcTaskResult
for task in tasks:
results = db.query(BizQcTaskResult).filter(
and_(BizQcTaskResult.task_id == task.id, BizQcTaskResult.del_flag == 0)
).all()
for r in results:
r.del_flag = 1
r.update_by = user_id
r.update_time = now
task.del_flag = 1
task.update_by = user_id
task.update_time = now
# 2. 软删除检测项目
lab_items = db.query(BizQcWorkOrderLabItem).filter(
and_(BizQcWorkOrderLabItem.work_order_id == work_order_id, BizQcWorkOrderLabItem.del_flag == 0)
).all()
for item in lab_items:
item.del_flag = 1
item.update_by = user_id
item.update_time = now
# 3. 软删除第三方检测项目
third_items = db.query(BizQcWorkOrderThirdItem).filter(
and_(BizQcWorkOrderThirdItem.work_order_id == work_order_id, BizQcWorkOrderThirdItem.del_flag == 0)
).all()
for item in third_items:
item.del_flag = 1
item.update_by = user_id
item.update_time = now
# 4. 软删除工单本体
wo.del_flag = 1
wo.update_by = user_id
wo.update_time = now
db.commit()
@@ -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_qcworkordercreatebody", "label": "QcWorkOrderCreateBody", "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": ""}], "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_qcworkordercreatebody", "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_qcworkordercreatebody", "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}], "raw_calls": []}
@@ -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": "L35"}, {"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": "L52"}, {"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": "L70"}, {"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": "L92"}, {"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": "L106"}, {"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": "L140"}, {"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": "L176"}, {"id": "api_qc_thirdpartyitembody", "label": "ThirdPartyItemBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L196"}, {"id": "api_qc_thirdpartyitemupdatebody", "label": "ThirdPartyItemUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L208"}, {"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": "L221"}, {"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": "L250"}, {"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": "L265"}, {"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": "L294"}, {"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": "L325"}, {"id": "api_qc_labtestitembody", "label": "LabTestItemBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L345"}, {"id": "api_qc_labtestitemupdatebody", "label": "LabTestItemUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L356"}, {"id": "api_qc_itemparambody", "label": "ItemParamBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L370"}, {"id": "api_qc_taskresultbody", "label": "TaskResultBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L375"}, {"id": "api_qc_taskcompletewithresultbody", "label": "TaskCompleteWithResultBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L380"}, {"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": "L390"}, {"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": "L412"}, {"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": "L426"}, {"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": "L454"}, {"id": "api_qLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_crud_production_work_order_py", "label": "production_work_order.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L1"}, {"id": "crud_production_work_order_fmt_dt", "label": "_fmt_dt()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L38"}, {"id": "crud_production_work_order_generate_work_order_no", "label": "_generate_work_order_no()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L42"}, {"id": "crud_production_work_order_wo_to_dict", "label": "_wo_to_dict()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L61"}, {"id": "crud_production_work_order_enrich_wo_items", "label": "_enrich_wo_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L86"}, {"id": "crud_production_work_order_reception_source_label", "label": "_reception_source_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L92"}, {"id": "crud_production_work_order_accept_conclusion_label", "label": "_accept_conclusion_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L97"}, {"id": "crud_production_work_order_attach_reception_detail", "label": "_attach_reception_detail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L102"}, {"id": "crud_production_work_order_wo_to_response_dict", "label": "_wo_to_response_dict()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L132"}, {"id": "crud_production_work_order_create_production_work_order_from_reception", "label": "create_production_work_order_from_reception()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L139"}, {"id": "crud_production_work_order_list_production_work_orders", "label": "list_production_work_orders()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L250"}, {"id": "crud_production_work_order_sync_production_work_order_status_from_tasks", "label": "sync_production_work_order_status_from_tasks()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L307"}, {"id": "crud_production_work_order_get_production_work_order", "label": "get_production_work_order()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L347"}, {"id": "crud_production_work_order_complete_production_work_order", "label": "complete_production_work_order()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L361"}, {"id": "crud_production_work_order_production_task_info", "label": "_production_task_info()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L392"}, {"id": "crud_production_work_order_production_info_item", "label": "_production_info_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L439"}, {"id": "crud_production_work_order_get_production_info_map_for_samples", "label": "get_production_info_map_for_samples()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L459"}, {"id": "crud_production_work_order_create_production_work_order_manual", "label": "create_production_work_order_manual()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L521"}, {"id": "crud_production_work_order_rationale_105", "label": "\u8be6\u60c5\u54cd\u5e94\u9644\u52a0\u8fd0\u8f93\u4e0e\u6837\u672c\u63a5\u6536\u4fe1\u606f\uff08\u4e0e\u8d28\u68c0\u5de5\u5355\u8be6\u60c5\u4e00\u81f4\uff09\u3002", "file_type": "rationale", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py", "source_location": "L105"}, {"id": "crud_production_work_order_rationale_133", "label": "\u5de5\u5355\u54cd\u5e94\uff08\u542b\u63a5\u6536\u8bb0\u5f55\u5173\u8054\u7684\u6837\u672c\u7c7b\u578b\u3001\u63a5\u6536\u4eba\u3001\u63Line 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_dec", "label": "_dec()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L55"}, {"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": "L61"}, {"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": "L65"}, {"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": "L70"}, {"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": "L75"}, {"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": "L79"}, {"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": "L84"}, {"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": "L96"}, {"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": "L113"}, {"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": "L221"}, {"id": "crud_qc_work_order_wo_to_dict", "label": "_wo_to_dict()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L235"}, {"id": "crud_qc_work_order_create_work_order_from_reception", "label": "create_work_order_from_reception()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L265"}, {"id": "crud_qc_work_order_list_work_orders", "label": "list_work_orders()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L380"}, {"id": "crud_qc_work_order_get_work_order", "label": "get_work_order()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L426"}, {"id": "crud_qc_work_order_lab_lines", "label": "_lab_lines()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L435"}, {"id": "crud_qc_work_order_third_lines", "label": "_third_lines()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L456"}, {"id": "crud_qc_work_order_qc_info_item", "label": "_qc_info_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L480"}, {"id": "crud_qc_work_order_get_qc_info_map_for_samples", "label": "get_qc_info_map_for_samples()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L501"}, {"id": "crud_qc_work_order_get_work_order_detail", "label": "get_work_order_detail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L564"}, {"id": "crud_qc_work_order_soft_delete_pending_work_order_tasks", "label": "_soft_delete_pending_work_order_tasks()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L614"}, {"id": "crud_qc_work_order_configure_work_order", "label": "configure_work_order()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L648"}, {"id": "crud_qc_work_order_try_complete_work_order", "label": "try_complete_work_order()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.pLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "label": "cell_product.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L1"}, {"id": "crud_cell_product_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L24"}, {"id": "crud_cell_product_build_facility_path_label", "label": "_build_facility_path_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L57"}, {"id": "crud_cell_product_enrich_product_dicts", "label": "_enrich_product_dicts()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L72"}, {"id": "crud_cell_product_product_to_response", "label": "product_to_response()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L203"}, {"id": "crud_cell_product_list_cell_products", "label": "list_cell_products()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L208"}, {"id": "crud_cell_product_get_cell_product", "label": "get_cell_product()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L272"}, {"id": "crud_cell_product_create_cell_product", "label": "create_cell_product()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L286"}, {"id": "crud_cell_product_update_cell_product", "label": "update_cell_product()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L366"}, {"id": "crud_cell_product_delete_cell_product", "label": "delete_cell_product()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L428"}, {"id": "crud_cell_product_next_code_start_seq", "label": "_next_code_start_seq()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L444"}, {"id": "crud_cell_product_get_next_code", "label": "get_next_code()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L465"}, {"id": "crud_cell_product_normalize_product_code_base", "label": "normalize_product_code_base()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L475"}, {"id": "crud_cell_product_build_split_product_codes", "label": "build_split_product_codes()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L486"}, {"id": "crud_cell_product_reserve_next_codes", "label": "reserve_next_codes()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L493"}, {"id": "crud_cell_product_validate_batch_items", "label": "_validate_batch_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L503"}, {"id": "crud_cell_product_create_cell_products_by_quantity", "label": "create_cell_products_by_quantity()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L546"}, {"id": "crud_cell_product_create_cell_products_batch", "label": "create_cell_products_batch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L578"}, {"id": "crud_cell_product_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L660"}, {"id": "crud_cell_product_product_node", "label": "_product_node()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L675"}, {"id": "crud_cell_product_first_chain_link", "label": "_first_chain_link()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L687"}, {"id": "crud_cell_product_build_ancestor_chain", "label": "_build_ancestor_chain()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L692"}, {"id": "crud_cell_product_build_descendant_chain", "label": "_build_descendant_chain()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L810"}, Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_schemas_qc_task_py", "label": "qc_task.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_task.py", "source_location": "L1"}, {"id": "schemas_qc_task_qctaskcreatebody", "label": "QcTaskCreateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_task.py", "source_location": "L5"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_qc_task_py", "target": "pydantic", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_task.py", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_qc_task_py", "target": "typing", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_task.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_qc_task_py", "target": "schemas_qc_task_qctaskcreatebody", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_task.py", "source_location": "L5", "weight": 1.0}, {"source": "schemas_qc_task_qctaskcreatebody", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_task.py", "source_location": "L5", "weight": 1.0}], "raw_calls": []}
@@ -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": "L31"}, {"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": "L37"}, {"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": "L41"}, {"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": "L54"}, {"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": "L59"}, {"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": "L91"}, {"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": "L109"}, {"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": "L213"}, {"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": "L224"}, {"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": "L267"}, {"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": "L340"}, {"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": "L357"}, {"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": "L366"}, {"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": "L389"}, {"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": "L406"}, {"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": "L410"}, {"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": "L454"}, {"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": "L506"}, {"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": "L536"}, {"id": "crud_qc_task_rationale_465", "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": "L465"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_py", "target": "datetime", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_py", "target": "decimal", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_py", "target": "typing", "relation": "imports_from", "cLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "label": "cell_product.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L1"}, {"id": "schemas_cell_product_coerce_optional_datetime", "label": "_coerce_optional_datetime()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L9"}, {"id": "schemas_cell_product_cellproductbatchitem", "label": "CellProductBatchItem", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L23"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "schemas_cell_product_cellproductcreate", "label": "CellProductCreate", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L32"}, {"id": "schemas_cell_product_coerce_date_fields", "label": "coerce_date_fields()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L56"}, {"id": "schemas_cell_product_validate_viability", "label": "validate_viability()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L61"}, {"id": "schemas_cell_product_validate_passage", "label": "validate_passage()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L68"}, {"id": "schemas_cell_product_cellproductupdate", "label": "CellProductUpdate", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L77"}, {"id": "schemas_cell_product_validate_status", "label": "validate_status()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L120"}, {"id": "schemas_cell_product_cellproductlistquery", "label": "CellProductListQuery", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L127"}, {"id": "schemas_cell_product_rationale_10", "label": "\u517c\u5bb9\u524d\u7aef date-picker \u7684 YYYY-MM-DD \u4e0e\u5b8c\u6574\u65e5\u671f\u65f6\u95f4\u5b57\u7b26\u4e32\u3002", "file_type": "rationale", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L10"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "datetime", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "typing", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "pydantic", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "app_core_utils", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L6", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "schemas_cell_product_coerce_optional_datetime", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L9", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "schemas_cell_product_cellproductbatchitem", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L23", "weight": 1.0}, {"source": "schemas_cell_product_cellproductbatchitem", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L23", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "schemas_cell_product_cellproductcreate", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L32", "weigLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_schemas_production_work_order_py", "label": "production_work_order.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L1"}, {"id": "schemas_production_work_order_productionworkorderbase", "label": "ProductionWorkOrderBase", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L6"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "schemas_production_work_order_productionworkorderlistquery", "label": "ProductionWorkOrderListQuery", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L10"}, {"id": "schemas_production_work_order_productionworkordercomplete", "label": "ProductionWorkOrderComplete", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L20"}, {"id": "schemas_production_work_order_productionworkordercreate", "label": "ProductionWorkOrderCreate", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L24"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_production_work_order_py", "target": "pydantic", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_production_work_order_py", "target": "typing", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_production_work_order_py", "target": "datetime", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_production_work_order_py", "target": "schemas_production_work_order_productionworkorderbase", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L6", "weight": 1.0}, {"source": "schemas_production_work_order_productionworkorderbase", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L6", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_production_work_order_py", "target": "schemas_production_work_order_productionworkorderlistquery", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L10", "weight": 1.0}, {"source": "schemas_production_work_order_productionworkorderlistquery", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L10", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_production_work_order_py", "target": "schemas_production_work_order_productionworkordercomplete", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L20", "weight": 1.0}, {"source": "schemas_production_work_order_productionworkordercomplete", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L20", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_production_work_order_py", "target": "schemas_production_work_order_productionworkordercreate", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L24", "weight": 1.0}, {"source": "schemas_production_work_order_productionworkordercreate", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py", "source_location": "L24", "weight": 1.0}], "raw_calls": []}
@@ -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": "L56"}, {"id": "api_production_consumablecreatebody", "label": "ConsumableCreateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L69"}, {"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": "L83"}, {"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": "L98"}, {"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": "L112"}, {"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": "L122"}, {"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": "L150"}, {"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": "L179"}, {"id": "api_production_stockinbody", "label": "StockInBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L191"}, {"id": "api_production_stockoutbody", "label": "StockOutBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L197"}, {"id": "api_production_batchstockinitem", "label": "BatchStockInItem", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L205"}, {"id": "api_production_batchstockinbody", "label": "BatchStockInBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L212"}, {"id": "api_production_batchstockoutitem", "label": "BatchStockOutItem", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L218"}, {"id": "api_production_batchstockoutbody", "label": "BatchStockOutBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L224"}, {"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": "L233"}, {"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": "L255"}, {"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": "L279"}, {"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": "L297"}, {"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": "L316"}, {"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": "L335"}, {"id": "api_production_reagentcreatebody", "label": "ReagentCreateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L361"}, {"id": "api_production_reagentupdatebody", "label": "ReagentUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L375"}, {"id": "api_production_list_reagents", "label": "list_reagents()", "file_type": "code", "source_filLine truncated
+18 -10
View File
@@ -1223,18 +1223,20 @@ class BizQcWorkOrder(Base):
id = Column(String(64), primary_key=True, comment="主键ID")
work_order_no = Column(String(32), nullable=False, comment="工单号")
reception_id = Column(String(64), nullable=False, comment="接收记录ID")
protocol_sample_id = Column(String(64), nullable=False, comment="协议样本ID")
collection_protocol_id = Column(String(64), nullable=False, comment="协议单ID")
work_order_type = Column(String(20), nullable=False, default="inbound", comment="工单类型: inbound=入库, temporary=临时")
reception_id = Column(String(64), nullable=True, comment="接收记录ID")
protocol_sample_id = Column(String(64), nullable=True, comment="协议样本ID")
collection_protocol_id = Column(String(64), nullable=True, comment="协议单ID")
protocol_no = Column(String(64), nullable=True, comment="协议号快照")
sample_code = Column(String(32), nullable=True, comment="样本编码快照")
reception_source = Column(String(20), nullable=False, default="cell_storage", comment="来源类型 cell_storage/research_donation")
reception_source = Column(String(20), nullable=True, default="cell_storage", comment="来源类型 cell_storage/research_donation")
qc_biz_type = Column(
String(20),
nullable=False,
default="inbound",
comment="质检业务类型 inbound入库质检 outbound出库质检",
comment="质检业务类型 inbound入库质检 outbound出库质检 temporary临时质检",
)
cell_product_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="含第三方检测")
@@ -1259,6 +1261,7 @@ class BizQcWorkOrderLabItem(Base):
id = Column(String(64), primary_key=True, comment="主键ID")
work_order_id = Column(String(64), nullable=False, comment="工单ID")
order_source = Column(String(20), nullable=False, default="inbound", comment="工单来源: inbound=入库, temporary=临时")
lab_test_item_id = Column(String(64), nullable=False, comment="实验室项目ID")
item_code = Column(String(64), nullable=False, comment="项目编码快照")
item_name = Column(String(200), nullable=False, comment="项目名称快照")
@@ -1280,6 +1283,7 @@ class BizQcWorkOrderThirdItem(Base):
id = Column(String(64), primary_key=True, comment="主键ID")
work_order_id = Column(String(64), nullable=False, comment="工单ID")
order_source = Column(String(20), nullable=False, default="inbound", comment="工单来源: inbound=入库, temporary=临时")
third_party_item_id = Column(String(64), nullable=False, comment="第三方项目ID")
company_id = Column(String(64), nullable=False, comment="公司ID")
company_name = Column(String(200), nullable=False, comment="公司名快照")
@@ -1304,6 +1308,7 @@ class BizQcTask(Base):
id = Column(String(64), primary_key=True, comment="主键ID")
work_order_id = Column(String(64), nullable=False, comment="工单ID")
order_source = Column(String(20), nullable=False, default="inbound", comment="工单来源: inbound=入库, temporary=临时")
task_no = Column(String(32), nullable=False, comment="任务号")
task_type = Column(String(16), nullable=False, comment="sysz实验室 dsfjc第三方")
source_line_id = Column(String(64), nullable=False, comment="工单明细行ID")
@@ -2273,13 +2278,16 @@ class BizProductionWorkOrder(Base):
id = Column(String(64), primary_key=True, comment="主键ID")
work_order_no = Column(String(32), nullable=False, comment="工单号")
reception_id = Column(String(64), nullable=False, comment="接收记录ID")
protocol_sample_id = Column(String(64), nullable=False, comment="协议样本ID")
collection_protocol_id = Column(String(64), nullable=False, comment="采集协议单ID")
work_order_type = Column(String(20), nullable=False, default="inbound", comment="工单类型: inbound=入库, temporary=临时")
reception_id = Column(String(64), nullable=True, comment="接收记录ID")
protocol_sample_id = Column(String(64), nullable=True, comment="协议样本ID")
collection_protocol_id = Column(String(64), nullable=True, comment="采集协议单ID")
protocol_no = Column(String(64), nullable=True, comment="协议号快照")
sample_code = Column(String(32), nullable=True, comment="样本编码快照")
reception_source = Column(String(20), nullable=False, default="cell_storage", comment="来源类型 cell_storage细胞存储 research_donation科研捐赠")
reception_source = Column(String(20), nullable=True, default="cell_storage", comment="来源类型 cell_storage细胞存储 research_donation科研捐赠")
work_order_status = Column(String(16), nullable=False, default="dpz", comment="工单状态 dpz待配置 jxz进行中 ywc已完成")
configured_time = Column(DateTime, nullable=True, comment="配置时间")
configured_by = Column(String(64), nullable=True, comment="配置人")
tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
order_num = Column(Integer, nullable=True, comment="排序号")
remark = Column(String(512), nullable=True, comment="备注")
@@ -2291,7 +2299,6 @@ class BizProductionWorkOrder(Base):
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记")
class BizProductionTask(Base):
"""生产任务 biz_production_task"""
@@ -2300,6 +2307,7 @@ class BizProductionTask(Base):
id = Column(String(64), primary_key=True, comment="主键ID")
task_no = Column(String(32), nullable=False, comment="任务号")
work_order_id = Column(String(64), nullable=False, comment="工单ID")
order_source = Column(String(20), nullable=False, default="inbound", comment="工单来源: inbound=入库, temporary=临时")
task_type = Column(String(20), nullable=False, default="cell_storage", comment="任务类型 cell_storage入库生产单 outbound出库生产单")
task_name = Column(String(200), nullable=True, comment="任务名称")
description = Column(String(500), nullable=True, comment="描述")
+1
View File
@@ -135,3 +135,4 @@ class CellProductListQuery(BaseModel):
product_type_id: Optional[str] = Field(None, alias="productTypeId")
product_status: Optional[str] = Field(None, alias="productStatus")
passage: Optional[str] = None
source_type: Optional[str] = Field(None, alias="sourceType")
+22 -1
View File
@@ -1,4 +1,4 @@
from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict, Field
from typing import Optional
from datetime import datetime
@@ -13,9 +13,30 @@ class ProductionWorkOrderListQuery(BaseModel):
keyword: Optional[str] = None
work_order_no: Optional[str] = None
work_order_status: Optional[str] = None
work_order_type: Optional[str] = None
protocol_no: Optional[str] = None
sample_code: Optional[str] = None
class ProductionWorkOrderComplete(BaseModel):
remark: Optional[str] = None
class ProductionWorkOrderCreate(BaseModel):
model_config = ConfigDict(populate_by_name=True)
work_order_type: Optional[str] = Field("inbound", alias="workOrderType", description="工单类型 inbound/temporary")
protocol_no: Optional[str] = Field(None, alias="protocolNo", description="协议号")
sample_code: Optional[str] = Field(None, alias="sampleCode", description="样本编码")
reception_source: Optional[str] = Field("cell_storage", alias="receptionSource", description="来源类型")
remark: Optional[str] = Field(None, description="备注")
class ProductionWorkOrderConfigureBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
task_name: Optional[str] = Field("", alias="taskName")
task_type: Optional[str] = Field("cell_storage", alias="taskType")
route_id: Optional[str] = Field(None, alias="routeId")
description: Optional[str] = Field("", alias="description")
remark: Optional[str] = Field("", alias="remark")
+18
View File
@@ -0,0 +1,18 @@
from pydantic import BaseModel, ConfigDict, Field
from typing import Optional
class QcTaskCreateBody(BaseModel):
"""手动创建质检任务"""
model_config = ConfigDict(populate_by_name=True)
task_type: str = Field(..., alias="taskType", description="检测类型 sysz/dsfjc")
item_code: str = Field(..., alias="itemCode", description="项目编码")
item_name: str = Field(..., alias="itemName", description="项目名称")
company_id: Optional[str] = Field(None, alias="companyId", description="第三方公司ID")
company_name: Optional[str] = Field(None, alias="companyName", description="第三方公司名")
fee_amount: Optional[float] = Field(None, alias="feeAmount", description="费用")
sample_code: Optional[str] = Field(None, alias="sampleCode", description="样本编码")
qc_biz_type: Optional[str] = Field("inbound", alias="qcBizType", description="质检业务类型")
work_order_id: Optional[str] = Field(None, alias="workOrderId", description="关联工单ID(可选)")
remark: Optional[str] = Field(None, description="备注")
+23
View File
@@ -0,0 +1,23 @@
from pydantic import BaseModel, ConfigDict, Field
from typing import Optional
class QcWorkOrderCreateBody(BaseModel):
"""手动创建质检工单"""
model_config = ConfigDict(populate_by_name=True)
work_order_type: Optional[str] = Field("inbound", alias="workOrderType", description="工单类型 inbound/temporary")
protocol_no: Optional[str] = Field(None, alias="protocolNo", description="协议号")
sample_code: Optional[str] = Field(None, alias="sampleCode", description="样本编码")
reception_source: Optional[str] = Field("cell_storage", alias="receptionSource", description="来源类型")
qc_biz_type: Optional[str] = Field("inbound", alias="qcBizType", description="质检业务类型")
cell_product_id: Optional[str] = Field(None, alias="cellProductId", description="质检对象-细胞产品ID")
remark: Optional[str] = Field(None, description="备注")
class QcWorkOrderUpdateBody(BaseModel):
"""编辑临时质检工单"""
model_config = ConfigDict(populate_by_name=True)
cell_product_id: Optional[str] = Field(None, alias="cellProductId", description="质检对象-细胞产品ID")
remark: Optional[str] = Field(None, description="备注")
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+126 -106
View File
@@ -1,12 +1,12 @@
# Graph Report - app (2026-05-24)
## Corpus Check
- 146 files · ~135,185 words
- 148 files · ~136,649 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 2601 nodes · 10532 edges · 54 communities detected
- Extraction: 41% EXTRACTED · 59% INFERRED · 0% AMBIGUOUS · INFERRED: 6233 edges (avg confidence: 0.63)
- 2619 nodes · 10690 edges · 58 communities detected
- Extraction: 40% EXTRACTED · 60% INFERRED · 0% AMBIGUOUS · INFERRED: 6361 edges (avg confidence: 0.63)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
@@ -58,16 +58,20 @@
- [[_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]]
- [[_COMMUNITY_Community 54|Community 54]]
- [[_COMMUNITY_Community 55|Community 55]]
- [[_COMMUNITY_Community 56|Community 56]]
- [[_COMMUNITY_Community 57|Community 57]]
- [[_COMMUNITY_Community 58|Community 58]]
- [[_COMMUNITY_Community 59|Community 59]]
## God Nodes (most connected - your core abstractions)
1. `success_response()` - 409 edges
2. `error_response()` - 328 edges
1. `success_response()` - 413 edges
2. `error_response()` - 331 edges
3. `SysUser` - 234 edges
4. `BizQcMaterialUsage` - 208 edges
5. `BizMaterialStockLog` - 204 edges
@@ -75,7 +79,7 @@
7. `BizQcMaterial` - 204 edges
8. `BizMaterial` - 201 edges
9. `BizQcItemParam` - 199 edges
10. `generate_uuid()` - 148 edges
10. `generate_uuid()` - 151 edges
## Surprising Connections (you probably didn't know these)
- `统一记录 API 请求/响应出入参的中间件` --uses--> `SysApiRequestLog` [INFERRED]
@@ -93,107 +97,107 @@
### Community 0 - "Community 0"
Cohesion: 0.02
Nodes (439): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+431 more)
Nodes (462): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+454 more)
### Community 1 - "Community 1"
Cohesion: 0.01
Nodes (408): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+400 more)
Nodes (391): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+383 more)
### Community 2 - "Community 2"
Cohesion: 0.05
Nodes (285): AuditCreateBody, BatchDetailIdsBody, BatchRefundFinanceSubmitBody, FinanceConfirmBody, FullmoonTodoInsuranceBody, ProtocolCreateBody, ProtocolUpdateBody, 细胞存储 — 采集协议单 / 审核 / 财务确认 / 分期预警 API (+277 more)
Cohesion: 0.02
Nodes (240): _persist_protocol_sub_resources(), get_designer_data(), apply_data_scope(), _get_child_dept_ids(), get_user_dept_id(), _resolve_user_role_scopes(), generate_uuid(), _add_months_yyyymmdd() (+232 more)
### Community 3 - "Community 3"
Cohesion: 0.02
Nodes (175): generate_uuid(), batch_stock_in(), batch_stock_out(), consumable_code_exists(), create_consumable(), _dec(), get_consumable(), _is_enabled_status() (+167 more)
Cohesion: 0.05
Nodes (244): InsuranceCompanyCreateBody, InsuranceCompanyInvoiceCreateBody, InsuranceCompanyInvoiceUpdateBody, InsuranceCompanyUpdateBody, InsuranceProjectPriceCreateBody, InsuranceProjectPriceUpdateBody, 保险公司及险种价格管理 API(保险公司 + 开票信息 + 险种价格), ProcessDefCreateBody (+236 more)
### Community 4 - "Community 4"
Cohesion: 0.02
Nodes (158): list_collection_regions(), create_collection_protocol(), delete_collection_protocol(), _ensure_customer_for_mother(), get_collection_protocol_detail(), _get_current_user_id(), _get_tenant_no(), list_collection_protocol_audits() (+150 more)
Nodes (105): list_roles_for_select(), _avatar_url_field(), _build_user_info_payload(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, 与 web-admin `userInfo` 字段约定一致。 (+97 more)
### Community 5 - "Community 5"
Cohesion: 0.04
Nodes (119): _format_attachment_file_size(), get_protocol_nos_used_in_protocols(), urls_to_attachment_list(), _accept_conclusion_label(), _attach_reception_detail(), complete_production_work_order(), create_production_work_order_from_reception(), _enrich_wo_items() (+111 more)
Cohesion: 0.08
Nodes (136): CellSampleTypeCreateBody, CellSampleTypeUpdateBody, CollectionRegionCreateBody, CollectionRegionUpdateBody, CustomerCreateBody, CustomerUpdateBody, HospitalCreateBody, HospitalUpdateBody (+128 more)
### Community 6 - "Community 6"
Cohesion: 0.07
Nodes (67): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, OccupancyCreateBody, 运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约) (+59 more)
Cohesion: 0.05
Nodes (99): _format_attachment_file_size(), urls_to_attachment_list(), _accept_conclusion_label(), _attach_reception_detail(), complete_production_work_order(), create_production_work_order_manual(), _enrich_wo_items(), _fmt_dt() (+91 more)
### Community 7 - "Community 7"
Cohesion: 0.05
Nodes (57): list_roles_for_select(), _avatar_url_field(), _build_user_info_payload(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, 与 web-admin `userInfo` 字段约定一致。 (+49 more)
Nodes (74): get_next_cell_product_code(), _build_ancestor_chain(), _build_descendant_chain(), _build_facility_path_label(), create_cell_products_batch(), delete_cell_product(), _enrich_product_dicts(), _first_chain_link() (+66 more)
### Community 8 - "Community 8"
Cohesion: 0.06
Nodes (55): get_designer_data(), ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody, 审批意见:前端传 commentText,兼容 comment。, StartInstanceBody, TaskActionBody, list_params_by_item() (+47 more)
Cohesion: 0.04
Nodes (31): BusinessException, InvalidStatusTransitionException, log_delivery_operation(), log_operation(), log_order_operation(), log_settlement_operation(), log_user_operation(), OrderLockedException (+23 more)
### Community 9 - "Community 9"
Cohesion: 0.05
Nodes (26): BusinessException, InvalidStatusTransitionException, log_delivery_operation(), log_operation(), log_order_operation(), log_settlement_operation(), log_user_operation(), OrderLockedException (+18 more)
Cohesion: 0.07
Nodes (67): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, OccupancyCreateBody, 运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约) (+59 more)
### Community 10 - "Community 10"
Cohesion: 0.34
Nodes (58): BatchStockInBody, BatchStockInItem, BatchStockOutBody, BatchStockOutItem, ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody (+50 more)
Nodes (59): BatchStockInBody, BatchStockInItem, BatchStockOutBody, BatchStockOutItem, ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody (+51 more)
### Community 11 - "Community 11"
Cohesion: 0.06
Nodes (57): get_next_cell_product_code(), _build_facility_path_label(), build_split_product_codes(), create_cell_product(), create_cell_products_batch(), create_cell_products_by_quantity(), delete_cell_product(), _enrich_product_dicts() (+49 more)
Cohesion: 0.14
Nodes (33): list_research_donation_audits(), _audit_to_dict(), batch_delete_donations(), _collection_to_dict(), _create_collection(), create_donation(), _create_party(), _create_samples() (+25 more)
### Community 12 - "Community 12"
Cohesion: 0.05
Nodes (38): ApiRequestLogMiddleware, _claims_from_auth(), _format_body_bytes(), _persist_log(), 统一记录 API 请求/响应出入参的中间件, _redact_obj(), _schedule_persist_log(), _should_skip() (+30 more)
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 13 - "Community 13"
Cohesion: 0.11
Nodes (44): create_collection_protocol(), _active_link_query(), assert_biz_no_available(), create_convert_link(), get_convert_partner(), get_partner_sample_codes(), has_active_convert_link(), upsert_convert_link_for_to_storage() (+36 more)
### Community 14 - "Community 14"
Cohesion: 0.16
Nodes (32): _create_insurance_company(), _create_insurance_company_invoice(), _create_insurance_project_price(), delete_insurance_company(), delete_insurance_company_invoice(), delete_insurance_project_price(), _fmt_dt(), _generate_uuid() (+24 more)
### Community 15 - "Community 15"
Cohesion: 0.1
Nodes (26): BaseHTTPMiddleware, parse_admin_request_json(), 运营 Web(/web/admin)JSON 请求体解析。 与 ApiCryptoMiddleware 配合:中间件未改写 body 时,在路由内解密 `, 读取 JSON body;dict 时走 resolve_request_payload(明文 / 密文 / data 包装)。 Returns:, ApiCryptoError, _b64url_decode(), _b64url_encode(), decrypt_json() (+18 more)
### Community 14 - "Community 14"
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 15 - "Community 15"
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 16 - "Community 16"
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)
Cohesion: 0.18
Nodes (17): generate_sms(), _get_current_user_id(), _get_tenant_no(), list_notifications(), _page_payload(), toggle_status(), ToggleStatusBody, _accept_conclusion_label() (+9 more)
### Community 17 - "Community 17"
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 18 - "Community 18"
Cohesion: 0.2
Nodes (14): deduct_for_task(), _display_remark(), list_usages(), list_usages_by_task(), _parse_receiver_purpose_from_remark(), 查询质检任务关联的物料使用记录(task_complete)。, 撤销任务此前 task_complete 扣减,便于重新填报物料使用。, 兼容旧数据:领用人/用途曾拼接在 remark 中。 (+6 more)
Cohesion: 0.25
Nodes (14): batch_stock_in(), batch_stock_out(), create_reagent(), _dec(), get_reagent(), _is_enabled_status(), list_reagents(), _norm_status() (+6 more)
### Community 19 - "Community 19"
Cohesion: 0.21
Nodes (13): create_collection_region(), create_hospital(), get_collection_region(), get_hospital(), _hospital_names_by_region_ids(), _hospital_to_dict(), list_collection_regions(), list_hospitals() (+5 more)
### Community 20 - "Community 20"
Cohesion: 0.25
Nodes (13): code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), list_materials(), _norm_status(), options_list() (+5 more)
### Community 20 - "Community 20"
### Community 21 - "Community 21"
Cohesion: 0.25
Nodes (13): batch_issue_protocol_nos(), batch_revoke_protocol_nos(), create_protocol_no(), get_protocol_no(), list_protocol_nos(), _parse_issued_time(), protocol_no_exists(), 采集协议号池 CRUD(biz_collection_protocol_no) (+5 more)
### Community 22 - "Community 22"
Cohesion: 0.26
Nodes (12): batch_issue_sample_codes(), batch_revoke_sample_codes(), create_sample_code(), get_sample_code(), list_sample_codes(), _parse_issued_time(), 样本编码池 CRUD(biz_sample_code), _resolve_issued_user_display_name() (+4 more)
### Community 21 - "Community 21"
### Community 23 - "Community 23"
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 22 - "Community 22"
Cohesion: 0.23
Nodes (12): Config, ProductBase, ProductCategoryBase, ProductCategoryCreate, ProductCategoryResponse, ProductCreate, ProductResponse, ProductUnitBase (+4 more)
### Community 23 - "Community 23"
Cohesion: 0.29
Nodes (10): _accept_conclusion_label(), _base_query(), _ensure_notification(), _fmt_dt(), generate_sms_content(), list_notifications(), resolve_notification(), _row_to_dict() (+2 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)
Cohesion: 0.31
Nodes (12): _code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), list_materials(), options_list(), _row_to_dict() (+4 more)
### Community 25 - "Community 25"
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)
Cohesion: 0.28
Nodes (10): _base_query(), _ensure_notification(), _fmt_dt(), generate_sms_content(), list_notifications(), resolve_notification(), _row_to_dict(), _storage_status_label() (+2 more)
### Community 26 - "Community 26"
Cohesion: 0.27
@@ -208,145 +212,161 @@ Cohesion: 0.31
Nodes (8): create_sop_document(), get_sop_document(), list_sop_documents(), SOP文档 CRUD(biz_sop_document), _row_to_dict(), soft_delete_sop_document(), sop_code_exists(), update_sop_document()
### Community 29 - "Community 29"
Cohesion: 0.36
Nodes (9): deduct_for_task(), _display_remark(), list_usages(), list_usages_by_task(), _parse_receiver_purpose_from_remark(), _resolve_receiver_purpose(), revert_task_material_usages(), _row_to_dict() (+1 more)
### Community 30 - "Community 30"
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 30 - "Community 30"
### Community 31 - "Community 31"
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 31 - "Community 31"
### Community 32 - "Community 32"
Cohesion: 0.27
Nodes (8): create_dict_data(), get_dict_data(), list_dict_data(), list_dict_data_options(), 字典数据 CRUD(sys_dict_data), _row_to_dict(), soft_delete_dict_data(), update_dict_data()
### Community 32 - "Community 32"
### Community 33 - "Community 33"
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 33 - "Community 33"
### Community 34 - "Community 34"
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 34 - "Community 34"
### Community 35 - "Community 35"
Cohesion: 0.31
Nodes (8): create_customer(), get_customer(), id_card_taken(), list_customers(), 客户 CRUD(biz_customer), _row_to_dict(), soft_delete_customer(), update_customer()
### Community 36 - "Community 36"
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 35 - "Community 35"
### Community 37 - "Community 37"
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 36 - "Community 36"
### Community 38 - "Community 38"
Cohesion: 0.36
Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_dict(), soft_delete_post(), update_post()
### Community 37 - "Community 37"
Cohesion: 0.36
Nodes (7): create_dict_type(), dict_type_code_exists(), get_dict_type(), list_dict_types(), _row_to_dict(), soft_delete_dict_type(), update_dict_type()
### Community 38 - "Community 38"
Cohesion: 0.36
Nodes (7): create_tenant(), get_tenant(), list_tenants(), _row_to_dict(), soft_delete_tenant(), tenant_no_exists(), update_tenant()
### Community 39 - "Community 39"
Cohesion: 0.36
Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
Nodes (7): create_tenant(), get_tenant(), list_tenants(), _row_to_dict(), soft_delete_tenant(), tenant_no_exists(), update_tenant()
### Community 40 - "Community 40"
Cohesion: 0.39
Nodes (8): create_lab_item(), get_lab_item(), item_code_exists(), list_active_lab_items(), list_lab_items(), _row_to_dict(), soft_delete_lab_item(), update_lab_item()
### Community 41 - "Community 41"
Cohesion: 0.36
Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
### Community 42 - "Community 42"
Cohesion: 0.29
Nodes (2): BaseModel, SoftDeleteModel
### Community 42 - "Community 42"
### Community 43 - "Community 43"
Cohesion: 0.43
Nodes (7): _display_remark(), list_stock_logs(), _parse_receiver_purpose_from_remark(), _resolve_receiver_purpose(), revert_step_stock_changes(), _row_to_dict(), _user_nick_name_map()
### Community 44 - "Community 44"
Cohesion: 0.38
Nodes (6): derive_legacy_status_from_three(), derive_three_status_from_legacy(), 阶段A兼容:从旧 status 推导三字段。 约束:旧 status 本身混合了支付/配送/订单生命周期,因此只能尽力推断; 若业务侧已有更精确, 阶段A兼容:从三字段反推旧 status(用于旧前端/旧筛选参数兼容)。, ThreeStatus, _to_decimal()
### Community 43 - "Community 43"
### Community 45 - "Community 45"
Cohesion: 0.6
Nodes (5): _dec(), _fmt_dt(), list_stock_logs(), _row_to_dict(), _user_nick_name_map()
### Community 46 - "Community 46"
Cohesion: 0.4
Nodes (4): list_ethnics_for_select(), list_ethnics(), 民族码表 CRUD(全局 sys_ethnic), _row_to_dict()
### Community 47 - "Community 47"
Cohesion: 0.5
Nodes (3): BaseSettings, Config, Settings
### Community 44 - "Community 44"
### Community 48 - "Community 48"
Cohesion: 0.5
Nodes (2): _auto_fill_create_dept_id(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID
### Community 45 - "Community 45"
### Community 49 - "Community 49"
Cohesion: 0.67
Nodes (2): get_menu_list(), 运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,供后端菜单模式联调)
### Community 46 - "Community 46"
### Community 50 - "Community 50"
Cohesion: 1.0
Nodes (1): 请求级别的用户上下文变量 通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传
### Community 47 - "Community 47"
### Community 51 - "Community 51"
Cohesion: 1.0
Nodes (1): API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)
### Community 50 - "Community 50"
### Community 54 - "Community 54"
Cohesion: 1.0
Nodes (1): 发送订单状态通知 Args: openid: 用户openid ord
### Community 51 - "Community 51"
### Community 55 - "Community 55"
Cohesion: 1.0
Nodes (1): 发送支付成功通知 Args: openid: 用户openid ord
### Community 52 - "Community 52"
### Community 56 - "Community 56"
Cohesion: 1.0
Nodes (1): 发送配送通知 Args: openid: 配送员openid task
### Community 53 - "Community 53"
### Community 57 - "Community 57"
Cohesion: 1.0
Nodes (1): 发送配送时效预警 Args: openid: 配送员openid ta
### Community 54 - "Community 54"
### Community 58 - "Community 58"
Cohesion: 1.0
Nodes (1): 发送退款结果通知 Args: openid: 用户openid ord
### Community 55 - "Community 55"
### Community 59 - "Community 59"
Cohesion: 1.0
Nodes (1): 发送催单通知给配送员 Args: deliver_openid: 配送员openid
## Knowledge Gaps
- **90 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。` (+85 more)
- **89 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。` (+84 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Community 41`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
- **Thin community `Community 42`** (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 44`** (4 nodes): `_auto_fill_create_dept_id()`, `get_db()`, `database.py`, `ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID`
- **Thin community `Community 48`** (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 45`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,供后端菜单模式联调)`
- **Thin community `Community 49`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,供后端菜单模式联调)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 46`** (2 nodes): `context.py`, `请求级别的用户上下文变量 通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
- **Thin community `Community 50`** (2 nodes): `context.py`, `请求级别的用户上下文变量 通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 47`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
- **Thin community `Community 51`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 50`** (1 nodes): `发送订单状态通知 Args: openid: 用户openid ord`
- **Thin community `Community 54`** (1 nodes): `发送订单状态通知 Args: openid: 用户openid ord`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 51`** (1 nodes): `发送支付成功通知 Args: openid: 用户openid ord`
- **Thin community `Community 55`** (1 nodes): `发送支付成功通知 Args: openid: 用户openid ord`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 52`** (1 nodes): `发送配送通知 Args: openid: 配送员openid task`
- **Thin community `Community 56`** (1 nodes): `发送配送通知 Args: openid: 配送员openid task`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 53`** (1 nodes): `发送配送时效预警 Args: openid: 配送员openid ta`
- **Thin community `Community 57`** (1 nodes): `发送配送时效预警 Args: openid: 配送员openid ta`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 54`** (1 nodes): `发送退款结果通知 Args: openid: 用户openid ord`
- **Thin community `Community 58`** (1 nodes): `发送退款结果通知 Args: openid: 用户openid ord`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 55`** (1 nodes): `发送催单通知给配送员 Args: deliver_openid: 配送员openid`
- **Thin community `Community 59`** (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 `generate_uuid()` connect `Community 3` to `Community 0`, `Community 1`, `Community 2`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 11`, `Community 12`, `Community 13`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 23`, `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`, `Community 38`, `Community 39`, `Community 40`?**
_High betweenness centrality (0.137) - this node is a cross-community bridge._
- **Why does `SysUser` connect `Community 1` to `Community 0`, `Community 2`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 10`, `Community 12`, `Community 14`, `Community 18`, `Community 20`, `Community 24`?**
- **Why does `generate_uuid()` connect `Community 2` to `Community 0`, `Community 3`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 9`, `Community 11`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 18`, `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`, `Community 38`, `Community 39`, `Community 40`, `Community 41`?**
_High betweenness centrality (0.113) - this node is a cross-community bridge._
- **Why does `SysUser` connect `Community 1` to `Community 0`, `Community 3`, `Community 4`, `Community 5`, `Community 9`, `Community 10`, `Community 16`, `Community 21`, `Community 22`?**
_High betweenness centrality (0.081) - this node is a cross-community bridge._
- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 4`, `Community 7`, `Community 8`, `Community 11`, `Community 45`, `Community 14`?**
_High betweenness centrality (0.056) - this node is a cross-community bridge._
- **Are the 408 inferred relationships involving `success_response()` (e.g. with `list_storage_release_reviews()` and `get_storage_release_review_detail()`) actually correct?**
_`success_response()` has 408 INFERRED edges - model-reasoned connections that need verification._
- **Are the 389 inferred relationships involving `str` (e.g. with `extract_login_credentials()` and `._make_request()`) actually correct?**
_`str` has 389 INFERRED edges - model-reasoned connections that need verification._
- **Are the 327 inferred relationships involving `error_response()` (e.g. with `_json_error_response()` and `list_storage_release_reviews()`) actually correct?**
_`error_response()` has 327 INFERRED edges - model-reasoned connections that need verification._
- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 2`, `Community 4`, `Community 7`, `Community 11`, `Community 46`, `Community 16`, `Community 49`?**
_High betweenness centrality (0.068) - this node is a cross-community bridge._
- **Are the 412 inferred relationships involving `success_response()` (e.g. with `list_storage_release_reviews()` and `get_storage_release_review_detail()`) actually correct?**
_`success_response()` has 412 INFERRED edges - model-reasoned connections that need verification._
- **Are the 392 inferred relationships involving `str` (e.g. with `extract_login_credentials()` and `._make_request()`) actually correct?**
_`str` has 392 INFERRED edges - model-reasoned connections that need verification._
- **Are the 330 inferred relationships involving `error_response()` (e.g. with `_json_error_response()` and `list_storage_release_reviews()`) actually correct?**
_`error_response()` has 330 INFERRED edges - model-reasoned connections that need verification._
- **Are the 232 inferred relationships involving `SysUser` (e.g. with `数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤) data_scope 值: 1 = 全部数据 2 = 本部门数据 3` and `从 SysUser 读取用户的 dept_id。`) actually correct?**
_`SysUser` has 232 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">2601 nodes &middot; 10532 edges &middot; 60 communities</div>
<div id="stats">2619 nodes &middot; 10690 edges &middot; 64 communities</div>
</div>
<script>
const RAW_NODES = [{"id": "init_py", "label": "__init__.py", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 48, "community_name": "Community 48", "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.3, "font": {"size": 0, "color": "#ffffff"}, "title": "main.py", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_lifespan", "label": "lifespan()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.3, "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_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": "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", "degree": 2}, {"id": "tasks_init_init_scheduled_tasks", "label": "init_scheduled_tasks()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "init_scheduled_tasks()", "community": 7, "community_name": "Community 7", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "tasks_init_shutdown_scheduler", "label": "shutdown_scheduler()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "shutdown_scheduler()", "community": 7, "community_name": "Community 7", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "core_auth_py", "label": "auth.py", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 11.3, "font": {"size": 0, "color": "#ffffff"}, "title": "auth.py", "community": 7, "community_name": "Community 7", "source_file": "core/auth.py", "file_type": "code", "degree": 18}, {"id": "core_auth_redis_session_enabled", "label": "_redis_session_enabled()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "_redis_session_enabled()", "community": 7, "community_name": "Community 7", "source_file": "core/auth.py", "file_type": "code", "degree": 2}, {"id": "core_auth_get_redis_client", "label": "_get_redis_client()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.7, "font": {"size": 0, "color": "#ffffff"}, "title": "_get_redis_client()", "community": 7, "community_name": "Community 7", "source_file": "core/auth.py", "file_type": "code", "degree": 9}, {"id": "core_auth_session_key", "label": "_session_key()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "_session_key()", "community": 7, "community_name": "Community 7", "source_file": "core/auth.py", "file_type": "code", "degree": 5}, {"id": "core_auth_refresh_key", "label": "_refresh_key()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "boLine 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_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": "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", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_session_key", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_refresh_key", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_store_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_revoke_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_extend_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_is_session_valid", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_store_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_is_refresh_token_valid", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_revoke_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_create_access_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_create_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_decode_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_current_user_by_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_current_user", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "Line truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 453}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 435}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 290}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 195}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 178}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 128}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 71}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 70}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 69}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 68}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 65}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 62}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 59}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 46}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 33}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 31}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 31}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 20}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 15}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 15}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 14}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 13}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 13}, {"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": 10}, {"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": 9}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 9}, {"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": 4}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 4}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 3}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 2}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 2}, {"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": 1}];
const RAW_NODES = [{"id": "init_py", "label": "__init__.py", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 52, "community_name": "Community 52", "source_file": "__init__.py", "file_type": "code", "degree": 0}, {"id": "main_py", "label": "main.py", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "main.py", "community": 4, "community_name": "Community 4", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_lifespan", "label": "lifespan()", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "lifespan()", "community": 4, "community_name": "Community 4", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_mask_secret", "label": "_mask_secret()", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "_mask_secret()", "community": 4, "community_name": "Community 4", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_root", "label": "root()", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "root()", "community": 4, "community_name": "Community 4", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_health_check", "label": "health_check()", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "health_check()", "community": 4, "community_name": "Community 4", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "tasks_init_py", "label": "__init__.py", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 4, "community_name": "Community 4", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "tasks_init_init_scheduled_tasks", "label": "init_scheduled_tasks()", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "init_scheduled_tasks()", "community": 4, "community_name": "Community 4", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "tasks_init_shutdown_scheduler", "label": "shutdown_scheduler()", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "shutdown_scheduler()", "community": 4, "community_name": "Community 4", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "core_auth_py", "label": "auth.py", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 11.3, "font": {"size": 0, "color": "#ffffff"}, "title": "auth.py", "community": 4, "community_name": "Community 4", "source_file": "core/auth.py", "file_type": "code", "degree": 18}, {"id": "core_auth_redis_session_enabled", "label": "_redis_session_enabled()", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "_redis_session_enabled()", "community": 4, "community_name": "Community 4", "source_file": "core/auth.py", "file_type": "code", "degree": 2}, {"id": "core_auth_get_redis_client", "label": "_get_redis_client()", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.7, "font": {"size": 0, "color": "#ffffff"}, "title": "_get_redis_client()", "community": 4, "community_name": "Community 4", "source_file": "core/auth.py", "file_type": "code", "degree": 9}, {"id": "core_auth_session_key", "label": "_session_key()", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "_session_key()", "community": 4, "community_name": "Community 4", "source_file": "core/auth.py", "file_type": "code", "degree": 5}, {"id": "core_auth_refresh_key", "label": "_refresh_key()", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "boLine 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_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": "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", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_session_key", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_refresh_key", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_store_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_revoke_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_extend_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_is_session_valid", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_store_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_is_refresh_token_valid", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_revoke_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_create_access_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_create_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_decode_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_current_user_by_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_current_user", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "Line truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 476}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 421}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 273}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 251}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 142}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 139}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 105}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 80}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 74}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 71}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 66}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 34}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 31}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 31}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 28}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 21}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 21}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 20}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 16}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 15}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 15}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 15}, {"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": 13}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 13}, {"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": 10}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 10}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 10}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 9}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 9}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 9}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 9}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 8}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 8}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 7}, {"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": 4}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 4}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 3}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 2}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 2}, {"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": 1}];
// HTML-escape helper — prevents XSS when injecting graph data into innerHTML
function esc(s) {
File diff suppressed because it is too large. Load diff
+10 -8
View File
@@ -31,12 +31,13 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/models/qc_material.py": 1779284411.5179992,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/base.py": 1778578258.0821667,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/storage_release_review.py": 1779491680.5729434,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_task.py": 1779597960.4337523,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/process_route.py": 1779367496.1718137,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/auth.py": 1778578258.0939043,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/delivery.py": 1778578258.0949655,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/price_list.py": 1778578258.096609,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/user.py": 1778578258.099944,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py": 1779586786.8458486,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/production_work_order.py": 1779598101.8960276,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/storage_occupancy.py": 1779418054.7652438,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/quotation.py": 1778578258.0991468,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/product_parameter_value.py": 1778578258.0980055,
@@ -50,26 +51,27 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/order.py": 1778578258.0960286,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/__init__.py": 1778578258.0864499,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/order_adjust.py": 1778578258.0963318,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py": 1779505698.9461272,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py": 1779594151.5265117,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/menu.py": 1778578258.0957444,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/attachment.py": 1778763456.6244864,
"/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": 1779597848.673695,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/product.py": 1778578258.0968297,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/workflow.py": 1778633845.5937889,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/product_review.py": 1778578258.098316,
"/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": 1779542343.2331185,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py": 1779533582.355786,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py": 1779597967.1911633,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/process_route.py": 1779587424.6859367,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_code.py": 1779279460.690872,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_result.py": 1779331731.6720955,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/post.py": 1779265648.38155,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_reception_check_result.py": 1779255909.8554008,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/user.py": 1778775532.8870275,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py": 1779586791.5759737,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/production_work_order.py": 1779598076.1864269,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/material.py": 1779283386.3879013,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/dict_type.py": 1779265621.5212462,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_occupancy.py": 1779418213.0446064,
@@ -90,7 +92,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/express_company.py": 1779265659.3453207,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_lab_test_item.py": 1779265673.3888667,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/role.py": 1779265616.3701816,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py": 1779421014.4336343,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py": 1779598140.610689,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/menu.py": 1779265729.0381103,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/collection_region.py": 1779277867.6408322,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_reception_check_item.py": 1779007909.2933013,
@@ -101,7 +103,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/ethnic.py": 1779265849.547566,
"/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": 1779533586.5012808,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py": 1779597853.4248848,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/process.py": 1779309113.0447981,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/pilot_service.py": 1779310971.8304002,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/customer.py": 1778733031.447253,
@@ -124,7 +126,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/api/storage_release_review.py": 1779526635.1455016,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/public.py": 1779313409.0685387,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/material.py": 1779526648.041865,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py": 1779526647.7395403,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py": 1779597978.6108778,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/collection_protocol.py": 1779265813.1605325,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/basic_info.py": 1779280842.8447173,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/__init__.py": 1779528183.6934836,
@@ -139,7 +141,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/api/sample_sms_notification.py": 1779526395.021059,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/sample_reception.py": 1779526633.9689634,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/insurance_company.py": 1779281097.279193,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py": 1779586806.431537,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py": 1779598093.744562,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/leave.py": 1779266168.8693042,
"/Users/wuzilong/work/00code/zkxw/backend/app/services/workflow_callback.py": 1778727966.7277644,
"/Users/wuzilong/work/00code/zkxw/backend/app/services/workflow_engine.py": 1778634664.6293883,
+731
View File
@@ -0,0 +1,731 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>细胞产品来源链路 Demo</title>
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-light: #ecfdf5;
--primary-border: #a7f3d0;
--text: #1f2937;
--text-muted: #6b7280;
--text-light: #9ca3af;
--sample-bg: #eff6ff;
--sample-border: #93c5fd;
--line-color: #d1d5db;
--line-active: #10b981;
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font-family);
background: #f9fafb;
color: var(--text);
padding: 24px;
}
h1 { font-size: 18px; font-weight: 600; margin-bottom: 8px; }
.subtitle { font-size: 13px; color: var(--text-muted); margin-bottom: 24px; }
.scenario-tabs {
display: flex;
gap: 8px;
margin-bottom: 8px;
flex-wrap: wrap;
}
.scenario-tab {
padding: 6px 16px;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
border: 1px solid var(--line-color);
background: white;
color: var(--text-muted);
transition: all 0.2s;
}
.scenario-tab:hover { border-color: var(--primary); color: var(--primary); }
.scenario-tab.active { background: var(--primary); color: white; border-color: var(--primary); }
.scenario-desc {
font-size: 13px;
color: var(--text-muted);
background: #fffbeb;
border: 1px solid #fde68a;
border-radius: 8px;
padding: 10px 16px;
margin-bottom: 20px;
line-height: 1.6;
}
.scenario-desc strong { color: var(--text); }
.scenario-panel { display: none; }
.scenario-panel.active { display: block; }
.chain-container {
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 32px 24px 24px;
overflow-x: auto;
min-height: 200px;
}
.chain-section-title {
font-size: 13px;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 6px;
}
.chain-section-title::before {
content: '';
display: inline-block;
width: 3px;
height: 14px;
background: var(--primary);
border-radius: 2px;
}
.chain-row {
display: flex;
align-items: center;
gap: 0;
position: relative;
}
.chain-node {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
z-index: 2;
flex-shrink: 0;
}
.node-card {
width: 148px;
padding: 10px 12px;
border-radius: 8px;
border: 1.5px solid var(--primary-border);
background: var(--primary-light);
cursor: pointer;
transition: all 0.2s;
position: relative;
}
.node-card:hover {
box-shadow: 0 4px 12px rgba(16,185,129,0.2);
transform: translateY(-1px);
}
.node-card.sample {
background: var(--sample-bg);
border-color: var(--sample-border);
}
.node-card.sample:hover {
box-shadow: 0 4px 12px rgba(147,197,253,0.3);
}
.node-card.current {
border-color: var(--primary);
border-width: 2px;
box-shadow: 0 0 0 3px rgba(16,185,129,0.15);
}
.node-card.current::after {
content: '当前';
position: absolute;
top: -10px;
right: -8px;
background: var(--primary);
color: white;
font-size: 10px;
padding: 1px 6px;
border-radius: 4px;
font-weight: 500;
}
.node-type-tag {
display: inline-block;
font-size: 10px;
padding: 1px 5px;
border-radius: 3px;
margin-bottom: 4px;
font-weight: 500;
}
.node-type-tag.product { background: #d1fae5; color: #065f46; }
.node-type-tag.sample-type { background: #dbeafe; color: #1e40af; }
.node-code { font-size: 12px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.node-name { font-size: 11px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.node-passage { font-size: 10px; color: var(--text-light); margin-top: 2px; }
/* 连线 — 加长到 180px */
.chain-connector {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
min-width: 180px;
height: 60px;
z-index: 1;
flex-shrink: 0;
}
.connector-label {
display: flex;
flex-direction: column;
align-items: center;
gap: 1px;
font-size: 10px;
color: var(--text-muted);
white-space: nowrap;
padding: 3px 8px;
background: white;
border: 1px solid #f3f4f6;
border-radius: 4px;
position: absolute;
top: 2px;
left: 50%;
transform: translateX(-50%);
}
.connector-label .wo { color: var(--primary-dark); font-weight: 500; }
.connector-label .task { color: var(--text-light); }
.connector-line {
position: absolute;
bottom: 14px;
left: 0;
right: 0;
height: 2px;
background: var(--line-color);
}
.connector-line.active { background: var(--line-active); }
.connector-arrow {
position: absolute;
bottom: 10px;
right: -5px;
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 7px solid var(--line-color);
}
.connector-line.active + .connector-arrow { border-left-color: var(--line-active); }
/* 分叉 */
.fork-area {
display: flex;
flex-direction: column;
position: relative;
margin-left: 0;
}
.fork-row {
display: flex;
align-items: center;
position: relative;
min-height: 72px;
padding-left: 40px;
}
.fork-area::before {
content: '';
position: absolute;
left: 18px;
top: 36px;
bottom: 36px;
width: 2px;
background: var(--line-color);
}
.fork-row::before {
content: '';
position: absolute;
left: 18px;
top: 50%;
width: 22px;
height: 2px;
background: var(--line-color);
}
.fork-row::after {
content: '';
position: absolute;
left: 18px;
top: 0;
height: 50%;
width: 2px;
background: white;
z-index: 1;
}
.fork-row:last-child::after {
top: auto;
bottom: 0;
}
.fork-row:first-child::after {
top: 0;
bottom: auto;
height: 50%;
background: white;
}
/* 图例 */
.legend {
display: flex;
gap: 20px;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #f3f4f6;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-muted);
}
.legend-dot {
width: 10px;
height: 10px;
border-radius: 3px;
border: 1.5px solid;
}
.legend-dot.product { background: var(--primary-light); border-color: var(--primary-border); }
.legend-dot.sample { background: var(--sample-bg); border-color: var(--sample-border); }
.legend-dot.current-node { background: var(--primary-light); border-color: var(--primary); border-width: 2px; }
.legend-dot.line-legend { background: transparent; border: none; width: 24px; height: 2px; border-radius: 0; background: var(--line-active); }
.popover-detail {
display: none;
position: absolute;
top: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 12px;
min-width: 200px;
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
z-index: 100;
font-size: 12px;
}
.popover-detail.visible { display: block; }
.popover-detail .detail-row { display: flex; justify-content: space-between; padding: 3px 0; }
.popover-detail .detail-label { color: var(--text-muted); }
.popover-detail .detail-value { color: var(--text); font-weight: 500; }
.empty-state {
text-align: center;
padding: 40px;
color: var(--text-muted);
font-size: 13px;
}
.empty-state .empty-icon { font-size: 32px; margin-bottom: 8px; opacity: 0.4; }
.sub-label {
font-size: 11px;
color: var(--text-light);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 4px;
}
.sub-label .arrow { color: var(--primary); }
</style>
</head>
<body>
<h1>细胞产品来源链路 — 设计 Demo</h1>
<p class="subtitle">展示细胞产品查看弹窗中的「来源链路」可视化方案。点击任意节点可查看详情。</p>
<div class="scenario-tabs">
<div class="scenario-tab active" onclick="switchScenario('s1', this)">场景1:简单链路</div>
<div class="scenario-tab" onclick="switchScenario('s2', this)">场景2:一生多(一个产品产出多个新产品)</div>
<div class="scenario-tab" onclick="switchScenario('s3', this)">场景3:完整追溯(往前看祖先 + 往后看子代)</div>
<div class="scenario-tab" onclick="switchScenario('s4', this)">场景4:手动录入(无祖先,但有子代)</div>
</div>
<div id="desc-s1" class="scenario-desc active">
<strong>场景1 说明:</strong>最简单的情况。一个样本经过一次生产工单,产出一个细胞产品(P1代)。这个P1代产品又经过另一个生产工单,产出了当前查看的P2代产品。每个节点之间只有一条连线。
</div>
<div id="desc-s2" class="scenario-desc" style="display:none;">
<strong>场景2 说明:</strong>当前产品(P1代)被进一步拿去做生产,<strong>一次生产产出了 3 个不同的新细胞产品</strong>。就像一块面团可以同时做出多个包子 — 连线从当前产品分叉出去,每条分叉线上面标注了对应的工单号和任务号。这就是「一生多」。
</div>
<div id="desc-s3" class="scenario-desc" style="display:none;">
<strong>场景3 说明:</strong>最完整的情况。全部在<strong>同一行</strong>展示 — 从左到右:<strong>样本 → P1(父产品) → P2(当前) → 分叉出2个P3(子产品)</strong>。祖先在左边,当前在中间,子代直接从当前节点往右延伸分叉。不分两行,一条链路从头看到尾。
</div>
<div id="desc-s4" class="scenario-desc" style="display:none;">
<strong>场景4 说明:</strong>手动录入的细胞产品是「起源」— 它没有祖先(不是从样本或别的产品生产来的),但后续它可以被拿去生产新的子产品。所以它左边没有祖先链路,但右边会分叉出子代。只有从未被用于生产的「孤立产品」才会显示空状态。
</div>
<div id="s1" class="scenario-panel active">
<div class="chain-container">
<div class="chain-section-title">来源链路</div>
<div class="chain-row">
<div class="chain-node">
<div class="node-card sample" onclick="togglePopover(this)">
<span class="node-type-tag sample-type">样本</span>
<div class="node-code">ZKZX34000627</div>
<div class="node-name">脐带血干细胞</div>
</div>
</div>
<div class="chain-connector">
<div class="connector-label">
<span class="wo">工单 WO20260519001</span>
<span class="task">任务 T20260519001</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260520001</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">P1 代</div>
</div>
</div>
<div class="chain-connector">
<div class="connector-label">
<span class="wo">工单 WO20260521002</span>
<span class="task">任务 T20260521002</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card current" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260522001</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">P2 代</div>
</div>
</div>
</div>
<div class="legend">
<div class="legend-item"><div class="legend-dot sample"></div>样本</div>
<div class="legend-item"><div class="legend-dot product"></div>细胞产品</div>
<div class="legend-item"><div class="legend-dot current-node"></div>当前查看</div>
<div class="legend-item"><div class="legend-dot line-legend"></div>生产连线(标注工单/任务号)</div>
</div>
</div>
</div>
<div id="s2" class="scenario-panel">
<div class="chain-container">
<div class="chain-section-title">来源链路</div>
<div class="sub-label"><span class="arrow">●</span> 当前产品被拿去生产,一次产出了 3 个新产品(分叉)</div>
<div class="chain-row">
<div class="chain-node">
<div class="node-card sample" onclick="togglePopover(this)">
<span class="node-type-tag sample-type">样本</span>
<div class="node-code">ZKZX34000627</div>
<div class="node-name">脐带血干细胞</div>
</div>
</div>
<div class="chain-connector">
<div class="connector-label">
<span class="wo">工单 WO20260519001</span>
<span class="task">任务 T20260519001</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card current" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260520001</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">P1 代</div>
</div>
</div>
<div class="fork-area">
<div class="fork-row">
<div class="chain-connector" style="min-width:160px;">
<div class="connector-label">
<span class="wo">工单 WO20260522003</span>
<span class="task">任务 T20260522003</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260523001</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">P2 代</div>
</div>
</div>
</div>
<div class="fork-row">
<div class="chain-connector" style="min-width:160px;">
<div class="connector-label">
<span class="wo">工单 WO20260522003</span>
<span class="task">任务 T20260522004</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260523002</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">P2 代</div>
</div>
</div>
</div>
<div class="fork-row">
<div class="chain-connector" style="min-width:160px;">
<div class="connector-label">
<span class="wo">工单 WO20260522005</span>
<span class="task">任务 T20260522005</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260523003</div>
<div class="node-name">围产期造血干细胞</div>
<div class="node-passage">P1 代</div>
</div>
</div>
</div>
</div>
</div>
<div class="legend">
<div class="legend-item"><div class="legend-dot sample"></div>样本</div>
<div class="legend-item"><div class="legend-dot product"></div>细胞产品</div>
<div class="legend-item"><div class="legend-dot current-node"></div>当前查看</div>
<div class="legend-item"><div class="legend-dot line-legend"></div>生产连线</div>
</div>
</div>
</div>
<div id="s3" class="scenario-panel">
<div class="chain-container">
<div class="chain-section-title">来源链路(完整双向追溯)</div>
<div class="sub-label"><span class="arrow">←</span> 祖先(从哪来)<span style="margin: 0 12px; color: #d1d5db;">|</span> 当前产品 <span style="margin: 0 12px; color: #d1d5db;">|</span> <span class="arrow">→</span> 子代(被拿去做什么)— 全部在同一行</div>
<div class="chain-row">
<div class="chain-node">
<div class="node-card sample" onclick="togglePopover(this)">
<span class="node-type-tag sample-type">样本</span>
<div class="node-code">ZKZX34000627</div>
<div class="node-name">脐带血干细胞</div>
</div>
</div>
<div class="chain-connector">
<div class="connector-label">
<span class="wo">工单 WO20260519001</span>
<span class="task">任务 T20260519001</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260520001</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">P1 代(父产品)</div>
</div>
</div>
<div class="chain-connector">
<div class="connector-label">
<span class="wo">工单 WO20260521002</span>
<span class="task">任务 T20260521002</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card current" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260522001</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">P2 代(当前)</div>
</div>
</div>
<div class="fork-area">
<div class="fork-row">
<div class="chain-connector" style="min-width:160px;">
<div class="connector-label">
<span class="wo">工单 WO20260523003</span>
<span class="task">任务 T20260523003</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260524001</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">P3 代(子产品1)</div>
</div>
</div>
</div>
<div class="fork-row">
<div class="chain-connector" style="min-width:160px;">
<div class="connector-label">
<span class="wo">工单 WO20260523004</span>
<span class="task">任务 T20260523004</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260524002</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">P3 代(子产品2)</div>
</div>
</div>
</div>
</div>
</div>
<div class="legend">
<div class="legend-item"><div class="legend-dot sample"></div>样本</div>
<div class="legend-item"><div class="legend-dot product"></div>细胞产品</div>
<div class="legend-item"><div class="legend-dot current-node"></div>当前查看</div>
<div class="legend-item"><div class="legend-dot line-legend"></div>生产连线</div>
</div>
</div>
</div>
<div id="s4" class="scenario-panel">
<div class="chain-container">
<div class="chain-section-title">来源链路</div>
<div class="sub-label"><span class="arrow">●</span> 手动录入产品(起源节点),无祖先,但后续被拿去生产了子产品</div>
<div class="chain-row">
<div class="chain-node">
<div class="node-card current" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260520001</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">手动录入(起源)</div>
</div>
</div>
<div class="fork-area">
<div class="fork-row">
<div class="chain-connector" style="min-width:160px;">
<div class="connector-label">
<span class="wo">工单 WO20260522003</span>
<span class="task">任务 T20260522003</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260523001</div>
<div class="node-name">脐带间充质干细胞</div>
<div class="node-passage">P1 代</div>
</div>
</div>
</div>
<div class="fork-row">
<div class="chain-connector" style="min-width:160px;">
<div class="connector-label">
<span class="wo">工单 WO20260522004</span>
<span class="task">任务 T20260522004</span>
</div>
<div class="connector-line active"></div>
<div class="connector-arrow"></div>
</div>
<div class="chain-node">
<div class="node-card" onclick="togglePopover(this)">
<span class="node-type-tag product">细胞产品</span>
<div class="node-code">CP20260523002</div>
<div class="node-name">围产期造血干细胞</div>
<div class="node-passage">P1 代</div>
</div>
</div>
</div>
</div>
</div>
<div class="legend">
<div class="legend-item"><div class="legend-dot product"></div>细胞产品</div>
<div class="legend-item"><div class="legend-dot current-node"></div>当前查看(手动录入/起源)</div>
<div class="legend-item"><div class="legend-dot line-legend"></div>生产连线</div>
</div>
</div>
</div>
<script>
function switchScenario(id, tab) {
document.querySelectorAll('.scenario-panel').forEach(function(p) { p.classList.remove('active'); });
document.querySelectorAll('.scenario-tab').forEach(function(t) { t.classList.remove('active'); });
document.querySelectorAll('.scenario-desc').forEach(function(d) { d.style.display = 'none'; });
document.getElementById(id).classList.add('active');
tab.classList.add('active');
var descMap = { s1: 'desc-s1', s2: 'desc-s2', s3: 'desc-s3', s4: 'desc-s4' };
document.getElementById(descMap[id]).style.display = 'block';
}
function togglePopover(card) {
var existing = card.querySelector('.popover-detail');
if (existing) { existing.classList.toggle('visible'); return; }
var code = card.querySelector('.node-code') ? card.querySelector('.node-code').textContent : '';
var name = card.querySelector('.node-name') ? card.querySelector('.node-name').textContent : '';
var isCurrent = card.classList.contains('current');
var isSample = card.classList.contains('sample');
var passage = card.querySelector('.node-passage') ? card.querySelector('.node-passage').textContent : '';
var status = isSample ? '已入库' : '冷冻中';
var popover = document.createElement('div');
popover.className = 'popover-detail visible';
var html = '<div class="detail-row"><span class="detail-label">编码</span><span class="detail-value">' + code + '</span></div>';
html += '<div class="detail-row"><span class="detail-label">名称</span><span class="detail-value">' + name + '</span></div>';
html += '<div class="detail-row"><span class="detail-label">状态</span><span class="detail-value">' + status + '</span></div>';
if (!isSample) { html += '<div class="detail-row"><span class="detail-label">类型</span><span class="detail-value">细胞产品</span></div>'; }
if (passage) { html += '<div class="detail-row"><span class="detail-label">代次</span><span class="detail-value">' + passage + '</span></div>'; }
if (isCurrent) { html += '<div class="detail-row"><span class="detail-label">标识</span><span class="detail-value" style="color:#10b981">当前查看</span></div>'; }
popover.innerHTML = html;
card.appendChild(popover);
setTimeout(function() {
document.addEventListener('click', function close(e) {
if (!popover.contains(e.target) && e.target !== card) {
popover.classList.remove('visible');
document.removeEventListener('click', close);
}
});
}, 10);
}
</script>
</body>
</html>
-23
View File
@@ -1,23 +0,0 @@
-- 样本接收短信通知记录表
CREATE TABLE biz_sample_sms_notification (
id VARCHAR(64) NOT NULL COMMENT 'UUID',
sample_reception_id VARCHAR(64) NOT NULL COMMENT '关联 biz_sample_reception.id',
protocol_id VARCHAR(64) DEFAULT NULL COMMENT '关联采集协议单ID(冗余,方便查询)',
sms_content VARCHAR(500) DEFAULT NULL COMMENT '提取的短信内容',
sms_notified TINYINT(1) DEFAULT 0 COMMENT '0=未通知 1=已通知',
notified_by VARCHAR(64) DEFAULT NULL COMMENT '通知操作人ID',
notified_time DATETIME DEFAULT NULL COMMENT '通知时间',
-- 公共字段
tenant_no VARCHAR(32) DEFAULT NULL,
order_num INT DEFAULT 0,
remark VARCHAR(500) DEFAULT NULL,
create_by VARCHAR(64) DEFAULT NULL,
create_dept_id VARCHAR(64) DEFAULT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64) DEFAULT NULL,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
del_flag CHAR(1) DEFAULT '0',
PRIMARY KEY (id),
KEY idx_sample_reception_id (sample_reception_id),
KEY idx_protocol_id (protocol_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='样本接收短信通知记录';
@@ -1,25 +0,0 @@
-- 样本入库短信通知记录表
CREATE TABLE biz_storage_entry_sms_notification (
id VARCHAR(64) NOT NULL COMMENT 'UUID',
storage_review_id VARCHAR(64) NOT NULL COMMENT '关联 biz_storage_release_review.id',
protocol_id VARCHAR(64) DEFAULT NULL COMMENT '关联采集协议单ID(冗余)',
sms_content VARCHAR(500) DEFAULT NULL COMMENT '生成的短信内容',
sms_notified TINYINT(1) DEFAULT 0 COMMENT '0=未通知 1=已通知',
notified_by VARCHAR(64) DEFAULT NULL COMMENT '通知操作人ID',
notified_by_name VARCHAR(64) DEFAULT NULL COMMENT '通知操作人姓名',
notified_time DATETIME DEFAULT NULL COMMENT '通知时间',
-- 公共字段
tenant_no VARCHAR(32) DEFAULT NULL,
order_num INT DEFAULT 0,
remark VARCHAR(500) DEFAULT NULL,
create_by VARCHAR(64) DEFAULT NULL,
create_dept_id VARCHAR(64) DEFAULT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64) DEFAULT NULL,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
del_flag CHAR(1) DEFAULT '0',
PRIMARY KEY (id),
KEY idx_storage_review_id (storage_review_id),
KEY idx_protocol_id (protocol_id),
KEY idx_tenant_no (tenant_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='样本入库短信通知记录';
@@ -1,3 +0,0 @@
-- Fix collation mismatch (1267) when joining biz_sample_sms_notification with biz_sample_reception
ALTER TABLE biz_sample_sms_notification
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@@ -1,24 +0,0 @@
-- 样本接收信息菜单(位于退费确认与储存证管理之间)
-- 注意:插入后需将储存证管理的 order_num 从 6 改为 7
-- 1. 新增菜单
INSERT INTO `sys_menu` (
`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`,
`is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
) VALUES (
'50000000000000000000000000000011',
'样本接收信息',
'50000000000000000000000000000001',
'C',
'sample-reception-info',
'/cell-storage/sample-reception-info/index',
'cell-storage:sample-reception-info:list',
1, '1', '1', 'zkxw', 6, NULL,
NULL, '', NOW(), NULL, NOW(), 0
);
-- 2. 后续菜单 order_num +1
UPDATE `sys_menu` SET `order_num` = `order_num` + 1
WHERE `parent_id` = '50000000000000000000000000000001'
AND `order_num` >= 6;
@@ -1,24 +0,0 @@
-- 样本入库信息菜单(位于样本接收信息与储存证管理之间)
-- 注意:插入后需将储存证管理及之后的 order_num +1
-- 1. 新增菜单
INSERT INTO `sys_menu` (
`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`,
`is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
) VALUES (
'50000000000000000000000000000012',
'样本入库信息',
'50000000000000000000000000000001',
'C',
'storage-entry-info',
'/cell-storage/storage-entry-info/index',
'cell-storage:storage-entry-info:list',
1, '1', '1', 'zkxw', 7, NULL,
NULL, '', NOW(), NULL, NOW(), 0
);
-- 2. 后续菜单 order_num +1
UPDATE `sys_menu` SET `order_num` = `order_num` + 1
WHERE `parent_id` = '50000000000000000000000000000001'
AND `order_num` >= 7;
+59
View File
@@ -0,0 +1,59 @@
-- ============================================================
-- 工单表合并迁移:biz_temp_* → 主表 + work_order_type
-- Date: 2026-05-24
-- 执行顺序:先本脚本,再 work_order_unification_phase1.sql
-- 适用:仍保留 biz_temp_qc_work_order / biz_temp_production_work_order 的旧库
-- ============================================================
-- 1. biz_qc_work_order: 加 work_order_type(若已存在则跳过,需 MySQL 8+ 或手动检查)
-- ALTER TABLE biz_qc_work_order
-- ADD COLUMN work_order_type VARCHAR(20) NOT NULL DEFAULT 'inbound'
-- COMMENT '工单类型: inbound=入库, temporary=临时' AFTER work_order_no;
-- 2. reception 关联字段改为可空(临时工单不需要)
-- ALTER TABLE biz_qc_work_order
-- MODIFY COLUMN reception_id VARCHAR(64) NULL,
-- MODIFY COLUMN protocol_sample_id VARCHAR(64) NULL,
-- MODIFY COLUMN collection_protocol_id VARCHAR(64) NULL,
-- MODIFY COLUMN reception_source VARCHAR(20) NULL DEFAULT 'cell_storage';
-- 3. 迁移临时质检工单(按 id 去重,避免重复执行)
INSERT INTO biz_qc_work_order (
id, work_order_no, work_order_type, work_order_status,
has_lab, has_third_party, third_party_fee_total,
configured_time, configured_by,
tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
t.id, t.work_order_no, 'temporary', t.work_order_status,
t.has_lab, t.has_third_party, t.third_party_fee_total,
t.configured_time, t.configured_by,
t.tenant_no, t.order_num, t.remark,
t.create_by, t.create_dept_id, t.create_time, t.update_by, t.update_time, t.del_flag
FROM biz_temp_qc_work_order t
WHERE NOT EXISTS (SELECT 1 FROM biz_qc_work_order m WHERE m.id = t.id);
-- 4. biz_production_work_order: 加 work_order_type / configured_time / configured_by(若未加请手动 ALTER)
-- ALTER TABLE biz_production_work_order ADD COLUMN work_order_type ...
-- ALTER TABLE biz_production_work_order ADD COLUMN configured_time DATETIME NULL ...
-- ALTER TABLE biz_production_work_order ADD COLUMN configured_by VARCHAR(64) NULL ...
-- 5. 迁移临时生产工单
INSERT INTO biz_production_work_order (
id, work_order_no, work_order_type, work_order_status,
configured_time, configured_by,
tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
t.id, t.work_order_no, 'temporary', t.work_order_status,
t.configured_time, t.configured_by,
t.tenant_no, t.order_num, t.remark,
t.create_by, t.create_dept_id, t.create_time, t.update_by, t.update_time, t.del_flag
FROM biz_temp_production_work_order t
WHERE NOT EXISTS (SELECT 1 FROM biz_production_work_order m WHERE m.id = t.id);
-- 6. 删除临时表(确认迁移成功后执行)
DROP TABLE IF EXISTS biz_temp_qc_work_order;
DROP TABLE IF EXISTS biz_temp_production_work_order;
+7
View File
@@ -0,0 +1,7 @@
-- ============================================================
-- 临时质检工单关联细胞产品(质检对象)
-- Date: 2026-05-24
-- ============================================================
ALTER TABLE biz_qc_work_order
ADD COLUMN cell_product_id VARCHAR(64) DEFAULT NULL COMMENT '质检对象-细胞产品ID' AFTER qc_biz_type;
@@ -0,0 +1,47 @@
-- ============================================================
-- 工单融合 Phase 1:菜单收口 + 数据修正 + 删除废弃临时表
-- Date: 2026-05-24
--
-- 执行顺序:
-- 1) 若库中仍有 biz_temp_* 表 → 先执行 merge_work_order_tables.sql
-- 2) 再执行本脚本
--
-- 说明:zkxw.sql 全量基线已无 biz_temp_* 表;本脚本 DROP 用于清理历史库残留。
-- ============================================================
-- 1. 菜单重命名与软删临时工单菜单
UPDATE sys_menu SET menu_name = '质检工单', update_time = NOW()
WHERE id = '70000000000000000000000000000007' AND del_flag = 0;
UPDATE sys_menu SET menu_name = '生产工单', update_time = NOW()
WHERE id = '90000000000000000000000000000011' AND del_flag = 0;
UPDATE sys_menu SET del_flag = 1, update_time = NOW()
WHERE id IN (
'70000000000000000000000000000012',
'90000000000000000000000000000110'
) AND del_flag = 0;
-- 2. 临时工单 reception_id 空串修正为 NULL(避免 uk_qc_wo_reception 冲突)
UPDATE biz_qc_work_order
SET reception_id = NULL,
protocol_sample_id = NULL,
collection_protocol_id = NULL,
update_time = NOW()
WHERE work_order_type = 'temporary'
AND del_flag = 0
AND (reception_id = '' OR reception_id IS NULL);
UPDATE biz_production_work_order
SET reception_id = NULL,
protocol_sample_id = NULL,
collection_protocol_id = NULL,
update_time = NOW()
WHERE work_order_type = 'temporary'
AND del_flag = 0
AND (reception_id = '' OR reception_id IS NULL);
-- 3. 删除已废弃的临时工单表(ORM/代码已不再使用)
-- 若表内仍有未迁移数据,请先执行 merge_work_order_tables.sql 再 DROP
DROP TABLE IF EXISTS biz_temp_qc_work_order;
DROP TABLE IF EXISTS biz_temp_production_work_order;
+964 -150
View File
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,729 @@
# 细胞产品库 — 来源链路展示 实施计划
> **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:** 后端在现有列表 API 中批量富化来源字段,新增 `/cell-products/{id}/chain` 链路 API。前端列表新增 4 列 + 1 个筛选,查看弹窗新增 `SourceChainGraph.vue` 组件(CSS 绘制横向 DAG)。
**Tech Stack:** FastAPI + SQLAlchemy + Pydantic v2(后端),Vue 3 + Element Plus + SCSS(前端)
**Design Spec:** `docs/superpowers/specs/2026-05-24-cell-product-source-chain-design.md`
**Demo:** `docs/demo-source-chain-graph.html`
---
## File Structure
| Action | File | Responsibility |
|--------|------|----------------|
| Modify | `backend/app/crud/cell_product.py` | 来源富化查询 + 链路递归函数 |
| Modify | `backend/app/schemas/cell_product.py` | CellProductListQuery 新增 source_type |
| Modify | `backend/app/api/production.py` | 列表路由传 source_type + 新增 chain 路由 |
| Modify | `web-admin/src/api/cell-product.ts` | 新增 fetchCellProductChain + list 参数扩展 |
| Modify | `web-admin/src/views/production/cell-product/index.vue` | 列表新增列/筛选 + 查看弹窗集成链路图 |
| Create | `web-admin/src/views/production/cell-product/SourceChainGraph.vue` | 链路图组件 |
---
### Task 1: 后端 — Schema 新增 source_type 筛选参数
**Files:**
- Modify: `backend/app/schemas/cell_product.py:127-137`
- [ ] **Step 1: 在 CellProductListQuery 中新增 source_type 字段**
在 `backend/app/schemas/cell_product.py` 的 `CellProductListQuery` 类末尾追加:
```python
source_type: Optional[str] = Field(None, alias="sourceType")
```
完整的 `CellProductListQuery` 应变为:
```python
class CellProductListQuery(BaseModel):
model_config = ConfigDict(populate_by_name=True)
page: int = 1
page_size: int = Field(20, alias="pageSize")
keyword: Optional[str] = None
product_code: Optional[str] = Field(None, alias="productCode")
product_name: Optional[str] = Field(None, alias="productName")
product_type_id: Optional[str] = Field(None, alias="productTypeId")
product_status: Optional[str] = Field(None, alias="productStatus")
passage: Optional[str] = None
source_type: Optional[str] = Field(None, alias="sourceType")
```
- [ ] **Step 2: 验证无语法错误**
Run: `cd /Users/wuzilong/work/00code/zkxw/backend && source venv/bin/activate && python -c "from app.schemas.cell_product import CellProductListQuery; print('OK')"`
Expected: `OK`
---
### Task 2: 后端 — 列表 API 增加 source_type 筛选
**Files:**
- Modify: `backend/app/api/production.py:1941-1966`
- Modify: `backend/app/crud/cell_product.py:150-208`
- [ ] **Step 1: API 路由层新增 sourceType 参数**
在 `backend/app/api/production.py` 的 `list_cell_products` 函数中:
1. 在参数列表 `passage` 之后新增:`sourceType: Optional[str] = None,`
2. 在 `cell_product_svc.list_cell_products(...)` 调用中新增:`source_type=sourceType,`
修改后的函数签名和调用:
```python
@router.get("/cell-products")
async def list_cell_products(
page: int = Query(1, ge=1),
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
keyword: Optional[str] = None,
productCode: Optional[str] = None,
productName: Optional[str] = None,
productTypeId: Optional[str] = None,
productStatus: Optional[str] = None,
passage: Optional[str] = None,
sourceType: Optional[str] = None,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
result = cell_product_svc.list_cell_products(
db, tenant_no=tenant_no, page=page, page_size=pageSize,
keyword=keyword,
product_code=productCode, product_name=productName,
product_type_id=productTypeId, product_status=productStatus,
passage=passage, source_type=sourceType,
user_info=user,
)
items = result.get("items", [])
total = result.get("total", 0)
tp = cell_product_svc.total_pages(total, pageSize)
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
```
- [ ] **Step 2: CRUD 层 list_cell_products 新增 source_type 过滤**
在 `backend/app/crud/cell_product.py` 的 `list_cell_products` 函数中:
1. 在参数列表 `passage` 之后新增:`source_type: Optional[str] = None,`
2. 在 `_filtered()` 内部、`if passage:` 块之后新增:
```python
if source_type:
q0 = q0.filter(BizCellProduct.source_type == source_type)
```
- [ ] **Step 3: 验证无语法错误**
Run: `cd /Users/wuzilong/work/00code/zkxw/backend && source venv/bin/activate && python -c "from app.crud.cell_product import list_cell_products; print('OK')"`
Expected: `OK`
---
### Task 3: 后端 — 列表富化查询新增来源字段
**Files:**
- Modify: `backend/app/crud/cell_product.py:72-142`(`_enrich_product_dicts` 函数)
- [ ] **Step 1: 在 `_enrich_product_dicts` 中新增来源信息批量富化**
在 `_enrich_product_dicts` 函数中,在 `slot_map` 查询之后、`for item in items:` 循环之前,新增以下代码块:
```python
# --- 来源信息富化 ---
from app.models.models import BizProductionTask, BizProductionWorkOrder
task_ids = {i.get("productionTaskId") for i in items if i.get("productionTaskId")}
task_map: Dict[str, Any] = {}
task_to_wo: Dict[str, str] = {}
wo_map: Dict[str, Any] = {}
if task_ids:
tasks = (
db.query(BizProductionTask)
.filter(
BizProductionTask.id.in_(task_ids),
BizProductionTask.del_flag == 0,
)
.all()
)
for t in tasks:
task_map[t.id] = t
if t.work_order_id:
task_to_wo[t.id] = t.work_order_id
wo_ids = set(task_to_wo.values())
if wo_ids:
wos = (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.id.in_(wo_ids),
BizProductionWorkOrder.del_flag == 0,
)
.all()
)
wo_map = {w.id: w for w in wos}
source_type_labels = {"production": "生产入库", "manual": "手动录入"}
```
然后在 `for item in items:` 循环内,在 `item["storageSlotName"] = ...` 之后追加:
```python
# 来源信息
src_type = item.get("sourceType", "")
item["sourceTypeLabel"] = source_type_labels.get(src_type, src_type or "")
tid = item.get("productionTaskId") or ""
if tid and tid in task_map:
t_obj = task_map[tid]
item["sourceTaskNo"] = t_obj.task_no or ""
wo_id = task_to_wo.get(tid)
if wo_id and wo_id in wo_map:
wo_obj = wo_map[wo_id]
item["sourceWorkOrderNo"] = wo_obj.work_order_no or ""
item["sourceProtocolNo"] = wo_obj.protocol_no or ""
item["sourceReceptionSource"] = wo_obj.reception_source or ""
else:
item["sourceWorkOrderNo"] = ""
item["sourceProtocolNo"] = ""
item["sourceReceptionSource"] = ""
else:
item["sourceTaskNo"] = ""
item["sourceWorkOrderNo"] = ""
item["sourceProtocolNo"] = ""
item["sourceReceptionSource"] = ""
```
注意:需要确保 `_row_to_dict` 不需要修改 — 它已经返回 `sourceType` 和 `productionTaskId`,新的富化字段是在 `_enrich_product_dicts` 中动态追加的。
- [ ] **Step 2: 验证无语法错误**
Run: `cd /Users/wuzilong/work/00code/zkxw/backend && source venv/bin/activate && python -c "from app.crud.cell_product import _enrich_product_dicts; print('OK')"`
Expected: `OK`
- [ ] **Step 3: 启动后端验证列表 API**
Run: `cd /Users/wuzilong/work/00code/zkxw/backend && source venv/bin/activate && python -c "
from app.core.database import SessionLocal
from app.crud.cell_product import list_cell_products
db = SessionLocal()
try:
result = list_cell_products(db, tenant_no='zkxw', page=1, page_size=3)
for item in result.get('items', []):
print(f\"{item.get('productCode')} | src={item.get('sourceTypeLabel')} | proto={item.get('sourceProtocolNo')} | wo={item.get('sourceWorkOrderNo')} | task={item.get('sourceTaskNo')}\")
finally:
db.close()
"`
Expected: 每行输出产品编码及来源字段(可能为空字符串)
---
### Task 4: 后端 — 新增链路 API
**Files:**
- Modify: `backend/app/crud/cell_product.py`(文件末尾追加)
- Modify: `backend/app/api/production.py`(在 `GET /cell-products/{product_id}` 路由之后追加)
- [ ] **Step 1: 在 `cell_product.py` 末尾新增链路递归查询函数**
在 `backend/app/crud/cell_product.py` 文件末尾追加:
```python
# ---------------------------------------------------------------------------
# 来源链路 API
# ---------------------------------------------------------------------------
_MAX_CHAIN_DEPTH = 10
_SOURCE_TYPE_LABELS = {"production": "生产入库", "manual": "手动录入"}
def _product_node(obj: BizCellProduct, is_current: bool = False) -> Dict[str, Any]:
return {
"id": obj.id,
"productCode": obj.product_code or "",
"productName": obj.product_name or "",
"nodeType": "cell_product",
"productStatus": obj.product_status or "",
"passage": obj.passage or "",
"isCurrent": is_current,
}
def _build_ancestor_chain(
db: Session, product: BizCellProduct, depth: int
) -> List[Dict[str, Any]]:
if depth >= _MAX_CHAIN_DEPTH:
return []
task_id = product.production_task_id
if not task_id:
return []
from app.models.models import BizProductionTask, BizProductionWorkOrder
from app.models.models import BizProductionTaskInput
task = (
db.query(BizProductionTask)
.filter(BizProductionTask.id == task_id, BizProductionTask.del_flag == 0)
.first()
)
if not task:
return []
wo = None
edge: Dict[str, Any] = {"workOrderNo": "", "workOrderType": "", "taskNo": task.task_no or "", "taskName": task.task_name or ""}
if task.work_order_id:
wo = (
db.query(BizProductionWorkOrder)
.filter(BizProductionWorkOrder.id == task.work_order_id, BizProductionWorkOrder.del_flag == 0)
.first()
)
if wo:
edge["workOrderNo"] = wo.work_order_no or ""
edge["workOrderType"] = wo.reception_source or ""
# 检查投入物中是否有父细胞产品
parent_input = (
db.query(BizProductionTaskInput)
.filter(
BizProductionTaskInput.task_id == task_id,
BizProductionTaskInput.input_type == "cell_product",
BizProductionTaskInput.del_flag == 0,
)
.first()
)
if parent_input and parent_input.input_id:
parent_product = (
db.query(BizCellProduct)
.filter(BizCellProduct.id == parent_input.input_id, BizCellProduct.del_flag == 0)
.first()
)
if parent_product:
return [{"node": _product_node(parent_product), "edge": edge, "parent": _build_ancestor_chain(db, parent_product, depth + 1)}]
# 检查投入物中是否有样本(终止节点)
sample_input = (
db.query(BizProductionTaskInput)
.filter(
BizProductionTaskInput.task_id == task_id,
BizProductionTaskInput.input_type == "sample",
BizProductionTaskInput.del_flag == 0,
)
.first()
)
if sample_input and sample_input.input_id:
from app.models.models import BizProtocolSample
sample = (
db.query(BizProtocolSample)
.filter(BizProtocolSample.id == sample_input.input_id, BizProtocolSample.del_flag == 0)
.first()
)
if sample:
sample_node = {
"id": sample.id,
"productCode": sample.sample_code or "",
"productName": "",
"nodeType": "sample",
"sampleCode": sample.sample_code or "",
"sampleType": sample.sample_type or "",
"isCurrent": False,
}
return [{"node": sample_node, "edge": edge, "parent": None}]
return []
def _build_descendant_chain(
db: Session, product_id: str, depth: int
) -> List[Dict[str, Any]]:
if depth >= _MAX_CHAIN_DEPTH:
return []
from app.models.models import BizProductionTaskInput, BizProductionTaskOutput, BizProductionTask, BizProductionWorkOrder
# 找到引用此产品作为投入物的所有任务
input_rows = (
db.query(BizProductionTaskInput)
.filter(
BizProductionTaskInput.input_type == "cell_product",
BizProductionTaskInput.input_id == product_id,
BizProductionTaskInput.del_flag == 0,
)
.all()
)
if not input_rows:
return []
task_ids = list({r.task_id for r in input_rows})
tasks = (
db.query(BizProductionTask)
.filter(BizProductionTask.id.in_(task_ids), BizProductionTask.del_flag == 0)
.all()
)
task_by_id = {t.id: t for t in tasks}
wo_ids = list({t.work_order_id for t in tasks if t.work_order_id})
wo_by_id: Dict[str, Any] = {}
if wo_ids:
wos = (
db.query(BizProductionWorkOrder)
.filter(BizProductionWorkOrder.id.in_(wo_ids), BizProductionWorkOrder.del_flag == 0)
.all()
)
wo_by_id = {w.id: w for w in wos}
# 对每个任务,找产出
result: List[Dict[str, Any]] = []
output_rows = (
db.query(BizProductionTaskOutput)
.filter(
BizProductionTaskOutput.task_id.in_(task_ids),
BizProductionTaskOutput.del_flag == 0,
)
.all()
)
child_product_ids = list({o.cell_product_id for o in output_rows if o.cell_product_id})
child_products = {}
if child_product_ids:
cps = (
db.query(BizCellProduct)
.filter(BizCellProduct.id.in_(child_product_ids), BizCellProduct.del_flag == 0)
.all()
)
child_products = {cp.id: cp for cp in cps}
for o in output_rows:
cp = child_products.get(o.cell_product_id)
if not cp:
continue
t = task_by_id.get(o.task_id)
edge: Dict[str, Any] = {"workOrderNo": "", "workOrderType": "", "taskNo": "", "taskName": ""}
if t:
edge["taskNo"] = t.task_no or ""
edge["taskName"] = t.task_name or ""
if t.work_order_id and t.work_order_id in wo_by_id:
wo_obj = wo_by_id[t.work_order_id]
edge["workOrderNo"] = wo_obj.work_order_no or ""
edge["workOrderType"] = wo_obj.reception_source or ""
result.append({
"node": _product_node(cp),
"edge": edge,
"children": _build_descendant_chain(db, cp.id, depth + 1),
})
return result
def get_product_chain(db: Session, product_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
product = get_cell_product(db, product_id, tenant_no)
if not product:
return None
return {
"current": _product_node(product, is_current=True),
"ancestors": _build_ancestor_chain(db, product, 0),
"descendants": _build_descendant_chain(db, product.id, 0),
}
```
- [ ] **Step 2: 在 production.py 新增链路路由**
在 `backend/app/api/production.py` 的 `@router.get("/cell-products/{product_id}")` 路由之后(约 line 1979 之后)追加新路由:
```python
@router.get("/cell-products/{product_id}/chain")
async def get_cell_product_chain(
product_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
chain = cell_product_svc.get_product_chain(db, product_id, tenant_no)
if not chain:
raise HTTPException(status_code=404, detail="细胞产品不存在")
return success_response(chain)
```
注意:此路由必须放在 `@router.get("/cell-products/{product_id}")` 之前或之后都可以,因为 FastAPI 按注册顺序匹配,`/cell-products/next-code` 已经在前面。但 `/chain` 是固定路径不会与 `{product_id}` 冲突 — 实际上 `/cell-products/{product_id}/chain` 和 `/cell-products/{product_id}` 是不同的路径模式,不存在冲突。
- [ ] **Step 3: 验证链路 API**
Run: `cd /Users/wuzilong/work/00code/zkxw/backend && source venv/bin/activate && python -c "
from app.core.database import SessionLocal
from app.crud.cell_product import get_product_chain
db = SessionLocal()
try:
from app.crud.cell_product import list_cell_products
prods = list_cell_products(db, tenant_no='zkxw', page=1, page_size=1)
items = prods.get('items', [])
if items:
pid = items[0]['id']
chain = get_product_chain(db, pid, 'zkxw')
if chain:
cur = chain['current']
print(f'Current: {cur[\"productCode\"]}')
print(f'Ancestors: {len(chain[\"ancestors\"])} levels')
print(f'Descendants: {len(chain[\"descendants\"])} branches')
else:
print('No chain data')
else:
print('No products found')
finally:
db.close()
"`
Expected: 输出当前产品的链路信息(祖先层数和子代分支数)
---
### Task 5: 前端 — API 层扩展
**Files:**
- Modify: `web-admin/src/api/cell-product.ts`
- [ ] **Step 1: 新增 fetchCellProductChain 函数 + fetchCellProductList 参数扩展**
将 `web-admin/src/api/cell-product.ts` 完整替换为:
```typescript
import request from '@/utils/http'
export function fetchCellProductList(params?: {
page?: number
pageSize?: number
keyword?: string
productStatus?: string
passage?: string
sourceType?: string
}) {
return request.get<{
list: any[]
total: number
page: number
pageSize: number
totalPages: number
}>({ url: '/web/admin/cell-products', params })
}
export function fetchCellProductDetail(id: string) {
return request.get<any>({ url: `/web/admin/cell-products/${id}` })
}
export function fetchCreateCellProduct(data: Record<string, any>) {
return request.post({ url: '/web/admin/cell-products', params: data })
}
export function fetchUpdateCellProduct(id: string, data: Record<string, any>) {
return request.put({ url: `/web/admin/cell-products/${id}`, params: data })
}
export function fetchDeleteCellProduct(id: string) {
return request.del({ url: `/web/admin/cell-products/${id}` })
}
export function fetchNextCellProductCode(count = 1) {
return request.get<{ code: string; codes?: string[] }>({
url: '/web/admin/cell-products/next-code',
params: count > 1 ? { count } : undefined
})
}
export function fetchCellProductChain(productId: string) {
return request.get<any>({ url: `/web/admin/cell-products/${productId}/chain` })
}
```
- [ ] **Step 2: 验证无 TS 错误**
Run: `cd /Users/wuzilong/work/00code/zkxw/web-admin && npx vue-tsc --noEmit --pretty 2>&1 | head -20`
Expected: 无新增错误(可能有预存的)
---
### Task 6: 前端 — 列表页新增列和筛选
**Files:**
- Modify: `web-admin/src/views/production/cell-product/index.vue`
- [ ] **Step 1: 搜索表单新增来源类型筛选**
在 `index.vue` 的搜索表单中找到 `productStatus` 的 `el-select` 之后,新增一个筛选下拉:
```html
<el-select v-model="searchForm.sourceType" placeholder="来源类型" clearable style="width: 130px">
<el-option label="生产入库" value="production" />
<el-option label="手动录入" value="manual" />
</el-select>
```
同时在 `searchForm` 的响应式定义中增加 `sourceType: ''` 字段。
在调用 `fetchCellProductList` 时传入 `sourceType: searchForm.sourceType || undefined`。
- [ ] **Step 2: 表格新增 4 列**
在 `el-table` 中 `productStatus`(状态)列之后,新增:
```html
<el-table-column label="来源类型" prop="sourceTypeLabel" min-width="120">
<template #default="{ row }">
<el-tag v-if="row.sourceTypeLabel" :type="row.sourceType === 'production' ? 'success' : 'info'" size="small">
{{ row.sourceTypeLabel }}
</el-tag>
<span v-else>—</span>
</template>
</el-table-column>
<el-table-column label="来源协议号" prop="sourceProtocolNo" min-width="160" show-overflow-tooltip>
<template #default="{ row }">{{ row.sourceProtocolNo || '—' }}</template>
</el-table-column>
<el-table-column label="来源工单号" prop="sourceWorkOrderNo" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.sourceWorkOrderNo || '—' }}</template>
</el-table-column>
<el-table-column label="来源任务号" prop="sourceTaskNo" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.sourceTaskNo || '—' }}</template>
</el-table-column>
```
- [ ] **Step 3: 验证列表页渲染**
Run: `cd /Users/wuzilong/work/00code/zkxw/web-admin && pnpm dev`
浏览器访问 `http://localhost:3006/#/production/cell-product`,确认:
1. 列表新增 4 列可见
2. 搜索栏有「来源类型」下拉
3. 选择「生产入库」筛选后列表正确过滤
---
### Task 7: 前端 — 新增 SourceChainGraph 链路图组件
**Files:**
- Create: `web-admin/src/views/production/cell-product/SourceChainGraph.vue`
- [ ] **Step 1: 创建链路图组件**
新建 `web-admin/src/views/production/cell-product/SourceChainGraph.vue`,实现以下功能:
- 接收 `productId` 和 `visible` props
- `visible` 变为 true 时调用 `fetchCellProductChain(productId)` 获取链路数据
- 渲染横向 DAG:祖先节点 → 当前节点 → 子代节点
- 节点卡片样式区分:样本(浅蓝)、细胞产品(浅绿)、当前产品(深绿边框 + 角标)
- 节点之间用 CSS border + 伪元素画水平连线和箭头
- 连线上方显示工单号和任务号标签
- 1对多分支使用竖线 + 横线分叉
- 手动录入产品(起源节点)无祖先但可能有子代,只有**既无祖先又无子代的孤立产品**才显示空状态文字「该产品暂无来源及衍生链路信息」
- 节点 hover 时用 `el-popover` 显示详情
组件核心模板结构:
```vue
<template>
<div class="source-chain-graph" v-loading="loading">
<div v-if="chainData" class="chain-container">
<div class="chain-section-title">来源链路</div>
<template v-if="chainData.ancestors.length || chainData.descendants.length">
<div class="chain-row">
<template v-for="(ancestor, idx) in flattenedAncestors" :key="'a-' + idx">
<ChainNodeCard :node="ancestor.node" />
<ChainConnector :edge="ancestor.edge" />
</template>
<ChainNodeCard :node="chainData.current" :is-current="true" />
<template v-if="chainData.descendants.length">
<ForkBranch :items="chainData.descendants" />
</template>
</div>
</template>
<div v-else class="empty-state">该产品暂无来源及衍生链路信息</div>
</div>
</div>
</template>
```
注意:实际实现中 `ChainNodeCard`、`ChainConnector`、`ForkBranch` 可以是同文件内的子组件或直接内联模板。核心样式参考 `docs/demo-source-chain-graph.html`。
- [ ] **Step 2: 验证组件无 TS 错误**
Run: `cd /Users/wuzilong/work/00code/zkxw/web-admin && npx vue-tsc --noEmit --pretty 2>&1 | head -20`
Expected: 无新增错误
---
### Task 8: 前端 — 查看弹窗集成链路图
**Files:**
- Modify: `web-admin/src/views/production/cell-product/index.vue`
- [ ] **Step 1: 在查看弹窗中集成 SourceChainGraph**
在 `index.vue` 中:
1. 导入组件:
```typescript
import SourceChainGraph from './SourceChainGraph.vue'
```
2. 在 `SystemViewDialog` 的 sections 配置中新增第三个 section(在产品图片 section 之后):
```typescript
{
label: '来源链路',
slot: 'sourceChain',
}
```
3. 在 `SystemViewDialog` 的模板中新增 slot:
```html
<template #sourceChain>
<SourceChainGraph :product-id="currentRow?.id" :visible="viewDialogVisible" />
</template>
```
注意:需要确认 `SystemViewDialog` 组件是否支持 `slot` 类型的 section。如果不支持,需要查看该组件源码并添加 slot 支持(通常是在 section 渲染时判断 `slot` 字段,用 `<slot :name="section.slot" />` 渲染自定义内容)。
- [ ] **Step 2: 端到端验证**
1. 启动后端:`cd backend && source venv/bin/activate && ./start-local.sh`
2. 启动前端:`cd web-admin && pnpm dev`
3. 浏览器访问 `http://localhost:3006/#/production/cell-product`
4. 点击任意产品的「查看」按钮
5. 确认查看弹窗中有「来源链路」tab/section
6. 确认链路图正确展示节点和连线
7. 确认节点点击可显示详情 Popover
8. 确认手动录入产品显示空状态
---
### Task 9: 提交
- [ ] **Step 1: 检查变更范围**
Run: `cd /Users/wuzilong/work/00code/zkxw && git status`
Expected: 仅涉及以下文件:
- `backend/app/crud/cell_product.py`
- `backend/app/schemas/cell_product.py`
- `backend/app/api/production.py`
- `web-admin/src/api/cell-product.ts`
- `web-admin/src/views/production/cell-product/index.vue`
- `web-admin/src/views/production/cell-product/SourceChainGraph.vue`(新建)
- `docs/demo-source-chain-graph.html`
- `docs/superpowers/specs/2026-05-24-cell-product-source-chain-design.md`
- `docs/superpowers/plans/2026-05-24-cell-product-source-chain.md`
- [ ] **Step 2: 提交**
```bash
git add backend/app/crud/cell_product.py backend/app/schemas/cell_product.py backend/app/api/production.py web-admin/src/api/cell-product.ts web-admin/src/views/production/cell-product/ docs/
git commit -m "feat(cell-product): 添加细胞产品来源链路展示功能
列表新增来源类型、来源协议号、来源工单号、来源任务号四列,
支持来源类型筛选。查看弹窗新增来源链路图,展示祖先-当前-子代
双向追溯链路(横向 DAG,CSS 绘制连线),支持1对多分叉。"
```
@@ -0,0 +1,305 @@
# 工单表合并重构实施计划
> **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:** 将入库+临时工单表合并为单表(`work_order_type` 字段区分类型),彻底消除多表 JOIN 复杂度。
**Architecture:** 质检工单合并为 `biz_qc_work_order`(加 `work_order_type`),生产工单合并为 `biz_production_work_order`(加 `work_order_type`)。任务表保持不变(`biz_qc_task` / `biz_production_task` 的 `order_source` 保留)。前端每个工单类型一个页面,根据 `work_order_type` 条件展示不同字段。
**Tech Stack:** MySQL 5.7 DDL / Python 3.9 FastAPI SQLAlchemy / Vue 3 TypeScript Element Plus
---
## 影响文件清单
### 删除(合并后不再需要)
- `backend/app/models/models.py` 中 `BizTempQcWorkOrder`、`BizTempProductionWorkOrder` 两个 class
- `backend/app/schemas/temp_qc_work_order.py`
- `backend/app/schemas/temp_production_work_order.py`
- `backend/app/crud/temp_qc_work_order.py`
- `backend/app/crud/temp_production_work_order.py`
- `web-admin/src/api/temp-qc-work-order.ts`
- `web-admin/src/api/temp-production-work-order.ts`
- `web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue`
- `web-admin/src/views/production/temp-production-work-order/index.vue`
- `web-admin/src/router/modules/sqxp.ts` 中临时工单路由条目
### 修改
- `docs/sql/` — 新建迁移 SQL
- `backend/app/models/models.py` — 加 `work_order_type`,reception 字段改 nullable,删 temp model
- `backend/app/crud/qc_work_order.py` — 合并临时工单的创建/配置/完成逻辑
- `backend/app/crud/production_work_order.py` — 合并临时工单的创建/配置/完成逻辑
- `backend/app/crud/qc_task.py` — **恢复** INNER JOIN(不再需要 LEFT JOIN + 子查询 hack)
- `backend/app/crud/production_task.py` — 同上,简化批量查询
- `backend/app/api/qc.py` — 合并临时工单 API 端点,删除 temp import
- `backend/app/api/production.py` — 同上
- `backend/app/schemas/qc_work_order.py` — 新增 `work_order_type` 字段
- `web-admin/src/api/qc-work-order.ts` 或 `business.ts` — 新增临时工单 API
- `web-admin/src/views/quality-inspection/qc-work-order/index.vue` — 支持临时工单创建/筛选
- `web-admin/src/views/production/production-work-order/index.vue` — 同上
---
## Task 1: DDL 迁移 SQL
**Files:**
- Create: `docs/sql/merge_work_order_tables.sql`
- [ ] **Step 1: 编写迁移 SQL**
```sql
-- ============================================================
-- 工单表合并迁移:入库+临时 → 单表 + work_order_type
-- Date: 2026-05-24
-- 执行顺序:必须先执行,再部署后端代码
-- ============================================================
-- 1. biz_qc_work_order: 加 work_order_type,现有数据全部设 inbound
ALTER TABLE biz_qc_work_order
ADD COLUMN work_order_type VARCHAR(20) NOT NULL DEFAULT 'inbound'
COMMENT '工单类型: inbound=入库, temporary=临时' AFTER work_order_no;
-- 2. biz_qc_work_order: reception 关联字段改为可空(临时工单不需要)
ALTER TABLE biz_qc_work_order
MODIFY COLUMN reception_id VARCHAR(64) NULL COMMENT '样本接收ID',
MODIFY COLUMN protocol_sample_id VARCHAR(64) NULL COMMENT '协议样本ID',
MODIFY COLUMN collection_protocol_id VARCHAR(64) NULL COMMENT '采集协议ID',
MODIFY COLUMN reception_source VARCHAR(20) NULL DEFAULT 'cell_storage' COMMENT '来源: cell_storage/research_donation';
-- 3. 迁移临时质检工单数据到主表
INSERT INTO biz_qc_work_order (
id, work_order_no, work_order_type, work_order_status,
has_lab, has_third_party, third_party_fee_total,
configured_time, configured_by,
tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
id, work_order_no, 'temporary', work_order_status,
has_lab, has_third_party, third_party_fee_total,
configured_time, configured_by,
tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
FROM biz_temp_qc_work_order;
-- 4. biz_production_work_order: 加 work_order_type
ALTER TABLE biz_production_work_order
ADD COLUMN work_order_type VARCHAR(20) NOT NULL DEFAULT 'inbound'
COMMENT '工单类型: inbound=入库, temporary=临时' AFTER work_order_no;
-- 5. biz_production_work_order: 加 configured_time/configured_by(临时工单需要)
ALTER TABLE biz_production_work_order
ADD COLUMN configured_time DATETIME NULL COMMENT '配置时间' AFTER work_order_status,
ADD COLUMN configured_by VARCHAR(64) NULL COMMENT '配置人' AFTER configured_time;
-- 6. biz_production_work_order: reception 关联字段改为可空
ALTER TABLE biz_production_work_order
MODIFY COLUMN reception_id VARCHAR(64) NULL COMMENT '样本接收ID',
MODIFY COLUMN protocol_sample_id VARCHAR(64) NULL COMMENT '协议样本ID',
MODIFY COLUMN collection_protocol_id VARCHAR(64) NULL COMMENT '采集协议ID',
MODIFY COLUMN reception_source VARCHAR(20) NULL DEFAULT 'cell_storage' COMMENT '来源';
-- 7. 迁移临时生产工单数据到主表
INSERT INTO biz_production_work_order (
id, work_order_no, work_order_type, work_order_status,
configured_time, configured_by,
tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
id, work_order_no, 'temporary', work_order_status,
configured_time, configured_by,
tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
FROM biz_temp_production_work_order;
-- 8. 删除临时表(确认数据迁移成功后执行)
DROP TABLE IF EXISTS biz_temp_qc_work_order;
DROP TABLE IF EXISTS biz_temp_production_work_order;
```
- [ ] **Step 2: 手动执行前备份**
```bash
mysqldump -u root -p zkxw biz_qc_work_order biz_temp_qc_work_order biz_production_work_order biz_temp_production_work_order > /tmp/work_order_backup_$(date +%Y%m%d).sql
```
---
## Task 2: 后端 Model 更新
**Files:**
- Modify: `backend/app/models/models.py`
- [ ] **Step 1: 更新 BizQcWorkOrder 模型**
- 在 `work_order_no` 后加 `work_order_type = Column(String(20), nullable=False, default="inbound")`
- 将 `reception_id`、`protocol_sample_id`、`collection_protocol_id` 改为 `nullable=True`
- 将 `reception_source` 改为 `nullable=True`
- [ ] **Step 2: 更新 BizProductionWorkOrder 模型**
- 加 `work_order_type = Column(String(20), nullable=False, default="inbound")`
- 加 `configured_time = Column(DateTime, nullable=True)`
- 加 `configured_by = Column(String(64), nullable=True)`
- 将 `reception_id`、`protocol_sample_id`、`collection_protocol_id` 改为 `nullable=True`
- 将 `reception_source` 改为 `nullable=True`
- [ ] **Step 3: 删除 BizTempQcWorkOrder 和 BizTempProductionWorkOrder 类**
- [ ] **Step 4: 验证 import**
```bash
cd backend && source venv/bin/activate && python -c "from app.main import app; print('OK')"
```
---
## Task 3: 后端 CRUD 合并 — 质检工单
**Files:**
- Modify: `backend/app/crud/qc_work_order.py`
- Delete: `backend/app/crud/temp_qc_work_order.py`
- [ ] **Step 1: 在 qc_work_order.py 中新增临时工单函数**
- 将 `temp_qc_work_order.py` 中的 `create_temp_qc_work_order`、`configure_temp_qc_work_order`、`list_temp_qc_work_orders`、`get_temp_qc_work_order_detail`、`try_complete_temp_qc_work_order`、`delete_temp_qc_work_order` 合并进 `qc_work_order.py`
- 修改工单号生成逻辑:根据 `work_order_type` 选择前缀(inbound=ZJ,temporary=LSZJ)
- 修改 list 查询:加 `work_order_type` 过滤
- 修改 configure 逻辑:使用同一个 `BizQcWorkOrder` 模型
- 修改 task 生成:保持 `order_source` 逻辑不变
- [ ] **Step 2: 删除 temp_qc_work_order.py**
---
## Task 4: 后端 CRUD 合并 — 生产工单
**Files:**
- Modify: `backend/app/crud/production_work_order.py`
- Delete: `backend/app/crud/temp_production_work_order.py`
- [ ] **Step 1: 合并临时生产工单函数**
- 同 Task 3 思路:将 temp 函数合并进主文件
- 工单号前缀:inbound=SC,temporary=LSSC
- 保留工艺路线克隆逻辑
- 保留 `order_source` 逻辑
- [ ] **Step 2: 删除 temp_production_work_order.py**
---
## Task 5: 后端 API + Schema 更新
**Files:**
- Modify: `backend/app/api/qc.py`
- Modify: `backend/app/api/production.py`
- Modify: `backend/app/schemas/qc_work_order.py`
- Delete: `backend/app/schemas/temp_qc_work_order.py`
- Delete: `backend/app/schemas/temp_production_work_order.py`
- [ ] **Step 1: 更新 qc_work_order schema**
- 在 `QcWorkOrderCreateBody` 中加 `work_order_type: str = "inbound"`
- 新增 `TempQcWorkOrderCreateBody`(remark only)或复用现有 schema
- 新增 `QcWorkOrderConfigureBody`(合并临时工单配置的 schema)
- [ ] **Step 2: 合并 qc.py API 端点**
- 将临时质检工单的 5 个端点合并到现有工单端点
- 新增 `POST /web/admin/qc-work-orders` 支持 `work_order_type` 参数
- 新增 `GET /web/admin/qc-work-orders` 支持 `work_order_type` 过滤
- 新增 `POST /web/admin/qc-work-orders/{id}/configure` 端点(从 temp 端点移过来)
- 删除 temp 相关 import 和端点
- [ ] **Step 3: 合并 production.py API 端点**
- 同上思路
- [ ] **Step 4: 删除 temp schema 文件**
- [ ] **Step 5: 验证 import**
---
## Task 6: 恢复 qc_task.py 的简单 INNER JOIN
**Files:**
- Modify: `backend/app/crud/qc_task.py`
- [ ] **Step 1: 恢复 list_tasks 为 INNER JOIN**
- 将 `outerjoin(BizQcWorkOrder, ...)` 恢复为 `join(BizQcWorkOrder, ...)`
- 删除 `BizTempQcWorkOrder` import
- 删除临时工单子查询(`temp_wo_sq`)
- 删除临时工单后处理(`temp_wo_ids`、`temp_wo_map`、if/elif 分支)
- 恢复原来的 ORDER BY 子句(含 `BizQcWorkOrder.work_order_no.desc()`)
- [ ] **Step 2: 验证**
```bash
cd backend && source venv/bin/activate && python -c "from app.crud.qc_task import list_tasks; print('OK')"
```
---
## Task 7: 简化 production_task.py
**Files:**
- Modify: `backend/app/crud/production_task.py`
- [ ] **Step 1: 简化 list_tasks 批量查询**
- 删除 `BizTempProductionWorkOrder` import
- 删除临时工单子查询和批量补充逻辑
- 恢复为只查 `BizProductionWorkOrder` 的简单模式
---
## Task 8: 前端 API + 页面合并
**Files:**
- Modify: `web-admin/src/api/qc-work-order.ts` 或 `business.ts`
- Modify: `web-admin/src/api/production-work-order.ts` 或 `business.ts`
- Modify: `web-admin/src/views/quality-inspection/qc-work-order/index.vue`
- Modify: `web-admin/src/views/production/production-work-order/index.vue`
- Delete: `web-admin/src/api/temp-qc-work-order.ts`
- Delete: `web-admin/src/api/temp-production-work-order.ts`
- Delete: `web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue`
- Delete: `web-admin/src/views/production/temp-production-work-order/index.vue`
- Modify: `web-admin/src/router/modules/sqxp.ts`
- [ ] **Step 1: 将临时工单 API 合并到主 API 模块**
- 在 qc-work-order API 中新增 configure 端点调用
- 新增 work_order_type 参数支持
- 删除 temp API 文件
- [ ] **Step 2: 更新 qc-work-order/index.vue**
- 新增"工单类型"筛选(入库/临时)
- 新增创建时选择类型(入库走接收流程,临时直接创建)
- 新增配置检测弹窗(从 temp 页面移植)
- 条件显示 reception 相关列(仅入库类型显示样本编码、协议号等)
- [ ] **Step 3: 更新 production-work-order/index.vue**
- 同上思路
- [ ] **Step 4: 更新路由 sqxp.ts**
- 删除临时工单路由条目
- 主工单路由保持不变
- [ ] **Step 5: 删除临时工单页面和 API 文件**
- [ ] **Step 6: 前端构建验证**
```bash
cd web-admin && pnpm build
```
---
## Task 9: 全局验证
- [ ] **Step 1: 后端 import 验证**
```bash
cd backend && source venv/bin/activate && python -c "from app.main import app; print('OK')"
```
- [ ] **Step 2: 前端构建验证**
```bash
cd web-admin && pnpm build
```
- [ ] **Step 3: 手动测试流程**
- 创建入库质检工单 → 配置 → 生成任务 ✓
- 创建临时质检工单 → 配置 → 生成任务 ✓
- 质检任务列表显示两种来源 ✓
- 创建入库生产工单 → 配置 → 生成任务 ✓
- 创建临时生产工单 → 配置 → 生成任务 ✓
@@ -0,0 +1,911 @@
# 临时工单模块实施计划
> **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:** 新建 2 张独立临时工单表(极简字段),现有任务表新增 `order_source` 字段区分入库/临时来源。后端 CRUD 独立,复用现有任务创建函数。前端新增 2 个标准列表页,现有任务列表新增"来源"Tag 列。
**Tech Stack:** Python 3.9 / FastAPI / SQLAlchemy / Pydantic v2 / MySQL 5.7 / Vue 3 / TypeScript / Element Plus / Tailwind CSS 4
**Design Spec:** `docs/superpowers/specs/2026-05-24-temporary-work-order-design.md`
---
## File Map
### 新建文件
| 文件 | 职责 |
|------|------|
| `backend/app/crud/temp_qc_work_order.py` | 临时质检工单 CRUD(创建、列表、详情、配置检测项目→生成任务、删除) |
| `backend/app/crud/temp_production_work_order.py` | 临时生产工单 CRUD(创建、列表、详情、配置→生成任务、删除) |
| `backend/app/schemas/temp_qc_work_order.py` | 临时质检工单 Pydantic Schema |
| `backend/app/schemas/temp_production_work_order.py` | 临时生产工单 Pydantic Schema |
| `web-admin/src/api/temp-qc-work-order.ts` | 临时质检工单前端 API 类型+函数 |
| `web-admin/src/api/temp-production-work-order.ts` | 临时生产工单前端 API 类型+函数 |
| `web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue` | 临时质检工单列表页 |
| `web-admin/src/views/production/temp-production-work-order/index.vue` | 临时生产工单列表页 |
| `docs/sql/temp_work_order_tables.sql` | 新建表 + ALTER 语句 |
### 修改文件
| 文件 | 修改内容 |
|------|---------|
| `backend/app/models/models.py` | 新增 2 个模型类;4 个现有模型新增 `order_source` 列 |
| `backend/app/crud/qc_task.py` | `create_task_for_line()` 新增 `order_source` 参数;`_task_to_dict()` 序列化 `order_source`;`list_tasks()` 支持按 `order_source` 过滤 |
| `backend/app/crud/production_task.py` | `create_production_task()` 新增 `order_source` 参数;序列化 `order_source`;列表支持按来源过滤 |
| `backend/app/api/qc.py` | 新增临时质检工单路由组(5 个端点) |
| `backend/app/api/production.py` | 新增临时生产工单路由组(5 个端点) |
| `web-admin/src/api/qc-task.ts` | 类型新增 `orderSource` 字段 |
| `web-admin/src/api/production-task.ts` | 类型新增 `orderSource` 字段 |
| `web-admin/src/views/quality-inspection/qc-task/index.vue` | 列表新增"来源"Tag 列 |
| `web-admin/src/views/production/production-task/index.vue` | 列表新增"来源"Tag 列 |
---
### Task 1: 数据库 DDL — 新建表 + ALTER 语句
**Files:**
- Create: `docs/sql/temp_work_order_tables.sql`
- [ ] **Step 1: 创建 SQL 文件**
```sql
-- ============================================================
-- 临时工单模块 DDL
-- Date: 2026-05-24
-- ============================================================
-- 1. 新建临时质检工单表
DROP TABLE IF EXISTS biz_temp_qc_work_order;
CREATE TABLE biz_temp_qc_work_order (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
work_order_no VARCHAR(64) NOT NULL COMMENT '工单号(LSZJ+日期+序号)',
work_order_status VARCHAR(20) NOT NULL DEFAULT 'dpz' COMMENT '状态: dpz=待配置, ypz=已配置, ywc=已完成',
configured_time DATETIME DEFAULT NULL COMMENT '配置时间',
configured_by VARCHAR(64) DEFAULT NULL COMMENT '配置人',
has_lab TINYINT DEFAULT 0 COMMENT '是否含实验室自检: 0=否, 1=是',
has_third_party TINYINT DEFAULT 0 COMMENT '是否含第三方检测: 0=否, 1=是',
third_party_fee_total DECIMAL(12,2) DEFAULT NULL COMMENT '第三方费用合计',
tenant_no VARCHAR(64) NOT NULL DEFAULT '' COMMENT '租户编号',
order_num INT DEFAULT NULL COMMENT '排序号',
remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
create_by VARCHAR(64) DEFAULT NULL COMMENT '创建人id',
create_dept_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记: 0=正常, 1=已删除',
PRIMARY KEY (id),
KEY idx_temp_qc_wo_tenant (tenant_no, del_flag),
KEY idx_temp_qc_wo_no (work_order_no, tenant_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='临时质检工单';
-- 2. 新建临时生产工单表
DROP TABLE IF EXISTS biz_temp_production_work_order;
CREATE TABLE biz_temp_production_work_order (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
work_order_no VARCHAR(64) NOT NULL COMMENT '工单号(LSSC+日期+序号)',
work_order_status VARCHAR(20) NOT NULL DEFAULT 'dpz' COMMENT '状态: dpz=待配置, jxz=进行中, ywc=已完成',
configured_time DATETIME DEFAULT NULL COMMENT '配置时间',
configured_by VARCHAR(64) DEFAULT NULL COMMENT '配置人',
tenant_no VARCHAR(64) NOT NULL DEFAULT '' COMMENT '租户编号',
order_num INT DEFAULT NULL COMMENT '排序号',
remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
create_by VARCHAR(64) DEFAULT NULL COMMENT '创建人id',
create_dept_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记: 0=正常, 1=已删除',
PRIMARY KEY (id),
KEY idx_temp_prod_wo_tenant (tenant_no, del_flag),
KEY idx_temp_prod_wo_no (work_order_no, tenant_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='临时生产工单';
-- 3. 质检任务表新增来源字段
ALTER TABLE biz_qc_task
ADD COLUMN order_source VARCHAR(20) NOT NULL DEFAULT 'inbound'
COMMENT '工单来源: inbound=入库, temporary=临时' AFTER work_order_id;
-- 4. 生产任务表新增来源字段
ALTER TABLE biz_production_task
ADD COLUMN order_source VARCHAR(20) NOT NULL DEFAULT 'inbound'
COMMENT '工单来源: inbound=入库, temporary=临时' AFTER work_order_id;
-- 5. 实验室项目明细表新增来源字段
ALTER TABLE biz_qc_work_order_lab_item
ADD COLUMN order_source VARCHAR(20) NOT NULL DEFAULT 'inbound'
COMMENT '工单来源: inbound=入库, temporary=临时' AFTER work_order_id;
-- 6. 第三方项目明细表新增来源字段
ALTER TABLE biz_qc_work_order_third_item
ADD COLUMN order_source VARCHAR(20) NOT NULL DEFAULT 'inbound'
COMMENT '工单来源: inbound=入库, temporary=临时' AFTER work_order_id;
```
- [ ] **Step 2: 在数据库执行 DDL**
Run: `mysql -u root -p zkxw < docs/sql/temp_work_order_tables.sql`
Expected: 所有表和字段创建成功,无错误
- [ ] **Step 3: Commit**
```bash
git add docs/sql/temp_work_order_tables.sql
git commit -m "feat(db): add temp work order tables and order_source columns"
```
---
### Task 2: 后端 Model — 新增模型类 + 修改现有模型
**Files:**
- Modify: `backend/app/models/models.py`
- [ ] **Step 1: 在 BizQcTask 模型中新增 `order_source` 列**
在 `BizQcTask` 类中,`work_order_id` 行之后添加:
```python
class BizQcTask(Base):
"""质检任务 biz_qc_task"""
__tablename__ = "biz_qc_task"
id = Column(String(64), primary_key=True, comment="主键ID")
work_order_id = Column(String(64), nullable=False, comment="工单ID")
order_source = Column(String(20), nullable=False, default="inbound", comment="工单来源: inbound=入库, temporary=临时")
task_no = Column(String(32), nullable=False, comment="任务号")
# ... 其余字段保持不变
```
- [ ] **Step 2: 在 BizProductionTask 模型中新增 `order_source` 列**
在 `BizProductionTask` 类中,`work_order_id` 行之后添加:
```python
class BizProductionTask(Base):
"""生产任务 biz_production_task"""
__tablename__ = "biz_production_task"
id = Column(String(64), primary_key=True, comment="主键ID")
task_no = Column(String(32), nullable=False, comment="任务号")
work_order_id = Column(String(64), nullable=False, comment="工单ID")
order_source = Column(String(20), nullable=False, default="inbound", comment="工单来源: inbound=入库, temporary=临时")
task_type = Column(String(20), nullable=False, default="cell_storage", comment="任务类型")
# ... 其余字段保持不变
```
- [ ] **Step 3: 在 BizQcWorkOrderLabItem 模型中新增 `order_source` 列**
在 `BizQcWorkOrderLabItem` 类中,`work_order_id` 行之后添加:
```python
class BizQcWorkOrderLabItem(Base):
"""质检工单-实验室项目明细 biz_qc_work_order_lab_item"""
__tablename__ = "biz_qc_work_order_lab_item"
id = Column(String(64), primary_key=True, comment="主键ID")
work_order_id = Column(String(64), nullable=False, comment="工单ID")
order_source = Column(String(20), nullable=False, default="inbound", comment="工单来源: inbound=入库, temporary=临时")
lab_test_item_id = Column(String(64), nullable=False, comment="实验室项目ID")
# ... 其余字段保持不变
```
- [ ] **Step 4: 在 BizQcWorkOrderThirdItem 模型中新增 `order_source` 列**
在 `BizQcWorkOrderThirdItem` 类中,`work_order_id` 行之后添加:
```python
class BizQcWorkOrderThirdItem(Base):
"""质检工单-第三方项目明细 biz_qc_work_order_third_item"""
__tablename__ = "biz_qc_work_order_third_item"
id = Column(String(64), primary_key=True, comment="主键ID")
work_order_id = Column(String(64), nullable=False, comment="工单ID")
order_source = Column(String(20), nullable=False, default="inbound", comment="工单来源: inbound=入库, temporary=临时")
third_party_item_id = Column(String(64), nullable=False, comment="第三方项目ID")
# ... 其余字段保持不变
```
- [ ] **Step 5: 新增 BizTempQcWorkOrder 模型类**
在 `BizQcWorkOrder` 类定义之后(约行 1253)添加:
```python
class BizTempQcWorkOrder(Base):
"""临时质检工单 biz_temp_qc_work_order"""
__tablename__ = "biz_temp_qc_work_order"
id = Column(String(64), primary_key=True, comment="主键ID")
work_order_no = Column(String(64), nullable=False, comment="工单号(LSZJ+日期+序号)")
work_order_status = Column(String(20), nullable=False, default="dpz", comment="状态: dpz=待配置, ypz=已配置, ywc=已完成")
configured_time = Column(DateTime, nullable=True, comment="配置时间")
configured_by = Column(String(64), nullable=True, comment="配置人")
has_lab = Column(SmallInteger, nullable=False, default=0, comment="是否含实验室自检")
has_third_party = Column(SmallInteger, nullable=False, default=0, comment="是否含第三方检测")
third_party_fee_total = Column(Numeric(18, 2), nullable=True, comment="第三方费用合计")
tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
order_num = Column(Integer, nullable=True, comment="排序号")
remark = Column(String(512), nullable=True, comment="备注")
create_by = Column(String(64), nullable=True, comment="创建人id")
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
update_by = Column(String(64), nullable=True, comment="更新人id")
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记")
```
- [ ] **Step 6: 新增 BizTempProductionWorkOrder 模型类**
在 `BizProductionWorkOrder` 类定义之后(约行 2292)添加:
```python
class BizTempProductionWorkOrder(Base):
"""临时生产工单 biz_temp_production_work_order"""
__tablename__ = "biz_temp_production_work_order"
id = Column(String(64), primary_key=True, comment="主键ID")
work_order_no = Column(String(64), nullable=False, comment="工单号(LSSC+日期+序号)")
work_order_status = Column(String(20), nullable=False, default="dpz", comment="状态: dpz=待配置, jxz=进行中, ywc=已完成")
configured_time = Column(DateTime, nullable=True, comment="配置时间")
configured_by = Column(String(64), nullable=True, comment="配置人")
tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
order_num = Column(Integer, nullable=True, comment="排序号")
remark = Column(String(512), nullable=True, comment="备注")
create_by = Column(String(64), nullable=True, comment="创建人id")
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
update_by = Column(String(64), nullable=True, comment="更新人id")
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记")
```
- [ ] **Step 7: Commit**
```bash
git add backend/app/models/models.py
git commit -m "feat(models): add temp work order models and order_source columns to task models"
```
---
### Task 3: 后端 — 修改现有 qc_task CRUD 支持 order_source
**Files:**
- Modify: `backend/app/crud/qc_task.py`
- [ ] **Step 1: `create_task_for_line()` 新增 `order_source` 参数**
将函数签名改为:
```python
def create_task_for_line(
db: Session,
*,
work_order, # BizQcWorkOrder 或 BizTempQcWorkOrder
task_type: str,
source_line_id: str,
item_code: str,
item_name: str,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
seq: int = 1,
company_id: Optional[str] = None,
company_name: Optional[str] = None,
fee_amount: Optional[Decimal] = None,
order_source: str = "inbound", # 新增参数
) -> BizQcTask:
obj = BizQcTask(
id=generate_uuid(),
work_order_id=work_order.id,
order_source=order_source, # 新增
task_no=_gen_task_no(db, work_order, seq),
task_type=task_type,
source_line_id=source_line_id,
sample_code=getattr(work_order, "sample_code", None) or None,
item_code=item_code,
item_name=item_name,
company_id=company_id,
company_name=company_name,
fee_amount=fee_amount,
task_status="dzx",
tenant_no=tenant_no,
order_num=seq,
create_by=user_id,
create_dept_id=create_dept_id or "",
create_time=datetime.now(),
update_by=user_id,
update_time=datetime.now(),
del_flag=0,
)
db.add(obj)
db.flush()
return obj
```
- [ ] **Step 2: `_task_to_dict()` 序列化 `order_source`**
在 `_task_to_dict` 返回的字典中添加 `"orderSource"` 字段:
```python
def _task_to_dict(
t: BizQcTask,
work_order_no: str = "",
db: Optional[Session] = None,
tenant_no: Optional[str] = None,
) -> Dict[str, Any]:
tn = tenant_no or t.tenant_no
return {
"id": t.id,
"workOrderId": t.work_order_id,
"orderSource": getattr(t, "order_source", "inbound") or "inbound", # 新增
"workOrderNo": work_order_no,
# ... 其余字段保持不变
}
```
- [ ] **Step 3: Commit**
```bash
git add backend/app/crud/qc_task.py
git commit -m "feat(qc-task): support order_source parameter for task creation"
```
---
### Task 4: 后端 — 修改现有 production_task CRUD 支持 order_source
**Files:**
- Modify: `backend/app/crud/production_task.py`
- [ ] **Step 1: `create_production_task()` 新增 `order_source` 参数**
在函数签名中添加 `order_source: str = "inbound"` 参数,在创建 BizProductionTask 时设置:
```python
# 在创建 task 对象时添加 order_source
task = BizProductionTask(
id=generate_uuid(),
task_no=task_no,
work_order_id=data.get("work_order_id", ""),
order_source=order_source, # 新增
task_type=data.get("task_type") or "cell_storage",
# ... 其余保持不变
)
```
- [ ] **Step 2: 任务序列化函数添加 `orderSource` 字段**
在 `_task_to_dict()` 或相应的序列化函数中添加 `"orderSource"` 字段。
- [ ] **Step 3: Commit**
```bash
git add backend/app/crud/production_task.py
git commit -m "feat(production-task): support order_source parameter for task creation"
```
---
### Task 5: 后端 — 临时质检工单 Schema + CRUD + API
**Files:**
- Create: `backend/app/schemas/temp_qc_work_order.py`
- Create: `backend/app/crud/temp_qc_work_order.py`
- Modify: `backend/app/api/qc.py`
- [ ] **Step 1: 创建 Schema**
Create `backend/app/schemas/temp_qc_work_order.py`:
```python
"""临时质检工单 Schema"""
from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, Field
class TempQcWorkOrderCreateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
remark: Optional[str] = Field("", alias="remark")
class TempQcWorkOrderConfigureBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
has_lab: bool = Field(False, alias="hasLab")
has_third_party: bool = Field(False, alias="hasThirdParty")
lab_item_ids: List[str] = Field(default_factory=list, alias="labItemIds")
third_party_item_ids: List[str] = Field(default_factory=list, alias="thirdPartyItemIds")
```
- [ ] **Step 2: 创建 CRUD**
Create `backend/app/crud/temp_qc_work_order.py`。核心函数:
1. `_gen_daily_no(db, tenant_no)` — 生成 `LSZJ{YYYYMMDD}{序号:04d}`
2. `_wo_to_dict(wo)` — 序列化(极简字段)
3. `create_temp_qc_work_order(db, *, tenant_no, user_id, create_dept_id, remark)` — 创建
4. `list_temp_qc_work_orders(db, *, page, page_size, keyword, work_order_status, tenant_no, user_info)` — 列表
5. `get_temp_qc_work_order(db, id, tenant_no)` — 获取单条
6. `get_temp_qc_work_order_detail(db, id, tenant_no)` — 详情(含 labItems, thirdPartyItems, tasks)
7. `configure_temp_qc_work_order(db, id, *, has_lab, has_third_party, lab_item_ids, third_party_item_ids, tenant_no, user_id, create_dept_id)` — 配置检测项目→自动生成任务(复用 `qc_task.create_task_for_line(order_source='temporary')`)
8. `try_complete_temp_qc_work_order(db, work_order_id, tenant_no)` — 所有临时任务完成时标记工单完成
9. `delete_temp_qc_work_order(db, id, tenant_no, user_id)` — 软删除
配置逻辑参照现有 `qc_work_order.configure_work_order()`,区别:
- 创建 BizQcWorkOrderLabItem/BizQcWorkOrderThirdItem 时设 `order_source='temporary'`
- 调用 `task_svc.create_task_for_line(order_source='temporary')`
- `try_complete` 查询时过滤 `order_source='temporary'`
- [ ] **Step 3: 在 api/qc.py 中新增路由组**
在文件末尾添加 5 个路由端点:
```python
# ---------- 临时质检工单 ----------
@router.post("/temp-qc-work-orders")
async def create_temp_qc_work_order(
body: TempQcWorkOrderCreateBody,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:tempQcWorkOrder:list")),
):
...
@router.get("/temp-qc-work-orders")
async def list_temp_qc_work_orders(...):
...
@router.get("/temp-qc-work-orders/{work_order_id}")
async def get_temp_qc_work_order_detail(...):
...
@router.delete("/temp-qc-work-orders/{work_order_id}")
async def delete_temp_qc_work_order(...):
...
@router.post("/temp-qc-work-orders/{work_order_id}/configure")
async def configure_temp_qc_work_order(
work_order_id: str,
body: TempQcWorkOrderConfigureBody,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:tempQcWorkOrder:list")),
):
...
```
- [ ] **Step 4: 启动后端验证 API 可达**
Run: `cd backend && source venv/bin/activate && python -c "from app.main import app; print('OK')"`
Expected: OK,无 import 错误
- [ ] **Step 5: Commit**
```bash
git add backend/app/schemas/temp_qc_work_order.py backend/app/crud/temp_qc_work_order.py backend/app/api/qc.py
git commit -m "feat(temp-qc): add temp QC work order backend (schema, CRUD, API)"
```
---
### Task 6: 后端 — 临时生产工单 Schema + CRUD + API
**Files:**
- Create: `backend/app/schemas/temp_production_work_order.py`
- Create: `backend/app/crud/temp_production_work_order.py`
- Modify: `backend/app/api/production.py`
- [ ] **Step 1: 创建 Schema**
Create `backend/app/schemas/temp_production_work_order.py`:
```python
"""临时生产工单 Schema"""
from __future__ import annotations
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field
class TempProductionWorkOrderCreateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
remark: Optional[str] = Field("", alias="remark")
class TempProductionWorkOrderConfigureBody(BaseModel):
"""配置→创建生产任务(与入库生产工单一致:选择工艺路线+克隆步骤)"""
model_config = ConfigDict(populate_by_name=True)
task_name: Optional[str] = Field("", alias="taskName")
task_type: Optional[str] = Field("cell_storage", alias="taskType")
route_id: Optional[str] = Field(None, alias="routeId")
description: Optional[str] = Field("", alias="description")
remark: Optional[str] = Field("", alias="remark")
```
- [ ] **Step 2: 创建 CRUD**
Create `backend/app/crud/temp_production_work_order.py`。核心函数:
1. `_gen_daily_no(db, tenant_no)` — 生成 `LSSC{YYYYMMDD}{序号:04d}`
2. `_wo_to_dict(wo)` — 序列化
3. `create_temp_production_work_order(db, *, tenant_no, user_id, create_dept_id, remark)` — 创建
4. `list_temp_production_work_orders(db, *, page, page_size, keyword, work_order_status, tenant_no, user_info)` — 列表
5. `get_temp_production_work_order(db, id, tenant_no)` — 获取单条
6. `get_temp_production_work_order_detail(db, id, tenant_no)` — 详情(含 tasks,每个 task 含 steps+子表)
7. `configure_temp_production_work_order(db, id, *, task_name, task_type, route_id, description, remark, tenant_no, user_id, create_dept_id)` — 配置→创建生产任务,**复用 `production_task.create_production_task(order_source='temporary')`,自动触发 `_clone_route_to_task()` 克隆工艺路线步骤+4类子表**
8. `delete_temp_production_work_order(db, id, tenant_no, user_id)` — 软删除
关键:`configure_temp_production_work_order` 直接调用 `production_task.create_production_task()`,传入 `order_source='temporary'` 和 `route_id`,由 `create_production_task` 内部自动执行 `_clone_route_to_task()` 克隆路线步骤。
同时需要调用 `sync_production_work_order_status_from_tasks` 的临时版本来同步工单状态。
- [ ] **Step 3: 在 api/production.py 中新增路由组**
添加 5 个路由端点(POST/GET/GET/{id}/DELETE/{id}/POST/{id}/configure),路由路径前缀 `/temp-production-work-orders`。
- [ ] **Step 4: 启动后端验证 API 可达**
Run: `cd backend && source venv/bin/activate && python -c "from app.main import app; print('OK')"`
Expected: OK
- [ ] **Step 5: Commit**
```bash
git add backend/app/schemas/temp_production_work_order.py backend/app/crud/temp_production_work_order.py backend/app/api/production.py
git commit -m "feat(temp-production): add temp production work order with route cloning support"
```
---
### Task 7: 前端 — 临时质检工单页面
**Files:**
- Create: `web-admin/src/api/temp-qc-work-order.ts`
- Create: `web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue`
- [ ] **Step 1: 创建 API 文件**
Create `web-admin/src/api/temp-qc-work-order.ts`,参照 `api/qc-work-order.ts` 简化版:
```typescript
import request from '@/utils/http'
type PageList<T> = {
list: T[]
total: number
page: number
pageSize: number
totalPages: number
}
export type TempQcWorkOrder = {
id: string
workOrderNo: string
workOrderStatus: string
hasLab: boolean
hasThirdParty: boolean
configuredTime?: string
configuredBy?: string
thirdPartyFeeTotal?: number
remark?: string
createTime?: string
updateTime?: string
}
export type TempQcWorkOrderDetail = TempQcWorkOrder & {
labItems?: Array<{ id: string; labTestItemId: string; itemCode: string; itemName: string }>
thirdPartyItems?: Array<{
id: string
thirdPartyItemId: string
companyId: string
companyName: string
itemCode: string
itemName: string
feeAmount: number
}>
tasks?: Array<{
id: string
taskNo: string
taskType: string
itemCode?: string
itemName?: string
taskStatus: string
conclusion?: string
conclusionLabel?: string
}>
}
export function fetchTempQcWorkOrderList(params?: {
page?: number
pageSize?: number
keyword?: string
workOrderStatus?: string
}) {
return request.get<PageList<TempQcWorkOrder>>({
url: '/web/admin/temp-qc-work-orders',
params
})
}
export function fetchTempQcWorkOrderDetail(id: string) {
return request.get<TempQcWorkOrderDetail>({
url: `/web/admin/temp-qc-work-orders/${id}`
})
}
export function fetchCreateTempQcWorkOrder(data: { remark?: string }) {
return request.post<TempQcWorkOrder>({
url: '/web/admin/temp-qc-work-orders',
data
})
}
export function fetchConfigureTempQcWorkOrder(
id: string,
data: {
hasLab: boolean
hasThirdParty: boolean
labItemIds: string[]
thirdPartyItemIds: string[]
}
) {
return request.post<TempQcWorkOrder>({
url: `/web/admin/temp-qc-work-orders/${id}/configure`,
data
})
}
export function fetchDeleteTempQcWorkOrder(id: string) {
return request.del({ url: `/web/admin/temp-qc-work-orders/${id}` })
}
```
- [ ] **Step 2: 创建列表页面**
Create `web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue`
参照模板页 `src/views/system/unit/index.vue`(标准列表页模式),包含:
- **列表列**:工单号、状态(Tag: dpz→info/ypz→warning/ywc→success)、是否含实验室自检、是否含第三方、配置时间、创建时间、操作
- **创建弹窗**:仅备注字段(el-input type="textarea")
- **配置弹窗**:复用现有配置检测项目组件逻辑(选择实验室自检/第三方检测项目)
- **详情弹窗**:工单基本信息 + 关联任务列表
- **Toolbar**:搜索(keyword)、状态筛选、新增按钮
- **注意**:遵循 AGENTS.md UI 规范(Tag 用 el-tag、dialog label-position="top"、2列 form-grid 等)
- [ ] **Step 3: 验证前端编译**
Run: `cd web-admin && pnpm build`
Expected: 构建成功,无 TypeScript 错误
- [ ] **Step 4: Commit**
```bash
git add web-admin/src/api/temp-qc-work-order.ts web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue
git commit -m "feat(temp-qc): add temp QC work order frontend page"
```
---
### Task 8: 前端 — 临时生产工单页面
**Files:**
- Create: `web-admin/src/api/temp-production-work-order.ts`
- Create: `web-admin/src/views/production/temp-production-work-order/index.vue`
- [ ] **Step 1: 创建 API 文件**
Create `web-admin/src/api/temp-production-work-order.ts`,参照 `api/production-work-order.ts` 简化版:
```typescript
import request from '@/utils/http'
type PageList<T> = {
list: T[]
total: number
page: number
pageSize: number
totalPages: number
}
export type TempProductionWorkOrder = {
id: string
workOrderNo: string
workOrderStatus: string
configuredTime?: string
configuredBy?: string
remark?: string
createTime?: string
updateTime?: string
}
export type TempProductionWorkOrderDetail = TempProductionWorkOrder & {
tasks?: Array<{
id: string
taskNo: string
taskType: string
taskName?: string
taskStatus: string
}>
}
export function fetchTempProductionWorkOrderList(params?: {
page?: number
pageSize?: number
keyword?: string
workOrderStatus?: string
}) {
return request.get<PageList<TempProductionWorkOrder>>({
url: '/web/admin/temp-production-work-orders',
params
})
}
export function fetchTempProductionWorkOrderDetail(id: string) {
return request.get<TempProductionWorkOrderDetail>({
url: `/web/admin/temp-production-work-orders/${id}`
})
}
export function fetchCreateTempProductionWorkOrder(data: { remark?: string }) {
return request.post<TempProductionWorkOrder>({
url: '/web/admin/temp-production-work-orders',
data
})
}
export function fetchConfigureTempProductionWorkOrder(
id: string,
data: {
taskName?: string
taskType?: string
routeId?: string
description?: string
remark?: string
}
) {
return request.post<TempProductionWorkOrder>({
url: `/web/admin/temp-production-work-orders/${id}/configure`,
data
})
}
export function fetchDeleteTempProductionWorkOrder(id: string) {
return request.del({ url: `/web/admin/temp-production-work-orders/${id}` })
}
```
- [ ] **Step 2: 创建列表页面**
Create `web-admin/src/views/production/temp-production-work-order/index.vue`
参照模板页 `src/views/system/unit/index.vue`(标准列表页),包含:
- **列表列**:工单号、状态(Tag: dpz→info/jxz→primary/ywc→success)、配置时间、创建时间、操作
- **创建弹窗**:仅备注字段
- **配置弹窗**:与入库生产工单的 DispatchTaskDialog 类似,包含任务名称(task_name)、任务类型(task_type cell_storage/outbound)、**工艺路线选择(route_id,选择后自动克隆步骤)**、描述(description)、备注(remark)。参照 `views/production/production-work-order/DispatchTaskDialog.vue` 的表单结构。
- **详情弹窗**:工单基本信息 + 关联任务列表
- **遵循 UI 规范**
- [ ] **Step 3: 验证前端编译**
Run: `cd web-admin && pnpm build`
Expected: 构建成功
- [ ] **Step 4: Commit**
```bash
git add web-admin/src/api/temp-production-work-order.ts web-admin/src/views/production/temp-production-work-order/index.vue
git commit -m "feat(temp-production): add temp production work order frontend page"
```
---
### Task 9: 前端 — 现有任务列表新增"来源"Tag 列
**Files:**
- Modify: `web-admin/src/api/qc-task.ts`
- Modify: `web-admin/src/views/quality-inspection/qc-task/index.vue`
- Modify: `web-admin/src/api/production-task.ts`
- Modify: `web-admin/src/views/production/production-task/index.vue`
- [ ] **Step 1: qc-task.ts 类型新增 orderSource**
在 `QcTask` 类型定义中添加:
```typescript
export type QcTask = {
// ... 现有字段
orderSource?: string // 'inbound' | 'temporary'
// ...
}
```
- [ ] **Step 2: 质检任务列表新增"来源"列**
在 `quality-inspection/qc-task/index.vue` 的 el-table 中添加列:
```vue
<el-table-column label="来源" prop="orderSource" min-width="100">
<template #default="{ row }">
<el-tag
:type="row.orderSource === 'temporary' ? 'warning' : 'success'"
size="small"
>
{{ row.orderSource === 'temporary' ? '临时' : '入库' }}
</el-tag>
</template>
</el-table-column>
```
位置放在"工单号"列之后。
- [ ] **Step 3: production-task.ts 类型新增 orderSource**
同 Step 1,在 ProductionTask 类型中添加 `orderSource`。
- [ ] **Step 4: 生产任务列表新增"来源"列**
同 Step 2,在 `production/production-task/index.vue` 的 el-table 中添加来源 Tag 列。
- [ ] **Step 5: 验证前端编译**
Run: `cd web-admin && pnpm build`
Expected: 构建成功
- [ ] **Step 6: Commit**
```bash
git add web-admin/src/api/qc-task.ts web-admin/src/views/quality-inspection/qc-task/index.vue web-admin/src/api/production-task.ts web-admin/src/views/production/production-task/index.vue
git commit -m "feat(tasks): add orderSource column with tag display to task lists"
```
---
### Task 10: 菜单配置 + 最终验证
**Files:**
- Modify: 数据库 `sys_menu` 表(通过 SQL INSERT)
- [ ] **Step 1: 插入菜单记录**
在数据库中执行 SQL,为临时质检工单和临时生产工单各插入菜单记录。具体 menu_id 和 parent_id 需要根据现有菜单树确定。INSERT 语句参考现有菜单数据格式。
- [ ] **Step 2: 全栈验证**
1. 启动后端:`cd backend && source venv/bin/activate && ./start-local.sh`
2. 启动前端:`cd web-admin && pnpm dev`
3. 登录系统,导航到临时质检工单页面
4. 测试:创建工单 → 配置检测项目 → 查看生成任务 → 完成任务 → 工单自动完成
5. 导航到临时生产工单页面
6. 测试:创建工单 → 配置生成任务 → 查看任务
7. 导航到质检任务/生产任务页面,确认"来源"Tag 列正确显示
- [ ] **Step 3: Commit**
```bash
git add docs/sql/temp_work_order_tables.sql
git commit -m "feat(menu): add temp work order menu items"
```
---
## Self-Review Checklist
- [x] **Spec coverage**: 每个设计文档章节都有对应 Task(数据库→Task1/2,后端→Task3/4/5/6,前端→Task7/8/9,菜单→Task10)
- [x] **Placeholder scan**: 无 TBD/TODO,所有步骤有具体代码或指令
- [x] **Type consistency**: order_source 字段名在 model/schema/crud/api/typescript 中一致使用
@@ -0,0 +1,324 @@
# 细胞产品库 — 来源链路展示设计
> 日期:2026-05-24
> 状态:待审核
> 方案:A(自定义 Vue 组件 + CSS 绘制连线)
> Demo:`docs/demo-source-chain-graph.html`
## 1. 需求概述
细胞产品库列表页(`/#/production/cell-product`)当前仅展示产品基础信息,缺少来源追溯能力。需要:
1. **列表新增来源信息列**:来源类型、来源协议号、来源工单号、来源任务号
2. **列表新增来源类型筛选**:下拉筛选(全部/生产入库/手动录入)
3. **查看弹窗新增链路图**:横向 DAG 可视化,展示从样本到当前产品的祖先链 + 当前产品到子代产品的后代链,双向递归到顶点/终点
4. **链路图连线标注**:连线上方显示工单号、任务号
### 数据模型
溯源链路涉及 5 张核心表:
```
biz_collection_protocol (采集协议单)
↓ collection_protocol_id
biz_protocol_sample (协议样本)
↓ protocol_sample_id + collection_protocol_id
biz_sample_reception (样本接收记录)
↓ reception_id + protocol_sample_id + collection_protocol_id
biz_production_work_order (生产工单) ← 冗余存储 protocol_no, sample_code
↓ work_order_id
biz_production_task (生产任务)
↓ task_id
biz_cell_product (细胞产品) ← production_task_id 关联到任务
```
后代链路通过投入物/产出物表关联:
```
父细胞产品 (biz_cell_product.id=A)
↓ biz_production_task_input(input_type='cell_product', input_id=A, task_id=T)
biz_production_task (id=T)
↓ biz_production_task_output(task_id=T, cell_product_id=B, C, ...)
子细胞产品们 (biz_cell_product.id=B, C, ...)
```
## 2. 后端改造
### 2.1 列表 API 增强
**接口**:`GET /web/admin/cell-products`(已有)
**改造文件**:`backend/app/crud/cell_product.py`
#### 2.1.1 `_enrich_product_dicts()` 增加来源富化
在现有富化逻辑(productTypeName, specName, storageSlotName)之后,增加来源信息的批量富化查询:
1. 收集所有非空 `production_task_id`
2. 一次查询 `biz_production_task`,获取 `task_no` 和 `work_order_id`
3. 一次查询 `biz_production_work_order`,获取 `work_order_no`、`protocol_no`、`reception_source`
4. 映射回每条产品记录
新增返回字段:
| 字段名 | 类型 | 来源路径 | 说明 |
|--------|------|----------|------|
| `sourceTypeLabel` | str | source_type 枚举映射 | 来源类型中文 |
| `sourceProtocolNo` | str | task → work_order → protocol_no | 来源协议号 |
| `sourceWorkOrderNo` | str | task → work_order → work_order_no | 来源工单号 |
| `sourceTaskNo` | str | task → task_no | 来源生产任务号 |
| `sourceReceptionSource` | str | work_order → reception_source | cell_storage/research_donation |
`source_type` 枚举映射:
- `production` → "生产入库"
- `manual` → "手动录入"
#### 2.1.2 `list_cell_products()` 增加筛选参数
入参新增 `source_type: Optional[str] = None`,在查询条件中增加:
```python
if source_type:
query = query.filter(BizCellProduct.source_type == source_type)
```
#### 2.1.3 Schema 更新
`CellProductListQuery` 新增 `source_type: Optional[str] = None`。
API 路由层 `production.py` 从 query params 读取 `source_type` 传入 service。
### 2.2 新增链路 API
**接口**:`GET /web/admin/cell-products/{product_id}/chain`
**新文件**:无(在 `backend/app/crud/cell_product.py` 中新增函数,在 `backend/app/api/production.py` 中新增路由)
#### 2.2.1 返回数据结构
```json
{
"current": {
"id": "uuid",
"productCode": "CP001",
"productName": "脐带间充质干细胞",
"nodeType": "cell_product",
"productStatus": "frozen",
"passage": "P2",
"isCurrent": true
},
"ancestors": [
{
"node": {
"id": "uuid",
"productCode": "CP000",
"productName": "...",
"nodeType": "cell_product",
"productStatus": "frozen",
"passage": "P1",
"isCurrent": false
},
"edge": {
"workOrderNo": "WO001",
"workOrderType": "cell_storage",
"taskNo": "T001",
"taskName": "制备任务"
},
"parent": null
}
],
"descendants": [
{
"node": {
"id": "uuid",
"productCode": "CP002",
"productName": "...",
"nodeType": "cell_product",
"productStatus": "frozen",
"passage": "P3",
"isCurrent": false
},
"edge": {
"workOrderNo": "WO002",
"workOrderType": "outbound",
"taskNo": "T002",
"taskName": "出库生产"
},
"children": []
}
]
}
```
当追溯链路中遇到样本节点(非细胞产品)时,`nodeType = "sample"`,包含 `sampleCode` 和 `sampleType` 字段。
#### 2.2.2 递归查询逻辑
**祖先方向**(从当前产品向上追溯):
1. 从 `biz_cell_product.production_task_id` 获取任务
2. 从 `biz_production_task.work_order_id` 获取工单(含 protocol_no, sample_code)
3. 检查该任务的投入物:`biz_production_task_input(input_type='cell_product')` 是否有父产品
4. 如有父产品,递归继续(将父产品作为新的起点)
5. 如投入物是样本(`input_type='sample'`),创建样本节点,终止递归
6. 如 `production_task_id` 为空或 `source_type='manual'`,终止递归
**子代方向**(从当前产品向下追溯):
1. 查询 `biz_production_task_input(input_type='cell_product', input_id=当前产品id)` 找到所有引用此产品的任务
2. 对每个任务,查询 `biz_production_task_output` 获取产出的细胞产品列表
3. 对每个产出产品,递归继续(1对多分支)
4. 如无任务引用此产品,终止递归
**性能保护**:最大递归深度 10 层。
#### 2.2.3 路由注册
在 `backend/app/api/production.py` Section 11(细胞产品库)中新增路由,放在 `GET /{product_id}` 之后。
## 3. 前端改造
### 3.1 列表页
**文件**:`web-admin/src/views/production/cell-product/index.vue`
#### 3.1.1 新增表格列
在 `productStatus`(状态)列之后,新增 4 列:
| prop | label | min-width | 渲染 |
|------|-------|-----------|------|
| `sourceTypeLabel` | 来源类型 | 120 | `<el-tag>`:生产入库=success、手动录入=info |
| `sourceProtocolNo` | 来源协议号 | 160 | 文本,无值显示 "—" |
| `sourceWorkOrderNo` | 来源工单号 | 140 | 文本,无值显示 "—" |
| `sourceTaskNo` | 来源任务号 | 140 | 文本,无值显示 "—" |
#### 3.1.2 新增搜索筛选
搜索栏增加「来源类型」下拉,位于 `productStatus` 筛选之后:
```html
<el-select v-model="searchForm.sourceType" placeholder="来源类型" clearable>
<el-option label="生产入库" value="production" />
<el-option label="手动录入" value="manual" />
</el-select>
```
#### 3.1.3 查看弹窗基础信息
在现有 `viewSections` 的「基本信息」section 中追加 2 个 `ViewField`:
| field | label | 渲染 |
|-------|-------|------|
| `sourceTypeLabel` | 来源类型 | `tagType` 映射 |
| `sourceProtocolNo` | 来源协议号 | 文本 |
### 3.2 链路图组件
**新文件**:`web-admin/src/views/production/cell-product/SourceChainGraph.vue`
#### 3.2.1 组件职责
- 接收 `productId` prop
- 调用 `GET /web/admin/cell-products/{id}/chain` 获取链路数据
- 渲染横向 DAG:祖先 → 当前 → 子代
- 支持节点点击查看详情(Element Plus Popover)
#### 3.2.2 组件 Props
| prop | type | 说明 |
|------|------|------|
| `productId` | string | 当前细胞产品 ID |
| `visible` | boolean | 是否显示(由父组件控制) |
#### 3.2.3 布局结构
```
SourceChainGraph
├── 祖先区域(水平 flex,从左到右:最早祖先 → ... → 当前节点)
│ ├── ChainNode(递归渲染祖先链)
│ └── ChainConnector(连线 + 工单/任务标签)
├── 当前节点(高亮样式,角标「当前」)
└── 子代区域(支持分支,水平 flex-wrap)
├── ChainNode(递归渲染子代链,1对多用 flex-wrap)
└── ChainConnector(连线 + 工单/任务标签)
```
#### 3.2.4 节点样式
| 节点类型 | 背景 | 边框 | 内容 |
|----------|------|------|------|
| 样本 | `#eff6ff`(浅蓝) | `#93c5fd` | 编码、类型 |
| 细胞产品 | `#ecfdf5`(浅绿) | `#a7f3d0` | 编码、名称、代次 |
| 当前产品 | `#ecfdf5` | `#10b981`(2px 加粗) | 编码、名称、代次 + 角标「当前」 |
节点尺寸:148×60px,内容过长用 `text-overflow: ellipsis` 截断。
#### 3.2.5 连线样式
- 水平线:2px border,颜色 `#d1d5db`
- 箭头:CSS border 三角形
- 连线上方标签:绝对定位 `<span>`,显示工单号和任务号,字号 10px
- 分叉线:从父节点右侧引出竖线 + 横线到各子节点
#### 3.2.6 查看弹窗集成
在 `index.vue` 的 `SystemViewDialog` 中新增第三个 section「来源链路」,使用 `template #sourceChain` 自定义插槽:
```html
<SystemViewDialog ...>
<!-- 基本信息 section -->
<!-- 产品图片 section -->
<!-- 来源链路 section (新增) -->
<template #sourceChain>
<SourceChainGraph :product-id="currentRow.id" :visible="viewDialogVisible" />
</template>
</SystemViewDialog>
```
`SystemViewDialog` 需支持第三个 section(通过 `viewSections` 配置中增加 `slot: 'sourceChain'`)。
### 3.3 API 层
**文件**:`web-admin/src/api/cell-product.ts`
新增接口函数:
```typescript
export function fetchCellProductChain(productId: string) {
return request.get({ url: `/web/admin/cell-products/${productId}/chain` })
}
```
`fetchCellProductList` 的参数类型新增 `sourceType?: string`。
## 4. 影响范围
### 后端文件
| 文件 | 改动类型 |
|------|----------|
| `backend/app/crud/cell_product.py` | 修改:`_enrich_product_dicts` 增加来源富化,新增 `get_product_chain()` 递归函数 |
| `backend/app/schemas/cell_product.py` | 修改:`CellProductListQuery` 增加 `source_type` 参数 |
| `backend/app/api/production.py` | 修改:列表路由读取 `source_type` 参数,新增 chain 路由 |
### 前端文件
| 文件 | 改动类型 |
|------|----------|
| `web-admin/src/views/production/cell-product/index.vue` | 修改:新增表格列、搜索筛选、查看弹窗字段 |
| `web-admin/src/views/production/cell-product/SourceChainGraph.vue` | 新增:链路图组件 |
| `web-admin/src/api/cell-product.ts` | 修改:新增 `fetchCellProductChain`,`fetchCellProductList` 参数增加 `sourceType` |
### 不涉及的文件
- 数据库无表结构变更(无新表、无新字段)
- 不涉及其他模块(订单、协议单、质检等)
## 5. 约束与边界
1. **手动录入产品(起源节点)**:无 `production_task_id`,所以祖先链为空。但后续如果被用于生产,子代链路仍然会展示。只有**既无祖先又无子代的孤立产品**才显示空状态提示「该产品暂无来源及衍生链路信息」
2. **最大递归深度**:10 层,防止异常数据导致无限递归
3. **性能**:链路 API 单次查询,预估节点数 ≤ 50,响应时间 < 500ms
4. **科研捐赠来源**:通过 `reception_source='research_donation'` 标识,链路追溯路径相同
5. **子代追踪**:依赖 `biz_production_task_input(input_type='cell_product')` 记录,如投入物未正确录入则无法追踪子代
6. **配色**:严格使用 Fresh Mint 色系(`#10b981`、`#ecfdf5`),样本节点使用蓝色系(`#eff6ff`、`#93c5fd`)做区分
@@ -0,0 +1,312 @@
# 临时工单模块设计文档
> 日期: 2026-05-24
> 状态: 待审核
> 模块: 临时质检工单 + 临时生产工单
## 1. 背景与目标
### 现状
系统已有 4 个模块:
| 模块 | 表名 | 说明 |
|------|------|------|
| 入库质检工单 | `biz_qc_work_order` | 关联样本接收记录和协议 |
| 质检任务 | `biz_qc_task` | 由入库质检工单配置检测项目后自动生成 |
| 入库生产工单 | `biz_production_work_order` | 关联样本接收记录和协议 |
| 生产任务 | `biz_production_task` | 由入库生产工单手动派发创建 |
### 目标
新增 2 个临时工单模块,复用现有任务生成机制,在任务表中记录工单来源:
1. **临时质检工单** — 独立工单(不关联样本/协议),配置检测项目后自动生成质检任务
2. **临时生产工单** — 独立工单(不关联样本/协议),选择工艺路线后创建生产任务(自动克隆路线步骤+子表,流程与入库生产工单一致)
### 设计决策记录
| 决策项 | 选择 | 原因 |
|--------|------|------|
| 关联关系 | 完全不关联 reception/protocol | 临时工单独立于入库流程 |
| 来源追踪 | 任务表新增 `order_source` 字段 | 最小侵入,清晰区分来源 |
| 任务生成方式 | 配置→自动批量生成 | 和入库模式一致 |
| 工单字段 | 极简(工单号+状态+备注) | 用户要求简单 |
| 工单号前缀 | LSZJ(临时质检)、LSSC(临时生产) | 区分于入库前缀 |
| 实现方案 | 新建独立临时工单表 | 和入库完全解耦,避免混合复杂度 |
---
## 2. 数据库设计
### 2.1 新建 `biz_temp_qc_work_order`(临时质检工单)
```sql
DROP TABLE IF EXISTS biz_temp_qc_work_order;
CREATE TABLE biz_temp_qc_work_order (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
work_order_no VARCHAR(64) NOT NULL COMMENT '工单号(LSZJ+日期+序号)',
work_order_status VARCHAR(20) NOT NULL DEFAULT 'dpz' COMMENT '状态: dpz=待配置, ypz=已配置, ywc=已完成',
configured_time DATETIME DEFAULT NULL COMMENT '配置时间',
configured_by VARCHAR(64) DEFAULT NULL COMMENT '配置人',
has_lab TINYINT DEFAULT 0 COMMENT '是否含实验室自检: 0=否, 1=是',
has_third_party TINYINT DEFAULT 0 COMMENT '是否含第三方检测: 0=否, 1=是',
third_party_fee_total DECIMAL(12,2) DEFAULT NULL COMMENT '第三方费用合计',
tenant_no VARCHAR(64) NOT NULL DEFAULT '' COMMENT '租户编号',
order_num INT DEFAULT NULL COMMENT '排序号',
remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
create_by VARCHAR(64) DEFAULT NULL COMMENT '创建人id',
create_dept_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记: 0=正常, 1=已删除',
PRIMARY KEY (id),
KEY idx_temp_qc_wo_tenant (tenant_no, del_flag),
KEY idx_temp_qc_wo_no (work_order_no, tenant_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='临时质检工单';
```
### 2.2 新建 `biz_temp_production_work_order`(临时生产工单)
```sql
DROP TABLE IF EXISTS biz_temp_production_work_order;
CREATE TABLE biz_temp_production_work_order (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
work_order_no VARCHAR(64) NOT NULL COMMENT '工单号(LSSC+日期+序号)',
work_order_status VARCHAR(20) NOT NULL DEFAULT 'dpz' COMMENT '状态: dpz=待配置, jxz=进行中, ywc=已完成',
configured_time DATETIME DEFAULT NULL COMMENT '配置时间',
configured_by VARCHAR(64) DEFAULT NULL COMMENT '配置人',
tenant_no VARCHAR(64) NOT NULL DEFAULT '' COMMENT '租户编号',
order_num INT DEFAULT NULL COMMENT '排序号',
remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
create_by VARCHAR(64) DEFAULT NULL COMMENT '创建人id',
create_dept_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记: 0=正常, 1=已删除',
PRIMARY KEY (id),
KEY idx_temp_prod_wo_tenant (tenant_no, del_flag),
KEY idx_temp_prod_wo_no (work_order_no, tenant_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='临时生产工单';
```
### 2.3 任务表新增 `order_source` 字段
```sql
-- 质检任务表新增来源字段
ALTER TABLE biz_qc_task
ADD COLUMN order_source VARCHAR(20) NOT NULL DEFAULT 'inbound'
COMMENT '工单来源: inbound=入库, temporary=临时' AFTER work_order_id;
-- 生产任务表新增来源字段
ALTER TABLE biz_production_task
ADD COLUMN order_source VARCHAR(20) NOT NULL DEFAULT 'inbound'
COMMENT '工单来源: inbound=入库, temporary=临时' AFTER work_order_id;
```
### 2.4 明细表处理(复用现有表)
临时质检工单复用现有 `biz_qc_work_order_lab_item` 和 `biz_qc_work_order_third_item` 表,通过 `order_source` 字段区分来源。
```sql
-- 实验室项目明细表新增来源字段
ALTER TABLE biz_qc_work_order_lab_item
ADD COLUMN order_source VARCHAR(20) NOT NULL DEFAULT 'inbound'
COMMENT '工单来源: inbound=入库, temporary=临时' AFTER work_order_id;
-- 第三方项目明细表新增来源字段
ALTER TABLE biz_qc_work_order_third_item
ADD COLUMN order_source VARCHAR(20) NOT NULL DEFAULT 'inbound'
COMMENT '工单来源: inbound=入库, temporary=临时' AFTER work_order_id;
```
### 2.5 关联关系
```
biz_temp_qc_work_order
├─(work_order_id + order_source='temporary')→ biz_qc_work_order_lab_item
├─(work_order_id + order_source='temporary')→ biz_qc_work_order_third_item
└─(work_order_id + order_source='temporary')→ biz_qc_task
→ biz_qc_task_result
biz_temp_production_work_order
└─(work_order_id + order_source='temporary')→ biz_production_task
→ biz_production_task_step + 子表
```
---
## 3. 后端架构
### 3.1 新增文件清单
| 层 | 临时质检工单 | 临时生产工单 |
|----|-------------|-------------|
| Model | `models.py` 新增 `BizTempQcWorkOrder` | `models.py` 新增 `BizTempProductionWorkOrder` |
| Schema | 新建 `schemas/temp_qc_work_order.py` | 新建 `schemas/temp_production_work_order.py` |
| CRUD | 新建 `crud/temp_qc_work_order.py` | 新建 `crud/temp_production_work_order.py` |
| API | `api/qc.py` 新增路由组 | `api/production.py` 新增路由组 |
### 3.2 API 路由设计
**临时质检工单:**
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/admin/temp-qc-work-orders` | 创建 |
| GET | `/api/admin/temp-qc-work-orders` | 列表 |
| GET | `/api/admin/temp-qc-work-orders/{id}` | 详情 |
| DELETE | `/api/admin/temp-qc-work-orders/{id}` | 删除(软删除) |
| POST | `/api/admin/temp-qc-work-orders/{id}/configure` | 配置检测项目(→自动生成任务) |
**临时生产工单:**
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/admin/temp-production-work-orders` | 创建 |
| GET | `/api/admin/temp-production-work-orders` | 列表 |
| GET | `/api/admin/temp-production-work-orders/{id}` | 详情 |
| DELETE | `/api/admin/temp-production-work-orders/{id}` | 删除(软删除) |
| POST | `/api/admin/temp-production-work-orders/{id}/configure` | 配置→自动生成任务 |
### 3.3 核心业务逻辑
#### 3.3.1 工单号生成
- 临时质检:`LSZJ` + `YYYYMMDD` + 4位序号(如 `LSZJ202605240001`)
- 临时生产:`LSSC` + `YYYYMMDD` + 4位序号(如 `LSSC202605240001`)
- 生成逻辑:参照现有 `_gen_qc_work_order_no()` 模式,按日期+租户查询当日最大序号+1
#### 3.3.2 创建工单
极简创建:仅需 `remark`(可选),自动生成工单号,状态默认 `dpz`(待配置)。
#### 3.3.3 配置检测项目(临时质检工单)
流程与入库质检工单的 `configure_work_order()` 一致:
```
用户选择检测项目 (lab_item_ids, third_party_ids)
├─ 校验工单状态 (dpz/ypz)
├─ 清除旧的待执行任务(order_source='temporary')
├─ 遍历 lab_item_ids:
│ ├─ 创建 BizQcWorkOrderLabItem (order_source='temporary')
│ └─ 调用 qc_task.create_task_for_line(order_source='temporary')
├─ 遍历 third_party_ids:
│ ├─ 创建 BizQcWorkOrderThirdItem (order_source='temporary')
│ └─ 调用 qc_task.create_task_for_line(order_source='temporary')
└─ 更新工单状态为 'ypz'
```
#### 3.3.4 配置→生成任务(临时生产工单)
与入库生产工单完全一致,选择工艺路线后创建生产任务(自动克隆路线步骤+4类子表):
```
用户配置任务信息 (task_name, task_type, route_id 工艺路线, description 可选)
├─ 创建 BizProductionTask (order_source='temporary', task_status='dpz')
├─ 如果选择了工艺路线: _clone_route_to_task() — 深拷贝路线步骤 + 子表
├─ 工单状态更新为 'jxz'
└─ 流程与入库生产工单的 create_production_task() 完全一致
```
后需复用 `production_task.create_production_task()` + `_clone_route_to_task()` 机制。
#### 3.3.5 状态同步
- **质检**:任务完成时检查同工单下所有任务(order_source='temporary')是否都已完成,若是则工单标记 `ywc`
- **生产**:任务完成时同步工单状态(复用 `sync_production_work_order_status_from_tasks()` 逻辑)
#### 3.3.6 删除
软删除(`del_flag=1`),级联删除关联的任务(仅 `order_source='temporary'`)和明细行。
---
## 4. 前端架构
### 4.1 新增文件清单
| 类型 | 临时质检工单 | 临时生产工单 |
|------|-------------|-------------|
| API | 新建 `api/temp-qc-work-order.ts` | 新建 `api/temp-production-work-order.ts` |
| 视图 | 新建 `views/quality-inspection/temp-qc-work-order/index.vue` | 新建 `views/production/temp-production-work-order/index.vue` |
### 4.2 页面设计
#### 临时质检工单页
参照模板页 `views/system/unit/index.vue`(标准列表页),功能简化:
- **列表列**:工单号、状态(Tag)、是否含实验室自检、是否含第三方、配置时间、创建时间、操作
- **创建弹窗**:仅备注字段
- **配置弹窗**:复用现有配置检测项目组件(选择实验室自检项目/第三方检测项目+公司+费用)
- **详情弹窗**:工单基本信息 + 关联任务列表
- **状态 Tag**:dpz→待配置(info)、ypz→已配置(warning)、ywc→已完成(success)
#### 临时生产工单页
同样参照标准列表页:
- **列表列**:工单号、状态(Tag)、配置时间、创建时间、操作
- **创建弹窗**:仅备注字段
- **配置弹窗**:简单表单(任务名称、任务类型 cell_storage/outbound、描述)
- **详情弹窗**:工单基本信息 + 关联任务列表
- **状态 Tag**:dpz→待配置(info)、jxz→进行中(primary)、ywc→已完成(success)
### 4.3 任务来源展示
在现有质检任务和生产任务列表页中:
- 新增"来源"列,使用 Tag 展示:
- `inbound` → Tag `<el-tag type="success" size="small">入库</el-tag>`
- `temporary` → Tag `<el-tag type="warning" size="small">临时</el-tag>`
### 4.4 路由配置
在 `src/router/modules/` 中对应的质量检验和生产管理路由组下新增子路由:
```typescript
// 质量检验路由组下
{ path: 'temp-qc-work-order', name: 'TempQcWorkOrder', component: () => import('@views/quality-inspection/temp-qc-work-order/index.vue') }
// 生产管理路由组下
{ path: 'temp-production-work-order', name: 'TempProductionWorkOrder', component: () => import('@views/production/temp-production-work-order/index.vue') }
```
### 4.5 菜单配置
需要在系统菜单管理中添加对应菜单项(数据库 `sys_menu` 表插入)。
---
## 5. 修改现有代码清单
以下列出需要修改的现有文件(非新增文件):
| 文件 | 修改内容 |
|------|---------|
| `backend/app/models/models.py` | 新增 `BizTempQcWorkOrder`、`BizTempProductionWorkOrder` 模型;`BizQcTask`、`BizProductionTask` 新增 `order_source` 列;`BizQcWorkOrderLabItem`、`BizQcWorkOrderThirdItem` 新增 `order_source` 列 |
| `backend/app/crud/qc_task.py` | `create_task_for_line()` 接受 `order_source` 参数;`list_tasks()` 展示 `order_source`;`_task_to_dict()` 序列化 `order_source` |
| `backend/app/crud/production_task.py` | `create_production_task()` 接受 `order_source` 参数;`list_production_tasks()` 展示 `order_source`;序列化 `order_source` |
| `backend/app/api/qc.py` | 新增临时质检工单路由组 |
| `backend/app/api/production.py` | 新增临时生产工单路由组 |
| `backend/app/api/__init__.py` | 如有新路由文件则注册 |
| `web-admin/src/api/qc-task.ts` | 类型定义新增 `orderSource` 字段 |
| `web-admin/src/api/production-task.ts` | 类型定义新增 `orderSource` 字段 |
| `web-admin/src/views/quality-inspection/qc-task/index.vue` | 列表新增"来源"Tag 列 |
| `web-admin/src/views/production/production-task/index.vue` | 列表新增"来源"Tag 列 |
| `docs/sql/zkxw.sql` | 同步新增表和 ALTER 语句 |
---
## 6. 风险与注意事项
1. **order_source 默认值**:所有现有数据默认为 `inbound`,无需数据迁移
2. **现有查询兼容**:所有现有查询 `work_order_id` 关联时,默认只查 `order_source='inbound'` 或不加过滤(因为现有数据的 work_order_id 不会和临时工单冲突——指向不同表的主键)
3. **工单号唯一性**:LSZJ/LSSC 前缀确保不与 ZJ/SC 冲突
4. **权限**:临时工单菜单需要在系统中配置,遵循现有 RBAC 权限体系
5. **软删除级联**:删除临时工单时,仅删除 `order_source='temporary'` 的任务,不影响入库任务
6. **工艺路线克隆**:临时生产工单复用 `_clone_route_to_task()` 机制,与入库生产工单完全一致(步骤+试剂+耗材+产出物+数据采集项全部深拷贝)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+111 -92
View File
@@ -1,11 +1,11 @@
# Graph Report - src (2026-05-24)
## Corpus Check
- Large corpus: 597 files · ~327,181 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
- Large corpus: 609 files · ~334,365 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
## Summary
- 1528 nodes · 1917 edges · 41 communities detected
- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 417 edges (avg confidence: 0.8)
- 1581 nodes · 1982 edges · 44 communities detected
- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 431 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
@@ -32,27 +32,30 @@
- [[_COMMUNITY_Community 20|Community 20]]
- [[_COMMUNITY_Community 21|Community 21]]
- [[_COMMUNITY_Community 22|Community 22]]
- [[_COMMUNITY_Community 23|Community 23]]
- [[_COMMUNITY_Community 24|Community 24]]
- [[_COMMUNITY_Community 25|Community 25]]
- [[_COMMUNITY_Community 26|Community 26]]
- [[_COMMUNITY_Community 27|Community 27]]
- [[_COMMUNITY_Community 28|Community 28]]
- [[_COMMUNITY_Community 29|Community 29]]
- [[_COMMUNITY_Community 30|Community 30]]
- [[_COMMUNITY_Community 31|Community 31]]
- [[_COMMUNITY_Community 32|Community 32]]
- [[_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 49|Community 49]]
## God Nodes (most connected - your core abstractions)
1. `get()` - 164 edges
1. `get()` - 170 edges
2. `WebSocketClient` - 26 edges
3. `push()` - 20 edges
4. `withFullscreenLoading()` - 17 edges
@@ -64,42 +67,42 @@
10. `StorageConfig` - 10 edges
## Surprising Connections (you probably didn't know these)
- `handleBatchDeleteHospital()` --calls--> `withFullscreenLoading()` [INFERRED]
views/basic-info/collection-region/index.vue → utils/fullscreen-loading.ts
- `handleBatchDelete()` --calls--> `withFullscreenLoading()` [INFERRED]
views/system/site/index.vue → utils/fullscreen-loading.ts
- `handleBatchDelete()` --calls--> `withFullscreenLoading()` [INFERRED]
views/basic-info/customer/index.vue → utils/fullscreen-loading.ts
- `handleUnauthorizedResponse()` --calls--> `fetchRefreshToken()` [INFERRED]
utils/http/index.ts → api/auth.ts
- `fetchConsumableList()` --calls--> `get()` [INFERRED]
api/production.ts → views/production/process-route/index.vue
- `fetchAllConsumables()` --calls--> `get()` [INFERRED]
api/production.ts → views/production/process-route/index.vue
- `fetchReagentList()` --calls--> `get()` [INFERRED]
api/production.ts → views/production/process-route/index.vue
- `toLogin()` --calls--> `logOut()` [INFERRED]
components/core/layouts/art-screen-lock/index.vue → utils/http/index.ts
- `fetchLeaveList()` --calls--> `get()` [INFERRED]
api/leave.ts → views/production/process-route/index.vue
## Communities
### Community 0 - "Community 0"
Cohesion: 0.01
Nodes (82): fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchCellSampleTypeList(), fetchChannelAgreementPrices() (+74 more)
Nodes (87): fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchCellSampleTypeList(), fetchChannelAgreementPrices() (+79 more)
### Community 1 - "Community 1"
Cohesion: 0.03
Nodes (60): fetchBatchGenerateProtocolNos(), fetchBatchGenerateSampleCodes(), fetchBatchIssueProtocolNos(), fetchBatchIssueSampleCodes(), fetchCollectionProtocolNoList(), fetchCreateCollectionProtocolNo(), fetchCreateSampleCode(), fetchDeleteCollectionProtocolNo() (+52 more)
Nodes (61): fetchBatchGenerateProtocolNos(), fetchBatchGenerateSampleCodes(), fetchBatchIssueProtocolNos(), fetchBatchIssueSampleCodes(), fetchCollectionProtocolNoList(), fetchCreateCollectionProtocolNo(), fetchCreateSampleCode(), fetchDeleteCollectionProtocolNo() (+53 more)
### Community 2 - "Community 2"
Cohesion: 0.04
Nodes (31): fetchGetUserInfo(), clickTab(), handleTouchEnd(), setTransition(), IframeRouteManager, RouteRegistry, useCommon(), setupAfterEachGuard() (+23 more)
Cohesion: 0.03
Nodes (39): fetchGetMenuList(), fetchGetMenuCodes(), fetchGetUserInfo(), fetchLogin(), fetchRefreshToken(), clickTab(), handleTouchEnd(), setTransition() (+31 more)
### Community 3 - "Community 3"
Cohesion: 0.03
Nodes (22): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllConsumableStockLogs(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents(), fetchAllReagentStockLogs() (+14 more)
### Community 4 - "Community 4"
Cohesion: 0.04
Nodes (40): fetchDeptTree(), fetchPostBoundUserIds(), FireworkSystem, reload(), formatUserLabel(), loadDeptData(), onPersonSelectChange(), subscribeTokenRefresh() (+32 more)
### Community 4 - "Community 4"
Cohesion: 0.03
Nodes (21): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllConsumableStockLogs(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents(), fetchAllReagentStockLogs() (+13 more)
### Community 5 - "Community 5"
Cohesion: 0.04
Nodes (34): fetchCollectionRegionList(), fetchCreateCollectionRegion(), fetchCreateHospital(), fetchDeleteCollectionRegion(), fetchDeleteHospital(), fetchHospitalList(), fetchUpdateCollectionRegion(), fetchUpdateHospital() (+26 more)
Nodes (33): fetchCollectionRegionList(), fetchCreateCollectionRegion(), fetchCreateHospital(), fetchDeleteCollectionRegion(), fetchDeleteHospital(), fetchHospitalList(), fetchUpdateCollectionRegion(), fetchUpdateHospital() (+25 more)
### Community 6 - "Community 6"
Cohesion: 0.04
@@ -107,15 +110,15 @@ Nodes (23): fetchAttachmentDownloadUrl(), fetchOrderAdjusts(), fetchOrderDetail(
### Community 7 - "Community 7"
Cohesion: 0.05
Nodes (39): fetchAccessLogList(), fetchCleanRoomList(), fetchCreateAccessLog(), fetchCreateCleanRoom(), fetchCreateSite(), fetchDeleteAccessLog(), fetchDeleteCleanRoom(), fetchDeleteSite() (+31 more)
Nodes (40): fetchAccessLogList(), fetchCleanRoomList(), fetchCreateAccessLog(), fetchCreateCleanRoom(), fetchCreateSite(), fetchDeleteAccessLog(), fetchDeleteCleanRoom(), fetchDeleteSite() (+32 more)
### Community 8 - "Community 8"
Cohesion: 0.06
Nodes (12): collectIds(), useSettingsHandlers(), useSettingsPanel(), useSettingsState(), ComponentLoader, RoutePermissionValidator, RouteTransformer, useCeremony() (+4 more)
Cohesion: 0.09
Nodes (5): handleReset(), handleSearch(), WebSocketClient, TableCache, emit()
### Community 9 - "Community 9"
Cohesion: 0.1
Nodes (2): WebSocketClient, TableCache
Cohesion: 0.06
Nodes (12): collectIds(), useSettingsHandlers(), useSettingsPanel(), useSettingsState(), ComponentLoader, RoutePermissionValidator, RouteTransformer, useCeremony() (+4 more)
### Community 10 - "Community 10"
Cohesion: 0.07
@@ -123,68 +126,68 @@ Nodes (21): auditorDisplay(), dash(), expressName(), formatYm(), formatYmd(), sa
### Community 11 - "Community 11"
Cohesion: 0.09
Nodes (8): getDefaultLanguage(), checkStorageCompatibility(), StorageConfig, getSystemStorage(), getSysVersion(), StorageKeyManager, StorageCompatibilityManager, validateStorageData()
Nodes (30): base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url(), hexToBytes(), sha256Hex() (+22 more)
### Community 12 - "Community 12"
Cohesion: 0.09
Nodes (29): base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url(), hexToBytes(), sha256Hex() (+21 more)
Nodes (8): getDefaultLanguage(), checkStorageCompatibility(), StorageConfig, getSystemStorage(), getSysVersion(), StorageKeyManager, StorageCompatibilityManager, validateStorageData()
### Community 13 - "Community 13"
Cohesion: 0.11
Nodes (8): disableDevTools(), handleLock(), handleUnlock(), isMobile(), toLogin(), logOut(), systemUpgrade(), VersionManager
Cohesion: 0.09
Nodes (16): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), postorder(), preorder(), applyTreeExpand(), handleAdd() (+8 more)
### Community 14 - "Community 14"
Cohesion: 0.1
Nodes (16): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), postorder(), preorder(), applyTreeExpand(), handleAdd() (+8 more)
### Community 15 - "Community 15"
Cohesion: 0.1
Nodes (8): fetchGetMenuList(), fetchGetMenuCodes(), fetchLogin(), fetchRefreshToken(), MenuProcessor, useAppMode(), useAuth(), buildWebEncryptedLoginBody()
### Community 16 - "Community 16"
Cohesion: 0.1
Nodes (13): fetchEthnicList(), addSample(), applyDonationBundleToForm(), confirmBatchTransportApply(), createEmptyTransport(), ensureDonationProtocolNoInOptions(), ensureEthnicOptionInList(), handleOpenForAdd() (+5 more)
### Community 17 - "Community 17"
### Community 15 - "Community 15"
Cohesion: 0.12
Nodes (15): addCopyButton(), highlightCode(), insertLineNumbers(), isBlockProcessed(), markBlockAsProcessed(), processBlock(), setupHighlightDirective(), setupRippleDirective() (+7 more)
### Community 18 - "Community 18"
### Community 16 - "Community 16"
Cohesion: 0.14
Nodes (17): fetchCreateUser(), fetchDeleteUser(), fetchResetPassword(), fetchUpdateUser(), fetchUserDetail(), formatUserDeptDisplay(), handleAdd(), handleDelete() (+9 more)
### Community 19 - "Community 19"
Cohesion: 0.11
Nodes (8): fetchCollectionProtocolAuditList(), fetchCollectionProtocolDetail(), fetchCollectionProtocolList(), fetchPaymentDetailList(), fetchReceiptConfirmedList(), fetchReceiptConfirmList(), fetchRefundConfirmedList(), fetchRefundConfirmList()
### Community 17 - "Community 17"
Cohesion: 0.15
Nodes (15): fetchProcessRouteList(), fetchConfigureTempProductionWorkOrder(), fetchCreateTempProductionWorkOrder(), fetchDeleteTempProductionWorkOrder(), fetchTempProductionWorkOrderDetail(), fetchTempProductionWorkOrderList(), handleConfigure(), handleDelete() (+7 more)
### Community 20 - "Community 20"
### Community 18 - "Community 18"
Cohesion: 0.19
Nodes (3): fetchGetLatestUpgradeLog(), systemUpgrade(), VersionManager
### Community 19 - "Community 19"
Cohesion: 0.12
Nodes (12): fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail(), fetchMyInstances(), fetchPendingTasks(), fetchProcessDefList() (+4 more)
### Community 21 - "Community 21"
### Community 20 - "Community 20"
Cohesion: 0.16
Nodes (10): useTable(), useTableImpl(), getColumnChecks(), useTableColumns(), createErrorHandler(), createSmartDebounce(), defaultResponseAdapter(), extractPagination() (+2 more)
### Community 22 - "Community 22"
### Community 21 - "Community 21"
Cohesion: 0.27
Nodes (14): useChart(), useChartComponent(), useChartOps(), colourBlend(), getCssVar(), getDarkColor(), getLightColor(), handleElementThemeColor() (+6 more)
### Community 23 - "Community 23"
Cohesion: 0.18
Nodes (11): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), handleAdd(), handleDelete(), handleReset(), handleSearch() (+3 more)
### Community 22 - "Community 22"
Cohesion: 0.17
Nodes (12): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), handleAdd(), handleBatchDelete(), handleDelete(), handleReset() (+4 more)
### Community 25 - "Community 25"
### Community 24 - "Community 24"
Cohesion: 0.17
Nodes (3): fetchResearchDonationAuditList(), fetchResearchDonationDetail(), fetchResearchDonationList()
### Community 26 - "Community 26"
### Community 25 - "Community 25"
Cohesion: 0.18
Nodes (3): fetchMaterialList(), fetchMaterialOptions(), fetchMaterialStockLogList()
### Community 27 - "Community 27"
### Community 26 - "Community 26"
Cohesion: 0.2
Nodes (2): fetchQcMaterialList(), fetchQcMaterialOptions()
### Community 28 - "Community 28"
Cohesion: 0.32
Nodes (3): annotateForkEdges(), chainApiToElements(), nextAncestorLink()
### Community 29 - "Community 29"
Cohesion: 0.39
Nodes (6): isIgnorableScriptError(), normalizeErrorMessage(), registerPromiseErrorHandler(), registerResourceErrorHandler(), scriptErrorHandler(), setupErrorHandle()
@@ -194,92 +197,108 @@ Cohesion: 0.36
Nodes (1): TableHeightCalculator
### Community 31 - "Community 31"
Cohesion: 0.25
Nodes (2): fetchQcTaskDetail(), fetchQcTaskList()
### Community 32 - "Community 32"
Cohesion: 0.33
Nodes (2): logApiCryptoBootInfo(), maskSecret()
### Community 32 - "Community 32"
### Community 33 - "Community 33"
Cohesion: 0.29
Nodes (2): fetchLeaveDetail(), fetchLeaveList()
### Community 34 - "Community 34"
Cohesion: 0.47
Nodes (3): isPaymentDueWithinWarnWindow(), parsePaymentDueYmd(), startOfLocalDay()
### Community 34 - "Community 34"
Cohesion: 0.33
Nodes (2): fetchQcThirdPartyItemList(), fetchQcThirdPartyItemsActive()
### Community 35 - "Community 35"
### Community 36 - "Community 36"
Cohesion: 0.33
Nodes (2): fetchQcThirdPartyCompanyList(), fetchQcThirdPartyCompanyOptions()
### Community 36 - "Community 36"
### Community 37 - "Community 37"
Cohesion: 0.33
Nodes (2): fetchQcLabTestItemList(), fetchQcLabTestItemsActive()
### Community 37 - "Community 37"
### Community 38 - "Community 38"
Cohesion: 0.33
Nodes (2): fetchQcTaskDetail(), fetchQcTaskList()
Nodes (2): fetchTempQcWorkOrderDetail(), fetchTempQcWorkOrderList()
### Community 39 - "Community 39"
Cohesion: 0.33
Nodes (2): fetchQcWorkOrderDetail(), fetchQcWorkOrderList()
### Community 41 - "Community 41"
Cohesion: 0.53
Nodes (4): isEtaTodayOrEarlier(), parseEtaLocalDate(), sampleReceptionRowClassName(), startOfLocalDay()
### Community 40 - "Community 40"
### Community 42 - "Community 42"
Cohesion: 0.5
Nodes (2): focusInput(), handleKeydown()
### Community 41 - "Community 41"
### Community 44 - "Community 44"
Cohesion: 0.67
Nodes (2): if(), logSidebarMenu()
### Community 42 - "Community 42"
### Community 45 - "Community 45"
Cohesion: 0.5
Nodes (1): fetchStorageEntrySmsList()
### Community 46 - "Community 46"
Cohesion: 0.5
Nodes (1): fetchSampleSmsNotificationList()
### Community 43 - "Community 43"
### Community 47 - "Community 47"
Cohesion: 1.0
Nodes (2): getSettingDefaults(), resetToDefaults()
### Community 45 - "Community 45"
### Community 49 - "Community 49"
Cohesion: 1.0
Nodes (2): defaultAvatarPathByRole(), resolveUserAvatarUrl()
## Knowledge Gaps
- **Thin community `Community 9`** (42 nodes): `WebSocketClient`, `.calculateReconnectDelay()`, `.clearAllTimers()`, `.clearConnectionTimers()`, `.clearTimer()`, `.close()`, `.closeCurrentSocketForReconnect()`, `.connect()`, `.connectionStatusText()`, `.constructor()`, `.destroyInstance()`, `.flushMessageQueue()`, `.getInstance()`, `.getReadyStateText()`, `.handleClose()`, `.handleConnectionTimeout()`, `.handleError()`, `.handleMessage()`, `.handleOpen()`, `.init()`, `.isWebSocketConnected()`, `.reconnect()`, `.resetHeartbeat()`, `.send()`, `.startHeartbeat()`, `.startPing()`, `TableCache`, `.cleanupExpired()`, `.clear()`, `.clearByTags()`, `.clearCurrentSearch()`, `.clearPagination()`, `.constructor()`, `.evictLRU()`, `.generateKey()`, `.generateTags()`, `.get()`, `.getStats()`, `.log()`, `.set()`, `index.ts`, `tableCache.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 27`** (10 nodes): `fetchBatchStockInQcMaterial()`, `fetchBatchStockOutQcMaterial()`, `fetchCreateQcMaterial()`, `fetchDeleteQcMaterial()`, `fetchQcMaterialList()`, `fetchQcMaterialOptions()`, `fetchStockInQcMaterial()`, `fetchStockOutQcMaterial()`, `fetchUpdateQcMaterial()`, `qc-material.ts`
- **Thin community `Community 26`** (10 nodes): `fetchBatchStockInQcMaterial()`, `fetchBatchStockOutQcMaterial()`, `fetchCreateQcMaterial()`, `fetchDeleteQcMaterial()`, `fetchQcMaterialList()`, `fetchQcMaterialOptions()`, `fetchStockInQcMaterial()`, `fetchStockOutQcMaterial()`, `fetchUpdateQcMaterial()`, `qc-material.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 30`** (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 31`** (7 nodes): `App.vue`, `main.ts`, `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `console.ts`
- **Thin community `Community 31`** (8 nodes): `fetchCompleteQcTask()`, `fetchCreateQcTask()`, `fetchDeleteQcTask()`, `fetchQcTaskDetail()`, `fetchQcTaskList()`, `fetchStartQcTask()`, `fetchUpdateQcTaskAttachments()`, `qc-task.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 34`** (6 nodes): `fetchCreateQcThirdPartyItem()`, `fetchDeleteQcThirdPartyItem()`, `fetchQcThirdPartyItemList()`, `fetchQcThirdPartyItemsActive()`, `fetchUpdateQcThirdPartyItem()`, `qc-third-party-item.ts`
- **Thin community `Community 32`** (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 35`** (6 nodes): `fetchCreateQcThirdPartyCompany()`, `fetchDeleteQcThirdPartyCompany()`, `fetchQcThirdPartyCompanyList()`, `fetchQcThirdPartyCompanyOptions()`, `fetchUpdateQcThirdPartyCompany()`, `qc-third-party-company.ts`
- **Thin community `Community 33`** (7 nodes): `fetchCreateLeave()`, `fetchDeleteLeave()`, `fetchLeaveDetail()`, `fetchLeaveList()`, `fetchSubmitLeave()`, `fetchUpdateLeave()`, `leave.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 36`** (6 nodes): `fetchCreateQcLabTestItem()`, `fetchDeleteQcLabTestItem()`, `fetchQcLabTestItemList()`, `fetchQcLabTestItemsActive()`, `fetchUpdateQcLabTestItem()`, `qc-lab-test-item.ts`
- **Thin community `Community 36`** (6 nodes): `fetchCreateQcThirdPartyCompany()`, `fetchDeleteQcThirdPartyCompany()`, `fetchQcThirdPartyCompanyList()`, `fetchQcThirdPartyCompanyOptions()`, `fetchUpdateQcThirdPartyCompany()`, `qc-third-party-company.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 37`** (6 nodes): `fetchCompleteQcTask()`, `fetchQcTaskDetail()`, `fetchQcTaskList()`, `fetchStartQcTask()`, `fetchUpdateQcTaskAttachments()`, `qc-task.ts`
- **Thin community `Community 37`** (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 40`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
- **Thin community `Community 38`** (6 nodes): `fetchConfigureTempQcWorkOrder()`, `fetchCreateTempQcWorkOrder()`, `fetchDeleteTempQcWorkOrder()`, `fetchTempQcWorkOrderDetail()`, `fetchTempQcWorkOrderList()`, `temp-qc-work-order.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 41`** (4 nodes): `if()`, `logSidebarMenu()`, `index.vue`, `SidebarSubmenu.vue`
- **Thin community `Community 39`** (6 nodes): `fetchConfigureQcWorkOrder()`, `fetchCreateQcWorkOrder()`, `fetchDeleteQcWorkOrder()`, `fetchQcWorkOrderDetail()`, `fetchQcWorkOrderList()`, `qc-work-order.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 42`** (4 nodes): `fetchGenerateSms()`, `fetchSampleSmsNotificationList()`, `fetchToggleSmsNotified()`, `sample-sms-notification.ts`
- **Thin community `Community 42`** (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 43`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
- **Thin community `Community 44`** (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 45`** (3 nodes): `defaultAvatarPathByRole()`, `resolveUserAvatarUrl()`, `user-avatar.ts`
- **Thin community `Community 45`** (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 46`** (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 47`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 49`** (3 nodes): `defaultAvatarPathByRole()`, `resolveUserAvatarUrl()`, `user-avatar.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 5`, `Community 6`, `Community 7`, `Community 14`, `Community 15`, `Community 16`, `Community 18`, `Community 19`, `Community 20`, `Community 23`, `Community 25`, `Community 26`, `Community 27`, `Community 34`, `Community 35`, `Community 36`, `Community 37`, `Community 42`?**
_High betweenness centrality (0.293) - this node is a cross-community bridge._
- **Why does `push()` connect `Community 4` to `Community 0`, `Community 6`, `Community 8`, `Community 9`, `Community 13`, `Community 16`?**
_High betweenness centrality (0.117) - this node is a cross-community bridge._
- **Why does `handleDynamicRoutes()` connect `Community 2` to `Community 8`, `Community 15`?**
_High betweenness centrality (0.049) - this node is a cross-community bridge._
- **Are the 163 inferred relationships involving `get()` (e.g. with `collectLeafFacilityNodesInTreeOrder()` and `buildTreePathLabelFromFlat()`) actually correct?**
_`get()` has 163 INFERRED edges - model-reasoned connections that need verification._
- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 13`, `Community 14`, `Community 16`, `Community 17`, `Community 18`, `Community 19`, `Community 22`, `Community 24`, `Community 25`, `Community 26`, `Community 28`, `Community 31`, `Community 33`, `Community 36`, `Community 37`, `Community 38`, `Community 39`, `Community 45`, `Community 46`?**
_High betweenness centrality (0.311) - this node is a cross-community bridge._
- **Why does `push()` connect `Community 3` to `Community 0`, `Community 6`, `Community 8`, `Community 9`, `Community 14`, `Community 18`?**
_High betweenness centrality (0.100) - this node is a cross-community bridge._
- **Why does `fetchGetLatestUpgradeLog()` connect `Community 18` to `Community 0`?**
_High betweenness centrality (0.044) - this node is a cross-community bridge._
- **Are the 169 inferred relationships involving `get()` (e.g. with `collectLeafFacilityNodesInTreeOrder()` and `buildTreePathLabelFromFlat()`) actually correct?**
_`get()` has 169 INFERRED edges - model-reasoned connections that need verification._
- **Are the 19 inferred relationships involving `push()` (e.g. with `collectLeafFacilityNodesInTreeOrder()` and `fetchAllPagedRecords()`) actually correct?**
_`push()` has 19 INFERRED edges - model-reasoned connections that need verification._
- **Are the 16 inferred relationships involving `withFullscreenLoading()` (e.g. with `.close()` and `handleSubmit()`) actually correct?**
+4 -4
View File
@@ -57,12 +57,12 @@
</div>
<div id="legend"></div>
</div>
<div id="stats">1528 nodes &middot; 1917 edges &middot; 247 communities</div>
<div id="stats">1581 nodes &middot; 1982 edges &middot; 257 communities</div>
</div>
<script>
const RAW_NODES = [{"id": "app_vue", "label": "App.vue", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "App.vue", "community": 31, "community_name": "Community 31", "source_file": "App.vue", "file_type": "code", "degree": 3}, {"id": "src_app_refreshbuttonautoicons", "label": "refreshButtonAutoIcons()", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshButtonAutoIcons()", "community": 31, "community_name": "Community 31", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "src_app_refreshpaneltitleicons", "label": "refreshPanelTitleIcons()", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshPanelTitleIcons()", "community": 31, "community_name": "Community 31", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "main_ts", "label": "main.ts", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.5, "font": {"size": 0, "color": "#ffffff"}, "title": "main.ts", "community": 31, "community_name": "Community 31", "source_file": "main.ts", "file_type": "code", "degree": 4}, {"id": "src_main_masksecret", "label": "maskSecret()", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "maskSecret()", "community": 31, "community_name": "Community 31", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "src_main_logapicryptobootinfo", "label": "logApiCryptoBootInfo()", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "logApiCryptoBootInfo()", "community": 31, "community_name": "Community 31", "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": 78, "community_name": "Community 78", "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": 79, "community_name": "Community 79", "source_file": "shims-loose.d.ts", "file_type": "code", "degree": 0}, {"id": "directives_index_ts", "label": "index.ts", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "index.ts", "community": 17, "community_name": "Community 17", "source_file": "directives/index.ts", "file_type": "code", "degree": 1}, {"id": "directives_index_setupglobdirectives", "label": "setupGlobDirectives()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.7, "font": {"size": 0, "color": "#ffffff"}, "title": "setupGlobDirectives()", "community": 17, "community_name": "Community 17", "source_file": "directives/index.ts", "file_type": "code", "degree": 5}, {"id": "directives_core_roles_ts", "label": "roles.ts", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "roles.ts", "community": 17, "community_name": "Community 17", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 3}, {"id": "core_roles_checkrolepermission", "label": "checkRolePermission()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "checkRolePermission()", "community": 17, "community_name": "Community 17", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 2}, {"id": "core_roles_removeelement", "label": "removeElement()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "removeElement()", "community": 17, "community_name": "Community 17", "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": 237}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 99}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 69}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 65}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 64}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 62}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 51}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 50}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 47}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 42}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 42}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 37}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 37}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 28}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 28}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 27}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 27}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 22}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 20}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 19}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 19}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 17}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 16}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 15}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 13}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 12}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 11}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 10}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 9}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 8}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 8}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 7}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 6}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 6}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 6}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 6}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 6}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 6}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 6}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 6}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 5}, {"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": 3}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 3}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 3}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 3}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 3}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 3}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 3}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 3}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 3}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 2}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 2}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 2}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 2}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 2}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 2}, {"cid": 59, "color": "#BAB0AC", "label": "Community 59", "count": 2}, {"cid": 60, "color": "#4E79A7", "label": "Community 60", "count": 2}, {"cid": 61, "color": "#F28E2B", "label": "Community 61", "count": 2}, {"cid": 62, "color": "#E15759", "label": "Community 62", "count": 2}, {"cid": 63, "color": "#76B7B2", "label": "Community 63", "count": 2}, {"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": 2}, {"ciLine truncated
const RAW_NODES = [{"id": "app_vue", "label": "App.vue", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "App.vue", "community": 32, "community_name": "Community 32", "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": 32, "community_name": "Community 32", "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": 32, "community_name": "Community 32", "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.5, "font": {"size": 0, "color": "#ffffff"}, "title": "main.ts", "community": 32, "community_name": "Community 32", "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.3, "font": {"size": 0, "color": "#ffffff"}, "title": "maskSecret()", "community": 32, "community_name": "Community 32", "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.3, "font": {"size": 0, "color": "#ffffff"}, "title": "logApiCryptoBootInfo()", "community": 32, "community_name": "Community 32", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "env_d_ts", "label": "env.d.ts", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "env.d.ts", "community": 83, "community_name": "Community 83", "source_file": "env.d.ts", "file_type": "code", "degree": 0}, {"id": "shims_loose_d_ts", "label": "shims-loose.d.ts", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "shims-loose.d.ts", "community": 84, "community_name": "Community 84", "source_file": "shims-loose.d.ts", "file_type": "code", "degree": 0}, {"id": "directives_index_ts", "label": "index.ts", "color": {"background": "#EDC948", "border": "#EDC948", "highlight": {"background": "#ffffff", "border": "#EDC948"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "index.ts", "community": 15, "community_name": "Community 15", "source_file": "directives/index.ts", "file_type": "code", "degree": 1}, {"id": "directives_index_setupglobdirectives", "label": "setupGlobDirectives()", "color": {"background": "#EDC948", "border": "#EDC948", "highlight": {"background": "#ffffff", "border": "#EDC948"}}, "size": 10.7, "font": {"size": 0, "color": "#ffffff"}, "title": "setupGlobDirectives()", "community": 15, "community_name": "Community 15", "source_file": "directives/index.ts", "file_type": "code", "degree": 5}, {"id": "directives_core_roles_ts", "label": "roles.ts", "color": {"background": "#EDC948", "border": "#EDC948", "highlight": {"background": "#ffffff", "border": "#EDC948"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "roles.ts", "community": 15, "community_name": "Community 15", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 3}, {"id": "core_roles_checkrolepermission", "label": "checkRolePermission()", "color": {"background": "#EDC948", "border": "#EDC948", "highlight": {"background": "#ffffff", "border": "#EDC948"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "checkRolePermission()", "community": 15, "community_name": "Community 15", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 2}, {"id": "core_roles_removeelement", "label": "removeElement()", "color": {"background": "#EDC948", "border": "#EDC948", "highlight": {"background": "#ffffff", "border": "#EDC948"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "removeElement()", "community": 15, "community_name": "Community 15", "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": 247}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 102}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 96}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 64}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 64}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 63}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 51}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 51}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 48}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 47}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 42}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 38}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 36}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 29}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 27}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 22}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 20}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 20}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 19}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 19}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 17}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 16}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 16}, {"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": 11}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 10}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 9}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 8}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 8}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 8}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 8}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 7}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 7}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 6}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 6}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 6}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 6}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 6}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 6}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 6}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 6}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 5}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 5}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 4}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 4}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 4}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 3}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 3}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 3}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 3}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 3}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 3}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 3}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 3}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 3}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 3}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 2}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 2}, {"cid": 59, "color": "#BAB0AC", "label": "Community 59", "count": 2}, {"cid": 60, "color": "#4E79A7", "label": "Community 60", "count": 2}, {"cid": 61, "color": "#F28E2B", "label": "Community 61", "count": 2}, {"cid": 62, "color": "#E15759", "label": "Community 62", "count": 2}, {"cid": 63, "color": "#76B7B2", "label": "Community 63", "count": 2}, {"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": 2}, {"ciLine 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
+26 -14
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": 1779591450.358872,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779591016.4400284,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1779604315.8915417,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779604032.215516,
"/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": 1778775532.895603,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/locales/index.ts": 1778582068.004279,
@@ -44,7 +44,7 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/fullscreen-loading.ts": 1779117454.818312,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/router.ts": 1778582068.0393815,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/protocol-payment-labels.ts": 1779199125.4894068,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts": 1779491563.5260131,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts": 1779591615.5295093,
"/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": 1778775532.8963482,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/tree-path.ts": 1778800847.103174,
@@ -201,7 +201,7 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-third-party-item.ts": 1779017836.101254,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts": 1779521577.6135197,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/workflow.ts": 1778729873.096252,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts": 1779586811.7854557,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts": 1779600111.2513275,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task-result.ts": 1779332042.258692,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-third-party-company.ts": 1779021425.3773527,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts": 1779244706.1724706,
@@ -211,19 +211,21 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts": 1779418057.065808,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/ethnic.ts": 1778929297.3919344,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-lab-test-item.ts": 1779017838.8231559,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts": 1779339624.2590876,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts": 1779594447.3049195,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/sample-sms-notification.ts": 1779526400.8134577,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/research-donation.ts": 1779160004.1431847,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts": 1779182848.881475,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts": 1779602723.2575788,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-item-param.ts": 1779022632.5658252,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-task.ts": 1779586811.7854652,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts": 1779602838.322659,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts": 1779602786.8834233,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-material-usage.ts": 1779284726.00147,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-material.ts": 1779283280.0705447,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/system.ts": 1778582067.862994,
"/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": 1779255909.8553667,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/auth.ts": 1778582067.8610063,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts": 1779431182.2279022,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts": 1779600090.8001392,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/auth/login/index.vue": 1779525781.6116724,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/refund-confirm/index.vue": 1779525781.6125906,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/index.vue": 1779543540.7143836,
@@ -277,17 +279,26 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/dept/index.vue": 1779543540.7411766,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/dict/index.vue": 1779543540.7390752,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/leave/index.vue": 1779543540.7368186,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue": 1779602838.3227367,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/output-type/index.vue": 1779543540.722031,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue": 1779590060.1180878,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue": 1779600859.6291723,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/SourceChainGraph.vue": 1779601463.4693549,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue": 1779601451.8287098,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainInject.ts": 1779601450.6340923,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chain-types.ts": 1779601448.2115095,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainFlowTheme.ts": 1779601377.1288052,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainNodeCard.vue": 1779600815.2857296,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/SampleCustomerViewDialog.vue": 1779600821.8288937,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts": 1779601450.3906622,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue": 1779543540.7289982,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/sop/index.vue": 1779543540.726837,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue": 1779586846.3205786,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue": 1779602728.4930344,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/reagent/index.vue": 1779589143.1296964,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue": 1779586813.5815248,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue": 1779602286.5549738,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts": 1779590059.4362776,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue": 1779525781.678735,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue": 1779525781.679505,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1779525781.680895,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1779601455.886211,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/EditTaskInfoDialog.vue": 1779525781.6816807,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputList.vue": 1779525781.6822913,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue": 1779525781.6835842,
@@ -297,12 +308,13 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process/index.vue": 1779543540.717012,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/index.vue": 1779543540.7154598,
"/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": 1779543540.7031858,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue": 1779602482.5582962,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception-check-item/index.vue": 1779543540.7019148,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/lab-test-item/index.vue": 1779543540.7013087,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-material/index.vue": 1779543540.7000535,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue": 1779543540.698455,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue": 1779586314.29511,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue": 1779604458.3034396,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue": 1779602725.6401753,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/index.vue": 1779543540.695075,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue": 1779525781.7374997,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseViewDialog.vue": 1779525781.743236,
@@ -342,7 +354,7 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/router/core/RoutePermissionValidator.ts": 1778582068.0127926,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/router/core/index.ts": 1778582068.0138035,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/router/core/IframeRouteManager.ts": 1778582068.0121944,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/router/modules/sqxp.ts": 1779528258.7914715,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/router/modules/sqxp.ts": 1779603531.1964977,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/router/modules/index.ts": 1778598510.6043763,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/router/modules/exception.ts": 1779546186.993006,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/router/routes/staticRoutes.ts": 1779546226.1088154,
+3
View File
@@ -58,12 +58,14 @@
"@element-plus/icons-vue": "^2.3.2",
"@iconify/vue": "^5.0.0",
"@tailwindcss/vite": "^4.1.14",
"@vue-flow/core": "^1.48.2",
"@vue/reactivity": "^3.5.21",
"@vueuse/core": "^13.9.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "next",
"axios": "^1.12.2",
"crypto-js": "^4.2.0",
"dagre": "^0.8.5",
"echarts": "^6.0.0",
"element-plus": "^2.11.2",
"exceljs": "^4.4.0",
@@ -89,6 +91,7 @@
"@commitlint/cli": "^19.4.1",
"@commitlint/config-conventional": "^19.4.1",
"@eslint/js": "^9.9.1",
"@types/dagre": "^0.7.54",
"@types/node": "^24.0.5",
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
+152
View File
@@ -20,6 +20,9 @@ importers:
'@tailwindcss/vite':
specifier: ^4.1.14
version: 4.1.14(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))
'@vue-flow/core':
specifier: ^1.48.2
version: 1.48.2(vue@3.5.22(typescript@5.6.3))
'@vue/reactivity':
specifier: ^3.5.21
version: 3.5.22
@@ -38,6 +41,9 @@ importers:
crypto-js:
specifier: ^4.2.0
version: 4.2.0
dagre:
specifier: ^0.8.5
version: 0.8.5
echarts:
specifier: ^6.0.0
version: 6.0.0
@@ -108,6 +114,9 @@ importers:
'@eslint/js':
specifier: ^9.9.1
version: 9.36.0
'@types/dagre':
specifier: ^0.7.54
version: 0.7.54
'@types/node':
specifier: ^24.0.5
version: 24.8.1
@@ -1131,6 +1140,9 @@ packages:
'@types/conventional-commits-parser@5.0.1':
resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==}
'@types/dagre@0.7.54':
resolution: {integrity: sha512-QjcRY+adGbYvBFS7cwv5txhVIwX1XXIUswWl+kSQTbI6NjgZydrZkEKX/etzVd7i+bCsCb40Z/xlBY5eoFuvWQ==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -1158,6 +1170,9 @@ packages:
'@types/web-bluetooth@0.0.16':
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
'@types/web-bluetooth@0.0.20':
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
@@ -1253,6 +1268,11 @@ packages:
'@volar/typescript@2.4.23':
resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==}
'@vue-flow/core@1.48.2':
resolution: {integrity: sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA==}
peerDependencies:
vue: ^3.3.0
'@vue/babel-helper-vue-transform-on@1.5.0':
resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==}
@@ -1326,6 +1346,9 @@ packages:
'@vue/shared@3.5.22':
resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==}
'@vueuse/core@10.11.1':
resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
'@vueuse/core@13.9.0':
resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==}
peerDependencies:
@@ -1334,12 +1357,18 @@ packages:
'@vueuse/core@9.13.0':
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
'@vueuse/metadata@10.11.1':
resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
'@vueuse/metadata@13.9.0':
resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==}
'@vueuse/metadata@9.13.0':
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
'@vueuse/shared@10.11.1':
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
'@vueuse/shared@13.9.0':
resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==}
peerDependencies:
@@ -1832,10 +1861,51 @@ packages:
resolution: {integrity: sha512-LaZ+8whPPUOo6Y0Zy4nIbf6JOleV3ejp41sT6N4RPKiKKA+ICWf4ueeIlxIO8b6JtdlDxRzHH/EcRji07nDxcg==}
engines: {node: '>=v12.20.0'}
d3-color@3.1.0:
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
engines: {node: '>=12'}
d3-dispatch@3.0.1:
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
engines: {node: '>=12'}
d3-drag@3.0.0:
resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
engines: {node: '>=12'}
d3-ease@3.0.1:
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
engines: {node: '>=12'}
d3-interpolate@3.0.1:
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
engines: {node: '>=12'}
d3-selection@3.0.0:
resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
engines: {node: '>=12'}
d3-timer@3.0.1:
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
engines: {node: '>=12'}
d3-transition@3.0.1:
resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
engines: {node: '>=12'}
peerDependencies:
d3-selection: 2 - 3
d3-zoom@3.0.0:
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
engines: {node: '>=12'}
d@1.0.2:
resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
engines: {node: '>=0.12'}
dagre@0.8.5:
resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==}
danmu.js@1.1.13:
resolution: {integrity: sha512-knFd0/cB2HA4FFWiA7eB2suc5vCvoHdqio33FyyCSfP7C+1A+zQcTvnvwfxaZhrxsGj4qaQI2I8XiTqedRaVmg==}
@@ -2409,6 +2479,9 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
graphlib@2.1.8:
resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==}
has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
@@ -4962,6 +5035,8 @@ snapshots:
dependencies:
'@types/node': 24.8.1
'@types/dagre@0.7.54': {}
'@types/estree@1.0.8': {}
'@types/event-emitter@0.3.5': {}
@@ -4984,6 +5059,8 @@ snapshots:
'@types/web-bluetooth@0.0.16': {}
'@types/web-bluetooth@0.0.20': {}
'@types/web-bluetooth@0.0.21': {}
'@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3)':
@@ -5126,6 +5203,17 @@ snapshots:
path-browserify: 1.0.1
vscode-uri: 3.1.0
'@vue-flow/core@1.48.2(vue@3.5.22(typescript@5.6.3))':
dependencies:
'@vueuse/core': 10.11.1(vue@3.5.22(typescript@5.6.3))
d3-drag: 3.0.0
d3-interpolate: 3.0.1
d3-selection: 3.0.0
d3-zoom: 3.0.0
vue: 3.5.22(typescript@5.6.3)
transitivePeerDependencies:
- '@vue/composition-api'
'@vue/babel-helper-vue-transform-on@1.5.0': {}
'@vue/babel-plugin-jsx@1.5.0(@babel/core@7.28.4)':
@@ -5259,6 +5347,16 @@ snapshots:
'@vue/shared@3.5.22': {}
'@vueuse/core@10.11.1(vue@3.5.22(typescript@5.6.3))':
dependencies:
'@types/web-bluetooth': 0.0.20
'@vueuse/metadata': 10.11.1
'@vueuse/shared': 10.11.1(vue@3.5.22(typescript@5.6.3))
vue-demi: 0.14.10(vue@3.5.22(typescript@5.6.3))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@vueuse/core@13.9.0(vue@3.5.22(typescript@5.6.3))':
dependencies:
'@types/web-bluetooth': 0.0.21
@@ -5276,10 +5374,19 @@ snapshots:
- '@vue/composition-api'
- vue
'@vueuse/metadata@10.11.1': {}
'@vueuse/metadata@13.9.0': {}
'@vueuse/metadata@9.13.0': {}
'@vueuse/shared@10.11.1(vue@3.5.22(typescript@5.6.3))':
dependencies:
vue-demi: 0.14.10(vue@3.5.22(typescript@5.6.3))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@vueuse/shared@13.9.0(vue@3.5.22(typescript@5.6.3))':
dependencies:
vue: 3.5.22(typescript@5.6.3)
@@ -5819,11 +5926,52 @@ snapshots:
cz-git@1.12.0: {}
d3-color@3.1.0: {}
d3-dispatch@3.0.1: {}
d3-drag@3.0.0:
dependencies:
d3-dispatch: 3.0.1
d3-selection: 3.0.0
d3-ease@3.0.1: {}
d3-interpolate@3.0.1:
dependencies:
d3-color: 3.1.0
d3-selection@3.0.0: {}
d3-timer@3.0.1: {}
d3-transition@3.0.1(d3-selection@3.0.0):
dependencies:
d3-color: 3.1.0
d3-dispatch: 3.0.1
d3-ease: 3.0.1
d3-interpolate: 3.0.1
d3-selection: 3.0.0
d3-timer: 3.0.1
d3-zoom@3.0.0:
dependencies:
d3-dispatch: 3.0.1
d3-drag: 3.0.0
d3-interpolate: 3.0.1
d3-selection: 3.0.0
d3-transition: 3.0.1(d3-selection@3.0.0)
d@1.0.2:
dependencies:
es5-ext: 0.10.64
type: 2.7.3
dagre@0.8.5:
dependencies:
graphlib: 2.1.8
lodash: 4.17.21
danmu.js@1.1.13:
dependencies:
event-emitter: 0.3.5
@@ -6485,6 +6633,10 @@ snapshots:
graphemer@1.4.0: {}
graphlib@2.1.8:
dependencies:
lodash: 4.17.21
has-flag@3.0.0: {}
has-flag@4.0.0: {}
+5
View File
@@ -6,6 +6,7 @@ export function fetchCellProductList(params?: {
keyword?: string
productStatus?: string
passage?: string
sourceType?: string
}) {
return request.get<{
list: any[]
@@ -38,3 +39,7 @@ export function fetchNextCellProductCode(count = 1) {
params: count > 1 ? { count } : undefined
})
}
export function fetchCellProductChain(productId: string) {
return request.get<any>({ url: `/web/admin/cell-products/${productId}/chain` })
}
@@ -4,6 +4,7 @@ import type { QcWorkOrderReceptionDetail } from '@/api/qc-work-order'
export interface ProductionWorkOrderItem {
id: string
workOrderNo: string
workOrderType?: string
protocolNo?: string
sampleCode?: string
receptionSource?: string
@@ -25,6 +26,7 @@ export function fetchProductionWorkOrderList(params?: {
pageSize?: number
keyword?: string
workOrderStatus?: string
workOrderType?: string
}) {
return request.get<{
list: ProductionWorkOrderItem[]
@@ -42,3 +44,36 @@ export function fetchProductionWorkOrderDetail(id: string) {
export function fetchCompleteProductionWorkOrder(id: string, data: Record<string, any>) {
return request.put({ url: `/web/admin/production-work-orders/${id}/complete`, params: data })
}
export function fetchCreateProductionWorkOrder(data: {
workOrderType?: string
protocolNo?: string
sampleCode?: string
receptionSource?: string
remark?: string
}) {
return request.post<ProductionWorkOrderItem>({
url: '/web/admin/production-work-orders',
data
})
}
export function fetchConfigureProductionWorkOrder(
id: string,
data: {
taskName?: string
taskType?: string
routeId?: string
description?: string
remark?: string
}
) {
return request.post<ProductionWorkOrderItem>({
url: `/web/admin/production-work-orders/${id}/configure`,
data
})
}
export function fetchDeleteProductionWorkOrder(id: string) {
return request.del({ url: `/web/admin/production-work-orders/${id}` })
}
+29
View File
@@ -42,6 +42,7 @@ export type QcTask = {
id: string
workOrderId: string
workOrderNo?: string
orderSource?: string
taskNo: string
taskType: string
itemCode: string
@@ -62,6 +63,12 @@ export type QcTask = {
receptionTime?: string
handlerName?: string
workOrderStatus?: string
cellProductId?: string
cellProductCode?: string
cellProductName?: string
cellProductTypeName?: string
cellProductPassage?: string
cellProductSpecName?: string
thirdPartyAttachments?: string
attachmentFiles?: Array<{ id?: string; fileUrl: string; fileName?: string; fileType?: string }>
completedTime?: string
@@ -121,3 +128,25 @@ export function fetchUpdateQcTaskAttachments(id: string, thirdPartyAttachments:
data: { thirdPartyAttachments }
})
}
export function fetchCreateQcTask(data: {
taskType: string
itemCode: string
itemName: string
companyId?: string
companyName?: string
feeAmount?: number
sampleCode?: string
qcBizType?: string
workOrderId?: string
remark?: string
}) {
return request.post<QcTask>({
url: '/web/admin/qc-tasks',
params: data
})
}
export function fetchDeleteQcTask(id: string) {
return request.del({ url: `/web/admin/qc-tasks/${id}` })
}
+36
View File
@@ -50,6 +50,7 @@ export type QcWorkOrderReceptionDetail = {
export type QcWorkOrder = {
id: string
workOrderNo: string
workOrderType?: string
receptionId: string
protocolSampleId: string
collectionProtocolId: string
@@ -57,6 +58,14 @@ export type QcWorkOrder = {
sampleCode: string
receptionSource?: string
receptionSourceLabel?: string
qcBizType?: string
qcBizTypeLabel?: string
cellProductId?: string
cellProductCode?: string
cellProductName?: string
cellProductTypeName?: string
cellProductPassage?: string
cellProductSpecName?: string
sampleTypeName?: string
receptionTime?: string
handlerName?: string
@@ -106,6 +115,7 @@ export function fetchQcWorkOrderList(params?: {
pageSize?: number
keyword?: string
workOrderStatus?: string
workOrderType?: string
}) {
return request.get<PageList<QcWorkOrder>>({
url: '/web/admin/qc-work-orders',
@@ -133,3 +143,29 @@ export function fetchConfigureQcWorkOrder(
data
})
}
export function fetchCreateQcWorkOrder(data: {
workOrderType?: string
protocolNo?: string
sampleCode?: string
receptionSource?: string
qcBizType?: string
cellProductId?: string
remark?: string
}) {
return request.post<QcWorkOrder>({
url: '/web/admin/qc-work-orders',
data
})
}
export function fetchUpdateQcWorkOrder(id: string, data: { cellProductId?: string; remark?: string }) {
return request.put<QcWorkOrder>({
url: `/web/admin/qc-work-orders/${id}`,
data
})
}
export function fetchDeleteQcWorkOrder(id: string) {
return request.del({ url: `/web/admin/qc-work-orders/${id}` })
}
@@ -91,6 +91,7 @@
taskTypeLabel,
taskStatusLabel,
qcBizTypeLabel,
qcBizTypeTagType,
receptionSourceLabel,
acceptConclusionLabel,
transportTypeLabel,
@@ -232,6 +233,7 @@
const d = viewDetail.value
if (!d) return []
const r = d.reception || {}
const isTempQc = d.qcBizType === 'temporary'
const transportFields: ViewSection['fields'] = [
{ label: '运输方式', value: transportTypeLabel(String(r.transportType || '')) }
]
@@ -249,6 +251,62 @@
if (r.etaTime) {
transportFields.push({ label: '预计到达', value: r.etaTime })
}
const cellProductSection: ViewSection[] = isTempQc
? [
{
title: '质检对象',
fields: [
{ label: '产品编码', value: d.cellProductCode || '—' },
{ label: '产品名称', value: d.cellProductName || '—' },
{ label: '产品类型', value: d.cellProductTypeName || '—' },
{ label: '代次', value: d.cellProductPassage || '—' },
{ label: '规格', value: d.cellProductSpecName || '—' }
]
}
]
: []
const sampleSections: ViewSection[] = isTempQc
? []
: [
{
title: '样本信息',
fields: [
{ label: '协议号', value: r.protocolNo || d.protocolNo || '—' },
{ label: '样本编码', value: r.sampleCode || d.sampleCode || '—' },
{
label: '样本来源',
value: r.sourceTypeLabel || d.receptionSourceLabel || receptionSourceLabel(d.receptionSource || ''),
tagType: 'primary'
},
{ label: '样本类型', value: r.sampleTypeName || d.sampleTypeName || '—' }
]
},
...(transportFields.length > 1 || r.transportType
? [{ title: '运输信息', fields: transportFields }]
: []),
{
title: '样本接收',
slot: 'reception-extra',
fields: [
{ label: '接收时间', value: r.receptionTime || d.receptionTime || '—' },
{ label: '接收经手人', value: r.handlerName || d.handlerName || '—' },
{
label: '接收温度(℃)',
value: r.receiveTemperature ? `${r.receiveTemperature}℃` : '—'
},
{ label: '体积(ml)', value: formatMetric(r.volumeMl) },
{ label: '净重(g)', value: formatMetric(r.netWeightG) },
{
label: '结论',
value:
r.acceptConclusionLabel ||
acceptConclusionLabel(String(r.acceptConclusion || ''))
},
{ label: '检查项', value: formatCheckResults(r.checkResults), span: true },
{ label: '备注', value: r.remark || '—', span: true }
]
}
]
const taskResultSummaryFields: ViewSection['fields'] = [
{
label: '任务状态',
@@ -295,7 +353,7 @@
title: '任务信息',
fields: [
{ label: '任务号', value: d.taskNo },
{ label: '业务类型', value: d.qcBizTypeLabel || qcBizTypeLabel(d.qcBizType || ''), tagType: 'success' },
{ label: '业务类型', value: d.qcBizTypeLabel || qcBizTypeLabel(d.qcBizType || ''), tagType: qcBizTypeTagType(d.qcBizType || '') },
{ label: '工单号', value: d.workOrderNo || '—' },
{
label: '工单状态',
@@ -310,44 +368,8 @@
{ label: '创建时间', value: d.createTime || '—' }
]
},
{
title: '样本信息',
fields: [
{ label: '协议号', value: r.protocolNo || d.protocolNo || '—' },
{ label: '样本编码', value: r.sampleCode || d.sampleCode || '—' },
{
label: '样本来源',
value: r.sourceTypeLabel || d.receptionSourceLabel || receptionSourceLabel(d.receptionSource || ''),
tagType: 'primary'
},
{ label: '样本类型', value: r.sampleTypeName || d.sampleTypeName || '—' }
]
},
...(transportFields.length > 1 || r.transportType
? [{ title: '运输信息', fields: transportFields }]
: []),
{
title: '样本接收',
slot: 'reception-extra',
fields: [
{ label: '接收时间', value: r.receptionTime || d.receptionTime || '—' },
{ label: '接收经手人', value: r.handlerName || d.handlerName || '—' },
{
label: '接收温度(℃)',
value: r.receiveTemperature ? `${r.receiveTemperature}℃` : '—'
},
{ label: '体积(ml)', value: formatMetric(r.volumeMl) },
{ label: '净重(g)', value: formatMetric(r.netWeightG) },
{
label: '结论',
value:
r.acceptConclusionLabel ||
acceptConclusionLabel(String(r.acceptConclusion || ''))
},
{ label: '检查项', value: formatCheckResults(r.checkResults), span: true },
{ label: '备注', value: r.remark || '—', span: true }
]
},
...cellProductSection,
...sampleSections,
...resultSection,
...tailSections
]
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_temp_production_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L1"}, {"id": "temp_production_work_order_index_statustagtype", "label": "statusTagType()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L312"}, {"id": "temp_production_work_order_index_statuslabel", "label": "statusLabel()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L316"}, {"id": "temp_production_work_order_index_loaddata", "label": "loadData()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L327"}, {"id": "temp_production_work_order_index_handlesearch", "label": "handleSearch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L343"}, {"id": "temp_production_work_order_index_handlereset", "label": "handleReset()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L348"}, {"id": "temp_production_work_order_index_handleadd", "label": "handleAdd()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L361"}, {"id": "temp_production_work_order_index_submitadd", "label": "submitAdd()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L366"}, {"id": "temp_production_work_order_index_loadrouteoptions", "label": "loadRouteOptions()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L398"}, {"id": "temp_production_work_order_index_handleconfigure", "label": "handleConfigure()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L407"}, {"id": "temp_production_work_order_index_submitconfigure", "label": "submitConfigure()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L419"}, {"id": "temp_production_work_order_index_handleview", "label": "handleView()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L467"}, {"id": "temp_production_work_order_index_handledelete", "label": "handleDelete()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L473"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_temp_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/temp-production-work-order/index.vue", "source_location": "L287", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_temp_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/temp-production-work-order/index.vue", "source_location": "L288", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_temp_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_system_view_dialog_index_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L298", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_temp_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_system_view_dialog_index_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/temp-production-work-order/index.vue", "source_location": "L299", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_temp_prodLine 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": "L104"}, {"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": "L116"}, {"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": "L122"}, {"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": "L137"}, {"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": "L150"}], "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": "L104", "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": "L116", "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": "L122", "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": "L137", "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": "L150", "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": "L110"}, {"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": "L117"}, {"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": "L131"}, {"caller_nid": "api_qc_work_order_fetchcreateqcworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L144"}, {"caller_nid": "api_qc_work_order_fetchdeleteqcworkorder", "callee": "del", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L151"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_qc_work_order_ts", "label": "temp-qc-work-order.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L1"}, {"id": "api_temp_qc_work_order_fetchtempqcworkorderlist", "label": "fetchTempQcWorkOrderList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L50"}, {"id": "api_temp_qc_work_order_fetchtempqcworkorderdetail", "label": "fetchTempQcWorkOrderDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L62"}, {"id": "api_temp_qc_work_order_fetchcreatetempqcworkorder", "label": "fetchCreateTempQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L68"}, {"id": "api_temp_qc_work_order_fetchconfiguretempqcworkorder", "label": "fetchConfigureTempQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L75"}, {"id": "api_temp_qc_work_order_fetchdeletetempqcworkorder", "label": "fetchDeleteTempQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L90"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_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/temp-qc-work-order.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_qc_work_order_ts", "target": "api_temp_qc_work_order_fetchtempqcworkorderlist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L50", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_qc_work_order_ts", "target": "api_temp_qc_work_order_fetchtempqcworkorderdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L62", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_qc_work_order_ts", "target": "api_temp_qc_work_order_fetchcreatetempqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L68", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_qc_work_order_ts", "target": "api_temp_qc_work_order_fetchconfiguretempqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L75", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_qc_work_order_ts", "target": "api_temp_qc_work_order_fetchdeletetempqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L90", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_temp_qc_work_order_fetchtempqcworkorderlist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L56"}, {"caller_nid": "api_temp_qc_work_order_fetchtempqcworkorderdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L63"}, {"caller_nid": "api_temp_qc_work_order_fetchcreatetempqcworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L69"}, {"caller_nid": "api_temp_qc_work_order_fetchconfiguretempqcworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L84"}, {"caller_nid": "api_temp_qc_work_order_fetchdeletetempqcworkorder", "callee": "del", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-qc-work-order.ts", "source_location": "L91"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chainlinkedge_vue", "label": "ChainLinkEdge.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue", "source_location": "L1"}, {"id": "source_chain_chainlinkedge_handledblclick", "label": "handleDblClick()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue", "source_location": "L94"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chainlinkedge_vue", "target": "source_chain_chainlinkedge_handledblclick", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue", "source_location": "L94", "weight": 1.0}], "raw_calls": [{"caller_nid": "source_chain_chainlinkedge_handledblclick", "callee": "trim", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue", "source_location": "L95"}, {"caller_nid": "source_chain_chainlinkedge_handledblclick", "callee": "warning", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue", "source_location": "L97"}, {"caller_nid": "source_chain_chainlinkedge_handledblclick", "callee": "openTask", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue", "source_location": "L100"}, {"caller_nid": "source_chain_chainlinkedge_handledblclick", "callee": "trim", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue", "source_location": "L100"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chainnodecard_vue", "label": "ChainNodeCard.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainNodeCard.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chain_types_ts", "label": "chain-types.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chain-types.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_sourcechaingraph_vue", "label": "SourceChainGraph.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/SourceChainGraph.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -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": "L23"}, {"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": "L38"}, {"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": "L42"}, {"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": "L46"}, {"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": "L58"}], "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": "L23", "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": "L38", "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": "L42", "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": "L46", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order_ts", "target": "api_production_work_order_fetchdeleteproductionworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L58", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_production_work_order_fetchproductionworkorderlist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L29"}, {"caller_nid": "api_production_work_order_fetchproductionworkorderdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L39"}, {"caller_nid": "api_production_work_order_fetchcompleteproductionworkorder", "callee": "put", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L43"}, {"caller_nid": "api_production_work_order_fetchcreateproductionworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L52"}, {"caller_nid": "api_production_work_order_fetchdeleteproductionworkorder", "callee": "del", "source_file": "/Users/wuzilong/work/00code/zkxw/Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chainnodecard_vue", "label": "ChainNodeCard.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainNodeCard.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chain_types_ts", "label": "chain-types.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chain-types.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_sourcechaingraph_vue", "label": "SourceChainGraph.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/SourceChainGraph.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_router_modules_sqxp_ts", "label": "sqxp.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/router/modules/sqxp.ts", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_router_modules_sqxp_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_types_router", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/router/modules/sqxp.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_taskexecutiondialog_vue", "label": "TaskExecutionDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "label": "chainToFlow.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L1"}, {"id": "source_chain_chaintoflow_nextancestorlink", "label": "nextAncestorLink()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L16"}, {"id": "source_chain_chaintoflow_makeflownode", "label": "makeFlowNode()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L24"}, {"id": "source_chain_chaintoflow_makeflowedge", "label": "makeFlowEdge()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L33"}, {"id": "source_chain_chaintoflow_chainapitoelements", "label": "chainApiToElements()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L54"}, {"id": "source_chain_chaintoflow_annotateforkedges", "label": "annotateForkEdges()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L109"}, {"id": "source_chain_chaintoflow_layoutchainelements", "label": "layoutChainElements()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L129"}, {"id": "source_chain_chaintoflow_haschaingraphcontent", "label": "hasChainGraphContent()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L168"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "dagre", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "core", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chain_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "source_chain_chaintoflow_nextancestorlink", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L16", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "source_chain_chaintoflow_makeflownode", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L24", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "source_chain_chaintoflow_makeflowedge", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L33", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "source_chain_chaintoflow_chainapitoelements", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L54", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "source_chain_chaintoflow_annotateforkedgLine 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_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": "L391"}, {"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": "L396"}, {"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": "L400"}, {"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": "L407"}, {"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": "L412"}, {"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": "L428"}, {"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": "L434"}, {"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": "L442"}, {"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": "L501"}, {"id": "qc_work_order_index_formatdetectmethods", "label": "formatDetectMethods()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L519"}, {"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": "L526"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_handlereceptionattachmentpreview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L391", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_handlereceptionattachmentdownload", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L396", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_taskattachmentfullurl", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L400", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_handletaskattachmentpreview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L407", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_handletaskattachmentdownload", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue",Line 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": "L104"}, {"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": "L116"}, {"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": "L122"}, {"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": "L137"}], "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": "L104", "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": "L116", "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": "L122", "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": "L137", "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": "L110"}, {"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": "L117"}, {"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": "L131"}, {"caller_nid": "api_qc_work_order_fetchcreateqcworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L144"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L1"}, {"id": "qc_task_index_canfillresult", "label": "canFillResult()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L528"}, {"id": "qc_task_index_clearmaterialrow", "label": "clearMaterialRow()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L550"}, {"id": "qc_task_index_ismaterialoptiondisabled", "label": "isMaterialOptionDisabled()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L557"}, {"id": "qc_task_index_isduplicatematerial", "label": "isDuplicateMaterial()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L562"}, {"id": "qc_task_index_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L709"}, {"id": "qc_task_index_normalizeresultparams", "label": "normalizeResultParams()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L713"}, {"id": "qc_task_index_toresultnumber", "label": "toResultNumber()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L721"}, {"id": "qc_task_index_setresultnumber", "label": "setResultNumber()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L727"}, {"id": "qc_task_index_hasresultvalue", "label": "hasResultValue()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L731"}, {"id": "qc_task_index_formatmetric", "label": "formatMetric()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L735"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_canfillresult", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L528", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_clearmaterialrow", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L550", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_ismaterialoptiondisabled", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L557", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_isduplicatematerial", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L562", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_handlereceptionattachmentdownload", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L709", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_normalizeresultparams", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L713", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_toresultnumber", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "label": "qc-task.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L1"}, {"id": "api_qc_task_fetchqctasklist", "label": "fetchQcTaskList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L85"}, {"id": "api_qc_task_fetchqctaskdetail", "label": "fetchQcTaskDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L100"}, {"id": "api_qc_task_fetchstartqctask", "label": "fetchStartQcTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L106"}, {"id": "api_qc_task_fetchcompleteqctask", "label": "fetchCompleteQcTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L112"}, {"id": "api_qc_task_fetchupdateqctaskattachments", "label": "fetchUpdateQcTaskAttachments()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L118"}, {"id": "api_qc_task_fetchcreateqctask", "label": "fetchCreateQcTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L125"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_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-task.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchqctasklist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L85", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchqctaskdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L100", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchstartqctask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L106", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchcompleteqctask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L112", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchupdateqctaskattachments", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L118", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchcreateqctask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L125", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_qc_task_fetchqctasklist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L94"}, {"caller_nid": "api_qc_task_fetchqctaskdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L101"}, {"caller_nid": "api_qc_task_fetchstartqctask", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L107"}, {"caller_nid": "api_qc_task_fetchcompleteqctask", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L113"}, {"caller_nid": "api_qc_task_fetchupdateqctaskattachments", "callee": "put", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L119"}, {"caller_nid": "api_qc_task_fetchcreateqctask", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L137"}]}
@@ -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_handleview", "label": "handleView()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L576"}, {"id": "production_work_order_index_handlecomplete", "label": "handleComplete()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L580"}, {"id": "production_work_order_index_handleopendispatch", "label": "handleOpenDispatch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L590"}, {"id": "production_work_order_index_handleedittask", "label": "handleEditTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L607"}, {"id": "production_work_order_index_opentaskeditor", "label": "openTaskEditor()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L622"}, {"id": "production_work_order_index_openedittaskinfo", "label": "openEditTaskInfo()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L634"}, {"id": "production_work_order_index_handleedittaskinfosuccess", "label": "handleEditTaskInfoSuccess()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L639"}, {"id": "production_work_order_index_handledeletetask", "label": "handleDeleteTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L645"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L576", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handlecomplete", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L580", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleopendispatch", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L590", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleedittask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L607", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_opentaskeditor", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L622", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_openedittaskinfo", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L634", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleedittaskinfosuccess", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L639", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_Line 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": "L23"}, {"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": "L38"}, {"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": "L42"}, {"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": "L46"}], "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": "L23", "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": "L38", "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": "L42", "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": "L46", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_production_work_order_fetchproductionworkorderlist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L29"}, {"caller_nid": "api_production_work_order_fetchproductionworkorderdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L39"}, {"caller_nid": "api_production_work_order_fetchcompleteproductionworkorder", "callee": "put", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L43"}, {"caller_nid": "api_production_work_order_fetchcreateproductionworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L52"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "label": "cell-product.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L1"}, {"id": "api_cell_product_fetchcellproductlist", "label": "fetchCellProductList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L3"}, {"id": "api_cell_product_fetchcellproductdetail", "label": "fetchCellProductDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L20"}, {"id": "api_cell_product_fetchcreatecellproduct", "label": "fetchCreateCellProduct()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L24"}, {"id": "api_cell_product_fetchupdatecellproduct", "label": "fetchUpdateCellProduct()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L28"}, {"id": "api_cell_product_fetchdeletecellproduct", "label": "fetchDeleteCellProduct()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L32"}, {"id": "api_cell_product_fetchnextcellproductcode", "label": "fetchNextCellProductCode()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L36"}, {"id": "api_cell_product_fetchcellproductchain", "label": "fetchCellProductChain()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L43"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_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/cell-product.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchcellproductlist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchcellproductdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L20", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchcreatecellproduct", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L24", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchupdatecellproduct", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L28", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchdeletecellproduct", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L32", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchnextcellproductcode", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L36", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchcellproductchain", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L43", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_cell_product_fetchcellproductlist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L11"}, {"caller_nid": "api_cell_product_fetchcellproductdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L21"}, {"caller_nid": "api_cell_product_fetchcreatecellproduct", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L25"}, {"caller_nid": "api_cell_product_fetchupdatecellproduct", "callee": "put", "source_file": "/Users/wuzilong/work/00code/zkxwLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_temp_qc_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L1"}, {"id": "temp_qc_work_order_index_formatconfiguredlabitems", "label": "formatConfiguredLabItems()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L336"}, {"id": "temp_qc_work_order_index_formatconfiguredthirditems", "label": "formatConfiguredThirdItems()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L342"}, {"id": "temp_qc_work_order_index_buildtasktypesections", "label": "buildTaskTypeSections()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L350"}, {"id": "temp_qc_work_order_index_buildconfiguredonlysection", "label": "buildConfiguredOnlySection()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L408"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_temp_qc_work_order_index_vue", "target": "temp_qc_work_order_index_formatconfiguredlabitems", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L336", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_temp_qc_work_order_index_vue", "target": "temp_qc_work_order_index_formatconfiguredthirditems", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L342", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_temp_qc_work_order_index_vue", "target": "temp_qc_work_order_index_buildtasktypesections", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L350", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_temp_qc_work_order_index_vue", "target": "temp_qc_work_order_index_buildconfiguredonlysection", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L408", "weight": 1.0}], "raw_calls": [{"caller_nid": "temp_qc_work_order_index_formatconfiguredlabitems", "callee": "join", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L338"}, {"caller_nid": "temp_qc_work_order_index_formatconfiguredlabitems", "callee": "map", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L338"}, {"caller_nid": "temp_qc_work_order_index_formatconfiguredthirditems", "callee": "join", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L344"}, {"caller_nid": "temp_qc_work_order_index_formatconfiguredthirditems", "callee": "map", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L344"}, {"caller_nid": "temp_qc_work_order_index_buildtasktypesections", "callee": "filter", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L355"}, {"caller_nid": "temp_qc_work_order_index_buildtasktypesections", "callee": "map", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/temp-qc-work-order/index.vue", "source_location": "L358"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_samplecustomerviewdialog_vue", "label": "SampleCustomerViewDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/SampleCustomerViewDialog.vue", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_samplecustomerviewdialog_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/SampleCustomerViewDialog.vue", "source_location": "L19", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_samplecustomerviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_system_view_dialog_index_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/SampleCustomerViewDialog.vue", "source_location": "L20", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_samplecustomerviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_system_view_dialog_index_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/SampleCustomerViewDialog.vue", "source_location": "L21", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_samplecustomerviewdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chain_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/SampleCustomerViewDialog.vue", "source_location": "L22", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_task_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "label": "qc-task.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L1"}, {"id": "api_qc_task_fetchqctasklist", "label": "fetchQcTaskList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L86"}, {"id": "api_qc_task_fetchqctaskdetail", "label": "fetchQcTaskDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L101"}, {"id": "api_qc_task_fetchstartqctask", "label": "fetchStartQcTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L107"}, {"id": "api_qc_task_fetchcompleteqctask", "label": "fetchCompleteQcTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L113"}, {"id": "api_qc_task_fetchupdateqctaskattachments", "label": "fetchUpdateQcTaskAttachments()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L119"}, {"id": "api_qc_task_fetchcreateqctask", "label": "fetchCreateQcTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L126"}, {"id": "api_qc_task_fetchdeleteqctask", "label": "fetchDeleteQcTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L144"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_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-task.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchqctasklist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L86", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchqctaskdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L101", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchstartqctask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L107", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchcompleteqctask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L113", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchupdateqctaskattachments", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L119", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchcreateqctask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L126", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_task_ts", "target": "api_qc_task_fetchdeleteqctask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L144", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_qc_task_fetchqctasklist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L95"}, {"caller_nid": "api_qc_task_fetchqctaskdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L102"}, {"caller_nid": "api_qc_task_fetchstartqctask", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L108"}, {"caller_nid": "api_qc_task_fetchcompleteqctask", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L114"}, {"caller_nid": "api_qc_task_fetchupdateqctaskattachments", "callee": "put", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts", "source_location": "L120"}, {"caller_nid": "api_qc_task_fetchcreateqctask", "callee": "post",Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_task_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaininject_ts", "label": "chainInject.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainInject.ts", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaininject_ts", "target": "vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainInject.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaininject_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chain_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainInject.ts", "source_location": "L2", "weight": 1.0}], "raw_calls": []}
@@ -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_handleview", "label": "handleView()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L567"}, {"id": "production_work_order_index_handlecomplete", "label": "handleComplete()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L571"}, {"id": "production_work_order_index_handleopendispatch", "label": "handleOpenDispatch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L581"}, {"id": "production_work_order_index_handleedittask", "label": "handleEditTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L598"}, {"id": "production_work_order_index_opentaskeditor", "label": "openTaskEditor()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L613"}, {"id": "production_work_order_index_openedittaskinfo", "label": "openEditTaskInfo()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L625"}, {"id": "production_work_order_index_handleedittaskinfosuccess", "label": "handleEditTaskInfoSuccess()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L630"}, {"id": "production_work_order_index_handledeletetask", "label": "handleDeleteTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L636"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L567", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handlecomplete", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L571", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleopendispatch", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L581", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleedittask", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L598", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_opentaskeditor", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L613", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_openedittaskinfo", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L625", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "production_work_order_index_handleedittaskinfosuccess", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L630", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L1"}, {"id": "qc_task_index_canfillresult", "label": "canFillResult()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L519"}, {"id": "qc_task_index_clearmaterialrow", "label": "clearMaterialRow()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L541"}, {"id": "qc_task_index_ismaterialoptiondisabled", "label": "isMaterialOptionDisabled()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L548"}, {"id": "qc_task_index_isduplicatematerial", "label": "isDuplicateMaterial()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L553"}, {"id": "qc_task_index_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L700"}, {"id": "qc_task_index_normalizeresultparams", "label": "normalizeResultParams()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L704"}, {"id": "qc_task_index_toresultnumber", "label": "toResultNumber()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L712"}, {"id": "qc_task_index_setresultnumber", "label": "setResultNumber()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L718"}, {"id": "qc_task_index_hasresultvalue", "label": "hasResultValue()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L722"}, {"id": "qc_task_index_formatmetric", "label": "formatMetric()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L726"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_canfillresult", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L519", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_clearmaterialrow", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L541", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_ismaterialoptiondisabled", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L548", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_isduplicatematerial", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L553", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_handlereceptionattachmentdownload", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L700", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_normalizeresultparams", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue", "source_location": "L704", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_task_index_vue", "target": "qc_task_index_toresultnumber", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_status_tag_registry_ts", "label": "status-tag-registry.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts", "source_location": "L1"}, {"id": "utils_status_tag_registry_createlabelgetter", "label": "createLabelGetter()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts", "source_location": "L29"}, {"id": "utils_status_tag_registry_createtagtypegetter", "label": "createTagTypeGetter()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts", "source_location": "L33"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_status_tag_registry_ts", "target": "utils_status_tag_registry_createlabelgetter", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts", "source_location": "L29", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_status_tag_registry_ts", "target": "utils_status_tag_registry_createtagtypegetter", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/status-tag-registry.ts", "source_location": "L33", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "label": "chainToFlow.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L1"}, {"id": "source_chain_chaintoflow_nextancestorlink", "label": "nextAncestorLink()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L16"}, {"id": "source_chain_chaintoflow_makeflownode", "label": "makeFlowNode()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L24"}, {"id": "source_chain_chaintoflow_makeflowedge", "label": "makeFlowEdge()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L33"}, {"id": "source_chain_chaintoflow_chainapitoelements", "label": "chainApiToElements()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L52"}, {"id": "source_chain_chaintoflow_layoutchainelements", "label": "layoutChainElements()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L104"}, {"id": "source_chain_chaintoflow_haschaingraphcontent", "label": "hasChainGraphContent()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L143"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "dagre", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "core", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chain_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "source_chain_chaintoflow_nextancestorlink", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L16", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "source_chain_chaintoflow_makeflownode", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L24", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "source_chain_chaintoflow_makeflowedge", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L33", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "source_chain_chaintoflow_chainapitoelements", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L52", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chaintoflow_ts", "target": "source_chain_chaintoflow_layoutchainelements", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts", "source_location": "L104", "weight": 1.0}, {"source": "users_wuzilong_work_0Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chainflowtheme_ts", "label": "chainFlowTheme.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainFlowTheme.ts", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chainflowtheme_ts", "target": "core", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainFlowTheme.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chainlinkedge_vue", "label": "ChainLinkEdge.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chainlinkedge_vue", "target": "core", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue", "source_location": "L21", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chainlinkedge_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_source_chain_chain_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue", "source_location": "L27", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_production_work_order_ts", "label": "temp-production-work-order.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L1"}, {"id": "api_temp_production_work_order_fetchtempproductionworkorderlist", "label": "fetchTempProductionWorkOrderList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L24"}, {"id": "api_temp_production_work_order_fetchtempproductionworkorderdetail", "label": "fetchTempProductionWorkOrderDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L39"}, {"id": "api_temp_production_work_order_fetchcreatetempproductionworkorder", "label": "fetchCreateTempProductionWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L45"}, {"id": "api_temp_production_work_order_fetchconfiguretempproductionworkorder", "label": "fetchConfigureTempProductionWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L52"}, {"id": "api_temp_production_work_order_fetchdeletetempproductionworkorder", "label": "fetchDeleteTempProductionWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L68"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_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/temp-production-work-order.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_production_work_order_ts", "target": "api_temp_production_work_order_fetchtempproductionworkorderlist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L24", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_production_work_order_ts", "target": "api_temp_production_work_order_fetchtempproductionworkorderdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L39", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_production_work_order_ts", "target": "api_temp_production_work_order_fetchcreatetempproductionworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L45", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_production_work_order_ts", "target": "api_temp_production_work_order_fetchconfiguretempproductionworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L52", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_temp_production_work_order_ts", "target": "api_temp_production_work_order_fetchdeletetempproductionworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L68", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_temp_production_work_order_fetchtempproductionworkorderlist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L30"}, {"caller_nid": "api_temp_production_work_order_fetchtempproductionworkorderdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L40"}, {"caller_nid": "api_temp_production_work_order_fetchcreatetempproductionworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L46"}, {"caller_nid": "api_temp_production_work_order_fetchconfiguretempproductionworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L62"}, {"caller_nid": "api_temp_production_work_order_fetchdeletetempproductionworkorder", "callee": "del", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/temp-production-work-order.ts", "source_location": "L69"}]Line 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_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": "L392"}, {"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": "L397"}, {"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": "L401"}, {"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": "L408"}, {"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": "L413"}, {"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": "L429"}, {"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": "L435"}, {"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": "L443"}, {"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": "L502"}, {"id": "qc_work_order_index_formatdetectmethods", "label": "formatDetectMethods()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L520"}, {"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": "L527"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_handlereceptionattachmentpreview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L392", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_handlereceptionattachmentdownload", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L397", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_taskattachmentfullurl", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L401", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_handletaskattachmentpreview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L408", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "qc_work_order_index_handletaskattachmentdownload", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue",Line truncated
+2 -2
View File
@@ -381,7 +381,7 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [
path: 'qc-work-order',
component: '/quality-inspection/qc-work-order/index',
meta: {
title: '入库质检工单',
title: '质检工单',
keepAlive: true,
menuCode: 'biz:qcWorkOrder:list'
}
@@ -553,7 +553,7 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [
name: 'ProductionWorkOrder',
path: 'production-work-order',
component: '/production/production-work-order/index',
meta: { title: '入库生产工单', keepAlive: true, menuCode: 'production:work-order:list' }
meta: { title: '生产工单', keepAlive: true, menuCode: 'production:work-order:list' }
},
{
id: '90000000000000000000000000000100',
+2 -1
View File
@@ -141,7 +141,8 @@ export const sourceTypeTagType = createTagTypeGetter(sourceType)
export const qcBizType: StatusRegistry = {
inbound: { label: '入库质检', tagType: 'success' },
outbound: { label: '出库质检', tagType: 'primary' }
outbound: { label: '出库质检', tagType: 'primary' },
temporary: { label: '临时质检', tagType: 'warning' }
}
export const qcBizTypeLabel = createLabelGetter(qcBizType)
export const qcBizTypeTagType = createTagTypeGetter(qcBizType)
@@ -0,0 +1,300 @@
<!-- 细胞产品来源链路图(Vue Flow) -->
<template>
<div class="source-chain-graph" v-loading="loading">
<div v-if="chainData" class="chain-container">
<div class="chain-section-title">来源链路</div>
<p v-if="showFlow" class="chain-hint">双击连线查看生产任务;双击卡片查看产品或客户信息</p>
<div v-if="!showFlow" class="chain-empty">该产品暂无来源及衍生链路信息</div>
<div v-else class="chain-flow-viewport">
<VueFlow
v-model:nodes="nodes"
v-model:edges="edges"
:nodes-draggable="false"
:nodes-connectable="false"
:elements-selectable="false"
:pan-on-drag="true"
:zoom-on-scroll="true"
:zoom-on-pinch="true"
:min-zoom="0.25"
:max-zoom="1.5"
:zoom-on-double-click="false"
:default-edge-options="defaultEdgeOptions"
@node-double-click="onNodeDoubleClick"
@edge-double-click="onEdgeDoubleClick"
>
<template #node-chainNode="nodeProps">
<ChainNodeCard v-bind="nodeProps" />
</template>
<template #edge-chainLink="edgeProps">
<ChainLinkEdge v-bind="edgeProps" />
</template>
</VueFlow>
</div>
</div>
<TaskExecutionDialog
v-model:visible="executionDialogVisible"
:task-id="executionTaskId"
:task-name="executionTaskName"
:readonly="true"
append-to-body
/>
<SampleCustomerViewDialog v-model:visible="sampleViewVisible" :sample="sampleViewNode" />
</div>
</template>
<script setup lang="ts">
import { ref, watch, nextTick, computed, provide } from 'vue'
import { ElMessage } from 'element-plus'
import { VueFlow, useVueFlow, type Edge, type Node, type NodeMouseEvent, type EdgeMouseEvent } from '@vue-flow/core'
import '@vue-flow/core/dist/style.css'
import '@vue-flow/core/dist/theme-default.css'
import { fetchCellProductChain } from '@/api/cell-product'
import { fetchCollectionProtocolDetail } from '@/api/collection-protocol'
import TaskExecutionDialog from '../production-work-order/TaskExecutionDialog.vue'
import ChainNodeCard from './source-chain/ChainNodeCard.vue'
import ChainLinkEdge from './source-chain/ChainLinkEdge.vue'
import SampleCustomerViewDialog from './source-chain/SampleCustomerViewDialog.vue'
import type { ChainApiData, ChainEdgeData, ChainNode, ChainNodeData } from './source-chain/chain-types'
import {
chainApiToElements,
hasChainGraphContent,
layoutChainElements,
} from './source-chain/chainToFlow'
import { chainDefaultEdgeOptions } from './source-chain/chainFlowTheme'
import { CHAIN_OPEN_TASK_KEY } from './source-chain/chainInject'
const props = defineProps<{
productId: string
visible: boolean
}>()
const emit = defineEmits<{
(e: 'openProduct', productId: string): void
}>()
const loading = ref(false)
const chainData = ref<ChainApiData | null>(null)
const nodes = ref<Node<ChainNodeData>[]>([])
const edges = ref<Edge<ChainEdgeData>[]>([])
const executionDialogVisible = ref(false)
const executionTaskId = ref('')
const executionTaskName = ref('')
const sampleViewVisible = ref(false)
const sampleViewNode = ref<ChainNode | null>(null)
const showFlow = computed(
() => chainData.value && hasChainGraphContent(chainData.value)
)
const defaultEdgeOptions = chainDefaultEdgeOptions
const { fitView, onNodesInitialized } = useVueFlow()
function scheduleFitView() {
nextTick(() => {
requestAnimationFrame(() => {
fitView({ padding: 0.15, duration: 280 })
})
})
}
onNodesInitialized(() => {
scheduleFitView()
})
async function applyChainData(data: ChainApiData) {
const { nodes: rawNodes, edges: rawEdges } = chainApiToElements(data)
nodes.value = layoutChainElements(rawNodes, rawEdges)
edges.value = rawEdges
scheduleFitView()
}
function onNodeDoubleClick({ node }: NodeMouseEvent) {
const chainNode = (node.data as ChainNodeData | undefined)?.node
if (!chainNode) return
if (chainNode.nodeType === 'cell_product') {
emit('openProduct', chainNode.id)
return
}
if (chainNode.nodeType === 'sample') {
openSampleCustomer(chainNode)
}
}
function openTaskById(taskId?: string, taskName?: string) {
const id = taskId?.trim()
if (!id) {
ElMessage.warning('该连线暂无关联生产任务')
return
}
executionTaskId.value = id
executionTaskName.value = taskName?.trim() || ''
executionDialogVisible.value = true
}
provide(CHAIN_OPEN_TASK_KEY, openTaskById)
function onEdgeDoubleClick(evt: EdgeMouseEvent) {
const data = evt.edge.data as ChainEdgeData | undefined
openTaskById(data?.taskId, data?.taskName)
}
async function openSampleCustomer(chainNode: ChainNode) {
let node = chainNode
const c = chainNode.customer
const hasCustomer =
c &&
(c.motherName || c.motherPhone || c.motherIdCard || c.newbornName)
if (!hasCustomer && chainNode.collectionProtocolId) {
try {
const detail = await fetchCollectionProtocolDetail(chainNode.collectionProtocolId)
const pc = detail?.partyCustomer as Record<string, string> | null
if (pc) {
node = {
...chainNode,
protocolNo:
chainNode.protocolNo ||
String((detail?.protocol as Record<string, string>)?.protocolNo || ''),
customer: {
motherName: pc.motherName || '',
motherPhone: pc.motherPhone || '',
motherIdCard: pc.motherIdCard || '',
newbornName: pc.newbornName || '',
},
}
}
} catch {
/* 全局错误处理 */
}
}
const final = node.customer
if (!final?.motherName && !final?.motherPhone && !final?.motherIdCard && !final?.newbornName) {
ElMessage.warning('暂无客户信息')
return
}
sampleViewNode.value = node
sampleViewVisible.value = true
}
async function loadChain() {
if (!props.productId || !props.visible) return
loading.value = true
try {
const res = await fetchCellProductChain(props.productId)
chainData.value = (res as ChainApiData) || null
nodes.value = []
edges.value = []
if (chainData.value && hasChainGraphContent(chainData.value)) {
await applyChainData(chainData.value)
}
} catch {
chainData.value = null
nodes.value = []
edges.value = []
} finally {
loading.value = false
}
}
watch(
() => [props.productId, props.visible],
([id, vis]) => {
if (id && vis) loadChain()
},
{ immediate: true }
)
</script>
<script lang="ts">
export default {
name: 'SourceChainGraph',
}
</script>
<style lang="scss">
.source-chain-graph {
min-height: 80px;
}
.chain-section-title {
font-size: 13px;
font-weight: 600;
color: #1e293b;
padding-left: 10px;
border-left: 3px solid #10b981;
margin-bottom: 8px;
line-height: 1;
}
.chain-hint {
margin: 0 0 10px 12px;
font-size: 11px;
color: #94a3b8;
line-height: 1.4;
}
.chain-container {
width: 100%;
}
.chain-empty {
text-align: center;
color: #94a3b8;
font-size: 13px;
padding: 32px 0;
}
.chain-flow-viewport {
width: 100%;
height: 320px;
border: 1px solid #e2e8f0;
border-radius: 8px;
background: #f8fafc;
overflow: hidden;
.vue-flow {
width: 100%;
height: 100%;
}
.vue-flow__pane {
cursor: grab;
}
.vue-flow__pane.dragging {
cursor: grabbing;
}
.vue-flow__edge-path {
stroke: #10b981;
stroke-width: 2;
}
.vue-flow__edge.selected .vue-flow__edge-path {
stroke: #059669;
}
.vue-flow__edges marker path {
fill: #10b981 !important;
stroke: #10b981 !important;
}
.vue-flow__edge .chain-flow-edge {
stroke: #10b981;
}
.vue-flow__edge-interaction {
cursor: pointer;
}
}
</style>
@@ -52,6 +52,17 @@
/>
</el-select>
</el-form-item>
<el-form-item label="来源类型">
<el-select
v-model="searchForm.sourceType"
placeholder="全部"
clearable
style="width: 100%"
>
<el-option label="生产入库" value="production" />
<el-option label="手动录入" value="manual" />
</el-select>
</el-form-item>
</div>
<div class="filter-actions-row">
<span class="filter-hint">按产品编码、产品名称模糊查询,支持按状态和代次筛选。</span>
@@ -204,6 +215,42 @@
</el-tag>
</template>
</el-table-column>
<el-table-column label="来源类型" min-width="110" align="center">
<template #default="{ row }">
<el-tag
v-if="row.sourceTypeLabel"
:type="row.sourceType === 'production' ? 'success' : 'info'"
size="small"
>
{{ row.sourceTypeLabel }}
</el-tag>
<span v-else>—</span>
</template>
</el-table-column>
<el-table-column
label="来源协议号"
min-width="160"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.sourceProtocolNo || '—' }}</template>
</el-table-column>
<el-table-column
label="来源工单号"
min-width="140"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.sourceWorkOrderNo || '—' }}</template>
</el-table-column>
<el-table-column
label="来源任务号"
min-width="140"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.sourceTaskNo || '—' }}</template>
</el-table-column>
<el-table-column
prop="expiryDate"
label="有效期"
@@ -583,6 +630,8 @@
:icon="Box"
:meta="viewRow?.productCode || ''"
:sections="viewSections"
width="1200px"
:grid-columns="4"
>
<template #images>
<div class="attachment-upload attachment-upload--readonly">
@@ -612,6 +661,13 @@
<span v-else class="upload-hint">暂无图片</span>
</div>
</template>
<template #sourceChain>
<SourceChainGraph
:product-id="viewRow?.id"
:visible="viewVisible"
@open-product="handleChainOpenProduct"
/>
</template>
</SystemViewDialog>
</section>
</div>
@@ -634,6 +690,7 @@
} from '@element-plus/icons-vue'
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
import SourceChainGraph from './SourceChainGraph.vue'
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
import {
fetchCellProductList,
@@ -691,7 +748,7 @@
}
/* ---- 搜索 & 分页 ---- */
const searchForm = reactive({ keyword: '', productStatus: '', passage: '' })
const searchForm = reactive({ keyword: '', productStatus: '', passage: '', sourceType: '' })
const tableData = ref<any[]>([])
const selectedRows = ref<any[]>([])
const loading = ref(false)
@@ -934,6 +991,12 @@
slot: 'images',
fields: [],
},
{
title: '来源链路',
titleStyle: 'field' as const,
slot: 'sourceChain',
fields: [],
},
]
})
@@ -947,6 +1010,7 @@
keyword: searchForm.keyword || undefined,
productStatus: searchForm.productStatus || undefined,
passage: searchForm.passage || undefined,
sourceType: searchForm.sourceType || undefined,
})
tableData.value = res.list || []
pagination.total = res.total ?? 0
@@ -987,6 +1051,7 @@
searchForm.keyword = ''
searchForm.productStatus = ''
searchForm.passage = ''
searchForm.sourceType = ''
pagination.page = 1
loadData()
}
@@ -1220,6 +1285,11 @@
}
}
async function handleChainOpenProduct(productId: string) {
if (!productId) return
await handleView({ id: productId })
}
/* ---- 查看 ---- */
async function handleView(row: any) {
try {
@@ -0,0 +1,163 @@
<!-- eslint-disable vue/no-multiple-template-root -->
<!-- 来源链路 · 单链折线 / 分叉贝塞尔曲线(组件内处理双击,避免被画布缩放拦截) -->
<template>
<g class="chain-link-edge-group" @dblclick.stop="handleDblClick">
<BaseEdge
:id="id"
:path="edgePath"
:marker-end="markerEnd"
:marker-start="markerStart"
:interaction-width="edgeInteractionWidth"
class="chain-flow-edge"
/>
<EdgeLabelRenderer v-if="hasLabel">
<div
class="chain-edge-label-compact nodrag nopan"
:style="labelStyle"
@dblclick.stop="handleDblClick"
>
<div v-if="workOrderLabel" class="label-line">
<span class="label-k">工单</span>
<span class="label-v wo" :title="workOrderLabel">{{ workOrderLabel }}</span>
</div>
<div v-if="taskLabel" class="label-line">
<span class="label-k">任务</span>
<span class="label-v task" :title="taskLabel">{{ taskLabel }}</span>
</div>
</div>
</EdgeLabelRenderer>
</g>
</template>
<script setup lang="ts">
import { computed, inject } from 'vue'
import { ElMessage } from 'element-plus'
import {
BaseEdge,
EdgeLabelRenderer,
getBezierPath,
getSmoothStepPath,
type EdgeProps,
} from '@vue-flow/core'
import type { ChainEdgeData } from './chain-types'
import { CHAIN_OPEN_TASK_KEY } from './chainInject'
const props = defineProps<EdgeProps<ChainEdgeData>>()
const openTask = inject(CHAIN_OPEN_TASK_KEY)
const edgeInteractionWidth = computed(() => props.interactionWidth ?? 28)
const pathInput = computed(() => ({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
}))
const bezierCurvature = computed(() => {
const dy = props.targetY - props.sourceY
if (Math.abs(dy) < 4) {
return 0.28
}
const sign = dy > 0 ? 1 : -1
return sign * Math.min(0.55, 0.32 + Math.abs(dy) / 280)
})
const path = computed(() => {
if (props.data?.curved) {
return getBezierPath({
...pathInput.value,
curvature: bezierCurvature.value,
})
}
return getSmoothStepPath({
...pathInput.value,
borderRadius: 8,
})
})
const edgePath = computed(() => path.value[0])
const labelX = computed(() => path.value[1])
const labelY = computed(() => path.value[2])
const workOrderLabel = computed(() => props.data?.workOrderNo?.trim() || '')
const taskLabel = computed(() => props.data?.taskNo?.trim() || '')
const hasLabel = computed(() => Boolean(workOrderLabel.value || taskLabel.value))
const labelStyle = computed(() => ({
transform: `translate(-50%, -50%) translate(${labelX.value}px, ${labelY.value}px)`,
}))
function handleDblClick() {
const taskId = props.data?.taskId?.trim()
if (!taskId) {
ElMessage.warning('该连线暂无关联生产任务')
return
}
openTask?.(taskId, props.data?.taskName?.trim())
}
</script>
<style lang="scss">
.chain-link-edge-group {
cursor: pointer;
}
.chain-flow-edge {
stroke: #10b981;
stroke-width: 2;
fill: none;
}
.chain-edge-label-compact {
position: absolute;
pointer-events: all;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 1px;
padding: 3px 6px;
background: #fff;
border: 1px solid #d1fae5;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
max-width: 118px;
z-index: 2;
}
.label-line {
display: flex;
align-items: baseline;
gap: 3px;
line-height: 1.2;
min-width: 0;
}
.label-k {
flex-shrink: 0;
font-size: 8px;
color: #94a3b8;
}
.label-v {
font-size: 8px;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
&.wo {
color: #059669;
}
&.task {
color: #64748b;
font-weight: 500;
}
}
</style>
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more