This commit is contained in:
zlwu committed 2026-08-21 09:35:11 +08:00
1 parent b9ff9c9d67
commit 44749ae716
87 files changed
+35089 -21918

No files matched your search

+1 -1
View File
@@ -1 +1 @@
90187
76884
+32 -1
View File
@@ -27,7 +27,7 @@ from app.crud import user as user_svc
from app.crud import role as role_svc
from app.crud import user as user_svc
from app.crud import oss as oss_svc
from app.models.models import SysDept, SysDictData, SysRole, SysUser
from app.models.models import SysDept, SysDictData, SysRole, SysUser, SysUserRole
from app.schemas.menu_favorite import (
MenuFavoriteCreateBody,
MenuFavoriteDeleteBody,
@@ -316,6 +316,20 @@ def list_users(
result_list = [
user_svc._row_to_dict(r, dept_name=dept_map.get(r.dept_id or "", "")) for r in items
]
user_ids = [d["id"] for d in result_list if d.get("id")]
role_map: dict[str, list[str]] = {uid: [] for uid in user_ids}
if user_ids:
for ur in (
db.query(SysUserRole)
.filter(
SysUserRole.user_id.in_(user_ids),
SysUserRole.del_flag == 0,
)
.all()
):
role_map.setdefault(ur.user_id, []).append(ur.role_id)
for d in result_list:
d["roleIds"] = role_map.get(d["id"], [])
tp = user_svc.total_pages(total, pageSize)
return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
@@ -1226,6 +1240,23 @@ def create_role_management(
return error_response(400, str(e))
@router.get("/role-management/{role_id}")
def get_role_management(
role_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
obj = role_svc.get_role(db, role_id)
if not obj or obj.tenant_no != tenant_no:
return error_response(404, "角色不存在")
try:
assert_row_in_data_scope(db, obj, SysRole, user, tenant_no)
except HTTPException:
return error_response(403, "无权查看该角色")
return success_response(role_svc._row_to_dict(obj))
@router.put("/role-management/{role_id}")
def update_role_management(
role_id: str,
+2 -1
View File
@@ -188,6 +188,7 @@ class VehicleBase(BaseModel):
class VehicleCreate(VehicleBase):
trailer_no: str = Field(..., min_length=1, max_length=20, alias="trailerNo", description="挂车号")
personnel_list: list[VehiclePersonnelRow] = Field(default_factory=list, alias="personnelList")
cert_list: list[CertRow] = Field(default_factory=list, alias="certList")
model_config = {"populate_by_name": True}
@@ -198,7 +199,7 @@ class VehicleUpdate(BaseModel):
carrier_id: Optional[str] = Field(default=None, alias="carrierId")
carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName")
license_plate: Optional[str] = Field(default=None, max_length=20, alias="licensePlate")
trailer_no: Optional[str] = Field(default=None, max_length=20, alias="trailerNo")
trailer_no: Optional[str] = Field(default=None, min_length=1, max_length=20, alias="trailerNo")
load_capacity: Optional[Decimal] = Field(default=None, alias="loadCapacity")
vehicle_status: Optional[str] = Field(default=None, max_length=20, alias="vehicleStatus")
photo_urls: Optional[str] = Field(
+1 -1
View File
@@ -23,7 +23,7 @@ class PastePersonnelIn(BaseModel):
class PasteVehicleIn(BaseModel):
license_plate: str = Field(alias="licensePlate")
trailer_no: Optional[str] = Field(default=None, alias="trailerNo")
trailer_no: str = Field(..., min_length=1, max_length=20, alias="trailerNo")
load_capacity: Optional[float] = Field(default=None, alias="loadCapacity")
carrier_id: Optional[str] = Field(default=None, alias="carrierId")
carrier_name: Optional[str] = Field(default=None, alias="carrierName")
@@ -31,6 +31,23 @@ PERSONNEL_BOTH_REQUIRED = (
VEHICLE_PART_HEAD = "head"
VEHICLE_PART_TRAILER = "trailer"
HUOLALA_TRAILER_NO = "货拉拉"
def is_huolala_trailer_no(trailer_no: str | None) -> bool:
return (trailer_no or "").strip() == HUOLALA_TRAILER_NO
def should_show_trailer_cert_slots(trailer_no: str | None) -> bool:
text = (trailer_no or "").strip()
return bool(text) and not is_huolala_trailer_no(text)
def should_require_trailer_certs(vehicle_type: str | None, trailer_no: str | None) -> bool:
return (vehicle_type or "").strip() == VEHICLE_TYPE_SELF and should_show_trailer_cert_slots(
trailer_no
)
def match_required_cert_name(actual_name: str | None, required_name: str) -> bool:
name = (actual_name or "").strip()
@@ -178,7 +195,7 @@ def validate_self_vehicle_required_certs(
cert_rows, SELF_HEAD_REQUIRED, part_label="主车"
)
)
if (trailer_no or "").strip():
if should_require_trailer_certs(VEHICLE_TYPE_SELF, trailer_no):
errors.extend(
collect_required_cert_errors(
cert_rows, SELF_TRAILER_REQUIRED, part_label="挂车"
@@ -196,23 +213,9 @@ def validate_carrier_vehicle_required_certs(
*,
trailer_no: str | None = None,
) -> None:
errors: list[str] = []
errors.extend(
collect_required_cert_errors(
cert_rows, CARRIER_HEAD_REQUIRED, part_label="主车"
)
)
if (trailer_no or "").strip():
errors.extend(
collect_required_cert_errors(
cert_rows, CARRIER_TRAILER_REQUIRED, part_label="挂车"
)
)
if errors:
raise HTTPException(
status_code=400,
detail=f"必备证书未齐全:{';'.join(errors)}",
)
"""三方车辆证件不强制;保留函数供兼容调用,恒为通过。"""
_ = (cert_rows, trailer_no)
return
def validate_vehicle_personnel_affiliation(
@@ -193,7 +193,10 @@ def _resolve_vehicle(
target = _get_vehicle(db, tenant_no, conflict.existing_id)
if conflict.action == "reuse":
return target.id
target.trailer_no = (draft.trailer_no or "").strip() or None
trailer_no = (draft.trailer_no or "").strip()
if not trailer_no:
raise HTTPException(status_code=400, detail="挂车号不能为空")
target.trailer_no = trailer_no
target.load_capacity = draft.load_capacity
target.carrier_id = carrier_id
target.carrier_name = carrier_name
@@ -201,6 +204,10 @@ def _resolve_vehicle(
target.update_by = user_id
return target.id
trailer_no = (draft.trailer_no or "").strip()
if not trailer_no:
raise HTTPException(status_code=400, detail="挂车号不能为空")
vehicle = PlVehicle(
id=generate_uuid(),
tenant_no=tenant_no,
@@ -208,7 +215,7 @@ def _resolve_vehicle(
carrier_id=carrier_id,
carrier_name=carrier_name,
license_plate=plate,
trailer_no=(draft.trailer_no or "").strip() or None,
trailer_no=trailer_no,
load_capacity=draft.load_capacity,
vehicle_status="available",
create_by=user_id,
+16 -12
View File
@@ -89,32 +89,40 @@
"87": "Community 87",
"88": "CrmProduct",
"89": "pl_ocr_service.py",
"90": "SysApiRequestLog",
"91": "pl_vehicle_recharge_service.py",
"90": "WechatMessageService",
"91": "opportunity_product.py",
"92": "Community 92",
"93": "list_products",
"93": "api_request_log_middleware.py",
"94": "assert_contract_editable_for_update",
"95": "Community 95",
"96": "__init__.py",
"95": "test_pl_public_link_archived.py",
"96": "opportunity_follow.py",
"97": "_ensure_subpack_slip_no",
"98": "LocalStorage",
"99": "QiniuStorage",
"100": "price_letter_detail",
"101": "list_product_options",
"102": "dept.py",
"101": "tenant.py",
"102": "crm_supplier_hazard.py",
"103": "pl_internal_customer.py",
"104": "apply_crm_create_by_scope",
"105": "pool_customer_assign",
"106": "site.py",
"107": "apply_pl_scope",
"108": "statistics.py",
"109": "Community 109",
"110": "plan_has_incomplete_hazard_cert_exists",
"111": "Community 111",
"112": ".to_tenant_user_dict",
"113": "quote_share_log.py",
"114": "db_models.py",
"115": "site.py",
"116": "Community 116",
"117": "get_web_auth_menu_codes",
"118": "Community 118",
"119": "Community 119",
"120": "sqlalchemy_exception_handler",
"121": "Session",
"122": "Community 122",
"125": "Community 125",
"123": "list_product_options",
"126": "customer_pool.py",
"129": "BaseStorage",
"134": "Community 134",
@@ -129,16 +137,12 @@
"151": "Community 151",
"154": "response.py",
"159": "customer_update",
"160": "register_local_callback",
"162": ".to_tenant_user_dict",
"164": "get_client_ip",
"166": "code:block1 (/iflytek/shanxia2.0/backend)",
"170": "test_quote_approver_setting.py",
"171": "minio_url.py",
"173": "resolve_create_time_bounds",
"174": "register_local_callback",
"179": "test_pl_vehicle_personnel_photo_schema.py",
"182": "statistics.py",
"188": "get_client_ip",
"206": "crm_quote_pdf.py",
"207": "shanxia2.0 后端骨架(FastAPI)",
@@ -89,35 +89,39 @@
"87": "Community 87",
"88": "CrmProduct",
"89": "pl_ocr_service.py",
"90": "SysApiRequestLog",
"91": "pl_vehicle_recharge_service.py",
"90": "WechatMessageService",
"91": "opportunity_product.py",
"92": "Community 92",
"93": "list_products",
"93": "api_request_log_middleware.py",
"94": "assert_contract_editable_for_update",
"95": "Community 95",
"96": "__init__.py",
"95": "test_pl_public_link_archived.py",
"96": "opportunity_follow.py",
"97": "_ensure_subpack_slip_no",
"98": "LocalStorage",
"99": "QiniuStorage",
"100": "price_letter_detail",
"101": "list_product_options",
"102": "dept.py",
"101": "tenant.py",
"102": "crm_supplier_hazard.py",
"103": "pl_internal_customer.py",
"104": "apply_crm_create_by_scope",
"105": "list_customers",
"106": "__init__.py",
"105": "pool_customer_assign",
"106": "site.py",
"107": "apply_pl_scope",
"108": "statistics.py",
"109": "Community 109",
"110": "Base",
"110": "plan_has_incomplete_hazard_cert_exists",
"111": "Community 111",
"112": ".to_tenant_user_dict",
"113": "quote_share_log.py",
"114": "db_models.py",
"115": "site.py",
"116": "Community 116",
"117": "get_web_auth_menu_codes",
"118": "Community 118",
"119": "Community 119",
"120": "sqlalchemy_exception_handler",
"121": "Session",
"122": "Community 122",
"125": "Community 125",
"126": "customer_pool.py",
"129": "BaseStorage",
"134": "Community 134",
@@ -132,16 +136,12 @@
"151": "Community 151",
"154": "response.py",
"159": "customer_update",
"160": "register_local_callback",
"162": ".to_tenant_user_dict",
"164": "get_client_ip",
"166": "code:block1 (/iflytek/shanxia2.0/backend)",
"170": "test_quote_approver_setting.py",
"171": "minio_url.py",
"173": "resolve_create_time_bounds",
"174": "register_local_callback",
"179": "test_pl_vehicle_personnel_photo_schema.py",
"182": "statistics.py",
"188": "get_client_ip",
"206": "crm_quote_pdf.py",
"207": "shanxia2.0 后端骨架(FastAPI)",
+260 -260
View File
@@ -1,16 +1,16 @@
# Graph Report - backend (2026-08-20)
## Corpus Check
- 334 files · ~218,755 words
- 332 files · ~220,574 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 4524 nodes · 16752 edges · 172 communities (156 shown, 16 thin omitted)
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 748 edges (avg confidence: 0.62)
- 4575 nodes · 16969 edges · 172 communities (156 shown, 16 thin omitted)
- Extraction: 95% EXTRACTED · 5% INFERRED · 0% AMBIGUOUS · INFERRED: 776 edges (avg confidence: 0.62)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `a79e8a3e`
- Built from commit: `80545263`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@@ -105,35 +105,39 @@
- [[_COMMUNITY_Community 87|Community 87]]
- [[_COMMUNITY_CrmProduct|CrmProduct]]
- [[_COMMUNITY_pl_ocr_service.py|pl_ocr_service.py]]
- [[_COMMUNITY_SysApiRequestLog|SysApiRequestLog]]
- [[_COMMUNITY_pl_vehicle_recharge_service.py|pl_vehicle_recharge_service.py]]
- [[_COMMUNITY_WechatMessageService|WechatMessageService]]
- [[_COMMUNITY_opportunity_product.py|opportunity_product.py]]
- [[_COMMUNITY_Community 92|Community 92]]
- [[_COMMUNITY_list_products|list_products]]
- [[_COMMUNITY_api_request_log_middleware.py|api_request_log_middleware.py]]
- [[_COMMUNITY_assert_contract_editable_for_update|assert_contract_editable_for_update]]
- [[_COMMUNITY_Community 95|Community 95]]
- [[_COMMUNITY___init__.py|__init__.py]]
- [[_COMMUNITY_test_pl_public_link_archived.py|test_pl_public_link_archived.py]]
- [[_COMMUNITY_opportunity_follow.py|opportunity_follow.py]]
- [[_COMMUNITY__ensure_subpack_slip_no|_ensure_subpack_slip_no]]
- [[_COMMUNITY_LocalStorage|LocalStorage]]
- [[_COMMUNITY_QiniuStorage|QiniuStorage]]
- [[_COMMUNITY_price_letter_detail|price_letter_detail]]
- [[_COMMUNITY_list_product_options|list_product_options]]
- [[_COMMUNITY_dept.py|dept.py]]
- [[_COMMUNITY_tenant.py|tenant.py]]
- [[_COMMUNITY_crm_supplier_hazard.py|crm_supplier_hazard.py]]
- [[_COMMUNITY_pl_internal_customer.py|pl_internal_customer.py]]
- [[_COMMUNITY_apply_crm_create_by_scope|apply_crm_create_by_scope]]
- [[_COMMUNITY_list_customers|list_customers]]
- [[_COMMUNITY___init__.py|__init__.py]]
- [[_COMMUNITY_pool_customer_assign|pool_customer_assign]]
- [[_COMMUNITY_site.py|site.py]]
- [[_COMMUNITY_apply_pl_scope|apply_pl_scope]]
- [[_COMMUNITY_statistics.py|statistics.py]]
- [[_COMMUNITY_Community 109|Community 109]]
- [[_COMMUNITY_Base|Base]]
- [[_COMMUNITY_plan_has_incomplete_hazard_cert_exists|plan_has_incomplete_hazard_cert_exists]]
- [[_COMMUNITY_Community 111|Community 111]]
- [[_COMMUNITY_.to_tenant_user_dict|.to_tenant_user_dict]]
- [[_COMMUNITY_quote_share_log.py|quote_share_log.py]]
- [[_COMMUNITY_db_models.py|db_models.py]]
- [[_COMMUNITY_site.py|site.py]]
- [[_COMMUNITY_Community 116|Community 116]]
- [[_COMMUNITY_get_web_auth_menu_codes|get_web_auth_menu_codes]]
- [[_COMMUNITY_Community 118|Community 118]]
- [[_COMMUNITY_Community 119|Community 119]]
- [[_COMMUNITY_sqlalchemy_exception_handler|sqlalchemy_exception_handler]]
- [[_COMMUNITY_Session|Session]]
- [[_COMMUNITY_Community 122|Community 122]]
- [[_COMMUNITY_Community 125|Community 125]]
- [[_COMMUNITY_customer_pool.py|customer_pool.py]]
- [[_COMMUNITY_BaseStorage|BaseStorage]]
- [[_COMMUNITY_Community 134|Community 134]]
@@ -148,16 +152,12 @@
- [[_COMMUNITY_Community 151|Community 151]]
- [[_COMMUNITY_response.py|response.py]]
- [[_COMMUNITY_customer_update|customer_update]]
- [[_COMMUNITY_register_local_callback|register_local_callback]]
- [[_COMMUNITY_.to_tenant_user_dict|.to_tenant_user_dict]]
- [[_COMMUNITY_get_client_ip|get_client_ip]]
- [[_COMMUNITY_codeblock1 (iflytekshanxia2.0backend)|code:block1 (/iflytek/shanxia2.0/backend)]]
- [[_COMMUNITY_test_quote_approver_setting.py|test_quote_approver_setting.py]]
- [[_COMMUNITY_minio_url.py|minio_url.py]]
- [[_COMMUNITY_resolve_create_time_bounds|resolve_create_time_bounds]]
- [[_COMMUNITY_register_local_callback|register_local_callback]]
- [[_COMMUNITY_test_pl_vehicle_personnel_photo_schema.py|test_pl_vehicle_personnel_photo_schema.py]]
- [[_COMMUNITY_statistics.py|statistics.py]]
- [[_COMMUNITY_get_client_ip|get_client_ip]]
- [[_COMMUNITY_crm_quote_pdf.py|crm_quote_pdf.py]]
- [[_COMMUNITY_shanxia2.0 后端骨架(FastAPI)|shanxia2.0 后端骨架(FastAPI)]]
@@ -179,18 +179,20 @@
- [[_COMMUNITY_PlanResourceSnapshotMixin|PlanResourceSnapshotMixin]]
## God Nodes (most connected - your core abstractions)
1. `BaseModel` - 452 edges
2. `get_tenant_no()` - 350 edges
3. `success_response()` - 268 edges
4. `DbUserContext` - 254 edges
5. `error_response()` - 247 edges
6. `generate_uuid()` - 194 edges
1. `BaseModel` - 456 edges
2. `get_tenant_no()` - 352 edges
3. `success_response()` - 269 edges
4. `DbUserContext` - 257 edges
5. `error_response()` - 248 edges
6. `generate_uuid()` - 196 edges
7. `BxUserContext` - 188 edges
8. `crm_user_info()` - 137 edges
9. `assert_row_deletable_by_creator()` - 83 edges
10. `ApiResponse` - 80 edges
## Surprising Connections (you probably didn't know these)
- `BackfillRule` --uses--> `Settings` [INFERRED]
scripts/migrate_crm_sys_oss.py → app/core/config.py
- `MigrationStats` --uses--> `Settings` [INFERRED]
scripts/migrate_bx_data.py → app/core/config.py
- `StepStats` --uses--> `Settings` [INFERRED]
@@ -199,8 +201,6 @@
scripts/migrate_crm_clue.py → app/core/config.py
- `StepStats` --uses--> `Settings` [INFERRED]
scripts/migrate_crm_clue.py → app/core/config.py
- `MigrationStats` --uses--> `Settings` [INFERRED]
scripts/migrate_crm_contract.py → app/core/config.py
## Import Cycles
- None detected.
@@ -208,116 +208,112 @@
## Communities (172 total, 16 thin omitted)
### Community 0 - "Community 0"
Cohesion: 0.06
Nodes (85): check_document_expiry(), document_reminder_stats(), get_inventory_ledger(), get_summary(), list_document_reminders(), list_transactions(), list_warnings(), date (+77 more)
Cohesion: 0.11
Nodes (42): check_document_expiry(), document_reminder_stats(), get_inventory_ledger(), get_summary(), list_document_reminders(), list_transactions(), list_warnings(), date (+34 more)
### Community 1 - "Community 1"
Cohesion: 0.08
Nodes (38): CRM 合同 API 路由 — GET/PUT/DELETE/POST 对齐原 Java CRM, 运营端角色守卫:admin / super_admin 可访问。, require_admin_or_super_admin(), ContractApproverSettingSaveBody, ContractApprovalActionBody, ContractApprovalPassBody, ContractApprovalRejectBody, ContractApprovalTransferBody (+30 more)
Nodes (38): CRM 合同 API 路由 — GET/PUT/DELETE/POST 对齐原 Java CRM, 接口菜单权限守卫。super_admin 直接放行。, require_menu_code(), ContractApproverSettingSaveBody, ContractApprovalActionBody, ContractApprovalPassBody, ContractApprovalRejectBody, ContractApprovalTransferBody (+30 more)
### Community 2 - "Community 2"
Cohesion: 0.16
Nodes (29): _apply_inventory_product_update(), _apply_inventory_update(), _base_query(), create_inventory(), create_link_for_plan(), delete_inventory(), _generate_batch_no(), _get_inventory() (+21 more)
### Community 3 - "Community 3"
Cohesion: 0.08
Nodes (57): attachment_preview_url(), create_dept(), create_dict_data(), create_dict_type(), create_menu(), create_menu_favorite(), create_post(), create_role_management() (+49 more)
Cohesion: 0.09
Nodes (79): attachment_download_url(), attachment_preview_url(), create_dept(), create_dict_data(), create_dict_type(), create_menu(), create_menu_favorite(), create_post() (+71 more)
### Community 4 - "Community 4"
Cohesion: 0.09
Nodes (76): _apply_list_filters(), assign_opportunities(), batch_update_expect_date(), batch_update_stage(), change_stage(), create_opportunity(), _decimal_value(), _enrich_user_names() (+68 more)
Cohesion: 0.12
Nodes (61): _apply_list_filters(), assign_opportunities(), batch_update_expect_date(), batch_update_stage(), change_stage(), create_opportunity(), _decimal_value(), _enrich_user_names() (+53 more)
### Community 5 - "Community 5"
Cohesion: 0.05
Nodes (116): archive_plan(), cancel_plan(), cancel_plan_wait_notice(), count_plans_by_contract(), create_from_contract(), create_plan(), create_plan_full(), delete_plan() (+108 more)
Nodes (75): _build_driver_url(), cancel_plan(), cancel_plan_wait_notice(), count_plans_by_contract(), create_from_contract(), create_plan_full(), delete_plan(), _determine_plan_type() (+67 more)
### Community 6 - "Community 6"
Cohesion: 0.09
Nodes (55): customer_create(), customer_update(), follow_by_customer(), follow_detail(), assign_customer(), batch_claim_customers(), check_duplicate_customer(), claim_customer() (+47 more)
Cohesion: 0.11
Nodes (48): assign_customer(), batch_claim_customers(), check_duplicate_customer(), claim_customer(), create_customer(), deal_customer(), _enrich_user_names(), _ensure_customer_in_scope() (+40 more)
### Community 7 - "Community 7"
Cohesion: 0.04
Nodes (161): category_add(), category_delete(), category_tree(), category_update(), clue_assign(), clue_check_convert(), clue_check_duplicate(), clue_convert() (+153 more)
Cohesion: 0.03
Nodes (201): clue_assign(), clue_convert(), clue_create(), clue_delete(), clue_detail(), clue_export(), clue_follow_create(), clue_follow_delete() (+193 more)
### Community 8 - "Community 8"
Cohesion: 0.10
Nodes (68): register_quote_routes(), accept_quote(), assert_quote_editable_for_update(), _build_quote_filters(), calculate_quote_amount(), create_new_version(), create_quote(), _decimal_value() (+60 more)
Cohesion: 0.13
Nodes (50): register_quote_routes(), accept_quote(), assert_quote_editable_for_update(), calculate_quote_amount(), create_new_version(), create_quote(), _decimal_value(), _enrich_contact_fields() (+42 more)
### Community 9 - "Community 9"
Cohesion: 0.07
Nodes (63): download_reimburse_import_template(), build_annotated_result_bytes(), build_applicant_label(), build_expense_path_labels(), build_import_template_bytes(), cell_str(), _ids_visible_in_web_tree(), import_reimburse_workbook() (+55 more)
Cohesion: 0.10
Nodes (49): download_reimburse_import_template(), build_annotated_result_bytes(), build_applicant_label(), build_expense_path_labels(), build_import_template_bytes(), cell_str(), _ids_visible_in_web_tree(), import_reimburse_workbook() (+41 more)
### Community 10 - "Community 10"
Cohesion: 0.13
Nodes (34): approve_settlement_legacy(), cancel_settlement(), create_settlement(), delete_settlement(), disburse_settlement(), _generate_settlement_no(), get_settlement(), get_settlement_approval_flow() (+26 more)
Cohesion: 0.14
Nodes (15): BxAdvanceApply, BxAdvanceApproval, AdvanceService, Any, Session, 同一节点多次审批时,取 approval_time 最新的一条作为当前节点状态。, 返回全部审批动作,按时间正序排列,用于展示完整审批历史。, current_month_bounds() (+7 more)
### Community 11 - "Community 11"
Cohesion: 0.15
Nodes (40): PlVehicleUreaApply, PlVehicleUreaDisburse, 车辆尿素申请 pl_vehicle_urea_apply, 车辆尿素发放登记 pl_vehicle_urea_disburse, Decimal, VehicleUreaApplyCreate, VehicleUreaApplyRead, VehicleUreaApplyUpdate (+32 more)
Cohesion: 0.10
Nodes (55): cancel_apply(), create_apply(), create_disburse(), delete_applies(), delete_disburses(), get_apply(), get_disburse(), list_applies() (+47 more)
### Community 12 - "Community 12"
Cohesion: 0.05
Nodes (58): create_leave(), delete_leave(), get_leave(), _leave_local_callback(), leave_workflow_callback(), list_leaves(), Session, submit_leave() (+50 more)
Cohesion: 0.06
Nodes (64): contact_add(), contact_delete(), contact_update(), customer_delete(), BaseModel, AttachmentUploadResponse, ChangePasswordRequest, Config (+56 more)
### Community 13 - "Community 13"
Cohesion: 0.14
Nodes (17): create_expense_item(), delete_expense_item(), get_expense_item(), get_expense_item_tree(), Session, update_expense_item(), _auto_fill_create_dept_id(), get_db() (+9 more)
Cohesion: 0.20
Nodes (13): create_expense_item(), delete_expense_item(), get_expense_item(), get_expense_item_tree(), Session, update_expense_item(), BxExpenseItem, _coerce_id() (+5 more)
### Community 14 - "Community 14"
Cohesion: 0.14
Nodes (22): create_workflow(), delete_workflow(), get_workflow(), get_workflow_by_biz_type(), list_workflows(), Session, save_workflow_nodes(), update_workflow() (+14 more)
Cohesion: 0.09
Nodes (36): bind_biz(), delete_attachment(), download_file(), list_by_biz(), _parse_id_list(), preview_attachment(), Session, UploadFile (+28 more)
### Community 15 - "Community 15"
Cohesion: 0.11
Nodes (16): BxReimburseApply, BxReimburseApproval, BxReimburseDetail, BxReimburseInvoice, BxReimburseOffset, 报销单勾选的发票附件(同一发票可勾选多个附件,各占一条)。, ReimburseInvoiceBriefOut, ReimburseInvoiceLinkIn (+8 more)
Cohesion: 0.10
Nodes (42): approve_reimburse(), approved_by_me(), _build_details_data(), create_reimburse(), delete_reimburse(), get_approval_history(), get_approvals(), get_generated_title() (+34 more)
### Community 16 - "Community 16"
Cohesion: 0.09
Nodes (60): cancel_apply(), create_apply(), create_record(), delete_applies(), delete_records(), get_apply(), get_record(), list_applies() (+52 more)
Cohesion: 0.10
Nodes (64): cancel_apply(), create_apply(), create_record(), delete_applies(), delete_records(), get_apply(), get_record(), list_applies() (+56 more)
### Community 17 - "Community 17"
Cohesion: 0.04
Nodes (81): SoftDeleteModel, BizAttachment, PlDriver, PlFreightSettlementTransport, PlInboundOrder, PlInboundOrderItem, PlInboundOrderItemBrandQty, PlInventory (+73 more)
Cohesion: 0.18
Nodes (21): allocate_reimburse_title_seq(), format_reimburse_title(), generate_apply_code(), generate_title(), is_auto_reimburse_title(), parse_optional_date(), parse_reimburse_date(), preview_reimburse_title_seq() (+13 more)
### Community 18 - "Community 18"
Cohesion: 0.10
Nodes (38): advance_pending_approval_stats(), advance_status_counts(), approve_advance(), approved_by_me(), create_advance(), delete_advance(), _fill_offset_amounts(), get_advance() (+30 more)
Cohesion: 0.08
Nodes (63): advance_pending_approval_stats(), advance_status_counts(), approve_advance(), approved_by_me(), create_advance(), delete_advance(), _fill_offset_amounts(), get_advance() (+55 more)
### Community 19 - "Community 19"
Cohesion: 0.12
Nodes (35): approve_reimburse(), approved_by_me(), _build_details_data(), create_reimburse(), delete_reimburse(), get_approval_history(), get_approvals(), get_generated_title() (+27 more)
Cohesion: 0.13
Nodes (11): _coerce_id(), OffsetItem, PaymentFinanceStatsOut, 报销单勾选的发票附件(同一发票可勾选多个附件,各占一条)。, ReimburseApplyCreate, ReimburseApprovalOut, ReimburseApproverPendingStatsOut, ReimburseDetailCreate (+3 more)
### Community 20 - "Community 20"
Cohesion: 0.12
Nodes (37): register_contract_routes(), batch_assign_owner(), batch_submit_contract_approval(), create_from_opportunity(), create_from_quote(), download_contract_pdf(), export_contracts(), finish_contract() (+29 more)
Cohesion: 0.20
Nodes (25): _apply_weighing_product_update(), _apply_weighing_update(), _base_query(), create_weighing(), delete_weighing(), _get_weighing(), get_weighing_related(), list_weighing() (+17 more)
### Community 21 - "Community 21"
Cohesion: 0.10
Nodes (51): _alias_row_to_dict(), _assert_brand_deletable(), barcode_exists(), _brand_row_to_dict(), _build_category_tree(), category_code_exists(), _category_row_to_dict(), _collect_category_with_ancestors() (+43 more)
Cohesion: 0.08
Nodes (60): _alias_row_to_dict(), _assert_brand_deletable(), barcode_exists(), _brand_row_to_dict(), _build_category_tree(), category_code_exists(), _category_row_to_dict(), _collect_category_with_ancestors() (+52 more)
### Community 22 - "Community 22"
Cohesion: 0.08
Nodes (28): parse_admin_request_json(), Any, Request, 运营 Web(/web/admin)JSON 请求体解析。 与 ApiCryptoMiddleware 配合:中间件未改写 body 时,在路由内解密 `{, 读取 JSON body;dict 时走 resolve_request_payload(明文 / 密文 / data 包装)。 Returns:, ApiCryptoError, _b64url_decode(), _b64url_encode() (+20 more)
### Community 23 - "Community 23"
Cohesion: 0.11
Nodes (7): convert_heic_to_jpeg(), OSSStorage, process_image_content(), QiniuStorage, 文件存储工具模块 支持多种存储方式: 1. 本地存储 (local) - 默认 2. 七牛云存储 (qiniu) - 推荐生产环境使用 3. 阿里云OSS (, 将 HEIC/HEIF 格式转换为 JPEG 格式, 处理图片内容,如果是 HEIC 格式则转换为 JPEG
### Community 24 - "Community 24"
Cohesion: 0.15
Nodes (39): _apply_plan_product_update(), _apply_plan_update(), AutoGenerateMonthlyResult, _base_query(), create_plan(), delete_plan(), _generate_plan_no(), get_plan() (+31 more)
Cohesion: 0.13
Nodes (44): _apply_plan_product_update(), _apply_plan_update(), AutoGenerateMonthlyResult, _base_query(), create_plan(), delete_plan(), _generate_plan_no(), get_plan() (+36 more)
### Community 25 - "Community 25"
Cohesion: 0.15
Nodes (23): approve_carrier(), create_carrier(), delete_carrier(), delete_driver(), delete_personnel(), delete_vehicle(), get_carrier(), list_carriers() (+15 more)
Cohesion: 0.26
Nodes (12): list_share_logs(), _parse_user_agent(), Any, Session, CRM 报价单分享访问日志 CRUD — 对照 CrmQuoteShareLogServiceImpl.java, Java: statisticsVisits, 简易 User-Agent 解析(替代 Hutool UserAgentUtil), Java: queryLogsByQuoteId (+4 more)
### Community 26 - "Community 26"
Cohesion: 0.07
Nodes (72): day_detail(), delete_assignment(), duty_user_options(), _enrich_assign_names(), month_summary(), Session, 指定用户时补全 personName(用 nick_name)。, 值班可安排人员:仅「值班人员」角色下的启用用户。 (+64 more)
Cohesion: 0.20
Nodes (31): DutyAssignment, DutyDay, DutySlot, 值班排班模型 — docs/superpowers/specs/2026-08-14-duty-schedule-design.md, assert_user_is_duty_staff(), _assignment_dict(), build_public_url(), _count_active_assignments() (+23 more)
### Community 27 - "Community 27"
Cohesion: 0.10
Nodes (34): CRM 报价单 API 路由 — 挂接到 crm.router, 接口菜单权限守卫。super_admin 直接放行。, require_menu_code(), QuoteApproverSettingSaveBody, QuoteAcceptBody, QuoteApprovalApproveBody, QuoteApprovalListBody, QuoteApprovalRefreshNamesBody (+26 more)
Cohesion: 0.11
Nodes (32): CRM 报价单 API 路由 — 挂接到 crm.router, QuoteApproverSettingSaveBody, QuoteAcceptBody, QuoteApprovalApproveBody, QuoteApprovalListBody, QuoteApprovalRefreshNamesBody, QuoteApprovalWithdrawBody, QuoteCreateBody (+24 more)
### Community 28 - "Community 28"
Cohesion: 0.10
@@ -325,34 +321,34 @@ Nodes (57): PlFreightSettlement, PlFreightSettlementApproval, 运费结算表 pl
### Community 29 - "Community 29"
Cohesion: 0.11
Nodes (54): _apply_contract_entity_fields(), _build_contract_filters(), _build_contract_name_from_parties(), _build_customer_address(), _calc_amount_with_tax(), contract_no_exists(), _contract_period_key(), contract_row_to_dict() (+46 more)
Nodes (42): _apply_contract_entity_fields(), _build_contract_filters(), _build_contract_name_from_parties(), _build_customer_address(), _calc_amount_with_tax(), contract_no_exists(), _contract_period_key(), contract_row_to_dict() (+34 more)
### Community 30 - "Community 30"
Cohesion: 0.16
Nodes (26): clear_target_categories(), compute_level(), count_target_rows(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_runtime_settings() (+18 more)
### Community 31 - "Community 31"
Cohesion: 0.06
Nodes (64): contact_add(), contact_delete(), contact_update(), opportunity_add(), opportunity_assign(), opportunity_batch_update_expect_date(), opportunity_batch_update_stage(), opportunity_change_stage() (+56 more)
Cohesion: 0.12
Nodes (37): approve_settlement_legacy(), cancel_settlement(), create_settlement(), delete_settlement(), disburse_settlement(), _generate_settlement_no(), get_settlement(), get_settlement_approval_flow() (+29 more)
### Community 32 - "Community 32"
Cohesion: 0.21
Nodes (17): apply_crm_create_by_scope(), _apply_list_filters(), create_supplier_info(), list_all_supplier_info(), list_supplier_info(), Any, Session, CRM 供方信息 CRUD — 对照 CrmSupplierInfoServiceImpl.java (+9 more)
Cohesion: 0.13
Nodes (35): create_clue_follow(), delete_clue_follow(), get_clue_follow(), get_follow_statistics(), list_clue_follows(), Any, Session, 更新线索跟进记录,必要时同步线索 next_time (+27 more)
### Community 33 - "Community 33"
Cohesion: 0.06
Nodes (55): operlog_detail(), operlog_list(), Session, 操作日志分页列表(基于 API 请求日志适配 RuoYi OperLog 结构), _user_info(), ApiRequestLogMiddleware, _claims_from_auth(), _format_body_bytes() (+47 more)
Cohesion: 0.18
Nodes (17): get_oper_log_meta(), OperLogMeta, 请求级操作日志元数据(ContextVar), 供异步落库线程使用,避免 ContextVar 跨线程丢失。, set_oper_log_meta(), snapshot_oper_log_meta(), _format_summary(), oper_log() (+9 more)
### Community 34 - "Community 34"
Cohesion: 0.12
Nodes (20): list_ethnics(), Any, Session, 民族码表 CRUD(全局 sys_ethnic), _row_to_dict(), 数据模型(与 docs/sql/shanxia2.0.sql 全库导出基线对齐的核心系统表), Web 管理端约定角色(由 sys_role.role_name 映射而来,非数据库枚举), 民族码表 sys_ethnic(国标 GB/T 3304,全局) (+12 more)
Cohesion: 0.40
Nodes (9): create_leave(), get_leave(), list_leaves(), Any, Session, _row_to_dict(), soft_delete_leave(), update_leave() (+1 more)
### Community 35 - "Community 35"
Cohesion: 0.18
Nodes (36): clear_portal_workflow(), _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_bx_user_mapping(), load_runtime_settings() (+28 more)
### Community 36 - "Community 36"
Cohesion: 0.16
Cohesion: 0.15
Nodes (27): 验证密码(哈希非 bcrypt 或损坏时返回 False,避免抛错), verify_password(), change_password(), create_user(), get_user(), get_user_with_relations(), _hash_password(), list_post_ids_for_user() (+19 more)
### Community 37 - "Community 37"
@@ -360,44 +356,44 @@ Cohesion: 0.20
Nodes (24): count_source_rows(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_runtime_settings(), load_settings_env(), load_user_mapping() (+16 more)
### Community 38 - "Community 38"
Cohesion: 0.12
Nodes (15): _db_pool_keepalive_loop(), _error_json(), lifespan(), Exception, Request, 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, 定期 ping MySQL,避免远程库/NAT 回收空闲 TCP 后首请求长时间卡住。 (+7 more)
Cohesion: 0.22
Nodes (10): Config, decrypt_login_payload(), ensure_initialized(), extract_login_credentials(), get_public_pem(), Any, 登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。, Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。 (+2 more)
### Community 39 - "Community 39"
Cohesion: 0.13
Nodes (42): clue_follow_detail(), assign_clues(), check_convert(), check_duplicate_clue(), convert_clue(), create_clue(), _enrich_user_names(), _ensure_clue_in_scope() (+34 more)
Cohesion: 0.22
Nodes (28): assign_clues(), check_convert(), check_duplicate_clue(), convert_clue(), create_clue(), _enrich_user_names(), _ensure_clue_in_scope(), get_clue() (+20 more)
### Community 40 - "Community 40"
Cohesion: 0.16
Nodes (29): CRM 合同子模块路由 — 变更/模板/审批独立页, register_contract_sub_routes(), _change_row_to_dict(), create_contract_change(), execute_change(), export_contract_changes(), export_contract_templates(), _get_change_row() (+21 more)
Nodes (34): CRM 合同子模块路由 — 变更/模板/审批独立页, register_contract_sub_routes(), _change_row_to_dict(), create_contract_change(), create_contract_template(), _enrich_change_rows(), execute_change(), export_contract_changes() (+26 more)
### Community 41 - "Community 41"
Cohesion: 0.12
Nodes (20): BxAdvanceApply, BxAdvanceApproval, AdvanceService, Any, Session, 同一节点多次审批时,取 approval_time 最新的一条作为当前节点状态。, 返回全部审批动作,按时间正序排列,用于展示完整审批历史。, filter_current_cycle_approvals() (+12 more)
Cohesion: 0.14
Nodes (24): PlTransportProductBrandQty, PlTransportProductFeedback, 运输单按产品回传 pl_transport_product_feedback, 运输单产品回传品牌数量 pl_transport_product_brand_qty, _dict_field(), format_brand_qty_summary(), load_transport_driver_product_feedbacks(), persist_io_item_brand_qtys() (+16 more)
### Community 42 - "Community 42"
Cohesion: 0.05
Nodes (103): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_oss_id_to_url(), load_runtime_settings(), load_settings_env() (+95 more)
Cohesion: 0.19
Nodes (32): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_oss_id_to_url(), load_runtime_settings(), load_settings_env() (+24 more)
### Community 43 - "Community 43"
Cohesion: 0.12
Nodes (25): create_invoice(), delete_invoice(), get_mine_wallet_summary(), list_invoice_ledgers(), list_invoice_wallets(), list_invoices(), Request, Session (+17 more)
Cohesion: 0.09
Nodes (34): create_invoice(), delete_invoice(), get_mine_wallet_summary(), list_invoice_ledgers(), list_invoice_wallets(), list_invoices(), Request, Session (+26 more)
### Community 44 - "Community 44"
Cohesion: 0.19
Nodes (32): create_tenant(), DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, MenuCreateBody (+24 more)
Cohesion: 0.10
Nodes (33): build_dict_value_from_label(), ensure_dict_data(), format_hhmm(), format_time_label(), grab(), join_work_plans(), list_duty_staff_users(), normalize_person_name() (+25 more)
### Community 45 - "Community 45"
Cohesion: 0.16
Nodes (21): _build_page(), _get_by_token(), _make_handlers(), _models(), Session, 运输计划 · 入库/出库单现场免登录公共 API, IoOrderItemPatch, IoOrderItemRead (+13 more)
Cohesion: 0.08
Nodes (25): category_add(), category_delete(), category_tree(), category_update(), product_add(), product_delete(), product_detail(), product_options() (+17 more)
### Community 46 - "Community 46"
Cohesion: 0.20
Nodes (27): apply_biz_backfill(), BizRef, build_biz_index(), count_target_rows(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str() (+19 more)
Cohesion: 0.19
Nodes (28): apply_biz_backfill(), BackfillRule, BizRef, build_biz_index(), count_target_rows(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows() (+20 more)
### Community 47 - "Community 47"
Cohesion: 0.18
Nodes (22): ReimburseDetailOut, allocate_reimburse_title_seq(), format_reimburse_title(), generate_apply_code(), generate_title(), is_auto_reimburse_title(), parse_optional_date(), parse_reimburse_date() (+14 more)
Cohesion: 0.14
Nodes (30): PlanContractGapRead, PlanContractLink, PlanProductRead, is_internal_customer_name(), 运输计划:内置山峡/云栋客户判定(合同可免), build_plan_contract_gap_read(), compute_contract_gap_sides(), _demander_gap_expr() (+22 more)
### Community 48 - "Community 48"
Cohesion: 0.29
@@ -408,72 +404,72 @@ Cohesion: 0.49
Nodes (10): clue_statistics_level(), _apply_create_range(), _base_query(), get_clue_level_statistics(), get_clue_overview_statistics(), get_clue_owner_statistics(), get_clue_source_statistics(), get_clue_trend_statistics() (+2 more)
### Community 50 - "Community 50"
Cohesion: 0.21
Nodes (15): BxInvoiceLedger, BxInvoiceWalletSummary, 报销(bx)模块数据模型 — 对齐 docs/sql/20260606_bx_tables.sql, InvoiceLedgerService, Any, Decimal, Session, should_skip_release() (+7 more)
Cohesion: 0.09
Nodes (43): register_contract_routes(), batch_assign_owner(), batch_submit_contract_approval(), create_from_opportunity(), create_from_quote(), download_contract_pdf(), _ensure_contract_in_scope(), export_contracts() (+35 more)
### Community 51 - "Community 51"
Cohesion: 0.11
Nodes (31): menu_badge_stats(), Session, Session, recognize_cert(), RecognizeCertBody, RecognizeCertResult, cancel_apply(), create_apply() (+23 more)
Cohesion: 0.10
Nodes (38): list_public_vehicle_plates(), Session, 地磅公共只读:免登车牌 options(现场填写页), 免登:按出入库/计划解析 tenant_no,返回 pl_vehicle 车牌(self + carrier)。, _resolve_tenant_no(), menu_badge_stats(), Session, Session (+30 more)
### Community 52 - "Community 52"
Cohesion: 0.20
Nodes (23): advance_issue_stats(), batch_offset(), export_advance_issues(), issue_advance(), list_advance_issues(), list_advance_offset_records(), list_pending_payments(), list_processed_payments() (+15 more)
Cohesion: 0.23
Nodes (12): CRM 模块 Excel 导出列定义(字段与字典类型对齐 Java *Vo @ExcelDictFormat)。, _build_workbook(), DictLabelResolver, export_rows_to_xlsx_response(), ExportColumn, Any, Excel 导出工具:集中处理字典 code → label(对齐 Java ExcelDictConvert / ExcelDictPreloadConver, CRM 列表导出统一入口:表头 + 行数据 + 字典 label 转换 + 文件流响应。 (+4 more)
### Community 53 - "Community 53"
Cohesion: 0.19
Nodes (14): parse_resource_snapshot(), Any, 运输计划 - 安排运输时车辆/人员/证件资源快照 Schema, 从 ORM 文本列或 dict 解析快照。, SnapshotCertRead, SnapshotPersonnelRow, TransportResourceSnapshot, build_personnel_summary() (+6 more)
Cohesion: 0.34
Nodes (32): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, MenuCreateBody (+24 more)
### Community 54 - "Community 54"
Cohesion: 0.26
Nodes (23): _get_by_plan(), get_inbound(), get_inbound_by_plan(), _get_order(), get_outbound(), get_outbound_by_plan(), inbound_site_url(), list_inbound() (+15 more)
### Community 55 - "Community 55"
Cohesion: 0.28
Nodes (11): assert_products_allowed_for_supplier(), enrich_products_with_hazard_flags(), get_supplier_hazard_flags(), product_allowed(), Any, Session, 批量回填明细行 isToxic/isExplosive(来自产品主数据,供前端切换主体剔除)。, test_dual_requires_both() (+3 more)
Cohesion: 0.12
Nodes (29): get_pool_record(), list_pool_records(), Any, Session, _row_to_dict(), total_pages(), delete_pool_rules(), execute_pool_rules() (+21 more)
### Community 56 - "Community 56"
Cohesion: 0.32
Nodes (10): _assert_users_exist(), get_setting(), Any, Session, _row_to_dict(), save_setting(), CrmContractApproverSetting, CrmProductAlias (+2 more)
Cohesion: 0.18
Nodes (31): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), legacy_parent_id(), load_oss_id_to_url(), load_runtime_settings() (+23 more)
### Community 57 - "Community 57"
Cohesion: 0.40
Nodes (9): create_oss(), create_oss(), get_oss_by_id(), get_oss_by_url(), Session, 按主键 id 查询附件(兼容旧 CRM 雪花 oss_id 迁入后的记录), 将 fileUrl 或旧 CRM 数字 ossId 解析为 MinIO 相对路径。 门户新上传把相对路径写入业务字段;迁入合同附件仍存数字 ossId, resolve_storage_path() (+1 more)
Cohesion: 0.47
Nodes (8): create_oss(), get_oss_by_id(), get_oss_by_url(), Session, 按主键 id 查询附件(兼容旧 CRM 雪花 oss_id 迁入后的记录), 将 fileUrl 或旧 CRM 数字 ossId 解析为 MinIO 相对路径。 门户新上传把相对路径写入业务字段;迁入合同附件仍存数字 ossId, resolve_storage_path(), SysOss
### Community 58 - "Community 58"
Cohesion: 0.28
Nodes (17): _address_to_read(), _build_address_query(), create_transport_address(), delete_transport_address(), _get_address(), get_transport_address(), list_all_transport_addresses(), list_transport_addresses() (+9 more)
Cohesion: 0.18
Nodes (29): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_oss_id_to_url(), load_runtime_settings(), load_settings_env() (+21 more)
### Community 59 - "Community 59"
Cohesion: 0.20
Nodes (28): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_oss_id_to_url(), load_runtime_settings(), load_settings_env() (+20 more)
### Community 60 - "Community 60"
Cohesion: 0.10
Nodes (19): 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方详情 — Java: CrmSupplierInfoController.getInfo, 新增供方 — Java: CrmSupplierInfoController.add, 修改供方 — Java: CrmSupplierInfoController.edit, 删除供方 — Java: CrmSupplierInfoController.remove, 导出供方列表 — Java: CrmSupplierInfoController.export, supplier_info_add(), supplier_info_delete() (+11 more)
Cohesion: 0.07
Nodes (33): 分页查询供方列表 — Java: CrmSupplierInfoController.list, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方详情 — Java: CrmSupplierInfoController.getInfo, 新增供方 — Java: CrmSupplierInfoController.add, 修改供方 — Java: CrmSupplierInfoController.edit, 删除供方 — Java: CrmSupplierInfoController.remove, 导出供方列表 — Java: CrmSupplierInfoController.export, supplier_info_add() (+25 more)
### Community 61 - "Community 61"
Cohesion: 0.18
Nodes (27): DbInboundOutbound, PlTransportDbLink, 运输-地磅出入库关联 pl_transport_db_link, _apply_plan_quantity_delta(), _create_db_inventory(), find_monthly_db_plan(), _generate_batch_no(), _inventory_has_weighing_slip() (+19 more)
Cohesion: 0.17
Nodes (31): DbInboundOutbound, PlTransportDbLink, 运输-地磅出入库关联 pl_transport_db_link, _apply_plan_quantity_delta(), _create_db_inventory(), _create_db_plan(), DibangInventoryLinkResult, find_monthly_db_plan() (+23 more)
### Community 62 - "Community 62"
Cohesion: 0.19
Nodes (26): _apply_weighing_product_update(), _apply_weighing_update(), _base_query(), create_weighing(), delete_weighing(), _get_weighing(), get_weighing_related(), list_weighing() (+18 more)
Cohesion: 0.27
Nodes (10): apply_product_snapshot_to_plan(), ProductSnapshot, Session, 地磅模块 — CRM 产品解析与快照写入。, 校验 CRM 产品并返回名称/规格快照。新建单据 product_id 必填。 require_on_shelf=False 用于运输计划联动地磅等历, resolve_crm_product(), _product(), resolve_crm_product:运输联动场景允许已下架产品。 (+2 more)
### Community 63 - "Community 63"
Cohesion: 0.38
Nodes (14): _cargo_stats(), get_dashboard_stats(), get_inbound_by_cargo(), get_inbound_by_partner_cargo(), get_outbound_by_cargo(), get_outbound_by_partner_cargo(), _partner_cargo_stats(), Session (+6 more)
### Community 64 - "Community 64"
Cohesion: 0.27
Nodes (17): get_personnel(), paste_recognize_commit(), PlPersonnel, ConflictAction, PasteCommitBody, PasteCommitResult, PastePersonnelIn, PasteVehicleIn (+9 more)
Cohesion: 0.18
Nodes (18): create_workflow(), delete_workflow(), get_workflow(), get_workflow_by_biz_type(), list_workflows(), Session, save_workflow_nodes(), update_workflow() (+10 more)
### Community 65 - "Community 65"
Cohesion: 0.10
Nodes (58): cancel_apply(), create_apply(), create_process(), delete_applies(), delete_processes(), get_apply(), get_process(), list_applies() (+50 more)
### Community 66 - "Community 66"
Cohesion: 0.12
Nodes (24): bind_biz(), delete_attachment(), download_file(), list_by_biz(), _parse_id_list(), preview_attachment(), Session, UploadFile (+16 more)
Cohesion: 0.15
Nodes (21): day_detail(), delete_assignment(), duty_user_options(), _enrich_assign_names(), month_summary(), Session, 指定用户时补全 personName(用 nick_name)。, 值班可安排人员:仅「值班人员」角色下的启用用户。 (+13 more)
### Community 67 - "Community 67"
Cohesion: 0.12
@@ -481,23 +477,23 @@ Nodes (36): get_password_hash(), Namespace, build_merged_users(), bx_role_labels
### Community 68 - "Community 68"
Cohesion: 0.33
Nodes (14): _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept(), get_dept(), list_depts(), list_depts_tree(), Any (+6 more)
Nodes (13): _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept(), get_dept(), list_depts(), list_depts_tree(), Any (+5 more)
### Community 69 - "Community 69"
Cohesion: 0.14
Nodes (28): _append_user_scope_conditions(), apply_data_scope(), assert_row_in_data_scope(), _collect_scope_ids(), _get_child_dept_ids(), get_user_dept_id(), Session, 数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤) data_scope 值: 1 = 全部数据 2 = 本部门数据 3 (+20 more)
Nodes (26): _append_user_scope_conditions(), apply_data_scope(), assert_row_in_data_scope(), _collect_scope_ids(), _get_child_dept_ids(), get_user_dept_id(), Session, 数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤) data_scope 值: 1 = 全部数据 2 = 本部门数据 3 (+18 more)
### Community 70 - "Community 70"
Cohesion: 0.18
Nodes (18): align_contract_no_to_sign_date(), generate_contract_no(), date, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号} 流水与撞号检测须含软删:uk_contr, 标准编号替换日期段,保留序号;非标准返回 None。, 将标准合同编号日期段对齐签订日;撞号则序号顺延(含软删查重)。 非标准编号原样返回。无签订日则原样返回。, replace_contract_no_date(), _mock_last_contract() (+10 more)
### Community 71 - "Community 71"
Cohesion: 0.20
Nodes (24): create_personnel(), list_personnel(), list_vehicles(), _personnel_to_detail(), _personnel_to_read(), 运输计划管理 - 基础数据 API(承运商、车辆、人员), update_personnel(), _vehicle_to_read() (+16 more)
Cohesion: 0.17
Nodes (20): operlog_detail(), operlog_list(), Session, 操作日志分页列表(基于 API 请求日志适配 RuoYi OperLog 结构), _user_info(), 获取用户所有角色的数据权限配置。 返回: [{"data_scope": str, "details": [SysRoleDataScopeDetai, _resolve_user_role_scopes(), _apply_oper_log_scope() (+12 more)
### Community 72 - "Community 72"
Cohesion: 0.23
Nodes (19): contact_by_customer(), contact_detail(), contact_set_primary(), create_contact(), get_contact(), list_contacts_by_customer(), _parse_optional_date(), Any (+11 more)
Cohesion: 0.15
Nodes (32): create_contact(), get_contact(), list_contacts_by_customer(), _parse_optional_date(), Any, date, Session, _row_to_dict() (+24 more)
### Community 73 - "Community 73"
Cohesion: 0.20
@@ -508,12 +504,12 @@ Cohesion: 0.31
Nodes (3): Any, Session, WorkflowEngine
### Community 75 - "apply_crm_create_by_scope"
Cohesion: 0.23
Nodes (12): change_current_user_password(), get_current_user_profile(), public_quote_share(), Request, Session, 获取当前用户信息(与 sys_user 字段对应)。, 公开分享查看报价单 — Java: GET /share/{shareCode}(无 JWT), get_current_user() (+4 more)
Cohesion: 0.39
Nodes (8): get_redis_client(), get_sync_redis_client(), Redis, 共享 Redis 客户端(同步:会话鉴权;异步:API 加密 nonce)。, 同步 Redis(auth 会话等,在线程池中调用)。, 异步 Redis(API 加密 nonce 等中间件)。, _redis_common_kwargs(), redis_enabled()
### Community 76 - "opportunity_follow.py"
Cohesion: 0.27
Nodes (10): carrier_agreement_expired(), carrier_audit_approved(), carrier_eligible_for_transport(), carrier_qualification_complete(), date, 资质附件均为选填,保留函数签名供兼容调用。, _urls_nonempty(), validate_carrier_qualification_fields() (+2 more)
Cohesion: 0.30
Nodes (10): PlCarrier, assert_carrier_eligible_for_transport(), carrier_audit_approved(), carrier_eligible_for_transport(), date, 资质附件均为选填,保留函数签名供兼容调用。, _urls_nonempty(), validate_carrier_qualification_fields() (+2 more)
### Community 77 - "Community 77"
Cohesion: 0.11
@@ -524,157 +520,177 @@ Cohesion: 0.24
Nodes (18): _actor_ids(), _actor_names(), assert_row_deletable_by_creator(), assert_rows_deletable_by_creator(), can_delete_by_creator(), is_super_admin(), normalize_user_info(), Any (+10 more)
### Community 79 - "PlCarrier"
Cohesion: 0.49
Nodes (10): apply_pl_scope(), Session, build_menu_badge_counts(), count_document_reminder_expired_pending(), count_freight_approval_pending(), count_freight_disburse_pending(), _count_pending_applies(), count_plan_archive_pending() (+2 more)
Cohesion: 0.06
Nodes (63): 返回 (作废, 归档)。现场链接实际走入库/出库单公共页。, _resolve_io_order_link_flags(), _build_public_page(), _build_site_product_read(), _format_file_size(), get_public_site(), _get_site_link_by_token(), _parse_feedback_images() (+55 more)
### Community 80 - "resolve_crm_product"
Cohesion: 0.27
Nodes (10): apply_product_snapshot_to_plan(), ProductSnapshot, Session, 地磅模块 — CRM 产品解析与快照写入。, 校验 CRM 产品并返回名称/规格快照。新建单据 product_id 必填。 require_on_shelf=False 用于运输计划联动地磅等历, resolve_crm_product(), _product(), resolve_crm_product:运输联动场景允许已下架产品。 (+2 more)
Cohesion: 0.13
Nodes (27): PlInventoryTransaction, 库存流水表 pl_inventory_transaction, load_brand_qtys_by_plan_product_ids(), persist_plan_product_brand_qtys(), Any, Decimal, Session, 按计划产品明细 ID 批量加载归档品牌数量(brandId/brandName/qty)。 (+19 more)
### Community 81 - "customer_follow.py"
Cohesion: 0.28
Nodes (8): invoice_info_by_customer(), _invoice_info_by_customer_impl(), invoice_info_delete(), invoice_info_list(), 按客户ID查询开票信息(与 by-customer 等价), InvoiceInfoByCustomerBody, InvoiceInfoDeleteBody, CRM 客户开票信息 Pydantic Schemas
Cohesion: 0.15
Nodes (14): invoice_info_add(), invoice_info_by_customer(), _invoice_info_by_customer_impl(), invoice_info_delete(), invoice_info_list(), invoice_info_set_default(), invoice_info_update(), 按客户ID查询开票信息(与 by-customer 等价) (+6 more)
### Community 82 - "storage.py"
Cohesion: 0.39
Nodes (8): get_redis_client(), get_sync_redis_client(), Redis, 共享 Redis 客户端(同步:会话鉴权;异步:API 加密 nonce)。, 同步 Redis(auth 会话等,在线程池中调用)。, 异步 Redis(API 加密 nonce 等中间件)。, _redis_common_kwargs(), redis_enabled()
Cohesion: 0.23
Nodes (9): calc_age_from_chinese_id_card(), date, 从 18 位身份证号计算周岁。号码不完整或出生日期无效时返回 None。, 按 18 位身份证号重算 pl_personnel.age,仅更新算出值与库中不一致的记录。返回更新条数。, refresh_personnel_ages_from_id_card(), 每日凌晨重算 pl_personnel 年龄(有合法 18 位身份证号的记录)。, refresh_personnel_ages_job(), test_calc_age_from_18_digit_id() (+1 more)
### Community 83 - "Community 83"
Cohesion: 0.15
Nodes (30): _advance_contract_after_approval(), approve_contract(), approve_contract_by_contract_id(), approve_pass(), approve_reject(), approve_transfer(), _build_approval_list_filters(), can_user_approve() (+22 more)
Cohesion: 0.14
Nodes (32): _advance_contract_after_approval(), approve_contract(), approve_contract_by_contract_id(), approve_pass(), approve_reject(), approve_transfer(), _build_approval_list_filters(), can_user_approve() (+24 more)
### Community 84 - "Community 84"
Cohesion: 0.23
Nodes (23): build_id_maps(), clip_text(), count_rows(), export_mapping(), fetch_rows(), legacy_id_to_str(), main(), map_id() (+15 more)
### Community 85 - "allocate_next_biz_no"
Cohesion: 0.33
Nodes (8): allocate_next_biz_no(), _format_biz_no(), _prefix_for(), datetime, Session, 业务协议号自动生成:XBCC / JZCC + YYYYMMDD + 6 位序号, 原子分配下一个业务协议号。 须在 create 事务内调用;使用 MySQL LAST_INSERT_ID 保证并发安全。, BizNoKind
Cohesion: 0.19
Nodes (11): get_current_time(), parse_datetime(), datetime, allocate_next_biz_no(), _format_biz_no(), _prefix_for(), datetime, Session (+3 more)
### Community 86 - "Community 86"
Cohesion: 0.33
Nodes (11): create_leave(), get_leave(), list_leaves(), Any, Session, _row_to_dict(), soft_delete_leave(), update_leave() (+3 more)
Cohesion: 0.24
Nodes (7): camel_to_snake(), CRUDBase, get_model_attr(), HistoryCRUD, Any, Session, ModelType
### Community 87 - "Community 87"
Cohesion: 0.10
Nodes (34): invalidate_all_approvals(), 作废该合同全部有效审批记录(含已通过/已拒绝),用于再编辑或重新提审。, approve_quote(), can_user_approve(), create_approval_records(), delete_pending_approvals(), get_quote_approval(), invalidate_all_approvals() (+26 more)
Cohesion: 0.11
Nodes (32): approve_quote(), can_user_approve(), create_approval_records(), delete_pending_approvals(), get_quote_approval(), invalidate_all_approvals(), list_quote_approvals(), Any (+24 more)
### Community 88 - "CrmProduct"
Cohesion: 0.47
Nodes (3): get_current_time(), parse_datetime(), datetime
Cohesion: 0.20
Nodes (16): _claims_from_auth(), _format_body_bytes(), _persist_log(), Any, Request, 统一记录 API 请求/响应出入参的中间件, _redact_obj(), _schedule_persist_log() (+8 more)
### Community 89 - "pl_ocr_service.py"
Cohesion: 0.19
Nodes (22): PlOcrUsageMonth, 阿里云 OCR 月度调用计数 pl_ocr_usage_month(按自然月全局统计), assert_quota_available(), _build_ocr_client(), compress_image_bytes(), current_usage_month(), _first_attachment_url(), get_or_create_usage_row() (+14 more)
### Community 90 - "SysApiRequestLog"
Cohesion: 0.40
Nodes (5): _avatar_url_field(), _build_user_info_payload(), 与 web-admin 静态路由 meta.roles(admin/super_admin)对齐的单元素数组。, 与 web-admin `userInfo` 字段约定一致。, _web_role_list()
### Community 90 - "WechatMessageService"
Cohesion: 0.15
Nodes (9): 微信消息通知服务 支持微信小程序模板消息推送 使用方式: from app.core.wechat_message import WechatMes, 发送订单状态通知 Args: openid: 用户openid order_n, 发送支付成功通知 Args: openid: 用户openid order_n, 发送配送通知 Args: openid: 配送员openid task_no:, 发送配送时效预警 Args: openid: 配送员openid task_n, 发送退款结果通知 Args: openid: 用户openid order_n, 发送催单通知给配送员 Args: deliver_openid: 配送员openid, 发送模板消息 Args: openid: 用户openid template_ (+1 more)
### Community 91 - "pl_vehicle_recharge_service.py"
Cohesion: 0.29
Nodes (16): ensure_crm_customer_linked_row_in_scope(), ensure_crm_owner_row_in_scope(), Session, CRM 数据权限辅助(负责人 owner_user_id + 标准 create_by/create_dept_id)。, 客户子表(开票信息等):先校验关联客户 owner scope,否则校验 create_by。, _clear_default_for_customer(), create_invoice_info(), get_invoice_info() (+8 more)
### Community 91 - "opportunity_product.py"
Cohesion: 0.51
Nodes (10): apply_site_feedback(), OrderItem, _header(), _item(), test_apply_site_feedback_does_not_reset_first_submitted_at(), test_apply_site_feedback_keeps_in_progress_when_requested(), test_apply_site_feedback_rejects_already_voided(), test_apply_site_feedback_rejects_invalid_status() (+2 more)
### Community 92 - "Community 92"
Cohesion: 0.28
Nodes (14): _assert_users_exist(), get_setting(), join_approver_ids(), parse_approver_ids(), prefill_approver_ids(), Any, Session, 返回可预填的审批人 ID 列表;不可用则空列表。 (+6 more)
Cohesion: 0.20
Nodes (21): _assert_users_exist(), get_setting(), Any, Session, _row_to_dict(), save_setting(), _assert_users_exist(), get_setting() (+13 more)
### Community 93 - "list_products"
Cohesion: 0.50
Nodes (4): list_roles_for_select(), 返回运营端角色下拉选项(label=库中角色名,value=Web 角色码)。, _map_role_name_to_web(), 将 sys_role.role_name 映射为 web-admin 使用的 role 字符串。
### Community 93 - "api_request_log_middleware.py"
Cohesion: 0.11
Nodes (7): convert_heic_to_jpeg(), OSSStorage, process_image_content(), QiniuStorage, 文件存储工具模块 支持多种存储方式: 1. 本地存储 (local) - 默认 2. 七牛云存储 (qiniu) - 推荐生产环境使用 3. 阿里云OSS (, 将 HEIC/HEIF 格式转换为 JPEG 格式, 处理图片内容,如果是 HEIC 格式则转换为 JPEG
### Community 94 - "assert_contract_editable_for_update"
Cohesion: 0.60
Nodes (3): assert_contract_editable_for_update(), test_assert_allows_editable(), test_assert_rejects_non_editable()
### Community 95 - "Community 95"
Cohesion: 0.35
Nodes (11): create_tenant(), get_tenant(), list_tenants(), Any, Session, _row_to_dict(), soft_delete_tenant(), tenant_no_exists() (+3 more)
### Community 95 - "test_pl_public_link_archived.py"
Cohesion: 0.47
Nodes (5): list_ethnics(), Any, Session, 民族码表 CRUD(全局 sys_ethnic), _row_to_dict()
### Community 96 - "__init__.py"
Cohesion: 0.39
Nodes (10): create_site(), get_site(), list_sites(), Any, Session, _row_to_dict(), site_code_exists(), soft_delete_site() (+2 more)
### Community 96 - "opportunity_follow.py"
Cohesion: 0.16
Nodes (18): _build_quote_filters(), _fill_contract_linked_count(), list_quotes(), _parse_optional_datetime(), _period_key(), Any, datetime, Query (+10 more)
### Community 97 - "_ensure_subpack_slip_no"
Cohesion: 0.21
Nodes (22): clear_target_products(), count_target_rows(), _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_runtime_settings() (+14 more)
### Community 99 - "QiniuStorage"
Cohesion: 0.50
Nodes (4): customer_options(), 客户下拉选项(租户内只读,不做 owner 数据权限), CustomerOptionsBody, 客户下拉选项请求体(租户内只读搜索,不做 owner 数据权限)
Cohesion: 0.67
Nodes (3): parse_optional_plan_datetime(), datetime, 运输计划相关日期时间解析:前端 date-picker 仅传 YYYY-MM-DD。
### Community 100 - "price_letter_detail"
Cohesion: 0.24
Nodes (16): Settings, BaseSettings, BackfillRule, apply_renumber(), build_mapping(), fetch_products(), format_new_product_no(), load_settings_env() (+8 more)
Cohesion: 0.27
Nodes (15): Settings, BaseSettings, apply_renumber(), build_mapping(), fetch_products(), format_new_product_no(), load_settings_env(), main() (+7 more)
### Community 101 - "list_product_options"
Cohesion: 0.67
Nodes (3): list_product_options(), 产品下拉 options:keyword 按产品名称/拼音首字母模糊匹配,不匹配产品编号。, test_list_product_options_keyword_only_filters_product_name()
### Community 101 - "tenant.py"
Cohesion: 0.35
Nodes (11): create_tenant(), get_tenant(), list_tenants(), Any, Session, _row_to_dict(), soft_delete_tenant(), tenant_no_exists() (+3 more)
### Community 102 - "dept.py"
Cohesion: 0.67
Nodes (3): attachment_download_url(), AttachmentDownloadUrlBody, 返回带 Content-Disposition 的下载 URL,保证浏览器使用原始文件名。
### Community 102 - "crm_supplier_hazard.py"
Cohesion: 0.60
Nodes (5): product_allowed(), test_dual_requires_both(), test_explosive_only_requires_allow_explosive(), test_plain_always_allowed(), test_toxic_only_requires_allow_toxic()
### Community 103 - "pl_internal_customer.py"
Cohesion: 0.35
Nodes (8): is_internal_customer_name(), 运输计划:内置山峡/云栋客户判定(合同可免), compute_contract_gap_sides(), side_is_exempt(), test_both_external_no_contracts_gaps_both(), test_both_internal_no_gap(), test_external_with_contracts_no_gap(), test_internal_supplier_external_demander_missing_only_demander()
Cohesion: 0.07
Nodes (86): archive_plan(), create_plan(), get_plan(), update_plan(), update_plan_driver_feedback(), update_plan_site_feedback(), PlPlan, PlPlanProduct (+78 more)
### Community 104 - "apply_crm_create_by_scope"
Cohesion: 0.25
Nodes (17): create_price_letter(), get_price_letter(), list_price_letters(), _parse_date(), Any, date, Session, _row_to_dict() (+9 more)
Cohesion: 0.29
Nodes (15): create_price_letter(), get_price_letter(), list_price_letters(), _parse_date(), Any, date, Session, _row_to_dict() (+7 more)
### Community 105 - "list_customers"
Cohesion: 0.15
Nodes (9): 微信消息通知服务 支持微信小程序模板消息推送 使用方式: from app.core.wechat_message import WechatMes, 发送订单状态通知 Args: openid: 用户openid order_n, 发送支付成功通知 Args: openid: 用户openid order_n, 发送配送通知 Args: openid: 配送员openid task_no:, 发送配送时效预警 Args: openid: 配送员openid task_n, 发送退款结果通知 Args: openid: 用户openid order_n, 发送催单通知给配送员 Args: deliver_openid: 配送员openid, 发送模板消息 Args: openid: 用户openid template_ (+1 more)
### Community 105 - "pool_customer_assign"
Cohesion: 0.17
Nodes (11): pool_customer_assign(), pool_customer_claim(), pool_customer_list(), pool_record_detail(), pool_record_list(), PoolAssignBody, PoolClaimBody, PoolCustomerListBody (+3 more)
### Community 106 - "site.py"
Cohesion: 0.39
Nodes (10): create_site(), get_site(), list_sites(), Any, Session, _row_to_dict(), site_code_exists(), soft_delete_site() (+2 more)
### Community 107 - "apply_pl_scope"
Cohesion: 0.14
Nodes (38): ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody, StartInstanceBody, create_instance(), create_process_def(), create_task(), get_all_tasks_for_business() (+30 more)
Cohesion: 0.10
Nodes (43): create_instance(), create_process_def(), create_task(), get_all_tasks_for_business(), get_instance(), get_latest_version(), get_nodes_by_version(), get_pending_tasks_by_node() (+35 more)
### Community 108 - "statistics.py"
Cohesion: 0.45
Nodes (11): _customer_base_query(), get_follow_statistics(), get_industry_statistics(), get_level_statistics(), get_overview_statistics(), get_owner_statistics(), get_source_statistics(), get_trend_statistics() (+3 more)
### Community 109 - "Community 109"
Cohesion: 0.12
Nodes (27): bind_vehicle_driver(), create_vehicle(), get_vehicle(), update_vehicle(), _vehicle_to_detail(), calc_age_from_chinese_id_card(), date, 从 18 位身份证号计算周岁。号码不完整或出生日期无效时返回 None。 (+19 more)
Cohesion: 0.24
Nodes (11): extract_minio_object_key(), join_relative_urls(), normalize_oss_storage_url(), MinIO 附件 URL 规范化(数据迁移共用)。 背景 ---- CRM / 地磅 / 报销与 shanxia2.0 共用同一 MinIO(bucket 默, 业务表 OSS ID 串 → 相对 URL 串(用于客户证照、跟进附件等)。, 去掉预签名 query(``?X-Amz-...``)。, 将绝对/相对 URL 规范为 MinIO object key(不含 bucket)。, 迁移 CRM ``sys_oss`` 行:``url`` + ``file_name`` → 门户相对路径。 (+3 more)
### Community 110 - "Base"
Cohesion: 0.22
Nodes (10): Config, decrypt_login_payload(), ensure_initialized(), extract_login_credentials(), get_public_pem(), Any, 登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。, Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。 (+2 more)
### Community 110 - "plan_has_incomplete_hazard_cert_exists"
Cohesion: 0.50
Nodes (4): ApiRequestLogCRUD, Session, SysApiRequestLog, SysApiRequestLogHis
### Community 111 - "Community 111"
Cohesion: 0.21
Nodes (18): _assert_editable(), _build_public_page(), _format_file_size(), _get_inventory_by_token(), get_public_inventory(), _parse_feedback_images(), Session, UploadFile (+10 more)
Cohesion: 0.28
Nodes (14): _assert_editable(), _build_public_page(), _format_file_size(), _get_inventory_by_token(), get_public_inventory(), _parse_feedback_images(), Session, UploadFile (+6 more)
### Community 112 - ".to_tenant_user_dict"
Cohesion: 0.40
Nodes (3): Any, 供 get_tenant_no 等门户工具使用。, 与门户 JWT / apply_data_scope 所需的 user_info 字段对齐。
### Community 113 - "quote_share_log.py"
Cohesion: 0.26
Nodes (12): list_share_logs(), _parse_user_agent(), Any, Session, CRM 报价单分享访问日志 CRUD — 对照 CrmQuoteShareLogServiceImpl.java, Java: statisticsVisits, 简易 User-Agent 解析(替代 Hutool UserAgentUtil), Java: queryLogsByQuoteId (+4 more)
Cohesion: 0.67
Nodes (3): Web 管理端约定角色(由 sys_role.role_name 映射而来,非数据库枚举), UserRole, str
### Community 114 - "db_models.py"
Cohesion: 0.16
Nodes (17): list_public_vehicle_plates(), Session, 地磅公共只读:免登车牌 options(现场填写页), 免登:按出入库/计划解析 tenant_no,返回 pl_vehicle 车牌(self + carrier)。, _resolve_tenant_no(), DbSequence, 地磅(db)模块数据模型 — 对齐 docs/sql/db_tables.sql, _allocate_inbound_outbound_id() (+9 more)
Cohesion: 0.24
Nodes (12): DbSequence, 地磅(db)模块数据模型 — 对齐 docs/sql/db_tables.sql, _allocate_inbound_outbound_id(), _ensure_sequence_row(), max_numeric_inbound_outbound_id(), next_inbound_outbound_id(), Session, 出入库单 id 生成 — 租户内纯数字自增,并发安全。 (+4 more)
### Community 115 - "site.py"
Cohesion: 0.12
Nodes (20): create_driver(), get_driver(), list_drivers(), _personnel_to_driver_read(), update_driver(), DriverBase, DriverCreate, DriverListResponse (+12 more)
Cohesion: 0.05
Nodes (114): approve_carrier(), bind_vehicle_driver(), create_carrier(), create_driver(), create_personnel(), create_vehicle(), delete_carrier(), delete_driver() (+106 more)
### Community 116 - "Community 116"
Cohesion: 0.17
Nodes (28): batch_insert_quote_products(), calculate_subtotal(), _decimal_value(), delete_products_by_quote_id(), _ensure_quote_parent_scope(), _get_field(), get_quote_product(), insert_quote_product() (+20 more)
### Community 117 - "get_web_auth_menu_codes"
Cohesion: 0.23
Nodes (12): change_current_user_password(), get_current_user_profile(), public_quote_share(), Request, Session, 获取当前用户信息(与 sys_user 字段对应)。, 公开分享查看报价单 — Java: GET /share/{shareCode}(无 JWT), get_current_user() (+4 more)
### Community 118 - "Community 118"
Cohesion: 0.20
Nodes (24): batch_insert_contract_products(), calculate_subtotal(), _decimal_value(), delete_price_tiers_by_contract_id(), delete_products_by_contract_id(), _get_field(), insert_contract_product(), _letter_oss_ids_to_json() (+16 more)
Cohesion: 0.15
Nodes (29): batch_insert_contract_products(), calculate_subtotal(), _decimal_value(), delete_price_tiers_by_contract_id(), delete_products_by_contract_id(), _get_field(), insert_contract_product(), _letter_oss_ids_to_json() (+21 more)
### Community 119 - "Community 119"
Cohesion: 0.16
Nodes (29): CertRow, VehiclePersonnelRow, _cert_row_complete(), collect_required_cert_errors(), _find_cert_for_required(), get_required_personnel_cert_names(), match_required_cert_name(), _missing_field_labels() (+21 more)
Cohesion: 0.12
Nodes (37): CertRow, _cert_row_complete(), collect_required_cert_errors(), _find_cert_for_required(), get_required_personnel_cert_names(), is_huolala_trailer_no(), match_required_cert_name(), _missing_field_labels() (+29 more)
### Community 120 - "sqlalchemy_exception_handler"
Cohesion: 0.11
Nodes (18): ApiCryptoMiddleware, ApiRequestLogMiddleware, _db_pool_keepalive_loop(), _error_json(), lifespan(), Exception, Request, 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误 (+10 more)
### Community 121 - "Session"
Cohesion: 0.11
Nodes (25): API 路由包 — 山峡化工生产一体化门户, create_leave(), delete_leave(), get_leave(), _leave_local_callback(), leave_workflow_callback(), list_leaves(), Session (+17 more)
### Community 122 - "Community 122"
Cohesion: 0.39
Nodes (10): create_post(), get_post(), list_posts(), post_code_exists(), Any, Session, _row_to_dict(), soft_delete_post() (+2 more)
### Community 125 - "Community 125"
Cohesion: 0.13
Nodes (17): camel_to_snake(), CRUDBase, get_model_attr(), HistoryCRUD, Any, Session, _cert_url_blank(), plan_has_hazard_product_exists() (+9 more)
### Community 126 - "customer_pool.py"
Cohesion: 0.36
Nodes (13): _build_tree(), _cascade_soft_delete(), create_menu(), get_menu(), list_menus(), list_menus_tree(), menu_name_exists(), Any (+5 more)
@@ -704,16 +720,16 @@ Cohesion: 0.22
Nodes (15): get_storage(), download_file_bytes(), file_stream(), get_access_url(), get_download_url(), get_preview_url(), normalize_storage_path(), 报销附件存储适配 — 复用门户 app.core.storage,对外保持 bx 协议。 (+7 more)
### Community 141 - "PlTransport"
Cohesion: 0.18
Nodes (25): approve_task(), _assert_wf_instance_in_scope(), _business_type_label(), create_process_def(), delete_process_def(), _enrich_task_list(), get_business_all_tasks(), _get_business_form_fields() (+17 more)
Cohesion: 0.15
Nodes (33): approve_task(), _assert_wf_instance_in_scope(), _business_type_label(), create_process_def(), delete_process_def(), _enrich_task_list(), get_business_all_tasks(), _get_business_form_fields() (+25 more)
### Community 144 - "dict_data.py"
Cohesion: 0.26
Nodes (13): UserCreateBody, create_dict_data(), get_dict_data(), list_dict_data(), list_dict_data_options(), Any, Session, 字典数据 CRUD(sys_dict_data) (+5 more)
Cohesion: 0.27
Nodes (11): create_dict_data(), get_dict_data(), list_dict_data(), list_dict_data_options(), Any, Session, 字典数据 CRUD(sys_dict_data), 字典下拉选项:租户主数据,忽略数据范围(user_info 保留兼容调用方,不参与过滤)。 (+3 more)
### Community 145 - "resolve_crm_product"
Cohesion: 0.08
Nodes (54): _generate_notify_message(), _is_rearrange(), 仅计划已正式安排才算「调整运输」。 创建/编辑表单预选车辆会留下 draft 运输单,计划仍为 pending; 若把非 cancelled, PlPlan, PlTransport, DibangInventoryLinkResult, _plan_type_to_business_type(), _product_quantity() (+46 more)
Cohesion: 0.05
Nodes (78): _build_page(), _get_by_token(), _make_handlers(), _models(), Session, 运输计划 · 入库/出库单现场免登录公共 API, SoftDeleteModel, BizAttachment (+70 more)
### Community 147 - "Community 147"
Cohesion: 0.37
@@ -724,48 +740,32 @@ Cohesion: 0.19
Nodes (7): _guess_preview_content_type(), MinIOStorage, Any, 从 MINIO_BASE_URL 解析公网 endpoint;未配置或与内网相同时复用 self.client。, 按对象键后缀推断内联预览 Content-Type(纠正上传时未写入 MIME 的情况)。, MinIO 预签名下载 URL,携带原始文件名。, MinIO 预签名内联预览 URL(图片新标签页 / PDF iframe 等)。 上传时 put_object 未写 content_typ
### Community 159 - "customer_update"
Cohesion: 0.18
Nodes (17): get_web_auth_menu_codes(), 获取当前用户菜单权限标识(sys_menu.perms,经角色继承)。, get_user_menu_codes(), Session, 读取用户授权的菜单权限标识集合(与前端 meta.menuCode / perms 对齐)。, _expand_with_ancestor_menu_ids(), _granted_menu_ids_for_user(), iter_user_menu_perm_codes() (+9 more)
### Community 160 - "register_local_callback"
Cohesion: 0.17
Nodes (9): API 路由包 — 山峡化工生产一体化门户, get_menu_list(), 运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,14 个一级菜单), Config, format_api_error_message(), PageResult, Exception, 将 HTTP/SQLAlchemy 等异常转为可展示的错误文案(避免 str([]) 空列表)。 (+1 more)
Cohesion: 0.15
Nodes (20): get_web_auth_menu_codes(), 获取当前用户菜单权限标识(sys_menu.perms,经角色继承)。, get_user_menu_codes(), Session, 菜单 code 权限守卫(V0.3:通过 sys_user_role → sys_role_menu → sys_menu.perms), 读取用户授权的菜单权限标识集合(与前端 meta.menuCode / perms 对齐)。, _expand_with_ancestor_menu_ids(), _granted_menu_ids_for_user() (+12 more)
### Community 162 - ".to_tenant_user_dict"
Cohesion: 0.42
Nodes (7): default_settlement_clause(), test_advance_payment_clause(), test_arrive_payment_clause(), test_empty_falls_back_to_arrive(), test_monthly_settlement_clause_uses_label(), test_unknown_falls_back_to_arrive(), test_volume_quarterly_other_labels()
### Community 164 - "get_client_ip"
Cohesion: 0.15
Nodes (29): _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/shanxia2.0.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。无 Token 或已失效时仍返回成功(幂等)。, Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录:账号为 sys_user.user_no,密码为 sys_user.password(bcrypt)。, refresh_token(), web_login() (+21 more)
Cohesion: 0.12
Nodes (34): _avatar_url_field(), _build_user_info_payload(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/shanxia2.0.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。无 Token 或已失效时仍返回成功(幂等)。, 与 web-admin 静态路由 meta.roles(admin/super_admin)对齐的单元素数组。, 与 web-admin `userInfo` 字段约定一致。 (+26 more)
### Community 170 - "test_quote_approver_setting.py"
Cohesion: 0.22
Nodes (3): BaseStorage, 带下载文件名的访问 URL;默认实现为普通访问地址。, 浏览器内联预览 URL;默认实现为普通访问地址。
### Community 171 - "minio_url.py"
Cohesion: 0.07
Nodes (48): pool_record_list(), pool_rule_add(), pool_rule_delete(), pool_rule_detail(), pool_rule_execute(), pool_rule_list(), pool_rule_update(), pool_rule_update_status() (+40 more)
### Community 173 - "resolve_create_time_bounds"
Cohesion: 0.07
Nodes (71): _build_driver_url(), get_plan_arrange_info(), 获取计划已安排运输后的司机链接与地磅出入库关联(供查看弹窗复制简述/二维码)。, _apply_plan_logistics_from_payload(), _apply_transport_arrange_fields(), arrange_transport(), _assert_vehicle_carrier_eligible(), _build_arrange_result() (+63 more)
### Community 174 - "register_local_callback"
Cohesion: 0.07
Nodes (57): 返回 (作废, 归档)。现场链接实际走入库/出库单公共页。, _resolve_io_order_link_flags(), _build_public_page(), _build_site_product_read(), _format_file_size(), get_public_site(), _get_site_link_by_token(), _parse_feedback_images() (+49 more)
Cohesion: 0.08
Nodes (77): get_plan_arrange_info(), 获取计划已安排运输后的司机链接与地磅出入库关联(供查看弹窗复制简述/二维码)。, _apply_plan_logistics_from_payload(), _apply_transport_arrange_fields(), arrange_transport(), _assert_vehicle_carrier_eligible(), _build_arrange_result(), _build_driver_url() (+69 more)
### Community 179 - "test_pl_vehicle_personnel_photo_schema.py"
Cohesion: 0.27
Nodes (16): add_favorite(), count_active(), find_active(), find_soft_deleted(), list_favorites(), _next_order_num(), normalize_menu_path(), Any (+8 more)
### Community 182 - "statistics.py"
Cohesion: 0.45
Nodes (11): _customer_base_query(), get_follow_statistics(), get_industry_statistics(), get_level_statistics(), get_overview_statistics(), get_owner_statistics(), get_source_statistics(), get_trend_statistics() (+3 more)
### Community 188 - "get_client_ip"
Cohesion: 0.23
Nodes (13): get_last_month_range(), month_range(), date, Session, 地磅计划按月自动生成 — 迁移自 simple-dibang-xiaoshou/auto-create-plan/auto_create_monthly_pla, 按月自动生成采购计划。 - 默认仅在每月 1 日执行(force=True 时跳过该限制,供手动触发验收) - 遍历所有租户,按上月计划模板生, run_auto_generate_monthly_plans(), default_execution_status() (+5 more)
Cohesion: 0.24
Nodes (12): get_last_month_range(), month_range(), date, Session, 地磅计划按月自动生成 — 迁移自 simple-dibang-xiaoshou/auto-create-plan/auto_create_monthly_pla, 按月自动生成采购计划。 - 默认仅在每月 1 日执行(force=True 时跳过该限制,供手动触发验收) - 遍历所有租户,按上月计划模板生, run_auto_generate_monthly_plans(), default_execution_status() (+4 more)
### Community 206 - "crm_quote_pdf.py"
Cohesion: 0.36
@@ -776,8 +776,8 @@ Cohesion: 0.18
Nodes (10): 1. 创建虚拟环境, 2. 安装依赖, 3. 配置环境变量, 4. 启动服务, API 路由分组, shanxia2.0 后端骨架(FastAPI), 开发规范, 快速开始 (+2 more)
### Community 220 - "api_crypto_middleware.py"
Cohesion: 0.21
Nodes (15): ApiCryptoMiddleware, _claims_from_auth(), _coerce_post_method_override_to_get(), _get_redis_client(), _is_crypto_api(), _json_error_response(), _mark_nonce_once(), _nonce_key() (+7 more)
Cohesion: 0.24
Nodes (13): _claims_from_auth(), _coerce_post_method_override_to_get(), _get_redis_client(), _is_crypto_api(), _json_error_response(), _mark_nonce_once(), _nonce_key(), Redis (+5 more)
### Community 237 - "order_status_compat.py"
Cohesion: 0.36
@@ -799,17 +799,17 @@ Nodes (6): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `generate_uuid()` connect `Community 109` to `Community 0`, `Community 4`, `Community 5`, `Community 6`, `Community 134`, `Community 8`, `statistics.py`, `Community 136`, `Community 11`, `Community 14`, `Community 15`, `dict_data.py`, `Community 17`, `resolve_crm_product`, `Community 16`, `Community 21`, `Community 24`, `Community 25`, `Community 28`, `Community 29`, `Community 32`, `get_client_ip`, `Community 36`, `Community 39`, `Community 40`, `Community 41`, `minio_url.py`, `resolve_create_time_bounds`, `Community 47`, `Community 50`, `Community 56`, `Community 57`, `Community 58`, `get_client_ip`, `Community 61`, `Community 62`, `Community 64`, `Community 65`, `Community 66`, `Community 67`, `Community 68`, `Community 71`, `Community 72`, `Community 83`, `allocate_next_biz_no`, `Community 86`, `Community 87`, `CrmProduct`, `pl_ocr_service.py`, `pl_vehicle_recharge_service.py`, `Community 92`, `Community 95`, `__init__.py`, `apply_crm_create_by_scope`, `apply_pl_scope`, `quote_share_log.py`, `db_models.py`, `site.py`, `Community 116`, `Community 118`, `Community 122`, `customer_pool.py`?**
_High betweenness centrality (0.111) - this node is a cross-community bridge._
- **Why does `Settings` connect `price_letter_detail` to `_ensure_subpack_slip_no`, `Community 35`, `Community 67`, `Community 37`, `Community 73`, `Community 42`, `Base`, `Community 46`, `Community 59`, `Community 30`?**
- **Why does `generate_uuid()` connect `Community 15` to `Community 0`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `statistics.py`, `Community 136`, `Community 134`, `Community 10`, `Community 11`, `Community 14`, `dict_data.py`, `resolve_crm_product`, `Community 16`, `Community 20`, `Community 21`, `Community 24`, `Community 25`, `Community 26`, `Community 28`, `Community 29`, `Community 32`, `Community 34`, `get_client_ip`, `Community 36`, `Community 39`, `Community 40`, `Community 41`, `Community 43`, `Community 44`, `resolve_create_time_bounds`, `Community 47`, `Community 51`, `Community 55`, `Community 57`, `Community 60`, `get_client_ip`, `Community 61`, `Community 64`, `Community 65`, `Community 67`, `Community 68`, `Community 72`, `resolve_crm_product`, `Community 83`, `allocate_next_biz_no`, `Community 87`, `pl_ocr_service.py`, `Community 92`, `tenant.py`, `pl_internal_customer.py`, `apply_crm_create_by_scope`, `site.py`, `apply_pl_scope`, `db_models.py`, `site.py`, `Community 116`, `Community 118`, `Community 122`, `customer_pool.py`?**
_High betweenness centrality (0.109) - this node is a cross-community bridge._
- **Why does `BaseModel` connect `Community 12` to `Community 0`, `Community 1`, `Community 2`, `Community 3`, `Community 5`, `Community 7`, `Community 11`, `PlTransport`, `Community 14`, `Community 13`, `Community 15`, `resolve_crm_product`, `Community 18`, `Community 19`, `Community 20`, `Community 16`, `Community 24`, `Community 27`, `Community 31`, `Community 40`, `Community 43`, `resolve_create_time_bounds`, `Community 45`, `Community 47`, `Community 51`, `Community 53`, `Community 54`, `Community 60`, `Community 63`, `Community 64`, `Community 65`, `Community 66`, `Community 71`, `PlCarrier`, `customer_follow.py`, `pl_internal_customer.py`, `pool_customer_assign`, `Community 111`, `site.py`, `Community 119`, `Session`?**
_High betweenness centrality (0.096) - this node is a cross-community bridge._
- **Why does `Settings` connect `price_letter_detail` to `_ensure_subpack_slip_no`, `Community 35`, `Community 67`, `Community 37`, `Community 38`, `Community 73`, `Community 42`, `Community 46`, `Community 56`, `Community 58`, `Community 59`, `Community 30`?**
_High betweenness centrality (0.093) - this node is a cross-community bridge._
- **Why does `BaseModel` connect `Community 0` to `Community 1`, `Community 2`, `Community 5`, `Community 6`, `Community 7`, `Community 10`, `Community 11`, `Community 12`, `PlTransport`, `Community 13`, `Community 15`, `dict_data.py`, `Community 17`, `Community 18`, `Community 19`, `Community 14`, `Community 16`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 31`, `register_local_callback`, `Community 33`, `Community 40`, `Community 43`, `Community 44`, `resolve_create_time_bounds`, `minio_url.py`, `Community 47`, `Community 45`, `register_local_callback`, `Community 51`, `Community 52`, `Community 53`, `Community 54`, `Community 58`, `Community 60`, `Community 62`, `Community 63`, `Community 64`, `Community 65`, `Community 66`, `Community 71`, `customer_follow.py`, `QiniuStorage`, `dept.py`, `apply_pl_scope`, `Community 111`, `site.py`, `Community 119`?**
_High betweenness centrality (0.091) - this node is a cross-community bridge._
- **Are the 8 inferred relationships involving `DbUserContext` (e.g. with `InventoryRelatedData` and `AutoGenerateMonthlyResult`) actually correct?**
_`DbUserContext` has 8 INFERRED edges - model-reasoned connections that need verification._
- **Are the 247 inferred relationships involving `ValueError` (e.g. with `.presigned_download_url()` and `.presigned_preview_url()`) actually correct?**
_`ValueError` has 247 INFERRED edges - model-reasoned connections that need verification._
- **What connects `API 路由包 — 山峡化工生产一体化门户`, `返回运营端角色下拉选项(label=库中角色名,value=Web 角色码)。`, `返回用户下拉选项(label=昵称,value=用户ID,附带 nickName/userNo)。 供申请人/审批人等选择场景使用,返回本租户全部有效` to the rest of the system?**
_568 weakly-connected nodes found - possible documentation gaps or missing edges._
_572 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.055633473585787754 - nodes in this community are weakly interconnected._
_Cohesion score 0.11313131313131314 - nodes in this community are weakly interconnected._
- **Should `Community 1` be split into smaller, more focused modules?**
_Cohesion score 0.07804878048780488 - nodes in this community are weakly interconnected._
File diff suppressed because it is too large. Load diff
+263 -243
View File
@@ -1,16 +1,16 @@
# Graph Report - backend (2026-08-20)
## Corpus Check
- 334 files · ~219,354 words
- 332 files · ~221,008 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 4540 nodes · 16819 edges · 169 communities (153 shown, 16 thin omitted)
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 752 edges (avg confidence: 0.62)
- 4579 nodes · 16988 edges · 173 communities (157 shown, 16 thin omitted)
- Extraction: 95% EXTRACTED · 5% INFERRED · 0% AMBIGUOUS · INFERRED: 776 edges (avg confidence: 0.62)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `211156a9`
- Built from commit: `cd0491e6`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@@ -105,32 +105,40 @@
- [[_COMMUNITY_Community 87|Community 87]]
- [[_COMMUNITY_CrmProduct|CrmProduct]]
- [[_COMMUNITY_pl_ocr_service.py|pl_ocr_service.py]]
- [[_COMMUNITY_SysApiRequestLog|SysApiRequestLog]]
- [[_COMMUNITY_pl_vehicle_recharge_service.py|pl_vehicle_recharge_service.py]]
- [[_COMMUNITY_WechatMessageService|WechatMessageService]]
- [[_COMMUNITY_opportunity_product.py|opportunity_product.py]]
- [[_COMMUNITY_Community 92|Community 92]]
- [[_COMMUNITY_list_products|list_products]]
- [[_COMMUNITY_api_request_log_middleware.py|api_request_log_middleware.py]]
- [[_COMMUNITY_assert_contract_editable_for_update|assert_contract_editable_for_update]]
- [[_COMMUNITY_Community 95|Community 95]]
- [[_COMMUNITY___init__.py|__init__.py]]
- [[_COMMUNITY_test_pl_public_link_archived.py|test_pl_public_link_archived.py]]
- [[_COMMUNITY_opportunity_follow.py|opportunity_follow.py]]
- [[_COMMUNITY__ensure_subpack_slip_no|_ensure_subpack_slip_no]]
- [[_COMMUNITY_LocalStorage|LocalStorage]]
- [[_COMMUNITY_QiniuStorage|QiniuStorage]]
- [[_COMMUNITY_price_letter_detail|price_letter_detail]]
- [[_COMMUNITY_list_product_options|list_product_options]]
- [[_COMMUNITY_dept.py|dept.py]]
- [[_COMMUNITY_tenant.py|tenant.py]]
- [[_COMMUNITY_crm_supplier_hazard.py|crm_supplier_hazard.py]]
- [[_COMMUNITY_pl_internal_customer.py|pl_internal_customer.py]]
- [[_COMMUNITY_apply_crm_create_by_scope|apply_crm_create_by_scope]]
- [[_COMMUNITY_pool_customer_assign|pool_customer_assign]]
- [[_COMMUNITY_site.py|site.py]]
- [[_COMMUNITY_apply_pl_scope|apply_pl_scope]]
- [[_COMMUNITY_statistics.py|statistics.py]]
- [[_COMMUNITY_Community 109|Community 109]]
- [[_COMMUNITY_plan_has_incomplete_hazard_cert_exists|plan_has_incomplete_hazard_cert_exists]]
- [[_COMMUNITY_Community 111|Community 111]]
- [[_COMMUNITY_.to_tenant_user_dict|.to_tenant_user_dict]]
- [[_COMMUNITY_quote_share_log.py|quote_share_log.py]]
- [[_COMMUNITY_db_models.py|db_models.py]]
- [[_COMMUNITY_site.py|site.py]]
- [[_COMMUNITY_Community 116|Community 116]]
- [[_COMMUNITY_get_web_auth_menu_codes|get_web_auth_menu_codes]]
- [[_COMMUNITY_Community 118|Community 118]]
- [[_COMMUNITY_Community 119|Community 119]]
- [[_COMMUNITY_sqlalchemy_exception_handler|sqlalchemy_exception_handler]]
- [[_COMMUNITY_Session|Session]]
- [[_COMMUNITY_Community 122|Community 122]]
- [[_COMMUNITY_Community 125|Community 125]]
- [[_COMMUNITY_list_product_options|list_product_options]]
- [[_COMMUNITY_customer_pool.py|customer_pool.py]]
- [[_COMMUNITY_BaseStorage|BaseStorage]]
- [[_COMMUNITY_Community 134|Community 134]]
@@ -145,16 +153,12 @@
- [[_COMMUNITY_Community 151|Community 151]]
- [[_COMMUNITY_response.py|response.py]]
- [[_COMMUNITY_customer_update|customer_update]]
- [[_COMMUNITY_register_local_callback|register_local_callback]]
- [[_COMMUNITY_.to_tenant_user_dict|.to_tenant_user_dict]]
- [[_COMMUNITY_get_client_ip|get_client_ip]]
- [[_COMMUNITY_codeblock1 (iflytekshanxia2.0backend)|code:block1 (/iflytek/shanxia2.0/backend)]]
- [[_COMMUNITY_test_quote_approver_setting.py|test_quote_approver_setting.py]]
- [[_COMMUNITY_minio_url.py|minio_url.py]]
- [[_COMMUNITY_resolve_create_time_bounds|resolve_create_time_bounds]]
- [[_COMMUNITY_register_local_callback|register_local_callback]]
- [[_COMMUNITY_test_pl_vehicle_personnel_photo_schema.py|test_pl_vehicle_personnel_photo_schema.py]]
- [[_COMMUNITY_statistics.py|statistics.py]]
- [[_COMMUNITY_get_client_ip|get_client_ip]]
- [[_COMMUNITY_crm_quote_pdf.py|crm_quote_pdf.py]]
- [[_COMMUNITY_shanxia2.0 后端骨架(FastAPI)|shanxia2.0 后端骨架(FastAPI)]]
@@ -176,18 +180,20 @@
- [[_COMMUNITY_PlanResourceSnapshotMixin|PlanResourceSnapshotMixin]]
## God Nodes (most connected - your core abstractions)
1. `BaseModel` - 455 edges
2. `get_tenant_no()` - 351 edges
3. `success_response()` - 268 edges
4. `DbUserContext` - 256 edges
5. `error_response()` - 247 edges
6. `generate_uuid()` - 194 edges
1. `BaseModel` - 456 edges
2. `get_tenant_no()` - 352 edges
3. `success_response()` - 270 edges
4. `DbUserContext` - 257 edges
5. `error_response()` - 249 edges
6. `generate_uuid()` - 196 edges
7. `BxUserContext` - 188 edges
8. `crm_user_info()` - 137 edges
9. `assert_row_deletable_by_creator()` - 83 edges
10. `ApiResponse` - 80 edges
## Surprising Connections (you probably didn't know these)
- `BackfillRule` --uses--> `Settings` [INFERRED]
scripts/migrate_crm_sys_oss.py → app/core/config.py
- `MigrationStats` --uses--> `Settings` [INFERRED]
scripts/migrate_bx_data.py → app/core/config.py
- `StepStats` --uses--> `Settings` [INFERRED]
@@ -196,97 +202,95 @@
scripts/migrate_crm_clue.py → app/core/config.py
- `StepStats` --uses--> `Settings` [INFERRED]
scripts/migrate_crm_clue.py → app/core/config.py
- `MigrationStats` --uses--> `Settings` [INFERRED]
scripts/migrate_crm_contract.py → app/core/config.py
## Import Cycles
- None detected.
## Communities (169 total, 16 thin omitted)
## Communities (173 total, 16 thin omitted)
### Community 0 - "Community 0"
Cohesion: 0.16
Nodes (32): check_document_expiry(), document_reminder_stats(), get_inventory_ledger(), get_summary(), list_document_reminders(), list_transactions(), list_warnings(), date (+24 more)
Cohesion: 0.27
Nodes (25): BrandOption, BrandQtyItem, PlanDriverFeedbackProductItem, PlanProductCertItem, PlanResourceSnapshotMixin, ApprovalRecordRead, ApprovalRequest, ApprovalSubmit (+17 more)
### Community 1 - "Community 1"
Cohesion: 0.08
Nodes (38): CRM 合同 API 路由 — GET/PUT/DELETE/POST 对齐原 Java CRM, 运营端角色守卫:admin / super_admin 可访问。, require_admin_or_super_admin(), ContractApproverSettingSaveBody, ContractApprovalActionBody, ContractApprovalPassBody, ContractApprovalRejectBody, ContractApprovalTransferBody (+30 more)
Nodes (36): CRM 合同 API 路由 — GET/PUT/DELETE/POST 对齐原 Java CRM, ContractApproverSettingSaveBody, ContractApprovalActionBody, ContractApprovalPassBody, ContractApprovalRejectBody, ContractApprovalTransferBody, ContractBatchAssignBody, ContractBatchSubmitBody (+28 more)
### Community 2 - "Community 2"
Cohesion: 0.13
Nodes (37): _apply_inventory_product_update(), _apply_inventory_update(), _base_query(), create_inventory(), create_link_for_plan(), delete_inventory(), _generate_batch_no(), _get_inventory() (+29 more)
Cohesion: 0.24
Nodes (18): _build_public_page(), _build_site_product_read(), _format_file_size(), get_public_site(), _get_site_link_by_token(), _parse_feedback_images(), Session, UploadFile (+10 more)
### Community 3 - "Community 3"
Cohesion: 0.09
Nodes (98): attachment_download_url(), attachment_preview_url(), AttachmentDownloadUrlBody, create_dept(), create_dict_data(), create_dict_type(), create_menu(), create_menu_favorite() (+90 more)
Cohesion: 0.08
Nodes (103): attachment_download_url(), attachment_preview_url(), AttachmentDownloadUrlBody, create_dept(), create_dict_data(), create_dict_type(), create_menu(), create_menu_favorite() (+95 more)
### Community 4 - "Community 4"
Cohesion: 0.09
Nodes (76): _apply_list_filters(), assign_opportunities(), batch_update_expect_date(), batch_update_stage(), change_stage(), create_opportunity(), _decimal_value(), _enrich_user_names() (+68 more)
Cohesion: 0.12
Nodes (61): _apply_list_filters(), assign_opportunities(), batch_update_expect_date(), batch_update_stage(), change_stage(), create_opportunity(), _decimal_value(), _enrich_user_names() (+53 more)
### Community 5 - "Community 5"
Cohesion: 0.05
Nodes (120): archive_plan(), cancel_plan(), cancel_plan_wait_notice(), count_plans_by_contract(), create_from_contract(), create_plan(), create_plan_full(), delete_plan() (+112 more)
Cohesion: 0.04
Nodes (141): archive_plan(), cancel_plan(), cancel_plan_wait_notice(), count_plans_by_contract(), create_from_contract(), create_plan(), create_plan_full(), delete_plan() (+133 more)
### Community 6 - "Community 6"
Cohesion: 0.18
Nodes (32): assign_customer(), batch_claim_customers(), check_duplicate_customer(), claim_customer(), create_customer(), deal_customer(), _enrich_user_names(), _ensure_customer_in_scope() (+24 more)
Nodes (33): assign_customer(), batch_claim_customers(), check_duplicate_customer(), claim_customer(), create_customer(), deal_customer(), _enrich_user_names(), _ensure_customer_in_scope() (+25 more)
### Community 7 - "Community 7"
Cohesion: 0.04
Nodes (196): clue_assign(), clue_check_convert(), clue_check_duplicate(), clue_convert(), clue_create(), clue_delete(), clue_detail(), clue_export() (+188 more)
Nodes (205): clue_assign(), clue_check_convert(), clue_check_duplicate(), clue_convert(), clue_create(), clue_delete(), clue_detail(), clue_export() (+197 more)
### Community 8 - "Community 8"
Cohesion: 0.10
Nodes (67): register_quote_routes(), accept_quote(), assert_quote_editable_for_update(), _build_quote_filters(), calculate_quote_amount(), create_new_version(), create_quote(), _decimal_value() (+59 more)
Nodes (54): assert_quote_editable_for_update(), _build_quote_filters(), calculate_quote_amount(), create_new_version(), create_quote(), _decimal_value(), _enrich_contact_fields(), _enrich_supplier_name() (+46 more)
### Community 9 - "Community 9"
Cohesion: 0.09
Nodes (53): download_reimburse_import_template(), clue_import_template(), customer_import_template(), product_import_template(), build_annotated_result_bytes(), build_applicant_label(), build_expense_path_labels(), build_import_template_bytes() (+45 more)
Cohesion: 0.10
Nodes (49): download_reimburse_import_template(), build_annotated_result_bytes(), build_applicant_label(), build_expense_path_labels(), build_import_template_bytes(), cell_str(), _ids_visible_in_web_tree(), import_reimburse_workbook() (+41 more)
### Community 10 - "Community 10"
Cohesion: 0.18
Nodes (31): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), legacy_parent_id(), load_oss_id_to_url(), load_runtime_settings() (+23 more)
Cohesion: 0.08
Nodes (31): 报销模块认证适配:门户 JWT 用户 → bx 业务上下文。, 菜单 code 权限守卫(V0.3:通过 sys_user_role → sys_role_menu → sys_menu.perms), 运营端角色守卫:admin / super_admin 可访问。, require_admin_or_super_admin(), BxAdvanceApply, BxAdvanceApproval, AdvanceService, Any (+23 more)
### Community 11 - "Community 11"
Cohesion: 0.10
Nodes (55): cancel_apply(), create_apply(), create_disburse(), delete_applies(), delete_disburses(), get_apply(), get_disburse(), list_applies() (+47 more)
Cohesion: 0.15
Nodes (40): PlVehicleUreaApply, PlVehicleUreaDisburse, 车辆尿素申请 pl_vehicle_urea_apply, 车辆尿素发放登记 pl_vehicle_urea_disburse, Decimal, VehicleUreaApplyCreate, VehicleUreaApplyRead, VehicleUreaApplyUpdate (+32 more)
### Community 12 - "Community 12"
Cohesion: 0.05
Nodes (70): contact_delete(), customer_create(), customer_update(), customer_name_exists(), BaseModel, AttachmentUploadResponse, ChangePasswordRequest, Config (+62 more)
Nodes (68): customer_create(), customer_update(), customer_name_exists(), BaseModel, AttachmentUploadResponse, ChangePasswordRequest, Config, LoginRequest (+60 more)
### Community 13 - "Community 13"
Cohesion: 0.20
Nodes (13): create_expense_item(), delete_expense_item(), get_expense_item(), get_expense_item_tree(), Session, update_expense_item(), BxExpenseItem, _coerce_id() (+5 more)
### Community 14 - "Community 14"
Cohesion: 0.17
Nodes (19): create_workflow(), delete_workflow(), get_workflow(), get_workflow_by_biz_type(), list_workflows(), Session, save_workflow_nodes(), update_workflow() (+11 more)
Cohesion: 0.20
Nodes (15): bind_biz(), delete_attachment(), download_file(), list_by_biz(), _parse_id_list(), preview_attachment(), Session, UploadFile (+7 more)
### Community 15 - "Community 15"
Cohesion: 0.11
Nodes (17): BxReimburseApply, BxReimburseApproval, BxReimburseDetail, BxReimburseInvoice, BxReimburseOffset, 报销单勾选的发票附件(同一发票可勾选多个附件,各占一条)。, ReimburseApplyOut, ReimburseInvoiceBriefOut (+9 more)
Cohesion: 0.09
Nodes (45): approve_reimburse(), approved_by_me(), _build_details_data(), create_reimburse(), delete_reimburse(), get_approval_history(), get_approvals(), get_generated_title() (+37 more)
### Community 16 - "Community 16"
Cohesion: 0.09
Nodes (60): cancel_apply(), create_apply(), create_record(), delete_applies(), delete_records(), get_apply(), get_record(), list_applies() (+52 more)
Cohesion: 0.05
Nodes (107): check_document_expiry(), document_reminder_stats(), get_inventory_ledger(), get_summary(), list_document_reminders(), list_transactions(), list_warnings(), date (+99 more)
### Community 17 - "Community 17"
Cohesion: 0.12
Nodes (29): PlInventoryTransaction, PlPlanProductBrandQty, 计划产品归档品牌数量 pl_plan_product_brand_qty, 库存流水表 pl_inventory_transaction, load_brand_qtys_by_plan_product_ids(), persist_plan_product_brand_qtys(), Any, Decimal (+21 more)
Cohesion: 0.14
Nodes (25): filter_current_cycle_approvals(), 审批轮次过滤 — 驳回后重新提交时,进度接口仅展示当前轮次。, 审批进度仅展示当前轮次;完整历史由 get_approval_history 返回。, allocate_reimburse_title_seq(), format_reimburse_title(), generate_apply_code(), generate_title(), is_auto_reimburse_title() (+17 more)
### Community 18 - "Community 18"
Cohesion: 0.09
Nodes (44): advance_pending_approval_stats(), advance_status_counts(), approve_advance(), approved_by_me(), create_advance(), delete_advance(), _fill_offset_amounts(), get_advance() (+36 more)
Cohesion: 0.17
Nodes (30): advance_pending_approval_stats(), advance_status_counts(), approve_advance(), approved_by_me(), create_advance(), delete_advance(), _fill_offset_amounts(), get_advance() (+22 more)
### Community 19 - "Community 19"
Cohesion: 0.07
Nodes (69): bx_health(), 报销(bx)API 路由聚合 — REST 风格,协议对齐 bx 原系统。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, advance_issue_stats(), batch_offset(), export_advance_issues(), issue_advance(), list_advance_issues() (+61 more)
Cohesion: 0.10
Nodes (31): advance_issue_stats(), batch_offset(), export_advance_issues(), issue_advance(), list_advance_issues(), list_advance_offset_records(), list_pending_payments(), list_processed_payments() (+23 more)
### Community 20 - "Community 20"
Cohesion: 0.11
Nodes (43): register_contract_routes(), batch_assign_owner(), batch_submit_contract_approval(), contract_row_to_dict(), download_contract_pdf(), _ensure_contract_in_scope(), export_contracts(), finish_contract() (+35 more)
Cohesion: 0.23
Nodes (18): register_quote_routes(), 接口菜单权限守卫。super_admin 直接放行。, require_menu_code(), accept_quote(), generate_pdf(), generate_share(), _get_quote_in_scope(), get_template() (+10 more)
### Community 21 - "Community 21"
Cohesion: 0.10
@@ -296,77 +300,73 @@ Nodes (53): _alias_row_to_dict(), _assert_brand_deletable(), barcode_exists(), _
Cohesion: 0.08
Nodes (28): parse_admin_request_json(), Any, Request, 运营 Web(/web/admin)JSON 请求体解析。 与 ApiCryptoMiddleware 配合:中间件未改写 body 时,在路由内解密 `{, 读取 JSON body;dict 时走 resolve_request_payload(明文 / 密文 / data 包装)。 Returns:, ApiCryptoError, _b64url_decode(), _b64url_encode() (+20 more)
### Community 23 - "Community 23"
Cohesion: 0.15
Nodes (5): LocalStorage, process_image_content(), Path, 带下载文件名的访问 URL;默认实现为普通访问地址。, 处理图片内容,如果是 HEIC 格式则转换为 JPEG
### Community 24 - "Community 24"
Cohesion: 0.09
Nodes (68): _apply_plan_product_update(), _apply_plan_update(), AutoGenerateMonthlyResult, _base_query(), create_plan(), delete_plan(), _generate_plan_no(), get_plan() (+60 more)
Cohesion: 0.06
Nodes (111): 地磅 API 路由聚合 — REST 风格,协议对齐原 simple-dibang-xiaoshou。, _apply_inventory_product_update(), _apply_inventory_update(), _base_query(), create_inventory(), create_link_for_plan(), delete_inventory(), _generate_batch_no() (+103 more)
### Community 25 - "Community 25"
Cohesion: 0.18
Nodes (29): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_oss_id_to_url(), load_runtime_settings(), load_settings_env() (+21 more)
Cohesion: 0.26
Nodes (12): list_share_logs(), _parse_user_agent(), Any, Session, CRM 报价单分享访问日志 CRUD — 对照 CrmQuoteShareLogServiceImpl.java, Java: statisticsVisits, 简易 User-Agent 解析(替代 Hutool UserAgentUtil), Java: queryLogsByQuoteId (+4 more)
### Community 26 - "Community 26"
Cohesion: 0.08
Nodes (68): day_detail(), delete_assignment(), duty_user_options(), _enrich_assign_names(), month_summary(), Session, 指定用户时补全 personName(用 nick_name)。, 值班可安排人员:仅「值班人员」角色下的启用用户。 (+60 more)
Cohesion: 0.06
Nodes (89): day_detail(), delete_assignment(), duty_user_options(), _enrich_assign_names(), ensure_duty_dict(), month_summary(), Session, 指定用户时补全 personName(用 nick_name)。 (+81 more)
### Community 27 - "Community 27"
Cohesion: 0.10
Nodes (35): CRM 报价单 API 路由 — 挂接到 crm.router, 接口菜单权限守卫。super_admin 直接放行。, require_menu_code(), total_pages(), QuoteApproverSettingSaveBody, QuoteAcceptBody, QuoteApprovalApproveBody, QuoteApprovalListBody (+27 more)
Cohesion: 0.11
Nodes (32): CRM 报价单 API 路由 — 挂接到 crm.router, QuoteApproverSettingSaveBody, QuoteAcceptBody, QuoteApprovalApproveBody, QuoteApprovalListBody, QuoteApprovalRefreshNamesBody, QuoteApprovalWithdrawBody, QuoteCreateBody (+24 more)
### Community 28 - "Community 28"
Cohesion: 0.05
Nodes (98): approve_settlement_legacy(), cancel_settlement(), create_settlement(), delete_settlement(), disburse_settlement(), _generate_settlement_no(), get_settlement(), get_settlement_approval_flow() (+90 more)
Cohesion: 0.11
Nodes (55): PlFreightSettlement, PlFreightSettlementApproval, PlFreightSettlementTransport, 运费结算表 pl_freight_settlement, 运费结算审批 pl_freight_settlement_approval, 结算单-运输关联表 pl_freight_settlement_transport, _advance_after_approval(), _approval_to_dict() (+47 more)
### Community 29 - "Community 29"
Cohesion: 0.11
Nodes (46): _apply_contract_entity_fields(), _build_contract_filters(), _build_contract_name_from_parties(), _build_customer_address(), _calc_amount_with_tax(), contract_no_exists(), _contract_period_key(), create_contract() (+38 more)
Cohesion: 0.10
Nodes (53): _apply_contract_entity_fields(), _build_contract_filters(), _build_contract_name_from_parties(), _build_customer_address(), _calc_amount_with_tax(), contract_no_exists(), _contract_period_key(), contract_row_to_dict() (+45 more)
### Community 30 - "Community 30"
Cohesion: 0.16
Nodes (26): clear_target_categories(), compute_level(), count_target_rows(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_runtime_settings() (+18 more)
### Community 31 - "Community 31"
Cohesion: 0.17
Nodes (11): pool_customer_assign(), pool_customer_claim(), pool_customer_list(), pool_record_detail(), pool_record_list(), PoolAssignBody, PoolClaimBody, PoolCustomerListBody (+3 more)
Cohesion: 0.05
Nodes (88): approve_settlement_legacy(), cancel_settlement(), create_settlement(), delete_settlement(), disburse_settlement(), _generate_settlement_no(), get_settlement(), get_settlement_approval_flow() (+80 more)
### Community 32 - "Community 32"
Cohesion: 0.22
Nodes (17): _apply_list_filters(), create_supplier_info(), get_supplier_info(), list_all_supplier_info(), list_supplier_info(), Any, Session, CRM 供方信息 CRUD — 对照 CrmSupplierInfoServiceImpl.java (+9 more)
Cohesion: 0.36
Nodes (15): create_opportunity_follow(), _ensure_parent_opportunity(), get_opportunity_follow(), list_follows_by_opportunity(), list_opportunity_follows(), _parse_optional_datetime(), Any, datetime (+7 more)
### Community 33 - "Community 33"
Cohesion: 0.10
Nodes (32): ApiRequestLogMiddleware, _claims_from_auth(), _format_body_bytes(), _persist_log(), Any, Request, 统一记录 API 请求/响应出入参的中间件, _redact_obj() (+24 more)
Cohesion: 0.28
Nodes (17): create_contact(), get_contact(), list_contacts(), list_contacts_by_customer(), _parse_optional_date(), Any, date, Session (+9 more)
### Community 34 - "Community 34"
Cohesion: 0.16
Nodes (15): ApiRequestLogCRUD, Session, list_ethnics(), Any, Session, 民族码表 CRUD(全局 sys_ethnic), _row_to_dict(), 数据模型(与 docs/sql/shanxia2.0.sql 全库导出基线对齐的核心系统表) (+7 more)
Cohesion: 0.33
Nodes (11): create_leave(), get_leave(), list_leaves(), Any, Session, _row_to_dict(), soft_delete_leave(), update_leave() (+3 more)
### Community 35 - "Community 35"
Cohesion: 0.18
Nodes (36): clear_portal_workflow(), _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_bx_user_mapping(), load_runtime_settings() (+28 more)
### Community 36 - "Community 36"
Cohesion: 0.14
Nodes (29): 验证密码(哈希非 bcrypt 或损坏时返回 False,避免抛错), verify_password(), change_password(), create_user(), get_user(), get_user_with_relations(), _hash_password(), list_post_ids_for_user() (+21 more)
Cohesion: 0.15
Nodes (27): 验证密码(哈希非 bcrypt 或损坏时返回 False,避免抛错), verify_password(), change_password(), create_user(), get_user(), get_user_with_relations(), _hash_password(), list_post_ids_for_user() (+19 more)
### Community 37 - "Community 37"
Cohesion: 0.20
Nodes (24): count_source_rows(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_runtime_settings(), load_settings_env(), load_user_mapping() (+16 more)
### Community 38 - "Community 38"
Cohesion: 0.08
Nodes (25): Config, decrypt_login_payload(), ensure_initialized(), extract_login_credentials(), get_public_pem(), Any, 登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。, Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。 (+17 more)
Cohesion: 0.22
Nodes (10): Config, decrypt_login_payload(), ensure_initialized(), extract_login_credentials(), get_public_pem(), Any, 登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。, Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。 (+2 more)
### Community 39 - "Community 39"
Cohesion: 0.22
Nodes (28): assign_clues(), check_convert(), check_duplicate_clue(), convert_clue(), create_clue(), _enrich_user_names(), _ensure_clue_in_scope(), get_clue() (+20 more)
### Community 40 - "Community 40"
Cohesion: 0.16
Nodes (33): CRM 合同子模块路由 — 变更/模板/审批独立页, register_contract_sub_routes(), _change_row_to_dict(), create_contract_change(), _enrich_change_rows(), execute_change(), export_contract_changes(), export_contract_templates() (+25 more)
Cohesion: 0.15
Nodes (35): CRM 合同子模块路由 — 变更/模板/审批独立页, register_contract_sub_routes(), _change_row_to_dict(), create_contract_change(), create_contract_template(), _enrich_change_rows(), execute_change(), export_contract_changes() (+27 more)
### Community 41 - "Community 41"
Cohesion: 0.14
@@ -377,100 +377,100 @@ Cohesion: 0.19
Nodes (32): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_oss_id_to_url(), load_runtime_settings(), load_settings_env() (+24 more)
### Community 43 - "Community 43"
Cohesion: 0.09
Nodes (34): create_invoice(), delete_invoice(), get_mine_wallet_summary(), list_invoice_ledgers(), list_invoice_wallets(), list_invoices(), Request, Session (+26 more)
Cohesion: 0.12
Nodes (23): create_invoice(), Request, 一次上传一条发票:多文件挂同一档案,金额为总额,只写一条额度流水。 使用 request.form().getlist,兼容只传 1 个文件时 Fas, BxInvoice, BxInvoiceLedger, BxInvoiceWalletSummary, InvoiceService, InvoiceLedgerService (+15 more)
### Community 44 - "Community 44"
Cohesion: 0.17
Nodes (20): operlog_detail(), operlog_list(), Session, 操作日志分页列表(基于 API 请求日志适配 RuoYi OperLog 结构), _user_info(), business_type_from_path(), title_from_path(), _apply_oper_log_scope() (+12 more)
Cohesion: 0.21
Nodes (17): apply_crm_create_by_scope(), _apply_list_filters(), create_supplier_info(), list_all_supplier_info(), list_supplier_info(), Any, Session, CRM 供方信息 CRUD — 对照 CrmSupplierInfoServiceImpl.java (+9 more)
### Community 45 - "Community 45"
Cohesion: 0.11
Nodes (17): category_add(), category_delete(), category_tree(), category_update(), product_delete(), product_update(), 删除产品(支持单条 body.id 和批量 body.ids), CategoryDeleteBody (+9 more)
Nodes (17): category_add(), category_delete(), category_tree(), category_update(), product_add(), product_delete(), 删除产品(支持单条 body.id 和批量 body.ids), CategoryDeleteBody (+9 more)
### Community 46 - "Community 46"
Cohesion: 0.18
Nodes (30): Settings, BaseSettings, apply_biz_backfill(), BackfillRule, BizRef, build_biz_index(), count_target_rows(), derive_source_url() (+22 more)
Cohesion: 0.19
Nodes (28): apply_biz_backfill(), BackfillRule, BizRef, build_biz_index(), count_target_rows(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows() (+20 more)
### Community 47 - "Community 47"
Cohesion: 0.11
Nodes (31): filter_current_cycle_approvals(), 审批轮次过滤 — 驳回后重新提交时,进度接口仅展示当前轮次。, 审批进度仅展示当前轮次;完整历史由 get_approval_history 返回。, allocate_reimburse_title_seq(), format_reimburse_title(), generate_apply_code(), generate_title(), is_auto_reimburse_title() (+23 more)
Cohesion: 0.13
Nodes (32): PlPlanContract, 运输计划-合同关联(分侧多选) pl_plan_contract, PlanContractGapRead, PlanContractLink, PlanProductBase, PlanProductRead, is_internal_customer_name(), 运输计划:内置山峡/云栋客户判定(合同可免) (+24 more)
### Community 48 - "Community 48"
Cohesion: 0.29
Nodes (12): build_name_pinyin_abbr(), normalize_name_pinyin_abbr(), 名称拼音首字母缩写:库内存小写,检索时大小写不敏感。, 保存时解析拼音字段。 - submitted_provided=True:用提交值(空则按 name 生成) - 否则:已有非空 existi, resolve_name_pinyin_abbr(), test_chinese_and_alnum(), test_empty_and_whitespace(), test_resolve_not_submitted_empty_existing_generates() (+4 more)
### Community 49 - "Community 49"
Cohesion: 0.32
Nodes (13): _apply_create_range(), _base_query(), get_clue_level_statistics(), get_clue_overview_statistics(), get_clue_owner_statistics(), get_clue_source_statistics(), get_clue_trend_statistics(), Any (+5 more)
Cohesion: 0.49
Nodes (10): clue_statistics_level(), _apply_create_range(), _base_query(), get_clue_level_statistics(), get_clue_overview_statistics(), get_clue_owner_statistics(), get_clue_source_statistics(), get_clue_trend_statistics() (+2 more)
### Community 50 - "Community 50"
Cohesion: 0.28
Nodes (15): create_clue_follow(), delete_clue_follow(), get_clue_follow(), get_follow_statistics(), list_clue_follows(), Any, Session, 更新线索跟进记录,必要时同步线索 next_time (+7 more)
Cohesion: 0.14
Nodes (32): register_contract_routes(), batch_assign_owner(), batch_submit_contract_approval(), download_contract_pdf(), _ensure_contract_in_scope(), finish_contract(), generate_template_no(), get_contract_detail() (+24 more)
### Community 51 - "Community 51"
Cohesion: 0.22
Nodes (9): menu_badge_stats(), Session, RecognizeCertResult, _auto_fill_create_dept_id(), get_db(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID, 菜单 code 权限守卫(V0.3:通过 sys_user_role → sys_role_menu → sys_menu.perms), PlMenuBadgeStatsRead (+1 more)
Cohesion: 0.30
Nodes (17): _address_to_read(), _build_address_query(), create_transport_address(), delete_transport_address(), _get_address(), get_transport_address(), list_all_transport_addresses(), list_transport_addresses() (+9 more)
### Community 52 - "Community 52"
Cohesion: 0.24
Nodes (11): CRM 模块 Excel 导出列定义(字段与字典类型对齐 Java *Vo @ExcelDictFormat)。, _build_workbook(), DictLabelResolver, export_rows_to_xlsx_response(), ExportColumn, Any, Excel 导出工具:集中处理字典 code → label(对齐 Java ExcelDictConvert / ExcelDictPreloadConver, CRM 列表导出统一入口:表头 + 行数据 + 字典 label 转换 + 文件流响应。 (+3 more)
Cohesion: 0.20
Nodes (13): CRM 模块 Excel 导出列定义(字段与字典类型对齐 Java *Vo @ExcelDictFormat)。, _build_workbook(), DictLabelResolver, export_rows_to_xlsx_response(), ExportColumn, Any, Session, Excel 导出工具:集中处理字典 code → label(对齐 Java ExcelDictConvert / ExcelDictPreloadConver (+5 more)
### Community 53 - "Community 53"
Cohesion: 0.21
Nodes (13): CertRead, parse_resource_snapshot(), Any, 运输计划 - 安排运输时车辆/人员/证件资源快照 Schema, 从 ORM 文本列或 dict 解析快照。, SnapshotCertRead, SnapshotPersonnelRow, TransportResourceSnapshot (+5 more)
Cohesion: 0.17
Nodes (14): _build_driver_url(), get_plan_arrange_info(), 获取计划已安排运输后的司机链接与地磅出入库关联(供查看弹窗复制简述/二维码)。, _build_arrange_result(), _build_driver_url(), TransportInventoryLinkRead, TransportSiteLinkRead, build_transport_inventory_link_read() (+6 more)
### Community 54 - "Community 54"
Cohesion: 0.26
Nodes (23): _get_by_plan(), get_inbound(), get_inbound_by_plan(), _get_order(), get_outbound(), get_outbound_by_plan(), inbound_site_url(), list_inbound() (+15 more)
Cohesion: 0.19
Nodes (28): _get_by_plan(), get_inbound(), get_inbound_by_plan(), _get_order(), get_outbound(), get_outbound_by_plan(), inbound_site_url(), list_inbound() (+20 more)
### Community 55 - "Community 55"
Cohesion: 0.28
Nodes (11): assert_products_allowed_for_supplier(), enrich_products_with_hazard_flags(), get_supplier_hazard_flags(), product_allowed(), Any, Session, 批量回填明细行 isToxic/isExplosive(来自产品主数据,供前端切换主体剔除)。, test_dual_requires_both() (+3 more)
Cohesion: 0.11
Nodes (30): get_current_time(), parse_datetime(), datetime, get_pool_record(), list_pool_records(), Any, Session, _row_to_dict() (+22 more)
### Community 56 - "Community 56"
Cohesion: 0.40
Nodes (12): create_follow(), _enrich_follow_dict(), get_follow(), list_follows(), list_follows_by_customer(), Any, Session, _row_to_dict() (+4 more)
Cohesion: 0.18
Nodes (31): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), legacy_parent_id(), load_oss_id_to_url(), load_runtime_settings() (+23 more)
### Community 57 - "Community 57"
Cohesion: 0.40
Nodes (9): create_oss(), create_oss(), get_oss_by_id(), get_oss_by_url(), Session, 按主键 id 查询附件(兼容旧 CRM 雪花 oss_id 迁入后的记录), 将 fileUrl 或旧 CRM 数字 ossId 解析为 MinIO 相对路径。 门户新上传把相对路径写入业务字段;迁入合同附件仍存数字 ossId, resolve_storage_path() (+1 more)
Cohesion: 0.38
Nodes (10): generate_uuid(), create_oss(), create_oss(), get_oss_by_id(), get_oss_by_url(), Session, 按主键 id 查询附件(兼容旧 CRM 雪花 oss_id 迁入后的记录), 将 fileUrl 或旧 CRM 数字 ossId 解析为 MinIO 相对路径。 门户新上传把相对路径写入业务字段;迁入合同附件仍存数字 ossId (+2 more)
### Community 58 - "Community 58"
Cohesion: 0.28
Nodes (17): _address_to_read(), _build_address_query(), create_transport_address(), delete_transport_address(), _get_address(), get_transport_address(), list_all_transport_addresses(), list_transport_addresses() (+9 more)
Cohesion: 0.18
Nodes (29): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_oss_id_to_url(), load_runtime_settings(), load_settings_env() (+21 more)
### Community 59 - "Community 59"
Cohesion: 0.20
Nodes (28): _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_oss_id_to_url(), load_runtime_settings(), load_settings_env() (+20 more)
### Community 60 - "Community 60"
Cohesion: 0.09
Nodes (21): 分页查询供方列表 — Java: CrmSupplierInfoController.list, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方详情 — Java: CrmSupplierInfoController.getInfo, 新增供方 — Java: CrmSupplierInfoController.add, 修改供方 — Java: CrmSupplierInfoController.edit, 删除供方 — Java: CrmSupplierInfoController.remove, 导出供方列表 — Java: CrmSupplierInfoController.export, supplier_info_add() (+13 more)
Cohesion: 0.12
Nodes (16): 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方详情 — Java: CrmSupplierInfoController.getInfo, 新增供方 — Java: CrmSupplierInfoController.add, 修改供方 — Java: CrmSupplierInfoController.edit, 删除供方 — Java: CrmSupplierInfoController.remove, supplier_info_add(), supplier_info_delete(), supplier_info_detail() (+8 more)
### Community 61 - "Community 61"
Cohesion: 0.07
Nodes (72): _generate_notify_message(), _is_rearrange(), QrCodeResponse, 仅计划已正式安排才算「调整运输」。 创建/编辑表单预选车辆会留下 draft 运输单,计划仍为 pending; 若把非 cancelled, SoftDeleteModel, BizAttachment, PlCarrier, PlDocumentReminder (+64 more)
Cohesion: 0.11
Nodes (43): PlTransportDbLink, 运输-地磅出入库关联 pl_transport_db_link, _apply_plan_quantity_delta(), _create_db_inventory(), _create_db_plan(), DibangInventoryLinkResult, find_monthly_db_plan(), _generate_batch_no() (+35 more)
### Community 62 - "Community 62"
Cohesion: 0.17
Nodes (11): price_letter_add(), price_letter_detail(), price_letter_list(), price_letter_update(), price_letter_void(), PriceLetterCreateBody, PriceLetterDetailBody, PriceLetterListBody (+3 more)
Cohesion: 0.19
Nodes (15): 返回 (作废, 归档)。现场链接实际走入库/出库单公共页。, _resolve_io_order_link_flags(), _resolve_site_link_flags(), _resolve_driver_link_flags(), 运输计划司机/现场公共链接:归档后应标记并拦截写操作。, test_driver_link_flags_archived_when_plan_archived(), test_driver_link_flags_voided_by_transport_cancelled(), test_driver_link_flags_voided_takes_precedence_shape() (+7 more)
### Community 63 - "Community 63"
Cohesion: 0.21
Nodes (19): _cargo_stats(), get_dashboard_stats(), get_inbound_by_cargo(), get_inbound_by_partner_cargo(), get_outbound_by_cargo(), get_outbound_by_partner_cargo(), _partner_cargo_stats(), Session (+11 more)
Cohesion: 0.38
Nodes (14): _cargo_stats(), get_dashboard_stats(), get_inbound_by_cargo(), get_inbound_by_partner_cargo(), get_outbound_by_cargo(), get_outbound_by_partner_cargo(), _partner_cargo_stats(), Session (+6 more)
### Community 64 - "Community 64"
Cohesion: 0.24
Nodes (11): extract_minio_object_key(), join_relative_urls(), normalize_oss_storage_url(), MinIO 附件 URL 规范化(数据迁移共用)。 背景 ---- CRM / 地磅 / 报销与 shanxia2.0 共用同一 MinIO(bucket 默, 业务表 OSS ID 串 → 相对 URL 串(用于客户证照、跟进附件等)。, 去掉预签名 query(``?X-Amz-...``)。, 将绝对/相对 URL 规范为 MinIO object key(不含 bucket)。, 迁移 CRM ``sys_oss`` 行:``url`` + ``file_name`` → 门户相对路径。 (+3 more)
Cohesion: 0.07
Nodes (42): bx_health(), 报销(bx)API 路由聚合 — REST 风格,协议对齐 bx 原系统。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, delete_invoice(), get_mine_wallet_summary(), list_invoice_ledgers(), list_invoice_wallets(), list_invoices() (+34 more)
### Community 65 - "Community 65"
Cohesion: 0.09
Nodes (68): Session, recognize_cert(), RecognizeCertBody, cancel_apply(), create_apply(), create_process(), delete_applies(), delete_processes() (+60 more)
Cohesion: 0.15
Nodes (42): PlVehicleMaintenanceApply, PlVehicleMaintenanceProcess, 车辆维保申请 pl_vehicle_maintenance_apply, 车辆维保处理登记 pl_vehicle_maintenance_process, Decimal, VehicleMaintenanceApplyCreate, VehicleMaintenanceApplyRead, VehicleMaintenanceApplyReminder (+34 more)
### Community 66 - "Community 66"
Cohesion: 0.10
Nodes (28): bind_biz(), delete_attachment(), download_file(), list_by_biz(), _parse_id_list(), preview_attachment(), Session, UploadFile (+20 more)
Cohesion: 0.29
Nodes (14): create_clue_follow(), delete_clue_follow(), get_clue_follow(), get_follow_statistics(), list_clue_follows(), Any, Session, 更新线索跟进记录,必要时同步线索 next_time (+6 more)
### Community 67 - "Community 67"
Cohesion: 0.12
@@ -481,36 +481,36 @@ Cohesion: 0.33
Nodes (13): _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept(), get_dept(), list_depts(), list_depts_tree(), Any (+5 more)
### Community 69 - "Community 69"
Cohesion: 0.14
Nodes (28): _append_user_scope_conditions(), apply_data_scope(), assert_row_in_data_scope(), _collect_scope_ids(), _get_child_dept_ids(), get_user_dept_id(), Session, 数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤) data_scope 值: 1 = 全部数据 2 = 本部门数据 3 (+20 more)
Cohesion: 0.15
Nodes (27): _append_user_scope_conditions(), apply_data_scope(), assert_row_in_data_scope(), _collect_scope_ids(), _get_child_dept_ids(), get_user_dept_id(), Session, 数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤) data_scope 值: 1 = 全部数据 2 = 本部门数据 3 (+19 more)
### Community 70 - "Community 70"
Cohesion: 0.20
Nodes (15): align_contract_no_to_sign_date(), 标准编号替换日期段,保留序号;非标准返回 None。, 将标准合同编号日期段对齐签订日;撞号则序号顺延(含软删查重)。 非标准编号原样返回。无签订日则原样返回。, replace_contract_no_date(), _mock_last_contract(), 软删占用的编号仍占唯一索引,生成时须跳过(对齐 uk_contract_tenant_no)。, 取最大流水号须含软删,避免复用已软删的最大编号。, test_align_bumps_seq_when_occupied() (+7 more)
Cohesion: 0.18
Nodes (18): align_contract_no_to_sign_date(), generate_contract_no(), date, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号} 流水与撞号检测须含软删:uk_contr, 标准编号替换日期段,保留序号;非标准返回 None。, 将标准合同编号日期段对齐签订日;撞号则序号顺延(含软删查重)。 非标准编号原样返回。无签订日则原样返回。, replace_contract_no_date(), _mock_last_contract() (+10 more)
### Community 71 - "Community 71"
Cohesion: 0.14
Nodes (27): create_vehicle(), get_vehicle(), list_personnel(), list_vehicles(), update_vehicle(), _vehicle_to_detail(), _vehicle_to_read(), PlCert (+19 more)
Cohesion: 0.06
Nodes (56): operlog_detail(), operlog_list(), Session, 操作日志分页列表(基于 API 请求日志适配 RuoYi OperLog 结构), _user_info(), ApiRequestLogMiddleware, _claims_from_auth(), _format_body_bytes() (+48 more)
### Community 72 - "Community 72"
Cohesion: 0.29
Nodes (16): create_contact(), get_contact(), list_contacts_by_customer(), _parse_optional_date(), Any, date, Session, _row_to_dict() (+8 more)
Nodes (16): ensure_crm_customer_linked_row_in_scope(), ensure_crm_owner_row_in_scope(), Session, CRM 数据权限辅助(负责人 owner_user_id + 标准 create_by/create_dept_id)。, 客户子表(开票信息等):先校验关联客户 owner scope,否则校验 create_by。, _clear_default_for_customer(), create_invoice_info(), get_invoice_info() (+8 more)
### Community 73 - "Community 73"
Cohesion: 0.20
Nodes (25): derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_oss_id_to_url(), load_runtime_settings(), load_settings_env(), load_user_mapping() (+17 more)
### Community 74 - "Community 74"
Cohesion: 0.12
Nodes (25): ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody, 审批意见:前端传 commentText,兼容 comment。, StartInstanceBody, TaskActionBody, 流程定义版本 wf_process_version, 流程实例 wf_process_instance (+17 more)
Cohesion: 0.13
Nodes (19): ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody, StartInstanceBody, 流程实例 wf_process_instance, WfNode, WfProcessInstance, _dispatch_local() (+11 more)
### Community 75 - "apply_crm_create_by_scope"
Cohesion: 0.18
Nodes (6): convert_heic_to_jpeg(), _guess_preview_content_type(), QiniuStorage, 文件存储工具模块 支持多种存储方式: 1. 本地存储 (local) - 默认 2. 七牛云存储 (qiniu) - 推荐生产环境使用 3. 阿里云OSS (, 按对象键后缀推断内联预览 Content-Type(纠正上传时未写入 MIME 的情况)。, 将 HEIC/HEIF 格式转换为 JPEG 格式
Cohesion: 0.33
Nodes (12): PlInventoryTransaction, 库存流水表 pl_inventory_transaction, apply_archive_inventory_for_plan(), apply_brand_stock_change(), get_or_create_inventory(), Decimal, Session, qty_delta_for_plan() (+4 more)
### Community 76 - "opportunity_follow.py"
Cohesion: 0.27
Nodes (10): carrier_agreement_expired(), carrier_audit_approved(), carrier_eligible_for_transport(), carrier_qualification_complete(), date, 资质附件均为选填,保留函数签名供兼容调用。, _urls_nonempty(), validate_carrier_qualification_fields() (+2 more)
Nodes (11): customer_dealed(), customer_export(), customer_key(), customer_list(), customer_long_time_no_follow(), customer_mine(), customer_to_follow(), customer_week_new() (+3 more)
### Community 77 - "Community 77"
Cohesion: 0.11
@@ -521,12 +521,12 @@ Cohesion: 0.24
Nodes (18): _actor_ids(), _actor_names(), assert_row_deletable_by_creator(), assert_rows_deletable_by_creator(), can_delete_by_creator(), is_super_admin(), normalize_user_info(), Any (+10 more)
### Community 79 - "PlCarrier"
Cohesion: 0.49
Nodes (10): apply_pl_scope(), Session, build_menu_badge_counts(), count_document_reminder_expired_pending(), count_freight_approval_pending(), count_freight_disburse_pending(), _count_pending_applies(), count_plan_archive_pending() (+2 more)
Cohesion: 0.16
Nodes (22): _build_public_page(), _format_file_size(), _format_plan_date(), get_public_transport(), _get_transport_by_token(), _parse_feedback_images(), Session, UploadFile (+14 more)
### Community 80 - "resolve_crm_product"
Cohesion: 0.25
Nodes (10): _cert_url_blank(), plan_has_hazard_product_exists(), plan_has_incomplete_hazard_cert_exists(), 证书 URL 为空(NULL / 空串 / 纯空白)。, 列表筛选:计划下存在 CRM 标识为易制毒/易制爆的产品。, 列表筛选:存在危化产品且至少一张必需证书未上传。, _compile(), 危化品证上传列表筛选:EXISTS 子查询应覆盖 CRM 危化标识与证书缺口。 (+2 more)
Cohesion: 0.21
Nodes (15): load_brand_qtys_by_plan_product_ids(), persist_plan_product_brand_qtys(), Any, Decimal, Session, 按计划产品明细 ID 批量加载归档品牌数量(brandId/brandName/qty)。, 软删旧品牌量行后按归档结果插入;无品牌约定则只软删不插入。, validate_brand_qty_rows() (+7 more)
### Community 81 - "customer_follow.py"
Cohesion: 0.15
@@ -537,128 +537,164 @@ Cohesion: 0.43
Nodes (5): calc_age_from_chinese_id_card(), date, 从 18 位身份证号计算周岁。号码不完整或出生日期无效时返回 None。, test_calc_age_from_18_digit_id(), test_calc_age_rejects_invalid()
### Community 83 - "Community 83"
Cohesion: 0.14
Nodes (32): _advance_contract_after_approval(), approve_contract(), approve_contract_by_contract_id(), approve_pass(), approve_reject(), approve_transfer(), _build_approval_list_filters(), can_user_approve() (+24 more)
Cohesion: 0.15
Nodes (30): _advance_contract_after_approval(), approve_contract(), approve_contract_by_contract_id(), approve_pass(), approve_reject(), approve_transfer(), _build_approval_list_filters(), can_user_approve() (+22 more)
### Community 84 - "Community 84"
Cohesion: 0.10
Nodes (32): 微信消息通知服务 支持微信小程序模板消息推送 使用方式: from app.core.wechat_message import WechatMes, 发送订单状态通知 Args: openid: 用户openid order_n, 发送支付成功通知 Args: openid: 用户openid order_n, 发送配送通知 Args: openid: 配送员openid task_no:, 发送配送时效预警 Args: openid: 配送员openid task_n, 发送退款结果通知 Args: openid: 用户openid order_n, 发送催单通知给配送员 Args: deliver_openid: 配送员openid, 发送模板消息 Args: openid: 用户openid template_ (+24 more)
Cohesion: 0.23
Nodes (23): build_id_maps(), clip_text(), count_rows(), export_mapping(), fetch_rows(), legacy_id_to_str(), main(), map_id() (+15 more)
### Community 85 - "allocate_next_biz_no"
Cohesion: 0.33
Nodes (8): allocate_next_biz_no(), _format_biz_no(), _prefix_for(), datetime, Session, 业务协议号自动生成:XBCC / JZCC + YYYYMMDD + 6 位序号, 原子分配下一个业务协议号。 须在 create 事务内调用;使用 MySQL LAST_INSERT_ID 保证并发安全。, BizNoKind
### Community 86 - "Community 86"
Cohesion: 0.33
Nodes (11): create_leave(), get_leave(), list_leaves(), Any, Session, _row_to_dict(), soft_delete_leave(), update_leave() (+3 more)
Cohesion: 0.24
Nodes (7): camel_to_snake(), CRUDBase, get_model_attr(), HistoryCRUD, Any, Session, ModelType
### Community 87 - "Community 87"
Cohesion: 0.11
Nodes (32): approve_quote(), can_user_approve(), create_approval_records(), delete_pending_approvals(), get_quote_approval(), invalidate_all_approvals(), list_quote_approvals(), Any (+24 more)
Cohesion: 0.10
Nodes (34): invalidate_all_approvals(), 作废该合同全部有效审批记录(含已通过/已拒绝),用于再编辑或重新提审。, approve_quote(), can_user_approve(), create_approval_records(), delete_pending_approvals(), get_quote_approval(), invalidate_all_approvals() (+26 more)
### Community 88 - "CrmProduct"
Cohesion: 0.38
Nodes (6): 解析司机:显式选择优先,否则取车辆关联司机;均无则允许为空。, _resolve_driver(), 运输计划解析司机:车辆未关联司机时允许为空。, 未显式选司机、车辆也无关联司机时,应返回空三元组而非 400。, test_resolve_driver_with_explicit_id_returns_personnel(), test_resolve_driver_without_vehicle_link_returns_none()
Cohesion: 0.42
Nodes (11): _enrich_follow_dict(), get_follow(), list_follows(), list_follows_by_customer(), Any, Session, _row_to_dict(), soft_delete_follow() (+3 more)
### Community 89 - "pl_ocr_service.py"
Cohesion: 0.19
Nodes (22): PlOcrUsageMonth, 阿里云 OCR 月度调用计数 pl_ocr_usage_month(按自然月全局统计), assert_quota_available(), _build_ocr_client(), compress_image_bytes(), current_usage_month(), _first_attachment_url(), get_or_create_usage_row() (+14 more)
### Community 90 - "SysApiRequestLog"
Cohesion: 0.40
Nodes (6): _ensure_subpack_slip_no(), _format_subpack_slip_no(), _normalize_subpack_slip_no(), 将历史分装单号统一为 NO:SXFZYYYYMMDD-###, 分配分装单号 NO:SXFZYYYYMMDD-###(按租户每日递增), _subpack_slip_no_seq()
### Community 90 - "WechatMessageService"
Cohesion: 0.15
Nodes (9): 微信消息通知服务 支持微信小程序模板消息推送 使用方式: from app.core.wechat_message import WechatMes, 发送订单状态通知 Args: openid: 用户openid order_n, 发送支付成功通知 Args: openid: 用户openid order_n, 发送配送通知 Args: openid: 配送员openid task_no:, 发送配送时效预警 Args: openid: 配送员openid task_n, 发送退款结果通知 Args: openid: 用户openid order_n, 发送催单通知给配送员 Args: deliver_openid: 配送员openid, 发送模板消息 Args: openid: 用户openid template_ (+1 more)
### Community 91 - "pl_vehicle_recharge_service.py"
Cohesion: 0.38
Nodes (13): ensure_crm_customer_linked_row_in_scope(), 客户子表(开票信息等):先校验关联客户 owner scope,否则校验 create_by。, _clear_default_for_customer(), create_invoice_info(), get_invoice_info(), list_invoice_info_by_customer(), Any, Session (+5 more)
### Community 91 - "opportunity_product.py"
Cohesion: 0.40
Nodes (12): apply_site_feedback(), 更新明细实际数量,并按需校验/持久化现场品牌数量。, update_io_order_items_qty_and_brand(), OrderItem, _header(), _item(), test_apply_site_feedback_does_not_reset_first_submitted_at(), test_apply_site_feedback_keeps_in_progress_when_requested() (+4 more)
### Community 92 - "Community 92"
Cohesion: 0.18
Nodes (22): _assert_users_exist(), get_setting(), Any, Session, _row_to_dict(), save_setting(), _assert_users_exist(), get_setting() (+14 more)
Cohesion: 0.20
Nodes (21): _assert_users_exist(), get_setting(), Any, Session, _row_to_dict(), save_setting(), _assert_users_exist(), get_setting() (+13 more)
### Community 93 - "list_products"
Cohesion: 0.40
Nodes (5): list_products(), 产品/客户列表 keyword:名称/编号/拼音首字母 OR 匹配。, test_list_customer_options_keyword_includes_name_and_pinyin(), test_list_customers_keyword_includes_name_and_pinyin(), test_list_products_keyword_includes_name_and_pinyin()
### Community 93 - "api_request_log_middleware.py"
Cohesion: 0.11
Nodes (7): convert_heic_to_jpeg(), OSSStorage, process_image_content(), QiniuStorage, 文件存储工具模块 支持多种存储方式: 1. 本地存储 (local) - 默认 2. 七牛云存储 (qiniu) - 推荐生产环境使用 3. 阿里云OSS (, 将 HEIC/HEIF 格式转换为 JPEG 格式, 处理图片内容,如果是 HEIC 格式则转换为 JPEG
### Community 94 - "assert_contract_editable_for_update"
Cohesion: 0.60
Nodes (3): assert_contract_editable_for_update(), test_assert_allows_editable(), test_assert_rejects_non_editable()
### Community 95 - "Community 95"
Cohesion: 0.35
Nodes (11): create_tenant(), get_tenant(), list_tenants(), Any, Session, _row_to_dict(), soft_delete_tenant(), tenant_no_exists() (+3 more)
### Community 95 - "test_pl_public_link_archived.py"
Cohesion: 0.21
Nodes (9): 按 18 位身份证号重算 pl_personnel.age,仅更新算出值与库中不一致的记录。返回更新条数。, refresh_personnel_ages_from_id_card(), 每天早上9点:保护期到期提醒(3天内到期且跟进不足), remind_customer_recycle(), warn_customer_recycle(), 每日扫描 pl_cert,生成证件到期提醒。, scan_pl_cert_expiry_job(), 每日凌晨重算 pl_personnel 年龄(有合法 18 位身份证号的记录)。 (+1 more)
### Community 96 - "__init__.py"
Cohesion: 0.39
Nodes (10): create_site(), get_site(), list_sites(), Any, Session, _row_to_dict(), site_code_exists(), soft_delete_site() (+2 more)
### Community 96 - "opportunity_follow.py"
Cohesion: 0.25
Nodes (9): _error_json(), Exception, Request, 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, sqlalchemy_exception_handler(), unhandled_exception_handler(), JSONResponse (+1 more)
### Community 97 - "_ensure_subpack_slip_no"
Cohesion: 0.21
Nodes (22): clear_target_products(), count_target_rows(), _dec(), derive_source_url(), fetch_portal_admin_id(), fetch_source_rows(), legacy_id_to_str(), load_runtime_settings() (+14 more)
### Community 98 - "LocalStorage"
Cohesion: 0.48
Nodes (6): merge_plan_logistics_into_order(), merge_plan_product_into_item(), resolve_unit_label(), test_merge_plan_logistics_into_order(), test_merge_plan_product_into_item(), test_resolve_unit_label_maps_dun()
### Community 99 - "QiniuStorage"
Cohesion: 0.67
Nodes (3): parse_optional_plan_datetime(), datetime, 运输计划相关日期时间解析:前端 date-picker 仅传 YYYY-MM-DD。
### Community 100 - "price_letter_detail"
Cohesion: 0.31
Nodes (13): apply_renumber(), build_mapping(), fetch_products(), format_new_product_no(), load_settings_env(), main(), product_id_sort_key(), Engine (+5 more)
Cohesion: 0.27
Nodes (15): Settings, BaseSettings, apply_renumber(), build_mapping(), fetch_products(), format_new_product_no(), load_settings_env(), main() (+7 more)
### Community 101 - "list_product_options"
Cohesion: 0.67
Nodes (3): list_product_options(), 产品下拉 options:keyword 按产品名称/拼音首字母模糊匹配,不匹配产品编号。, test_list_product_options_keyword_only_filters_product_name()
### Community 101 - "tenant.py"
Cohesion: 0.14
Nodes (22): list_ethnics(), Any, Session, 民族码表 CRUD(全局 sys_ethnic), _row_to_dict(), create_tenant(), get_tenant(), list_tenants() (+14 more)
### Community 102 - "crm_supplier_hazard.py"
Cohesion: 0.21
Nodes (11): list_products(), get_supplier_hazard_flags(), product_allowed(), 产品/客户列表 keyword:名称/编号/拼音首字母 OR 匹配。, test_list_customer_options_keyword_includes_name_and_pinyin(), test_list_customers_keyword_includes_name_and_pinyin(), test_list_products_keyword_includes_name_and_pinyin(), test_dual_requires_both() (+3 more)
### Community 103 - "pl_internal_customer.py"
Cohesion: 0.17
Nodes (21): PlanContractGapRead, is_internal_customer_name(), 运输计划:内置山峡/云栋客户判定(合同可免), build_plan_contract_gap_read(), compute_contract_gap_sides(), _demander_gap_expr(), list_plan_contract_gaps(), plan_has_contract_gap_exists() (+13 more)
Cohesion: 0.28
Nodes (7): parse_resource_snapshot(), Any, 运输计划 - 安排运输时车辆/人员/证件资源快照 Schema, 从 ORM 文本列或 dict 解析快照。, TransportResourceSnapshot, apply_resource_snapshot_json(), 将快照 JSON 写入计划/运输单,并同步标量冗余字段。
### Community 104 - "apply_crm_create_by_scope"
Cohesion: 0.28
Nodes (16): create_price_letter(), get_price_letter(), list_price_letters(), _parse_date(), Any, date, Session, _row_to_dict() (+8 more)
### Community 105 - "pool_customer_assign"
Cohesion: 0.17
Nodes (11): pool_customer_assign(), pool_customer_claim(), pool_customer_list(), pool_record_detail(), pool_record_list(), PoolAssignBody, PoolClaimBody, PoolCustomerListBody (+3 more)
### Community 106 - "site.py"
Cohesion: 0.39
Nodes (10): create_site(), get_site(), list_sites(), Any, Session, _row_to_dict(), site_code_exists(), soft_delete_site() (+2 more)
### Community 107 - "apply_pl_scope"
Cohesion: 0.16
Nodes (30): create_instance(), create_process_def(), create_task(), get_all_tasks_for_business(), get_instance(), get_latest_version(), get_nodes_by_version(), get_pending_tasks_by_node() (+22 more)
Cohesion: 0.14
Nodes (34): create_instance(), create_process_def(), create_task(), get_all_tasks_for_business(), get_instance(), get_latest_version(), get_nodes_by_version(), get_pending_tasks_by_node() (+26 more)
### Community 108 - "statistics.py"
Cohesion: 0.45
Nodes (11): _customer_base_query(), get_follow_statistics(), get_industry_statistics(), get_level_statistics(), get_overview_statistics(), get_owner_statistics(), get_source_statistics(), get_trend_statistics() (+3 more)
### Community 109 - "Community 109"
Cohesion: 0.12
Nodes (34): bind_vehicle_driver(), paste_recognize_commit(), generate_uuid(), PlVehiclePersonnel, 车辆-人员关联表 pl_vehicle_personnel, VehiclePersonnelRow, ConflictAction, PasteCommitBody (+26 more)
Cohesion: 0.24
Nodes (11): extract_minio_object_key(), join_relative_urls(), normalize_oss_storage_url(), MinIO 附件 URL 规范化(数据迁移共用)。 背景 ---- CRM / 地磅 / 报销与 shanxia2.0 共用同一 MinIO(bucket 默, 业务表 OSS ID 串 → 相对 URL 串(用于客户证照、跟进附件等)。, 去掉预签名 query(``?X-Amz-...``)。, 将绝对/相对 URL 规范为 MinIO object key(不含 bucket)。, 迁移 CRM ``sys_oss`` 行:``url`` + ``file_name`` → 门户相对路径。 (+3 more)
### Community 110 - "plan_has_incomplete_hazard_cert_exists"
Cohesion: 0.47
Nodes (5): list_public_vehicle_plates(), Session, 地磅公共只读:免登车牌 options(现场填写页), 免登:按出入库/计划解析 tenant_no,返回 pl_vehicle 车牌(self + carrier)。, _resolve_tenant_no()
### Community 111 - "Community 111"
Cohesion: 0.28
Nodes (14): _assert_editable(), _build_public_page(), _format_file_size(), _get_inventory_by_token(), get_public_inventory(), _parse_feedback_images(), Session, UploadFile (+6 more)
### Community 113 - "quote_share_log.py"
Cohesion: 0.26
Nodes (12): list_share_logs(), _parse_user_agent(), Any, Session, CRM 报价单分享访问日志 CRUD — 对照 CrmQuoteShareLogServiceImpl.java, Java: statisticsVisits, 简易 User-Agent 解析(替代 Hutool UserAgentUtil), Java: queryLogsByQuoteId (+4 more)
### Community 112 - ".to_tenant_user_dict"
Cohesion: 0.40
Nodes (3): Any, 供 get_tenant_no 等门户工具使用。, 与门户 JWT / apply_data_scope 所需的 user_info 字段对齐。
### Community 114 - "db_models.py"
Cohesion: 0.16
Nodes (17): list_public_vehicle_plates(), Session, 地磅公共只读:免登车牌 options(现场填写页), 免登:按出入库/计划解析 tenant_no,返回 pl_vehicle 车牌(self + carrier)。, _resolve_tenant_no(), DbSequence, 地磅(db)模块数据模型 — 对齐 docs/sql/db_tables.sql, _allocate_inbound_outbound_id() (+9 more)
Cohesion: 0.28
Nodes (11): DbSequence, _allocate_inbound_outbound_id(), _ensure_sequence_row(), max_numeric_inbound_outbound_id(), next_inbound_outbound_id(), Session, 出入库单 id 生成 — 租户内纯数字自增,并发安全。, 当前租户下纯数字 id 的最大值(不含 UUID 等非数字主键)。 (+3 more)
### Community 115 - "site.py"
Cohesion: 0.09
Nodes (53): approve_carrier(), create_carrier(), create_driver(), create_personnel(), delete_carrier(), delete_driver(), delete_personnel(), delete_vehicle() (+45 more)
Cohesion: 0.05
Nodes (126): approve_carrier(), bind_vehicle_driver(), create_carrier(), create_driver(), create_personnel(), create_vehicle(), delete_carrier(), delete_driver() (+118 more)
### Community 116 - "Community 116"
Cohesion: 0.18
Nodes (27): calculate_subtotal(), _decimal_value(), delete_products_by_quote_id(), _ensure_quote_parent_scope(), _get_field(), get_quote_product(), insert_quote_product(), list_products_by_quote_ids() (+19 more)
Cohesion: 0.19
Nodes (26): batch_insert_quote_products(), calculate_subtotal(), _decimal_value(), _ensure_quote_parent_scope(), _get_field(), get_quote_product(), insert_quote_product(), list_products_by_quote_ids() (+18 more)
### Community 117 - "get_web_auth_menu_codes"
Cohesion: 0.50
Nodes (4): 简易搜索:计划编号 / 客户(供应商) / 车牌 / 承运商名称。, unsettled_plans_q_filter(), 未结算计划简易搜索:q 应覆盖计划编号/客户/车牌/承运商名称。, test_unsettled_plans_q_filter_includes_carrier_and_plate()
### Community 118 - "Community 118"
Cohesion: 0.20
Nodes (24): batch_insert_contract_products(), calculate_subtotal(), _decimal_value(), delete_price_tiers_by_contract_id(), delete_products_by_contract_id(), _get_field(), insert_contract_product(), _letter_oss_ids_to_json() (+16 more)
Cohesion: 0.15
Nodes (29): batch_insert_contract_products(), calculate_subtotal(), _decimal_value(), delete_price_tiers_by_contract_id(), delete_products_by_contract_id(), _get_field(), insert_contract_product(), _letter_oss_ids_to_json() (+21 more)
### Community 119 - "Community 119"
Cohesion: 0.16
Nodes (28): CertRow, _cert_row_complete(), collect_required_cert_errors(), _find_cert_for_required(), get_required_personnel_cert_names(), match_required_cert_name(), _missing_field_labels(), Session (+20 more)
Cohesion: 0.12
Nodes (37): CertRow, _cert_row_complete(), collect_required_cert_errors(), _find_cert_for_required(), get_required_personnel_cert_names(), is_huolala_trailer_no(), match_required_cert_name(), _missing_field_labels() (+29 more)
### Community 120 - "sqlalchemy_exception_handler"
Cohesion: 0.21
Nodes (6): _db_pool_keepalive_loop(), lifespan(), 定期 ping MySQL,避免远程库/NAT 回收空闲 TCP 后首请求长时间卡住。, init_scheduled_tasks(), shutdown_scheduler(), 免登车牌 options 接口入参校验(不依赖真实 DB 数据)。
### Community 121 - "Session"
Cohesion: 0.09
Nodes (27): API 路由包 — 山峡化工生产一体化门户, create_leave(), delete_leave(), get_leave(), _leave_local_callback(), leave_workflow_callback(), list_leaves(), Session (+19 more)
### Community 122 - "Community 122"
Cohesion: 0.39
Nodes (10): create_post(), get_post(), list_posts(), post_code_exists(), Any, Session, _row_to_dict(), soft_delete_post() (+2 more)
### Community 125 - "Community 125"
Cohesion: 0.24
Nodes (7): camel_to_snake(), CRUDBase, get_model_attr(), HistoryCRUD, Any, Session, ModelType
### Community 123 - "list_product_options"
Cohesion: 0.67
Nodes (3): list_product_options(), 产品下拉 options:keyword 按产品名称/拼音首字母模糊匹配,不匹配产品编号。, test_list_product_options_keyword_only_filters_product_name()
### Community 126 - "customer_pool.py"
Cohesion: 0.36
@@ -689,8 +725,8 @@ Cohesion: 0.22
Nodes (15): get_storage(), download_file_bytes(), file_stream(), get_access_url(), get_download_url(), get_preview_url(), normalize_storage_path(), 报销附件存储适配 — 复用门户 app.core.storage,对外保持 bx 协议。 (+7 more)
### Community 141 - "PlTransport"
Cohesion: 0.19
Nodes (23): approve_task(), _assert_wf_instance_in_scope(), _business_type_label(), create_process_def(), delete_process_def(), _enrich_task_list(), get_business_all_tasks(), _get_business_form_fields() (+15 more)
Cohesion: 0.18
Nodes (25): approve_task(), _assert_wf_instance_in_scope(), _business_type_label(), create_process_def(), delete_process_def(), _enrich_task_list(), get_business_all_tasks(), _get_business_form_fields() (+17 more)
### Community 144 - "dict_data.py"
Cohesion: 0.27
@@ -698,23 +734,19 @@ Nodes (11): create_dict_data(), get_dict_data(), list_dict_data(), list_dict_dat
### Community 145 - "resolve_crm_product"
Cohesion: 0.07
Nodes (62): _build_page(), _get_by_token(), _make_handlers(), _models(), Session, 运输计划 · 入库/出库单现场免登录公共 API, PlInboundOrder, PlInboundOrderItem (+54 more)
Nodes (56): _build_page(), _get_by_token(), _models(), Session, 运输计划 · 入库/出库单现场免登录公共 API, BizAttachment, PlDriver, PlInboundOrder (+48 more)
### Community 147 - "Community 147"
Cohesion: 0.37
Nodes (17): check_env(), check_python(), create_venv(), init_env(), install_dependencies(), log_error(), log_info(), log_success() (+9 more)
### Community 154 - "response.py"
Cohesion: 0.24
Nodes (5): MinIOStorage, Any, 从 MINIO_BASE_URL 解析公网 endpoint;未配置或与内网相同时复用 self.client。, MinIO 预签名下载 URL,携带原始文件名。, MinIO 预签名内联预览 URL(图片新标签页 / PDF iframe 等)。 上传时 put_object 未写 content_typ
Cohesion: 0.19
Nodes (7): _guess_preview_content_type(), MinIOStorage, Any, 从 MINIO_BASE_URL 解析公网 endpoint;未配置或与内网相同时复用 self.client。, 按对象键后缀推断内联预览 Content-Type(纠正上传时未写入 MIME 的情况)。, MinIO 预签名下载 URL,携带原始文件名。, MinIO 预签名内联预览 URL(图片新标签页 / PDF iframe 等)。 上传时 put_object 未写 content_typ
### Community 159 - "customer_update"
Cohesion: 0.18
Nodes (17): get_web_auth_menu_codes(), 获取当前用户菜单权限标识(sys_menu.perms,经角色继承)。, get_user_menu_codes(), Session, 读取用户授权的菜单权限标识集合(与前端 meta.menuCode / perms 对齐)。, _expand_with_ancestor_menu_ids(), _granted_menu_ids_for_user(), iter_user_menu_perm_codes() (+9 more)
### Community 160 - "register_local_callback"
Cohesion: 0.09
Nodes (27): API 路由包 — 山峡化工生产一体化门户, create_leave(), delete_leave(), get_leave(), _leave_local_callback(), leave_workflow_callback(), list_leaves(), Session (+19 more)
Cohesion: 0.16
Nodes (19): get_web_auth_menu_codes(), 获取当前用户菜单权限标识(sys_menu.perms,经角色继承)。, get_user_menu_codes(), Session, 读取用户授权的菜单权限标识集合(与前端 meta.menuCode / perms 对齐)。, _expand_with_ancestor_menu_ids(), _granted_menu_ids_for_user(), iter_user_menu_perm_codes() (+11 more)
### Community 162 - ".to_tenant_user_dict"
Cohesion: 0.42
@@ -725,32 +757,20 @@ Cohesion: 0.09
Nodes (46): _avatar_url_field(), _build_user_info_payload(), change_current_user_password(), get_current_user_profile(), _jwt_user_payload(), logout(), public_quote_share(), Request (+38 more)
### Community 170 - "test_quote_approver_setting.py"
Cohesion: 0.15
Nodes (3): BaseStorage, OSSStorage, 浏览器内联预览 URL;默认实现为普通访问地址。
### Community 171 - "minio_url.py"
Cohesion: 0.09
Nodes (34): get_current_time(), parse_datetime(), datetime, get_pool_record(), list_pool_records(), Any, Session, _row_to_dict() (+26 more)
Cohesion: 0.22
Nodes (3): BaseStorage, 带下载文件名的访问 URL;默认实现为普通访问地址。, 浏览器内联预览 URL;默认实现为普通访问地址。
### Community 173 - "resolve_create_time_bounds"
Cohesion: 0.08
Nodes (71): _build_driver_url(), get_plan_arrange_info(), 获取计划已安排运输后的司机链接与地磅出入库关联(供查看弹窗复制简述/二维码)。, _apply_plan_logistics_from_payload(), _apply_transport_arrange_fields(), arrange_transport(), _assert_vehicle_carrier_eligible(), _build_arrange_result() (+63 more)
### Community 174 - "register_local_callback"
Cohesion: 0.06
Nodes (58): 列表展示用:优先 CRM 标准名(详情仍返回库内 product_name,以便编辑反推别名)。, _resolve_list_product_name(), 返回 (作废, 归档)。现场链接实际走入库/出库单公共页。, _resolve_io_order_link_flags(), _build_public_page(), _build_site_product_read(), _format_file_size(), get_public_site() (+50 more)
Cohesion: 0.14
Nodes (33): _apply_plan_logistics_from_payload(), _apply_transport_arrange_fields(), arrange_transport(), _assert_vehicle_carrier_eligible(), create_transport(), _ensure_driver_access_token(), generate_qrcode(), _generate_transport_no() (+25 more)
### Community 179 - "test_pl_vehicle_personnel_photo_schema.py"
Cohesion: 0.27
Nodes (16): add_favorite(), count_active(), find_active(), find_soft_deleted(), list_favorites(), _next_order_num(), normalize_menu_path(), Any (+8 more)
### Community 182 - "statistics.py"
Cohesion: 0.45
Nodes (11): _customer_base_query(), get_follow_statistics(), get_industry_statistics(), get_level_statistics(), get_overview_statistics(), get_owner_statistics(), get_source_statistics(), get_trend_statistics() (+3 more)
### Community 188 - "get_client_ip"
Cohesion: 0.30
Nodes (10): get_last_month_range(), month_range(), date, Session, 地磅计划按月自动生成 — 迁移自 simple-dibang-xiaoshou/auto-create-plan/auto_create_monthly_pla, 按月自动生成采购计划。 - 默认仅在每月 1 日执行(force=True 时跳过该限制,供手动触发验收) - 遍历所有租户,按上月计划模板生, run_auto_generate_monthly_plans(), default_execution_status() (+2 more)
Cohesion: 0.24
Nodes (12): get_last_month_range(), month_range(), date, Session, 地磅计划按月自动生成 — 迁移自 simple-dibang-xiaoshou/auto-create-plan/auto_create_monthly_pla, 按月自动生成采购计划。 - 默认仅在每月 1 日执行(force=True 时跳过该限制,供手动触发验收) - 遍历所有租户,按上月计划模板生, run_auto_generate_monthly_plans(), default_execution_status() (+4 more)
### Community 206 - "crm_quote_pdf.py"
Cohesion: 0.36
@@ -761,8 +781,8 @@ Cohesion: 0.18
Nodes (10): 1. 创建虚拟环境, 2. 安装依赖, 3. 配置环境变量, 4. 启动服务, API 路由分组, shanxia2.0 后端骨架(FastAPI), 开发规范, 快速开始 (+2 more)
### Community 220 - "api_crypto_middleware.py"
Cohesion: 0.15
Nodes (22): ApiCryptoMiddleware, _claims_from_auth(), _coerce_post_method_override_to_get(), _get_redis_client(), _is_crypto_api(), _json_error_response(), _mark_nonce_once(), _nonce_key() (+14 more)
Cohesion: 0.14
Nodes (23): ApiCryptoMiddleware, _claims_from_auth(), _coerce_post_method_override_to_get(), _get_redis_client(), _is_crypto_api(), _json_error_response(), _mark_nonce_once(), _nonce_key() (+15 more)
### Community 237 - "order_status_compat.py"
Cohesion: 0.36
@@ -784,17 +804,17 @@ Nodes (6): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `generate_uuid()` connect `Community 109` to `Community 0`, `Community 4`, `Community 5`, `Community 6`, `Community 134`, `Community 8`, `statistics.py`, `Community 136`, `Community 11`, `Community 14`, `Community 15`, `dict_data.py`, `Community 17`, `Community 18`, `resolve_crm_product`, `Community 16`, `Community 21`, `Community 24`, `Community 28`, `Community 29`, `Community 32`, `get_client_ip`, `Community 36`, `Community 39`, `Community 40`, `Community 41`, `minio_url.py`, `Community 43`, `resolve_create_time_bounds`, `Community 47`, `Community 50`, `Community 56`, `Community 57`, `Community 58`, `get_client_ip`, `Community 61`, `Community 65`, `Community 66`, `Community 67`, `Community 68`, `Community 71`, `Community 72`, `Community 83`, `allocate_next_biz_no`, `Community 86`, `Community 87`, `pl_ocr_service.py`, `pl_vehicle_recharge_service.py`, `Community 92`, `Community 95`, `__init__.py`, `apply_crm_create_by_scope`, `apply_pl_scope`, `quote_share_log.py`, `db_models.py`, `site.py`, `Community 116`, `Community 118`, `Community 122`, `customer_pool.py`?**
_High betweenness centrality (0.114) - this node is a cross-community bridge._
- **Why does `BaseModel` connect `Community 12` to `Community 0`, `Community 1`, `Community 2`, `Community 3`, `Community 5`, `Community 7`, `Community 11`, `Community 13`, `Community 14`, `Community 15`, `Community 16`, `Community 18`, `Community 19`, `Community 24`, `Community 26`, `Community 27`, `Community 28`, `Community 31`, `register_local_callback`, `Community 40`, `Community 43`, `Community 44`, `Community 45`, `resolve_create_time_bounds`, `Community 51`, `Community 53`, `Community 54`, `Community 58`, `Community 60`, `Community 61`, `Community 62`, `Community 63`, `Community 65`, `Community 66`, `Community 71`, `Community 74`, `customer_follow.py`, `pl_internal_customer.py`, `Community 109`, `Community 111`, `site.py`, `Community 119`?**
- **Why does `generate_uuid()` connect `Community 57` to `Community 4`, `Community 5`, `Community 6`, `Community 134`, `Community 8`, `statistics.py`, `Community 136`, `Community 10`, `Community 11`, `Community 15`, `Community 16`, `dict_data.py`, `Community 17`, `resolve_crm_product`, `Community 21`, `Community 24`, `Community 25`, `Community 26`, `Community 28`, `Community 29`, `Community 32`, `Community 33`, `Community 34`, `get_client_ip`, `Community 36`, `Community 39`, `Community 40`, `Community 41`, `Community 43`, `Community 44`, `resolve_create_time_bounds`, `Community 47`, `Community 51`, `Community 55`, `get_client_ip`, `Community 61`, `Community 64`, `Community 65`, `Community 66`, `Community 67`, `Community 68`, `Community 72`, `apply_crm_create_by_scope`, `resolve_crm_product`, `Community 83`, `allocate_next_biz_no`, `Community 87`, `CrmProduct`, `pl_ocr_service.py`, `Community 92`, `tenant.py`, `apply_crm_create_by_scope`, `site.py`, `apply_pl_scope`, `db_models.py`, `site.py`, `Community 116`, `Community 118`, `Community 122`, `customer_pool.py`?**
_High betweenness centrality (0.123) - this node is a cross-community bridge._
- **Why does `BaseModel` connect `Community 12` to `Community 0`, `Community 1`, `Community 2`, `Community 3`, `Community 5`, `Community 7`, `Community 11`, `PlTransport`, `Community 14`, `Community 13`, `Community 15`, `resolve_crm_product`, `Community 18`, `Community 19`, `Community 16`, `Community 24`, `Community 26`, `Community 27`, `Community 31`, `Community 40`, `resolve_create_time_bounds`, `Community 45`, `Community 47`, `Community 51`, `Community 53`, `Community 54`, `Community 60`, `Community 63`, `Community 64`, `Community 65`, `Community 71`, `Community 74`, `opportunity_follow.py`, `customer_follow.py`, `pl_internal_customer.py`, `pool_customer_assign`, `Community 111`, `quote_share_log.py`, `site.py`, `Community 119`, `Session`?**
_High betweenness centrality (0.105) - this node is a cross-community bridge._
- **Why does `Settings` connect `Community 46` to `_ensure_subpack_slip_no`, `Community 35`, `Community 67`, `Community 37`, `Community 38`, `price_letter_detail`, `Community 73`, `Community 42`, `Community 10`, `Community 25`, `Community 59`, `Community 30`?**
_High betweenness centrality (0.084) - this node is a cross-community bridge._
- **Why does `Settings` connect `price_letter_detail` to `_ensure_subpack_slip_no`, `Community 35`, `Community 67`, `Community 37`, `Community 38`, `Community 73`, `Community 42`, `Community 46`, `Community 56`, `Community 58`, `Community 59`, `Community 30`?**
_High betweenness centrality (0.093) - this node is a cross-community bridge._
- **Are the 8 inferred relationships involving `DbUserContext` (e.g. with `InventoryRelatedData` and `AutoGenerateMonthlyResult`) actually correct?**
_`DbUserContext` has 8 INFERRED edges - model-reasoned connections that need verification._
- **Are the 247 inferred relationships involving `ValueError` (e.g. with `.presigned_download_url()` and `.presigned_preview_url()`) actually correct?**
_`ValueError` has 247 INFERRED edges - model-reasoned connections that need verification._
- **What connects `API 路由包 — 山峡化工生产一体化门户`, `返回运营端角色下拉选项(label=库中角色名,value=Web 角色码)。`, `返回用户下拉选项(label=昵称,value=用户ID,附带 nickName/userNo)。 供申请人/审批人等选择场景使用,返回本租户全部有效` to the rest of the system?**
_569 weakly-connected nodes found - possible documentation gaps or missing edges._
_573 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 1` be split into smaller, more focused modules?**
_Cohesion score 0.07804878048780488 - nodes in this community are weakly interconnected._
_Cohesion score 0.08367071524966262 - nodes in this community are weakly interconnected._
- **Should `Community 3` be split into smaller, more focused modules?**
_Cohesion score 0.0830188679245283 - nodes in this community are weakly interconnected._
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_py", "label": "test_duty_schedule.py", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_parse_hhmm", "label": "test_parse_hhmm()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L38"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_parse_hhmm_invalid_raises", "label": "test_parse_hhmm_invalid_raises()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L45"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_format_time_label_same_day", "label": "test_format_time_label_same_day()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L50"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_format_time_label_cross_day", "label": "test_format_time_label_cross_day()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L54"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_format_time_label_empty", "label": "test_format_time_label_empty()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L58"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_work_plans_join_split", "label": "test_work_plans_join_split()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L62"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_build_dict_value_from_label", "label": "test_build_dict_value_from_label()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L68"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_ensure_dict_data_reuses_existing_label", "label": "test_ensure_dict_data_reuses_existing_label()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L74"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_ensure_dict_data_inserts_new_without_commit", "label": "test_ensure_dict_data_inserts_new_without_commit()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L88"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_normalize_person_name_ok", "label": "test_normalize_person_name_ok()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L108"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_normalize_person_name_empty", "label": "test_normalize_person_name_empty()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L112"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_normalize_person_name_too_long", "label": "test_normalize_person_name_too_long()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L117"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_shift_labels", "label": "test_shift_labels()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L122"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_grab_rejects_when_full", "label": "test_grab_rejects_when_full()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L126"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_grab_allows_custom_shift_type_when_slot_exists", "label": "test_grab_allows_custom_shift_type_when_slot_exists()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L188"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_grab_rejects_when_closed", "label": "test_grab_rejects_when_closed()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L256"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_grab_rejects_invalid_user", "label": "test_grab_rejects_invalid_user()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L274"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_grab_rejects_duplicate_same_shift", "label": "test_grab_rejects_duplicate_same_shift()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L303"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_save_day_rejects_shrink_below_grab_count", "label": "test_save_day_rejects_shrink_below_grab_count()Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_py", "label": "test_duty_schedule.py", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_parse_hhmm", "label": "test_parse_hhmm()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L38"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_format_time_label_same_day", "label": "test_format_time_label_same_day()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L45"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_format_time_label_cross_day", "label": "test_format_time_label_cross_day()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L49"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_format_time_label_empty", "label": "test_format_time_label_empty()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L53"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_work_plans_join_split", "label": "test_work_plans_join_split()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L57"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_build_dict_value_from_label", "label": "test_build_dict_value_from_label()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L63"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_ensure_dict_data_reuses_existing_label", "label": "test_ensure_dict_data_reuses_existing_label()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L69"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_ensure_dict_data_inserts_new_without_commit", "label": "test_ensure_dict_data_inserts_new_without_commit()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L83"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_normalize_person_name_ok", "label": "test_normalize_person_name_ok()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L103"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_normalize_person_name_empty", "label": "test_normalize_person_name_empty()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L107"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_normalize_person_name_too_long", "label": "test_normalize_person_name_too_long()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L112"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_shift_labels", "label": "test_shift_labels()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L117"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_grab_rejects_when_full", "label": "test_grab_rejects_when_full()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L121"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_grab_rejects_when_closed", "label": "test_grab_rejects_when_closed()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L183"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_grab_rejects_invalid_user", "label": "test_grab_rejects_invalid_user()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L201"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_grab_rejects_duplicate_same_shift", "label": "test_grab_rejects_duplicate_same_shift()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L230"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_save_day_rejects_shrink_below_grab_count", "label": "test_save_day_rejects_shrink_below_grab_count()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L296"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_resolve_duty_staff_role_id_by_fixed_id", "label": "test_resolve_duty_staff_role_id_by_fixed_id()", "file_type": "code", "source_file": "tests/test_duty_schedule.py", "source_location": "L370"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_duty_schedule_test_list_duty_staff_users_empty_when_no_role", "label": "test_list_duty_staff_users_emptyLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_py", "label": "plan.py", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductbase", "label": "PlanProductBase", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L16"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductcreate", "label": "PlanProductCreate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L55"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductupdate", "label": "PlanProductUpdate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L59"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planarchivebrandqtyitem", "label": "PlanArchiveBrandQtyItem", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L81"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductread", "label": "PlanProductRead", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L88"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planbase", "label": "PlanBase", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L105"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plancontractlink", "label": "PlanContractLink", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L155"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plancreate", "label": "PlanCreate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L161"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plantransportcreateblock", "label": "PlanTransportCreateBlock", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L176"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planupdate", "label": "PlanUpdate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L190"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_hazardcertreadybody", "label": "HazardCertReadyBody", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L239"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planread", "label": "PlanRead", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L244"}, {"id": "planresourcesnapshotmixin", "label": "PlanResourceSnapshotMixin", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planwithproducts", "label": "PlanWithProducts", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L278"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planinboundsliptemplatesave", "label": "PlanInboundSlipTemplateSave", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L282"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plandeliverysliptemplatesave", "label": "PlanDeliverySlipTemplateSave", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L287"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plansubpacksliptemplatesave", "label": "PlanSubpackSlipTemplateSave", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L294"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_createfromcontractrequest", "label": "CreateFromContractRequest", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L306"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plancreatefull", "label": "PlanCreateFull", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L317"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planlistquery", "label": "PlanListQuery", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L328"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductcertitem", "label": "PlanProductCertItem", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L353"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductcertsupdate", "label": "PlanProductCertsUpdate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L361"}, {"id": "users_wuzilong_work_00code_sLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_py", "label": "pl_menu_badge_service.py", "file_type": "code", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_count_pending_applies", "label": "_count_pending_applies()", "file_type": "code", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L46"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_menu_badge_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_count_document_reminder_expired_pending", "label": "count_document_reminder_expired_pending()", "file_type": "code", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L56"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_count_freight_approval_pending", "label": "count_freight_approval_pending()", "file_type": "code", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L69"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_count_plan_cert_pending", "label": "count_plan_cert_pending()", "file_type": "code", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L88"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_count_plan_archive_pending", "label": "count_plan_archive_pending()", "file_type": "code", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L122"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_count_plan_contract_gaps", "label": "count_plan_contract_gaps()", "file_type": "code", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L133"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_count_freight_disburse_pending", "label": "count_freight_disburse_pending()", "file_type": "code", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L146"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_build_menu_badge_counts", "label": "build_menu_badge_counts()", "file_type": "code", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L156"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_py", "target": "collections", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_py", "target": "app_models_pl_models", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L9", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_py", "target": "app_services_pl_cert_service", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L18", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_py", "target": "app_services_pl_data_scope", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L19", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_py", "target": "app_services_pl_freight_settlement_service", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_menu_badge_service.py", "source_location": "L20", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_menu_badge_service_py", "target": "app_services_pl_plan_service", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_menu_badge_service.pLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_py", "label": "plan.py", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_resolve_list_product_name", "label": "_resolve_list_product_name()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L106"}, {"id": "plplanproduct", "label": "PlPlanProduct", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_resolve_create_date", "label": "_resolve_create_date()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L122"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_generate_plan_no", "label": "_generate_plan_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L126"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_format_subpack_slip_no", "label": "_format_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L158"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_normalize_subpack_slip_no", "label": "_normalize_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L162"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_subpack_slip_no_seq", "label": "_subpack_slip_no_seq()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L187"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_ensure_subpack_slip_no", "label": "_ensure_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L195"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_determine_plan_type", "label": "_determine_plan_type()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L227"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_get_plan_type_label", "label": "_get_plan_type_label()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L232"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_generate_notify_message", "label": "_generate_notify_message()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L241"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_plan_product_name_exists", "label": "_plan_product_name_exists()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L261"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_plan_plate_match", "label": "_plan_plate_match()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L275"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_list_plans", "label": "list_plans()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L285"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "planlistresponse", "label": "PlanListResponse", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_count_plans_by_contract", "label": "count_plans_by_contract()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L467"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_list_contract_gaps", "label": "list_contract_gaps()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L516"}, {"id": "plancontractgaplistresponse", "label": "PlanContractGapListResponse", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_put_plan_side_contracts", "label": "put_plan_side_contracts()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_py", "label": "pl_paste_recognize_service.py", "file_type": "code", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_conflict_map", "label": "_conflict_map()", "file_type": "code", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L22"}, {"id": "conflictaction", "label": "ConflictAction", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_paste_recognize_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_find_personnel_by_id_card", "label": "_find_personnel_by_id_card()", "file_type": "code", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L34"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_paste_recognize_service.py"}, {"id": "plpersonnel", "label": "PlPersonnel", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_paste_recognize_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_find_vehicle_by_plate", "label": "_find_vehicle_by_plate()", "file_type": "code", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L51"}, {"id": "plvehicle", "label": "PlVehicle", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_paste_recognize_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_get_personnel", "label": "_get_personnel()", "file_type": "code", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L68"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_get_vehicle", "label": "_get_vehicle()", "file_type": "code", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L85"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_resolve_personnel", "label": "_resolve_personnel()", "file_type": "code", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L100"}, {"id": "pastepersonnelin", "label": "PastePersonnelIn", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_paste_recognize_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_resolve_vehicle", "label": "_resolve_vehicle()", "file_type": "code", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L154"}, {"id": "pastevehiclein", "label": "PasteVehicleIn", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_paste_recognize_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_commit_paste_recognize", "label": "commit_paste_recognize()", "file_type": "code", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L229"}, {"id": "pastecommitbody", "label": "PasteCommitBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_paste_recognize_service.py"}, {"id": "pastecommitresult", "label": "PasteCommitResult", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_paste_recognize_service.py"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_py", "target": "fastapi", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L6", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_paste_recognize_service_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_paste_recognize_service.py", "source_location": "L7", "weight"Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_py", "label": "duty_models.py", "file_type": "code", "source_file": "app/models/duty_models.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyday", "label": "DutyDay", "file_type": "code", "source_file": "app/models/duty_models.py", "source_location": "L31"}, {"id": "base", "label": "Base", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyslot", "label": "DutySlot", "file_type": "code", "source_file": "app/models/duty_models.py", "source_location": "L51"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyassignment", "label": "DutyAssignment", "file_type": "code", "source_file": "app/models/duty_models.py", "source_location": "L75"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_rationale_1", "label": "\u503c\u73ed\u6392\u73ed\u6a21\u578b \u2014 docs/superpowers/specs/2026-08-14-duty-schedule-design.md", "file_type": "rationale", "source_file": "app/models/duty_models.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/models/duty_models.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_py", "target": "sqlalchemy", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/models/duty_models.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_py", "target": "app_core_database", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/models/duty_models.py", "source_location": "L6", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyday", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/models/duty_models.py", "source_location": "L31", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyday", "target": "base", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/models/duty_models.py", "source_location": "L31", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyslot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/models/duty_models.py", "source_location": "L51", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyslot", "target": "base", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/models/duty_models.py", "source_location": "L51", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyassignment", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/models/duty_models.py", "source_location": "L75", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyassignment", "target": "base", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/models/duty_models.py", "source_location": "L75", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_rationale_1", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "app/models/duty_models.py", "source_location": "L1", "weight": 1.0}], "raw_calls": [], "callable_nids": ["users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyassignment", "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyday", "users_wuzilong_work_00code_shanxia2_0_backend_app_models_duty_models_dutyslot"]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_py", "label": "plan.py", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_resolve_list_product_name", "label": "_resolve_list_product_name()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L104"}, {"id": "plplanproduct", "label": "PlPlanProduct", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_resolve_create_date", "label": "_resolve_create_date()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L120"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_generate_plan_no", "label": "_generate_plan_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L124"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_format_subpack_slip_no", "label": "_format_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L156"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_normalize_subpack_slip_no", "label": "_normalize_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L160"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_subpack_slip_no_seq", "label": "_subpack_slip_no_seq()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L185"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_ensure_subpack_slip_no", "label": "_ensure_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L193"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_determine_plan_type", "label": "_determine_plan_type()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L225"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_get_plan_type_label", "label": "_get_plan_type_label()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L230"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_generate_notify_message", "label": "_generate_notify_message()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L239"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_plan_product_name_exists", "label": "_plan_product_name_exists()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L259"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_plan_plate_match", "label": "_plan_plate_match()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L273"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_list_plans", "label": "list_plans()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L283"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "planlistresponse", "label": "PlanListResponse", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_count_plans_by_contract", "label": "count_plans_by_contract()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L465"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_list_contract_gaps", "label": "list_contract_gaps()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L514"}, {"id": "plancontractgaplistresponse", "label": "PlanContractGapListResponse", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_create_plan", "label": "create_plan()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L560"}, {"id": "plancreLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_py", "label": "pl_cert_required_service.py", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_is_huolala_trailer_no", "label": "is_huolala_trailer_no()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L37"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_should_show_trailer_cert_slots", "label": "should_show_trailer_cert_slots()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L41"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_should_require_trailer_certs", "label": "should_require_trailer_certs()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L46"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_match_required_cert_name", "label": "match_required_cert_name()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L52"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_cert_row_complete", "label": "_cert_row_complete()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L61"}, {"id": "certrow", "label": "CertRow", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_cert_required_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_find_cert_for_required", "label": "_find_cert_for_required()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L77"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_missing_field_labels", "label": "_missing_field_labels()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L97"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_collect_required_cert_errors", "label": "collect_required_cert_errors()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L116"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_get_required_personnel_cert_names", "label": "get_required_personnel_cert_names()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L141"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_validate_personnel_type_role_compat", "label": "validate_personnel_type_role_compat()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L152"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_validate_personnel_required_certs", "label": "validate_personnel_required_certs()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L163"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_validate_self_vehicle_required_certs", "label": "validate_self_vehicle_required_certs()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L187"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_validate_carrier_vehicle_required_certs", "label": "validate_carrier_vehicle_required_certs()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L211"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_validate_vehicle_personnel_affiliation", "label": "validate_vehicle_personnel_affiliation()", "file_type": "code", "source_file": "app/services/pl_cert_required_service.py", "source_location": "L221"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_cert_required_service.py"}, {"id": "vehiclepersonnelrow", "label": "VehiclePersonnelRow", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_cert_required_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_cert_required_service_validate_vehicle_personnel_cert_links", "label": "validate_vehicle_personnel_cert_links()", "file_type": "code", "source_file": "app/services/pl_certLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_py", "label": "base_data.py", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_carrierbase", "label": "CarrierBase", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L19"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_carrierqualificationbase", "label": "CarrierQualificationBase", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L27"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_carriercreate", "label": "CarrierCreate", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L45"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_carrierupdate", "label": "CarrierUpdate", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L49"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_carrierauditreject", "label": "CarrierAuditReject", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L71"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_carrierread", "label": "CarrierRead", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L77"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_carrier_to_read", "label": "carrier_to_read()", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L96"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/schemas/pl/base_data.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_vehiclepersonnelrow", "label": "VehiclePersonnelRow", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L132"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_certrow", "label": "CertRow", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L143"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_certread", "label": "CertRead", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L158"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_vehiclebase", "label": "VehicleBase", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L174"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_vehiclecreate", "label": "VehicleCreate", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L190"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_vehicleupdate", "label": "VehicleUpdate", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L197"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_vehicleread", "label": "VehicleRead", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L214"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_vehicledetailread", "label": "VehicleDetailRead", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L229"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_personnelbase", "label": "PersonnelBase", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L240"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_personnelcreate", "label": "PersonnelCreate", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L264"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_personnelupdate", "label": "PersonnelUpdate", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L269"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_personnelread", "label": "PersonnelRead", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L287"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_personneldetailread", "label": "PersonnelDetailRead", "file_type": "code", "source_file": "app/schemas/pl/base_data.py", "source_location": "L302"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_base_data_driverbase", "label": "DriverBase", "file_type": "code", "sLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_py", "label": "paste_recognize.py", "file_type": "code", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_conflictaction", "label": "ConflictAction", "file_type": "code", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L7"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_pastepersonnelin", "label": "PastePersonnelIn", "file_type": "code", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L15"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_pastevehiclein", "label": "PasteVehicleIn", "file_type": "code", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L24"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_pastecommitbody", "label": "PasteCommitBody", "file_type": "code", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L34"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_pastecommitresult", "label": "PasteCommitResult", "file_type": "code", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L45"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_rationale_1", "label": "\u8fd0\u8f93\u8ba1\u5212 - \u4e09\u65b9\u7c98\u8d34\u8bc6\u522b\u63d0\u4ea4 Schema", "file_type": "rationale", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_py", "target": "pydantic", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_conflictaction", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_conflictaction", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_pastepersonnelin", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L15", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_pastepersonnelin", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L15", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_pastevehiclein", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L24", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_pastevehiclein", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L24", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_pastecommitbody", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L34", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_pastecommitbody", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/paste_recognize.py", "source_location": "L34", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_paste_recognize_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_apLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "label": "pl_plan_contract_gap.py", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_compute_contract_gap_sides", "label": "compute_contract_gap_sides()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L23"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_side_is_exempt", "label": "side_is_exempt()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L38"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_side_contract_exists", "label": "_side_contract_exists()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L42"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_supplier_gap_expr", "label": "_supplier_gap_expr()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L53"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_demander_gap_expr", "label": "_demander_gap_expr()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L66"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_plan_has_contract_gap_exists", "label": "plan_has_contract_gap_exists()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L79"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_build_plan_contract_gap_read", "label": "build_plan_contract_gap_read()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L84"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "plancontractlink", "label": "PlanContractLink", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "plancontractgapread", "label": "PlanContractGapRead", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_list_plan_contract_gaps", "label": "list_plan_contract_gaps()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L113"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_rationale_80", "label": "\u5217\u8868\u7b5b\u9009\uff1a\u8ba1\u5212\u5b58\u5728\u5408\u540c\u7f3a\u53e3\uff08\u975e\u5185\u7f6e\u4fa7\u7f3a\u5c11\u6709\u6548 pl_plan_contract \u5173\u8054\uff09\u3002", "file_type": "rationale", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L80"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "target": "sqlalchemy", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L6", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "targLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_py", "label": "test_pl_plan_contract_gap.py", "file_type": "code", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_both_external_no_contracts_gaps_both", "label": "test_both_external_no_contracts_gaps_both()", "file_type": "code", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L9"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_internal_supplier_external_demander_missing_only_demander", "label": "test_internal_supplier_external_demander_missing_only_demander()", "file_type": "code", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L19"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_both_internal_no_gap", "label": "test_both_internal_no_gap()", "file_type": "code", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L29"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_external_with_contracts_no_gap", "label": "test_external_with_contracts_no_gap()", "file_type": "code", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L39"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_empty_names_treated_as_non_internal_gap_both", "label": "test_empty_names_treated_as_non_internal_gap_both()", "file_type": "code", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L49"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_compile", "label": "_compile()", "file_type": "code", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L59"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_plan_has_contract_gap_exists_mentions_contract_and_internal_names", "label": "test_plan_has_contract_gap_exists_mentions_contract_and_internal_names()", "file_type": "code", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L65"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_py", "target": "sqlalchemy_dialects", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_py", "target": "app_services_pl_internal_customer", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_py", "target": "app_services_pl_plan_contract_gap", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_both_external_no_contracts_gaps_both", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L9", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_internal_supplier_external_demander_missing_only_demander", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L19", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_both_internal_no_gap", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L29", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_external_with_contracts_no_gap", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_plan_contract_gap.py", "source_location": "L39", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_plan_contract_gap_test_empty_names_treated_as_non_internal_gap_both", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_plLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_py", "label": "duty.py", "file_type": "code", "source_file": "app/schemas/duty.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutyassignmentin", "label": "DutyAssignmentIn", "file_type": "code", "source_file": "app/schemas/duty.py", "source_location": "L13"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutyslotsavein", "label": "DutySlotSaveIn", "file_type": "code", "source_file": "app/schemas/duty.py", "source_location": "L19"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutyslotsavein_split_work_plans", "label": "._split_work_plans()", "file_type": "code", "source_file": "app/schemas/duty.py", "source_location": "L31"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutydaysavebody", "label": "DutyDaySaveBody", "file_type": "code", "source_file": "app/schemas/duty.py", "source_location": "L39"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutylinkbody", "label": "DutyLinkBody", "file_type": "code", "source_file": "app/schemas/duty.py", "source_location": "L45"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutygrabbody", "label": "DutyGrabBody", "file_type": "code", "source_file": "app/schemas/duty.py", "source_location": "L49"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutydictensurebody", "label": "DutyDictEnsureBody", "file_type": "code", "source_file": "app/schemas/duty.py", "source_location": "L54"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_rationale_1", "label": "\u503c\u73ed\u6392\u73ed Pydantic schemas", "file_type": "rationale", "source_file": "app/schemas/duty.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_py", "target": "pydantic", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutyassignmentin", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": "L13", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutyassignmentin", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": "L13", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutyslotsavein", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": "L19", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutyslotsavein", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": "L19", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutyslotsavein", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutyslotsavein_split_work_plans", "relation": "method", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": "L31", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutydaysavebody", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": "L39", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutydaysavebody", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": "L39", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_duty_dutylinkbody", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/duty.py", "source_location": Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "label": "pl_plan_contract_gap.py", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_compute_contract_gap_sides", "label": "compute_contract_gap_sides()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L27"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_side_is_exempt", "label": "side_is_exempt()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L42"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_side_contract_exists", "label": "_side_contract_exists()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L46"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_supplier_gap_expr", "label": "_supplier_gap_expr()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L57"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_demander_gap_expr", "label": "_demander_gap_expr()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L70"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_plan_has_contract_gap_exists", "label": "plan_has_contract_gap_exists()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L83"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_build_plan_contract_gap_read", "label": "build_plan_contract_gap_read()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L88"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "plancontractlink", "label": "PlanContractLink", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "planproductread", "label": "PlanProductRead", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "plancontractgapread", "label": "PlanContractGapRead", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_load_products_by_plan_ids", "label": "_load_products_by_plan_ids()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L120"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_list_plan_contract_gaps", "label": "list_plan_contract_gaps()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L141"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_update_plan_side_contracts", "label": "update_plan_side_contracts()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L228"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_rationale_84", "label": "\u5217\u8868\u7b5b\u9009\uff1a\u8ba1\u5212\u5b58\u5728\u5408\u540c\u7f3a\u53e3\uff08\u975e\u5185\u7f6e\u4fa7\u7f3a\u5c11\u6709\u6548 pl_plan_contract \u5173\u8054\uff09\u3002", "file_type": "rationale", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L84"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_rationale_238", "label": "\u5355\u4fa7\u4fdd\u5b58\u8ba1\u5212\u5408\u540c\u5173\u8054\uff1b\u5141\u8bb8\u7a7a\u5217\u8868\uff0c\u4e0d\u5f3a\u5236\u975e\u5185\u7f6e\u4fa7\u5fc5\u987b\u6709\u5408\u540c\u3002", "file_type": "rationale", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L238"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "target": "datetime", "relLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_py", "label": "test_pl_trailer_cert_rules.py", "file_type": "code", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_complete_self_head_certs", "label": "_complete_self_head_certs()", "file_type": "code", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L15"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_test_huolala_trailer_helpers", "label": "test_huolala_trailer_helpers()", "file_type": "code", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L23"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_test_self_huolala_skips_trailer_required_certs", "label": "test_self_huolala_skips_trailer_required_certs()", "file_type": "code", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L34"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_test_self_normal_trailer_requires_trailer_certs", "label": "test_self_normal_trailer_requires_trailer_certs()", "file_type": "code", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L41"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_rationale_1", "label": "\u6302\u8f66\u53f7\u5206\u6d41\uff1a\u8d27\u62c9\u62c9\u514d\u6302\u8f66\u8bc1\uff1b\u81ea\u6709\u6b63\u5e38\u6302\u8f66\u53f7\u5f3a\u5236\uff1b\u4e09\u65b9\u4e0d\u5f3a\u5236\u3002", "file_type": "rationale", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_py", "target": "pytest", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_py", "target": "fastapi", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_py", "target": "app_services_pl_cert_required_service", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_complete_self_head_certs", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L15", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_test_huolala_trailer_helpers", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L23", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_test_self_huolala_skips_trailer_required_certs", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L34", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_test_self_normal_trailer_requires_trailer_certs", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L41", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_test_self_huolala_skips_trailer_required_certs", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_complete_self_head_certs", "relation": "calls", "context": "call", "confidence": "EXTRACTED", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L36", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_test_self_normal_trailer_requires_trailer_certs", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_trailer_cert_rules_complete_self_head_certs", "relation": "calls", "context": "call", "confidence": "EXTRACTED", "source_file": "tests/test_pl_trailer_cert_rules.py", "source_location": "L44", "weight": 1.0}, {"souLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_py", "label": "duty.py", "file_type": "code", "source_file": "app/api/duty.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_enrich_assign_names", "label": "_enrich_assign_names()", "file_type": "code", "source_file": "app/api/duty.py", "source_location": "L21"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/duty.py"}, {"id": "dutydaysavebody", "label": "DutyDaySaveBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/duty.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_month_summary", "label": "month_summary()", "file_type": "code", "source_file": "app/api/duty.py", "source_location": "L39"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_day_detail", "label": "day_detail()", "file_type": "code", "source_file": "app/api/duty.py", "source_location": "L51"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_duty_user_options", "label": "duty_user_options()", "file_type": "code", "source_file": "app/api/duty.py", "source_location": "L65"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_ensure_duty_dict", "label": "ensure_duty_dict()", "file_type": "code", "source_file": "app/api/duty.py", "source_location": "L77"}, {"id": "dutydictensurebody", "label": "DutyDictEnsureBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/duty.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_save_day", "label": "save_day()", "file_type": "code", "source_file": "app/api/duty.py", "source_location": "L99"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_set_link", "label": "set_link()", "file_type": "code", "source_file": "app/api/duty.py", "source_location": "L114"}, {"id": "dutylinkbody", "label": "DutyLinkBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/duty.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_delete_assignment", "label": "delete_assignment()", "file_type": "code", "source_file": "app/api/duty.py", "source_location": "L130"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_rationale_22", "label": "\u6307\u5b9a\u7528\u6237\u65f6\u8865\u5168 personName\uff08\u7528 nick_name\uff09\u3002", "file_type": "rationale", "source_file": "app/api/duty.py", "source_location": "L22"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_rationale_69", "label": "\u503c\u73ed\u53ef\u5b89\u6392\u4eba\u5458\uff1a\u4ec5\u300c\u503c\u73ed\u4eba\u5458\u300d\u89d2\u8272\u4e0b\u7684\u542f\u7528\u7528\u6237\u3002", "file_type": "rationale", "source_file": "app/api/duty.py", "source_location": "L69"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/api/duty.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/api/duty.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_py", "target": "fastapi", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/api/duty.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/api/duty.py", "source_location": "L8", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_py", "target": "app_core_database", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/api/duty.py", "source_location": "L10", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_py", "target": "app_core_menu_guard", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/api/duty.py", "source_location": "L11", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_py", "target": "app_core_response", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/api/duty.py", "source_location": "L12", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_duty_py", "target": "app_crud"Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "label": "pl_plan_contract_gap.py", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_compute_contract_gap_sides", "label": "compute_contract_gap_sides()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L27"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_side_is_exempt", "label": "side_is_exempt()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L42"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_side_contract_exists", "label": "_side_contract_exists()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L46"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_supplier_gap_expr", "label": "_supplier_gap_expr()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L57"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_demander_gap_expr", "label": "_demander_gap_expr()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L70"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_plan_has_contract_gap_exists", "label": "plan_has_contract_gap_exists()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L83"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_build_plan_contract_gap_read", "label": "build_plan_contract_gap_read()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L88"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "plancontractlink", "label": "PlanContractLink", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "plancontractgapread", "label": "PlanContractGapRead", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_list_plan_contract_gaps", "label": "list_plan_contract_gaps()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L117"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_contract_gap.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_update_plan_side_contracts", "label": "update_plan_side_contracts()", "file_type": "code", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L202"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_rationale_84", "label": "\u5217\u8868\u7b5b\u9009\uff1a\u8ba1\u5212\u5b58\u5728\u5408\u540c\u7f3a\u53e3\uff08\u975e\u5185\u7f6e\u4fa7\u7f3a\u5c11\u6709\u6548 pl_plan_contract \u5173\u8054\uff09\u3002", "file_type": "rationale", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L84"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_rationale_212", "label": "\u5355\u4fa7\u4fdd\u5b58\u8ba1\u5212\u5408\u540c\u5173\u8054\uff1b\u5141\u8bb8\u7a7a\u5217\u8868\uff0c\u4e0d\u5f3a\u5236\u975e\u5185\u7f6e\u4fa7\u5fc5\u987b\u6709\u5408\u540c\u3002", "file_type": "rationale", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L212"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_contract_gap_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_plan_contract_gap.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuziloLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_py", "label": "duty_schedule.py", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_dutybizerror", "label": "DutyBizError", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L35"}, {"id": "valueerror", "label": "ValueError", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_parse_hhmm", "label": "parse_hhmm()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L39"}, {"id": "time", "label": "time", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_format_hhmm", "label": "format_hhmm()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L55"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_format_time_label", "label": "format_time_label()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L61"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_split_work_plans", "label": "split_work_plans()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L72"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_join_work_plans", "label": "join_work_plans()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L78"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_build_dict_value_from_label", "label": "build_dict_value_from_label()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L83"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_ensure_dict_data", "label": "ensure_dict_data()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L97"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_load_dict_label_map", "label": "_load_dict_label_map()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L164"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_normalize_person_name", "label": "normalize_person_name()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L177"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_new_access_token", "label": "new_access_token()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L186"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_build_public_url", "label": "build_public_url()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L190"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_assignment_dict", "label": "_assignment_dict()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L195"}, {"id": "dutyassignment", "label": "DutyAssignment", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_slot_dict", "label": "_slot_dict()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L204"}, {"id": "dutyslot", "label": "DutySlot", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_day_dict", "label": "_day_dict()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L234"}, {"id": "dutyday", "label": "DutyDay", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_load_day_by_datLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_py", "label": "admin.py", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_list_roles_for_select", "label": "list_roles_for_select()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L47"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/admin.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_list_users_for_select", "label": "list_users_for_select()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L79"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_list_roles_for_designer", "label": "list_roles_for_designer()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L105"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_tenantcreatebody", "label": "TenantCreateBody", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L126"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_tenantupdatebody", "label": "TenantUpdateBody", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L137"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_list_tenants", "label": "list_tenants()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L149"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_create_tenant", "label": "create_tenant()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L170"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_update_tenant", "label": "update_tenant()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L192"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_delete_tenant", "label": "delete_tenant()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L217"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_get_tenant_no", "label": "_get_tenant_no()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L234"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_usercreatebody", "label": "UserCreateBody", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L247"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_userupdatebody", "label": "UserUpdateBody", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L264"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_list_users", "label": "list_users()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L280"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_get_user_by_id", "label": "get_user_by_id()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L338"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_create_user", "label": "create_user()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L358"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_update_user", "label": "update_user()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L391"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_delete_user", "label": "delete_user()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L421"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_reset_user_password", "label": "reset_user_password()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L434"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_dicttypecreatebody", "label": "DictTypeCreateBody", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L451"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_dicttypeupdatebody", "label": "DictTypeUpdateBody", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L460"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_list_dict_types", "label": "list_dict_types()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L470"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_create_dict_type", "label": "create_dict_type()", "file_type": "code", "source_file": "app/api/admin.py", "source_location": "L486"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_admin_update_dict_type", "label": "update_dict_type()", "file_type": Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_py", "label": "duty_schedule.py", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_dutybizerror", "label": "DutyBizError", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L35"}, {"id": "valueerror", "label": "ValueError", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_parse_hhmm", "label": "parse_hhmm()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L39"}, {"id": "time", "label": "time", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_format_hhmm", "label": "format_hhmm()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L52"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_format_time_label", "label": "format_time_label()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L58"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_split_work_plans", "label": "split_work_plans()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L69"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_join_work_plans", "label": "join_work_plans()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L75"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_build_dict_value_from_label", "label": "build_dict_value_from_label()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L80"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_ensure_dict_data", "label": "ensure_dict_data()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L94"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_load_dict_label_map", "label": "_load_dict_label_map()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L161"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_normalize_person_name", "label": "normalize_person_name()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L174"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_new_access_token", "label": "new_access_token()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L183"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_build_public_url", "label": "build_public_url()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L187"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_assignment_dict", "label": "_assignment_dict()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L192"}, {"id": "dutyassignment", "label": "DutyAssignment", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_slot_dict", "label": "_slot_dict()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L201"}, {"id": "dutyslot", "label": "DutySlot", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_day_dict", "label": "_day_dict()", "file_type": "code", "source_file": "app/services/duty_schedule.py", "source_location": "L214"}, {"id": "dutyday", "label": "DutyDay", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/duty_schedule.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_duty_schedule_load_day_by_datLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "label": "__init__.py", "file_type": "code", "source_file": "app/schemas/pl/__init__.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_init_rationale_1", "label": "\u8fd0\u8f93\u8ba1\u5212\u7ba1\u7406 - Pydantic Schema", "file_type": "rationale", "source_file": "app/schemas/pl/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_base_data", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_plan", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/__init__.py", "source_location": "L33", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_transport", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/__init__.py", "source_location": "L53", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_freight", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/__init__.py", "source_location": "L66", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_inventory", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/__init__.py", "source_location": "L78", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_vehicle_recharge", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/__init__.py", "source_location": "L98", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_init_rationale_1", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/__init__.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_py", "label": "plan.py", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductbase", "label": "PlanProductBase", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L16"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductcreate", "label": "PlanProductCreate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L55"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductupdate", "label": "PlanProductUpdate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L59"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planarchivebrandqtyitem", "label": "PlanArchiveBrandQtyItem", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L81"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductread", "label": "PlanProductRead", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L88"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planbase", "label": "PlanBase", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L105"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plancontractlink", "label": "PlanContractLink", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L155"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plancreate", "label": "PlanCreate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L161"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plantransportcreateblock", "label": "PlanTransportCreateBlock", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L176"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planupdate", "label": "PlanUpdate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L190"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_hazardcertreadybody", "label": "HazardCertReadyBody", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L239"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planread", "label": "PlanRead", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L244"}, {"id": "planresourcesnapshotmixin", "label": "PlanResourceSnapshotMixin", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planwithproducts", "label": "PlanWithProducts", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L278"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planinboundsliptemplatesave", "label": "PlanInboundSlipTemplateSave", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L282"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plandeliverysliptemplatesave", "label": "PlanDeliverySlipTemplateSave", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L287"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plansubpacksliptemplatesave", "label": "PlanSubpackSlipTemplateSave", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L294"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_createfromcontractrequest", "label": "CreateFromContractRequest", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L306"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plancreatefull", "label": "PlanCreateFull", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L317"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planlistquery", "label": "PlanListQuery", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L328"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductcertitem", "label": "PlanProductCertItem", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L353"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductcertsupdate", "label": "PlanProductCertsUpdate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L361"}, {"id": "users_wuzilong_work_00code_sLine truncated
+1 -1
View File
@@ -1 +1 @@
{"/Users/zlwu/work/00code/shanxia2.0/backend/start-prod.sh":{"size":7357,"mtime_ns":1782639355476060453,"hash":"ca89869446be74a2bf2367b3b28f448580220859a39a84ad9613ac5fe35a8433"},"/Users/zlwu/work/00code/shanxia2.0/backend/start.sh":{"size":9548,"mtime_ns":1780114661446983244,"hash":"4d632863e8a0cd81194b491d83a0218a4157435ae812dec76a6f6197b4d48e57"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/__init__.py":{"size":21,"mtime_ns":1780113982281973984,"hash":"60a04ff08fd97084e83ea809fd13feda6d2848be20ebf309e31fc1dad7523230"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/main.py":{"size":6280,"mtime_ns":1782790852770448643,"hash":"8f9da799977bee7134d87a26ff8434ac5ef8eabb0b7368dd537eee49ad7fd00b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/tasks/__init__.py":{"size":2207,"mtime_ns":1783208067881286091,"hash":"313e6da0e91d743ce891dd283458c79b50b24c4d909dc96ca7b3fd9d2bfa5013"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/auth.py":{"size":7457,"mtime_ns":1782790841500516545,"hash":"34bac56e1c2cb6215f4b5fa2dc32d77d4b4abca51428819eebf4afd269cc51cf"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_crypto.py":{"size":1870,"mtime_ns":1780108930724444498,"hash":"c04b748aea86890a5971f749523c7b17dbad1ddd6f9f7f07a5679d159c984e33"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/login_crypto.py":{"size":3858,"mtime_ns":1780108930725155710,"hash":"b82d51afabf2293a9cb1fb2e5f994c383948e7f47ea95fe674686b4f3f3342b1"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py":{"size":6958,"mtime_ns":1784032424448146538,"hash":"c2fb44aedb880733de28afa7f92d2c1c681eb600f821bbeba2bcda3933e5bc37"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/wechat_pay.py":{"size":23585,"mtime_ns":1780108930725768130,"hash":"3d52265ce65d21e7103f94afa1e8547aee0823b5ea6d65c50317436ac81fb4fc"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py":{"size":11971,"mtime_ns":1782701669198208175,"hash":"0b84c32828939e6102bb3647691fa8f04b1f68e0e0e9f28127374cc44e19a11c"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/database.py":{"size":1510,"mtime_ns":1782790847265601586,"hash":"8bc9c8ae62e2be2c6920036582ba448dfcd1711c41cebb62274ff6bc6ee14b0b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/security.py":{"size":728,"mtime_ns":1780108930725470004,"hash":"df0f01115064f7b8de1c81dbe9a6db39360c236610b68c33b3234a51d7f0025b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/request_payload.py":{"size":2842,"mtime_ns":1780108930725350836,"hash":"e9937151530ab1417ef297aef59111977725cf90be2a8fa76c00a0618789aba0"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/__init__.py":{"size":61,"mtime_ns":1780108930724312414,"hash":"3dd1842ee59b0c804176b03b4b8a323bd4a252b7feb1bbc32db64f3a8928570e"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/response.py":{"size":1824,"mtime_ns":1780108930725412462,"hash":"f1739352d65ea8287d76e282a59553aa8d63c97e35764c64ac378aa5bd9c347e"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/menu_guard.py":{"size":1611,"mtime_ns":1780108930725223002,"hash":"3e26077288c3620a4780038b891a326206b45bffe00aca545bdae30172487235"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/db_collation.py":{"size":548,"mtime_ns":1780108930725025043,"hash":"0c680e25a18244ca839a7de6ed1f4313b33bc8da118d89c463aee1b6af00c6f9"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/context.py":{"size":421,"mtime_ns":1780108930724824458,"hash":"83a10aee67162374993c10a30bf8bb8bd9c4b35cf48f42c832415ccebd91660f"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/utils.py":{"size":1498,"mtime_ns":1780108930725607296,"hash":"09323693661c1d4560458b86a954128ccff82bbaf6522da0b44f24da99ddf9ea"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/storage.py":{"size":14141,"mtime_ns":1782855136810943216,"hash":"3ae2c413c10898a8adb3d6773870bafbfbc18c739c10e0e8b4f765b4b5ca6c08"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/order_status_compat.py":{"size":2828,"mtime_ns":1780108930725284669,"hash":"5349e5a0745ee6734c4a995337779307adefc46f453906171988a77de2081540"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/exceptions.py":{"size":4946,"mtime_ns":1780108930725084335,"hash":"79c997d964bca1a4f12f2b92fb1f181fdb9e2b3cf72eae39f4cc164d23794fd7"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/data_scope.py":{"size":8043,"mtime_ns":1782648925694296304,"hash":"626dff86937e5cacec634ecbc61cd53d3763d760223a1d4ec7cd11f64e59a220"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/admin_request_json.py":{"size":2669,"mtime_ns":1780108930724378914,"hash":"4f68b3cf84291e7bddad6ecf4bb4d29ca5e1ec246a9f7f9c4cf5909d0ec1174a"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_crypto_middleware.py":{"size":10876,"mtime_ns":1782790001926627098,"hash":"b39f128b6612589ea6ac5babd30752e9585b3af07b82bb1ad53c9e3c0d90816f"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/wechat_message.py":{"size":11194,"mtime_ns":1780108930725684255,"hash":"5086070deb14d99d1a69e419b2299ec4Line truncated
{"/Users/zlwu/work/00code/shanxia2.0/backend/start-prod.sh":{"size":7357,"mtime_ns":1782639355476060453,"hash":"ca89869446be74a2bf2367b3b28f448580220859a39a84ad9613ac5fe35a8433"},"/Users/zlwu/work/00code/shanxia2.0/backend/start.sh":{"size":9548,"mtime_ns":1780114661446983244,"hash":"4d632863e8a0cd81194b491d83a0218a4157435ae812dec76a6f6197b4d48e57"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/__init__.py":{"size":21,"mtime_ns":1780113982281973984,"hash":"60a04ff08fd97084e83ea809fd13feda6d2848be20ebf309e31fc1dad7523230"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/main.py":{"size":6280,"mtime_ns":1782790852770448643,"hash":"8f9da799977bee7134d87a26ff8434ac5ef8eabb0b7368dd537eee49ad7fd00b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/tasks/__init__.py":{"size":2207,"mtime_ns":1783208067881286091,"hash":"313e6da0e91d743ce891dd283458c79b50b24c4d909dc96ca7b3fd9d2bfa5013"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/auth.py":{"size":7457,"mtime_ns":1782790841500516545,"hash":"34bac56e1c2cb6215f4b5fa2dc32d77d4b4abca51428819eebf4afd269cc51cf"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_crypto.py":{"size":1870,"mtime_ns":1780108930724444498,"hash":"c04b748aea86890a5971f749523c7b17dbad1ddd6f9f7f07a5679d159c984e33"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/login_crypto.py":{"size":3858,"mtime_ns":1780108930725155710,"hash":"b82d51afabf2293a9cb1fb2e5f994c383948e7f47ea95fe674686b4f3f3342b1"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py":{"size":6958,"mtime_ns":1784032424448146538,"hash":"c2fb44aedb880733de28afa7f92d2c1c681eb600f821bbeba2bcda3933e5bc37"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/wechat_pay.py":{"size":23585,"mtime_ns":1780108930725768130,"hash":"3d52265ce65d21e7103f94afa1e8547aee0823b5ea6d65c50317436ac81fb4fc"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py":{"size":11971,"mtime_ns":1782701669198208175,"hash":"0b84c32828939e6102bb3647691fa8f04b1f68e0e0e9f28127374cc44e19a11c"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/database.py":{"size":1510,"mtime_ns":1782790847265601586,"hash":"8bc9c8ae62e2be2c6920036582ba448dfcd1711c41cebb62274ff6bc6ee14b0b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/security.py":{"size":728,"mtime_ns":1780108930725470004,"hash":"df0f01115064f7b8de1c81dbe9a6db39360c236610b68c33b3234a51d7f0025b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/request_payload.py":{"size":2842,"mtime_ns":1780108930725350836,"hash":"e9937151530ab1417ef297aef59111977725cf90be2a8fa76c00a0618789aba0"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/__init__.py":{"size":61,"mtime_ns":1780108930724312414,"hash":"3dd1842ee59b0c804176b03b4b8a323bd4a252b7feb1bbc32db64f3a8928570e"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/response.py":{"size":1824,"mtime_ns":1780108930725412462,"hash":"f1739352d65ea8287d76e282a59553aa8d63c97e35764c64ac378aa5bd9c347e"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/menu_guard.py":{"size":1611,"mtime_ns":1780108930725223002,"hash":"3e26077288c3620a4780038b891a326206b45bffe00aca545bdae30172487235"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/db_collation.py":{"size":548,"mtime_ns":1780108930725025043,"hash":"0c680e25a18244ca839a7de6ed1f4313b33bc8da118d89c463aee1b6af00c6f9"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/context.py":{"size":421,"mtime_ns":1780108930724824458,"hash":"83a10aee67162374993c10a30bf8bb8bd9c4b35cf48f42c832415ccebd91660f"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/utils.py":{"size":1498,"mtime_ns":1780108930725607296,"hash":"09323693661c1d4560458b86a954128ccff82bbaf6522da0b44f24da99ddf9ea"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/storage.py":{"size":14141,"mtime_ns":1782855136810943216,"hash":"3ae2c413c10898a8adb3d6773870bafbfbc18c739c10e0e8b4f765b4b5ca6c08"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/order_status_compat.py":{"size":2828,"mtime_ns":1780108930725284669,"hash":"5349e5a0745ee6734c4a995337779307adefc46f453906171988a77de2081540"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/exceptions.py":{"size":4946,"mtime_ns":1780108930725084335,"hash":"79c997d964bca1a4f12f2b92fb1f181fdb9e2b3cf72eae39f4cc164d23794fd7"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/data_scope.py":{"size":8043,"mtime_ns":1782648925694296304,"hash":"626dff86937e5cacec634ecbc61cd53d3763d760223a1d4ec7cd11f64e59a220"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/admin_request_json.py":{"size":2669,"mtime_ns":1780108930724378914,"hash":"4f68b3cf84291e7bddad6ecf4bb4d29ca5e1ec246a9f7f9c4cf5909d0ec1174a"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_crypto_middleware.py":{"size":10876,"mtime_ns":1782790001926627098,"hash":"b39f128b6612589ea6ac5babd30752e9585b3af07b82bb1ad53c9e3c0d90816f"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/wechat_message.py":{"size":11194,"mtime_ns":1780108930725684255,"hash":"5086070deb14d99d1a69e419b2299ec4Line truncated
+4 -4
View File
@@ -63,12 +63,12 @@
</div>
<div id="legend"></div>
</div>
<div id="stats">4540 nodes &middot; 16819 edges &middot; 169 communities</div>
<div id="stats">4579 nodes &middot; 16988 edges &middot; 173 communities</div>
</div>
<script>
const RAW_NODES = [{"id": "app_init", "label": "__init__.py", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 304, "community_name": "__init__.py", "source_file": "app/__init__.py", "file_type": "code", "degree": 0}, {"id": "app_api_init", "label": "__init__.py", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.6, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 160, "community_name": "register_local_callback", "source_file": "app/api/__init__.py", "file_type": "code", "degree": 10}, {"id": "app_api_init_rationale_1", "label": "API \u8def\u7531\u5305 \u2014 \u5c71\u5ce1\u5316\u5de5\u751f\u4ea7\u4e00\u4f53\u5316\u95e8\u6237", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "API \u8def\u7531\u5305 \u2014 \u5c71\u5ce1\u5316\u5de5\u751f\u4ea7\u4e00\u4f53\u5316\u95e8\u6237", "community": 160, "community_name": "register_local_callback", "source_file": "app/api/__init__.py", "file_type": "rationale", "degree": 1}, {"id": "app_api_admin", "label": "admin.py", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 17.7, "font": {"size": 12, "color": "#ffffff"}, "title": "admin.py", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 119}, {"id": "app_api_admin_list_roles_for_select", "label": "list_roles_for_select()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "list_roles_for_select()", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 5}, {"id": "app_api_admin_py_session", "label": "Session", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 13.7, "font": {"size": 0, "color": "#ffffff"}, "title": "Session", "community": 3, "community_name": "Community 3", "source_file": "", "file_type": "code", "degree": 57}, {"id": "app_api_admin_list_users_for_select", "label": "list_users_for_select()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "list_users_for_select()", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 5}, {"id": "app_api_admin_list_roles_for_designer", "label": "list_roles_for_designer()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "list_roles_for_designer()", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 5}, {"id": "app_api_admin_tenantcreatebody", "label": "TenantCreateBody", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.6, "font": {"size": 0, "color": "#ffffff"}, "title": "TenantCreateBody", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 10}, {"id": "app_api_admin_tenantupdatebody", "label": "TenantUpdateBody", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.6, "font": {"size": 0, "color": "#ffffff"}, "title": "TenantUpdateBody", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 10}, {"id": "app_api_admin_list_tenants", "label": "list_tenants()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "list_tenants()", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 3}, {"id": "app_api_admin_create_tenant", "label": "create_tenant()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "create_tenant()", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 5}, {"id": "app_api_admin_update_tenant", "label": "update_tenant()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border":Line truncated
const RAW_EDGES = [{"from": "app_api_init", "to": "app_api_admin", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_bx_init", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_crm", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_db_init", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_leave", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_menu", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_monitor", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_public", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_workflow", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init_rationale_1", "to": "app_api_init", "label": "rationale_for", "title": "rationale_for [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_attachment_download_url", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_attachment_preview_url", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_attachmentdownloadurlbody", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_dept", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_dict_data", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_dict_type", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_menu", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_menu_favorite", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_post", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_role_management", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_site", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_tenant", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_user", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_delete_dept", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_delete_dict_data", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"Line truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 35}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 41}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 41}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 100}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 79}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 125}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 33}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 201}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 68}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 56}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 33}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 61}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 82}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 24}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 32}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 57}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 66}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 32}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 73}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 83}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 45}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 54}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 43}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 20}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 74}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 31}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 74}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 38}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 103}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 47}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 27}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 12}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 18}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 36}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 21}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 38}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 33}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 26}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 34}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 29}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 34}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 26}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 34}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 64}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 24}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 18}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 31}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 36}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 14}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 15}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 16}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 15}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 16}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 16}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 24}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 13}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 13}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 10}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 20}, {"cid": 59, "color": "#BAB0AC", "label": "Community 59", "count": 30}, {"cid": 60, "color": "#4E79A7", "label": "Community 60", "count": 22}, {"cid": 61, "color": "#F28E2B", "label": "Community 61", "count": 81}, {"cid": 62, "color": "#E15759", "label": "Community 62", "count": 12}, {"cid": 63, "color": "#76B7B2", "label": "Community 63", "count": 24}, {"cid": 64, "color": "#59A14F", "label": "Community 64", "count": 12}, {"cid": 65, "color": "#EDC948", "label": "Community 65", "count": 73}, {"cid": 66, "color": "#B07AA1", "label": "Community 66", "count": 49}, {"cid": 67, "color": "#FF9DA7", "label": "Community 67", "count": 38}, {"cid": 68, "color": "#9C755F", "label": "Community 68", "count": 14}, {"cid": 69, "color": "#BAB0AC", "label": "Community 69", "count": 31}, {"cid": 70, "color": "#4E79A7Line truncated
const RAW_NODES = [{"id": "app_init", "label": "__init__.py", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 304, "community_name": "__init__.py", "source_file": "app/__init__.py", "file_type": "code", "degree": 0}, {"id": "app_api_init", "label": "__init__.py", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.6, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 121, "community_name": "Session", "source_file": "app/api/__init__.py", "file_type": "code", "degree": 10}, {"id": "app_api_init_rationale_1", "label": "API \u8def\u7531\u5305 \u2014 \u5c71\u5ce1\u5316\u5de5\u751f\u4ea7\u4e00\u4f53\u5316\u95e8\u6237", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "API \u8def\u7531\u5305 \u2014 \u5c71\u5ce1\u5316\u5de5\u751f\u4ea7\u4e00\u4f53\u5316\u95e8\u6237", "community": 121, "community_name": "Session", "source_file": "app/api/__init__.py", "file_type": "rationale", "degree": 1}, {"id": "app_api_admin", "label": "admin.py", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 17.9, "font": {"size": 12, "color": "#ffffff"}, "title": "admin.py", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 121}, {"id": "app_api_admin_list_roles_for_select", "label": "list_roles_for_select()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "list_roles_for_select()", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 5}, {"id": "app_api_admin_py_session", "label": "Session", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 13.8, "font": {"size": 0, "color": "#ffffff"}, "title": "Session", "community": 3, "community_name": "Community 3", "source_file": "", "file_type": "code", "degree": 58}, {"id": "app_api_admin_list_users_for_select", "label": "list_users_for_select()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "list_users_for_select()", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 5}, {"id": "app_api_admin_list_roles_for_designer", "label": "list_roles_for_designer()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "list_roles_for_designer()", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 5}, {"id": "app_api_admin_tenantcreatebody", "label": "TenantCreateBody", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.7, "font": {"size": 0, "color": "#ffffff"}, "title": "TenantCreateBody", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 11}, {"id": "app_api_admin_tenantupdatebody", "label": "TenantUpdateBody", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.7, "font": {"size": 0, "color": "#ffffff"}, "title": "TenantUpdateBody", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 11}, {"id": "app_api_admin_list_tenants", "label": "list_tenants()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "list_tenants()", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 3}, {"id": "app_api_admin_create_tenant", "label": "create_tenant()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "create_tenant()", "community": 3, "community_name": "Community 3", "source_file": "app/api/admin.py", "file_type": "code", "degree": 5}, {"id": "app_api_admin_update_tenant", "label": "update_tenant()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "fonLine truncated
const RAW_EDGES = [{"from": "app_api_init", "to": "app_api_admin", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_bx_init", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_crm", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_db_init", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_leave", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_menu", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_monitor", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_public", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init", "to": "app_api_workflow", "label": "re_exports", "title": "re_exports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_init_rationale_1", "to": "app_api_init", "label": "rationale_for", "title": "rationale_for [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_attachment_download_url", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_attachment_preview_url", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_attachmentdownloadurlbody", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_dept", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_dict_data", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_dict_type", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_menu", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_menu_favorite", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_post", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_role_management", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_site", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_tenant", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_create_user", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_delete_dept", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_api_admin", "to": "app_api_admin_delete_dict_data", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"Line truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 26}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 39}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 19}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 106}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 63}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 148}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 34}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 211}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 55}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 52}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 66}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 45}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 80}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 25}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 24}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 86}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 117}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 29}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 34}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 42}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 18}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 54}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 43}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 8}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 125}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 13}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 96}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 35}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 56}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 54}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 27}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 101}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 16}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 18}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 12}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 38}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 31}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 26}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 13}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 29}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 36}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 26}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 34}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 50}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 18}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 18}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 29}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 35}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 14}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 11}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 36}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 19}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 19}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 16}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 30}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 37}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 33}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 11}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 31}, {"cid": 59, "color": "#BAB0AC", "label": "Community 59", "count": 30}, {"cid": 60, "color": "#4E79A7", "label": "Community 60", "count": 17}, {"cid": 61, "color": "#F28E2B", "label": "Community 61", "count": 46}, {"cid": 62, "color": "#E15759", "label": "Community 62", "count": 16}, {"cid": 63, "color": "#76B7B2", "label": "Community 63", "count": 16}, {"cid": 64, "color": "#59A14F", "label": "Community 64", "count": 60}, {"cid": 65, "color": "#EDC948", "label": "Community 65", "count": 45}, {"cid": 66, "color": "#B07AA1", "label": "Community 66", "count": 15}, {"cid": 67, "color": "#FF9DA7", "label": "Community 67", "count": 38}, {"cid": 68, "color": "#9C755F", "label": "Community 68", "count": 14}, {"cid": 69, "color": "#BAB0AC", "label": "Community 69", "count": 29}, {"cid": 70, "color": "#4E79ALine 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
@@ -1,106 +0,0 @@
-- backend/scripts/migrate_20260814_duty_schedule.sql
-- 值班排班 V1:按日链接 + 班次名额 + 报名
CREATE TABLE IF NOT EXISTS `duty_day` (
`id` varchar(64) NOT NULL COMMENT '主键ID(UUID)',
`duty_date` date NOT NULL COMMENT '值班日期',
`access_token` varchar(64) NOT NULL COMMENT '公开访问令牌',
`link_status` varchar(16) NOT NULL DEFAULT 'open' COMMENT 'open/closed',
`tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号',
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
`create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建部门',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` smallint(6) NOT NULL DEFAULT '0' COMMENT '删除标志',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_token` (`access_token`),
UNIQUE KEY `uk_tenant_duty_date` (`tenant_no`, `duty_date`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='值班日(一天一条公开链接)';
CREATE TABLE IF NOT EXISTS `duty_slot` (
`id` varchar(64) NOT NULL COMMENT '主键ID(UUID)',
`duty_day_id` varchar(64) NOT NULL COMMENT '值班日ID',
`shift_type` varchar(32) NOT NULL COMMENT 'morning/evening/duty',
`capacity` int(11) NOT NULL DEFAULT 1 COMMENT '名额',
`tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号',
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
`create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建部门',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` smallint(6) NOT NULL DEFAULT '0' COMMENT '删除标志',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_day_shift` (`duty_day_id`, `shift_type`, `del_flag`),
KEY `idx_day` (`duty_day_id`, `del_flag`),
KEY `idx_tenant` (`tenant_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='值班班次名额';
CREATE TABLE IF NOT EXISTS `duty_assignment` (
`id` varchar(64) NOT NULL COMMENT '主键ID(UUID)',
`duty_slot_id` varchar(64) NOT NULL COMMENT '班次名额ID',
`source` varchar(16) NOT NULL COMMENT 'assign/grab',
`user_id` varchar(64) DEFAULT NULL COMMENT '系统用户ID(指定时)',
`person_name` varchar(64) NOT NULL COMMENT '展示姓名',
`tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号',
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
`create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建部门',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` smallint(6) NOT NULL DEFAULT '0' COMMENT '删除标志',
PRIMARY KEY (`id`),
KEY `idx_slot` (`duty_slot_id`, `del_flag`),
KEY `idx_tenant` (`tenant_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='值班占位(指定或抢班)';
-- 菜单:一级「值班管理」+ 叶子「值班排班」(门户首页按一级模块出卡片)
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 (
'DUTY000000000000000000000000000',
'值班管理',
'2',
'M',
'/duty',
'/index/index',
NULL,
1, '1', '1', 'shanxia2.0', 85,
'值班一级模块(门户首页分区)',
'system', '', NOW(), NULL, NOW(), 0
)
ON DUPLICATE KEY UPDATE
`menu_name`=VALUES(`menu_name`),
`component`=VALUES(`component`),
`path`=VALUES(`path`);
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 (
'DUTY000000000000000000000000001',
'值班排班',
'DUTY000000000000000000000000000',
'C',
'schedule',
'/duty/schedule/index',
'duty:schedule:list',
1, '1', '1', 'shanxia2.0', 1,
'日历排班与公开抢班链接',
'system', '', NOW(), NULL, NOW(), 0
)
ON DUPLICATE KEY UPDATE
`menu_name`=VALUES(`menu_name`),
`parent_id`=VALUES(`parent_id`),
`component`=VALUES(`component`),
`perms`=VALUES(`perms`),
`path`=VALUES(`path`);
@@ -1,82 +0,0 @@
-- backend/scripts/migrate_20260815_duty_menu_portal_fix.sql
-- 修复:值班菜单改为一级目录 /duty + 叶子 schedule,才能出现在门户首页启动器
-- 可重复执行(幂等)
-- 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 (
'DUTY000000000000000000000000000',
'值班管理',
'2',
'M',
'/duty',
'/index/index',
NULL,
1, '1', '1', 'shanxia2.0', 85,
'值班一级模块(门户首页分区)',
'system', '', NOW(), NULL, NOW(), 0
)
ON DUPLICATE KEY UPDATE
`menu_name`=VALUES(`menu_name`),
`parent_id`=VALUES(`parent_id`),
`menu_type`=VALUES(`menu_type`),
`path`=VALUES(`path`),
`component`=VALUES(`component`),
`visible`=VALUES(`visible`),
`status`=VALUES(`status`);
-- 2) 叶子「值班排班」挂到值班管理下
UPDATE `sys_menu`
SET
`parent_id` = 'DUTY000000000000000000000000000',
`menu_type` = 'C',
`path` = 'schedule',
`component` = '/duty/schedule/index',
`perms` = 'duty:schedule:list',
`visible` = '1',
`status` = '1',
`order_num` = 1,
`update_time` = NOW()
WHERE `id` = 'DUTY000000000000000000000000001';
-- 3) 给「超级管理员」「管理员」授权(若已有则忽略)
-- 注意:INSERT…SELECT 中 UUID() 在 MySQL 5.7 可能对多行算出相同值导致主键冲突,
-- 故用 role_id+menu_id 派生确定性主键,保证幂等可重跑。
INSERT INTO `sys_role_menu` (
`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
)
SELECT
CONCAT('DUTYRM', LEFT(MD5(CONCAT(r.id, '|', m.menu_id)), 26)),
r.id,
m.menu_id,
'shanxia2.0',
NULL,
'duty portal',
'system',
'',
NOW(),
NULL,
NOW(),
0
FROM `sys_role` r
JOIN (
SELECT 'DUTY000000000000000000000000000' AS menu_id
UNION ALL
SELECT 'DUTY000000000000000000000000001'
) m
WHERE r.del_flag = 0
AND r.role_name IN ('超级管理员', '管理员')
AND NOT EXISTS (
SELECT 1 FROM `sys_role_menu` rm
WHERE rm.role_id = r.id
AND rm.menu_id = m.menu_id
AND IFNULL(rm.del_flag, 0) = 0
)
ON DUPLICATE KEY UPDATE
`del_flag` = 0,
`update_time` = NOW(),
`remark` = VALUES(`remark`);
@@ -1,33 +0,0 @@
-- backend/scripts/migrate_20260816_crm_settlement_method_options.sql
-- 合同结算方式:按量结算 / 月结 / 季结 / 其他
-- 可重复执行(幂等)
INSERT INTO `sys_dict_data` (
`id`, `dict_label`, `dict_value`, `tag_type`, `dict_type`, `tenant_no`,
`order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`,
`update_by`, `update_time`, `del_flag`
) VALUES
(
'SXDC20260816SETTLE00000000000001', '按量结算', 'volume_settlement', '',
'crm_settlement_method', 'shanxia2.0', 2, '', '1', '', NOW(), NULL, NOW(), 0
),
(
'SXDC20260816SETTLE00000000000002', '月结', 'monthly_settlement', '',
'crm_settlement_method', 'shanxia2.0', 3, '', '1', '', NOW(), NULL, NOW(), 0
),
(
'SXDC20260816SETTLE00000000000003', '季结', 'quarterly_settlement', '',
'crm_settlement_method', 'shanxia2.0', 4, '', '1', '', NOW(), NULL, NOW(), 0
),
(
'SXDC20260816SETTLE00000000000004', '其他', 'other_settlement', '',
'crm_settlement_method', 'shanxia2.0', 5, '', '1', '', NOW(), NULL, NOW(), 0
)
ON DUPLICATE KEY UPDATE
`dict_label` = VALUES(`dict_label`),
`dict_value` = VALUES(`dict_value`),
`dict_type` = VALUES(`dict_type`),
`tenant_no` = VALUES(`tenant_no`),
`order_num` = VALUES(`order_num`),
`del_flag` = 0,
`update_time` = NOW();
@@ -1,6 +0,0 @@
-- backend/scripts/migrate_20260817_bx_reimburse_detail_occur_date.sql
-- 报销明细增加可选发生日期 occur_date
ALTER TABLE `bx_reimburse_detail`
ADD COLUMN `occur_date` date DEFAULT NULL COMMENT '费用发生日期'
AFTER `amount`;
@@ -1,26 +0,0 @@
-- backend/scripts/migrate_20260817_duty_staff_role.sql
-- 值班排班可排班名单角色:仅作标签,不挂菜单
INSERT INTO `sys_role` (
`id`, `role_name`, `data_scope`, `status`, `tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
) VALUES (
'R65CAAE057914CCCAE2A81BE627A5101',
'值班人员',
'4',
'1',
'shanxia2.0',
90,
'值班排班可指定/抢班名单;无菜单权限',
'system',
'',
NOW(),
NULL,
NOW(),
0
)
ON DUPLICATE KEY UPDATE
`role_name` = VALUES(`role_name`),
`status` = VALUES(`status`),
`remark` = VALUES(`remark`),
`del_flag` = 0;
@@ -57,7 +57,8 @@ INSERT INTO `sys_role_menu` (
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
)
SELECT
REPLACE(UUID(), '-', ''),
-- 勿用 UUID():同句多行 INSERT 易撞主键;按 role+menu 生成确定性 id
MD5(CONCAT(`role_id`, '#PL_PLAN_CONTRACT')),
`role_id`,
'PL_PLAN_CONTRACT',
`tenant_no`,
@@ -85,7 +86,7 @@ INSERT INTO `sys_role_menu` (
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
)
SELECT
REPLACE(UUID(), '-', ''),
MD5(CONCAT(`role_id`, '#PL_PLAN_CONTRACT_UPD')),
`role_id`,
'PL_PLAN_CONTRACT_UPD',
`tenant_no`,
@@ -0,0 +1,48 @@
"""挂车号分流:货拉拉免挂车证;自有正常挂车号强制;三方不强制。"""
from __future__ import annotations
import pytest
from fastapi import HTTPException
from app.services.pl_cert_required_service import (
is_huolala_trailer_no,
should_require_trailer_certs,
should_show_trailer_cert_slots,
validate_self_vehicle_required_certs,
)
def _complete_self_head_certs():
return [
{"cert_name": "行驶证", "expire_date": "2030-01-01", "vehicle_part": "head"},
{"cert_name": "营运证", "expire_date": "2030-01-01", "vehicle_part": "head"},
{"cert_name": "保险单", "expire_date": "2030-01-01", "vehicle_part": "head"},
]
def test_huolala_trailer_helpers():
assert is_huolala_trailer_no("货拉拉")
assert is_huolala_trailer_no(" 货拉拉 ")
assert not is_huolala_trailer_no("皖LP359")
assert not should_show_trailer_cert_slots("货拉拉")
assert should_show_trailer_cert_slots("皖LP359")
assert not should_require_trailer_certs("self", "货拉拉")
assert should_require_trailer_certs("self", "皖LP359")
assert not should_require_trailer_certs("carrier", "皖LP359")
def test_self_huolala_skips_trailer_required_certs():
validate_self_vehicle_required_certs(
_complete_self_head_certs(),
trailer_no="货拉拉",
)
def test_self_normal_trailer_requires_trailer_certs():
with pytest.raises(HTTPException) as ei:
validate_self_vehicle_required_certs(
_complete_self_head_certs(),
trailer_no="皖LP359",
)
assert ei.value.status_code == 400
assert "挂车" in ei.value.detail
+490
View File
@@ -0,0 +1,490 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>开票信息 · 快速识别 Demo</title>
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--text: #1e293b;
--muted: #64748b;
--border: #e2e8f0;
--panel: #f8fafc;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
color: var(--text);
background: linear-gradient(160deg, #ecfdf5 0%, #f8fafc 40%, #eef2ff 100%);
min-height: 100vh;
padding: 32px 20px 48px;
}
.page { max-width: 1100px; margin: 0 auto; }
h1 { font-size: 22px; margin: 0 0 6px; }
.lead { color: var(--muted); margin: 0 0 24px; font-size: 14px; line-height: 1.6; }
.tabs {
display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px;
}
.tab {
border: 1px solid var(--border);
background: #fff;
border-radius: 999px;
padding: 8px 14px;
font-size: 13px;
cursor: pointer;
color: var(--text);
}
.tab.active {
background: var(--primary);
border-color: var(--primary);
color: #fff;
font-weight: 600;
}
.stage {
background: #fff;
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: 0 8px 28px rgba(15, 23, 42, 0.06);
overflow: hidden;
}
.stage-head {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
background: var(--panel);
font-size: 13px;
color: var(--muted);
}
.stage-body { padding: 20px; background: var(--panel); }
.dialog {
max-width: 720px;
margin: 0 auto;
background: #fff;
border-radius: 10px;
border: 1px solid var(--border);
box-shadow: 0 12px 40px rgba(15, 23, 42, 0.12);
overflow: hidden;
}
.dialog-hd {
display: flex; justify-content: space-between; align-items: center;
padding: 14px 18px; border-bottom: 1px solid var(--border);
font-weight: 600; font-size: 15px;
}
.dialog-bd { padding: 16px 18px; }
.dialog-ft {
display: flex; justify-content: flex-end; gap: 8px;
padding: 12px 18px; border-top: 1px solid var(--border);
}
.paste {
border: 1px dashed #6ee7b7;
background: var(--primary-bg);
border-radius: 8px;
padding: 12px;
margin-bottom: 14px;
}
.paste-hd {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 8px;
}
.paste-title { font-weight: 600; color: var(--primary-dark); }
.paste-tip { font-size: 11px; color: var(--muted); }
textarea {
width: 100%;
min-height: 120px;
border: 1px solid #a7f3d0;
border-radius: 6px;
padding: 10px;
font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
resize: vertical;
color: var(--text);
background: #fff;
}
.row-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 10px; }
.btn {
height: 32px; padding: 0 14px; border-radius: 8px;
border: 1px solid #cbd5e1; background: #fff; color: #475569;
font-size: 13px; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
}
.btn-primary {
background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600;
}
.btn-primary:hover { background: var(--primary-dark); }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.span2 { grid-column: 1 / -1; }
label { display: block; font-size: 11px; color: var(--muted); margin-bottom: 4px; }
input, .fake-input {
width: 100%; height: 36px; border: 1px solid var(--border); border-radius: 8px;
padding: 0 10px; font-size: 13px; background: #fff; color: var(--text);
}
input.filled { border-color: var(--primary); background: #f0fdf4; }
.msg {
margin: 10px 0 0; font-size: 12px; color: var(--primary-dark);
min-height: 18px;
}
.msg.warn { color: #d97706; }
.toolbar { display: flex; gap: 8px; margin-bottom: 12px; }
.panel-title {
font-weight: 600; margin-bottom: 12px; padding-left: 8px;
border-left: 3px solid var(--primary);
}
.table {
background: #fff; border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
}
.thead, .trow {
display: grid; grid-template-columns: 48px 1.4fr 1.2fr 1.6fr 72px;
gap: 8px; padding: 10px 12px; font-size: 12px;
}
.thead { background: #f1f5f9; color: var(--muted); font-weight: 600; }
.empty { padding: 28px; text-align: center; color: #94a3b8; font-size: 13px; }
.note {
margin-top: 16px; padding: 12px 14px; border-radius: 8px;
background: #fff; border: 1px solid var(--border); font-size: 13px; color: var(--muted); line-height: 1.6;
}
.hidden { display: none !important; }
@media (max-width: 720px) {
.grid { grid-template-columns: 1fr; }
.thead, .trow { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="page">
<h1>客户管理 · 开票信息「快速识别」交互 Demo</h1>
<p class="lead">
粘贴客户发来的开票资料文本,一键解析并填入表单字段。
下方可切换三种入口方案;方案 A 可直接点「识别并填入」试效果。
</p>
<div class="tabs">
<button class="tab active" data-tab="a">A · 表单顶部折叠区(推荐)</button>
<button class="tab" data-tab="b">B · 工具栏「快速识别新增」</button>
<button class="tab" data-tab="c">C · 二级小弹窗</button>
</div>
<!-- A -->
<div class="stage" id="pane-a">
<div class="stage-head">方案 A:新增/编辑弹窗内顶部粘贴区 → 识别后回填下方输入框</div>
<div class="stage-body">
<div class="dialog">
<div class="dialog-hd">
<span>添加开票信息</span>
<span style="color:#94a3b8;font-weight:400;">✕</span>
</div>
<div class="dialog-bd">
<div class="paste">
<div class="paste-hd">
<span class="paste-title">⚡ 粘贴识别</span>
<span class="paste-tip">支持常见「开票资料」文本格式</span>
</div>
<textarea id="pasteA" placeholder="在此粘贴开票资料…"></textarea>
<div class="row-actions">
<button class="btn" type="button" id="clearA">清空</button>
<button class="btn" type="button" id="sampleA">填入样例</button>
<button class="btn btn-primary" type="button" id="recognizeA">识别并填入</button>
</div>
<p class="msg" id="msgA"></p>
</div>
<div class="grid">
<div class="span2">
<label>单位名称(章)</label>
<input id="f-companyName" placeholder="请输入单位名称" />
</div>
<div class="span2">
<label>税号</label>
<input id="f-taxNo" placeholder="请输入税号" />
</div>
<div class="span2">
<label>单位地址</label>
<input id="f-companyAddress" placeholder="请输入单位地址" />
</div>
<div>
<label>电话/传真</label>
<input id="f-phoneFax" placeholder="请输入电话/传真" />
</div>
<div>
<label>开户银行</label>
<input id="f-bankName" placeholder="请输入开户银行" />
</div>
<div class="span2">
<label>账号</label>
<input id="f-bankAccount" placeholder="请输入账号" />
</div>
<div class="span2">
<label>备注(未映射字段可写入)</label>
<input id="f-remark" placeholder="银行行号 / 邮编 / 邮寄信息等" />
</div>
</div>
</div>
<div class="dialog-ft">
<button class="btn" type="button">取消</button>
<button class="btn btn-primary" type="button">确定</button>
</div>
</div>
</div>
</div>
<!-- B -->
<div class="stage hidden" id="pane-b">
<div class="stage-head">方案 B:开票信息列表工具栏增加「快速识别新增」</div>
<div class="stage-body">
<div class="panel-title">开票信息</div>
<div class="toolbar">
<button class="btn btn-primary" type="button">+ 添加开票信息</button>
<button class="btn" type="button" id="openB">📋 快速识别新增</button>
</div>
<div class="table">
<div class="thead"><span>#</span><span>单位名称</span><span>税号</span><span>开户银行</span><span>默认</span></div>
<div class="empty">暂无开票信息,请点击「添加开票信息」或「快速识别新增」</div>
</div>
<div class="dialog hidden" id="dialogB" style="margin-top:16px;">
<div class="dialog-hd"><span>快速识别新增</span><span style="color:#94a3b8;font-weight:400;">✕</span></div>
<div class="dialog-bd">
<textarea id="pasteB" style="min-height:140px;" placeholder="粘贴开票资料后点识别…"></textarea>
<div class="row-actions">
<button class="btn" type="button" id="sampleB">填入样例</button>
<button class="btn btn-primary" type="button" id="recognizeB">识别并打开表单</button>
</div>
<p class="msg" id="msgB"></p>
</div>
</div>
</div>
</div>
<!-- C -->
<div class="stage hidden" id="pane-c">
<div class="stage-head">方案 C:新增弹窗标题旁「粘贴识别」→ 二级小窗</div>
<div class="stage-body">
<div class="dialog" style="position:relative;">
<div class="dialog-hd">
<span>添加开票信息</span>
<button class="btn" type="button" id="openC" style="height:28px;font-size:12px;color:var(--primary-dark);border-color:#a7f3d0;background:var(--primary-bg);">⚡ 粘贴识别</button>
</div>
<div class="dialog-bd" style="opacity:.55;">
<div class="grid">
<div class="span2"><label>单位名称(章)</label><input placeholder="请输入单位名称" disabled /></div>
<div class="span2"><label>税号</label><input placeholder="请输入税号" disabled /></div>
</div>
</div>
<div class="dialog hidden" id="dialogC" style="position:absolute;left:50%;top:58%;transform:translate(-50%,-50%);width:86%;z-index:3;box-shadow:0 16px 48px rgba(15,23,42,.2);">
<div class="dialog-hd"><span>粘贴开票资料</span></div>
<div class="dialog-bd">
<textarea id="pasteC" style="min-height:100px;"></textarea>
<div class="row-actions">
<button class="btn" type="button" id="sampleC">填入样例</button>
<button class="btn btn-primary" type="button" id="recognizeC">识别</button>
</div>
<p class="msg" id="msgC"></p>
</div>
</div>
</div>
</div>
</div>
<div class="note">
<strong style="color:var(--text);">字段映射(与现表单一致):</strong>
公司名称/单位名 → 单位名称;纳税人识别号/税号 → 税号;地址 → 单位地址;
电话/传真 → 电话/传真;开户行/银行开户行 → 开户银行;帐号/账号 → 账号(自动去空格)。
「地址及电话」合并行会拆分。银行行号、邮编、发票邮寄地址无独立字段时写入备注。
</div>
</div>
<script>
const SAMPLE_1 = `开票资料:
公司名称:安庆市山峡化工有限公司
纳税人识别号:9134080006084212XH
开户行:中国建设银行安庆分行城中支行
帐号:34050168860800000121
银行行号:105368000056
地址:安徽省安庆市高新区霞虹路5号
电话/传真:0556-5348918
邮编:246000
发票邮寄地址:
姓名:汪璇
电话:18895395317
地址:安徽省安庆市高新区霞虹路5号`
const SAMPLE_2 = `单位名:安徽江峰生物科技有限公司
税 号:91340200MA8PYJNB8X
地址及电话:安徽省芜湖市皖江江北新兴产业集中区沈巷片区起步区皖跃路与和睦路交叉口西南角福州路4号
银行开户行:中国建设银行芜湖经开区支行
账 号:3405 0167 8808 0000 2481
银行行号:1053 6205 0066
电话:0553-5201019`
function normalizeSpaces(s) {
return (s || '').replace(/\s+/g, ' ').trim()
}
function pick(text, patterns) {
for (const re of patterns) {
const m = text.match(re)
if (m && m[1]) return normalizeSpaces(m[1])
}
return ''
}
function parseInvoicePaste(raw) {
const text = (raw || '').replace(/\r\n/g, '\n')
const warnings = []
const result = {
companyName: '',
taxNo: '',
companyAddress: '',
phoneFax: '',
bankName: '',
bankAccount: '',
remarkExtra: []
}
result.companyName = pick(text, [
/(?:公司名称|单位名称|单位名|名称)\s*[::]\s*(.+)/,
])
result.taxNo = pick(text, [
/(?:纳税人识别号|纳税识别号|统一社会信用代码|税\s*号|税号)\s*[::]\s*([0-9A-Za-z]+)/,
])
result.bankName = pick(text, [
/(?:银行开户行|开户银行|开户行)\s*[::]\s*(.+)/,
])
let account = pick(text, [
/(?:银行账号|账\s*号|帐\s*号|账号|帐号)\s*[::]\s*([0-9\s]+)/,
])
if (account) result.bankAccount = account.replace(/\s+/g, '')
const addrPhone = pick(text, [/(?:地址及电话)\s*[::]\s*(.+)/])
if (addrPhone) {
const phoneInAddr = addrPhone.match(/(0\d{2,3}-?\d{7,8}|1\d{10})$/)
if (phoneInAddr) {
result.phoneFax = phoneInAddr[1]
result.companyAddress = normalizeSpaces(addrPhone.slice(0, phoneInAddr.index))
} else {
result.companyAddress = addrPhone
}
} else {
// 优先取「开票资料」段的地址,避开「发票邮寄地址」块内的地址
const beforeMail = text.split(/发票邮寄地址/)[0] || text
result.companyAddress = pick(beforeMail, [/(?:单位地址|地\s*址|地址)\s*[::]\s*(.+)/])
}
result.phoneFax = result.phoneFax || pick(text, [
/(?:电话\/传真|电话\/传貞|电话传真|传真)\s*[::]\s*(.+)/,
/(?:^|\n)\s*电\s*话\s*[::]\s*(.+)/,
])
const bankCode = pick(text, [/(?:银行行号|联行号|行号)\s*[::]\s*([0-9\s]+)/])
if (bankCode) result.remarkExtra.push('银行行号:' + bankCode.replace(/\s+/g, ''))
const zip = pick(text, [/(?:邮\s*编|邮编)\s*[::]\s*(\d{6})/])
if (zip) result.remarkExtra.push('邮编:' + zip)
const mailBlock = text.split(/发票邮寄地址\s*[::]?/)[1]
if (mailBlock) {
const name = pick(mailBlock, [/(?:姓\s*名|姓名)\s*[::]\s*(.+)/])
const phone = pick(mailBlock, [/(?:电\s*话|电话|手机)\s*[::]\s*(.+)/])
const addr = pick(mailBlock, [/(?:地\s*址|地址)\s*[::]\s*(.+)/])
const parts = []
if (name) parts.push('收件人 ' + name)
if (phone) parts.push(phone)
if (addr) parts.push(addr)
if (parts.length) result.remarkExtra.push('邮寄:' + parts.join(' / '))
}
if (!result.companyName) warnings.push('未识别到单位名称')
if (!result.taxNo) warnings.push('未识别到税号')
return { result, warnings }
}
function applyToForm(parsed) {
const map = {
companyName: 'f-companyName',
taxNo: 'f-taxNo',
companyAddress: 'f-companyAddress',
phoneFax: 'f-phoneFax',
bankName: 'f-bankName',
bankAccount: 'f-bankAccount',
}
Object.keys(map).forEach((k) => {
const el = document.getElementById(map[k])
const v = parsed.result[k] || ''
el.value = v
el.classList.toggle('filled', !!v)
})
const remark = document.getElementById('f-remark')
remark.value = parsed.result.remarkExtra.join(';')
remark.classList.toggle('filled', !!remark.value)
}
function runRecognize(textareaId, msgId, apply) {
const raw = document.getElementById(textareaId).value
const parsed = parseInvoicePaste(raw)
if (apply) applyToForm(parsed)
const msg = document.getElementById(msgId)
if (!raw.trim()) {
msg.className = 'msg warn'
msg.textContent = '请先粘贴开票资料文本'
return
}
const filled = ['companyName','taxNo','companyAddress','phoneFax','bankName','bankAccount']
.filter((k) => parsed.result[k]).length
msg.className = 'msg' + (parsed.warnings.length ? ' warn' : '')
msg.textContent = `已识别 ${filled} 个字段` +
(parsed.result.remarkExtra.length ? `,另有 ${parsed.result.remarkExtra.length} 项写入备注` : '') +
(parsed.warnings.length ? ';' + parsed.warnings.join(';') : '')
}
document.querySelectorAll('.tab').forEach((tab) => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'))
tab.classList.add('active')
;['a','b','c'].forEach((k) => {
document.getElementById('pane-' + k).classList.toggle('hidden', k !== tab.dataset.tab)
})
})
})
document.getElementById('pasteA').value = SAMPLE_1
document.getElementById('sampleA').onclick = () => {
document.getElementById('pasteA').value =
document.getElementById('pasteA').value.includes('安庆市山峡') ? SAMPLE_2 : SAMPLE_1
}
document.getElementById('clearA').onclick = () => {
document.getElementById('pasteA').value = ''
document.getElementById('msgA').textContent = ''
}
document.getElementById('recognizeA').onclick = () => runRecognize('pasteA', 'msgA', true)
document.getElementById('openB').onclick = () => {
document.getElementById('dialogB').classList.remove('hidden')
document.getElementById('pasteB').value = SAMPLE_2
}
document.getElementById('sampleB').onclick = () => {
document.getElementById('pasteB').value = SAMPLE_1
}
document.getElementById('recognizeB').onclick = () => {
runRecognize('pasteB', 'msgB', true)
document.querySelector('[data-tab="a"]').click()
document.getElementById('pasteA').value = document.getElementById('pasteB').value
}
document.getElementById('openC').onclick = () => {
document.getElementById('dialogC').classList.toggle('hidden')
if (!document.getElementById('pasteC').value) document.getElementById('pasteC').value = SAMPLE_1
}
document.getElementById('sampleC').onclick = () => {
document.getElementById('pasteC').value = SAMPLE_2
}
document.getElementById('recognizeC').onclick = () => {
runRecognize('pasteC', 'msgC', true)
document.getElementById('dialogC').classList.add('hidden')
document.querySelector('[data-tab="a"]').click()
document.getElementById('pasteA').value = document.getElementById('pasteC').value
}
// 首次自动演示一次
runRecognize('pasteA', 'msgA', true)
</script>
</body>
</html>
@@ -0,0 +1,506 @@
# CRM 开票信息粘贴快速识别 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在客户开票信息新增/编辑弹窗顶部增加「粘贴识别」区,用纯前端规则解析常见开票资料文本并覆盖填入表单字段。
**Architecture:** 解析逻辑独立为纯函数工具 `crm-invoice-paste-recognize.ts`(可单测);`InvoiceInfo.vue` 仅负责 UI 与回填。无后端/大模型依赖。未映射字段(行号、邮编、邮寄)追加写入 `remark`。
**Tech Stack:** Vue 3 + Element Plus;Node `tsx --test` + `node:test`(与 `pl-cert-slots.test.ts` 一致)
**Spec:** `docs/superpowers/specs/2026-08-20-crm-invoice-paste-recognize-design.md`
**Demo:** `docs/demo-crm-invoice-paste-recognize.html`
---
## File map
| 操作 | 文件 | 职责 |
|------|------|------|
| Create | `web-admin/src/utils/crm-invoice-paste-recognize.ts` | 解析开票粘贴文本 → 结构化结果 |
| Create | `web-admin/src/utils/crm-invoice-paste-recognize.test.ts` | 两段业务样例 + 边界单测 |
| Modify | `web-admin/src/views/crm/customer/components/InvoiceInfo.vue` | 弹窗顶部粘贴区 + 识别回填 |
| Optional | `docs/demo-crm-invoice-paste-recognize.html` | 若与实现规则漂移则同步 |
| Modify | graphify | `./scripts/update-graph.sh frontend` |
**Commit 约定:** 仅在用户明确要求时执行 git commit;计划中的 Commit 步骤默认跳过。
---
### Task 1: 解析工具单测(先红)
**Files:**
- Create: `web-admin/src/utils/crm-invoice-paste-recognize.test.ts`
- Create: `web-admin/src/utils/crm-invoice-paste-recognize.ts`(本 Task 仅写最小 stub,使测试可 import;完整实现在 Task 2)
- [x] **Step 1: 写入失败测试(完整样例断言)**
创建 `web-admin/src/utils/crm-invoice-paste-recognize.test.ts`:
```ts
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import {
CRM_INVOICE_PASTE_SAMPLE_ANQING,
CRM_INVOICE_PASTE_SAMPLE_JIANGFENG,
parseCrmInvoicePaste,
mergeInvoiceRemark
} from './crm-invoice-paste-recognize'
describe('parseCrmInvoicePaste · 安庆山峡样例', () => {
it('maps core fields and packs extra into remarkParts', () => {
const r = parseCrmInvoicePaste(CRM_INVOICE_PASTE_SAMPLE_ANQING)
assert.equal(r.companyName, '安庆市山峡化工有限公司')
assert.equal(r.taxNo, '9134080006084212XH')
assert.equal(r.bankName, '中国建设银行安庆分行城中支行')
assert.equal(r.bankAccount, '34050168860800000121')
assert.equal(r.companyAddress, '安徽省安庆市高新区霞虹路5号')
assert.equal(r.phoneFax, '0556-5348918')
assert.ok(r.filledCount >= 6)
assert.ok(r.remarkParts.some((p) => p.includes('银行行号') && p.includes('105368000056')))
assert.ok(r.remarkParts.some((p) => p.includes('邮编') && p.includes('246000')))
assert.ok(r.remarkParts.some((p) => p.includes('邮寄') && p.includes('汪璇')))
// 邮寄块内地址不得覆盖单位地址
assert.equal(r.companyAddress, '安徽省安庆市高新区霞虹路5号')
})
})
describe('parseCrmInvoicePaste · 江峰生物样例', () => {
it('handles spaced labels, spaced account, and 地址及电话', () => {
const r = parseCrmInvoicePaste(CRM_INVOICE_PASTE_SAMPLE_JIANGFENG)
assert.equal(r.companyName, '安徽江峰生物科技有限公司')
assert.equal(r.taxNo, '91340200MA8PYJNB8X')
assert.equal(r.bankName, '中国建设银行芜湖经开区支行')
assert.equal(r.bankAccount, '34050167880800002481')
assert.ok((r.companyAddress || '').includes('安徽省芜湖市'))
assert.ok(!(r.companyAddress || '').includes('0553'))
assert.equal(r.phoneFax, '0553-5201019')
assert.ok(r.remarkParts.some((p) => p.includes('银行行号') && p.includes('105362050066')))
})
})
describe('parseCrmInvoicePaste · 边界', () => {
it('returns empty filledCount for blank text', () => {
const r = parseCrmInvoicePaste(' \n ')
assert.equal(r.filledCount, 0)
assert.ok(r.warnings.length > 0)
})
it('does not treat 名称 inside unrelated prose without colon as company', () => {
const r = parseCrmInvoicePaste('请查收开票资料,谢谢')
assert.equal(r.filledCount, 0)
})
})
describe('mergeInvoiceRemark', () => {
it('appends new parts without duplicating existing', () => {
const next = mergeInvoiceRemark('已有备注', ['银行行号:105', '邮编:246000'])
assert.equal(next, '已有备注;银行行号:105;邮编:246000')
const again = mergeInvoiceRemark(next, ['银行行号:105', '邮寄:张三'])
assert.equal(again, '已有备注;银行行号:105;邮编:246000;邮寄:张三')
})
it('returns existing when parts empty', () => {
assert.equal(mergeInvoiceRemark('x', []), 'x')
assert.equal(mergeInvoiceRemark('', ['a']), 'a')
})
})
```
- [ ] **Step 2: 写入最小 stub(使 import 不炸,测试应失败)**
创建 `web-admin/src/utils/crm-invoice-paste-recognize.ts`:
```ts
export const CRM_INVOICE_PASTE_SAMPLE_ANQING = `开票资料:
公司名称:安庆市山峡化工有限公司
纳税人识别号:9134080006084212XH
开户行:中国建设银行安庆分行城中支行
帐号:34050168860800000121
银行行号:105368000056
地址:安徽省安庆市高新区霞虹路5号
电话/传真:0556-5348918
邮编:246000
发票邮寄地址:
姓名:汪璇
电话:18895395317
地址:安徽省安庆市高新区霞虹路5号`
export const CRM_INVOICE_PASTE_SAMPLE_JIANGFENG = `单位名:安徽江峰生物科技有限公司
税 号:91340200MA8PYJNB8X
地址及电话:安徽省芜湖市皖江江北新兴产业集中区沈巷片区起步区皖跃路与和睦路交叉口西南角福州路4号
银行开户行:中国建设银行芜湖经开区支行
账 号:3405 0167 8808 0000 2481
银行行号:1053 6205 0066
电话:0553-5201019`
export interface CrmInvoicePasteResult {
companyName?: string
taxNo?: string
companyAddress?: string
legalRepresentative?: string
agent?: string
phoneFax?: string
bankName?: string
bankAccount?: string
remarkParts: string[]
warnings: string[]
filledCount: number
}
export function parseCrmInvoicePaste(_raw: string): CrmInvoicePasteResult {
return { remarkParts: [], warnings: ['not implemented'], filledCount: 0 }
}
export function mergeInvoiceRemark(existing: string | undefined, parts: string[]): string {
return (existing || '').trim()
}
```
- [ ] **Step 3: 跑测试确认失败**
```bash
cd web-admin && pnpm exec tsx --test src/utils/crm-invoice-paste-recognize.test.ts
```
Expected: FAIL(断言 companyName 等不符)
- [ ] **Step 4: Commit(仅用户要求时)** — 跳过默认
---
### Task 2: 实现解析工具(转绿)
**Files:**
- Modify: `web-admin/src/utils/crm-invoice-paste-recognize.ts`
- [ ] **Step 1: 用完整实现替换 stub**
将 `parseCrmInvoicePaste` / `mergeInvoiceRemark` 换成下列逻辑(可整文件替换 stub 导出之后的实现部分;样例常量与 interface 保留):
```ts
function normalizeSpaces(s: string): string {
return (s || '').replace(/\s+/g, ' ').trim()
}
function splitMailBlock(text: string): { main: string; mail: string } {
const idx = text.search(/发票邮寄地址\s*[::]?/)
if (idx < 0) return { main: text, mail: '' }
return {
main: text.slice(0, idx),
mail: text.slice(idx).replace(/^发票邮寄地址\s*[::]?/, '')
}
}
function pickLine(block: string, labelRe: RegExp): string {
const lines = block.split(/\r?\n/)
for (const raw of lines) {
const line = raw.trim()
if (!line) continue
const m = line.match(labelRe)
if (m && m[1] != null) return normalizeSpaces(m[1])
}
return ''
}
const PHONE_TAIL =
/((?:0\d{2,3}-?\d{7,8})|(?:1\d{10}))\s*$/
export function parseCrmInvoicePaste(raw: string): CrmInvoicePasteResult {
const warnings: string[] = []
const text = (raw || '').replace(/\r\n/g, '\n').trim()
if (!text) {
return { remarkParts: [], warnings: ['请先粘贴开票资料文本'], filledCount: 0 }
}
const { main, mail } = splitMailBlock(text)
const remarkParts: string[] = []
const companyName = pickLine(
main,
/^(?:公司名称|单位名称|单位名|名称)\s*[::]\s*(.+)$/
)
const taxNo = pickLine(
main,
/^(?:纳税人识别号|纳税识别号|统一社会信用代码|税\s*号|税号)\s*[::]\s*([0-9A-Za-z]+)\s*$/
)
const bankName = pickLine(
main,
/^(?:银行开户行|开户银行|开户行)\s*[::]\s*(.+)$/
)
let bankAccount = pickLine(
main,
/^(?:银行账号|账\s*号|帐\s*号|账号|帐号)\s*[::]\s*([0-9\s]+)\s*$/
)
if (bankAccount) bankAccount = bankAccount.replace(/\s+/g, '')
let companyAddress = ''
let phoneFax = ''
const addrPhone = pickLine(main, /^(?:地址及电话)\s*[::]\s*(.+)$/)
if (addrPhone) {
const m = addrPhone.match(PHONE_TAIL)
if (m && m.index != null) {
phoneFax = m[1]
companyAddress = normalizeSpaces(addrPhone.slice(0, m.index))
} else {
companyAddress = addrPhone
}
} else {
companyAddress = pickLine(main, /^(?:单位地址|地\s*址|地址)\s*[::]\s*(.+)$/)
}
if (!phoneFax) {
phoneFax =
pickLine(main, /^(?:电话\/传真|电话传真|传真)\s*[::]\s*(.+)$/) ||
pickLine(main, /^(?:电\s*话|电话)\s*[::]\s*(.+)$/)
}
const legalRepresentative = pickLine(
main,
/^(?:法定代表人|法人代表|法人)\s*[::]\s*(.+)$/
)
const agent = pickLine(main, /^(?:委托代理人|代理人)\s*[::]\s*(.+)$/)
const bankCode = pickLine(main, /^(?:银行行号|联行号|行号)\s*[::]\s*([0-9\s]+)\s*$/)
if (bankCode) remarkParts.push(`银行行号:${bankCode.replace(/\s+/g, '')}`)
const zip = pickLine(main, /^(?:邮\s*编|邮编)\s*[::]\s*(\d{6})\s*$/)
if (zip) remarkParts.push(`邮编:${zip}`)
if (mail.trim()) {
const name = pickLine(mail, /^(?:姓\s*名|姓名)\s*[::]\s*(.+)$/)
const phone = pickLine(mail, /^(?:电\s*话|电话|手机)\s*[::]\s*(.+)$/)
const addr = pickLine(mail, /^(?:地\s*址|地址)\s*[::]\s*(.+)$/)
const bits = [name && `收件人 ${name}`, phone, addr].filter(Boolean) as string[]
if (bits.length) remarkParts.push(`邮寄:${bits.join(' / ')}`)
}
const fields = {
companyName: companyName || undefined,
taxNo: taxNo || undefined,
companyAddress: companyAddress || undefined,
legalRepresentative: legalRepresentative || undefined,
agent: agent || undefined,
phoneFax: phoneFax || undefined,
bankName: bankName || undefined,
bankAccount: bankAccount || undefined
}
const filledCount = Object.values(fields).filter(Boolean).length
if (!companyName) warnings.push('未识别到单位名称')
if (!taxNo) warnings.push('未识别到税号')
if (filledCount === 0) warnings.push('未能识别任何开票字段,请检查格式或手动填写')
return { ...fields, remarkParts, warnings, filledCount }
}
export function mergeInvoiceRemark(existing: string | undefined, parts: string[]): string {
const base = (existing || '').trim()
const extras = (parts || []).map((p) => p.trim()).filter(Boolean)
if (!extras.length) return base
let next = base
for (const p of extras) {
if (!next) {
next = p
continue
}
if (next.includes(p)) continue
next = `${next};${p}`
}
return next
}
```
注意:江峰样例「地址及电话」行**没有**末尾电话(电话在单独「电话:」行),`PHONE_TAIL` 不应误切地址;电话由后续 `电话` 行填入。安庆样例「地址」在 main 段、邮寄段另有「地址」——`splitMailBlock` 保证单位地址只从 main 取。
- [ ] **Step 2: 跑测试确认通过**
```bash
cd web-admin && pnpm exec tsx --test src/utils/crm-invoice-paste-recognize.test.ts
```
Expected: 全部 PASS
若江峰「地址及电话」行实际含电话或格式有空格差异导致失败:按失败断言微调 `pickLine` / 样例常量,保持业务语义。
- [ ] **Step 3: Commit(仅用户要求时)** — 跳过默认
---
### Task 3: InvoiceInfo.vue 接入粘贴识别 UI
**Files:**
- Modify: `web-admin/src/views/crm/customer/components/InvoiceInfo.vue`
- [ ] **Step 1: template — 在 `el-form` 内、`.form-grid-2` 前插入粘贴面板**
在 `<el-form ... class="crm-dialog-form">` 后立即加入:
```vue
<div class="invoice-paste-panel">
<div class="invoice-paste-panel__hd">
<span class="invoice-paste-panel__title">粘贴识别</span>
<span class="invoice-paste-panel__tip">粘贴开票资料文本后点「识别并填入」,将覆盖下方对应字段</span>
</div>
<el-input
v-model="pasteText"
type="textarea"
:rows="5"
placeholder="在此粘贴客户发来的开票资料…"
/>
<div class="invoice-paste-panel__actions">
<el-button class="btn-ghost" @click="clearPaste">
<el-icon><Delete /></el-icon>清空
</el-button>
<el-button class="btn-main" type="primary" @click="handleRecognizePaste">
<el-icon><DocumentCopy /></el-icon>识别并填入
</el-button>
</div>
</div>
```
- [ ] **Step 2: script — import、状态、处理函数**
1. 图标增加 `DocumentCopy`(已有 `Delete`)。
2. 增加:
```ts
import {
parseCrmInvoicePaste,
mergeInvoiceRemark
} from '@/utils/crm-invoice-paste-recognize'
const pasteText = ref('')
const clearPaste = () => {
pasteText.value = ''
}
const handleRecognizePaste = () => {
const parsed = parseCrmInvoicePaste(pasteText.value)
if (!parsed.filledCount && !parsed.remarkParts.length) {
ElMessage.warning(parsed.warnings[0] || '未能识别,请检查格式或手动填写')
return
}
const keys = [
'companyName',
'taxNo',
'companyAddress',
'legalRepresentative',
'agent',
'phoneFax',
'bankName',
'bankAccount'
] as const
for (const k of keys) {
const v = parsed[k]
if (v) form.value[k] = v
}
if (parsed.remarkParts.length) {
form.value.remark = mergeInvoiceRemark(form.value.remark, parsed.remarkParts)
}
const tip =
`已识别 ${parsed.filledCount} 个字段` +
(parsed.remarkParts.length ? `,另有 ${parsed.remarkParts.length} 项写入备注` : '') +
(parsed.warnings.length ? `;${parsed.warnings.join(';')}` : '')
if (parsed.warnings.length && parsed.filledCount === 0) {
ElMessage.warning(tip)
} else if (parsed.warnings.length) {
ElMessage.warning(tip)
} else {
ElMessage.success(tip)
}
}
```
3. 在 `reset` 末尾增加 `pasteText.value = ''`。
- [ ] **Step 3: scoped 样式**
```scss
.invoice-paste-panel {
margin-bottom: 16px;
padding: 12px;
border: 1px dashed #6ee7b7;
border-radius: 8px;
background: var(--primary-bg, #ecfdf5);
}
.invoice-paste-panel__hd {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 8px;
margin-bottom: 8px;
}
.invoice-paste-panel__title {
font-weight: 600;
color: var(--primary-dark, #059669);
}
.invoice-paste-panel__tip {
font-size: 12px;
color: var(--mint-text-muted, #64748b);
}
.invoice-paste-panel__actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 10px;
}
```
- [ ] **Step 4: 手工验收清单**
1. 打开 `/#/crm/customer` → 客户详情 → 开票信息 → 添加开票信息
2. 粘贴安庆样例 → 识别并填入 → 六项主字段正确,备注含行号/邮编/邮寄
3. 再粘贴江峰样例 → 识别并填入 → 字段被覆盖为江峰数据
4. 「清空」只清粘贴框,表单不变
5. 点确定仍走原有保存接口
- [ ] **Step 5: Commit(仅用户要求时)** — 跳过默认
---
### Task 4: 收尾
**Files:**
- Optional sync: `docs/demo-crm-invoice-paste-recognize.html`(若 Demo 规则与实现不一致再改)
- Spec 状态:将 `docs/superpowers/specs/2026-08-20-crm-invoice-paste-recognize-design.md` 顶部状态改为「已实现(待联调验收)」
- Graphify: `./scripts/update-graph.sh frontend`
- [ ] **Step 1: 更新 spec 状态行**
- [ ] **Step 2: 跑单测再确认绿**
```bash
cd web-admin && pnpm exec tsx --test src/utils/crm-invoice-paste-recognize.test.ts
```
- [ ] **Step 3: 更新前端图谱**
```bash
./scripts/update-graph.sh frontend
```
- [ ] **Step 4: Commit(仅用户要求时)** — 跳过默认
---
## Spec coverage(自检)
| Spec 要求 | Task |
|-----------|------|
| 方案 A 顶部粘贴区 | Task 3 |
| 纯前端规则 | Task 1–2 |
| 覆盖已识别字段 | Task 3 `handleRecognizePaste` |
| 空解析值不清空字段 | Task 3 仅 `if (v)` 赋值 |
| remark 追加、去重 | Task 2 `mergeInvoiceRemark` |
| 安庆/江峰样例 | Task 1–2 单测 |
| 不改后端/API | 无相关 Task |
## Placeholder scan
无 TBD / 「类似 Task N」占位。
@@ -0,0 +1,38 @@
# 用户列表角色展示与角色详情复用 — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 用户管理列表展示角色,点击打开与角色管理完全一致的查看/配置弹窗(本页复用)。
**Architecture:** 抽取 `RoleManageDialogs`;列表接口补 `roleIds`;按 roleId 拉详情打开查看。
**Tech Stack:** FastAPI + Vue 3 + Element Plus
---
### Task 1: 后端列表 roleIds + 角色详情
**Files:**
- Modify: `backend/app/api/admin.py`
- Modify: `web-admin/src/api/admin.ts`
- [x] 用户列表批量填充 `roleIds`
- [x] 新增 `GET /web/admin/role-management/{role_id}` 与 `fetchRoleDetail`
### Task 2: 抽取 RoleManageDialogs
**Files:**
- Create: `web-admin/src/components/business/role-manage-dialogs/index.vue`
- Modify: `web-admin/src/views/system/role/index.vue`
- [x] 抽出查看/编辑/菜单/数据权限弹窗与逻辑
- [x] expose: `openAdd` / `openEdit` / `openView` / `openMenuConfig` / `openDataScope` / `openDelete`
- [x] emit `changed`;角色页接入
### Task 3: 用户管理接入
**Files:**
- Modify: `web-admin/src/views/system/user/index.vue`
- [x] 列表「角色」列(可点 tag)
- [x] 挂载 RoleManageDialogs;详情区角色可点
@@ -0,0 +1,144 @@
# CRM 客户开票信息 · 粘贴快速识别 · 设计说明
> Demo:`docs/demo-crm-invoice-paste-recognize.html`(方案 A)
> 日期:2026-08-20
> 状态:已实现(待联调验收)
## 1. 背景与目标
客户管理详情弹窗右侧「开票信息」新增/编辑时,业务常从微信/邮件粘贴整段「开票资料」文本,再手工逐项填入,效率低且易错。
**目标:** 在新增/编辑弹窗顶部提供「粘贴识别」区,用**纯前端规则**解析常见开票资料文本,并将结果填入对应表单字段;用户可再微调后保存。不依赖后端、不调用大模型。
**非目标:**
- 不新增数据库字段(银行行号、邮编、邮寄地址等仍无独立列)
- 不接阿里云 / OCR / 大模型
- 不改开票信息保存 API 与权限
## 2. 已确认决策
| 项 | 结论 |
|---|---|
| 入口布局 | 方案 A:新增/编辑弹窗表单顶部「粘贴识别」区 |
| 识别方式 | 纯前端规则(标签别名 + 行解析),零成本、离线 |
| 回填策略 | **识别后覆盖对应字段**(已有值一并覆盖) |
| 无表单字段的信息 | 写入 `remark`(银行行号、邮编、发票邮寄信息等) |
| 备注合并 | 识别产生的备注内容**追加到 remark 末尾**;若与已有内容重复则不重复追加 |
| 范围 | 仅 `web-admin` 客户详情 → `InvoiceInfo.vue`;**以及**客户新增弹窗 `CustomerFormDialog.vue` 底部开票信息(二级粘贴弹窗) |
## 3. 交互设计
组件:`web-admin/src/views/crm/customer/components/InvoiceInfo.vue`
在现有 `el-form` 顶部、`.form-grid-2` 字段区之前增加粘贴区:
```
el-dialog「添加/修改开票信息」
└─ el-form
├─ 粘贴识别面板(虚线边框 + primary-bg)
│ ├─ 标题「粘贴识别」+ 简短提示
│ ├─ el-input type=textarea(粘贴原文)
│ ├─ [清空] [识别并填入]
│ └─ 结果提示文案(成功/部分成功/失败)
└─ 原有字段栅格(单位名称、税号…)
```
行为:
1. 打开新增/编辑弹窗时,粘贴区默认**空**(不强制预填样例,避免误覆盖业务数据)。
2. 点击「识别并填入」→ 调用解析工具 → **覆盖**已映射字段;未映射信息写入/追加 `remark`。
3. 点击「清空」仅清空粘贴框与提示,**不**清空表单字段。
4. 关闭弹窗时重置粘贴区与表单(沿用现有 `reset`)。
5. 识别失败或零字段:`ElMessage.warning`,不改表单。
6. 部分成功:照常覆盖已识别字段,并用消息列出未识别的关键项(如缺单位名称)。
视觉对齐 Fresh Mint:主色 `#10b981` / `#059669` / `#ecfdf5`;按钮带图标(DocumentCopy / Delete / Close / Check 等既有习惯)。
## 4. 解析规则
新增工具:`web-admin/src/utils/crm-invoice-paste-recognize.ts`
参考既有模式:`web-admin/src/utils/pl-paste-recognize.ts`(纯函数、导出样例常量、返回 `warnings`)。
### 4.1 输入输出
```ts
interface CrmInvoicePasteResult {
companyName?: string
taxNo?: string
companyAddress?: string
legalRepresentative?: string
agent?: string
phoneFax?: string
bankName?: string
bankAccount?: string
/** 无独立字段的附加信息,供写入 remark */
remarkParts: string[]
warnings: string[]
/** 成功写入表单的字段数(不含 remarkParts) */
filledCount: number
}
```
### 4.2 标签别名 → 字段
| 字段 | 标签别名(行首,支持全/半角冒号、标签内空格) |
|---|---|
| companyName | 公司名称、单位名称、单位名、名称 |
| taxNo | 纳税人识别号、纳税识别号、统一社会信用代码、税号 |
| companyAddress | 单位地址、地址(见 4.3 邮寄段排除) |
| phoneFax | 电话/传真、电话传真、传真、电话 |
| bankName | 银行开户行、开户银行、开户行 |
| bankAccount | 银行账号、账号、帐号(去除内部空格) |
| legalRepresentative | 法定代表人、法人代表、法人 |
| agent | 代理人、委托代理人 |
「地址及电话」单独规则:整行取值后,若末尾匹配固话/手机则拆到 `phoneFax`,其余为 `companyAddress`。
### 4.3 段落与冲突
- 若存在「发票邮寄地址」标题,其后块内的「姓名 / 电话 / 地址」**不**写入 `companyName` / `phoneFax` / `companyAddress`,而是汇总进 `remarkParts`(如 `邮寄:汪璇 / 188… / 地址…`)。
- 「开票资料」等总标题行忽略。
- 银行行号 / 联行号 / 行号 → `remarkParts`:`银行行号:…`(去空格)。
- 邮编 → `remarkParts`:`邮编:…`。
### 4.4 验收样例(必须通过)
**样例 1(安庆山峡):** 公司名称、税号、开户行、帐号、地址、电话/传真正确回填;银行行号、邮编、邮寄信息进 remark。
**样例 2(江峰生物):** 单位名、税号、地址及电话拆分、开户行、带空格账号去空格、电话正确;银行行号进 remark。
单测:`web-admin/src/utils/crm-invoice-paste-recognize.test.ts`(与现有 vitest 风格一致,如 `pl-cert-slots.test.ts`)。
## 5. 数据流
```
用户粘贴文本
→ parseCrmInvoicePaste(text)
→ 覆盖 form 对应键(仅当解析值非空)
→ remark:将 remarkParts 用「;」拼接后追加到现有 remark
→ ElMessage 提示 filledCount / warnings
→ 用户点确定 → 现有 /web/crm/invoice-info/add|update(不变)
```
覆盖规则细节:某字段解析值为空字符串时,**不**清空该表单字段(只覆盖「识别到了」的字段)。
## 6. 风险与后续
| 风险 | 应对 |
|---|---|
| 新标签写法未覆盖 | 补别名;UI 提示可手动改 |
| 无标签散文文本 | 识别失败,手动填写 |
| 长期需行号等独立字段 | 另开变更加列;本设计不扩表 |
可选后续(本变更不做):规则失败时降级调大模型。
## 7. 实现清单(实现阶段)
1. 新增 `crm-invoice-paste-recognize.ts` + 单测(含两段业务样例)
2. 改 `InvoiceInfo.vue`:顶部粘贴区 + 识别回填
3. 保留/更新 Demo:`docs/demo-crm-invoice-paste-recognize.html`(与线上规则一致即可)
4. 更新 graphify(web-admin)
不改 backend、不改 SQL、不改 OpenSpec 基线(纯前端体验增强)。
@@ -0,0 +1,38 @@
# 运输计划列表 · 合同列展示与提醒
> 日期:2026-08-20
> 状态:已确认,按方案 1 实现
> 范围:`web-admin` 运输计划列表 `/#/pl/plan-module/plan` 合同列;合同完善页对齐展示
## 1. 背景与目标
合同列原仅展示已关联合同编号;缺合同时显示「—」,无法提醒业务补充。
目标:保留编号展示,并对**非内置**客户缺合同一侧显示「待补充」提醒;内置公司该侧缺合同不提醒。
## 2. 已确认决策
| 项 | 结论 |
|---|---|
| 实现路径 | 方案 1:前端共用 `planContractLines`(`utils/pl-plan-contract-display.ts`) |
| 内置公司 | `安庆市山峡化工有限公司`、`安徽云栋化工有限公司`(`isPlInternalCustomerName`) |
| 非内置且无合同 | `供`/`需` + `待补充`,文字色 `#d97706`,不加 Tag |
| 内置且无合同 | `供`/`需` + `—`,中性色,不提醒 |
| 有合同时 | 显示合同编号(多份则多行) |
| 非目标 | 不改列表 API;不做点击跳转 |
## 3. 展示规则
供、需两侧**各自至少一行**:
1. 该侧有关联合同 → 每份合同一行,显示 `contractNo`(无则 `contractId`)
2. 该侧无合同且客户为内置 → 一行,`—`
3. 该侧无合同且非内置 → 一行,`待补充`(警示色)
## 4. 合同完善页对齐(同日追加)
页面:`/#/pl/plan-module/plan-contract`
- 供方/需方副文案:`已关联N份合同` / `未关联合同`(内置仍为「内置 · 合同可免」)
- 「合同缺口」列:与运输计划合同列相同展示,右侧增加「补充/收起」按钮(原展开补充)
- 去掉操作列;计划查看仍通过计划编号链接 / 双击
@@ -0,0 +1,30 @@
# 用户管理列表展示角色并可打开角色详情 — 设计文档
> **日期**: 2026-08-20
> **状态**: 已确认
> **仓库**: shanxia2.0(`backend/` + `web-admin/`)
> **方案**: A — 抽取 `RoleManageDialogs` 本页复用
## 已确认决策
| # | 决策 |
|---|------|
| D1 | 用户列表展示用户角色;点击角色打开与角色管理「查看」一致的弹窗 |
| D2 | 弹窗含菜单配置 / 数据权限 / 编辑 / 删除,本页复用角色管理逻辑(不跳转) |
| D3 | 抽取可复用组件,角色管理页与用户管理页共用 |
| D4 | 用户列表接口批量返回 `roleIds`;新增角色详情 GET 供按 id 打开查看 |
## 后端
- `GET /web/admin/users`:当前页用户批量查 `sys_user_role`,写入 `roleIds`
- `GET /web/admin/role-management/{role_id}`:返回单条角色(租户校验)
## 前端
- 新建 `web-admin/src/components/business/role-manage-dialogs/index.vue`
- 角色页改为挂载该组件;工具栏/列表查看调用 expose 方法
- 用户页增加「角色」列(可点 tag);挂载同一组件;用户详情中角色亦可点
## 不在范围
- 按角色筛选用户;改角色权限模型
+1 -1
View File
@@ -1 +1 @@
{"/Users/wuzilong/work/00code/shanxia2.0/.agents/skills/brainstorming/scripts/start-server.sh":{"size":4606,"mtime_ns":1780108930000000000,"hash":"7c3c6c5cce454f755159758334ffb434c970a29960bbff6669efe0cb22d1f2fb"},"/Users/wuzilong/work/00code/shanxia2.0/.agents/skills/brainstorming/scripts/stop-server.sh":{"size":1314,"mtime_ns":1780108930000000000,"hash":"9581ff879388aaa10463aea1fc1e9f9c00d69f2106f055ac976f97513ab84aba"},"/Users/wuzilong/work/00code/shanxia2.0/.agents/skills/systematic-debugging/find-polluter.sh":{"size":1528,"mtime_ns":1780108930000000000,"hash":"8340aac47e1279874e46d7ecb4954b5faae531a883abf05b118951687d8ca044"},"/Users/wuzilong/work/00code/shanxia2.0/.cursor/mcp.json":{"size":468,"mtime_ns":1780120483000000000,"hash":"ce4b438767a095c6a830e4612c221303ea16ab46af23f438bfb033eace904c36"},"/Users/wuzilong/work/00code/shanxia2.0/.opencode/opencode.json":{"size":105,"mtime_ns":1780120095000000000,"hash":"936122822138b12f363fb9b7a8567edaa1f834c835137a4c72d81c17116ce11e"},"/Users/wuzilong/work/00code/shanxia2.0/.opencode/package.json":{"size":65,"mtime_ns":1780109751000000000,"hash":"cf97d417f1ebce0ba81ad4629a5345e13cc95cb799c6eb2632caead5364880de"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_17f66cf13ffeo6JpSbBDM7Jx4t.json":{"size":214,"mtime_ns":1780274139000000000,"hash":"26182a523926188400188b8cc668585f8e81586416fa2399fd3585b0c07ff86c"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_1818ffbf9ffezXCeRFVV3Jj01O.json":{"size":263,"mtime_ns":1780238536000000000,"hash":"bf2e860eaa6fa5051d4cb0f70a53cf419b558c57759154e559712efef636dcb5"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_181f43997ffeO8mkoWfZl8v2Ri.json":{"size":214,"mtime_ns":1780236423000000000,"hash":"8252a79ead22a73fe0e229ef4eb6e1aac159547cdc04607d4102c75b077501a2"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_1823f3f66ffesCD5qohVVcWtbf.json":{"size":214,"mtime_ns":1780230242000000000,"hash":"27ba5fb0606f8ddda44c4f01ce5fbce77d7429f890317c6a1a6f9456058b1f70"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_183d2d323ffeBMoLOLwwPmAXym.json":{"size":214,"mtime_ns":1780210339000000000,"hash":"26814a22a2f0ee80b65a7306c4f52becca5edb09358cc12b588efa896546f571"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_183f72fc4ffeCZI7jMs3P9gB5n.json":{"size":214,"mtime_ns":1780201410000000000,"hash":"cd6c278503c190ec5dae59675957e9a963cbd94ce09cf974b838a0e717b42817"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_184f894aeffej3cvErXgdYFhY8.json":{"size":263,"mtime_ns":1780201410000000000,"hash":"d3bb6c88a4c332c09d2684c3a34f10e311620956a8777a894cb1492ab15cce86"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_186a13679ffenULQIK102x2NfS.json":{"size":214,"mtime_ns":1780201410000000000,"hash":"eb2fe454abea5ef36854bc2858ba6e9ac5e8f4d18e0121901f007816e0e40ebe"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_187ac729affeBH1jDFb5X7rWz0.json":{"size":214,"mtime_ns":1780201410000000000,"hash":"b571d2be1c871201f1d572c536b57c89a277b0b3eb3f3a3e4125db5008b37a3f"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_18880f06fffeJSijqM7oWZ6JYI.json":{"size":214,"mtime_ns":1780201410000000000,"hash":"efb86e46a8d0c2af314450407bfacd3f422ec8f68025291a440760cffe4ca092"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_1892f6be2ffe1K80MWGl0oc2Fo.json":{"size":214,"mtime_ns":1780114611000000000,"hash":"9b109fac4a24678ea27b4afe2840bcd06c339b490508d6aa531e051fe3f034b7"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/__init__.py":{"size":21,"mtime_ns":1780113982000000000,"hash":"5d7c80dd077475acb7eb12227dc0e7cf38bbb5fd9e84c4eab9eb2be8c5406357"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/__init__.py":{"size":665,"mtime_ns":1780804649000000000,"hash":"349c40e55e8aae86d0913a17da2f9d338d842243e5fdf807bad52382bc2876d9"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/admin.py":{"size":52987,"mtime_ns":1786164471385093466,"hash":"4566289fd8c57c51d4047a461b3953f67f5ddb83dbd59cd999c9d0c77caf142a"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/bx/__init__.py":{"size":827,"mtime_ns":1784980394000000000,"hash":"7605bb52b5361261c5e04d52bec9a1e704b49dd4ffe5c561da079831e8d8db9e"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/bx/advance.py":{"size":13404,"mtime_ns":1784992736000000000,"hash":"7e4bacb178dde4fddc158713abe0eb9e8ab6fedf57e571701e45a5af3189f858"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/bx/attachment.py":{"size":5758,"mtime_ns":1782790898000000000,"hash":"f2d20fc5b2a98748835ae1710666f11eae1aa323d80a4678189bb2d032559b4b"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/bx/expense_item.py":{"size":3362,"mtime_ns":1782650913000000000,"hash":"142f0f6a3e45cb5d7b8153ee3048c5fb82258167264f325dd4fa8801b2e56c35"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/apLine truncated
{"/Users/wuzilong/work/00code/shanxia2.0/.agents/skills/brainstorming/scripts/start-server.sh":{"size":4606,"mtime_ns":1780108930000000000,"hash":"7c3c6c5cce454f755159758334ffb434c970a29960bbff6669efe0cb22d1f2fb"},"/Users/wuzilong/work/00code/shanxia2.0/.agents/skills/brainstorming/scripts/stop-server.sh":{"size":1314,"mtime_ns":1780108930000000000,"hash":"9581ff879388aaa10463aea1fc1e9f9c00d69f2106f055ac976f97513ab84aba"},"/Users/wuzilong/work/00code/shanxia2.0/.agents/skills/systematic-debugging/find-polluter.sh":{"size":1528,"mtime_ns":1780108930000000000,"hash":"8340aac47e1279874e46d7ecb4954b5faae531a883abf05b118951687d8ca044"},"/Users/wuzilong/work/00code/shanxia2.0/.cursor/mcp.json":{"size":468,"mtime_ns":1780120483000000000,"hash":"ce4b438767a095c6a830e4612c221303ea16ab46af23f438bfb033eace904c36"},"/Users/wuzilong/work/00code/shanxia2.0/.opencode/opencode.json":{"size":105,"mtime_ns":1780120095000000000,"hash":"936122822138b12f363fb9b7a8567edaa1f834c835137a4c72d81c17116ce11e"},"/Users/wuzilong/work/00code/shanxia2.0/.opencode/package.json":{"size":65,"mtime_ns":1780109751000000000,"hash":"cf97d417f1ebce0ba81ad4629a5345e13cc95cb799c6eb2632caead5364880de"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_17f66cf13ffeo6JpSbBDM7Jx4t.json":{"size":214,"mtime_ns":1780274139000000000,"hash":"26182a523926188400188b8cc668585f8e81586416fa2399fd3585b0c07ff86c"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_1818ffbf9ffezXCeRFVV3Jj01O.json":{"size":263,"mtime_ns":1780238536000000000,"hash":"bf2e860eaa6fa5051d4cb0f70a53cf419b558c57759154e559712efef636dcb5"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_181f43997ffeO8mkoWfZl8v2Ri.json":{"size":214,"mtime_ns":1780236423000000000,"hash":"8252a79ead22a73fe0e229ef4eb6e1aac159547cdc04607d4102c75b077501a2"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_1823f3f66ffesCD5qohVVcWtbf.json":{"size":214,"mtime_ns":1780230242000000000,"hash":"27ba5fb0606f8ddda44c4f01ce5fbce77d7429f890317c6a1a6f9456058b1f70"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_183d2d323ffeBMoLOLwwPmAXym.json":{"size":214,"mtime_ns":1780210339000000000,"hash":"26814a22a2f0ee80b65a7306c4f52becca5edb09358cc12b588efa896546f571"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_183f72fc4ffeCZI7jMs3P9gB5n.json":{"size":214,"mtime_ns":1780201410000000000,"hash":"cd6c278503c190ec5dae59675957e9a963cbd94ce09cf974b838a0e717b42817"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_184f894aeffej3cvErXgdYFhY8.json":{"size":263,"mtime_ns":1780201410000000000,"hash":"d3bb6c88a4c332c09d2684c3a34f10e311620956a8777a894cb1492ab15cce86"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_186a13679ffenULQIK102x2NfS.json":{"size":214,"mtime_ns":1780201410000000000,"hash":"eb2fe454abea5ef36854bc2858ba6e9ac5e8f4d18e0121901f007816e0e40ebe"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_187ac729affeBH1jDFb5X7rWz0.json":{"size":214,"mtime_ns":1780201410000000000,"hash":"b571d2be1c871201f1d572c536b57c89a277b0b3eb3f3a3e4125db5008b37a3f"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_18880f06fffeJSijqM7oWZ6JYI.json":{"size":214,"mtime_ns":1780201410000000000,"hash":"efb86e46a8d0c2af314450407bfacd3f422ec8f68025291a440760cffe4ca092"},"/Users/wuzilong/work/00code/shanxia2.0/.sisyphus/run-continuation/ses_1892f6be2ffe1K80MWGl0oc2Fo.json":{"size":214,"mtime_ns":1780114611000000000,"hash":"9b109fac4a24678ea27b4afe2840bcd06c339b490508d6aa531e051fe3f034b7"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/__init__.py":{"size":21,"mtime_ns":1780113982000000000,"hash":"5d7c80dd077475acb7eb12227dc0e7cf38bbb5fd9e84c4eab9eb2be8c5406357"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/__init__.py":{"size":665,"mtime_ns":1780804649000000000,"hash":"349c40e55e8aae86d0913a17da2f9d338d842243e5fdf807bad52382bc2876d9"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/admin.py":{"size":54082,"mtime_ns":1787220370865962181,"hash":"19b2510b1f322551a593aec7255899d2996d4018bea3d8592d43244eaccb864d"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/bx/__init__.py":{"size":827,"mtime_ns":1784980394000000000,"hash":"7605bb52b5361261c5e04d52bec9a1e704b49dd4ffe5c561da079831e8d8db9e"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/bx/advance.py":{"size":13404,"mtime_ns":1784992736000000000,"hash":"7e4bacb178dde4fddc158713abe0eb9e8ab6fedf57e571701e45a5af3189f858"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/bx/attachment.py":{"size":5758,"mtime_ns":1782790898000000000,"hash":"f2d20fc5b2a98748835ae1710666f11eae1aa323d80a4678189bb2d032559b4b"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/bx/expense_item.py":{"size":3362,"mtime_ns":1782650913000000000,"hash":"142f0f6a3e45cb5d7b8153ee3048c5fb82258167264f325dd4fa8801b2e56c35"},"/Users/wuzilong/work/00code/shanxia2.0/backend/app/apLine truncated
+84 -54
View File
@@ -895,8 +895,8 @@
"semantic_hash": ""
},
"src/api/admin.ts": {
"mtime": 1782858538.0,
"ast_hash": "8545ed8e302ba499591836e0782295d4",
"mtime": 1787220093.8625574,
"ast_hash": "fac11296ec53398f4691c208d921701b",
"semantic_hash": ""
},
"src/api/auth.ts": {
@@ -1055,8 +1055,8 @@
"semantic_hash": ""
},
"src/api/duty.ts": {
"mtime": 1786969299.1686106,
"ast_hash": "bd9f1237e7e6ea01b6bb1473c510cb71",
"mtime": 1787234045.9291797,
"ast_hash": "e8982d92c92ac54f079e481534b2a73d",
"semantic_hash": ""
},
"src/api/leave.ts": {
@@ -1080,8 +1080,8 @@
"semantic_hash": ""
},
"src/api/pl/index.ts": {
"mtime": 1786887373.7497141,
"ast_hash": "10ac14d0d43e9589be3578298ba896b1",
"mtime": 1787202702.8297155,
"ast_hash": "3b5d8d257960c7a03b624c8701714ae3",
"semantic_hash": ""
},
"src/api/pl/public-io-order.ts": {
@@ -1100,8 +1100,8 @@
"semantic_hash": ""
},
"src/api/public-duty.ts": {
"mtime": 1786968954.0059338,
"ast_hash": "127789ed27c3c669b980ff60f965a0c7",
"mtime": 1787231703.803332,
"ast_hash": "41f1ff2a6ddadf71da81aa6ecda7838f",
"semantic_hash": ""
},
"src/api/system.ts": {
@@ -1305,8 +1305,8 @@
"semantic_hash": ""
},
"src/components/business/pl-cert-form-list/index.vue": {
"mtime": 1787151550.8721821,
"ast_hash": "62ab0a61780ebb61c92bcfc4a66a9f24",
"mtime": 1787200499.0801086,
"ast_hash": "765bcb3446fc048ee4991b0e2f39af57",
"semantic_hash": ""
},
"src/components/business/pl-cert-list-attachment-cell/index.vue": {
@@ -1350,8 +1350,8 @@
"semantic_hash": ""
},
"src/components/business/pl-paste-recognize-dialog/index.vue": {
"mtime": 1787147807.4346647,
"ast_hash": "85a7a05e56ebc0f7cca073e36451abe0",
"mtime": 1787201026.672315,
"ast_hash": "eb5dcc11735086b2d7e154e61f795c27",
"semantic_hash": ""
},
"src/components/business/pl-personnel-cert-badges/index.vue": {
@@ -1460,18 +1460,18 @@
"semantic_hash": ""
},
"src/components/business/pl-vehicle-cert-badges/index.vue": {
"mtime": 1783850091.0,
"ast_hash": "c7bbed571b94aa4bf897410ad81ec536",
"mtime": 1787200477.8921318,
"ast_hash": "6421d199e5bcfd4cb285aee8b1f28f28",
"semantic_hash": ""
},
"src/components/business/pl-vehicle-cert-part-tabs/index.vue": {
"mtime": 1785644489.2764986,
"ast_hash": "bae6a74433f52d5e502620a827a5fca0",
"mtime": 1787200486.2461286,
"ast_hash": "d197df773d8ad038a8d65585fd69306c",
"semantic_hash": ""
},
"src/components/business/pl-vehicle-cert-part-view/index.vue": {
"mtime": 1783844812.0,
"ast_hash": "4cb51d89cbfacbc635f405599710ffa2",
"mtime": 1787200535.3856585,
"ast_hash": "8277088eda625235634300c8d730347b",
"semantic_hash": ""
},
"src/components/business/pl-vehicle-cert-personnel-tabs/index.vue": {
@@ -1480,8 +1480,8 @@
"semantic_hash": ""
},
"src/components/business/pl-vehicle-form-dialog/index.vue": {
"mtime": 1787150343.6059313,
"ast_hash": "77f4a08838e5d834b0a399459e9f2ab3",
"mtime": 1787201032.7385561,
"ast_hash": "831293e13201b4e080c30c212e302898",
"semantic_hash": ""
},
"src/components/business/pl-vehicle-library-select-dialog/index.vue": {
@@ -1559,6 +1559,11 @@
"ast_hash": "cf35a4a1a15585735b23d73f10776cc1",
"semantic_hash": ""
},
"src/components/business/role-manage-dialogs/index.vue": {
"mtime": 1787220629.7271721,
"ast_hash": "58554f68c0c15fc624e5508906dd210d",
"semantic_hash": ""
},
"src/components/business/system-view-dialog/ViewHighlights.vue": {
"mtime": 1783165880.0,
"ast_hash": "a9f4468de7ce3cf93bafa9bfbc66ccb8",
@@ -2065,8 +2070,8 @@
"semantic_hash": ""
},
"src/components/core/layouts/art-work-tab/index.vue": {
"mtime": 1786198065.0342228,
"ast_hash": "fe74285d72054ecc1afb663507948e3c",
"mtime": 1787219541.0344598,
"ast_hash": "8b676a7d606e1ccc2e72f5f1839845da",
"semantic_hash": ""
},
"src/components/core/list-toolbar-date-shortcuts/index.vue": {
@@ -2265,8 +2270,8 @@
"semantic_hash": ""
},
"src/constants/menu-badge-codes.ts": {
"mtime": 1783258107.0,
"ast_hash": "1904b11e968d07a433f2ef3e16a1618e",
"mtime": 1787203588.2981055,
"ast_hash": "7d79397a972a5e294a66bc6bfb9119db",
"semantic_hash": ""
},
"src/directives/business/highlight.ts": {
@@ -2515,8 +2520,8 @@
"semantic_hash": ""
},
"src/router/modules/pl.ts": {
"mtime": 1785440735.9462945,
"ast_hash": "3c56bc52f40b09ec2da3a2dc9db0fd55",
"mtime": 1787199071.7289624,
"ast_hash": "d17221d4726e34f1207420bf6b97b3d7",
"semantic_hash": ""
},
"src/router/modules/sqxp.ts": {
@@ -2585,8 +2590,8 @@
"semantic_hash": ""
},
"src/store/modules/worktab.ts": {
"mtime": 1786280245.6061282,
"ast_hash": "778bc0efe9357ad7d50f84b7506cca69",
"mtime": 1787219543.1887548,
"ast_hash": "e72022ba3b20f3408f2d06409dae3452",
"semantic_hash": ""
},
"src/types/api.d.ts": {
@@ -2714,6 +2719,16 @@
"ast_hash": "ab7a6866a0214f7246fe9aae2dad2e03",
"semantic_hash": ""
},
"src/utils/crm-invoice-paste-recognize.test.ts": {
"mtime": 1787206477.090261,
"ast_hash": "24ce207686a5dccd9eec1ec52b63132d",
"semantic_hash": ""
},
"src/utils/crm-invoice-paste-recognize.ts": {
"mtime": 1787206657.8001394,
"ast_hash": "1da265b2140afc0dcd51711c435c64fa",
"semantic_hash": ""
},
"src/utils/crm-opportunity-labels.ts": {
"mtime": 1782858529.0,
"ast_hash": "c022b96e5df75c265ee8a01e634ff323",
@@ -2914,9 +2929,14 @@
"ast_hash": "a7cc500302d53c098d473329ef9180fa",
"semantic_hash": ""
},
"src/utils/pl-cert-slots.test.ts": {
"mtime": 1787200416.5701144,
"ast_hash": "c3ff5174b6c21765c5a849a9f5b57a72",
"semantic_hash": ""
},
"src/utils/pl-cert-slots.ts": {
"mtime": 1785643488.9656408,
"ast_hash": "9248600d21502299389bfee571e40a91",
"mtime": 1787200543.3205378,
"ast_hash": "1c8fdb8e9b1ad4307eddb1dc7d36f629",
"semantic_hash": ""
},
"src/utils/pl-delivery-slip-template.ts": {
@@ -2980,8 +3000,8 @@
"semantic_hash": ""
},
"src/utils/pl-vehicle-cert.ts": {
"mtime": 1785643499.7194405,
"ast_hash": "2897093af801798ce064c7dd8f6b1b67",
"mtime": 1787200443.296327,
"ast_hash": "69caca801e20716c6d370edb817c4dbc",
"semantic_hash": ""
},
"src/utils/protocol-payment-installment.ts": {
@@ -3395,8 +3415,8 @@
"semantic_hash": ""
},
"src/views/crm/customer/CustomerFormDialog.vue": {
"mtime": 1786192012.7771738,
"ast_hash": "cf4a1b0d6bf2d7931b11fcc9af8a53d4",
"mtime": 1787207959.7869265,
"ast_hash": "796a937e91a3c7c73773735ef011eb8a",
"semantic_hash": ""
},
"src/views/crm/customer/DealDialog.vue": {
@@ -3430,8 +3450,8 @@
"semantic_hash": ""
},
"src/views/crm/customer/components/InvoiceInfo.vue": {
"mtime": 1786192314.8539593,
"ast_hash": "df3f9abef03d6db169f0356f5befe526",
"mtime": 1787207257.0870967,
"ast_hash": "9e76ec3e09c503d06f5b3bc29df87c99",
"semantic_hash": ""
},
"src/views/crm/customer/index.vue": {
@@ -3695,8 +3715,8 @@
"semantic_hash": ""
},
"src/views/duty/schedule/index.vue": {
"mtime": 1786969297.1906035,
"ast_hash": "ba4b75453086c1ff6413892bcfbb556c",
"mtime": 1787234462.2003765,
"ast_hash": "fe4682df19815d046f58b0dd5ca24eb9",
"semantic_hash": ""
},
"src/views/exception/403/index.vue": {
@@ -3715,8 +3735,8 @@
"semantic_hash": ""
},
"src/views/index/index.vue": {
"mtime": 1786162441.1627371,
"ast_hash": "bde6a32b1fc35cffb4e6d03ba4925173",
"mtime": 1787219541.0509884,
"ast_hash": "4abdf71f426a2bfefb3d6fbd599f5bca",
"semantic_hash": ""
},
"src/views/index/route-parent.vue": {
@@ -3829,6 +3849,11 @@
"ast_hash": "f19e1074f6f2b4c9f95bce7c483b59ea",
"semantic_hash": ""
},
"src/views/pl/plan-contract/index.vue": {
"mtime": 1787236690.242886,
"ast_hash": "75837d09720030c21f4c2e765b44cd26",
"semantic_hash": ""
},
"src/views/pl/plan/arrange.vue": {
"mtime": 1782858538.0,
"ast_hash": "3420c1a2d986f36d0b278defda6329ab",
@@ -3845,13 +3870,13 @@
"semantic_hash": ""
},
"src/views/pl/plan/index.vue": {
"mtime": 1787147466.1533017,
"ast_hash": "4a14368de655abf1e4361b93347cab33",
"mtime": 1787236512.340019,
"ast_hash": "5b01993aaf8703e98e1ee6b1b89783e6",
"semantic_hash": ""
},
"src/views/pl/self-vehicle/index.vue": {
"mtime": 1787152550.412662,
"ast_hash": "28955d3b01584179790f77895eb8285c",
"mtime": 1787201066.4023204,
"ast_hash": "b844faa5591ee5c69c7ccb4b6c6147d9",
"semantic_hash": ""
},
"src/views/pl/transport-address/index.vue": {
@@ -3895,13 +3920,13 @@
"semantic_hash": ""
},
"src/views/pl/vehicle/index.vue": {
"mtime": 1787152551.1184893,
"ast_hash": "01235ece9b227d4f55d36a8bdbc029d0",
"mtime": 1787201030.554322,
"ast_hash": "05293530c23f62935613d89b45d8a32c",
"semantic_hash": ""
},
"src/views/portal/home/index.vue": {
"mtime": 1786963399.1649768,
"ast_hash": "00fe6e9b3f878555f25c2752df37471c",
"mtime": 1787203595.089986,
"ast_hash": "b2db1a1777b7adb054c988f430a89047",
"semantic_hash": ""
},
"src/views/public/db/inventory/index.vue": {
@@ -3910,8 +3935,8 @@
"semantic_hash": ""
},
"src/views/public/duty/index.vue": {
"mtime": 1786969493.842352,
"ast_hash": "94311d43e582f4cf1d61b606bc947cc8",
"mtime": 1787234649.574401,
"ast_hash": "1050829eeb0e6a23ac33ab6d7bd1a84c",
"semantic_hash": ""
},
"src/views/public/pl/_shared/PublicIoOrderPage.vue": {
@@ -3975,8 +4000,8 @@
"semantic_hash": ""
},
"src/views/system/role/index.vue": {
"mtime": 1786180978.7866106,
"ast_hash": "3ccc9e18802aae71827e4fbc19379b75",
"mtime": 1787220228.2226913,
"ast_hash": "cfe8d00b62dea0dff0a3268b4c63e527",
"semantic_hash": ""
},
"src/views/system/site/index.vue": {
@@ -3995,8 +4020,8 @@
"semantic_hash": ""
},
"src/views/system/user/index.vue": {
"mtime": 1786181035.9460511,
"ast_hash": "d7cf20650351fe415c057decd8b4ee23",
"mtime": 1787234874.1930904,
"ast_hash": "6e35a35ca805783cc6b38eda3b6f28e9",
"semantic_hash": ""
},
"src/views/system/user/modules/user-dialog.vue": {
@@ -5428,5 +5453,10 @@
"mtime": 1780108931.0,
"ast_hash": "839d61e0376bb89e1c2bd458ecb2051b",
"semantic_hash": ""
},
"src/utils/pl-plan-contract-display.ts": {
"mtime": 1787236495.9425528,
"ast_hash": "13157c155af06090bf5bf8c1888ebc26",
"semantic_hash": ""
}
}
+24 -7
View File
@@ -231,28 +231,47 @@
"229": "index.vue",
"230": "syncStatsColumnWidths",
"231": "index.vue",
"232": "product.ts",
"232": "handleEdit",
"233": "Community 233",
"234": "Community 234",
"235": "index.vue",
"238": "detail.vue",
"235": "index.js",
"236": "fetchData",
"237": "handleDisburse",
"238": "pl-plan-contract-display.ts",
"239": "Community 239",
"240": "form.vue",
"241": "index.vue",
"240": "fetchData",
"241": "zh_CN.js",
"242": "Community 242",
"243": "Community 243",
"244": "product.ts",
"245": "index.js",
"246": "detail.vue",
"247": "Community 247",
"248": "Community 248",
"249": "loading.ts",
"250": "Iframe.vue",
"251": "handleDisburse",
"252": "detail.vue",
"253": "Community 253",
"254": "handleSearch",
"255": "Community 255",
"256": "loadUserOptions",
"257": "loadUserOptions",
"258": "getPlMaintenanceApplyStatusLabel",
"259": "Community 259",
"260": "Community 260",
"261": "Community 261",
"262": "form.vue",
"263": "handleDelete",
"264": "handleSearch",
"265": "Community 265",
"266": "v2.4.2.3 - 2025-06-18",
"267": "index.vue",
"268": "handleAdd",
"269": "Community 269",
"270": "Community 270",
"271": "getInventoryStatCellValue",
"272": "handleEdit",
"280": "Community 280",
"283": "Community 283",
"284": "Community 284",
@@ -305,7 +324,6 @@
"350": "Community 350",
"355": "Community 355",
"362": "Community 362",
"364": "Community 364",
"367": "Community 367",
"372": "detail.vue",
"377": "Community 377",
@@ -563,7 +581,6 @@
"1270": "index.js",
"1271": "plugin.js",
"1272": "plugin.min.js",
"1275": "index.js",
"1277": "plugin.js",
"1278": "plugin.min.js",
"1280": "plugin.js",
@@ -231,19 +231,13 @@
"229": "index.vue",
"230": "syncStatsColumnWidths",
"231": "index.vue",
"232": "product.ts",
"233": "Community 233",
"234": "Community 234",
"235": "index.vue",
"236": "canDeleteByCreator",
"237": "pickBxPageResult",
"238": "detail.vue",
"235": "index.js",
"239": "Community 239",
"240": "form.vue",
"241": "index.vue",
"241": "zh_CN.js",
"242": "Community 242",
"243": "Community 243",
"244": "zh_CN.js",
"245": "index.js",
"247": "Community 247",
"248": "Community 248",
@@ -308,7 +302,6 @@
"350": "Community 350",
"355": "Community 355",
"362": "Community 362",
"364": "Community 364",
"367": "Community 367",
"372": "detail.vue",
"377": "Community 377",
@@ -566,7 +559,6 @@
"1270": "index.js",
"1271": "plugin.js",
"1272": "plugin.min.js",
"1275": "index.js",
"1277": "plugin.js",
"1278": "plugin.min.js",
"1280": "plugin.js",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+3
View File
@@ -495,6 +495,9 @@ export function fetchRoleList(params?: { page?: number; pageSize?: number; keywo
totalPages: number
}>({ url: '/web/admin/role-management', params })
}
export function fetchRoleDetail(id: string) {
return request.get<Api.Role.RoleItem>({ url: `/web/admin/role-management/${id}` })
}
export function fetchCreateRole(data: {
roleName: string
dataScope?: string
@@ -248,13 +248,13 @@
} from '@/utils/pl-cert-slots'
import { matchRequiredCertName } from '@/utils/pl-personnel-cert'
import {
PL_CARRIER_VEHICLE_HEAD_REQUIRED_CERT_NAMES,
PL_CARRIER_VEHICLE_TRAILER_REQUIRED_CERT_NAMES,
PL_SELF_VEHICLE_HEAD_REQUIRED_CERT_NAMES,
PL_SELF_VEHICLE_TRAILER_REQUIRED_CERT_NAMES,
PL_VEHICLE_CERT_PART_HEAD,
PL_VEHICLE_CERT_PART_TRAILER,
normalizeVehicleCertPart
normalizeVehicleCertPart,
shouldRequireVehicleTrailerCerts,
shouldShowVehicleTrailerCertSlots
} from '@/utils/pl-vehicle-cert'
type CertRowWithKey = CertRowWithMeta & { _key: string }
@@ -308,7 +308,7 @@
)
const headPlateLabel = computed(() => props.licensePlate?.trim() || '—')
const hasTrailerPlateOption = computed(() => Boolean(props.trailerNo?.trim()))
const hasTrailerPlateOption = computed(() => shouldShowVehicleTrailerCertSlots(props.trailerNo))
const vehiclePlateOptions = computed(() => {
const options = [
@@ -339,15 +339,17 @@
return props.requiredCertNames
}
if (!props.enforceSelfVehicleRequired && !props.enforceCarrierVehicleRequired) return []
const part = normalizeVehicleCertPart(row.vehiclePart)
// 三方仅展示槽位,不按名称强制必填(依赖 _required=false)
if (props.enforceCarrierVehicleRequired) {
return part === PL_VEHICLE_CERT_PART_TRAILER
? PL_CARRIER_VEHICLE_TRAILER_REQUIRED_CERT_NAMES
: PL_CARRIER_VEHICLE_HEAD_REQUIRED_CERT_NAMES
return []
}
return part === PL_VEHICLE_CERT_PART_TRAILER
? PL_SELF_VEHICLE_TRAILER_REQUIRED_CERT_NAMES
: PL_SELF_VEHICLE_HEAD_REQUIRED_CERT_NAMES
const part = normalizeVehicleCertPart(row.vehiclePart)
if (part === PL_VEHICLE_CERT_PART_TRAILER) {
return shouldRequireVehicleTrailerCerts('self', props.trailerNo)
? PL_SELF_VEHICLE_TRAILER_REQUIRED_CERT_NAMES
: []
}
return PL_SELF_VEHICLE_HEAD_REQUIRED_CERT_NAMES
}
function isRowRequired(row: CertRowWithKey) {
@@ -57,8 +57,13 @@
<el-form-item label="主车号">
<el-input v-model="vehicleForm.licensePlate" maxlength="20" />
</el-form-item>
<el-form-item label="挂车号">
<el-input v-model="vehicleForm.trailerNo" maxlength="20" clearable />
<el-form-item label="挂车号" required>
<el-input
v-model="vehicleForm.trailerNo"
placeholder="必填;无挂车可填「货拉拉」"
maxlength="20"
clearable
/>
</el-form-item>
<el-form-item label="核载(吨)">
<el-input-number
@@ -310,6 +315,10 @@
ElMessage.warning('请填写主车车牌')
return
}
if (showVehicleFields.value && !vehicleForm.trailerNo.trim()) {
ElMessage.warning('请填写挂车号')
return
}
if (
props.mode === 'personnel-first' &&
!driverForm.personnelName.trim() &&
@@ -345,7 +354,7 @@
vehicle: needVehicle
? {
licensePlate: vehicleForm.licensePlate.trim(),
trailerNo: vehicleForm.trailerNo || undefined,
trailerNo: vehicleForm.trailerNo.trim(),
loadCapacity: vehicleForm.loadCapacity ?? undefined,
carrierId: carrierId.value || undefined,
carrierName: carrier?.carrierName
@@ -30,6 +30,7 @@ import {
PL_VEHICLE_CERT_PART_HEAD,
PL_VEHICLE_CERT_PART_TRAILER,
getVehiclePartRequiredCertBadges,
shouldShowVehicleTrailerCertSlots,
type PlVehicleCertCheckType
} from '@/utils/pl-vehicle-cert'
@@ -46,7 +47,7 @@ const props = withDefaults(
}
)
const hasTrailer = computed(() => Boolean(props.trailerNo?.trim()))
const hasTrailer = computed(() => shouldShowVehicleTrailerCertSlots(props.trailerNo))
const badgeGroups = computed(() => {
const groups = [
@@ -61,7 +61,8 @@ import {
PL_VEHICLE_CERT_PART_HEAD,
PL_VEHICLE_CERT_PART_TRAILER,
mergeVehicleCertsByPart,
splitVehicleCertsByPart
splitVehicleCertsByPart,
shouldShowVehicleTrailerCertSlots
} from '@/utils/pl-vehicle-cert'
type CertRowWithKey = CertRow & { _key?: string }
@@ -98,7 +99,7 @@ const unifiedCertFormRef = ref<CertFormListRef>()
const headCertFormRef = ref<CertFormListRef>()
const trailerCertFormRef = ref<CertFormListRef>()
const hasTrailer = computed(() => Boolean(props.trailerNo?.trim()))
const hasTrailer = computed(() => shouldShowVehicleTrailerCertSlots(props.trailerNo))
const headTabLabel = computed(() => props.licensePlate?.trim() || '主车')
const trailerTabLabel = computed(() => props.trailerNo?.trim() || '挂车')
@@ -48,6 +48,7 @@ import {
PL_VEHICLE_CERT_PART_TRAILER,
getVehiclePartCertMissingSuffix,
type PlVehicleCertCheckType,
shouldShowVehicleTrailerCertSlots,
splitVehicleCertsByPart
} from '@/utils/pl-vehicle-cert'
@@ -89,7 +90,7 @@ const trailerPartName = computed(
`${PL_VEHICLE_CERT_PART_TRAILER}-${props.vehicleId || props.trailerNo || props.licensePlate || 'vehicle'}`
)
const hasTrailer = computed(() => Boolean(props.trailerNo?.trim()))
const hasTrailer = computed(() => shouldShowVehicleTrailerCertSlots(props.trailerNo))
const headTabLabel = computed(() => props.licensePlate?.trim() || '主车')
const trailerTabLabel = computed(() => props.trailerNo?.trim() || '挂车')
@@ -30,7 +30,7 @@
<el-form-item label="挂车号" prop="trailerNo">
<el-input
v-model="formData.trailerNo"
placeholder="请输入挂车号"
placeholder="必填;无挂车可填「货拉拉」"
maxlength="20"
clearable
/>
@@ -167,7 +167,7 @@
:cert-name-options="certNameOptions"
layout="table"
:enforce-self-vehicle-required="props.vehicleType === 'self'"
:enforce-carrier-vehicle-required="false"
:enforce-carrier-vehicle-required="props.vehicleType === 'carrier'"
/>
</el-form>
@@ -271,7 +271,8 @@
const formRules = computed(() => {
const rules: Record<string, unknown[]> = {
licensePlate: [{ required: true, message: '请输入车牌号', trigger: 'blur' }]
licensePlate: [{ required: true, message: '请输入车牌号', trigger: 'blur' }],
trailerNo: [{ required: true, message: '请输入挂车号', trigger: 'blur' }]
}
if (props.vehicleType === 'self') {
rules.vehicleStatus = [{ required: true, message: '请选择车辆状态', trigger: 'change' }]
File diff suppressed because it is too large. Load diff
@@ -31,7 +31,7 @@
item.customTitle || formatMenuTitle(item.title)
}}</span>
<span
v-if="list.length > 1 && !item.fixedTab"
v-if="list.length > 1 && store.isTabClosable(item)"
class="worktab-close inline-flex flex-cc relative rounded-full tad-200"
@click.stop="closeWorktab('current', item.path)"
>
+3 -1
View File
@@ -8,6 +8,7 @@ export const MENU_BADGE_CODES = {
PL_VEHICLE_UREA_DISBURSE: 'pl:vehicle-urea-disburse:list',
PL_PLAN_CERT: 'pl:plan-cert:list',
PL_PLAN_ARCHIVE: 'pl:plan-archive:list',
PL_PLAN_CONTRACT: 'pl:plan-contract:list',
BX_ADVANCE_APPROVE: 'bx:advanceApprove:list',
BX_REIMBURSE_APPROVE: 'bx:reimburseApprove:list',
BX_VIEW_TODO: 'bx:viewTodo:list',
@@ -29,5 +30,6 @@ export const PL_MENU_BADGE_CODES = [
MENU_BADGE_CODES.PL_VEHICLE_MAINTENANCE_PROCESS,
MENU_BADGE_CODES.PL_VEHICLE_UREA_DISBURSE,
MENU_BADGE_CODES.PL_PLAN_CERT,
MENU_BADGE_CODES.PL_PLAN_ARCHIVE
MENU_BADGE_CODES.PL_PLAN_ARCHIVE,
MENU_BADGE_CODES.PL_PLAN_CONTRACT
] as const
+31 -4
View File
@@ -93,13 +93,31 @@ export const useWorktabStore = defineStore(
return opened.value.find((tab) => tab.path === path)
}
/** 门户首页(不可关闭、不可取消固定) */
const isHomeTab = (tab: Partial<WorkTab>): boolean => {
const home = HOME_PAGE_PATH || '/home'
return tab.path === home || tab.name === 'PortalHome'
}
/**
* 检查标签页是否可关闭
*/
const isTabClosable = (tab: WorkTab): boolean => {
if (isHomeTab(tab)) return false
return !tab.fixedTab
}
/** 确保首页始终为固定标签(修复持久化丢失 fixedTab 的情况) */
const ensureHomeTabFixed = (): void => {
const home = HOME_PAGE_PATH || '/home'
opened.value = opened.value.map((tab) =>
isHomeTab(tab) && !tab.fixedTab ? { ...tab, path: tab.path || home, fixedTab: true } : tab
)
if (current.value.path && isHomeTab(current.value) && !current.value.fixedTab) {
current.value = { ...current.value, fixedTab: true }
}
}
/**
* 过滤不应展示在 worktab 中的父级占位路由。
* `/system` 仅用于承载与重定向,真实标签页为其子路由(如 `/system/info/tenant`)。
@@ -247,7 +265,8 @@ export const useWorktabStore = defineStore(
}
if (existingIndex === -1) {
const newTab = { ...tab, fixedTab: singlePage ? false : tab.fixedTab }
const shouldFix = !singlePage && (Boolean(tab.fixedTab) || isHomeTab(tab))
const newTab = { ...tab, fixedTab: shouldFix }
if (singlePage) {
markTabsToRemove(opened.value.filter((t) => t.path !== newTab.path))
@@ -261,9 +280,9 @@ export const useWorktabStore = defineStore(
return
}
const insertIndex = tab.fixedTab ? findFixedTabInsertIndex() : opened.value.length
const insertIndex = shouldFix ? findFixedTabInsertIndex() : opened.value.length
if (tab.fixedTab) {
if (shouldFix) {
opened.value.splice(insertIndex, 0, newTab)
} else {
opened.value.push(newTab)
@@ -275,6 +294,8 @@ export const useWorktabStore = defineStore(
} else {
// 更新现有标签页(当动态路由参数或查询变更时,复用同一标签)
const existingTab = opened.value[existingIndex]
const shouldFix =
!singlePage && (Boolean(tab.fixedTab) || Boolean(existingTab.fixedTab) || isHomeTab(tab))
opened.value[existingIndex] = {
...existingTab,
@@ -282,7 +303,7 @@ export const useWorktabStore = defineStore(
params: tab.params,
query: tab.query,
title: tab.title || existingTab.title,
fixedTab: singlePage ? false : (tab.fixedTab ?? existingTab.fixedTab),
fixedTab: shouldFix,
keepAlive: tab.keepAlive ?? existingTab.keepAlive,
name: tab.name || existingTab.name,
icon: tab.icon || existingTab.icon
@@ -563,6 +584,10 @@ export const useWorktabStore = defineStore(
}
const tab = { ...opened.value[targetIndex] }
// 首页始终固定,不允许取消
if (isHomeTab(tab) && tab.fixedTab) {
return
}
tab.fixedTab = !tab.fixedTab
// 移除原位置
@@ -620,6 +645,8 @@ export const useWorktabStore = defineStore(
opened.value = validTabs
}
ensureHomeTabFixed()
if (isSinglePageMode()) {
enforceSinglePageTabs()
} else {
@@ -0,0 +1,67 @@
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import {
CRM_INVOICE_PASTE_SAMPLE_ANQING,
CRM_INVOICE_PASTE_SAMPLE_JIANGFENG,
parseCrmInvoicePaste,
mergeInvoiceRemark
} from './crm-invoice-paste-recognize'
describe('parseCrmInvoicePaste · 安庆山峡样例', () => {
it('maps core fields and packs extra into remarkParts', () => {
const r = parseCrmInvoicePaste(CRM_INVOICE_PASTE_SAMPLE_ANQING)
assert.equal(r.companyName, '安庆市山峡化工有限公司')
assert.equal(r.taxNo, '9134080006084212XH')
assert.equal(r.bankName, '中国建设银行安庆分行城中支行')
assert.equal(r.bankAccount, '34050168860800000121')
assert.equal(r.companyAddress, '安徽省安庆市高新区霞虹路5号')
assert.equal(r.phoneFax, '0556-5348918')
assert.ok(r.filledCount >= 6)
assert.ok(r.remarkParts.some((p) => p.includes('银行行号') && p.includes('105368000056')))
assert.ok(r.remarkParts.some((p) => p.includes('邮编') && p.includes('246000')))
assert.ok(r.remarkParts.some((p) => p.includes('邮寄') && p.includes('汪璇')))
// 邮寄块内地址不得覆盖单位地址
assert.equal(r.companyAddress, '安徽省安庆市高新区霞虹路5号')
})
})
describe('parseCrmInvoicePaste · 江峰生物样例', () => {
it('handles spaced labels, spaced account, and 地址及电话', () => {
const r = parseCrmInvoicePaste(CRM_INVOICE_PASTE_SAMPLE_JIANGFENG)
assert.equal(r.companyName, '安徽江峰生物科技有限公司')
assert.equal(r.taxNo, '91340200MA8PYJNB8X')
assert.equal(r.bankName, '中国建设银行芜湖经开区支行')
assert.equal(r.bankAccount, '34050167880800002481')
assert.ok((r.companyAddress || '').includes('安徽省芜湖市'))
assert.ok(!(r.companyAddress || '').includes('0553'))
assert.equal(r.phoneFax, '0553-5201019')
assert.ok(r.remarkParts.some((p) => p.includes('银行行号') && p.includes('105362050066')))
})
})
describe('parseCrmInvoicePaste · 边界', () => {
it('returns empty filledCount for blank text', () => {
const r = parseCrmInvoicePaste(' \n ')
assert.equal(r.filledCount, 0)
assert.ok(r.warnings.length > 0)
})
it('does not treat 名称 inside unrelated prose without colon as company', () => {
const r = parseCrmInvoicePaste('请查收开票资料,谢谢')
assert.equal(r.filledCount, 0)
})
})
describe('mergeInvoiceRemark', () => {
it('appends new parts without duplicating existing', () => {
const next = mergeInvoiceRemark('已有备注', ['银行行号:105', '邮编:246000'])
assert.equal(next, '已有备注;银行行号:105;邮编:246000')
const again = mergeInvoiceRemark(next, ['银行行号:105', '邮寄:张三'])
assert.equal(again, '已有备注;银行行号:105;邮编:246000;邮寄:张三')
})
it('returns existing when parts empty', () => {
assert.equal(mergeInvoiceRemark('x', []), 'x')
assert.equal(mergeInvoiceRemark('', ['a']), 'a')
})
})
@@ -0,0 +1,167 @@
export const CRM_INVOICE_PASTE_SAMPLE_ANQING = `开票资料:
公司名称:安庆市山峡化工有限公司
纳税人识别号:9134080006084212XH
开户行:中国建设银行安庆分行城中支行
帐号:34050168860800000121
银行行号:105368000056
地址:安徽省安庆市高新区霞虹路5号
电话/传真:0556-5348918
邮编:246000
发票邮寄地址:
姓名:汪璇
电话:18895395317
地址:安徽省安庆市高新区霞虹路5号`
export const CRM_INVOICE_PASTE_SAMPLE_JIANGFENG = `单位名:安徽江峰生物科技有限公司
税 号:91340200MA8PYJNB8X
地址及电话:安徽省芜湖市皖江江北新兴产业集中区沈巷片区起步区皖跃路与和睦路交叉口西南角福州路4号
银行开户行:中国建设银行芜湖经开区支行
账 号:3405 0167 8808 0000 2481
银行行号:1053 6205 0066
电话:0553-5201019`
export interface CrmInvoicePasteResult {
companyName?: string
taxNo?: string
companyAddress?: string
legalRepresentative?: string
agent?: string
phoneFax?: string
bankName?: string
bankAccount?: string
remarkParts: string[]
warnings: string[]
filledCount: number
}
function normalizeSpaces(s: string): string {
return (s || '').replace(/\s+/g, ' ').trim()
}
function splitMailBlock(text: string): { main: string; mail: string } {
const idx = text.search(/发票邮寄地址\s*[::]?/)
if (idx < 0) return { main: text, mail: '' }
return {
main: text.slice(0, idx),
mail: text.slice(idx).replace(/^发票邮寄地址\s*[::]?/, '')
}
}
function pickLine(block: string, labelRe: RegExp): string {
const lines = block.split(/\r?\n/)
for (const raw of lines) {
const line = raw.trim()
if (!line) continue
const m = line.match(labelRe)
if (m && m[1] != null) return normalizeSpaces(m[1])
}
return ''
}
const PHONE_TAIL =
/((?:0\d{2,3}-?\d{7,8})|(?:1\d{10}))\s*$/
export function parseCrmInvoicePaste(raw: string): CrmInvoicePasteResult {
const warnings: string[] = []
const text = (raw || '').replace(/\r\n/g, '\n').trim()
if (!text) {
return { remarkParts: [], warnings: ['请先粘贴开票资料文本'], filledCount: 0 }
}
const { main, mail } = splitMailBlock(text)
const remarkParts: string[] = []
const companyName = pickLine(
main,
/^(?:公司名称|单位名称|单位名|名称)\s*[::]\s*(.+)$/
)
const taxNo = pickLine(
main,
/^(?:纳税人识别号|纳税识别号|统一社会信用代码|税\s*号|税号)\s*[::]\s*([0-9A-Za-z]+)\s*$/
)
const bankName = pickLine(
main,
/^(?:银行开户行|开户银行|开户行)\s*[::]\s*(.+)$/
)
let bankAccount = pickLine(
main,
/^(?:银行账号|账\s*号|帐\s*号|账号|帐号)\s*[::]\s*([0-9\s]+)\s*$/
)
if (bankAccount) bankAccount = bankAccount.replace(/\s+/g, '')
let companyAddress = ''
let phoneFax = ''
const addrPhone = pickLine(main, /^(?:地址及电话)\s*[::]\s*(.+)$/)
if (addrPhone) {
const m = addrPhone.match(PHONE_TAIL)
if (m && m.index != null) {
phoneFax = m[1]
companyAddress = normalizeSpaces(addrPhone.slice(0, m.index))
} else {
companyAddress = addrPhone
}
} else {
companyAddress = pickLine(main, /^(?:单位地址|地\s*址|地址)\s*[::]\s*(.+)$/)
}
if (!phoneFax) {
phoneFax =
pickLine(main, /^(?:电话\/传真|电话传真|传真)\s*[::]\s*(.+)$/) ||
pickLine(main, /^(?:电\s*话|电话)\s*[::]\s*(.+)$/)
}
const legalRepresentative = pickLine(
main,
/^(?:法定代表人|法人代表|法人)\s*[::]\s*(.+)$/
)
const agent = pickLine(main, /^(?:委托代理人|代理人)\s*[::]\s*(.+)$/)
const bankCode = pickLine(main, /^(?:银行行号|联行号|行号)\s*[::]\s*([0-9\s]+)\s*$/)
if (bankCode) remarkParts.push(`银行行号:${bankCode.replace(/\s+/g, '')}`)
const zip = pickLine(main, /^(?:邮\s*编|邮编)\s*[::]\s*(\d{6})\s*$/)
if (zip) remarkParts.push(`邮编:${zip}`)
if (mail.trim()) {
const name = pickLine(mail, /^(?:姓\s*名|姓名)\s*[::]\s*(.+)$/)
const phone = pickLine(mail, /^(?:电\s*话|电话|手机)\s*[::]\s*(.+)$/)
const addr = pickLine(mail, /^(?:地\s*址|地址)\s*[::]\s*(.+)$/)
const bits = [name && `收件人 ${name}`, phone, addr].filter(Boolean) as string[]
if (bits.length) remarkParts.push(`邮寄:${bits.join(' / ')}`)
}
const fields = {
companyName: companyName || undefined,
taxNo: taxNo || undefined,
companyAddress: companyAddress || undefined,
legalRepresentative: legalRepresentative || undefined,
agent: agent || undefined,
phoneFax: phoneFax || undefined,
bankName: bankName || undefined,
bankAccount: bankAccount || undefined
}
const filledCount = Object.values(fields).filter(Boolean).length
if (!companyName) warnings.push('未识别到单位名称')
if (!taxNo) warnings.push('未识别到税号')
if (filledCount === 0) warnings.push('未能识别任何开票字段,请检查格式或手动填写')
return { ...fields, remarkParts, warnings, filledCount }
}
export function mergeInvoiceRemark(existing: string | undefined, parts: string[]): string {
const base = (existing || '').trim()
const extras = (parts || []).map((p) => p.trim()).filter(Boolean)
if (!extras.length) return base
let next = base
for (const p of extras) {
if (!next) {
next = p
continue
}
if (next.includes(p)) continue
next = `${next};${p}`
}
return next
}
+81
View File
@@ -0,0 +1,81 @@
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import {
collectSelfVehicleRequiredCertErrors,
collectCarrierVehicleRequiredCertErrors,
ensureSelfVehicleCertSlots,
ensureCarrierVehicleCertSlots
} from './pl-cert-slots'
import {
isHuolalaTrailerNo,
shouldRequireVehicleTrailerCerts,
shouldShowVehicleTrailerCertSlots,
PL_VEHICLE_CERT_PART_TRAILER
} from './pl-vehicle-cert'
function completeSelfHeadCerts() {
return [
{ certName: '行驶证', expireDate: '2030-01-01', vehiclePart: 'head' },
{ certName: '营运证', expireDate: '2030-01-01', vehiclePart: 'head' },
{ certName: '保险单', expireDate: '2030-01-01', vehiclePart: 'head' }
]
}
describe('trailer cert rule helpers', () => {
it('detects 货拉拉 and gates trailer slot/require', () => {
assert.equal(isHuolalaTrailerNo('货拉拉'), true)
assert.equal(isHuolalaTrailerNo(' 货拉拉 '), true)
assert.equal(isHuolalaTrailerNo('皖LP359'), false)
assert.equal(shouldShowVehicleTrailerCertSlots('货拉拉'), false)
assert.equal(shouldShowVehicleTrailerCertSlots('皖LP359'), true)
assert.equal(shouldRequireVehicleTrailerCerts('self', '货拉拉'), false)
assert.equal(shouldRequireVehicleTrailerCerts('self', '皖LP359'), true)
assert.equal(shouldRequireVehicleTrailerCerts('carrier', '皖LP359'), false)
})
})
describe('ensure trailer cert slots', () => {
it('hides trailer slots for 货拉拉', () => {
const selfSlots = ensureSelfVehicleCertSlots([], '货拉拉')
assert.equal(
selfSlots.some(c => c.vehiclePart === PL_VEHICLE_CERT_PART_TRAILER),
false
)
const carrierSlots = ensureCarrierVehicleCertSlots([], '货拉拉')
assert.equal(
carrierSlots.some(c => c.vehiclePart === PL_VEHICLE_CERT_PART_TRAILER),
false
)
})
it('shows required trailer slots for self normal trailer', () => {
const slots = ensureSelfVehicleCertSlots([], '皖LP359')
const trailer = slots.filter(c => c.vehiclePart === PL_VEHICLE_CERT_PART_TRAILER)
assert.ok(trailer.length > 0)
assert.ok(trailer.every(c => c._required === true))
})
it('shows optional trailer slots for carrier normal trailer', () => {
const slots = ensureCarrierVehicleCertSlots([], '皖LP359')
const trailer = slots.filter(c => c.vehiclePart === PL_VEHICLE_CERT_PART_TRAILER)
assert.ok(trailer.length > 0)
assert.ok(trailer.every(c => c._required === false))
})
})
describe('collect trailer cert errors', () => {
it('skips trailer validation for self 货拉拉', () => {
const errors = collectSelfVehicleRequiredCertErrors(completeSelfHeadCerts(), '货拉拉')
assert.deepEqual(errors, [])
})
it('requires trailer certs for self normal trailer', () => {
const errors = collectSelfVehicleRequiredCertErrors(completeSelfHeadCerts(), '皖LP359')
assert.ok(errors.some(e => e.includes('挂车')))
})
it('does not require trailer certs for carrier', () => {
const errors = collectCarrierVehicleRequiredCertErrors([], '皖LP359')
assert.deepEqual(errors, [])
})
})
+40 -15
View File
@@ -10,7 +10,9 @@ import {
type PlVehicleCertCheckType,
getVehicleHeadRequiredCertNames,
getVehicleTrailerRequiredCertNames,
normalizeVehicleCertPart
normalizeVehicleCertPart,
shouldRequireVehicleTrailerCerts,
shouldShowVehicleTrailerCertSlots
} from '@/utils/pl-vehicle-cert'
export type CertRowWithMeta = CertRow & { _key?: string; _required?: boolean }
@@ -75,6 +77,11 @@ export function collectVehicleRequiredCertErrors(
trailerNo: string | null | undefined,
vehicleType: PlVehicleCertCheckType
): string[] {
// 三方车辆证件不强制
if (vehicleType === 'carrier') {
return []
}
const headCerts = certList.filter(
c => normalizeVehicleCertPart(c.vehiclePart) === PL_VEHICLE_CERT_PART_HEAD
)
@@ -87,7 +94,7 @@ export function collectVehicleRequiredCertErrors(
getVehicleHeadRequiredCertNames(vehicleType),
'主车'
),
...(trailerNo?.trim()
...(shouldRequireVehicleTrailerCerts(vehicleType, trailerNo)
? collectRequiredCertSlotErrors(
trailerCerts,
getVehicleTrailerRequiredCertNames(vehicleType),
@@ -131,10 +138,11 @@ function findCertIndexForRequired(
export function mergeCertListWithRequiredSlots(
existing: CertRowWithMeta[],
requiredNames: readonly string[],
options?: { vehiclePart?: string; stripManagedNames?: readonly string[] }
options?: { vehiclePart?: string; stripManagedNames?: readonly string[]; markRequired?: boolean }
): CertRowWithMeta[] {
const vehiclePart = options?.vehiclePart
const stripManagedNames = options?.stripManagedNames
const markRequired = options?.markRequired !== false
const normalizedPart = vehiclePart ? normalizeVehicleCertPart(vehiclePart) : null
let source = existing
@@ -167,13 +175,13 @@ export function mergeCertListWithRequiredSlots(
...next[idx],
certName: requiredName,
vehiclePart: normalizedPart || next[idx].vehiclePart || PL_VEHICLE_CERT_PART_HEAD,
_required: true
_required: markRequired
}
}
return createEmptyCertRow(
requiredName,
normalizedPart || PL_VEHICLE_CERT_PART_HEAD,
true
markRequired
)
})
@@ -187,18 +195,26 @@ export function mergeCertListWithRequiredSlots(
function ensureVehicleCertSlotsByNames(
certList: CertRowWithMeta[],
trailerNo: string | null | undefined,
headRequired: readonly string[],
trailerRequired: readonly string[]
headNames: readonly string[],
trailerNames: readonly string[],
options?: { headRequired?: boolean; trailerRequired?: boolean }
): CertRowWithMeta[] {
let merged = mergeCertListWithRequiredSlots(certList, headRequired, {
vehiclePart: PL_VEHICLE_CERT_PART_HEAD
const headRequired = options?.headRequired !== false
const trailerRequired = Boolean(options?.trailerRequired)
let merged = mergeCertListWithRequiredSlots(certList, headNames, {
vehiclePart: PL_VEHICLE_CERT_PART_HEAD,
markRequired: headRequired
})
if (trailerNo?.trim()) {
merged = mergeCertListWithRequiredSlots(merged, trailerRequired, {
vehiclePart: PL_VEHICLE_CERT_PART_TRAILER
if (shouldShowVehicleTrailerCertSlots(trailerNo)) {
merged = mergeCertListWithRequiredSlots(merged, trailerNames, {
vehiclePart: PL_VEHICLE_CERT_PART_TRAILER,
markRequired: trailerRequired
})
} else {
merged = stripTrailerRequiredSlots(merged, trailerRequired)
// 无挂车号或货拉拉:移除全部挂车侧证书行
merged = merged.filter(
cert => normalizeVehicleCertPart(cert.vehiclePart) !== PL_VEHICLE_CERT_PART_TRAILER
)
}
return merged
}
@@ -211,7 +227,11 @@ export function ensureSelfVehicleCertSlots(
certList,
trailerNo,
PL_SELF_VEHICLE_HEAD_REQUIRED_CERT_NAMES,
PL_SELF_VEHICLE_TRAILER_REQUIRED_CERT_NAMES
PL_SELF_VEHICLE_TRAILER_REQUIRED_CERT_NAMES,
{
headRequired: true,
trailerRequired: shouldRequireVehicleTrailerCerts('self', trailerNo)
}
)
}
@@ -223,7 +243,12 @@ export function ensureCarrierVehicleCertSlots(
certList,
trailerNo,
PL_CARRIER_VEHICLE_HEAD_REQUIRED_CERT_NAMES,
PL_CARRIER_VEHICLE_TRAILER_REQUIRED_CERT_NAMES
PL_CARRIER_VEHICLE_TRAILER_REQUIRED_CERT_NAMES,
{
// 三方主车/挂车证件均展示槽位但不强制
headRequired: false,
trailerRequired: false
}
)
}
@@ -0,0 +1,45 @@
import { isPlInternalCustomerName } from '@/utils/pl-internal-customer'
export type PlanContractDisplayLine = {
side: string
no: string
pending?: boolean
}
type PlanContractLike = {
contractId?: string
contractNo?: string
}
type PlanContractParties = {
supplierName?: string | null
customerName?: string | null
supplierContracts?: PlanContractLike[]
demanderContracts?: PlanContractLike[]
}
/** 单侧:有编号显示编号;非内置缺合同「待补充」;内置缺合同「—」 */
export function planContractSideLines(
side: '供' | '需',
contracts: PlanContractLike[] | undefined,
customerName?: string | null
): PlanContractDisplayLine[] {
const lines: PlanContractDisplayLine[] = []
for (const item of contracts || []) {
const no = item.contractNo || item.contractId
if (no) lines.push({ side, no: String(no) })
}
if (lines.length) return lines
if (isPlInternalCustomerName(customerName)) {
return [{ side, no: '—' }]
}
return [{ side, no: '待补充', pending: true }]
}
/** 合同列:供/需各至少一行 */
export function planContractLines(plan: PlanContractParties): PlanContractDisplayLine[] {
return [
...planContractSideLines('供', plan.supplierContracts, plan.supplierName),
...planContractSideLines('需', plan.demanderContracts, plan.customerName)
]
}
+27 -2
View File
@@ -25,8 +25,29 @@ export const PL_CARRIER_VEHICLE_HEAD_REQUIRED_CERT_NAMES = ['行驶证', '营运
export const PL_CARRIER_VEHICLE_TRAILER_REQUIRED_CERT_NAMES = ['行驶证', '营运证'] as const
/** 特殊挂车号:不展示挂车证件槽位、不校验挂车证件 */
export const PL_HUOLALA_TRAILER_NO = '货拉拉'
export type PlVehicleCertCheckType = 'self' | 'carrier'
export function isHuolalaTrailerNo(trailerNo?: string | null): boolean {
return (trailerNo || '').trim() === PL_HUOLALA_TRAILER_NO
}
/** 有挂车号且非货拉拉时展示挂车证件区 */
export function shouldShowVehicleTrailerCertSlots(trailerNo?: string | null): boolean {
const text = (trailerNo || '').trim()
return Boolean(text) && !isHuolalaTrailerNo(text)
}
/** 仅山峡自有 + 正常挂车号时强制挂车证件 */
export function shouldRequireVehicleTrailerCerts(
vehicleType: PlVehicleCertCheckType,
trailerNo?: string | null
): boolean {
return vehicleType === 'self' && shouldShowVehicleTrailerCertSlots(trailerNo)
}
export const PL_VEHICLE_CERT_NAME_OPTIONS = [
'行驶证',
'营运证',
@@ -126,7 +147,11 @@ export function hasVehicleCertMissing(options: {
}): boolean {
const { vehicleType, certList = [], trailerNo } = options
const { headCerts, trailerCerts } = splitVehicleCertsByPart(certList)
const hasTrailer = Boolean(trailerNo?.trim())
// 三方车辆证件不强制,列表缺失后缀仅对自有生效
if (vehicleType === 'carrier') {
return false
}
if (
getMissingVehiclePartCertNames(headCerts, PL_VEHICLE_CERT_PART_HEAD, vehicleType).length > 0
@@ -135,7 +160,7 @@ export function hasVehicleCertMissing(options: {
}
if (
hasTrailer &&
shouldRequireVehicleTrailerCerts(vehicleType, trailerNo) &&
getMissingVehiclePartCertNames(trailerCerts, PL_VEHICLE_CERT_PART_TRAILER, vehicleType)
.length > 0
) {
@@ -207,6 +207,12 @@
<template v-if="!form.id">
<el-divider content-position="left">开票信息</el-divider>
<div class="form-grid-4">
<el-form-item label="快速识别">
<el-button class="btn-ghost" @click="openInvoicePasteDialog">
<el-icon><DocumentCopy /></el-icon>
快速识别
</el-button>
</el-form-item>
<el-form-item label="单位名称(章)">
<el-input
v-model="invoiceForm.companyName"
@@ -276,11 +282,49 @@
</div>
</template>
</el-dialog>
<el-dialog
v-model="invoicePasteDialog.visible"
title="快速识别开票信息"
width="640px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="order-module-dialog-mask"
:close-on-click-modal="false"
@close="closeInvoicePasteDialog"
>
<div class="invoice-paste-panel">
<p class="invoice-paste-panel__tip">粘贴开票资料文本后点「识别并填入」,将覆盖新增表单中的开票信息对应字段</p>
<el-input
v-model="invoicePasteText"
type="textarea"
:rows="10"
placeholder="在此粘贴客户发来的开票资料…"
/>
</div>
<template #footer>
<div class="dialog-footer">
<el-button class="btn-ghost" @click="clearInvoicePaste">
<el-icon><Delete /></el-icon>
清空
</el-button>
<el-button class="btn-ghost" @click="closeInvoicePasteDialog">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button class="btn-main" type="primary" @click="handleInvoicePasteRecognize">
<el-icon><Check /></el-icon>
识别并填入
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, nextTick } from 'vue'
import { Close, Check, Refresh } from '@element-plus/icons-vue'
import { Close, Check, Refresh, DocumentCopy, Delete } from '@element-plus/icons-vue'
import type { FormItemRule } from 'element-plus'
import { ElMessage } from 'element-plus'
import {
@@ -292,6 +336,10 @@ import {
import type { CustomerForm as CustomerFormType } from '@/api/crm/customerTypes'
import { addInvoiceInfo } from '@/api/crm/invoiceInfo'
import type { InvoiceInfoForm } from '@/api/crm/invoiceInfo'
import {
parseCrmInvoicePaste,
mergeInvoiceRemark
} from '@/utils/crm-invoice-paste-recognize'
import { useDictStore } from '@/store/modules/dict'
import { useUserStore } from '@/store/modules/user'
import { fetchUserOptions } from '@/api/admin'
@@ -475,6 +523,60 @@ function resetInvoiceForm() {
}
}
const invoicePasteDialog = reactive({
visible: false
})
const invoicePasteText = ref('')
function openInvoicePasteDialog() {
invoicePasteText.value = ''
invoicePasteDialog.visible = true
}
function closeInvoicePasteDialog() {
invoicePasteDialog.visible = false
invoicePasteText.value = ''
}
function clearInvoicePaste() {
invoicePasteText.value = ''
}
function handleInvoicePasteRecognize() {
const parsed = parseCrmInvoicePaste(invoicePasteText.value)
if (!parsed.filledCount && !parsed.remarkParts.length) {
ElMessage.warning(parsed.warnings[0] || '未能识别,请检查格式或手动填写')
return
}
const keys = [
'companyName',
'taxNo',
'companyAddress',
'legalRepresentative',
'agent',
'phoneFax',
'bankName',
'bankAccount'
] as const
for (const k of keys) {
const v = parsed[k]
if (v) invoiceForm.value[k] = v
}
if (parsed.remarkParts.length) {
invoiceForm.value.remark = mergeInvoiceRemark(invoiceForm.value.remark, parsed.remarkParts)
}
const tip =
`已识别 ${parsed.filledCount} 个字段` +
(parsed.remarkParts.length ? `,另有 ${parsed.remarkParts.length} 项写入备注` : '') +
(parsed.warnings.length ? `;${parsed.warnings.join(';')}` : '')
if (parsed.warnings.length) {
ElMessage.warning(tip)
} else {
ElMessage.success(tip)
}
closeInvoicePasteDialog()
}
async function syncInvoiceInfoAfterCreate(customerId: string | number) {
if (!hasInvoiceInfoFilled()) return
@@ -635,4 +737,11 @@ defineExpose({ open })
min-width: 0;
}
}
.invoice-paste-panel__tip {
margin: 0 0 10px;
font-size: 12px;
color: var(--mint-text-muted, #64748b);
line-height: 1.5;
}
</style>
@@ -134,6 +134,26 @@
@close="handleClose"
>
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="crm-dialog-form">
<div class="invoice-paste-panel">
<div class="invoice-paste-panel__hd">
<span class="invoice-paste-panel__title">粘贴识别</span>
<span class="invoice-paste-panel__tip">粘贴开票资料文本后点「识别并填入」,将覆盖下方对应字段</span>
</div>
<el-input
v-model="pasteText"
type="textarea"
:rows="5"
placeholder="在此粘贴客户发来的开票资料…"
/>
<div class="invoice-paste-panel__actions">
<el-button class="btn-ghost" @click="clearPaste">
<el-icon><Delete /></el-icon>清空
</el-button>
<el-button class="btn-main" type="primary" @click="handleRecognizePaste">
<el-icon><DocumentCopy /></el-icon>识别并填入
</el-button>
</div>
</div>
<div class="form-grid-2">
<el-form-item label="单位名称(章)" prop="companyName" class="form-col-span-2">
<el-input v-model="form.companyName" placeholder="请输入单位名称" maxlength="200" />
@@ -186,10 +206,14 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { Plus, Edit, Delete, Star, Close, Check } from '@element-plus/icons-vue'
import { Plus, Edit, Delete, Star, Close, Check, DocumentCopy } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/http'
import { canDeleteByCreator, CREATOR_DELETE_TIP } from '@/utils/creator-delete'
import {
parseCrmInvoicePaste,
mergeInvoiceRemark
} from '@/utils/crm-invoice-paste-recognize'
interface InvoiceInfoVO {
id: string | number
@@ -235,6 +259,7 @@ const dialog = ref({
})
const formRef = ref()
const pasteText = ref('')
const form = ref<InvoiceInfoForm>({
id: undefined,
customerId: '',
@@ -355,6 +380,44 @@ const handleClose = () => {
reset()
}
const clearPaste = () => {
pasteText.value = ''
}
const handleRecognizePaste = () => {
const parsed = parseCrmInvoicePaste(pasteText.value)
if (!parsed.filledCount && !parsed.remarkParts.length) {
ElMessage.warning(parsed.warnings[0] || '未能识别,请检查格式或手动填写')
return
}
const keys = [
'companyName',
'taxNo',
'companyAddress',
'legalRepresentative',
'agent',
'phoneFax',
'bankName',
'bankAccount'
] as const
for (const k of keys) {
const v = parsed[k]
if (v) form.value[k] = v
}
if (parsed.remarkParts.length) {
form.value.remark = mergeInvoiceRemark(form.value.remark, parsed.remarkParts)
}
const tip =
`已识别 ${parsed.filledCount} 个字段` +
(parsed.remarkParts.length ? `,另有 ${parsed.remarkParts.length} 项写入备注` : '') +
(parsed.warnings.length ? `;${parsed.warnings.join(';')}` : '')
if (parsed.warnings.length) {
ElMessage.warning(tip)
} else {
ElMessage.success(tip)
}
}
const reset = () => {
form.value = {
id: undefined,
@@ -371,6 +434,7 @@ const reset = () => {
remark: '',
}
formRef.value?.resetFields()
pasteText.value = ''
}
defineExpose({ getList })
@@ -384,4 +448,37 @@ defineExpose({ getList })
.invoice-empty-alert {
margin-bottom: 12px;
}
.invoice-paste-panel {
margin-bottom: 16px;
padding: 12px;
border: 1px dashed #6ee7b7;
border-radius: 8px;
background: var(--primary-bg, #ecfdf5);
}
.invoice-paste-panel__hd {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 8px;
margin-bottom: 8px;
}
.invoice-paste-panel__title {
font-weight: 600;
color: var(--primary-dark, #059669);
}
.invoice-paste-panel__tip {
font-size: 12px;
color: var(--mint-text-muted, #64748b);
}
.invoice-paste-panel__actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 10px;
}
</style>
+3 -5
View File
@@ -197,7 +197,7 @@
v-model="dialogVisible"
class="dialog-form-layout duty-day-dialog"
:title="`配置 · ${editingDate}`"
width="960px"
width="1100px"
align-center
destroy-on-close
:close-on-click-modal="false"
@@ -221,7 +221,7 @@
</div>
</div>
<template v-if="slot.enabled">
<div class="slot-fields-row">
<div class="form-grid-4 slot-fields-row">
<el-form-item label="值班时段">
<el-select
:model-value="slot.timeRangeValue"
@@ -229,7 +229,7 @@
allow-create
default-first-option
clearable
placeholder="选择或输入,如 09:00-17:00 / 18:00-次日07:00"
placeholder="选择或输入时段"
style="width: 100%"
@change="(v) => onTimeRangeChange(slot, v as string)"
>
@@ -241,8 +241,6 @@
/>
</el-select>
</el-form-item>
</div>
<div class="form-grid-3 slot-fields-row">
<el-form-item label="值班安排">
<el-select
v-model="slot.pendingUserId"
+2 -2
View File
@@ -74,10 +74,10 @@
if (menuType.value !== MenuTypeEnum.PORTAL) {
settingStore.switchMenuLayouts(MenuTypeEnum.PORTAL)
}
// 门户:开启多页签;首页固定 Tab
// 门户:开启多页签;首页固定 Tab(不可关闭)
settingStore.setWorkTab(true)
const home = homePath.value || HOME_PAGE_PATH || '/home'
if (home && !worktabStore.opened.some((t) => t.path === home)) {
if (home) {
worktabStore.openTab(
{
path: home,
+129 -64
View File
@@ -1,12 +1,8 @@
<!-- eslint-disable vue/no-v-model-argument -->
<!-- 运输计划合同完善:仅缺合同计划,行内展开分侧保存 -->
<template>
<div class="page-container">
<section class="panel">
<div class="panel-top pl-plan-contract-panel-top">
<h2 class="title-bar-heading">运输计划合同完善</h2>
<span class="pl-plan-contract-stat">待完善 {{ pagination.total }} 条</span>
</div>
<div class="filter-block">
<el-form :model="searchForm" class="filter-form" @submit.prevent>
<div class="filter-grid">
@@ -77,6 +73,7 @@
<el-button class="btn-ghost" :loading="loading" @click="fetchData">
<el-icon><Refresh /></el-icon>刷新
</el-button>
<span class="pl-plan-contract-stat">待完善 {{ pagination.total }} 条</span>
</div>
</div>
@@ -92,7 +89,7 @@
@expand-change="handleExpandChange"
@row-dblclick="handleView"
>
<el-table-column type="expand" min-width="48">
<el-table-column type="expand" class-name="pl-contract-expand-col" width="1">
<template #default="{ row }">
<div class="pl-contract-expand" v-if="expandDraft[row.id]">
<div class="pl-contract-expand__grid">
@@ -200,6 +197,52 @@
{{ getPlanTypeLabel(row.planType) }}
</template>
</el-table-column>
<el-table-column
label="合同缺口"
min-width="260"
align="left"
header-align="left"
class-name="col-name-wrap"
label-class-name="col-name-wrap"
>
<template #default="{ row }">
<div class="pl-gap-contract-cell">
<div class="pl-plan-contract-cell">
<div
v-for="(line, index) in planContractLines(row)"
:key="`${line.side}-${line.no}-${index}`"
class="pl-plan-contract-cell__row"
>
<span class="pl-plan-contract-cell__side">{{ line.side }}</span>
<span
class="pl-plan-contract-cell__no"
:class="{ 'is-pending': line.pending }"
>{{ line.no }}</span>
</div>
</div>
<el-button
link
type="warning"
class="pl-gap-contract-cell__action"
@click.stop="toggleExpand(row)"
>
<el-icon><EditPen /></el-icon>
{{ isExpanded(row.id) ? '收起' : '补充' }}
</el-button>
</div>
</template>
</el-table-column>
<el-table-column prop="followStatus" label="跟进状态" min-width="110" align="center">
<template #default="{ row }">
<el-tag
:type="getPlPlanFollowStatusTagType(row.followStatus)"
size="small"
effect="dark"
>
{{ getPlPlanFollowStatusLabel(row.followStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="supplierName"
label="供方"
@@ -234,7 +277,7 @@
</el-table-column>
<el-table-column
label="产品名称"
min-width="240"
min-width="160"
align="left"
class-name="col-name-wrap"
label-class-name="col-name-wrap"
@@ -268,39 +311,6 @@
<PlPlanListProductCell :products="row.products" field="actualQty" />
</template>
</el-table-column>
<el-table-column label="合同缺口" min-width="130" align="center">
<template #default="{ row }">
<el-tag :type="gapTag(row).type" size="small" effect="dark">
{{ gapTag(row).text }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="followStatus" label="跟进状态" min-width="110" align="center">
<template #default="{ row }">
<el-tag
:type="getPlPlanFollowStatusTagType(row.followStatus)"
size="small"
effect="dark"
>
{{ getPlPlanFollowStatusLabel(row.followStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" min-width="200" align="center">
<template #default="{ row }">
<el-button
link
type="warning"
@click="toggleExpand(row)"
>
<el-icon><EditPen /></el-icon>
{{ isExpanded(row.id) ? '收起' : '展开补充' }}
</el-button>
<el-button link type="primary" @click="handleView(row)">
<el-icon><View /></el-icon>查看计划
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
@@ -327,7 +337,7 @@
</template>
<script setup lang="ts">
import { Check, EditPen, Refresh, RefreshLeft, Search, View } from '@element-plus/icons-vue'
import { Check, EditPen, Refresh, RefreshLeft, Search } from '@element-plus/icons-vue'
import {
planApi,
type PlanContractGapItem,
@@ -343,6 +353,7 @@ import {
getPlPlanFollowStatusLabel,
getPlPlanFollowStatusTagType
} from '@/utils/pl-display'
import { planContractLines } from '@/utils/pl-plan-contract-display'
import { refreshMenuBadges } from '@/utils/menu-badge-registry'
import { useMenuStore } from '@/store/modules/menu'
@@ -395,20 +406,6 @@ function linksFingerprint(links: PlanContractLink[]) {
.join(',')
}
function gapTag(row: PlanContractGapItem) {
const sides = row.gapSides || []
if (sides.includes('supplier') && sides.includes('demander')) {
return { text: '两侧均缺', type: 'danger' as const }
}
if (sides.includes('supplier')) {
return { text: '缺供方合同', type: 'warning' as const }
}
if (sides.includes('demander')) {
return { text: '缺需方合同', type: 'warning' as const }
}
return { text: '—', type: 'info' as const }
}
function getPlanTypeLabel(type?: string | null) {
const map: Record<string, string> = {
direct: '直销单',
@@ -422,11 +419,11 @@ function partySubText(row: PlanContractGapItem, side: 'supplier' | 'demander') {
if (side === 'supplier') {
if (row.supplierContractExempt) return '内置 · 合同可免'
const n = row.supplierContracts?.length || 0
return n ? `已关联 ${n} 份` : '未关联'
return n ? `已关联${n}份合同` : '未关联合同'
}
if (row.demanderContractExempt) return '内置 · 合同可免'
const n = row.demanderContracts?.length || 0
return n ? `已关联 ${n} 份` : '未关联'
return n ? `已关联${n}份合同` : '未关联合同'
}
function sideBoxClass(row: PlanContractGapItem, side: 'supplier' | 'demander') {
@@ -647,18 +644,24 @@ onMounted(() => {
</script>
<style scoped lang="scss">
.pl-plan-contract-panel-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 20px 12px;
border-bottom: 1px solid #eef2f6;
.pl-plan-list-toolbar-row {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
> .list-toolbar-date-shortcuts,
> .quick-search-input,
> .quick-search-group,
> .el-button,
> .pl-plan-contract-stat {
flex-shrink: 0;
}
}
.pl-plan-contract-stat {
display: inline-flex;
align-items: center;
margin-left: auto;
padding: 4px 10px;
border-radius: 8px;
font-size: 12px;
@@ -755,4 +758,66 @@ onMounted(() => {
gap: 8px;
margin-top: 10px;
}
:deep(.pl-contract-expand-col) {
.cell {
padding: 0 !important;
}
.el-table__expand-icon {
display: none;
}
}
.pl-gap-contract-cell {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
width: 100%;
}
.pl-gap-contract-cell__action {
flex-shrink: 0;
margin-top: 0;
}
.pl-plan-contract-cell {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
min-width: 0;
flex: 1;
text-align: left;
}
.pl-plan-contract-cell__row {
display: flex;
align-items: baseline;
gap: 6px;
width: 100%;
min-width: 0;
font-size: 13px;
line-height: 1.5;
color: var(--text, #1e293b);
}
.pl-plan-contract-cell__side {
flex-shrink: 0;
font-weight: 600;
color: var(--mint-text-muted, #64748b);
}
.pl-plan-contract-cell__no {
min-width: 0;
white-space: normal;
word-break: break-word;
overflow-wrap: anywhere;
&.is-pending {
font-weight: 600;
color: #d97706;
}
}
</style>
+40 -53
View File
@@ -268,29 +268,15 @@
show-overflow-tooltip
/>
<el-table-column
v-if="isColumnVisible('contractSummary')"
label="合同"
min-width="220"
align="left"
header-align="left"
class-name="col-name-wrap"
label-class-name="col-name-wrap"
v-if="isColumnVisible('licensePlate')"
prop="licensePlate"
label="车牌号"
min-width="160"
show-overflow-tooltip
align="center"
>
<template #default="{ row }">
<div
v-if="planContractLines(row).length"
class="pl-plan-contract-cell"
>
<div
v-for="(line, index) in planContractLines(row)"
:key="`${line.side}-${line.no}-${index}`"
class="pl-plan-contract-cell__row"
>
<span class="pl-plan-contract-cell__side">{{ line.side }}</span>
<span class="pl-plan-contract-cell__no">{{ line.no }}</span>
</div>
</div>
<span v-else class="pl-plan-contract-cell__empty">—</span>
{{ formatPlVehiclePlateDisplay(row.licensePlate, row.trailerNo) }}
</template>
</el-table-column>
<el-table-column
@@ -374,18 +360,6 @@
<PlPlanListCertCell :products="row.products" compact hide-product />
</template>
</el-table-column>
<el-table-column
v-if="isColumnVisible('licensePlate')"
prop="licensePlate"
label="车牌号"
min-width="160"
show-overflow-tooltip
align="center"
>
<template #default="{ row }">
{{ formatPlVehiclePlateDisplay(row.licensePlate, row.trailerNo) }}
</template>
</el-table-column>
<el-table-column
v-if="isColumnVisible('personnelSummary')"
prop="personnelSummary"
@@ -405,6 +379,31 @@
min-width="100"
align="center"
/>
<el-table-column
v-if="isColumnVisible('contractSummary')"
label="合同"
min-width="220"
align="left"
header-align="left"
class-name="col-name-wrap"
label-class-name="col-name-wrap"
>
<template #default="{ row }">
<div class="pl-plan-contract-cell">
<div
v-for="(line, index) in planContractLines(row)"
:key="`${line.side}-${line.no}-${index}`"
class="pl-plan-contract-cell__row"
>
<span class="pl-plan-contract-cell__side">{{ line.side }}</span>
<span
class="pl-plan-contract-cell__no"
:class="{ 'is-pending': line.pending }"
>{{ line.no }}</span>
</div>
</div>
</template>
</el-table-column>
</ListTable>
<ListColumnStatsRow
ref="planStatsRowRef"
@@ -568,6 +567,7 @@ import ListToolbarDateShortcuts from '@/components/core/list-toolbar-date-shortc
import { useDbInstantSearch } from '@/composables/useDbInstantSearch'
import { useListDateShortcut } from '@/composables/useListDateShortcut'
import { canBatchPrintSlip } from '@/utils/pl-batch-print'
import { planContractLines } from '@/utils/pl-plan-contract-display'
import {
canPrintSlipCommand,
formatPlVehiclePlateDisplay,
@@ -607,7 +607,7 @@ const listColumnDefs = [
{ prop: 'planType', label: '单据类型' },
{ prop: 'supplierName', label: '供方' },
{ prop: 'customerName', label: '需方' },
{ prop: 'contractSummary', label: '合同' },
{ prop: 'licensePlate', label: '车牌号' },
{ prop: 'productName', label: '产品名称' },
{ prop: 'productSpec', label: '型号/规格' },
{ prop: 'productPackageSpec', label: '包装规格' },
@@ -615,9 +615,9 @@ const listColumnDefs = [
{ prop: 'productPlanQty', label: '计划数量' },
{ prop: 'productActualQty', label: '实际数量' },
{ prop: 'planAttachments', label: '计划附件' },
{ prop: 'licensePlate', label: '车牌号' },
{ prop: 'personnelSummary', label: '人员' },
{ prop: 'followUserName', label: '跟进人', defaultVisible: false }
{ prop: 'followUserName', label: '跟进人', defaultVisible: false },
{ prop: 'contractSummary', label: '合同' }
]
const {
@@ -785,20 +785,6 @@ function getPlanTypeLabel(type?: string | null) {
return map[type] || type
}
/** 合同列:供/需合同各占一行展示 */
function planContractLines(plan: Plan): Array<{ side: string; no: string }> {
const lines: Array<{ side: string; no: string }> = []
for (const item of plan.supplierContracts || []) {
const no = item.contractNo || item.contractId
if (no) lines.push({ side: '供', no: String(no) })
}
for (const item of plan.demanderContracts || []) {
const no = item.contractNo || item.contractId
if (no) lines.push({ side: '需', no: String(no) })
}
return lines
}
async function fetchData() {
loading.value = true
try {
@@ -1190,9 +1176,10 @@ async function openArrangeFromQuery() {
white-space: normal;
word-break: break-word;
overflow-wrap: anywhere;
}
.pl-plan-contract-cell__empty {
color: var(--mint-text-muted, #64748b);
&.is-pending {
font-weight: 600;
color: #d97706;
}
}
</style>
@@ -260,7 +260,12 @@
<el-input v-model="formData.licensePlate" placeholder="请输入车牌号" maxlength="20" />
</el-form-item>
<el-form-item label="挂车号" prop="trailerNo">
<el-input v-model="formData.trailerNo" placeholder="请输入挂车号" maxlength="20" clearable />
<el-input
v-model="formData.trailerNo"
placeholder="必填;无挂车可填「货拉拉」"
maxlength="20"
clearable
/>
</el-form-item>
<el-form-item label="载重(吨)" prop="loadCapacity">
<el-input-number
@@ -621,6 +626,7 @@ const formData = reactive({
const formRules = {
licensePlate: [{ required: true, message: '请输入车牌号', trigger: 'blur' }],
trailerNo: [{ required: true, message: '请输入挂车号', trigger: 'blur' }],
vehicleStatus: [{ required: true, message: '请选择车辆状态', trigger: 'change' }],
carrierId: [{ required: true, message: '请选择承运商', trigger: 'change' }]
}
+9 -3
View File
@@ -268,7 +268,12 @@
<el-input v-model="formData.licensePlate" placeholder="请输入车牌号" maxlength="20" />
</el-form-item>
<el-form-item label="挂车号" prop="trailerNo">
<el-input v-model="formData.trailerNo" placeholder="请输入挂车号" maxlength="20" clearable />
<el-input
v-model="formData.trailerNo"
placeholder="必填;无挂车可填「货拉拉」"
maxlength="20"
clearable
/>
</el-form-item>
<el-form-item label="载重(吨)" prop="loadCapacity">
<el-input-number
@@ -398,7 +403,7 @@
:cert-name-options="certNameOptions"
layout="table"
:enforce-self-vehicle-required="false"
:enforce-carrier-vehicle-required="false"
enforce-carrier-vehicle-required
/>
</el-form>
<template #footer>
@@ -640,7 +645,8 @@ const formData = reactive({
})
const formRules = {
licensePlate: [{ required: true, message: '请输入车牌号', trigger: 'blur' }]
licensePlate: [{ required: true, message: '请输入车牌号', trigger: 'blur' }],
trailerNo: [{ required: true, message: '请输入挂车号', trigger: 'blur' }]
}
function getStatusTagType(status: string) {
+2 -1
View File
@@ -219,7 +219,8 @@
备用金发放: MENU_BADGE_CODES.BX_ADVANCE_ISSUE,
报销审批: MENU_BADGE_CODES.BX_REIMBURSE_APPROVE,
支付发放冲抵: MENU_BADGE_CODES.BX_PAYMENT,
待我审批: MENU_BADGE_CODES.BX_VIEW_TODO
待我审批: MENU_BADGE_CODES.BX_VIEW_TODO,
运输计划合同完善: MENU_BADGE_CODES.PL_PLAN_CONTRACT
}
const router = useRouter()
+11 -6
View File
@@ -460,17 +460,22 @@
margin-top: 10px;
}
.person {
padding: 4px 9px;
font-size: 12px;
color: #6b7b74;
background: #f3f7f5;
border: 1px solid #dcebe4;
padding: 5px 11px;
font-size: 13px;
font-weight: 600;
line-height: 1.3;
color: #fff;
background: #0a9f6d;
border: none;
border-radius: 999px;
box-shadow: 0 1px 2px rgba(10, 159, 109, 0.25);
}
.person.empty {
font-weight: 400;
color: #8a9a93;
background: transparent;
border-style: dashed;
border: 1px dashed #c5d5ce;
box-shadow: none;
}
.field {
display: block;
File diff suppressed because it is too large. Load diff
+106 -27
View File
@@ -45,7 +45,11 @@
<el-button class="btn-ghost" :loading="loading" @click="handleReset"
><el-icon><RefreshLeft /></el-icon>重置</el-button
>
<el-button class="btn-main" type="primary" :loading="loading" @click="handleSearch"
<el-button
class="btn-main"
type="primary"
:loading="loading"
@click="handleSearch"
><el-icon><Search /></el-icon>搜索</el-button
>
</div>
@@ -55,7 +59,7 @@
<div class="list-toolbar">
<div class="list-toolbar-row">
<ListQuickSearchInput
<ListQuickSearchInput
v-model="quickSearchKeyword"
placeholder="账号/姓名/手机号"
@search="handleQuickSearch"
@@ -105,7 +109,8 @@
</div>
<div class="list-body list-wrap">
<ListTable table-key="system-user"
<ListTable
table-key="system-user"
class="list-table"
:data="tableData"
v-loading="loading"
@@ -114,7 +119,12 @@
@selection-change="handleSelectionChange"
@row-dblclick="handleView"
>
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
<el-table-column
type="selection"
min-width="55"
align="center"
header-align="center"
/>
<el-table-column
type="index"
label="序号"
@@ -151,7 +161,27 @@
header-align="center"
>
<template #default="{ row }">
{{ formatUserDeptDisplay(row) }}
{{ row.deptName || '—' }}
</template>
</el-table-column>
<el-table-column
v-if="isColumnVisible('roles')"
prop="roles"
label="角色"
min-width="180"
align="center"
header-align="center"
>
<template #default="{ row }">
<template v-if="resolveUserRoles(row).length">
<template v-for="(role, idx) in resolveUserRoles(row)" :key="role.id">
<span v-if="idx > 0" class="user-role-sep">、</span>
<el-link type="primary" @click.stop="handleOpenRole(role.id)">{{
role.label
}}</el-link>
</template>
</template>
<span v-else>—</span>
</template>
</el-table-column>
<el-table-column
@@ -191,9 +221,9 @@
</ListTable>
<div class="pagination list-pager-bar">
<div class="list-pager-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
<div class="list-pager-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
<el-pagination
:current-page="pagination.page"
@@ -324,14 +354,38 @@
删除
</el-button>
</template>
<template #userRoles>
<div class="user-view-roles">
<template v-if="viewRow && resolveUserRoles(viewRow).length">
<el-tag
v-for="role in resolveUserRoles(viewRow)"
:key="role.id"
type="info"
size="small"
class="user-role-tag"
@click="handleOpenRole(role.id)"
>
{{ role.label }}
</el-tag>
</template>
<span v-else>—</span>
</div>
</template>
</SystemViewDialog>
<RoleManageDialogs ref="roleDialogsRef" />
</section>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { canDeleteByCreator, filterDeletableByCreator, allRowsDeletableByCreator, CREATOR_DELETE_TIP } from '@/utils/creator-delete'
import {
canDeleteByCreator,
filterDeletableByCreator,
allRowsDeletableByCreator,
CREATOR_DELETE_TIP
} from '@/utils/creator-delete'
import { syncViewRowFromFlatList } from '@/utils/view-dialog-refresh'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
@@ -346,6 +400,7 @@
Delete
} from '@element-plus/icons-vue'
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
import RoleManageDialogs from '@/components/business/role-manage-dialogs/index.vue'
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
@@ -359,12 +414,12 @@
fetchDeptTree,
fetchRoleOptions
} from '@/api/admin'
import { buildPathLabelMapFromTreeRoots } from '@/utils/tree-path'
import { useDictStore } from '@/store/modules/dict'
import { buildPathLabelMapFromTreeRoots } from '@/utils/tree-path'
import { useDictStore } from '@/store/modules/dict'
defineOptions({ name: 'SystemUser' })
defineOptions({ name: 'SystemUser' })
const dictStore = useDictStore()
const dictStore = useDictStore()
const roleOptions = ref<Array<{ label: string; value: string }>>([])
@@ -396,13 +451,20 @@ const dictStore = useDictStore()
return deptPathLabelMap.value.get(id) || row.deptName || '—'
}
function formatUserRoleDisplay(row: Api.User.UserItem) {
function resolveUserRoles(row: Api.User.UserItem) {
const ids = row.roleIds || []
if (!ids.length) return '—'
const labels = ids
.map((id) => roleOptions.value.find((o) => o.value === id)?.label)
.filter((name): name is string => Boolean(name))
return labels.length ? labels.join('、') : '—'
return ids
.map((id) => {
const label = roleOptions.value.find((o) => o.value === id)?.label
return label ? { id, label } : null
})
.filter((item): item is { id: string; label: string } => Boolean(item))
}
const roleDialogsRef = ref<InstanceType<typeof RoleManageDialogs> | null>(null)
function handleOpenRole(roleId: string) {
roleDialogsRef.value?.openView(roleId)
}
const searchForm = reactive<{ keyword: string; status: string }>({ keyword: '', status: '' })
@@ -415,18 +477,14 @@ const dictStore = useDictStore()
const listColumnDefs = [
{ prop: 'userNo', label: '用户账号' },
{ prop: 'deptName', label: '所属组织' },
{ prop: 'roles', label: '角色' },
{ prop: 'phoneNo', label: '手机号' },
{ prop: 'status', label: '状态' },
{ prop: 'createTime', label: '创建时间', defaultVisible: false }
]
const {
visibleColumns,
checkAll,
isIndeterminate,
isColumnVisible,
resetToDefault
} = usePersistedListColumns('system-user-list-columns', listColumnDefs)
const { visibleColumns, checkAll, isIndeterminate, isColumnVisible, resetToDefault } =
usePersistedListColumns('system-user-list-columns', listColumnDefs)
const dialogVisible = ref(false)
const dialogTitle = ref('新增用户')
@@ -722,9 +780,14 @@ const dictStore = useDictStore()
long: false
},
{ label: '创建时间', value: row.createTime, long: false },
{ label: '角色', value: formatUserRoleDisplay(row), long: false },
{ label: '备注', value: row.remark, long: false }
]
},
{
title: '角色',
titleStyle: 'field' as const,
fields: [],
slot: 'userRoles'
}
]
})
@@ -823,4 +886,20 @@ const dictStore = useDictStore()
margin-bottom: 0;
}
}
.user-role-tag {
margin: 2px 4px;
cursor: pointer;
}
.user-role-sep {
color: var(--mint-text-muted, var(--el-text-color-secondary));
}
.user-view-roles {
display: flex;
flex-wrap: wrap;
gap: 4px;
align-items: center;
}
</style>