提交
This commit is contained in:
1 parent
493f8d6756
commit
d84f4b944c
406 files changed
+58954
-10376
No files matched your search
@@ -0,0 +1,101 @@
|
||||
# shanxia2.0 Backend 生产部署说明
|
||||
|
||||
## 1. 上传到服务器
|
||||
|
||||
将 `backend/` 目录上传到:
|
||||
|
||||
```
|
||||
/iflytek/shanxia2.0/backend
|
||||
```
|
||||
|
||||
**必须排除**(否则 Linux 上会报 `bad interpreter: No such file or directory`):
|
||||
|
||||
- `venv/`(**禁止上传本机 venv**,必须在服务器上用 `./start-prod.sh install` 重建)
|
||||
- `logs/`、`__pycache__/`、`*.pyc`
|
||||
- `graphify-out/`
|
||||
- `uploads/`(若生产已有附件数据则保留服务器端目录)
|
||||
- `.env`(开发配置,生产用 `.env.production`)
|
||||
|
||||
本地打包示例:
|
||||
|
||||
```bash
|
||||
cd /path/to/shanxia2.0
|
||||
tar czf shanxia2.0-backend.tar.gz \
|
||||
--exclude='backend/venv' \
|
||||
--exclude='backend/logs' \
|
||||
--exclude='backend/__pycache__' \
|
||||
--exclude='backend/graphify-out' \
|
||||
--exclude='backend/.env' \
|
||||
backend/
|
||||
```
|
||||
|
||||
## 2. 服务器首次启动
|
||||
|
||||
```bash
|
||||
cd /iflytek/shanxia2.0/backend
|
||||
chmod +x start-prod.sh
|
||||
|
||||
# 首次:安装 Python 依赖(自动创建 venv)
|
||||
./start-prod.sh install
|
||||
|
||||
# 启动(默认 127.0.0.1:9111,读取 .env.production)
|
||||
./start-prod.sh start
|
||||
```
|
||||
|
||||
## 3. 日常运维
|
||||
|
||||
```bash
|
||||
./start-prod.sh status # 查看状态
|
||||
./start-prod.sh logs # 最近日志
|
||||
./start-prod.sh restart # 发版后重启
|
||||
./start-prod.sh stop # 停止
|
||||
```
|
||||
|
||||
健康检查:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:9111/health
|
||||
# {"status":"healthy"}
|
||||
```
|
||||
|
||||
## 4. 配置文件
|
||||
|
||||
| 文件 | 用途 |
|
||||
|---|---|
|
||||
| `.env.production` | 生产环境变量(DB/Redis/MinIO/JWT/RSA 等) |
|
||||
| `start-prod.sh` | 启动脚本(默认端口 **9111**) |
|
||||
|
||||
默认监听 `127.0.0.1:9111`,由 Nginx `:9911` 反代 `/api/*`,无需对公网开放 9111。
|
||||
|
||||
## 5. 与 Nginx 配合
|
||||
|
||||
Nginx 配置见仓库 `生产环境部署/nginx-shanxia2.0.conf`:
|
||||
|
||||
- 前端:`http://121.43.228.102:9911`
|
||||
- API:`/api/` → `http://127.0.0.1:9111`
|
||||
|
||||
## 6. 常见问题
|
||||
|
||||
**Q: `bad interpreter: .../Users/.../venv/bin/python3: No such file or directory`**
|
||||
A: 误把本机 Mac 的 `venv/` 上传到了 Linux。在服务器执行:
|
||||
|
||||
```bash
|
||||
cd /iflytek/shanxia2.0/backend
|
||||
rm -rf venv
|
||||
./start-prod.sh install
|
||||
./start-prod.sh start
|
||||
```
|
||||
|
||||
新版 `start-prod.sh` 也会自动检测并重建无效 venv。
|
||||
|
||||
**Q: 启动失败,日志报 ModuleNotFoundError**
|
||||
A: 执行 `./start-prod.sh install` 重新安装依赖。
|
||||
|
||||
**Q: 登录失败 / 公钥变化**
|
||||
A: 确认 `.env.production` 中 `LOGIN_RSA_PRIVATE_KEY_PEM` 已配置且未被修改。
|
||||
|
||||
**Q: 数据库连接失败**
|
||||
A: 检查服务器到 `47.99.164.66:3306` 网络与白名单。
|
||||
|
||||
**Q: 需要改端口**
|
||||
A: `PORT=9111 ./start-prod.sh restart` 或修改脚本默认值。
|
||||
+1
-1
@@ -1 +1 @@
|
||||
4800
|
||||
17486
|
||||
@@ -211,7 +211,7 @@ async def delete_tenant(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
tenant_svc.soft_delete_tenant(db, tenant_id)
|
||||
tenant_svc.soft_delete_tenant(db, tenant_id, user_info=_user)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
@@ -401,7 +401,7 @@ async def delete_user(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
user_svc.soft_delete_user(db, user_id)
|
||||
user_svc.soft_delete_user(db, user_id, user_info=_user)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
@@ -513,7 +513,7 @@ async def delete_dict_type(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
dict_type_svc.soft_delete_dict_type(db, dict_type_id)
|
||||
dict_type_svc.soft_delete_dict_type(db, dict_type_id, user_info=_user)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
@@ -617,7 +617,7 @@ async def delete_dict_data(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
dict_data_svc.soft_delete_dict_data(db, data_id)
|
||||
dict_data_svc.soft_delete_dict_data(db, data_id, user_info=_user)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
@@ -760,7 +760,7 @@ async def delete_dept(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
dept_svc.soft_delete_dept(db, dept_id)
|
||||
dept_svc.soft_delete_dept(db, dept_id, user_info=_user)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
@@ -879,7 +879,7 @@ async def delete_site(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
site_svc.soft_delete_site(db, site_id)
|
||||
site_svc.soft_delete_site(db, site_id, user_info=_user)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
@@ -978,7 +978,7 @@ async def delete_post(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
post_svc.soft_delete_post(db, post_id)
|
||||
post_svc.soft_delete_post(db, post_id, user_info=_user)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
@@ -1138,7 +1138,7 @@ async def delete_menu(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
menu_svc.soft_delete_menu(db, menu_id)
|
||||
menu_svc.soft_delete_menu(db, menu_id, user_info=_user)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
@@ -1248,7 +1248,7 @@ async def delete_role_management(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
role_svc.soft_delete_role(db, role_id)
|
||||
role_svc.soft_delete_role(db, role_id, user_info=_user)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
|
||||
@@ -12,6 +12,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.menu_guard import require_admin_or_super_admin
|
||||
from app.core.oper_log_decorator import oper_log
|
||||
from app.core.response import error_response, success_response
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.crud.crm.data_scope_helpers import (
|
||||
@@ -414,6 +415,7 @@ async def customer_detail(
|
||||
|
||||
|
||||
@router.post("/customer/add")
|
||||
@oper_log(title="CRM客户管理", business_type=1, summary="新增客户【{customerName}】")
|
||||
async def customer_create(
|
||||
body: CustomerCreateBody,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -443,6 +445,7 @@ async def customer_create(
|
||||
|
||||
|
||||
@router.post("/customer/update")
|
||||
@oper_log(title="CRM客户管理", business_type=2, summary="修改客户【{customerName}】")
|
||||
async def customer_update(
|
||||
body: CustomerUpdateBody,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -469,6 +472,7 @@ async def customer_update(
|
||||
|
||||
|
||||
@router.post("/customer/delete")
|
||||
@oper_log(title="CRM客户管理", business_type=3, business_id_field="ids", summary="删除客户")
|
||||
async def customer_delete(
|
||||
body: CustomerDeleteBody,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -926,6 +930,7 @@ async def product_import_template(
|
||||
|
||||
|
||||
@router.post("/customer/transfer")
|
||||
@oper_log(title="CRM客户管理", business_type=2, business_id_field="customerId", summary="转移客户")
|
||||
async def customer_transfer(
|
||||
body: dict,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -951,6 +956,7 @@ async def customer_transfer(
|
||||
|
||||
|
||||
@router.post("/customer/intoPool")
|
||||
@oper_log(title="CRM客户管理", business_type=2, business_id_field="customerId", summary="客户放入公海")
|
||||
async def customer_into_pool(
|
||||
body: dict,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -975,6 +981,7 @@ async def customer_into_pool(
|
||||
|
||||
|
||||
@router.post("/customer/deal/{customer_id}")
|
||||
@oper_log(title="CRM客户管理", business_type=2, path_param="customer_id", summary="客户成交")
|
||||
async def customer_deal(
|
||||
customer_id: str,
|
||||
body: dict = None,
|
||||
@@ -1026,6 +1033,7 @@ async def customer_check_duplicate(
|
||||
|
||||
|
||||
@router.post("/customer/export")
|
||||
@oper_log(title="CRM客户管理", business_type=5, summary="导出客户列表")
|
||||
async def customer_export(
|
||||
body: CustomerListBody = None,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -1350,6 +1358,7 @@ async def pool_customer_list(
|
||||
|
||||
|
||||
@router.post("/customer/pool/assign")
|
||||
@oper_log(title="CRM客户管理", business_type=2, business_id_field="customerIds", summary="公海分配客户")
|
||||
async def pool_customer_assign(
|
||||
body: PoolAssignBody,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -1368,6 +1377,7 @@ async def pool_customer_assign(
|
||||
|
||||
|
||||
@router.post("/customer/claim")
|
||||
@oper_log(title="CRM客户管理", business_type=2, business_id_field="customerId", summary="认领公海客户")
|
||||
async def pool_customer_claim(
|
||||
body: PoolClaimBody,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
|
||||
@@ -9,6 +9,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.menu_guard import require_admin_or_super_admin
|
||||
from app.core.oper_log_decorator import oper_log
|
||||
from app.core.response import error_response, success_response
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.crud.crm.data_scope_helpers import crm_user_info
|
||||
@@ -156,6 +157,7 @@ def register_contract_routes(router):
|
||||
return error_response(500, str(e))
|
||||
|
||||
@router.post("/contract")
|
||||
@oper_log(title="CRM合同管理", business_type=1, summary="新增合同【{contractName}】")
|
||||
async def contract_add(
|
||||
body: ContractCreateBody,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -180,6 +182,7 @@ def register_contract_routes(router):
|
||||
return error_response(500, str(e))
|
||||
|
||||
@router.put("/contract")
|
||||
@oper_log(title="CRM合同管理", business_type=2, summary="修改合同【{contractName}】")
|
||||
async def contract_edit(
|
||||
body: ContractUpdateBody,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -208,6 +211,7 @@ def register_contract_routes(router):
|
||||
return error_response(500, str(e))
|
||||
|
||||
@router.delete("/contract/{ids}")
|
||||
@oper_log(title="CRM合同管理", business_type=3, path_param="ids", summary="删除合同")
|
||||
async def contract_remove(
|
||||
ids: str,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -337,6 +341,7 @@ def register_contract_routes(router):
|
||||
return error_response(500, str(e))
|
||||
|
||||
@router.put("/contract/invalid")
|
||||
@oper_log(title="CRM合同管理", business_type=2, summary="作废合同")
|
||||
async def contract_invalid(
|
||||
body: ContractInvalidBody,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
@@ -441,6 +446,7 @@ def register_contract_routes(router):
|
||||
return error_response(500, str(e))
|
||||
|
||||
@router.post("/contract/export")
|
||||
@oper_log(title="CRM合同管理", business_type=5, summary="导出合同列表")
|
||||
async def contract_export(
|
||||
body: ContractExportBody = None,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
|
||||
@@ -10,6 +10,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.db_auth import DbUserContext, get_db_current_user, get_db_current_user_optional
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator, user_info_from_context
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.services.db_data_scope import apply_db_scope, assert_db_row_in_scope
|
||||
from app.services.db.inventory_id import next_inbound_outbound_id
|
||||
@@ -426,6 +427,7 @@ def delete_inventory(
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
row = _get_inventory(db, tenant_no, id, user)
|
||||
assert_row_deletable_by_creator(row, user_info_from_context(user))
|
||||
row.del_flag = 1
|
||||
row.update_by = user.id
|
||||
db.commit()
|
||||
|
||||
@@ -9,6 +9,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.db_auth import DbUserContext, get_db_current_user
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator, user_info_from_context
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.core.utils import generate_uuid
|
||||
from app.services.db_data_scope import apply_db_scope, assert_db_row_in_scope
|
||||
@@ -295,6 +296,7 @@ def delete_plan(
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
plan = _get_plan(db, tenant_no, id, user)
|
||||
assert_row_deletable_by_creator(plan, user_info_from_context(user))
|
||||
plan.del_flag = 1
|
||||
plan.update_by = user.id
|
||||
db.commit()
|
||||
|
||||
@@ -9,6 +9,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.db_auth import DbUserContext, get_db_current_user
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator, user_info_from_context
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.core.utils import generate_uuid
|
||||
from app.services.db_data_scope import apply_db_scope, assert_db_row_in_scope
|
||||
@@ -360,6 +361,7 @@ def delete_weighing(
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
row = _get_weighing(db, tenant_no, id, user)
|
||||
assert_row_deletable_by_creator(row, user_info_from_context(user))
|
||||
row.is_voided = 1
|
||||
row.update_by = user.id
|
||||
db.commit()
|
||||
|
||||
@@ -182,7 +182,7 @@ async def delete_leave(
|
||||
return error_response(404, "请假记录不存在")
|
||||
if leave["status"] == "pending":
|
||||
return error_response(400, "审批中的请假单不能删除")
|
||||
leave_crud.soft_delete_leave(db, leave_id, user_id=user_id)
|
||||
leave_crud.soft_delete_leave(db, leave_id, user_id=user_id, user_info=_user)
|
||||
db.commit()
|
||||
return success_response(None)
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ async def operlog_list(
|
||||
page_size=page_size,
|
||||
title=body.title if body else None,
|
||||
business_type=body.business_type if body else None,
|
||||
business_id=body.business_id if body else None,
|
||||
business_title=body.business_title if body else None,
|
||||
oper_name=body.oper_name if body else None,
|
||||
status=body.status if body else None,
|
||||
user_info=info,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.pl import plan, transport, freight, inventory
|
||||
from app.api.pl import base_data
|
||||
from app.api.pl import base_data, vehicle_recharge, vehicle_maintenance, vehicle_urea
|
||||
|
||||
pl_router = APIRouter(prefix="/transport", tags=["运输计划管理"])
|
||||
|
||||
@@ -20,3 +20,15 @@ pl_router.include_router(freight.router)
|
||||
|
||||
# 盘存统计
|
||||
pl_router.include_router(inventory.router)
|
||||
|
||||
# ETC/加油充值
|
||||
pl_router.include_router(vehicle_recharge.apply_router)
|
||||
pl_router.include_router(vehicle_recharge.record_router)
|
||||
|
||||
# 车辆维保
|
||||
pl_router.include_router(vehicle_maintenance.apply_router)
|
||||
pl_router.include_router(vehicle_maintenance.process_router)
|
||||
|
||||
# 车辆尿素
|
||||
pl_router.include_router(vehicle_urea.apply_router)
|
||||
pl_router.include_router(vehicle_urea.disburse_router)
|
||||
@@ -1,4 +1,5 @@
|
||||
"""运输计划管理 - 基础数据 API(承运商、车辆、人员)"""
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
@@ -7,6 +8,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.db_auth import DbUserContext, get_db_current_user
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator, user_info_from_context
|
||||
from app.services.pl_data_scope import apply_pl_scope, assert_pl_row_in_scope
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.core.utils import generate_uuid
|
||||
@@ -18,9 +20,11 @@ from app.models.pl_models import (
|
||||
PlVehiclePersonnel,
|
||||
)
|
||||
from app.schemas.pl import (
|
||||
CarrierAuditReject,
|
||||
CarrierCreate,
|
||||
CarrierRead,
|
||||
CarrierUpdate,
|
||||
carrier_to_read,
|
||||
DriverCreate,
|
||||
DriverRead,
|
||||
DriverUpdate,
|
||||
@@ -40,6 +44,10 @@ from app.services.pl_cert_service import (
|
||||
nearest_expire_date,
|
||||
save_owner_certs,
|
||||
)
|
||||
from app.services.pl_carrier_service import (
|
||||
carrier_eligible_for_transport,
|
||||
carrier_qualification_complete,
|
||||
)
|
||||
from app.services.pl_personnel_service import (
|
||||
load_vehicle_personnel_rows,
|
||||
load_vehicle_personnel_summaries,
|
||||
@@ -58,6 +66,7 @@ router = APIRouter(prefix="", tags=["运输计划-基础数据"])
|
||||
def list_carriers(
|
||||
q: Optional[str] = Query(default=None, description="模糊搜索"),
|
||||
status: Optional[str] = Query(default=None, description="状态"),
|
||||
audit_status: Optional[str] = Query(default=None, alias="auditStatus", description="审核状态"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> list[CarrierRead]:
|
||||
@@ -70,6 +79,9 @@ def list_carriers(
|
||||
if status:
|
||||
query = query.filter(PlCarrier.status == status)
|
||||
|
||||
if audit_status:
|
||||
query = query.filter(PlCarrier.audit_status == audit_status)
|
||||
|
||||
if q:
|
||||
query = query.filter(
|
||||
or_(
|
||||
@@ -84,7 +96,7 @@ def list_carriers(
|
||||
case((PlCarrier.order_num.is_(None), 1), else_=0).asc(),
|
||||
PlCarrier.create_time.desc(),
|
||||
)
|
||||
return query.all()
|
||||
return [carrier_to_read(row) for row in query.all()]
|
||||
|
||||
|
||||
@router.post("/carrier", response_model=CarrierRead, status_code=status.HTTP_201_CREATED)
|
||||
@@ -102,6 +114,11 @@ def create_carrier(
|
||||
contact_name=payload.contact_name,
|
||||
contact_phone=payload.contact_phone,
|
||||
status=payload.status,
|
||||
business_license_urls=payload.business_license_urls.strip(),
|
||||
road_transport_license_urls=payload.road_transport_license_urls.strip(),
|
||||
transport_agreement_urls=payload.transport_agreement_urls.strip(),
|
||||
transport_agreement_expire_date=payload.transport_agreement_expire_date,
|
||||
audit_status="pending",
|
||||
remark=payload.remark,
|
||||
create_by=user.id,
|
||||
create_dept_id=user.dept_id,
|
||||
@@ -109,7 +126,7 @@ def create_carrier(
|
||||
db.add(carrier)
|
||||
db.commit()
|
||||
db.refresh(carrier)
|
||||
return carrier
|
||||
return carrier_to_read(carrier)
|
||||
|
||||
|
||||
@router.get("/carrier/{id}", response_model=CarrierRead)
|
||||
@@ -120,7 +137,7 @@ def get_carrier(
|
||||
) -> CarrierRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
carrier = _get_carrier(db, tenant_no, id, user)
|
||||
return carrier
|
||||
return carrier_to_read(carrier)
|
||||
|
||||
|
||||
@router.put("/carrier/{id}", response_model=CarrierRead)
|
||||
@@ -133,13 +150,28 @@ def update_carrier(
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
carrier = _get_carrier(db, tenant_no, id, user)
|
||||
|
||||
for key, value in payload.model_dump(exclude_unset=True).items():
|
||||
payload_data = payload.model_dump(exclude_unset=True)
|
||||
qual_keys = (
|
||||
"business_license_urls",
|
||||
"road_transport_license_urls",
|
||||
"transport_agreement_urls",
|
||||
"transport_agreement_expire_date",
|
||||
)
|
||||
qual_changed = any(key in payload_data for key in qual_keys)
|
||||
|
||||
for key, value in payload_data.items():
|
||||
setattr(carrier, key, value)
|
||||
|
||||
if qual_changed and carrier.audit_status == "approved":
|
||||
carrier.audit_status = "pending"
|
||||
carrier.audit_by = None
|
||||
carrier.audit_time = None
|
||||
carrier.audit_remark = None
|
||||
|
||||
carrier.update_by = user.id
|
||||
db.commit()
|
||||
db.refresh(carrier)
|
||||
return carrier
|
||||
return carrier_to_read(carrier)
|
||||
|
||||
|
||||
@router.delete("/carrier/{id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@@ -150,11 +182,59 @@ def delete_carrier(
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
carrier = _get_carrier(db, tenant_no, id, user)
|
||||
assert_row_deletable_by_creator(carrier, user_info_from_context(user))
|
||||
carrier.del_flag = 1
|
||||
carrier.update_by = user.id
|
||||
db.commit()
|
||||
|
||||
|
||||
@router.post("/carrier/{id}/approve", response_model=CarrierRead)
|
||||
def approve_carrier(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> CarrierRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
carrier = _get_carrier(db, tenant_no, id, user)
|
||||
|
||||
if carrier.audit_status == "approved":
|
||||
raise HTTPException(status_code=400, detail="承运商已审核通过")
|
||||
if not carrier_qualification_complete(carrier):
|
||||
raise HTTPException(status_code=400, detail="承运商资质附件不完整,无法审核通过")
|
||||
|
||||
carrier.audit_status = "approved"
|
||||
carrier.audit_by = user.id
|
||||
carrier.audit_time = datetime.now()
|
||||
carrier.audit_remark = None
|
||||
carrier.update_by = user.id
|
||||
db.commit()
|
||||
db.refresh(carrier)
|
||||
return carrier_to_read(carrier)
|
||||
|
||||
|
||||
@router.post("/carrier/{id}/reject", response_model=CarrierRead)
|
||||
def reject_carrier(
|
||||
id: str,
|
||||
payload: CarrierAuditReject,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> CarrierRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
carrier = _get_carrier(db, tenant_no, id, user)
|
||||
|
||||
if carrier.audit_status == "approved":
|
||||
raise HTTPException(status_code=400, detail="承运商已审核通过,无法驳回")
|
||||
|
||||
carrier.audit_status = "rejected"
|
||||
carrier.audit_by = user.id
|
||||
carrier.audit_time = datetime.now()
|
||||
carrier.audit_remark = (payload.remark or "").strip() or None
|
||||
carrier.update_by = user.id
|
||||
db.commit()
|
||||
db.refresh(carrier)
|
||||
return carrier_to_read(carrier)
|
||||
|
||||
|
||||
def _get_carrier(db: Session, tenant_no: str, carrier_id: str, user: DbUserContext) -> PlCarrier:
|
||||
carrier = (
|
||||
db.query(PlCarrier)
|
||||
@@ -212,6 +292,11 @@ def list_vehicles(
|
||||
vehicle_type: Optional[str] = Query(default=None, alias="vehicleType", description="车辆类型"),
|
||||
carrier_id: Optional[str] = Query(default=None, alias="carrierId", description="承运商ID"),
|
||||
vehicle_status: Optional[str] = Query(default=None, alias="vehicleStatus", description="车辆状态"),
|
||||
exclude_ineligible_carrier: Optional[bool] = Query(
|
||||
default=False,
|
||||
alias="excludeIneligibleCarrier",
|
||||
description="排除资质不全或协议过期的承运商车辆",
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> list[VehicleRead]:
|
||||
@@ -240,6 +325,34 @@ def list_vehicles(
|
||||
PlVehicle.create_time.desc(),
|
||||
)
|
||||
vehicles = query.all()
|
||||
|
||||
if exclude_ineligible_carrier:
|
||||
carrier_ids = {
|
||||
v.carrier_id for v in vehicles if v.vehicle_type == "carrier" and v.carrier_id
|
||||
}
|
||||
carrier_map: dict[str, PlCarrier] = {}
|
||||
if carrier_ids:
|
||||
carriers = (
|
||||
db.query(PlCarrier)
|
||||
.filter(
|
||||
PlCarrier.id.in_(carrier_ids),
|
||||
PlCarrier.tenant_no == tenant_no,
|
||||
PlCarrier.del_flag == 0,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
carrier_map = {c.id: c for c in carriers}
|
||||
vehicles = [
|
||||
v
|
||||
for v in vehicles
|
||||
if v.vehicle_type != "carrier"
|
||||
or not v.carrier_id
|
||||
or (
|
||||
v.carrier_id in carrier_map
|
||||
and carrier_eligible_for_transport(carrier_map[v.carrier_id])
|
||||
)
|
||||
]
|
||||
|
||||
summaries = load_vehicle_personnel_summaries(
|
||||
db, tenant_no, [v.id for v in vehicles]
|
||||
)
|
||||
@@ -387,6 +500,7 @@ def delete_vehicle(
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
vehicle = _get_vehicle(db, tenant_no, id, user)
|
||||
assert_row_deletable_by_creator(vehicle, user_info_from_context(user))
|
||||
vehicle.del_flag = 1
|
||||
vehicle.update_by = user.id
|
||||
db.commit()
|
||||
@@ -568,6 +682,7 @@ def delete_personnel(
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
personnel = _get_personnel(db, tenant_no, id, user)
|
||||
assert_row_deletable_by_creator(personnel, user_info_from_context(user))
|
||||
personnel.del_flag = 1
|
||||
personnel.update_by = user.id
|
||||
db.commit()
|
||||
@@ -710,6 +825,7 @@ def delete_driver(
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
personnel = _get_personnel(db, tenant_no, id, user)
|
||||
assert_row_deletable_by_creator(personnel, user_info_from_context(user))
|
||||
personnel.del_flag = 1
|
||||
personnel.update_by = user.id
|
||||
db.commit()
|
||||
@@ -783,5 +899,6 @@ def unbind_vehicle_driver(
|
||||
assert_pl_row_in_scope(db, vd, PlVehiclePersonnel, user, tenant_no)
|
||||
else:
|
||||
assert_pl_row_in_scope(db, vd, PlVehicleDriver, user, tenant_no)
|
||||
assert_row_deletable_by_creator(vd, user_info_from_context(user))
|
||||
vd.del_flag = 1
|
||||
db.commit()
|
||||
@@ -11,6 +11,7 @@ from app.core.tenant_context import get_tenant_no
|
||||
from app.models.pl_models import PlFreightSettlement
|
||||
from app.schemas.pl.freight import (
|
||||
FreightSettlementApprovalActionBody,
|
||||
FreightSettlementByTransportRead,
|
||||
FreightSettlementCreate,
|
||||
FreightSettlementDisburseBody,
|
||||
FreightSettlementListResponse,
|
||||
@@ -100,6 +101,21 @@ def list_settlements(
|
||||
return FreightSettlementListResponse(total=total, list=items)
|
||||
|
||||
|
||||
@router.get("/by-transport/{transport_id}", response_model=Optional[FreightSettlementByTransportRead])
|
||||
def get_settlement_by_transport(
|
||||
transport_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> Optional[FreightSettlementByTransportRead]:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
detail = freight_svc.get_settlement_by_transport_id(
|
||||
db, tenant_no=tenant_no, transport_id=transport_id, user=user
|
||||
)
|
||||
if not detail:
|
||||
return None
|
||||
return FreightSettlementByTransportRead.model_validate(detail)
|
||||
|
||||
|
||||
@router.post("/", response_model=FreightSettlementRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_settlement(
|
||||
payload: FreightSettlementCreate,
|
||||
|
||||
@@ -9,6 +9,7 @@ from sqlalchemy.orm import Session
|
||||
from app.core.config import settings
|
||||
from app.core.database import get_db
|
||||
from app.core.db_auth import DbUserContext, get_db_current_user
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator, user_info_from_context
|
||||
from app.services.pl_data_scope import apply_pl_scope, assert_pl_row_in_scope
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.core.utils import generate_uuid
|
||||
@@ -970,6 +971,7 @@ def delete_plan(
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
plan = _get_plan(db, tenant_no, id, user)
|
||||
|
||||
assert_row_deletable_by_creator(plan, user_info_from_context(user))
|
||||
plan.del_flag = 1
|
||||
plan.update_by = user.id
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ from app.services.pl_transport_site_service import (
|
||||
sync_site_links_for_transport,
|
||||
)
|
||||
from app.services.pl_plan_service import is_ton_product_unit
|
||||
from app.services.pl_carrier_service import assert_carrier_eligible_for_transport
|
||||
|
||||
router = APIRouter(prefix="/order", tags=["运输计划-运输管理"])
|
||||
|
||||
@@ -166,6 +167,41 @@ def _get_vehicle_info(db: Session, vehicle_id: str, tenant_no: Optional[str] = N
|
||||
return info
|
||||
|
||||
|
||||
def _assert_vehicle_carrier_eligible(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
vehicle_id: str,
|
||||
*,
|
||||
rearrange: bool = False,
|
||||
) -> None:
|
||||
vehicle = (
|
||||
db.query(PlVehicle)
|
||||
.filter(
|
||||
PlVehicle.id == vehicle_id,
|
||||
PlVehicle.tenant_no == tenant_no,
|
||||
PlVehicle.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not vehicle:
|
||||
raise HTTPException(status_code=400, detail="车辆不存在")
|
||||
if not vehicle.carrier_id:
|
||||
return
|
||||
|
||||
carrier = (
|
||||
db.query(PlCarrier)
|
||||
.filter(
|
||||
PlCarrier.id == vehicle.carrier_id,
|
||||
PlCarrier.tenant_no == tenant_no,
|
||||
PlCarrier.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not carrier:
|
||||
raise HTTPException(status_code=400, detail="承运商不存在")
|
||||
assert_carrier_eligible_for_transport(carrier, rearrange=rearrange)
|
||||
|
||||
|
||||
def _ensure_driver_access_token(transport: PlTransport) -> str:
|
||||
token = (transport.driver_access_token or "").strip()
|
||||
if not token:
|
||||
@@ -262,7 +298,8 @@ def _apply_transport_arrange_fields(
|
||||
transport.freight_price = payload.freight_price
|
||||
transport.freight_amount = freight_amount or 0
|
||||
transport.freight_type = payload.freight_type
|
||||
transport.has_invoice = payload.has_invoice
|
||||
if payload.has_invoice is not None:
|
||||
transport.has_invoice = payload.has_invoice
|
||||
if payload.remark is not None:
|
||||
transport.remark = payload.remark
|
||||
transport.update_by = user_id
|
||||
@@ -441,6 +478,26 @@ def arrange_transport(
|
||||
raise HTTPException(status_code=404, detail="计划不存在")
|
||||
assert_pl_row_in_scope(db, plan, PlPlan, user, tenant_no)
|
||||
|
||||
existing_transport = None
|
||||
if plan.transport_id:
|
||||
existing_transport = db.query(PlTransport).filter(
|
||||
PlTransport.id == plan.transport_id,
|
||||
PlTransport.tenant_no == tenant_no,
|
||||
PlTransport.del_flag == 0,
|
||||
).first()
|
||||
|
||||
is_rearrange = _is_rearrange(plan, existing_transport)
|
||||
|
||||
if is_rearrange and existing_transport and existing_transport.vehicle_id:
|
||||
_assert_vehicle_carrier_eligible(
|
||||
db,
|
||||
tenant_no,
|
||||
existing_transport.vehicle_id,
|
||||
rearrange=True,
|
||||
)
|
||||
|
||||
_assert_vehicle_carrier_eligible(db, tenant_no, payload.vehicle_id, rearrange=False)
|
||||
|
||||
# 获取车辆信息
|
||||
vehicle_info = _get_vehicle_info(db, payload.vehicle_id, tenant_no)
|
||||
|
||||
@@ -481,16 +538,6 @@ def arrange_transport(
|
||||
_apply_plan_logistics_from_payload(plan, payload)
|
||||
start_point, end_point = _resolve_transport_endpoints(plan, payload)
|
||||
|
||||
existing_transport = None
|
||||
if plan.transport_id:
|
||||
existing_transport = db.query(PlTransport).filter(
|
||||
PlTransport.id == plan.transport_id,
|
||||
PlTransport.tenant_no == tenant_no,
|
||||
PlTransport.del_flag == 0,
|
||||
).first()
|
||||
|
||||
is_rearrange = _is_rearrange(plan, existing_transport)
|
||||
|
||||
if (
|
||||
plan.plan_type in ("inbound", "outbound")
|
||||
and is_rearrange
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
"""车辆维保 API"""
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.db_auth import DbUserContext, get_db_current_user
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.schemas.pl.vehicle_maintenance import (
|
||||
VehicleMaintenanceApplyCreate,
|
||||
VehicleMaintenanceApplyListResponse,
|
||||
VehicleMaintenanceApplyRead,
|
||||
VehicleMaintenanceApplyReminder,
|
||||
VehicleMaintenanceApplyUpdate,
|
||||
VehicleMaintenanceProcessBoardListResponse,
|
||||
VehicleMaintenanceProcessCreate,
|
||||
VehicleMaintenanceProcessRead,
|
||||
VehicleMaintenanceProcessUpdate,
|
||||
)
|
||||
from app.services import pl_vehicle_maintenance_service as svc
|
||||
|
||||
apply_router = APIRouter(prefix="/vehicle-maintenance-apply", tags=["运输计划-车辆维保申请"])
|
||||
process_router = APIRouter(prefix="/vehicle-maintenance-process", tags=["运输计划-车辆维保处理"])
|
||||
|
||||
|
||||
@apply_router.get("/", response_model=VehicleMaintenanceApplyListResponse)
|
||||
def list_applies(
|
||||
q: Optional[str] = Query(default=None),
|
||||
maintenance_type: Optional[str] = Query(default=None, alias="maintenanceType"),
|
||||
status: Optional[str] = Query(default=None),
|
||||
apply_date_from: Optional[date] = Query(default=None, alias="applyDateFrom"),
|
||||
apply_date_to: Optional[date] = Query(default=None, alias="applyDateTo"),
|
||||
applicant_id: Optional[str] = Query(default=None, alias="applicantId"),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100, alias="pageSize"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleMaintenanceApplyListResponse:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
items, total = svc.list_applies(
|
||||
db,
|
||||
tenant_no,
|
||||
user,
|
||||
q=q,
|
||||
maintenance_type=maintenance_type,
|
||||
status=status,
|
||||
apply_date_from=apply_date_from,
|
||||
apply_date_to=apply_date_to,
|
||||
applicant_id=applicant_id,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return VehicleMaintenanceApplyListResponse(
|
||||
items=items, total=total, page=page, pageSize=page_size
|
||||
)
|
||||
|
||||
|
||||
@apply_router.get("/reminders", response_model=list[VehicleMaintenanceApplyReminder])
|
||||
def list_reminders(
|
||||
license_plate: str = Query(..., alias="licensePlate"),
|
||||
exclude_apply_id: Optional[str] = Query(default=None, alias="excludeApplyId"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> list[VehicleMaintenanceApplyReminder]:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.list_reminders(
|
||||
db,
|
||||
tenant_no,
|
||||
user,
|
||||
license_plate,
|
||||
exclude_apply_id=exclude_apply_id,
|
||||
)
|
||||
|
||||
|
||||
@apply_router.get("/{id}", response_model=VehicleMaintenanceApplyRead)
|
||||
def get_apply(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleMaintenanceApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.get_apply(db, tenant_no, user, id)
|
||||
|
||||
|
||||
@apply_router.post("/", response_model=VehicleMaintenanceApplyRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_apply(
|
||||
payload: VehicleMaintenanceApplyCreate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleMaintenanceApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.create_apply(db, tenant_no, user, payload)
|
||||
|
||||
|
||||
@apply_router.put("/{id}", response_model=VehicleMaintenanceApplyRead)
|
||||
def update_apply(
|
||||
id: str,
|
||||
payload: VehicleMaintenanceApplyUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleMaintenanceApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.update_apply(db, tenant_no, user, id, payload)
|
||||
|
||||
|
||||
@apply_router.post("/{id}/cancel", response_model=VehicleMaintenanceApplyRead)
|
||||
def cancel_apply(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleMaintenanceApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.cancel_apply(db, tenant_no, user, id)
|
||||
|
||||
|
||||
@apply_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_applies(
|
||||
ids: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
apply_ids = [item for item in ids.split(",") if item]
|
||||
svc.delete_applies(db, tenant_no, user, apply_ids)
|
||||
|
||||
|
||||
@process_router.get("/process-board", response_model=VehicleMaintenanceProcessBoardListResponse)
|
||||
def list_process_board(
|
||||
q: Optional[str] = Query(default=None),
|
||||
maintenance_type: Optional[str] = Query(default=None, alias="maintenanceType"),
|
||||
apply_status: Optional[str] = Query(default=None, alias="applyStatus"),
|
||||
apply_date_from: Optional[date] = Query(default=None, alias="applyDateFrom"),
|
||||
apply_date_to: Optional[date] = Query(default=None, alias="applyDateTo"),
|
||||
process_date_from: Optional[date] = Query(default=None, alias="processDateFrom"),
|
||||
process_date_to: Optional[date] = Query(default=None, alias="processDateTo"),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100, alias="pageSize"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleMaintenanceProcessBoardListResponse:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
items, total = svc.list_process_board(
|
||||
db,
|
||||
tenant_no,
|
||||
user,
|
||||
q=q,
|
||||
maintenance_type=maintenance_type,
|
||||
apply_status=apply_status,
|
||||
apply_date_from=apply_date_from,
|
||||
apply_date_to=apply_date_to,
|
||||
process_date_from=process_date_from,
|
||||
process_date_to=process_date_to,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return VehicleMaintenanceProcessBoardListResponse(
|
||||
items=items, total=total, page=page, pageSize=page_size
|
||||
)
|
||||
|
||||
|
||||
@process_router.get("/{id}", response_model=VehicleMaintenanceProcessRead)
|
||||
def get_process(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleMaintenanceProcessRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.get_process(db, tenant_no, user, id)
|
||||
|
||||
|
||||
@process_router.post("/", response_model=VehicleMaintenanceProcessRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_process(
|
||||
payload: VehicleMaintenanceProcessCreate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleMaintenanceProcessRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.create_process(db, tenant_no, user, payload)
|
||||
|
||||
|
||||
@process_router.put("/{id}", response_model=VehicleMaintenanceProcessRead)
|
||||
def update_process(
|
||||
id: str,
|
||||
payload: VehicleMaintenanceProcessUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleMaintenanceProcessRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.update_process(db, tenant_no, user, id, payload)
|
||||
|
||||
|
||||
@process_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_processes(
|
||||
ids: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
process_ids = [item for item in ids.split(",") if item]
|
||||
svc.delete_processes(db, tenant_no, user, process_ids)
|
||||
@@ -0,0 +1,228 @@
|
||||
"""车辆 ETC/加油充值 API"""
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.db_auth import DbUserContext, get_db_current_user
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.schemas.pl.vehicle_recharge import (
|
||||
VehicleRechargeApplyCreate,
|
||||
VehicleRechargeApplyListResponse,
|
||||
VehicleRechargeApplyOption,
|
||||
VehicleRechargeApplyRead,
|
||||
VehicleRechargeApplyUpdate,
|
||||
VehicleRechargeDisburseBoardListResponse,
|
||||
VehicleRechargeRecordCreate,
|
||||
VehicleRechargeRecordListResponse,
|
||||
VehicleRechargeRecordRead,
|
||||
VehicleRechargeRecordUpdate,
|
||||
)
|
||||
from app.services import pl_vehicle_recharge_service as svc
|
||||
|
||||
apply_router = APIRouter(prefix="/vehicle-recharge-apply", tags=["运输计划-ETC加油申请"])
|
||||
record_router = APIRouter(prefix="/vehicle-recharge-record", tags=["运输计划-ETC加油发放"])
|
||||
|
||||
|
||||
@apply_router.get("/", response_model=VehicleRechargeApplyListResponse)
|
||||
def list_applies(
|
||||
q: Optional[str] = Query(default=None),
|
||||
recharge_type: Optional[str] = Query(default=None, alias="rechargeType"),
|
||||
status: Optional[str] = Query(default=None),
|
||||
apply_date_from: Optional[date] = Query(default=None, alias="applyDateFrom"),
|
||||
apply_date_to: Optional[date] = Query(default=None, alias="applyDateTo"),
|
||||
applicant_id: Optional[str] = Query(default=None, alias="applicantId"),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100, alias="pageSize"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleRechargeApplyListResponse:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
items, total = svc.list_applies(
|
||||
db,
|
||||
tenant_no,
|
||||
user,
|
||||
q=q,
|
||||
recharge_type=recharge_type,
|
||||
status=status,
|
||||
apply_date_from=apply_date_from,
|
||||
apply_date_to=apply_date_to,
|
||||
applicant_id=applicant_id,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return VehicleRechargeApplyListResponse(
|
||||
items=items, total=total, page=page, pageSize=page_size
|
||||
)
|
||||
|
||||
|
||||
@apply_router.get("/pending-options", response_model=list[VehicleRechargeApplyOption])
|
||||
def pending_apply_options(
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> list[VehicleRechargeApplyOption]:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
rows = svc.list_pending_apply_options(db, tenant_no, user)
|
||||
return [VehicleRechargeApplyOption.model_validate(row) for row in rows]
|
||||
|
||||
|
||||
@apply_router.get("/{id}", response_model=VehicleRechargeApplyRead)
|
||||
def get_apply(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleRechargeApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.get_apply(db, tenant_no, user, id)
|
||||
|
||||
|
||||
@apply_router.post("/", response_model=VehicleRechargeApplyRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_apply(
|
||||
payload: VehicleRechargeApplyCreate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleRechargeApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.create_apply(db, tenant_no, user, payload)
|
||||
|
||||
|
||||
@apply_router.put("/{id}", response_model=VehicleRechargeApplyRead)
|
||||
def update_apply(
|
||||
id: str,
|
||||
payload: VehicleRechargeApplyUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleRechargeApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.update_apply(db, tenant_no, user, id, payload)
|
||||
|
||||
|
||||
@apply_router.post("/{id}/cancel", response_model=VehicleRechargeApplyRead)
|
||||
def cancel_apply(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleRechargeApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.cancel_apply(db, tenant_no, user, id)
|
||||
|
||||
|
||||
@apply_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_applies(
|
||||
ids: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
apply_ids = [item for item in ids.split(",") if item]
|
||||
svc.delete_applies(db, tenant_no, user, apply_ids)
|
||||
|
||||
|
||||
@record_router.get("/", response_model=VehicleRechargeRecordListResponse)
|
||||
def list_records(
|
||||
q: Optional[str] = Query(default=None),
|
||||
recharge_type: Optional[str] = Query(default=None, alias="rechargeType"),
|
||||
recharge_date_from: Optional[date] = Query(default=None, alias="rechargeDateFrom"),
|
||||
recharge_date_to: Optional[date] = Query(default=None, alias="rechargeDateTo"),
|
||||
gas_station: Optional[str] = Query(default=None, alias="gasStation"),
|
||||
apply_no: Optional[str] = Query(default=None, alias="applyNo"),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100, alias="pageSize"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleRechargeRecordListResponse:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
items, total = svc.list_records(
|
||||
db,
|
||||
tenant_no,
|
||||
user,
|
||||
q=q,
|
||||
recharge_type=recharge_type,
|
||||
recharge_date_from=recharge_date_from,
|
||||
recharge_date_to=recharge_date_to,
|
||||
gas_station=gas_station,
|
||||
apply_no=apply_no,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return VehicleRechargeRecordListResponse(
|
||||
items=items, total=total, page=page, pageSize=page_size
|
||||
)
|
||||
|
||||
|
||||
@record_router.get("/disburse-board", response_model=VehicleRechargeDisburseBoardListResponse)
|
||||
def list_disburse_board(
|
||||
q: Optional[str] = Query(default=None),
|
||||
recharge_type: Optional[str] = Query(default=None, alias="rechargeType"),
|
||||
apply_status: Optional[str] = Query(default=None, alias="applyStatus"),
|
||||
apply_date_from: Optional[date] = Query(default=None, alias="applyDateFrom"),
|
||||
apply_date_to: Optional[date] = Query(default=None, alias="applyDateTo"),
|
||||
recharge_date_from: Optional[date] = Query(default=None, alias="rechargeDateFrom"),
|
||||
recharge_date_to: Optional[date] = Query(default=None, alias="rechargeDateTo"),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100, alias="pageSize"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleRechargeDisburseBoardListResponse:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
items, total = svc.list_disburse_board(
|
||||
db,
|
||||
tenant_no,
|
||||
user,
|
||||
q=q,
|
||||
recharge_type=recharge_type,
|
||||
apply_status=apply_status,
|
||||
apply_date_from=apply_date_from,
|
||||
apply_date_to=apply_date_to,
|
||||
recharge_date_from=recharge_date_from,
|
||||
recharge_date_to=recharge_date_to,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return VehicleRechargeDisburseBoardListResponse(
|
||||
items=items, total=total, page=page, pageSize=page_size
|
||||
)
|
||||
|
||||
|
||||
@record_router.get("/{id}", response_model=VehicleRechargeRecordRead)
|
||||
def get_record(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleRechargeRecordRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.get_record(db, tenant_no, user, id)
|
||||
|
||||
|
||||
@record_router.post("/", response_model=VehicleRechargeRecordRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_record(
|
||||
payload: VehicleRechargeRecordCreate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleRechargeRecordRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.create_record(db, tenant_no, user, payload)
|
||||
|
||||
|
||||
@record_router.put("/{id}", response_model=VehicleRechargeRecordRead)
|
||||
def update_record(
|
||||
id: str,
|
||||
payload: VehicleRechargeRecordUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleRechargeRecordRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.update_record(db, tenant_no, user, id, payload)
|
||||
|
||||
|
||||
@record_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_records(
|
||||
ids: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
record_ids = [item for item in ids.split(",") if item]
|
||||
svc.delete_records(db, tenant_no, user, record_ids)
|
||||
@@ -0,0 +1,180 @@
|
||||
"""车辆尿素 API"""
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.db_auth import DbUserContext, get_db_current_user
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.schemas.pl.vehicle_urea import (
|
||||
VehicleUreaApplyCreate,
|
||||
VehicleUreaApplyListResponse,
|
||||
VehicleUreaApplyRead,
|
||||
VehicleUreaApplyUpdate,
|
||||
VehicleUreaDisburseBoardListResponse,
|
||||
VehicleUreaDisburseCreate,
|
||||
VehicleUreaDisburseRead,
|
||||
VehicleUreaDisburseUpdate,
|
||||
)
|
||||
from app.services import pl_vehicle_urea_service as svc
|
||||
|
||||
apply_router = APIRouter(prefix="/vehicle-urea-apply", tags=["运输计划-车辆尿素申请"])
|
||||
disburse_router = APIRouter(prefix="/vehicle-urea-disburse", tags=["运输计划-车辆尿素发放"])
|
||||
|
||||
|
||||
@apply_router.get("/", response_model=VehicleUreaApplyListResponse)
|
||||
def list_applies(
|
||||
q: Optional[str] = Query(default=None),
|
||||
status: Optional[str] = Query(default=None),
|
||||
apply_date_from: Optional[date] = Query(default=None, alias="applyDateFrom"),
|
||||
apply_date_to: Optional[date] = Query(default=None, alias="applyDateTo"),
|
||||
applicant_id: Optional[str] = Query(default=None, alias="applicantId"),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100, alias="pageSize"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleUreaApplyListResponse:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
items, total = svc.list_applies(
|
||||
db,
|
||||
tenant_no,
|
||||
user,
|
||||
q=q,
|
||||
status=status,
|
||||
apply_date_from=apply_date_from,
|
||||
apply_date_to=apply_date_to,
|
||||
applicant_id=applicant_id,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return VehicleUreaApplyListResponse(
|
||||
items=items, total=total, page=page, pageSize=page_size
|
||||
)
|
||||
|
||||
|
||||
@apply_router.get("/{id}", response_model=VehicleUreaApplyRead)
|
||||
def get_apply(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleUreaApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.get_apply(db, tenant_no, user, id)
|
||||
|
||||
|
||||
@apply_router.post("/", response_model=VehicleUreaApplyRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_apply(
|
||||
payload: VehicleUreaApplyCreate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleUreaApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.create_apply(db, tenant_no, user, payload)
|
||||
|
||||
|
||||
@apply_router.put("/{id}", response_model=VehicleUreaApplyRead)
|
||||
def update_apply(
|
||||
id: str,
|
||||
payload: VehicleUreaApplyUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleUreaApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.update_apply(db, tenant_no, user, id, payload)
|
||||
|
||||
|
||||
@apply_router.post("/{id}/cancel", response_model=VehicleUreaApplyRead)
|
||||
def cancel_apply(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleUreaApplyRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.cancel_apply(db, tenant_no, user, id)
|
||||
|
||||
|
||||
@apply_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_applies(
|
||||
ids: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
apply_ids = [item for item in ids.split(",") if item]
|
||||
svc.delete_applies(db, tenant_no, user, apply_ids)
|
||||
|
||||
|
||||
@disburse_router.get("/disburse-board", response_model=VehicleUreaDisburseBoardListResponse)
|
||||
def list_disburse_board(
|
||||
q: Optional[str] = Query(default=None),
|
||||
apply_status: Optional[str] = Query(default=None, alias="applyStatus"),
|
||||
apply_date_from: Optional[date] = Query(default=None, alias="applyDateFrom"),
|
||||
apply_date_to: Optional[date] = Query(default=None, alias="applyDateTo"),
|
||||
disburse_date_from: Optional[date] = Query(default=None, alias="disburseDateFrom"),
|
||||
disburse_date_to: Optional[date] = Query(default=None, alias="disburseDateTo"),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100, alias="pageSize"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleUreaDisburseBoardListResponse:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
items, total = svc.list_disburse_board(
|
||||
db,
|
||||
tenant_no,
|
||||
user,
|
||||
q=q,
|
||||
apply_status=apply_status,
|
||||
apply_date_from=apply_date_from,
|
||||
apply_date_to=apply_date_to,
|
||||
disburse_date_from=disburse_date_from,
|
||||
disburse_date_to=disburse_date_to,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return VehicleUreaDisburseBoardListResponse(
|
||||
items=items, total=total, page=page, pageSize=page_size
|
||||
)
|
||||
|
||||
|
||||
@disburse_router.get("/{id}", response_model=VehicleUreaDisburseRead)
|
||||
def get_disburse(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleUreaDisburseRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.get_disburse(db, tenant_no, user, id)
|
||||
|
||||
|
||||
@disburse_router.post("/", response_model=VehicleUreaDisburseRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_disburse(
|
||||
payload: VehicleUreaDisburseCreate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleUreaDisburseRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.create_disburse(db, tenant_no, user, payload)
|
||||
|
||||
|
||||
@disburse_router.put("/{id}", response_model=VehicleUreaDisburseRead)
|
||||
def update_disburse(
|
||||
id: str,
|
||||
payload: VehicleUreaDisburseUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> VehicleUreaDisburseRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return svc.update_disburse(db, tenant_no, user, id, payload)
|
||||
|
||||
|
||||
@disburse_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_disburses(
|
||||
ids: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> None:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
disburse_ids = [item for item in ids.split(",") if item]
|
||||
svc.delete_disburses(db, tenant_no, user, disburse_ids)
|
||||
@@ -57,12 +57,14 @@ def _build_user_info_payload(user: SysUser, web_role: str) -> dict:
|
||||
|
||||
|
||||
def _jwt_user_payload(user: SysUser, web_role: str) -> dict:
|
||||
tenant_no = (getattr(user, "tenant_no", None) or "").strip() or settings.DEFAULT_TENANT_NO
|
||||
return {
|
||||
"sub": user.id,
|
||||
"username": user.user_no,
|
||||
"role": web_role,
|
||||
"region_id": user.dept_id,
|
||||
"real_name": user.nick_name,
|
||||
"tenant_no": tenant_no,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ async def delete_process_def(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
wf_crud.soft_delete_process_def(db, def_id)
|
||||
wf_crud.soft_delete_process_def(db, def_id, user_info=_user)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
|
||||
@@ -13,6 +13,8 @@ from starlette.responses import Response
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.database import SessionLocal
|
||||
from app.core.oper_log_context import clear_oper_log_meta, snapshot_oper_log_meta
|
||||
from app.core.oper_log_infer import business_type_from_path, title_from_path
|
||||
|
||||
logger = logging.getLogger("sqxp")
|
||||
|
||||
@@ -40,6 +42,26 @@ def _truncate(s: str, max_len: int) -> str:
|
||||
return s[:max_len] + f"\n... [truncated, total {len(s)} chars]"
|
||||
|
||||
|
||||
# MySQL TEXT 列上限为 65535 字节(非字符);utf8mb4 下中文等多字节字符需按字节截断。
|
||||
_DB_TEXT_MAX_BYTES = 60000
|
||||
|
||||
|
||||
def _truncate_utf8_bytes(s: str, max_bytes: int = _DB_TEXT_MAX_BYTES) -> str:
|
||||
if not s:
|
||||
return s
|
||||
encoded = s.encode("utf-8")
|
||||
if len(encoded) <= max_bytes:
|
||||
return s
|
||||
truncated = encoded[:max_bytes]
|
||||
while truncated and (truncated[-1] & 0xC0) == 0x80:
|
||||
truncated = truncated[:-1]
|
||||
text = truncated.decode("utf-8", errors="ignore")
|
||||
return (
|
||||
text
|
||||
+ f"\n... [truncated, total {len(s)} chars, {len(encoded)} bytes]"
|
||||
)
|
||||
|
||||
|
||||
def _redact_obj(obj: Any) -> Any:
|
||||
if isinstance(obj, dict):
|
||||
out = {}
|
||||
@@ -86,12 +108,14 @@ def _format_body_bytes(raw: bytes, content_type: str, max_bytes: int) -> str:
|
||||
return _truncate(raw.decode("utf-8", errors="replace"), settings.API_REQUEST_LOG_MAX_BODY)
|
||||
|
||||
|
||||
def _claims_from_auth(auth_header: Optional[str]) -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
||||
def _claims_from_auth(
|
||||
auth_header: Optional[str],
|
||||
) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]]:
|
||||
if not auth_header or not auth_header.startswith("Bearer "):
|
||||
return None, None, None
|
||||
return None, None, None, None, None
|
||||
token = auth_header[7:].strip()
|
||||
if not token:
|
||||
return None, None, None
|
||||
return None, None, None, None, None
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
@@ -99,9 +123,28 @@ def _claims_from_auth(auth_header: Optional[str]) -> Tuple[Optional[str], Option
|
||||
algorithms=[settings.ALGORITHM],
|
||||
options={"verify_signature": False, "verify_exp": False},
|
||||
)
|
||||
return payload.get("sub"), payload.get("username"), payload.get("role")
|
||||
return (
|
||||
payload.get("sub"),
|
||||
payload.get("username"),
|
||||
payload.get("role"),
|
||||
payload.get("real_name"),
|
||||
payload.get("tenant_no"),
|
||||
)
|
||||
except Exception:
|
||||
return None, None, None
|
||||
return None, None, None, None, None
|
||||
|
||||
|
||||
def _should_skip_exclude(path: str) -> bool:
|
||||
raw = (settings.API_REQUEST_LOG_EXCLUDE_PATHS or "").strip()
|
||||
if not raw:
|
||||
return False
|
||||
for item in raw.split(","):
|
||||
prefix = item.strip()
|
||||
if not prefix:
|
||||
continue
|
||||
if path == prefix or path.startswith(prefix):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _should_skip(request: Request) -> bool:
|
||||
@@ -112,6 +155,8 @@ def _should_skip(request: Request) -> bool:
|
||||
return True
|
||||
if p.startswith("/docs") or p.startswith("/redoc"):
|
||||
return True
|
||||
if _should_skip_exclude(p):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -129,6 +174,12 @@ def _persist_log(
|
||||
role: Optional[str],
|
||||
ip_address: Optional[str],
|
||||
user_agent: Optional[str],
|
||||
create_name: Optional[str] = None,
|
||||
tenant_no: Optional[str] = None,
|
||||
business_title: Optional[str] = None,
|
||||
business_type: Optional[int] = None,
|
||||
business_id: Optional[str] = None,
|
||||
oper_summary: Optional[str] = None,
|
||||
) -> None:
|
||||
from datetime import datetime
|
||||
|
||||
@@ -142,8 +193,8 @@ def _persist_log(
|
||||
http_method=http_method,
|
||||
path=_truncate(path, 500),
|
||||
query_string=_truncate(query_string or "", 2000) or None,
|
||||
request_body=request_body,
|
||||
response_body=response_body,
|
||||
request_body=_truncate_utf8_bytes(request_body) if request_body else None,
|
||||
response_body=_truncate_utf8_bytes(response_body) if response_body else None,
|
||||
status_code=status_code,
|
||||
duration_ms=duration_ms,
|
||||
user_id=user_id,
|
||||
@@ -151,10 +202,15 @@ def _persist_log(
|
||||
role=role,
|
||||
ip_address=ip_address,
|
||||
user_agent=_truncate(user_agent or "", 500) if user_agent else None,
|
||||
tenant_no=_truncate(tenant_no or "", 64) if tenant_no else None,
|
||||
business_title=_truncate(business_title or "", 100) if business_title else None,
|
||||
business_type=business_type,
|
||||
business_id=_truncate(business_id or "", 64) if business_id else None,
|
||||
oper_summary=_truncate(oper_summary or "", 500) if oper_summary else None,
|
||||
is_del=0,
|
||||
create_at=datetime.now(),
|
||||
create_by=None,
|
||||
create_name=None,
|
||||
create_by=user_id,
|
||||
create_name=_truncate(create_name or "", 100) if create_name else None,
|
||||
)
|
||||
db.add(row)
|
||||
db.commit()
|
||||
@@ -209,7 +265,7 @@ class ApiRequestLogMiddleware(BaseHTTPMiddleware):
|
||||
|
||||
request = Request(request.scope, receive)
|
||||
|
||||
uid, uname, role = _claims_from_auth(request.headers.get("authorization"))
|
||||
uid, uname, role, real_name, tenant_no = _claims_from_auth(request.headers.get("authorization"))
|
||||
ip = request.headers.get("x-forwarded-for", "").split(",")[0].strip()
|
||||
if not ip and request.client:
|
||||
ip = request.client.host
|
||||
@@ -258,6 +314,16 @@ class ApiRequestLogMiddleware(BaseHTTPMiddleware):
|
||||
elapsed_ms = int((time.perf_counter() - start) * 1000)
|
||||
path = request.url.path
|
||||
qs = request.url.query
|
||||
meta = snapshot_oper_log_meta()
|
||||
biz_title = meta.business_title if meta and meta.business_title else title_from_path(path)
|
||||
biz_type = (
|
||||
meta.business_type
|
||||
if meta and meta.business_type is not None
|
||||
else business_type_from_path(path, request.method)
|
||||
)
|
||||
biz_id = meta.business_id if meta else None
|
||||
oper_summary = meta.oper_summary if meta else None
|
||||
effective_tenant = tenant_no or settings.DEFAULT_TENANT_NO
|
||||
_schedule_persist_log(
|
||||
http_method=request.method,
|
||||
path=path,
|
||||
@@ -271,6 +337,13 @@ class ApiRequestLogMiddleware(BaseHTTPMiddleware):
|
||||
role=role,
|
||||
ip_address=ip,
|
||||
user_agent=ua,
|
||||
create_name=real_name,
|
||||
tenant_no=effective_tenant,
|
||||
business_title=biz_title,
|
||||
business_type=biz_type,
|
||||
business_id=biz_id,
|
||||
oper_summary=oper_summary,
|
||||
)
|
||||
clear_oper_log_meta()
|
||||
|
||||
return out
|
||||
@@ -122,6 +122,7 @@ class Settings(BaseSettings):
|
||||
API_REQUEST_LOG_ENABLED: bool = True
|
||||
API_REQUEST_LOG_MAX_BODY: int = 65536
|
||||
API_REQUEST_LOG_MAX_BODY_BYTES: int = 131072
|
||||
API_REQUEST_LOG_EXCLUDE_PATHS: str = ""
|
||||
|
||||
# Web API 请求/响应加密配置
|
||||
API_ENCRYPT_ENABLED: bool = False
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"""删除权限:仅创建人可删(super_admin 例外)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Iterable, Optional, Union
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
RowLike = Union[Any, Dict[str, Any]]
|
||||
|
||||
|
||||
def normalize_user_info(
|
||||
user_info: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
user_id: Optional[str] = None,
|
||||
actor_name: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
info: Dict[str, Any] = dict(user_info or {})
|
||||
uid = (user_id or info.get("user_id") or info.get("id") or "").strip()
|
||||
if uid:
|
||||
info["user_id"] = uid
|
||||
name = (actor_name or info.get("real_name") or info.get("username") or info.get("userName") or info.get("name") or "").strip()
|
||||
if name:
|
||||
info["real_name"] = name
|
||||
return info
|
||||
|
||||
|
||||
def user_info_from_context(ctx_or_dict: Any) -> Dict[str, Any]:
|
||||
if isinstance(ctx_or_dict, dict):
|
||||
return normalize_user_info(ctx_or_dict)
|
||||
info: Dict[str, Any] = {}
|
||||
if hasattr(ctx_or_dict, "to_user_info"):
|
||||
info.update(ctx_or_dict.to_user_info())
|
||||
name = (getattr(ctx_or_dict, "name", None) or "").strip()
|
||||
if name:
|
||||
info["real_name"] = name
|
||||
return normalize_user_info(info)
|
||||
|
||||
|
||||
def is_super_admin(user_info: Optional[Dict[str, Any]]) -> bool:
|
||||
if not user_info:
|
||||
return False
|
||||
return (user_info.get("role") or "").strip() == "super_admin"
|
||||
|
||||
|
||||
def row_creator_id(row: RowLike) -> str:
|
||||
if row is None:
|
||||
return ""
|
||||
if isinstance(row, dict):
|
||||
return (row.get("create_by") or row.get("createBy") or "").strip()
|
||||
return (getattr(row, "create_by", None) or getattr(row, "createBy", None) or "").strip()
|
||||
|
||||
|
||||
def _actor_ids(user_info: Optional[Dict[str, Any]]) -> set[str]:
|
||||
if not user_info:
|
||||
return set()
|
||||
ids: set[str] = set()
|
||||
for key in ("user_id", "id"):
|
||||
val = (user_info.get(key) or "").strip()
|
||||
if val:
|
||||
ids.add(val)
|
||||
return ids
|
||||
|
||||
|
||||
def _actor_names(user_info: Optional[Dict[str, Any]]) -> set[str]:
|
||||
if not user_info:
|
||||
return set()
|
||||
names: set[str] = set()
|
||||
for key in ("real_name", "username", "userName", "name"):
|
||||
val = (user_info.get(key) or "").strip()
|
||||
if val:
|
||||
names.add(val)
|
||||
return names
|
||||
|
||||
|
||||
def can_delete_by_creator(
|
||||
row: RowLike,
|
||||
user_info: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
user_id: Optional[str] = None,
|
||||
actor_name: Optional[str] = None,
|
||||
) -> bool:
|
||||
info = normalize_user_info(user_info, user_id=user_id, actor_name=actor_name)
|
||||
if is_super_admin(info):
|
||||
return True
|
||||
creator = row_creator_id(row)
|
||||
if not creator:
|
||||
return False
|
||||
if creator in _actor_ids(info):
|
||||
return True
|
||||
if creator in _actor_names(info):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def assert_row_deletable_by_creator(
|
||||
row: RowLike,
|
||||
user_info: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
user_id: Optional[str] = None,
|
||||
actor_name: Optional[str] = None,
|
||||
message: str = "仅创建人可删除",
|
||||
) -> None:
|
||||
if can_delete_by_creator(row, user_info, user_id=user_id, actor_name=actor_name):
|
||||
return
|
||||
raise HTTPException(status_code=403, detail=message)
|
||||
|
||||
|
||||
def assert_rows_deletable_by_creator(
|
||||
rows: Iterable[RowLike],
|
||||
user_info: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
user_id: Optional[str] = None,
|
||||
actor_name: Optional[str] = None,
|
||||
message: str = "仅创建人可删除",
|
||||
) -> None:
|
||||
info = normalize_user_info(user_info, user_id=user_id, actor_name=actor_name)
|
||||
for row in rows:
|
||||
assert_row_deletable_by_creator(row, info, message=message)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""请求级操作日志元数据(ContextVar)"""
|
||||
from __future__ import annotations
|
||||
|
||||
from contextvars import ContextVar
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class OperLogMeta:
|
||||
business_title: Optional[str] = None
|
||||
business_type: Optional[int] = None
|
||||
business_id: Optional[str] = None
|
||||
oper_summary: Optional[str] = None
|
||||
|
||||
|
||||
_oper_log_meta: ContextVar[Optional[OperLogMeta]] = ContextVar("oper_log_meta", default=None)
|
||||
|
||||
|
||||
def set_oper_log_meta(meta: OperLogMeta) -> None:
|
||||
_oper_log_meta.set(meta)
|
||||
|
||||
|
||||
def get_oper_log_meta() -> Optional[OperLogMeta]:
|
||||
return _oper_log_meta.get()
|
||||
|
||||
|
||||
def clear_oper_log_meta() -> None:
|
||||
_oper_log_meta.set(None)
|
||||
|
||||
|
||||
def snapshot_oper_log_meta() -> Optional[OperLogMeta]:
|
||||
"""供异步落库线程使用,避免 ContextVar 跨线程丢失。"""
|
||||
return get_oper_log_meta()
|
||||
@@ -0,0 +1,164 @@
|
||||
"""路由操作日志装饰器:写入 ContextVar 供中间件落库"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import inspect
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Optional, TypeVar, get_type_hints
|
||||
|
||||
from app.core.oper_log_context import OperLogMeta, set_oper_log_meta
|
||||
|
||||
F = TypeVar("F", bound=Callable[..., Any])
|
||||
|
||||
_SUMMARY_VERBS = {
|
||||
0: "操作",
|
||||
1: "新增",
|
||||
2: "修改",
|
||||
3: "删除",
|
||||
5: "导出",
|
||||
6: "导入",
|
||||
}
|
||||
|
||||
|
||||
def _to_dict(obj: Any) -> dict:
|
||||
if obj is None:
|
||||
return {}
|
||||
if isinstance(obj, dict):
|
||||
return obj
|
||||
if hasattr(obj, "model_dump"):
|
||||
try:
|
||||
return obj.model_dump(by_alias=True)
|
||||
except Exception:
|
||||
return obj.model_dump()
|
||||
return {}
|
||||
|
||||
|
||||
def _pick_field(data: dict, field: str) -> Optional[str]:
|
||||
if not field or not data:
|
||||
return None
|
||||
if field in data and data[field] is not None:
|
||||
return str(data[field])
|
||||
# camelCase from snake
|
||||
parts = field.split("_")
|
||||
camel = parts[0] + "".join(p.capitalize() for p in parts[1:])
|
||||
if camel in data and data[camel] is not None:
|
||||
return str(data[camel])
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_business_id(
|
||||
kwargs: dict,
|
||||
business_id_field: Optional[str],
|
||||
path_param: Optional[str],
|
||||
) -> Optional[str]:
|
||||
if path_param and kwargs.get(path_param) is not None:
|
||||
val = str(kwargs[path_param])
|
||||
if "," in val:
|
||||
return val.split(",")[0].strip() or None
|
||||
return val
|
||||
body = kwargs.get("body")
|
||||
data = _to_dict(body)
|
||||
if business_id_field:
|
||||
val = _pick_field(data, business_id_field)
|
||||
if val:
|
||||
return val
|
||||
raw = data.get(business_id_field)
|
||||
if isinstance(raw, list) and raw:
|
||||
return str(raw[0])
|
||||
if data.get("id"):
|
||||
return str(data["id"])
|
||||
return None
|
||||
|
||||
|
||||
def _format_summary(template: Optional[str], business_type: int, data: dict) -> Optional[str]:
|
||||
if template:
|
||||
out = template
|
||||
for key, val in data.items():
|
||||
out = out.replace(f"{{{key}}}", str(val) if val is not None else "")
|
||||
if "{" not in out and "}" not in out:
|
||||
return out[:500]
|
||||
name = (
|
||||
_pick_field(data, "customer_name")
|
||||
or _pick_field(data, "customerName")
|
||||
or _pick_field(data, "contract_name")
|
||||
or _pick_field(data, "contractName")
|
||||
or _pick_field(data, "contract_no")
|
||||
or _pick_field(data, "contractNo")
|
||||
or ""
|
||||
)
|
||||
verb = _SUMMARY_VERBS.get(business_type, "操作")
|
||||
if name:
|
||||
return f"{verb}【{name}】"[:500]
|
||||
return None
|
||||
|
||||
|
||||
def _preserve_fastapi_signature(wrapper: Callable[..., Any], func: Callable[..., Any]) -> None:
|
||||
"""FastAPI 通过 inspect.signature + wrapper.__globals__ 解析类型;需在原函数命名空间下解析后写回。"""
|
||||
try:
|
||||
hints = get_type_hints(func, globalns=func.__globals__, localns=None)
|
||||
sig = inspect.signature(func)
|
||||
typed_params = [
|
||||
inspect.Parameter(
|
||||
name=param.name,
|
||||
kind=param.kind,
|
||||
default=param.default,
|
||||
annotation=hints.get(param.name, param.annotation),
|
||||
)
|
||||
for param in sig.parameters.values()
|
||||
]
|
||||
wrapper.__signature__ = inspect.Signature(
|
||||
parameters=typed_params,
|
||||
return_annotation=hints.get("return", sig.return_annotation),
|
||||
)
|
||||
wrapper.__annotations__ = hints
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def oper_log(
|
||||
title: str,
|
||||
business_type: int = 0,
|
||||
business_id_field: Optional[str] = "id",
|
||||
path_param: Optional[str] = None,
|
||||
summary: Optional[str] = None,
|
||||
) -> Callable[[F], F]:
|
||||
"""在 handler 执行前设置操作日志元数据。"""
|
||||
|
||||
def decorator(func: F) -> F:
|
||||
@wraps(func)
|
||||
async def async_wrapper(*args, **kwargs):
|
||||
data = _to_dict(kwargs.get("body"))
|
||||
business_id = _resolve_business_id(kwargs, business_id_field, path_param)
|
||||
oper_summary = _format_summary(summary, business_type, data)
|
||||
set_oper_log_meta(
|
||||
OperLogMeta(
|
||||
business_title=title,
|
||||
business_type=business_type,
|
||||
business_id=business_id,
|
||||
oper_summary=oper_summary,
|
||||
)
|
||||
)
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
@wraps(func)
|
||||
def sync_wrapper(*args, **kwargs):
|
||||
data = _to_dict(kwargs.get("body"))
|
||||
business_id = _resolve_business_id(kwargs, business_id_field, path_param)
|
||||
oper_summary = _format_summary(summary, business_type, data)
|
||||
set_oper_log_meta(
|
||||
OperLogMeta(
|
||||
business_title=title,
|
||||
business_type=business_type,
|
||||
business_id=business_id,
|
||||
oper_summary=oper_summary,
|
||||
)
|
||||
)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
_preserve_fastapi_signature(async_wrapper, func)
|
||||
return async_wrapper # type: ignore[return-value]
|
||||
_preserve_fastapi_signature(sync_wrapper, func)
|
||||
return sync_wrapper # type: ignore[return-value]
|
||||
|
||||
return decorator
|
||||
@@ -0,0 +1,63 @@
|
||||
"""操作日志路径推断(中间件落库与查询兜底)"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import List, Tuple
|
||||
|
||||
_ACTION_TYPE_MAP = {
|
||||
"add": 1,
|
||||
"create": 1,
|
||||
"insert": 1,
|
||||
"update": 2,
|
||||
"edit": 2,
|
||||
"modify": 2,
|
||||
"delete": 3,
|
||||
"remove": 3,
|
||||
"export": 5,
|
||||
"import": 6,
|
||||
"transfer": 2,
|
||||
"intopool": 2,
|
||||
"deal": 2,
|
||||
"claim": 2,
|
||||
"release": 2,
|
||||
"invalid": 2,
|
||||
"assign": 2,
|
||||
"sign": 2,
|
||||
"renew": 1,
|
||||
}
|
||||
|
||||
_MODULE_TITLE_RULES: List[Tuple[re.Pattern[str], str]] = [
|
||||
(re.compile(r"/crm/customer", re.I), "CRM客户管理"),
|
||||
(re.compile(r"/crm/contact", re.I), "CRM联系人"),
|
||||
(re.compile(r"/crm/opportunity", re.I), "CRM商机管理"),
|
||||
(re.compile(r"/crm/contract", re.I), "CRM合同管理"),
|
||||
(re.compile(r"/crm/invoice-info", re.I), "CRM开票信息"),
|
||||
(re.compile(r"/crm/", re.I), "CRM管理"),
|
||||
(re.compile(r"/web/admin/", re.I), "系统管理"),
|
||||
(re.compile(r"/web/auth/", re.I), "认证授权"),
|
||||
(re.compile(r"/web/monitor/", re.I), "系统监控"),
|
||||
]
|
||||
|
||||
|
||||
def title_from_path(path: str) -> str:
|
||||
for pattern, title in _MODULE_TITLE_RULES:
|
||||
if pattern.search(path or ""):
|
||||
return title
|
||||
parts = [p for p in (path or "").split("/") if p]
|
||||
return parts[-1] if parts else "操作日志"
|
||||
|
||||
|
||||
def business_type_from_path(path: str, http_method: str) -> int:
|
||||
parts = [p for p in (path or "").split("/") if p]
|
||||
if parts:
|
||||
last = parts[-1].lower()
|
||||
if last in _ACTION_TYPE_MAP:
|
||||
return _ACTION_TYPE_MAP[last]
|
||||
method = (http_method or "").upper()
|
||||
if method == "POST":
|
||||
return 1
|
||||
if method in ("PUT", "PATCH"):
|
||||
return 2
|
||||
if method == "DELETE":
|
||||
return 3
|
||||
return 0
|
||||
@@ -106,6 +106,11 @@ class ApiRequestLogCRUD:
|
||||
role=log.role,
|
||||
ip_address=log.ip_address,
|
||||
user_agent=log.user_agent,
|
||||
tenant_no=log.tenant_no,
|
||||
business_title=log.business_title,
|
||||
business_type=log.business_type,
|
||||
business_id=log.business_id,
|
||||
oper_summary=log.oper_summary,
|
||||
create_at=log.create_at,
|
||||
create_by=log.create_by,
|
||||
create_name=log.create_name,
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.crm_models import CrmClue
|
||||
from app.models.models import SysUser
|
||||
@@ -399,6 +400,7 @@ def soft_delete_clue(
|
||||
_ensure_clue_in_scope(db, obj, user_info, obj.tenant_no)
|
||||
if obj.is_converted == 1:
|
||||
raise ValueError("已转化的线索不可删除")
|
||||
assert_row_deletable_by_creator(obj, user_info, user_id=user_id)
|
||||
obj.del_flag = 1
|
||||
obj.update_by = user_id
|
||||
obj.update_time = datetime.now()
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.clue import get_clue, _ensure_clue_in_scope
|
||||
from app.crud.crm.data_scope_helpers import apply_crm_create_by_scope, ensure_crm_create_by_row_in_scope
|
||||
@@ -216,6 +217,7 @@ def delete_clue_follow(
|
||||
ensure_crm_create_by_row_in_scope(
|
||||
db, row, user_info, row.tenant_no, message="无权访问该跟进记录"
|
||||
)
|
||||
assert_row_deletable_by_creator(row, user_info)
|
||||
|
||||
clue_id = row.clue_id
|
||||
row.del_flag = 1
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.data_scope_helpers import (
|
||||
apply_crm_create_by_scope,
|
||||
@@ -335,6 +336,7 @@ def soft_delete_contact(
|
||||
if tenant_no and obj.tenant_no != tenant_no:
|
||||
raise ValueError("联系人不存在")
|
||||
ensure_crm_contact_row_in_scope(db, obj, user_info, obj.tenant_no)
|
||||
assert_row_deletable_by_creator(obj, user_info, user_id=user_id)
|
||||
customer_id = obj.customer_id
|
||||
obj.del_flag = 1
|
||||
obj.update_by = user_id
|
||||
|
||||
@@ -12,6 +12,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Query, Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.data_scope_helpers import apply_crm_owner_scope, ensure_crm_owner_row_in_scope, apply_crm_create_by_scope, ensure_crm_create_by_row_in_scope
|
||||
from app.crud.crm.contact import get_contact
|
||||
@@ -1064,6 +1065,7 @@ def soft_delete_contracts(
|
||||
continue
|
||||
if validate and entity.status != "draft":
|
||||
raise ValueError("只能删除草稿状态的合同")
|
||||
assert_row_deletable_by_creator(entity, user_info)
|
||||
entity.del_flag = 1
|
||||
entity.update_time = datetime.now()
|
||||
count += 1
|
||||
@@ -2287,6 +2289,7 @@ def soft_delete_contract_changes(
|
||||
)
|
||||
if validate and entity.approval_status != "pending":
|
||||
raise ValueError("只能删除待审批状态的变更记录")
|
||||
assert_row_deletable_by_creator(entity, user_info)
|
||||
entity.del_flag = 1
|
||||
entity.update_time = datetime.now()
|
||||
count += 1
|
||||
@@ -2491,6 +2494,7 @@ def soft_delete_contract_templates(
|
||||
)
|
||||
if validate and (entity.usage_count or 0) > 0:
|
||||
raise ValueError(f"模板【{entity.template_name}】已被使用,不能删除")
|
||||
assert_row_deletable_by_creator(entity, user_info)
|
||||
entity.del_flag = 1
|
||||
entity.update_time = datetime.now()
|
||||
count += 1
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.crm_models import CrmCustomer
|
||||
from app.models.models import SysUser
|
||||
@@ -501,6 +502,7 @@ def soft_delete_customer(
|
||||
if tenant_no and obj.tenant_no != tenant_no:
|
||||
raise ValueError("客户不存在")
|
||||
_ensure_customer_in_scope(db, obj, user_info, obj.tenant_no)
|
||||
assert_row_deletable_by_creator(obj, user_info, user_id=user_id)
|
||||
obj.del_flag = 1
|
||||
obj.update_by = user_id
|
||||
obj.update_time = datetime.now()
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.customer import _resolve_user_display_name, get_customer, _ensure_customer_in_scope
|
||||
from app.models.crm_models import CrmCustomerFollow
|
||||
@@ -240,6 +241,7 @@ def soft_delete_follow(
|
||||
if tenant_no and obj.tenant_no != tenant_no:
|
||||
raise ValueError("跟进记录不存在")
|
||||
ensure_crm_create_by_row_in_scope(db, obj, user_info, obj.tenant_no, message="无权访问该跟进记录")
|
||||
assert_row_deletable_by_creator(obj, user_info, user_id=user_id)
|
||||
obj.del_flag = 1
|
||||
obj.update_by = user_id
|
||||
obj.update_time = datetime.now()
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.data_scope_helpers import (
|
||||
ensure_crm_customer_linked_row_in_scope,
|
||||
@@ -246,6 +247,7 @@ def soft_delete_invoice_info(
|
||||
ensure_crm_customer_linked_row_in_scope(
|
||||
db, obj, user_info, obj.tenant_no, message="无权访问该开票信息"
|
||||
)
|
||||
assert_row_deletable_by_creator(obj, user_info, user_id=user_id)
|
||||
obj.del_flag = 1
|
||||
obj.update_by = user_id
|
||||
obj.update_time = datetime.now()
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import case, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.crm_models import (
|
||||
CrmContact,
|
||||
@@ -663,6 +664,7 @@ def soft_delete_opportunities(
|
||||
for row in rows:
|
||||
_ensure_opportunity_in_scope(db, row, user_info, row.tenant_no)
|
||||
_guard_terminal_status(row, "删除")
|
||||
assert_row_deletable_by_creator(row, user_info, user_id=user_id)
|
||||
row.del_flag = 1
|
||||
row.update_by = user_id
|
||||
row.update_time = now
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.data_scope_helpers import apply_crm_create_by_scope, ensure_crm_create_by_row_in_scope
|
||||
from app.crud.crm.opportunity import _ensure_opportunity_in_scope
|
||||
@@ -300,6 +301,7 @@ def soft_delete_opportunity_follow(
|
||||
ensure_crm_create_by_row_in_scope(
|
||||
db, row, user_info, row.tenant_no, message="无权访问该跟进记录"
|
||||
)
|
||||
assert_row_deletable_by_creator(row, user_info, user_id=user_id)
|
||||
row.del_flag = 1
|
||||
row.update_by = user_id
|
||||
row.update_time = datetime.now()
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.opportunity import _ensure_opportunity_in_scope
|
||||
from app.models.crm_models import CrmOpportunity, CrmOpportunityProduct, CrmProduct
|
||||
@@ -316,6 +317,7 @@ def soft_delete_opportunity_products(
|
||||
_get_opportunity_for_product(db, opp_id, tenant_no, user_info)
|
||||
now = datetime.now()
|
||||
for row in rows:
|
||||
assert_row_deletable_by_creator(row, user_info, user_id=user_id)
|
||||
row.del_flag = 1
|
||||
row.update_by = user_id
|
||||
row.update_time = now
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.crm_models import CrmCustomer, CrmCustomerPool, CrmCustomerPoolRule
|
||||
from app.crud.crm.data_scope_helpers import (
|
||||
@@ -172,6 +173,7 @@ def delete_pool_rules(
|
||||
ensure_crm_create_by_row_in_scope(db, row, user_info, tenant_no, message="无权访问该公海规则")
|
||||
if row.status == "enable":
|
||||
raise ValueError(f"公海池规则【{row.pool_name}】正在启用中,请先禁用后再删除")
|
||||
assert_row_deletable_by_creator(row, user_info)
|
||||
row.del_flag = 1
|
||||
row.update_time = datetime.now()
|
||||
count += 1
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.data_scope_helpers import (
|
||||
apply_crm_create_by_scope,
|
||||
@@ -256,6 +257,7 @@ def soft_delete_category(db: Session, category_id: str, *, user_info=None) -> No
|
||||
if has_products:
|
||||
raise ValueError("该分类下存在子分类/产品,无法删除")
|
||||
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
obj.del_flag = 1
|
||||
db.commit()
|
||||
|
||||
@@ -566,6 +568,7 @@ def soft_delete_product(db: Session, product_id: str, *, user_info=None) -> None
|
||||
ensure_crm_create_by_row_in_scope(
|
||||
db, obj, user_info, obj.tenant_no, message="无权访问该产品"
|
||||
)
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
obj.del_flag = 1
|
||||
db.commit()
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import exists, func, or_
|
||||
from sqlalchemy.orm import Query, Session, aliased
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.contact import get_contact
|
||||
from app.crud.crm.customer import get_customer
|
||||
@@ -806,6 +807,7 @@ def soft_delete_quotes(
|
||||
raise ValueError("只能删除草稿状态的报价单")
|
||||
if entity.contract_id:
|
||||
raise ValueError("已转合同的报价单不能删除")
|
||||
assert_row_deletable_by_creator(entity, user_info)
|
||||
entity.del_flag = 1
|
||||
entity.update_time = datetime.now()
|
||||
count += 1
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.product import get_product
|
||||
from app.models.crm_models import CrmQuoteProduct
|
||||
@@ -253,6 +254,7 @@ def soft_delete_quote_products(
|
||||
)
|
||||
if sibling_count <= 1:
|
||||
raise ValueError("报价单至少保留1个产品")
|
||||
assert_row_deletable_by_creator(entity, user_info)
|
||||
entity.del_flag = 1
|
||||
entity.update_time = datetime.now()
|
||||
count += 1
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.crm.data_scope_helpers import (
|
||||
apply_crm_create_by_scope,
|
||||
@@ -223,6 +224,7 @@ def soft_delete_supplier_info(
|
||||
ensure_crm_create_by_row_in_scope(
|
||||
db, obj, user_info, obj.tenant_no, message="无权访问该供方信息"
|
||||
)
|
||||
assert_row_deletable_by_creator(obj, user_info, user_id=user_id)
|
||||
obj.del_flag = 1
|
||||
obj.update_by = user_id
|
||||
obj.update_time = datetime.now()
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional
|
||||
from sqlalchemy import and_, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysDept
|
||||
|
||||
@@ -22,6 +23,7 @@ def _row_to_dict(d: SysDept) -> Dict[str, Any]:
|
||||
"tenantNo": d.tenant_no,
|
||||
"orderNum": d.order_num if d.order_num is not None else 0,
|
||||
"remark": d.remark or "",
|
||||
"createBy": d.create_by or "",
|
||||
"createTime": d.create_time.strftime("%Y-%m-%d %H:%M:%S") if d.create_time else "",
|
||||
"updateTime": d.update_time.strftime("%Y-%m-%d %H:%M:%S") if d.update_time else "",
|
||||
}
|
||||
@@ -194,10 +196,11 @@ def _cascade_soft_delete(db: Session, dept_id: str) -> None:
|
||||
db.add(child)
|
||||
|
||||
|
||||
def soft_delete_dept(db: Session, dept_id: str) -> None:
|
||||
def soft_delete_dept(db: Session, dept_id: str, *, user_info: dict = None) -> None:
|
||||
obj = get_dept(db, dept_id)
|
||||
if not obj:
|
||||
raise ValueError("组织不存在")
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
_cascade_soft_delete(db, dept_id)
|
||||
obj.del_flag = 1
|
||||
obj.update_time = datetime.now()
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysDictData
|
||||
|
||||
@@ -22,6 +23,7 @@ def _row_to_dict(d: SysDictData) -> Dict[str, Any]:
|
||||
"tenantNo": d.tenant_no,
|
||||
"orderNum": d.order_num if d.order_num is not None else 0,
|
||||
"remark": d.remark or "",
|
||||
"createBy": d.create_by or "",
|
||||
"createTime": d.create_time.strftime("%Y-%m-%d %H:%M:%S") if d.create_time else "",
|
||||
"updateTime": d.update_time.strftime("%Y-%m-%d %H:%M:%S") if d.update_time else "",
|
||||
}
|
||||
@@ -139,10 +141,11 @@ def update_dict_data(
|
||||
return obj
|
||||
|
||||
|
||||
def soft_delete_dict_data(db: Session, data_id: str) -> None:
|
||||
def soft_delete_dict_data(db: Session, data_id: str, *, user_info: dict = None) -> None:
|
||||
obj = get_dict_data(db, data_id)
|
||||
if not obj:
|
||||
raise ValueError("字典数据不存在")
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
obj.del_flag = 1
|
||||
obj.update_time = datetime.now()
|
||||
db.add(obj)
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysDictType, SysDictData
|
||||
|
||||
@@ -20,6 +21,7 @@ def _row_to_dict(t: SysDictType) -> Dict[str, Any]:
|
||||
"tenantNo": t.tenant_no,
|
||||
"orderNum": t.order_num if t.order_num is not None else 0,
|
||||
"remark": t.remark or "",
|
||||
"createBy": t.create_by or "",
|
||||
"createTime": t.create_time.strftime("%Y-%m-%d %H:%M:%S") if t.create_time else "",
|
||||
"updateTime": t.update_time.strftime("%Y-%m-%d %H:%M:%S") if t.update_time else "",
|
||||
}
|
||||
@@ -150,10 +152,11 @@ def update_dict_type(
|
||||
return obj
|
||||
|
||||
|
||||
def soft_delete_dict_type(db: Session, dict_type_id: str) -> None:
|
||||
def soft_delete_dict_type(db: Session, dict_type_id: str, *, user_info: dict = None) -> None:
|
||||
obj = get_dict_type(db, dict_type_id)
|
||||
if not obj:
|
||||
raise ValueError("字典类型不存在")
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
obj.del_flag = 1
|
||||
obj.update_time = datetime.now()
|
||||
db.add(obj)
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import and_, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import BizLeaveRequest
|
||||
|
||||
@@ -138,10 +139,11 @@ def update_leave_status(db: Session, leave_id: str, status: str, wf_instance_id:
|
||||
db.flush()
|
||||
|
||||
|
||||
def soft_delete_leave(db: Session, leave_id: str, user_id: str = "") -> None:
|
||||
def soft_delete_leave(db: Session, leave_id: str, user_id: str = "", *, user_info: dict = None) -> None:
|
||||
row = db.query(BizLeaveRequest).filter(BizLeaveRequest.id == leave_id, BizLeaveRequest.del_flag == 0).first()
|
||||
if not row:
|
||||
return
|
||||
assert_row_deletable_by_creator(row, user_info)
|
||||
row.del_flag = 1
|
||||
row.update_by = user_id
|
||||
row.update_time = datetime.now()
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysMenu
|
||||
|
||||
@@ -25,6 +26,7 @@ def _row_to_dict(m: SysMenu) -> Dict[str, Any]:
|
||||
"tenantNo": m.tenant_no,
|
||||
"orderNum": m.order_num if m.order_num is not None else 0,
|
||||
"remark": m.remark or "",
|
||||
"createBy": m.create_by or "",
|
||||
"createTime": m.create_time.strftime("%Y-%m-%d %H:%M:%S") if m.create_time else "",
|
||||
"updateTime": m.update_time.strftime("%Y-%m-%d %H:%M:%S") if m.update_time else "",
|
||||
}
|
||||
@@ -230,10 +232,11 @@ def _cascade_soft_delete(db: Session, menu_id: str) -> None:
|
||||
db.add(child)
|
||||
|
||||
|
||||
def soft_delete_menu(db: Session, menu_id: str) -> None:
|
||||
def soft_delete_menu(db: Session, menu_id: str, *, user_info: dict = None) -> None:
|
||||
obj = get_menu(db, menu_id)
|
||||
if not obj:
|
||||
raise ValueError("菜单不存在")
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
_cascade_soft_delete(db, menu_id)
|
||||
obj.del_flag = 1
|
||||
obj.update_time = datetime.now()
|
||||
|
||||
@@ -1,72 +1,17 @@
|
||||
"""操作日志:基于 sys_api_request_log 适配 RuoYi OperLog 视图"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy import desc, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.oper_log_infer import business_type_from_path, title_from_path
|
||||
from app.models.models import SysApiRequestLog, SysUser
|
||||
|
||||
_FMT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
# path 末段 -> sys_oper_type 字典值
|
||||
_ACTION_TYPE_MAP = {
|
||||
"add": 1,
|
||||
"create": 1,
|
||||
"insert": 1,
|
||||
"update": 2,
|
||||
"edit": 2,
|
||||
"modify": 2,
|
||||
"delete": 3,
|
||||
"remove": 3,
|
||||
"export": 5,
|
||||
"import": 6,
|
||||
"transfer": 2,
|
||||
"intopool": 2,
|
||||
"deal": 2,
|
||||
"claim": 2,
|
||||
"release": 2,
|
||||
}
|
||||
|
||||
# path 片段 -> 模块标题
|
||||
_MODULE_TITLE_RULES: List[Tuple[re.Pattern[str], str]] = [
|
||||
(re.compile(r"/crm/customer", re.I), "CRM客户管理"),
|
||||
(re.compile(r"/crm/contact", re.I), "CRM联系人"),
|
||||
(re.compile(r"/crm/opportunity", re.I), "CRM商机管理"),
|
||||
(re.compile(r"/crm/contract", re.I), "CRM合同管理"),
|
||||
(re.compile(r"/crm/invoice-info", re.I), "CRM开票信息"),
|
||||
(re.compile(r"/crm/", re.I), "CRM管理"),
|
||||
(re.compile(r"/web/admin/", re.I), "系统管理"),
|
||||
(re.compile(r"/web/auth/", re.I), "认证授权"),
|
||||
]
|
||||
|
||||
|
||||
def _title_from_path(path: str) -> str:
|
||||
for pattern, title in _MODULE_TITLE_RULES:
|
||||
if pattern.search(path or ""):
|
||||
return title
|
||||
parts = [p for p in (path or "").split("/") if p]
|
||||
return parts[-1] if parts else "操作日志"
|
||||
|
||||
|
||||
def _business_type_from_path(path: str, http_method: str) -> int:
|
||||
parts = [p for p in (path or "").split("/") if p]
|
||||
if parts:
|
||||
last = parts[-1].lower()
|
||||
if last in _ACTION_TYPE_MAP:
|
||||
return _ACTION_TYPE_MAP[last]
|
||||
method = (http_method or "").upper()
|
||||
if method == "POST":
|
||||
return 1
|
||||
if method in ("PUT", "PATCH"):
|
||||
return 2
|
||||
if method == "DELETE":
|
||||
return 3
|
||||
return 0
|
||||
|
||||
|
||||
def _oper_status(status_code: int) -> int:
|
||||
return 0 if 200 <= int(status_code or 0) < 400 else 1
|
||||
@@ -74,6 +19,7 @@ def _oper_status(status_code: int) -> int:
|
||||
|
||||
def _row_to_oper_log(row: Any) -> Dict[str, Any]:
|
||||
path = getattr(row, "path", "") or ""
|
||||
http_method = getattr(row, "http_method", "") or ""
|
||||
status_code = int(getattr(row, "status_code", 0) or 0)
|
||||
create_at = getattr(row, "create_at", None)
|
||||
oper_time = create_at.strftime(_FMT) if isinstance(create_at, datetime) else ""
|
||||
@@ -81,11 +27,19 @@ def _row_to_oper_log(row: Any) -> Dict[str, Any]:
|
||||
request_body = getattr(row, "request_body", None) or ""
|
||||
response_body = getattr(row, "response_body", None) or ""
|
||||
|
||||
stored_title = getattr(row, "business_title", None)
|
||||
stored_type = getattr(row, "business_type", None)
|
||||
title = stored_title or title_from_path(path)
|
||||
business_type = stored_type if stored_type is not None else business_type_from_path(path, http_method)
|
||||
oper_summary = getattr(row, "oper_summary", None) or ""
|
||||
|
||||
return {
|
||||
"id": getattr(row, "id", ""),
|
||||
"title": _title_from_path(path),
|
||||
"businessType": _business_type_from_path(path, getattr(row, "http_method", "")),
|
||||
"method": getattr(row, "http_method", "") or "",
|
||||
"title": title,
|
||||
"businessType": business_type,
|
||||
"businessId": getattr(row, "business_id", None) or "",
|
||||
"operSummary": oper_summary,
|
||||
"method": http_method,
|
||||
"operName": oper_name,
|
||||
"operUrl": path,
|
||||
"operIp": getattr(row, "ip_address", "") or "",
|
||||
@@ -164,12 +118,33 @@ def list_oper_logs(
|
||||
business_type: Optional[int] = None,
|
||||
oper_name: Optional[str] = None,
|
||||
status: Optional[int] = None,
|
||||
business_id: Optional[str] = None,
|
||||
business_title: Optional[str] = None,
|
||||
user_info: Optional[dict] = None,
|
||||
tenant_no: Optional[str] = None,
|
||||
) -> Tuple[List[Dict[str, Any]], int]:
|
||||
q = db.query(SysApiRequestLog).filter(SysApiRequestLog.is_del == 0)
|
||||
|
||||
effective_tenant = tenant_no or (user_info or {}).get("tenant_no")
|
||||
if effective_tenant:
|
||||
q = q.filter(SysApiRequestLog.tenant_no == effective_tenant)
|
||||
|
||||
if user_info:
|
||||
q = _apply_oper_log_scope(q, db, user_info, tenant_no or user_info.get("tenant_no"))
|
||||
q = _apply_oper_log_scope(q, db, user_info, effective_tenant or "")
|
||||
|
||||
if business_id:
|
||||
q = q.filter(SysApiRequestLog.business_id == business_id.strip())
|
||||
|
||||
if business_title:
|
||||
kw = f"%{business_title.strip()}%"
|
||||
q = q.filter(SysApiRequestLog.business_title.like(kw))
|
||||
|
||||
if title:
|
||||
title_kw = f"%{title.strip()}%"
|
||||
q = q.filter(SysApiRequestLog.business_title.like(title_kw))
|
||||
|
||||
if business_type is not None:
|
||||
q = q.filter(SysApiRequestLog.business_type == int(business_type))
|
||||
|
||||
if oper_name:
|
||||
kw = f"%{oper_name.strip()}%"
|
||||
@@ -191,21 +166,15 @@ def list_oper_logs(
|
||||
)
|
||||
)
|
||||
|
||||
# title / businessType 为衍生字段,先拉取再在内存过滤(数据量可控)
|
||||
rows = q.order_by(desc(SysApiRequestLog.create_at)).limit(5000).all()
|
||||
items = [_row_to_oper_log(r) for r in rows]
|
||||
|
||||
if title:
|
||||
title_kw = title.strip().lower()
|
||||
items = [i for i in items if title_kw in (i.get("title") or "").lower()]
|
||||
|
||||
if business_type is not None:
|
||||
items = [i for i in items if i.get("businessType") == int(business_type)]
|
||||
|
||||
total = len(items)
|
||||
total = int(q.with_entities(func.count(SysApiRequestLog.id)).order_by(None).scalar() or 0)
|
||||
start = (max(page, 1) - 1) * page_size
|
||||
end = start + page_size
|
||||
return items[start:end], total
|
||||
rows = (
|
||||
q.order_by(desc(SysApiRequestLog.create_at))
|
||||
.offset(start)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return [_row_to_oper_log(r) for r in rows], total
|
||||
|
||||
|
||||
def get_oper_log(
|
||||
@@ -220,4 +189,6 @@ def get_oper_log(
|
||||
return None
|
||||
if user_info and not _oper_log_row_in_scope(db, row, user_info, tenant_no):
|
||||
return None
|
||||
if tenant_no and row.tenant_no and row.tenant_no != tenant_no:
|
||||
return None
|
||||
return _row_to_oper_log(row)
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysPost
|
||||
|
||||
@@ -21,6 +22,7 @@ def _row_to_dict(p: SysPost) -> Dict[str, Any]:
|
||||
"tenantNo": p.tenant_no,
|
||||
"orderNum": p.order_num if p.order_num is not None else 0,
|
||||
"remark": p.remark or "",
|
||||
"createBy": p.create_by or "",
|
||||
"createTime": p.create_time.strftime("%Y-%m-%d %H:%M:%S") if p.create_time else "",
|
||||
"updateTime": p.update_time.strftime("%Y-%m-%d %H:%M:%S") if p.update_time else "",
|
||||
}
|
||||
@@ -150,10 +152,11 @@ def update_post(
|
||||
return obj
|
||||
|
||||
|
||||
def soft_delete_post(db: Session, post_id: str) -> None:
|
||||
def soft_delete_post(db: Session, post_id: str, *, user_info: dict = None) -> None:
|
||||
obj = get_post(db, post_id)
|
||||
if not obj:
|
||||
raise ValueError("职位不存在")
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
obj.del_flag = 1
|
||||
obj.update_time = datetime.now()
|
||||
db.add(obj)
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysRole, SysRoleMenu, SysRoleDataScopeDetail
|
||||
|
||||
@@ -26,6 +27,7 @@ def _row_to_dict(r: SysRole) -> Dict[str, Any]:
|
||||
"tenantNo": r.tenant_no or "",
|
||||
"orderNum": r.order_num if r.order_num is not None else 0,
|
||||
"remark": r.remark or "",
|
||||
"createBy": r.create_by or "",
|
||||
"createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "",
|
||||
"updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "",
|
||||
}
|
||||
@@ -173,10 +175,11 @@ def update_role(
|
||||
return obj
|
||||
|
||||
|
||||
def soft_delete_role(db: Session, role_id: str) -> None:
|
||||
def soft_delete_role(db: Session, role_id: str, *, user_info: dict = None) -> None:
|
||||
obj = get_role(db, role_id)
|
||||
if not obj:
|
||||
raise ValueError("角色不存在")
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
obj.del_flag = 1
|
||||
obj.update_time = datetime.now()
|
||||
db.add(obj)
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysSite
|
||||
|
||||
@@ -26,6 +27,7 @@ def _row_to_dict(s: SysSite) -> Dict[str, Any]:
|
||||
"tenantNo": s.tenant_no,
|
||||
"orderNum": s.order_num if s.order_num is not None else 0,
|
||||
"remark": s.remark or "",
|
||||
"createBy": s.create_by or "",
|
||||
"createTime": s.create_time.strftime("%Y-%m-%d %H:%M:%S") if s.create_time else "",
|
||||
"updateTime": s.update_time.strftime("%Y-%m-%d %H:%M:%S") if s.update_time else "",
|
||||
}
|
||||
@@ -180,10 +182,11 @@ def update_site(
|
||||
return obj
|
||||
|
||||
|
||||
def soft_delete_site(db: Session, site_id: str) -> None:
|
||||
def soft_delete_site(db: Session, site_id: str, *, user_info: dict = None) -> None:
|
||||
obj = get_site(db, site_id)
|
||||
if not obj:
|
||||
raise ValueError("站点不存在")
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
obj.del_flag = 1
|
||||
obj.update_time = datetime.now()
|
||||
db.add(obj)
|
||||
|
||||
@@ -9,6 +9,7 @@ from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysTenant
|
||||
|
||||
@@ -22,6 +23,7 @@ def _row_to_dict(t: SysTenant) -> Dict[str, Any]:
|
||||
"contactPhone": t.contact_phone or "",
|
||||
"orderNum": t.order_num if t.order_num is not None else 0,
|
||||
"remark": t.remark or "",
|
||||
"createBy": t.create_by or "",
|
||||
"createTime": t.create_time.strftime("%Y-%m-%d %H:%M:%S") if t.create_time else "",
|
||||
"updateTime": t.update_time.strftime("%Y-%m-%d %H:%M:%S") if t.update_time else "",
|
||||
}
|
||||
@@ -153,12 +155,13 @@ def update_tenant(
|
||||
return obj
|
||||
|
||||
|
||||
def soft_delete_tenant(db: Session, tenant_id: str) -> None:
|
||||
def soft_delete_tenant(db: Session, tenant_id: str, *, user_info: dict = None) -> None:
|
||||
obj = get_tenant(db, tenant_id)
|
||||
if not obj:
|
||||
raise ValueError("租户不存在")
|
||||
if obj.tenant_no == settings.DEFAULT_TENANT_NO:
|
||||
raise ValueError("默认租户不可删除")
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
obj.del_flag = 1
|
||||
obj.update_time = datetime.now()
|
||||
db.add(obj)
|
||||
|
||||
@@ -10,6 +10,7 @@ from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.crud.base import CRUDBase
|
||||
from app.models.models import SysUser, SysUserRole, SysUserPost, SysDept
|
||||
@@ -64,6 +65,7 @@ def _row_to_dict(u: SysUser, dept_name: str = "") -> Dict[str, Any]:
|
||||
"tenantNo": u.tenant_no,
|
||||
"orderNum": u.order_num if u.order_num is not None else 0,
|
||||
"remark": u.remark or "",
|
||||
"createBy": u.create_by or "",
|
||||
"createTime": u.create_time.strftime("%Y-%m-%d %H:%M:%S") if u.create_time else "",
|
||||
"updateTime": u.update_time.strftime("%Y-%m-%d %H:%M:%S") if u.update_time else "",
|
||||
"deptName": dept_name,
|
||||
@@ -413,10 +415,11 @@ def update_user(
|
||||
return obj
|
||||
|
||||
|
||||
def soft_delete_user(db: Session, user_id: str) -> None:
|
||||
def soft_delete_user(db: Session, user_id: str, *, user_info: dict = None) -> None:
|
||||
obj = get_user(db, user_id)
|
||||
if not obj:
|
||||
raise ValueError("用户不存在")
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
obj.del_flag = 1
|
||||
obj.update_time = datetime.now()
|
||||
db.add(obj)
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import (
|
||||
SysUser,
|
||||
@@ -35,6 +36,7 @@ def _row_to_dict(t: WfProcessDef) -> Dict[str, Any]:
|
||||
"tenantNo": t.tenant_no,
|
||||
"orderNum": t.order_num if t.order_num is not None else 0,
|
||||
"remark": t.remark or "",
|
||||
"createBy": t.create_by or "",
|
||||
"createTime": t.create_time.strftime("%Y-%m-%d %H:%M:%S") if t.create_time else "",
|
||||
"updateTime": t.update_time.strftime("%Y-%m-%d %H:%M:%S") if t.update_time else "",
|
||||
}
|
||||
@@ -191,10 +193,11 @@ def update_process_def(
|
||||
return obj
|
||||
|
||||
|
||||
def soft_delete_process_def(db: Session, def_id: str) -> None:
|
||||
def soft_delete_process_def(db: Session, def_id: str, *, user_info: dict = None) -> None:
|
||||
obj = get_process_def(db, def_id)
|
||||
if not obj:
|
||||
raise ValueError("流程定义不存在")
|
||||
assert_row_deletable_by_creator(obj, user_info)
|
||||
obj.del_flag = 1
|
||||
obj.update_time = datetime.now()
|
||||
db.add(obj)
|
||||
|
||||
@@ -369,6 +369,11 @@ class SysApiRequestLog(Base):
|
||||
role = Column(String(64), nullable=True, comment="角色")
|
||||
ip_address = Column(String(50), nullable=True, comment="IP地址")
|
||||
user_agent = Column(String(500), nullable=True, comment="User-Agent")
|
||||
tenant_no = Column(String(64), nullable=True, comment="租户编号")
|
||||
business_title = Column(String(100), nullable=True, comment="操作模块")
|
||||
business_type = Column(Integer, nullable=True, comment="操作类型")
|
||||
business_id = Column(String(64), nullable=True, comment="业务主键")
|
||||
oper_summary = Column(String(500), nullable=True, comment="操作摘要")
|
||||
is_del = Column(Integer, default=0, comment="是否删除")
|
||||
create_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
create_by = Column(String(32), nullable=True, comment="创建人ID")
|
||||
@@ -396,6 +401,11 @@ class SysApiRequestLogHis(Base):
|
||||
role = Column(String(64), nullable=True, comment="角色")
|
||||
ip_address = Column(String(50), nullable=True, comment="IP地址")
|
||||
user_agent = Column(String(500), nullable=True, comment="User-Agent")
|
||||
tenant_no = Column(String(64), nullable=True, comment="租户编号")
|
||||
business_title = Column(String(100), nullable=True, comment="操作模块")
|
||||
business_type = Column(Integer, nullable=True, comment="操作类型")
|
||||
business_id = Column(String(64), nullable=True, comment="业务主键")
|
||||
oper_summary = Column(String(500), nullable=True, comment="操作摘要")
|
||||
create_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
||||
create_by = Column(String(32), nullable=True, comment="创建人ID")
|
||||
create_name = Column(String(100), nullable=True, comment="创建人姓名")
|
||||
|
||||
@@ -35,6 +35,14 @@ class PlCarrier(Base):
|
||||
contact_name = Column(String(100), nullable=True, comment="联系人")
|
||||
contact_phone = Column(String(32), nullable=True, comment="联系电话")
|
||||
status = Column(CHAR(1), nullable=False, default="1", comment="合作状态 1-合作中 0-已终止")
|
||||
business_license_urls = Column(String(2000), nullable=True, comment="营业执照附件URL,逗号分隔")
|
||||
road_transport_license_urls = Column(String(2000), nullable=True, comment="道路经营许可证附件URL,逗号分隔")
|
||||
transport_agreement_urls = Column(String(2000), nullable=True, comment="运输协议附件URL,逗号分隔")
|
||||
transport_agreement_expire_date = Column(Date, nullable=True, comment="运输协议有效期")
|
||||
audit_status = Column(String(20), nullable=False, default="pending", comment="审核状态 pending-待审核 approved-已通过 rejected-已驳回")
|
||||
audit_by = Column(String(64), nullable=True, comment="审核人id")
|
||||
audit_time = Column(DateTime, nullable=True, comment="审核时间")
|
||||
audit_remark = Column(String(512), nullable=True, comment="审核备注(驳回原因等)")
|
||||
tenant_no = Column(String(64), nullable=False, default="shanxia2.0", comment="租户编号")
|
||||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||||
remark = Column(String(512), nullable=True, comment="备注")
|
||||
@@ -102,6 +110,7 @@ class PlCert(Base):
|
||||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||||
owner_type = Column(String(20), nullable=False, comment="归属类型 vehicle-车辆 personnel-人员")
|
||||
owner_id = Column(String(64), nullable=False, comment="归属主体ID")
|
||||
vehicle_part = Column(String(20), nullable=True, comment="车辆部位 head-主车 trailer-挂车,人员证书为空")
|
||||
cert_name = Column(String(100), nullable=False, comment="证书名称")
|
||||
cert_no = Column(String(64), nullable=True, comment="证号")
|
||||
expire_date = Column(Date, nullable=True, comment="有效期")
|
||||
@@ -737,3 +746,157 @@ class PlDocumentReminder(Base):
|
||||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除")
|
||||
|
||||
|
||||
class PlVehicleRechargeApply(Base):
|
||||
"""车辆ETC/加油充值申请 pl_vehicle_recharge_apply"""
|
||||
|
||||
__tablename__ = "pl_vehicle_recharge_apply"
|
||||
|
||||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||||
apply_no = Column(String(32), nullable=False, unique=True, comment="申请单号")
|
||||
license_plate = Column(String(20), nullable=False, comment="车牌号")
|
||||
recharge_type = Column(String(20), nullable=False, comment="充值类型 etc/fuel")
|
||||
apply_date = Column(Date, nullable=False, comment="申请日期")
|
||||
odometer_km = Column(DECIMAL(10, 1), nullable=True, comment="期初公里数")
|
||||
attachment_urls = Column(String(2000), nullable=True, comment="附件URL")
|
||||
applicant_id = Column(String(64), nullable=False, comment="申请人ID")
|
||||
status = Column(String(20), nullable=False, default="pending", comment="申请状态")
|
||||
tenant_no = Column(String(64), nullable=False, default="shanxia2.0", comment="租户编号")
|
||||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||||
remark = Column(String(512), nullable=True, comment="备注")
|
||||
create_by = Column(String(64), nullable=True, comment="创建人id")
|
||||
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
|
||||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除")
|
||||
|
||||
|
||||
class PlVehicleMaintenanceApply(Base):
|
||||
"""车辆维保申请 pl_vehicle_maintenance_apply"""
|
||||
|
||||
__tablename__ = "pl_vehicle_maintenance_apply"
|
||||
|
||||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||||
apply_no = Column(String(32), nullable=False, unique=True, comment="申请单号")
|
||||
license_plate = Column(String(20), nullable=False, comment="车牌号")
|
||||
applicant_id = Column(String(64), nullable=False, comment="申请人ID")
|
||||
apply_date = Column(Date, nullable=False, comment="申请时间")
|
||||
maintenance_type = Column(String(64), nullable=False, comment="维保类型")
|
||||
start_date = Column(Date, nullable=False, comment="维保开始时间")
|
||||
expected_pickup_date = Column(Date, nullable=True, comment="预计取车时间")
|
||||
maintenance_content = Column(String(1000), nullable=True, comment="维保内容")
|
||||
shop_address = Column(String(500), nullable=True, comment="维保店铺地址")
|
||||
attachment_urls = Column(String(2000), nullable=True, comment="附件URL")
|
||||
status = Column(String(20), nullable=False, default="pending", comment="申请状态")
|
||||
tenant_no = Column(String(64), nullable=False, default="shanxia2.0", comment="租户编号")
|
||||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||||
remark = Column(String(512), nullable=True, comment="备注")
|
||||
create_by = Column(String(64), nullable=True, comment="创建人id")
|
||||
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
|
||||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除")
|
||||
|
||||
|
||||
class PlVehicleMaintenanceProcess(Base):
|
||||
"""车辆维保处理登记 pl_vehicle_maintenance_process"""
|
||||
|
||||
__tablename__ = "pl_vehicle_maintenance_process"
|
||||
|
||||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||||
process_no = Column(String(32), nullable=False, unique=True, comment="处理单号")
|
||||
apply_id = Column(String(64), nullable=True, unique=True, comment="关联申请单ID")
|
||||
license_plate = Column(String(20), nullable=True, comment="车牌号冗余")
|
||||
amount = Column(DECIMAL(10, 2), nullable=True, comment="金额")
|
||||
handler_id = Column(String(64), nullable=False, comment="处理人ID")
|
||||
process_date = Column(Date, nullable=False, comment="处理时间")
|
||||
attachment_urls = Column(String(2000), nullable=True, comment="附件URL")
|
||||
tenant_no = Column(String(64), nullable=False, default="shanxia2.0", comment="租户编号")
|
||||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||||
remark = Column(String(512), nullable=True, comment="备注")
|
||||
create_by = Column(String(64), nullable=True, comment="创建人id")
|
||||
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
|
||||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除")
|
||||
|
||||
|
||||
class PlVehicleUreaApply(Base):
|
||||
"""车辆尿素申请 pl_vehicle_urea_apply"""
|
||||
|
||||
__tablename__ = "pl_vehicle_urea_apply"
|
||||
|
||||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||||
apply_no = Column(String(32), nullable=False, unique=True, comment="申请单号")
|
||||
license_plate = Column(String(20), nullable=False, comment="车牌号")
|
||||
applicant_id = Column(String(64), nullable=False, comment="申请人ID")
|
||||
apply_date = Column(Date, nullable=False, comment="申请时间")
|
||||
warehouse_in_date = Column(Date, nullable=False, comment="入库时间")
|
||||
unit = Column(String(32), nullable=False, comment="单位")
|
||||
quantity = Column(DECIMAL(10, 2), nullable=False, comment="入库数")
|
||||
apply_amount = Column(DECIMAL(10, 2), nullable=False, comment="申请金额")
|
||||
attachment_urls = Column(String(2000), nullable=True, comment="附件URL")
|
||||
status = Column(String(20), nullable=False, default="pending", comment="申请状态")
|
||||
tenant_no = Column(String(64), nullable=False, default="shanxia2.0", comment="租户编号")
|
||||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||||
remark = Column(String(512), nullable=True, comment="备注")
|
||||
create_by = Column(String(64), nullable=True, comment="创建人id")
|
||||
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
|
||||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除")
|
||||
|
||||
|
||||
class PlVehicleUreaDisburse(Base):
|
||||
"""车辆尿素发放登记 pl_vehicle_urea_disburse"""
|
||||
|
||||
__tablename__ = "pl_vehicle_urea_disburse"
|
||||
|
||||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||||
disburse_no = Column(String(32), nullable=False, unique=True, comment="发放单号")
|
||||
apply_id = Column(String(64), nullable=True, unique=True, comment="关联申请单ID")
|
||||
license_plate = Column(String(20), nullable=True, comment="车牌号冗余")
|
||||
amount = Column(DECIMAL(10, 2), nullable=False, comment="发放金额")
|
||||
handler_id = Column(String(64), nullable=False, comment="处理人ID")
|
||||
disburse_date = Column(Date, nullable=False, comment="发放时间")
|
||||
attachment_urls = Column(String(2000), nullable=True, comment="附件URL")
|
||||
tenant_no = Column(String(64), nullable=False, default="shanxia2.0", comment="租户编号")
|
||||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||||
remark = Column(String(512), nullable=True, comment="备注")
|
||||
create_by = Column(String(64), nullable=True, comment="创建人id")
|
||||
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
|
||||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除")
|
||||
|
||||
|
||||
class PlVehicleRechargeRecord(Base):
|
||||
"""车辆ETC/加油充值发放登记 pl_vehicle_recharge_record"""
|
||||
|
||||
__tablename__ = "pl_vehicle_recharge_record"
|
||||
|
||||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||||
recharge_order_no = Column(String(32), nullable=False, unique=True, comment="充值订单号")
|
||||
recharge_date = Column(Date, nullable=False, comment="充值日期")
|
||||
recharge_type = Column(String(20), nullable=False, comment="充值类型")
|
||||
card_no = Column(String(64), nullable=True, comment="卡号")
|
||||
gas_station = Column(String(200), nullable=True, comment="加油站/渠道")
|
||||
amount = Column(DECIMAL(10, 2), nullable=False, comment="充值金额")
|
||||
attachment_urls = Column(String(2000), nullable=True, comment="附件URL")
|
||||
apply_id = Column(String(64), nullable=True, unique=True, comment="关联申请单ID")
|
||||
license_plate = Column(String(20), nullable=True, comment="车牌号冗余")
|
||||
tenant_no = Column(String(64), nullable=False, default="shanxia2.0", comment="租户编号")
|
||||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||||
remark = Column(String(512), nullable=True, comment="备注")
|
||||
create_by = Column(String(64), nullable=True, comment="创建人id")
|
||||
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
|
||||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记:0-正常 1-已删除")
|
||||
@@ -57,6 +57,7 @@ class AdvanceApplyOut(BaseModel):
|
||||
status: str
|
||||
current_node: Optional[str] = None
|
||||
apply_time: DatetimeToStr = Field(validation_alias="create_time")
|
||||
create_by: Optional[str] = Field(default=None, validation_alias="create_by")
|
||||
remark: Optional[str] = None
|
||||
actual_amount: Optional[float] = None
|
||||
offset_amount: Optional[float] = None
|
||||
|
||||
@@ -57,6 +57,7 @@ class ExpenseItemOut(BaseModel):
|
||||
sort_order: int
|
||||
status: int
|
||||
remark: Optional[str] = None
|
||||
create_by: Optional[str] = None
|
||||
child_count: int = 0
|
||||
children: Optional[List["ExpenseItemOut"]] = None
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ class ReimburseApplyOut(BaseModel):
|
||||
amount: float
|
||||
current_node: Optional[str] = None
|
||||
apply_time: DatetimeToStr = Field(validation_alias="create_time")
|
||||
create_by: Optional[str] = Field(default=None, validation_alias="create_by")
|
||||
status: Optional[str] = None
|
||||
remark: Optional[str] = None
|
||||
invoice_count: int = 0
|
||||
|
||||
@@ -59,4 +59,5 @@ class WorkflowDefOut(BaseModel):
|
||||
biz_type: str
|
||||
wf_name: str
|
||||
status: int
|
||||
create_by: Optional[str] = None
|
||||
nodes: List[WorkflowNodeOut] = []
|
||||
@@ -12,6 +12,8 @@ class OperLogListBody(BaseModel):
|
||||
page_size: int = Field(default=10, alias="pageSize", ge=1, le=500, description="每页条数")
|
||||
title: Optional[str] = Field(default=None, description="操作模块")
|
||||
business_type: Optional[int] = Field(default=None, alias="businessType", description="操作类型")
|
||||
business_id: Optional[str] = Field(default=None, alias="businessId", description="业务主键")
|
||||
business_title: Optional[str] = Field(default=None, alias="businessTitle", description="操作模块模糊")
|
||||
oper_name: Optional[str] = Field(default=None, alias="operName", description="操作人员")
|
||||
status: Optional[int] = Field(default=None, description="操作状态 0成功 1失败")
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""运输计划管理 - Pydantic Schema"""
|
||||
from app.schemas.pl.base_data import (
|
||||
CarrierBase,
|
||||
CarrierAuditReject,
|
||||
CarrierCreate,
|
||||
CarrierRead,
|
||||
CarrierUpdate,
|
||||
@@ -23,6 +24,7 @@ from app.schemas.pl.base_data import (
|
||||
VehiclePersonnelRow,
|
||||
VehicleRead,
|
||||
VehicleUpdate,
|
||||
carrier_to_read,
|
||||
)
|
||||
from app.schemas.pl.plan import (
|
||||
PlanBase,
|
||||
@@ -84,6 +86,17 @@ from app.schemas.pl.inventory import (
|
||||
TransportStatsItem,
|
||||
TransportStatsResponse,
|
||||
)
|
||||
from app.schemas.pl.vehicle_recharge import (
|
||||
VehicleRechargeApplyCreate,
|
||||
VehicleRechargeApplyUpdate,
|
||||
VehicleRechargeApplyRead,
|
||||
VehicleRechargeApplyListResponse,
|
||||
VehicleRechargeApplyOption,
|
||||
VehicleRechargeRecordCreate,
|
||||
VehicleRechargeRecordUpdate,
|
||||
VehicleRechargeRecordRead,
|
||||
VehicleRechargeRecordListResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Base data
|
||||
@@ -94,9 +107,11 @@ __all__ = [
|
||||
"VehiclePersonnelRow",
|
||||
"CertRow",
|
||||
"CertRead",
|
||||
"CarrierAuditReject",
|
||||
"CarrierCreate",
|
||||
"CarrierUpdate",
|
||||
"CarrierRead",
|
||||
"carrier_to_read",
|
||||
"VehicleCreate",
|
||||
"VehicleUpdate",
|
||||
"VehicleRead",
|
||||
@@ -166,4 +181,14 @@ __all__ = [
|
||||
"TransportStatsQuery",
|
||||
"TransportStatsItem",
|
||||
"TransportStatsResponse",
|
||||
# Vehicle recharge
|
||||
"VehicleRechargeApplyCreate",
|
||||
"VehicleRechargeApplyUpdate",
|
||||
"VehicleRechargeApplyRead",
|
||||
"VehicleRechargeApplyListResponse",
|
||||
"VehicleRechargeApplyOption",
|
||||
"VehicleRechargeRecordCreate",
|
||||
"VehicleRechargeRecordUpdate",
|
||||
"VehicleRechargeRecordRead",
|
||||
"VehicleRechargeRecordListResponse",
|
||||
]
|
||||
@@ -3,7 +3,12 @@ from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
from app.services.pl_carrier_service import (
|
||||
carrier_agreement_expired,
|
||||
carrier_qualification_complete,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -19,9 +24,42 @@ class CarrierBase(BaseModel):
|
||||
remark: Optional[str] = Field(default=None, description="备注")
|
||||
|
||||
|
||||
class CarrierCreate(CarrierBase):
|
||||
class CarrierQualificationBase(BaseModel):
|
||||
business_license_urls: Optional[str] = Field(
|
||||
default=None, max_length=2000, alias="businessLicenseUrls", description="营业执照附件URL"
|
||||
)
|
||||
road_transport_license_urls: Optional[str] = Field(
|
||||
default=None,
|
||||
max_length=2000,
|
||||
alias="roadTransportLicenseUrls",
|
||||
description="道路经营许可证附件URL",
|
||||
)
|
||||
transport_agreement_urls: Optional[str] = Field(
|
||||
default=None, max_length=2000, alias="transportAgreementUrls", description="运输协议附件URL"
|
||||
)
|
||||
transport_agreement_expire_date: Optional[date] = Field(
|
||||
default=None, alias="transportAgreementExpireDate", description="运输协议有效期"
|
||||
)
|
||||
|
||||
|
||||
class CarrierCreate(CarrierBase, CarrierQualificationBase):
|
||||
business_license_urls: str = Field(..., max_length=2000, alias="businessLicenseUrls")
|
||||
road_transport_license_urls: str = Field(..., max_length=2000, alias="roadTransportLicenseUrls")
|
||||
transport_agreement_urls: str = Field(..., max_length=2000, alias="transportAgreementUrls")
|
||||
transport_agreement_expire_date: date = Field(..., alias="transportAgreementExpireDate")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_qualification(self) -> "CarrierCreate":
|
||||
if not self.business_license_urls or not str(self.business_license_urls).strip():
|
||||
raise ValueError("请上传营业执照")
|
||||
if not self.road_transport_license_urls or not str(self.road_transport_license_urls).strip():
|
||||
raise ValueError("请上传道路经营许可证")
|
||||
if not self.transport_agreement_urls or not str(self.transport_agreement_urls).strip():
|
||||
raise ValueError("请上传运输协议")
|
||||
return self
|
||||
|
||||
|
||||
class CarrierUpdate(BaseModel):
|
||||
carrier_name: Optional[str] = Field(default=None, max_length=200, alias="carrierName")
|
||||
@@ -29,11 +67,61 @@ class CarrierUpdate(BaseModel):
|
||||
contact_phone: Optional[str] = Field(default=None, max_length=32, alias="contactPhone")
|
||||
status: Optional[str] = Field(default=None, max_length=1)
|
||||
remark: Optional[str] = None
|
||||
business_license_urls: Optional[str] = Field(
|
||||
default=None, max_length=2000, alias="businessLicenseUrls"
|
||||
)
|
||||
road_transport_license_urls: Optional[str] = Field(
|
||||
default=None, max_length=2000, alias="roadTransportLicenseUrls"
|
||||
)
|
||||
transport_agreement_urls: Optional[str] = Field(
|
||||
default=None, max_length=2000, alias="transportAgreementUrls"
|
||||
)
|
||||
transport_agreement_expire_date: Optional[date] = Field(
|
||||
default=None, alias="transportAgreementExpireDate"
|
||||
)
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_qualification_patch(self) -> "CarrierUpdate":
|
||||
fields = (
|
||||
self.business_license_urls,
|
||||
self.road_transport_license_urls,
|
||||
self.transport_agreement_urls,
|
||||
self.transport_agreement_expire_date,
|
||||
)
|
||||
if not any(item is not None for item in fields):
|
||||
return self
|
||||
if self.business_license_urls is not None and not str(self.business_license_urls).strip():
|
||||
raise ValueError("请上传营业执照")
|
||||
if (
|
||||
self.road_transport_license_urls is not None
|
||||
and not str(self.road_transport_license_urls).strip()
|
||||
):
|
||||
raise ValueError("请上传道路经营许可证")
|
||||
if self.transport_agreement_urls is not None and not str(self.transport_agreement_urls).strip():
|
||||
raise ValueError("请上传运输协议")
|
||||
if self.transport_agreement_expire_date is None and any(
|
||||
item is not None for item in fields[:3]
|
||||
):
|
||||
raise ValueError("请填写运输协议有效期")
|
||||
return self
|
||||
|
||||
|
||||
class CarrierAuditReject(BaseModel):
|
||||
remark: Optional[str] = Field(default=None, max_length=512, description="驳回原因")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class CarrierRead(CarrierBase):
|
||||
class CarrierRead(CarrierBase, CarrierQualificationBase):
|
||||
id: str
|
||||
audit_status: str = Field(default="pending", alias="auditStatus")
|
||||
audit_by: Optional[str] = Field(default=None, alias="auditBy")
|
||||
audit_time: Optional[datetime] = Field(default=None, alias="auditTime")
|
||||
audit_remark: Optional[str] = Field(default=None, alias="auditRemark")
|
||||
qualification_complete: bool = Field(default=False, alias="qualificationComplete")
|
||||
transport_agreement_expired: bool = Field(default=False, alias="transportAgreementExpired")
|
||||
tenant_no: str = Field(alias="tenantNo")
|
||||
order_num: Optional[int] = Field(default=None, alias="orderNum")
|
||||
create_by: Optional[str] = Field(default=None, alias="createBy")
|
||||
@@ -45,6 +133,37 @@ class CarrierRead(CarrierBase):
|
||||
model_config = {"from_attributes": True, "populate_by_name": True}
|
||||
|
||||
|
||||
def carrier_to_read(carrier, today: Optional[date] = None) -> CarrierRead:
|
||||
complete = carrier_qualification_complete(carrier)
|
||||
expired = carrier_agreement_expired(carrier, today) if complete else False
|
||||
return CarrierRead(
|
||||
id=carrier.id,
|
||||
carrier_name=carrier.carrier_name,
|
||||
contact_name=carrier.contact_name,
|
||||
contact_phone=carrier.contact_phone,
|
||||
status=carrier.status,
|
||||
remark=carrier.remark,
|
||||
business_license_urls=carrier.business_license_urls,
|
||||
road_transport_license_urls=carrier.road_transport_license_urls,
|
||||
transport_agreement_urls=carrier.transport_agreement_urls,
|
||||
transport_agreement_expire_date=carrier.transport_agreement_expire_date,
|
||||
audit_status=carrier.audit_status or "pending",
|
||||
audit_by=carrier.audit_by,
|
||||
audit_time=carrier.audit_time,
|
||||
audit_remark=carrier.audit_remark,
|
||||
qualification_complete=complete,
|
||||
transport_agreement_expired=expired,
|
||||
tenant_no=carrier.tenant_no,
|
||||
order_num=carrier.order_num,
|
||||
create_by=carrier.create_by,
|
||||
create_dept_id=carrier.create_dept_id,
|
||||
create_time=carrier.create_time,
|
||||
update_by=carrier.update_by,
|
||||
update_time=carrier.update_time,
|
||||
del_flag=carrier.del_flag,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 子表行 Schema
|
||||
# =============================================================================
|
||||
@@ -63,6 +182,11 @@ class VehiclePersonnelRow(BaseModel):
|
||||
|
||||
class CertRow(BaseModel):
|
||||
cert_id: Optional[str] = Field(default=None, alias="certId")
|
||||
vehicle_part: Optional[str] = Field(
|
||||
default=None,
|
||||
alias="vehiclePart",
|
||||
description="车辆部位 head-主车 trailer-挂车,人员证书为空",
|
||||
)
|
||||
cert_name: str = Field(..., alias="certName")
|
||||
cert_no: Optional[str] = Field(default=None, alias="certNo")
|
||||
expire_date: Optional[date] = Field(default=None, alias="expireDate")
|
||||
|
||||
@@ -206,3 +206,14 @@ class FreightSettlementListQuery(BaseModel):
|
||||
class FreightSettlementListResponse(BaseModel):
|
||||
total: int
|
||||
list: list[FreightSettlementListItem]
|
||||
|
||||
|
||||
class FreightSettlementByTransportRead(BaseModel):
|
||||
id: str
|
||||
settlement_no: str = Field(alias="settlementNo")
|
||||
invoice_amount: Optional[Decimal] = Field(default=None, alias="invoiceAmount")
|
||||
invoice_no: Optional[str] = Field(default=None, alias="invoiceNo")
|
||||
invoice_date: Optional[date] = Field(default=None, alias="invoiceDate")
|
||||
invoice_attachment_urls: Optional[str] = Field(default=None, alias="invoiceAttachmentUrls")
|
||||
settlement_status: str = Field(alias="settlementStatus")
|
||||
model_config = {"populate_by_name": True}
|
||||
@@ -129,7 +129,7 @@ class PlanTransportCreateBlock(BaseModel):
|
||||
transport_qty: Optional[Decimal] = Field(default=None, alias="transportQty", description="运输量")
|
||||
freight_price: Optional[Decimal] = Field(default=None, alias="freightPrice", description="运输单价")
|
||||
freight_amount: Optional[Decimal] = Field(default=None, alias="freightAmount", description="运费金额")
|
||||
has_invoice: int = Field(default=0, alias="hasInvoice", description="是否开票")
|
||||
has_invoice: Optional[int] = Field(default=None, alias="hasInvoice", description="是否开票(已废弃,由运费结算维护)")
|
||||
remark: Optional[str] = None
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class TransportArrange(BaseModel):
|
||||
freight_type: str = Field(default="by_weight", alias="freightType", description="运费计算方式 by_weight/by_trip")
|
||||
freight_price: Optional[Decimal] = Field(default=None, alias="freightPrice", description="运输单价")
|
||||
freight_amount: Optional[Decimal] = Field(default=None, alias="freightAmount", description="运费金额")
|
||||
has_invoice: int = Field(default=0, alias="hasInvoice", description="是否开票")
|
||||
has_invoice: Optional[int] = Field(default=None, alias="hasInvoice", description="是否开票(已废弃,由运费结算维护)")
|
||||
remark: Optional[str] = None
|
||||
generate_dibang_plan: bool = Field(default=False, alias="generateDibangPlan", description="是否生成地磅单")
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
"""车辆维保 Schema"""
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class VehicleMaintenanceApplyCreate(BaseModel):
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
applicant_id: Optional[str] = Field(default=None, alias="applicantId")
|
||||
apply_date: date = Field(..., alias="applyDate")
|
||||
maintenance_type: str = Field(..., alias="maintenanceType")
|
||||
start_date: date = Field(..., alias="startDate")
|
||||
expected_pickup_date: Optional[date] = Field(default=None, alias="expectedPickupDate")
|
||||
maintenance_content: Optional[str] = Field(default=None, alias="maintenanceContent")
|
||||
shop_address: Optional[str] = Field(default=None, alias="shopAddress")
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
remark: Optional[str] = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleMaintenanceApplyUpdate(VehicleMaintenanceApplyCreate):
|
||||
pass
|
||||
|
||||
|
||||
class VehicleMaintenanceApplyRead(BaseModel):
|
||||
id: str
|
||||
apply_no: str = Field(..., alias="applyNo")
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
applicant_id: str = Field(..., alias="applicantId")
|
||||
applicant_name: Optional[str] = Field(default=None, alias="applicantName")
|
||||
apply_date: date = Field(..., alias="applyDate")
|
||||
maintenance_type: str = Field(..., alias="maintenanceType")
|
||||
start_date: date = Field(..., alias="startDate")
|
||||
expected_pickup_date: Optional[date] = Field(default=None, alias="expectedPickupDate")
|
||||
maintenance_content: Optional[str] = Field(default=None, alias="maintenanceContent")
|
||||
shop_address: Optional[str] = Field(default=None, alias="shopAddress")
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
status: str
|
||||
remark: Optional[str] = None
|
||||
create_time: Optional[datetime] = Field(default=None, alias="createTime")
|
||||
update_time: Optional[datetime] = Field(default=None, alias="updateTime")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleMaintenanceApplyListResponse(BaseModel):
|
||||
items: list[VehicleMaintenanceApplyRead]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int = Field(..., alias="pageSize")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleMaintenanceApplyReminder(BaseModel):
|
||||
id: str
|
||||
apply_no: str = Field(..., alias="applyNo")
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
maintenance_type: str = Field(..., alias="maintenanceType")
|
||||
apply_date: date = Field(..., alias="applyDate")
|
||||
start_date: date = Field(..., alias="startDate")
|
||||
expected_pickup_date: Optional[date] = Field(default=None, alias="expectedPickupDate")
|
||||
maintenance_content: Optional[str] = Field(default=None, alias="maintenanceContent")
|
||||
shop_address: Optional[str] = Field(default=None, alias="shopAddress")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleMaintenanceProcessCreate(BaseModel):
|
||||
apply_id: Optional[str] = Field(default=None, alias="applyId")
|
||||
license_plate: Optional[str] = Field(default=None, alias="licensePlate")
|
||||
amount: Optional[Decimal] = None
|
||||
handler_id: Optional[str] = Field(default=None, alias="handlerId")
|
||||
process_date: date = Field(..., alias="processDate")
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
remark: Optional[str] = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
@field_validator("amount")
|
||||
@classmethod
|
||||
def validate_amount(cls, v: Optional[Decimal]) -> Optional[Decimal]:
|
||||
if v is not None and v <= 0:
|
||||
raise ValueError("金额须大于 0")
|
||||
return v
|
||||
|
||||
|
||||
class VehicleMaintenanceProcessUpdate(VehicleMaintenanceProcessCreate):
|
||||
pass
|
||||
|
||||
|
||||
class VehicleMaintenanceProcessRead(BaseModel):
|
||||
id: str
|
||||
process_no: str = Field(..., alias="processNo")
|
||||
apply_id: Optional[str] = Field(default=None, alias="applyId")
|
||||
apply_no: Optional[str] = Field(default=None, alias="applyNo")
|
||||
license_plate: Optional[str] = Field(default=None, alias="licensePlate")
|
||||
amount: Optional[Decimal] = None
|
||||
handler_id: str = Field(..., alias="handlerId")
|
||||
handler_name: Optional[str] = Field(default=None, alias="handlerName")
|
||||
process_date: date = Field(..., alias="processDate")
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
remark: Optional[str] = None
|
||||
create_by: Optional[str] = Field(default=None, alias="createBy")
|
||||
create_by_name: Optional[str] = Field(default=None, alias="createByName")
|
||||
create_time: Optional[datetime] = Field(default=None, alias="createTime")
|
||||
update_time: Optional[datetime] = Field(default=None, alias="updateTime")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleMaintenanceProcessListResponse(BaseModel):
|
||||
items: list[VehicleMaintenanceProcessRead]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int = Field(..., alias="pageSize")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleMaintenanceProcessBoardItem(BaseModel):
|
||||
apply_id: str = Field(..., alias="applyId")
|
||||
apply_no: str = Field(..., alias="applyNo")
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
maintenance_type: str = Field(..., alias="maintenanceType")
|
||||
apply_date: date = Field(..., alias="applyDate")
|
||||
start_date: date = Field(..., alias="startDate")
|
||||
expected_pickup_date: Optional[date] = Field(default=None, alias="expectedPickupDate")
|
||||
maintenance_content: Optional[str] = Field(default=None, alias="maintenanceContent")
|
||||
shop_address: Optional[str] = Field(default=None, alias="shopAddress")
|
||||
applicant_name: Optional[str] = Field(default=None, alias="applicantName")
|
||||
apply_status: str = Field(..., alias="applyStatus")
|
||||
apply_remark: Optional[str] = Field(default=None, alias="applyRemark")
|
||||
process_id: Optional[str] = Field(default=None, alias="processId")
|
||||
process_no: Optional[str] = Field(default=None, alias="processNo")
|
||||
process_date: Optional[date] = Field(default=None, alias="processDate")
|
||||
amount: Optional[Decimal] = None
|
||||
handler_name: Optional[str] = Field(default=None, alias="handlerName")
|
||||
process_remark: Optional[str] = Field(default=None, alias="processRemark")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleMaintenanceProcessBoardListResponse(BaseModel):
|
||||
items: list[VehicleMaintenanceProcessBoardItem]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int = Field(..., alias="pageSize")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
@@ -0,0 +1,162 @@
|
||||
"""车辆 ETC/加油充值 Schema"""
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
RECHARGE_TYPES = {"etc", "fuel"}
|
||||
|
||||
|
||||
class VehicleRechargeApplyCreate(BaseModel):
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
recharge_type: str = Field(..., alias="rechargeType")
|
||||
apply_date: date = Field(..., alias="applyDate")
|
||||
odometer_km: Optional[Decimal] = Field(default=None, alias="odometerKm")
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
applicant_id: Optional[str] = Field(default=None, alias="applicantId")
|
||||
remark: Optional[str] = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
@field_validator("recharge_type")
|
||||
@classmethod
|
||||
def validate_recharge_type(cls, v: str) -> str:
|
||||
if v not in RECHARGE_TYPES:
|
||||
raise ValueError("充值类型无效")
|
||||
return v
|
||||
|
||||
|
||||
class VehicleRechargeApplyUpdate(VehicleRechargeApplyCreate):
|
||||
pass
|
||||
|
||||
|
||||
class VehicleRechargeApplyRead(BaseModel):
|
||||
id: str
|
||||
apply_no: str = Field(..., alias="applyNo")
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
recharge_type: str = Field(..., alias="rechargeType")
|
||||
apply_date: date = Field(..., alias="applyDate")
|
||||
odometer_km: Optional[Decimal] = Field(default=None, alias="odometerKm")
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
applicant_id: str = Field(..., alias="applicantId")
|
||||
applicant_name: Optional[str] = Field(default=None, alias="applicantName")
|
||||
status: str
|
||||
remark: Optional[str] = None
|
||||
create_time: Optional[datetime] = Field(default=None, alias="createTime")
|
||||
update_time: Optional[datetime] = Field(default=None, alias="updateTime")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleRechargeApplyListResponse(BaseModel):
|
||||
items: list[VehicleRechargeApplyRead]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int = Field(..., alias="pageSize")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleRechargeApplyOption(BaseModel):
|
||||
id: str
|
||||
apply_no: str = Field(..., alias="applyNo")
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
recharge_type: str = Field(..., alias="rechargeType")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleRechargeRecordCreate(BaseModel):
|
||||
recharge_date: date = Field(..., alias="rechargeDate")
|
||||
recharge_type: str = Field(..., alias="rechargeType")
|
||||
card_no: Optional[str] = Field(default=None, alias="cardNo")
|
||||
gas_station: Optional[str] = Field(default=None, alias="gasStation")
|
||||
amount: Decimal
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
apply_id: Optional[str] = Field(default=None, alias="applyId")
|
||||
license_plate: Optional[str] = Field(default=None, alias="licensePlate")
|
||||
remark: Optional[str] = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
@field_validator("recharge_type")
|
||||
@classmethod
|
||||
def validate_recharge_type(cls, v: str) -> str:
|
||||
if v not in RECHARGE_TYPES:
|
||||
raise ValueError("充值类型无效")
|
||||
return v
|
||||
|
||||
@field_validator("amount")
|
||||
@classmethod
|
||||
def validate_amount(cls, v: Decimal) -> Decimal:
|
||||
if v is None or v <= 0:
|
||||
raise ValueError("充值金额须大于 0")
|
||||
return v
|
||||
|
||||
|
||||
class VehicleRechargeRecordUpdate(VehicleRechargeRecordCreate):
|
||||
pass
|
||||
|
||||
|
||||
class VehicleRechargeRecordRead(BaseModel):
|
||||
id: str
|
||||
recharge_order_no: str = Field(..., alias="rechargeOrderNo")
|
||||
recharge_date: date = Field(..., alias="rechargeDate")
|
||||
recharge_type: str = Field(..., alias="rechargeType")
|
||||
card_no: Optional[str] = Field(default=None, alias="cardNo")
|
||||
gas_station: Optional[str] = Field(default=None, alias="gasStation")
|
||||
amount: Decimal
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
apply_id: Optional[str] = Field(default=None, alias="applyId")
|
||||
apply_no: Optional[str] = Field(default=None, alias="applyNo")
|
||||
license_plate: Optional[str] = Field(default=None, alias="licensePlate")
|
||||
remark: Optional[str] = None
|
||||
create_by: Optional[str] = Field(default=None, alias="createBy")
|
||||
create_by_name: Optional[str] = Field(default=None, alias="createByName")
|
||||
create_time: Optional[datetime] = Field(default=None, alias="createTime")
|
||||
update_time: Optional[datetime] = Field(default=None, alias="updateTime")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleRechargeRecordListResponse(BaseModel):
|
||||
items: list[VehicleRechargeRecordRead]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int = Field(..., alias="pageSize")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleRechargeDisburseBoardItem(BaseModel):
|
||||
"""发放登记页:申请单 + 关联发放信息(左连接)"""
|
||||
|
||||
apply_id: str = Field(..., alias="applyId")
|
||||
apply_no: str = Field(..., alias="applyNo")
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
recharge_type: str = Field(..., alias="rechargeType")
|
||||
apply_date: date = Field(..., alias="applyDate")
|
||||
odometer_km: Optional[Decimal] = Field(default=None, alias="odometerKm")
|
||||
applicant_name: Optional[str] = Field(default=None, alias="applicantName")
|
||||
apply_status: str = Field(..., alias="applyStatus")
|
||||
apply_remark: Optional[str] = Field(default=None, alias="applyRemark")
|
||||
record_id: Optional[str] = Field(default=None, alias="recordId")
|
||||
recharge_order_no: Optional[str] = Field(default=None, alias="rechargeOrderNo")
|
||||
recharge_date: Optional[date] = Field(default=None, alias="rechargeDate")
|
||||
card_no: Optional[str] = Field(default=None, alias="cardNo")
|
||||
gas_station: Optional[str] = Field(default=None, alias="gasStation")
|
||||
amount: Optional[Decimal] = None
|
||||
record_create_by_name: Optional[str] = Field(default=None, alias="recordCreateByName")
|
||||
record_remark: Optional[str] = Field(default=None, alias="recordRemark")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleRechargeDisburseBoardListResponse(BaseModel):
|
||||
items: list[VehicleRechargeDisburseBoardItem]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int = Field(..., alias="pageSize")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
@@ -0,0 +1,150 @@
|
||||
"""车辆尿素 Schema"""
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class VehicleUreaApplyCreate(BaseModel):
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
applicant_id: Optional[str] = Field(default=None, alias="applicantId")
|
||||
apply_date: date = Field(..., alias="applyDate")
|
||||
warehouse_in_date: date = Field(..., alias="warehouseInDate")
|
||||
unit: str = Field(default="吨桶")
|
||||
quantity: Decimal
|
||||
apply_amount: Decimal = Field(..., alias="applyAmount")
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
remark: Optional[str] = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
@field_validator("quantity")
|
||||
@classmethod
|
||||
def validate_quantity(cls, v: Decimal) -> Decimal:
|
||||
if v is None or v <= 0:
|
||||
raise ValueError("入库数须大于 0")
|
||||
return v
|
||||
|
||||
@field_validator("apply_amount")
|
||||
@classmethod
|
||||
def validate_apply_amount(cls, v: Decimal) -> Decimal:
|
||||
if v is None or v <= 0:
|
||||
raise ValueError("申请金额须大于 0")
|
||||
return v
|
||||
|
||||
|
||||
class VehicleUreaApplyUpdate(VehicleUreaApplyCreate):
|
||||
pass
|
||||
|
||||
|
||||
class VehicleUreaApplyRead(BaseModel):
|
||||
id: str
|
||||
apply_no: str = Field(..., alias="applyNo")
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
applicant_id: str = Field(..., alias="applicantId")
|
||||
applicant_name: Optional[str] = Field(default=None, alias="applicantName")
|
||||
apply_date: date = Field(..., alias="applyDate")
|
||||
warehouse_in_date: date = Field(..., alias="warehouseInDate")
|
||||
unit: str
|
||||
quantity: Decimal
|
||||
apply_amount: Decimal = Field(..., alias="applyAmount")
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
status: str
|
||||
remark: Optional[str] = None
|
||||
create_time: Optional[datetime] = Field(default=None, alias="createTime")
|
||||
update_time: Optional[datetime] = Field(default=None, alias="updateTime")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleUreaApplyListResponse(BaseModel):
|
||||
items: list[VehicleUreaApplyRead]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int = Field(..., alias="pageSize")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleUreaDisburseCreate(BaseModel):
|
||||
apply_id: Optional[str] = Field(default=None, alias="applyId")
|
||||
license_plate: Optional[str] = Field(default=None, alias="licensePlate")
|
||||
amount: Decimal
|
||||
handler_id: Optional[str] = Field(default=None, alias="handlerId")
|
||||
disburse_date: date = Field(..., alias="disburseDate")
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
remark: Optional[str] = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
@field_validator("amount")
|
||||
@classmethod
|
||||
def validate_amount(cls, v: Decimal) -> Decimal:
|
||||
if v is None or v <= 0:
|
||||
raise ValueError("发放金额须大于 0")
|
||||
return v
|
||||
|
||||
|
||||
class VehicleUreaDisburseUpdate(VehicleUreaDisburseCreate):
|
||||
pass
|
||||
|
||||
|
||||
class VehicleUreaDisburseRead(BaseModel):
|
||||
id: str
|
||||
disburse_no: str = Field(..., alias="disburseNo")
|
||||
apply_id: Optional[str] = Field(default=None, alias="applyId")
|
||||
apply_no: Optional[str] = Field(default=None, alias="applyNo")
|
||||
license_plate: Optional[str] = Field(default=None, alias="licensePlate")
|
||||
amount: Decimal
|
||||
handler_id: str = Field(..., alias="handlerId")
|
||||
handler_name: Optional[str] = Field(default=None, alias="handlerName")
|
||||
disburse_date: date = Field(..., alias="disburseDate")
|
||||
attachment_urls: Optional[str] = Field(default=None, alias="attachmentUrls")
|
||||
remark: Optional[str] = None
|
||||
create_by: Optional[str] = Field(default=None, alias="createBy")
|
||||
create_by_name: Optional[str] = Field(default=None, alias="createByName")
|
||||
create_time: Optional[datetime] = Field(default=None, alias="createTime")
|
||||
update_time: Optional[datetime] = Field(default=None, alias="updateTime")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleUreaDisburseListResponse(BaseModel):
|
||||
items: list[VehicleUreaDisburseRead]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int = Field(..., alias="pageSize")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleUreaDisburseBoardItem(BaseModel):
|
||||
apply_id: str = Field(..., alias="applyId")
|
||||
apply_no: str = Field(..., alias="applyNo")
|
||||
license_plate: str = Field(..., alias="licensePlate")
|
||||
apply_date: date = Field(..., alias="applyDate")
|
||||
warehouse_in_date: date = Field(..., alias="warehouseInDate")
|
||||
unit: str
|
||||
quantity: Decimal
|
||||
apply_amount: Decimal = Field(..., alias="applyAmount")
|
||||
applicant_name: Optional[str] = Field(default=None, alias="applicantName")
|
||||
apply_status: str = Field(..., alias="applyStatus")
|
||||
apply_remark: Optional[str] = Field(default=None, alias="applyRemark")
|
||||
disburse_id: Optional[str] = Field(default=None, alias="disburseId")
|
||||
disburse_no: Optional[str] = Field(default=None, alias="disburseNo")
|
||||
disburse_date: Optional[date] = Field(default=None, alias="disburseDate")
|
||||
amount: Optional[Decimal] = None
|
||||
handler_name: Optional[str] = Field(default=None, alias="handlerName")
|
||||
disburse_remark: Optional[str] = Field(default=None, alias="disburseRemark")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class VehicleUreaDisburseBoardListResponse(BaseModel):
|
||||
items: list[VehicleUreaDisburseBoardItem]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int = Field(..., alias="pageSize")
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any, List, Optional, Sequence, Tuple, Type, Ty
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.utils import generate_uuid
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator, user_info_from_context
|
||||
from app.services.bx_data_scope import apply_bx_scope, assert_bx_row_in_scope
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -119,6 +120,10 @@ class BxCrudService:
|
||||
)
|
||||
if not obj:
|
||||
return False
|
||||
if user:
|
||||
assert_row_deletable_by_creator(obj, user_info_from_context(user))
|
||||
elif user_id:
|
||||
assert_row_deletable_by_creator(obj, {"user_id": user_id})
|
||||
obj.del_flag = 1
|
||||
if hasattr(obj, "update_by") and user_id:
|
||||
obj.update_by = user_id
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
"""运输计划 - 承运商资质与运输资格"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.models.pl_models import PlCarrier
|
||||
|
||||
|
||||
def _urls_nonempty(value: Optional[str]) -> bool:
|
||||
return bool(value and str(value).strip())
|
||||
|
||||
|
||||
def carrier_qualification_complete(carrier: PlCarrier) -> bool:
|
||||
return (
|
||||
_urls_nonempty(carrier.business_license_urls)
|
||||
and _urls_nonempty(carrier.road_transport_license_urls)
|
||||
and _urls_nonempty(carrier.transport_agreement_urls)
|
||||
and carrier.transport_agreement_expire_date is not None
|
||||
)
|
||||
|
||||
|
||||
def carrier_agreement_expired(carrier: PlCarrier, today: Optional[date] = None) -> bool:
|
||||
if not carrier_qualification_complete(carrier):
|
||||
return False
|
||||
ref = today or date.today()
|
||||
expire = carrier.transport_agreement_expire_date
|
||||
return expire is not None and expire < ref
|
||||
|
||||
|
||||
def carrier_audit_approved(carrier: PlCarrier) -> bool:
|
||||
return getattr(carrier, "audit_status", None) == "approved"
|
||||
|
||||
|
||||
def carrier_eligible_for_transport(carrier: PlCarrier, today: Optional[date] = None) -> bool:
|
||||
if carrier.status != "1":
|
||||
return False
|
||||
if not carrier_audit_approved(carrier):
|
||||
return False
|
||||
if not carrier_qualification_complete(carrier):
|
||||
return False
|
||||
if carrier_agreement_expired(carrier, today):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def assert_carrier_eligible_for_transport(
|
||||
carrier: PlCarrier,
|
||||
*,
|
||||
rearrange: bool = False,
|
||||
) -> None:
|
||||
if carrier.status != "1":
|
||||
raise HTTPException(status_code=400, detail="该承运商已终止合作,无法安排运输")
|
||||
if not carrier_audit_approved(carrier):
|
||||
raise HTTPException(status_code=400, detail="该承运商尚未审核通过,无法安排运输")
|
||||
if not carrier_qualification_complete(carrier):
|
||||
raise HTTPException(status_code=400, detail="该承运商资质附件不完整,无法安排运输")
|
||||
if carrier_agreement_expired(carrier):
|
||||
if rearrange:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="该承运商运输协议已过期,请先更新协议后再调整运输",
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="该承运商运输协议已过期,无法安排运输")
|
||||
|
||||
|
||||
def normalize_carrier_urls(value: Optional[str]) -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
trimmed = str(value).strip()
|
||||
return trimmed or None
|
||||
|
||||
|
||||
def validate_carrier_qualification_fields(
|
||||
*,
|
||||
business_license_urls: Optional[str],
|
||||
road_transport_license_urls: Optional[str],
|
||||
transport_agreement_urls: Optional[str],
|
||||
transport_agreement_expire_date: Optional[date],
|
||||
require_all: bool,
|
||||
) -> None:
|
||||
if not require_all:
|
||||
touched = any(
|
||||
item is not None
|
||||
for item in (
|
||||
business_license_urls,
|
||||
road_transport_license_urls,
|
||||
transport_agreement_urls,
|
||||
transport_agreement_expire_date,
|
||||
)
|
||||
)
|
||||
if not touched:
|
||||
return
|
||||
|
||||
if not _urls_nonempty(business_license_urls):
|
||||
raise HTTPException(status_code=400, detail="请上传营业执照")
|
||||
if not _urls_nonempty(road_transport_license_urls):
|
||||
raise HTTPException(status_code=400, detail="请上传道路经营许可证")
|
||||
if not _urls_nonempty(transport_agreement_urls):
|
||||
raise HTTPException(status_code=400, detail="请上传运输协议")
|
||||
if transport_agreement_expire_date is None:
|
||||
raise HTTPException(status_code=400, detail="请填写运输协议有效期")
|
||||
@@ -32,6 +32,7 @@ def certs_to_read(certs: list[PlCert]) -> list[CertRead]:
|
||||
CertRead(
|
||||
id=c.id,
|
||||
cert_id=c.id,
|
||||
vehicle_part=c.vehicle_part,
|
||||
cert_name=c.cert_name,
|
||||
cert_no=c.cert_no,
|
||||
expire_date=c.expire_date,
|
||||
@@ -74,6 +75,7 @@ def save_owner_certs(
|
||||
cert_id = row.cert_id
|
||||
if cert_id and cert_id in existing_map:
|
||||
cert = existing_map[cert_id]
|
||||
cert.vehicle_part = row.vehicle_part
|
||||
cert.cert_name = row.cert_name
|
||||
cert.cert_no = row.cert_no
|
||||
cert.expire_date = row.expire_date
|
||||
@@ -88,6 +90,7 @@ def save_owner_certs(
|
||||
tenant_no=tenant_no,
|
||||
owner_type=owner_type,
|
||||
owner_id=owner_id,
|
||||
vehicle_part=row.vehicle_part,
|
||||
cert_name=row.cert_name,
|
||||
cert_no=row.cert_no,
|
||||
expire_date=row.expire_date,
|
||||
|
||||
@@ -11,6 +11,7 @@ from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.utils import generate_uuid
|
||||
from app.core.delete_guard import assert_row_deletable_by_creator, user_info_from_context
|
||||
from app.models.models import SysUser
|
||||
from app.services.pl_data_scope import apply_pl_scope, assert_pl_row_in_scope
|
||||
from app.models.pl_models import (
|
||||
@@ -88,6 +89,46 @@ def get_settlement_row(
|
||||
return row
|
||||
|
||||
|
||||
def get_settlement_by_transport_id(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_no: str,
|
||||
transport_id: str,
|
||||
user=None,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
if not transport_id:
|
||||
return None
|
||||
row = (
|
||||
db.query(PlFreightSettlement)
|
||||
.join(
|
||||
PlFreightSettlementTransport,
|
||||
PlFreightSettlementTransport.settlement_id == PlFreightSettlement.id,
|
||||
)
|
||||
.filter(
|
||||
PlFreightSettlementTransport.transport_id == transport_id,
|
||||
PlFreightSettlementTransport.tenant_no == tenant_no,
|
||||
PlFreightSettlementTransport.del_flag == 0,
|
||||
PlFreightSettlement.tenant_no == tenant_no,
|
||||
PlFreightSettlement.del_flag == 0,
|
||||
)
|
||||
.order_by(PlFreightSettlement.create_time.desc())
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
return None
|
||||
if user:
|
||||
assert_pl_row_in_scope(db, row, PlFreightSettlement, user, tenant_no)
|
||||
return {
|
||||
"id": row.id,
|
||||
"settlementNo": row.settlement_no,
|
||||
"invoiceAmount": row.invoice_amount,
|
||||
"invoiceNo": row.invoice_no,
|
||||
"invoiceDate": row.invoice_date,
|
||||
"invoiceAttachmentUrls": row.invoice_attachment_urls,
|
||||
"settlementStatus": row.settlement_status,
|
||||
}
|
||||
|
||||
|
||||
def list_unsettled_plans(
|
||||
db: Session,
|
||||
*,
|
||||
@@ -264,6 +305,11 @@ def delete_draft_settlement(
|
||||
if settlement.settlement_status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅草稿结算单可删除")
|
||||
|
||||
if user:
|
||||
assert_row_deletable_by_creator(settlement, user_info_from_context(user))
|
||||
elif user_id:
|
||||
assert_row_deletable_by_creator(settlement, {"user_id": user_id})
|
||||
|
||||
now = datetime.now()
|
||||
settlement.del_flag = 1
|
||||
settlement.update_by = user_id
|
||||
|
||||
@@ -379,7 +379,8 @@ def _apply_payload_to_transport_row(
|
||||
transport.freight_price = transport_payload.freight_price
|
||||
transport.freight_amount = freight_amount or 0
|
||||
transport.freight_type = transport_payload.freight_type
|
||||
transport.has_invoice = transport_payload.has_invoice
|
||||
if transport_payload.has_invoice is not None:
|
||||
transport.has_invoice = transport_payload.has_invoice
|
||||
transport.unit = "吨" if transport_payload.freight_type == "by_weight" else "趟"
|
||||
if transport_payload.remark is not None:
|
||||
transport.remark = transport_payload.remark
|
||||
@@ -651,7 +652,7 @@ def create_transport_for_plan(
|
||||
freight_price=transport_payload.freight_price,
|
||||
freight_amount=freight_amount or 0,
|
||||
freight_type=transport_payload.freight_type,
|
||||
has_invoice=transport_payload.has_invoice,
|
||||
has_invoice=transport_payload.has_invoice if transport_payload.has_invoice is not None else 0,
|
||||
transport_status="draft",
|
||||
approval_status="none",
|
||||
remark=transport_payload.remark,
|
||||
|
||||
@@ -0,0 +1,683 @@
|
||||
"""车辆维保业务逻辑"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.db_auth import DbUserContext
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysUser
|
||||
from app.models.pl_models import (
|
||||
PlVehicle,
|
||||
PlVehicleMaintenanceApply,
|
||||
PlVehicleMaintenanceProcess,
|
||||
)
|
||||
from app.schemas.pl.vehicle_maintenance import (
|
||||
VehicleMaintenanceApplyCreate,
|
||||
VehicleMaintenanceApplyRead,
|
||||
VehicleMaintenanceApplyReminder,
|
||||
VehicleMaintenanceApplyUpdate,
|
||||
VehicleMaintenanceProcessBoardItem,
|
||||
VehicleMaintenanceProcessCreate,
|
||||
VehicleMaintenanceProcessRead,
|
||||
VehicleMaintenanceProcessUpdate,
|
||||
)
|
||||
from app.services.pl_data_scope import apply_pl_scope, assert_pl_row_in_scope
|
||||
|
||||
|
||||
def _generate_seq_no(db: Session, model, column_attr: str, prefix: str) -> str:
|
||||
column = getattr(model, column_attr)
|
||||
last = (
|
||||
db.query(model)
|
||||
.filter(column.like(f"{prefix}%"))
|
||||
.order_by(column.desc())
|
||||
.first()
|
||||
)
|
||||
seq = int(getattr(last, column_attr)[-6:]) + 1 if last else 1
|
||||
return f"{prefix}{seq:06d}"
|
||||
|
||||
|
||||
def generate_apply_no(db: Session) -> str:
|
||||
return _generate_seq_no(
|
||||
db, PlVehicleMaintenanceApply, "apply_no", f"SXWB{date.today():%Y%m%d}"
|
||||
)
|
||||
|
||||
|
||||
def generate_process_no(db: Session) -> str:
|
||||
return _generate_seq_no(
|
||||
db,
|
||||
PlVehicleMaintenanceProcess,
|
||||
"process_no",
|
||||
f"SXWC{date.today():%Y%m%d}",
|
||||
)
|
||||
|
||||
|
||||
def _resolve_user_name(db: Session, user_id: Optional[str]) -> Optional[str]:
|
||||
if not user_id:
|
||||
return None
|
||||
row = (
|
||||
db.query(SysUser)
|
||||
.filter(SysUser.id == user_id, SysUser.del_flag == 0)
|
||||
.first()
|
||||
)
|
||||
return row.nick_name or row.user_no if row else None
|
||||
|
||||
|
||||
def _assert_vehicle_exists(db: Session, tenant_no: str, license_plate: str) -> None:
|
||||
exists = (
|
||||
db.query(PlVehicle.id)
|
||||
.filter(
|
||||
PlVehicle.tenant_no == tenant_no,
|
||||
PlVehicle.license_plate == license_plate,
|
||||
PlVehicle.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not exists:
|
||||
raise HTTPException(status_code=400, detail="车牌号不存在")
|
||||
|
||||
|
||||
def _resolve_user_id(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user_id: Optional[str],
|
||||
user: DbUserContext,
|
||||
*,
|
||||
field_label: str = "用户",
|
||||
) -> str:
|
||||
target = (user_id or "").strip() or user.id
|
||||
row = (
|
||||
db.query(SysUser.id)
|
||||
.filter(
|
||||
SysUser.id == target,
|
||||
SysUser.tenant_no == tenant_no,
|
||||
SysUser.del_flag == 0,
|
||||
SysUser.status == "1",
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(status_code=400, detail=f"{field_label}不存在或已停用")
|
||||
return target
|
||||
|
||||
|
||||
def _validate_apply_dates(
|
||||
apply_date: date,
|
||||
start_date: date,
|
||||
expected_pickup_date: Optional[date],
|
||||
) -> None:
|
||||
if apply_date > start_date:
|
||||
raise HTTPException(status_code=400, detail="申请时间不能晚于维保开始时间")
|
||||
if expected_pickup_date and start_date > expected_pickup_date:
|
||||
raise HTTPException(status_code=400, detail="维保开始时间不能晚于预计取车时间")
|
||||
|
||||
|
||||
def _get_apply_row(
|
||||
db: Session, tenant_no: str, apply_id: str, user: DbUserContext
|
||||
) -> PlVehicleMaintenanceApply:
|
||||
row = (
|
||||
db.query(PlVehicleMaintenanceApply)
|
||||
.filter(
|
||||
PlVehicleMaintenanceApply.id == apply_id,
|
||||
PlVehicleMaintenanceApply.tenant_no == tenant_no,
|
||||
PlVehicleMaintenanceApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="申请单不存在")
|
||||
assert_pl_row_in_scope(db, row, PlVehicleMaintenanceApply, user, tenant_no)
|
||||
return row
|
||||
|
||||
|
||||
def _get_process_row(
|
||||
db: Session, tenant_no: str, process_id: str, user: DbUserContext
|
||||
) -> PlVehicleMaintenanceProcess:
|
||||
row = (
|
||||
db.query(PlVehicleMaintenanceProcess)
|
||||
.filter(
|
||||
PlVehicleMaintenanceProcess.id == process_id,
|
||||
PlVehicleMaintenanceProcess.tenant_no == tenant_no,
|
||||
PlVehicleMaintenanceProcess.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="处理登记不存在")
|
||||
assert_pl_row_in_scope(db, row, PlVehicleMaintenanceProcess, user, tenant_no)
|
||||
return row
|
||||
|
||||
|
||||
def _has_active_process(db: Session, tenant_no: str, apply_id: str) -> bool:
|
||||
return (
|
||||
db.query(PlVehicleMaintenanceProcess.id)
|
||||
.filter(
|
||||
PlVehicleMaintenanceProcess.apply_id == apply_id,
|
||||
PlVehicleMaintenanceProcess.tenant_no == tenant_no,
|
||||
PlVehicleMaintenanceProcess.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
is not None
|
||||
)
|
||||
|
||||
|
||||
def apply_to_read(row: PlVehicleMaintenanceApply, db: Session) -> VehicleMaintenanceApplyRead:
|
||||
return VehicleMaintenanceApplyRead(
|
||||
id=row.id,
|
||||
applyNo=row.apply_no,
|
||||
licensePlate=row.license_plate,
|
||||
applicantId=row.applicant_id,
|
||||
applicantName=_resolve_user_name(db, row.applicant_id),
|
||||
applyDate=row.apply_date,
|
||||
maintenanceType=row.maintenance_type,
|
||||
startDate=row.start_date,
|
||||
expectedPickupDate=row.expected_pickup_date,
|
||||
maintenanceContent=row.maintenance_content,
|
||||
shopAddress=row.shop_address,
|
||||
attachmentUrls=row.attachment_urls,
|
||||
status=row.status,
|
||||
remark=row.remark,
|
||||
createTime=row.create_time,
|
||||
updateTime=row.update_time,
|
||||
)
|
||||
|
||||
|
||||
def process_to_read(
|
||||
row: PlVehicleMaintenanceProcess, db: Session, apply_no: Optional[str] = None
|
||||
) -> VehicleMaintenanceProcessRead:
|
||||
if apply_no is None and row.apply_id:
|
||||
apply = (
|
||||
db.query(PlVehicleMaintenanceApply.apply_no)
|
||||
.filter(
|
||||
PlVehicleMaintenanceApply.id == row.apply_id,
|
||||
PlVehicleMaintenanceApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
apply_no = apply.apply_no if apply else None
|
||||
return VehicleMaintenanceProcessRead(
|
||||
id=row.id,
|
||||
processNo=row.process_no,
|
||||
applyId=row.apply_id,
|
||||
applyNo=apply_no,
|
||||
licensePlate=row.license_plate,
|
||||
amount=row.amount,
|
||||
handlerId=row.handler_id,
|
||||
handlerName=_resolve_user_name(db, row.handler_id),
|
||||
processDate=row.process_date,
|
||||
attachmentUrls=row.attachment_urls,
|
||||
remark=row.remark,
|
||||
createBy=row.create_by,
|
||||
createByName=_resolve_user_name(db, row.create_by),
|
||||
createTime=row.create_time,
|
||||
updateTime=row.update_time,
|
||||
)
|
||||
|
||||
|
||||
def _resolve_apply_for_process(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
payload: VehicleMaintenanceProcessCreate | VehicleMaintenanceProcessUpdate,
|
||||
user: DbUserContext,
|
||||
*,
|
||||
current_process_id: Optional[str] = None,
|
||||
) -> tuple[Optional[PlVehicleMaintenanceApply], str]:
|
||||
license_plate = (payload.license_plate or "").strip()
|
||||
|
||||
if payload.apply_id:
|
||||
apply = _get_apply_row(db, tenant_no, payload.apply_id, user)
|
||||
if apply.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待处理申请可关联")
|
||||
existing = (
|
||||
db.query(PlVehicleMaintenanceProcess)
|
||||
.filter(
|
||||
PlVehicleMaintenanceProcess.apply_id == apply.id,
|
||||
PlVehicleMaintenanceProcess.tenant_no == tenant_no,
|
||||
PlVehicleMaintenanceProcess.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if existing and existing.id != current_process_id:
|
||||
raise HTTPException(status_code=400, detail="该申请单已被关联")
|
||||
return apply, apply.license_plate
|
||||
|
||||
if not license_plate:
|
||||
raise HTTPException(status_code=400, detail="请填写车牌号或关联申请单")
|
||||
_assert_vehicle_exists(db, tenant_no, license_plate)
|
||||
return None, license_plate
|
||||
|
||||
|
||||
def list_applies(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
*,
|
||||
q: Optional[str] = None,
|
||||
maintenance_type: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
apply_date_from: Optional[date] = None,
|
||||
apply_date_to: Optional[date] = None,
|
||||
applicant_id: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[VehicleMaintenanceApplyRead], int]:
|
||||
query = db.query(PlVehicleMaintenanceApply).filter(
|
||||
PlVehicleMaintenanceApply.tenant_no == tenant_no,
|
||||
PlVehicleMaintenanceApply.del_flag == 0,
|
||||
)
|
||||
if q:
|
||||
query = query.filter(
|
||||
or_(
|
||||
PlVehicleMaintenanceApply.apply_no.contains(q),
|
||||
PlVehicleMaintenanceApply.license_plate.contains(q),
|
||||
)
|
||||
)
|
||||
if maintenance_type:
|
||||
query = query.filter(
|
||||
PlVehicleMaintenanceApply.maintenance_type.contains(maintenance_type)
|
||||
)
|
||||
if status:
|
||||
query = query.filter(PlVehicleMaintenanceApply.status == status)
|
||||
if apply_date_from:
|
||||
query = query.filter(PlVehicleMaintenanceApply.apply_date >= apply_date_from)
|
||||
if apply_date_to:
|
||||
query = query.filter(PlVehicleMaintenanceApply.apply_date <= apply_date_to)
|
||||
if applicant_id:
|
||||
query = query.filter(PlVehicleMaintenanceApply.applicant_id == applicant_id)
|
||||
|
||||
query = apply_pl_scope(query, PlVehicleMaintenanceApply, user, db, tenant_no)
|
||||
total = query.count()
|
||||
rows = (
|
||||
query.order_by(PlVehicleMaintenanceApply.create_time.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return [apply_to_read(row, db) for row in rows], total
|
||||
|
||||
|
||||
def get_apply(
|
||||
db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
||||
) -> VehicleMaintenanceApplyRead:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def create_apply(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
payload: VehicleMaintenanceApplyCreate,
|
||||
) -> VehicleMaintenanceApplyRead:
|
||||
license_plate = payload.license_plate.strip()
|
||||
maintenance_type = payload.maintenance_type.strip()
|
||||
if not maintenance_type:
|
||||
raise HTTPException(status_code=400, detail="维保类型不能为空")
|
||||
_assert_vehicle_exists(db, tenant_no, license_plate)
|
||||
_validate_apply_dates(
|
||||
payload.apply_date, payload.start_date, payload.expected_pickup_date
|
||||
)
|
||||
applicant_id = _resolve_user_id(
|
||||
db, tenant_no, payload.applicant_id, user, field_label="申请人"
|
||||
)
|
||||
now = datetime.now()
|
||||
row = PlVehicleMaintenanceApply(
|
||||
id=generate_uuid(),
|
||||
apply_no=generate_apply_no(db),
|
||||
license_plate=license_plate,
|
||||
applicant_id=applicant_id,
|
||||
apply_date=payload.apply_date,
|
||||
maintenance_type=maintenance_type,
|
||||
start_date=payload.start_date,
|
||||
expected_pickup_date=payload.expected_pickup_date,
|
||||
maintenance_content=(payload.maintenance_content or "").strip() or None,
|
||||
shop_address=(payload.shop_address or "").strip() or None,
|
||||
attachment_urls=payload.attachment_urls,
|
||||
status="pending",
|
||||
tenant_no=tenant_no,
|
||||
remark=payload.remark,
|
||||
create_by=user.id,
|
||||
create_dept_id=user.dept_id or "",
|
||||
create_time=now,
|
||||
update_by=user.id,
|
||||
update_time=now,
|
||||
del_flag=0,
|
||||
)
|
||||
db.add(row)
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def update_apply(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
apply_id: str,
|
||||
payload: VehicleMaintenanceApplyUpdate,
|
||||
) -> VehicleMaintenanceApplyRead:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
if row.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待处理申请可编辑")
|
||||
license_plate = payload.license_plate.strip()
|
||||
maintenance_type = payload.maintenance_type.strip()
|
||||
if not maintenance_type:
|
||||
raise HTTPException(status_code=400, detail="维保类型不能为空")
|
||||
_assert_vehicle_exists(db, tenant_no, license_plate)
|
||||
_validate_apply_dates(
|
||||
payload.apply_date, payload.start_date, payload.expected_pickup_date
|
||||
)
|
||||
row.license_plate = license_plate
|
||||
row.applicant_id = _resolve_user_id(
|
||||
db, tenant_no, payload.applicant_id, user, field_label="申请人"
|
||||
)
|
||||
row.apply_date = payload.apply_date
|
||||
row.maintenance_type = maintenance_type
|
||||
row.start_date = payload.start_date
|
||||
row.expected_pickup_date = payload.expected_pickup_date
|
||||
row.maintenance_content = (payload.maintenance_content or "").strip() or None
|
||||
row.shop_address = (payload.shop_address or "").strip() or None
|
||||
row.attachment_urls = payload.attachment_urls
|
||||
row.remark = payload.remark
|
||||
row.update_by = user.id
|
||||
row.update_time = datetime.now()
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def cancel_apply(
|
||||
db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
||||
) -> VehicleMaintenanceApplyRead:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
if row.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待处理申请可作废")
|
||||
row.status = "cancelled"
|
||||
row.update_by = user.id
|
||||
row.update_time = datetime.now()
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def delete_applies(
|
||||
db: Session, tenant_no: str, user: DbUserContext, apply_ids: list[str]
|
||||
) -> None:
|
||||
for apply_id in apply_ids:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
if row.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待处理申请可删除")
|
||||
if _has_active_process(db, tenant_no, apply_id):
|
||||
raise HTTPException(status_code=400, detail="已关联处理记录的申请不可删除")
|
||||
row.del_flag = 1
|
||||
row.update_by = user.id
|
||||
row.update_time = datetime.now()
|
||||
db.commit()
|
||||
|
||||
|
||||
def list_reminders(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
license_plate: str,
|
||||
*,
|
||||
exclude_apply_id: Optional[str] = None,
|
||||
) -> list[VehicleMaintenanceApplyReminder]:
|
||||
plate = license_plate.strip()
|
||||
if not plate:
|
||||
return []
|
||||
today = date.today()
|
||||
query = db.query(PlVehicleMaintenanceApply).filter(
|
||||
PlVehicleMaintenanceApply.tenant_no == tenant_no,
|
||||
PlVehicleMaintenanceApply.del_flag == 0,
|
||||
PlVehicleMaintenanceApply.status == "pending",
|
||||
PlVehicleMaintenanceApply.license_plate == plate,
|
||||
)
|
||||
if exclude_apply_id:
|
||||
query = query.filter(PlVehicleMaintenanceApply.id != exclude_apply_id)
|
||||
query = apply_pl_scope(query, PlVehicleMaintenanceApply, user, db, tenant_no)
|
||||
rows = query.order_by(PlVehicleMaintenanceApply.create_time.desc()).all()
|
||||
result: list[VehicleMaintenanceApplyReminder] = []
|
||||
for row in rows:
|
||||
pickup = row.expected_pickup_date
|
||||
if pickup is not None and today > pickup:
|
||||
continue
|
||||
result.append(
|
||||
VehicleMaintenanceApplyReminder(
|
||||
id=row.id,
|
||||
applyNo=row.apply_no,
|
||||
licensePlate=row.license_plate,
|
||||
maintenanceType=row.maintenance_type,
|
||||
applyDate=row.apply_date,
|
||||
startDate=row.start_date,
|
||||
expectedPickupDate=row.expected_pickup_date,
|
||||
maintenanceContent=row.maintenance_content,
|
||||
shopAddress=row.shop_address,
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def board_item_to_read(
|
||||
apply_row: PlVehicleMaintenanceApply,
|
||||
process_row: Optional[PlVehicleMaintenanceProcess],
|
||||
db: Session,
|
||||
) -> VehicleMaintenanceProcessBoardItem:
|
||||
return VehicleMaintenanceProcessBoardItem(
|
||||
applyId=apply_row.id,
|
||||
applyNo=apply_row.apply_no,
|
||||
licensePlate=apply_row.license_plate,
|
||||
maintenanceType=apply_row.maintenance_type,
|
||||
applyDate=apply_row.apply_date,
|
||||
startDate=apply_row.start_date,
|
||||
expectedPickupDate=apply_row.expected_pickup_date,
|
||||
maintenanceContent=apply_row.maintenance_content,
|
||||
shopAddress=apply_row.shop_address,
|
||||
applicantName=_resolve_user_name(db, apply_row.applicant_id),
|
||||
applyStatus=apply_row.status,
|
||||
applyRemark=apply_row.remark,
|
||||
processId=process_row.id if process_row else None,
|
||||
processNo=process_row.process_no if process_row else None,
|
||||
processDate=process_row.process_date if process_row else None,
|
||||
amount=process_row.amount if process_row else None,
|
||||
handlerName=_resolve_user_name(db, process_row.handler_id) if process_row else None,
|
||||
processRemark=process_row.remark if process_row else None,
|
||||
)
|
||||
|
||||
|
||||
def list_process_board(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
*,
|
||||
q: Optional[str] = None,
|
||||
maintenance_type: Optional[str] = None,
|
||||
apply_status: Optional[str] = None,
|
||||
apply_date_from: Optional[date] = None,
|
||||
apply_date_to: Optional[date] = None,
|
||||
process_date_from: Optional[date] = None,
|
||||
process_date_to: Optional[date] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[VehicleMaintenanceProcessBoardItem], int]:
|
||||
query = (
|
||||
db.query(PlVehicleMaintenanceApply, PlVehicleMaintenanceProcess)
|
||||
.outerjoin(
|
||||
PlVehicleMaintenanceProcess,
|
||||
and_(
|
||||
PlVehicleMaintenanceProcess.apply_id == PlVehicleMaintenanceApply.id,
|
||||
PlVehicleMaintenanceProcess.tenant_no == tenant_no,
|
||||
PlVehicleMaintenanceProcess.del_flag == 0,
|
||||
),
|
||||
)
|
||||
.filter(
|
||||
PlVehicleMaintenanceApply.tenant_no == tenant_no,
|
||||
PlVehicleMaintenanceApply.del_flag == 0,
|
||||
)
|
||||
)
|
||||
if q:
|
||||
query = query.filter(
|
||||
or_(
|
||||
PlVehicleMaintenanceApply.apply_no.contains(q),
|
||||
PlVehicleMaintenanceApply.license_plate.contains(q),
|
||||
func.coalesce(PlVehicleMaintenanceProcess.process_no, "").contains(q),
|
||||
)
|
||||
)
|
||||
if maintenance_type:
|
||||
query = query.filter(
|
||||
PlVehicleMaintenanceApply.maintenance_type.contains(maintenance_type)
|
||||
)
|
||||
if apply_status:
|
||||
query = query.filter(PlVehicleMaintenanceApply.status == apply_status)
|
||||
if apply_date_from:
|
||||
query = query.filter(PlVehicleMaintenanceApply.apply_date >= apply_date_from)
|
||||
if apply_date_to:
|
||||
query = query.filter(PlVehicleMaintenanceApply.apply_date <= apply_date_to)
|
||||
if process_date_from:
|
||||
query = query.filter(
|
||||
PlVehicleMaintenanceProcess.process_date >= process_date_from
|
||||
)
|
||||
if process_date_to:
|
||||
query = query.filter(PlVehicleMaintenanceProcess.process_date <= process_date_to)
|
||||
|
||||
query = apply_pl_scope(query, PlVehicleMaintenanceApply, user, db, tenant_no)
|
||||
total = query.count()
|
||||
rows = (
|
||||
query.order_by(PlVehicleMaintenanceApply.create_time.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return [
|
||||
board_item_to_read(apply_row, process_row, db)
|
||||
for apply_row, process_row in rows
|
||||
], total
|
||||
|
||||
|
||||
def get_process(
|
||||
db: Session, tenant_no: str, user: DbUserContext, process_id: str
|
||||
) -> VehicleMaintenanceProcessRead:
|
||||
row = _get_process_row(db, tenant_no, process_id, user)
|
||||
return process_to_read(row, db)
|
||||
|
||||
|
||||
def create_process(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
payload: VehicleMaintenanceProcessCreate,
|
||||
) -> VehicleMaintenanceProcessRead:
|
||||
apply_row, license_plate = _resolve_apply_for_process(db, tenant_no, payload, user)
|
||||
handler_id = _resolve_user_id(
|
||||
db, tenant_no, payload.handler_id, user, field_label="处理人"
|
||||
)
|
||||
now = datetime.now()
|
||||
row = PlVehicleMaintenanceProcess(
|
||||
id=generate_uuid(),
|
||||
process_no=generate_process_no(db),
|
||||
apply_id=apply_row.id if apply_row else None,
|
||||
license_plate=license_plate,
|
||||
amount=payload.amount,
|
||||
handler_id=handler_id,
|
||||
process_date=payload.process_date,
|
||||
attachment_urls=payload.attachment_urls,
|
||||
tenant_no=tenant_no,
|
||||
remark=payload.remark,
|
||||
create_by=user.id,
|
||||
create_dept_id=user.dept_id or "",
|
||||
create_time=now,
|
||||
update_by=user.id,
|
||||
update_time=now,
|
||||
del_flag=0,
|
||||
)
|
||||
db.add(row)
|
||||
if apply_row:
|
||||
apply_row.status = "processed"
|
||||
apply_row.update_by = user.id
|
||||
apply_row.update_time = now
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return process_to_read(row, db)
|
||||
|
||||
|
||||
def update_process(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
process_id: str,
|
||||
payload: VehicleMaintenanceProcessUpdate,
|
||||
) -> VehicleMaintenanceProcessRead:
|
||||
row = _get_process_row(db, tenant_no, process_id, user)
|
||||
old_apply_id = row.apply_id
|
||||
apply_row, license_plate = _resolve_apply_for_process(
|
||||
db, tenant_no, payload, user, current_process_id=process_id
|
||||
)
|
||||
handler_id = _resolve_user_id(
|
||||
db, tenant_no, payload.handler_id, user, field_label="处理人"
|
||||
)
|
||||
now = datetime.now()
|
||||
|
||||
if old_apply_id and old_apply_id != (apply_row.id if apply_row else None):
|
||||
old_apply = (
|
||||
db.query(PlVehicleMaintenanceApply)
|
||||
.filter(
|
||||
PlVehicleMaintenanceApply.id == old_apply_id,
|
||||
PlVehicleMaintenanceApply.tenant_no == tenant_no,
|
||||
PlVehicleMaintenanceApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if old_apply:
|
||||
old_apply.status = "pending"
|
||||
old_apply.update_by = user.id
|
||||
old_apply.update_time = now
|
||||
|
||||
row.apply_id = apply_row.id if apply_row else None
|
||||
row.license_plate = license_plate
|
||||
row.amount = payload.amount
|
||||
row.handler_id = handler_id
|
||||
row.process_date = payload.process_date
|
||||
row.attachment_urls = payload.attachment_urls
|
||||
row.remark = payload.remark
|
||||
row.update_by = user.id
|
||||
row.update_time = now
|
||||
|
||||
if apply_row:
|
||||
apply_row.status = "processed"
|
||||
apply_row.update_by = user.id
|
||||
apply_row.update_time = now
|
||||
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return process_to_read(row, db)
|
||||
|
||||
|
||||
def delete_processes(
|
||||
db: Session, tenant_no: str, user: DbUserContext, process_ids: list[str]
|
||||
) -> None:
|
||||
now = datetime.now()
|
||||
for process_id in process_ids:
|
||||
row = _get_process_row(db, tenant_no, process_id, user)
|
||||
apply_id = row.apply_id
|
||||
row.del_flag = 1
|
||||
row.update_by = user.id
|
||||
row.update_time = now
|
||||
if apply_id:
|
||||
apply = (
|
||||
db.query(PlVehicleMaintenanceApply)
|
||||
.filter(
|
||||
PlVehicleMaintenanceApply.id == apply_id,
|
||||
PlVehicleMaintenanceApply.tenant_no == tenant_no,
|
||||
PlVehicleMaintenanceApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if apply and apply.status == "processed":
|
||||
apply.status = "pending"
|
||||
apply.update_by = user.id
|
||||
apply.update_time = now
|
||||
db.commit()
|
||||
@@ -0,0 +1,670 @@
|
||||
"""车辆 ETC/加油充值业务逻辑"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.db_auth import DbUserContext
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysUser
|
||||
from app.models.pl_models import (
|
||||
PlVehicle,
|
||||
PlVehicleRechargeApply,
|
||||
PlVehicleRechargeRecord,
|
||||
)
|
||||
from app.schemas.pl.vehicle_recharge import (
|
||||
VehicleRechargeApplyCreate,
|
||||
VehicleRechargeApplyRead,
|
||||
VehicleRechargeApplyUpdate,
|
||||
VehicleRechargeDisburseBoardItem,
|
||||
VehicleRechargeRecordCreate,
|
||||
VehicleRechargeRecordRead,
|
||||
VehicleRechargeRecordUpdate,
|
||||
)
|
||||
from app.services.pl_data_scope import apply_pl_scope, assert_pl_row_in_scope
|
||||
|
||||
|
||||
def _generate_seq_no(db: Session, model, column_attr: str, prefix: str) -> str:
|
||||
column = getattr(model, column_attr)
|
||||
last = (
|
||||
db.query(model)
|
||||
.filter(column.like(f"{prefix}%"))
|
||||
.order_by(column.desc())
|
||||
.first()
|
||||
)
|
||||
seq = int(getattr(last, column_attr)[-6:]) + 1 if last else 1
|
||||
return f"{prefix}{seq:06d}"
|
||||
|
||||
|
||||
def generate_apply_no(db: Session) -> str:
|
||||
return _generate_seq_no(
|
||||
db, PlVehicleRechargeApply, "apply_no", f"SXSQ{date.today():%Y%m%d}"
|
||||
)
|
||||
|
||||
|
||||
def generate_recharge_order_no(db: Session) -> str:
|
||||
return _generate_seq_no(
|
||||
db,
|
||||
PlVehicleRechargeRecord,
|
||||
"recharge_order_no",
|
||||
f"SXCL{date.today():%Y%m%d}",
|
||||
)
|
||||
|
||||
|
||||
def _resolve_user_name(db: Session, user_id: Optional[str]) -> Optional[str]:
|
||||
if not user_id:
|
||||
return None
|
||||
row = (
|
||||
db.query(SysUser)
|
||||
.filter(SysUser.id == user_id, SysUser.del_flag == 0)
|
||||
.first()
|
||||
)
|
||||
return row.nick_name or row.user_no if row else None
|
||||
|
||||
|
||||
def _assert_vehicle_exists(db: Session, tenant_no: str, license_plate: str) -> None:
|
||||
exists = (
|
||||
db.query(PlVehicle.id)
|
||||
.filter(
|
||||
PlVehicle.tenant_no == tenant_no,
|
||||
PlVehicle.license_plate == license_plate,
|
||||
PlVehicle.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not exists:
|
||||
raise HTTPException(status_code=400, detail="车牌号不存在")
|
||||
|
||||
|
||||
def _resolve_applicant_id(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
applicant_id: Optional[str],
|
||||
user: DbUserContext,
|
||||
) -> str:
|
||||
target = (applicant_id or "").strip() or user.id
|
||||
row = (
|
||||
db.query(SysUser.id)
|
||||
.filter(
|
||||
SysUser.id == target,
|
||||
SysUser.tenant_no == tenant_no,
|
||||
SysUser.del_flag == 0,
|
||||
SysUser.status == "1",
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(status_code=400, detail="申请人不存在或已停用")
|
||||
return target
|
||||
|
||||
|
||||
def _get_apply_row(
|
||||
db: Session, tenant_no: str, apply_id: str, user: DbUserContext
|
||||
) -> PlVehicleRechargeApply:
|
||||
row = (
|
||||
db.query(PlVehicleRechargeApply)
|
||||
.filter(
|
||||
PlVehicleRechargeApply.id == apply_id,
|
||||
PlVehicleRechargeApply.tenant_no == tenant_no,
|
||||
PlVehicleRechargeApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="申请单不存在")
|
||||
assert_pl_row_in_scope(db, row, PlVehicleRechargeApply, user, tenant_no)
|
||||
return row
|
||||
|
||||
|
||||
def _get_record_row(
|
||||
db: Session, tenant_no: str, record_id: str, user: DbUserContext
|
||||
) -> PlVehicleRechargeRecord:
|
||||
row = (
|
||||
db.query(PlVehicleRechargeRecord)
|
||||
.filter(
|
||||
PlVehicleRechargeRecord.id == record_id,
|
||||
PlVehicleRechargeRecord.tenant_no == tenant_no,
|
||||
PlVehicleRechargeRecord.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="发放登记不存在")
|
||||
assert_pl_row_in_scope(db, row, PlVehicleRechargeRecord, user, tenant_no)
|
||||
return row
|
||||
|
||||
|
||||
def _has_active_record(db: Session, tenant_no: str, apply_id: str) -> bool:
|
||||
return (
|
||||
db.query(PlVehicleRechargeRecord.id)
|
||||
.filter(
|
||||
PlVehicleRechargeRecord.apply_id == apply_id,
|
||||
PlVehicleRechargeRecord.tenant_no == tenant_no,
|
||||
PlVehicleRechargeRecord.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
is not None
|
||||
)
|
||||
|
||||
|
||||
def apply_to_read(row: PlVehicleRechargeApply, db: Session) -> VehicleRechargeApplyRead:
|
||||
return VehicleRechargeApplyRead(
|
||||
id=row.id,
|
||||
applyNo=row.apply_no,
|
||||
licensePlate=row.license_plate,
|
||||
rechargeType=row.recharge_type,
|
||||
applyDate=row.apply_date,
|
||||
odometerKm=row.odometer_km,
|
||||
attachmentUrls=row.attachment_urls,
|
||||
applicantId=row.applicant_id,
|
||||
applicantName=_resolve_user_name(db, row.applicant_id),
|
||||
status=row.status,
|
||||
remark=row.remark,
|
||||
createTime=row.create_time,
|
||||
updateTime=row.update_time,
|
||||
)
|
||||
|
||||
|
||||
def record_to_read(
|
||||
row: PlVehicleRechargeRecord, db: Session, apply_no: Optional[str] = None
|
||||
) -> VehicleRechargeRecordRead:
|
||||
if apply_no is None and row.apply_id:
|
||||
apply = (
|
||||
db.query(PlVehicleRechargeApply.apply_no)
|
||||
.filter(
|
||||
PlVehicleRechargeApply.id == row.apply_id,
|
||||
PlVehicleRechargeApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
apply_no = apply.apply_no if apply else None
|
||||
return VehicleRechargeRecordRead(
|
||||
id=row.id,
|
||||
rechargeOrderNo=row.recharge_order_no,
|
||||
rechargeDate=row.recharge_date,
|
||||
rechargeType=row.recharge_type,
|
||||
cardNo=row.card_no,
|
||||
gasStation=row.gas_station,
|
||||
amount=row.amount,
|
||||
attachmentUrls=row.attachment_urls,
|
||||
applyId=row.apply_id,
|
||||
applyNo=apply_no,
|
||||
licensePlate=row.license_plate,
|
||||
remark=row.remark,
|
||||
createBy=row.create_by,
|
||||
createByName=_resolve_user_name(db, row.create_by),
|
||||
createTime=row.create_time,
|
||||
updateTime=row.update_time,
|
||||
)
|
||||
|
||||
|
||||
def _resolve_apply_for_record(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
payload: VehicleRechargeRecordCreate | VehicleRechargeRecordUpdate,
|
||||
user: DbUserContext,
|
||||
*,
|
||||
current_record_id: Optional[str] = None,
|
||||
) -> tuple[Optional[PlVehicleRechargeApply], str, str]:
|
||||
license_plate = (payload.license_plate or "").strip()
|
||||
recharge_type = payload.recharge_type
|
||||
|
||||
if payload.apply_id:
|
||||
apply = _get_apply_row(db, tenant_no, payload.apply_id, user)
|
||||
if apply.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待发放申请可关联")
|
||||
existing = (
|
||||
db.query(PlVehicleRechargeRecord)
|
||||
.filter(
|
||||
PlVehicleRechargeRecord.apply_id == apply.id,
|
||||
PlVehicleRechargeRecord.tenant_no == tenant_no,
|
||||
PlVehicleRechargeRecord.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if existing and existing.id != current_record_id:
|
||||
raise HTTPException(status_code=400, detail="该申请单已被关联")
|
||||
if apply.recharge_type != recharge_type:
|
||||
raise HTTPException(status_code=400, detail="充值类型与申请单不一致")
|
||||
return apply, apply.license_plate, apply.recharge_type
|
||||
|
||||
if not license_plate:
|
||||
raise HTTPException(status_code=400, detail="请填写车牌号或关联申请单")
|
||||
_assert_vehicle_exists(db, tenant_no, license_plate)
|
||||
return None, license_plate, recharge_type
|
||||
|
||||
|
||||
def list_applies(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
*,
|
||||
q: Optional[str] = None,
|
||||
recharge_type: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
apply_date_from: Optional[date] = None,
|
||||
apply_date_to: Optional[date] = None,
|
||||
applicant_id: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[VehicleRechargeApplyRead], int]:
|
||||
query = db.query(PlVehicleRechargeApply).filter(
|
||||
PlVehicleRechargeApply.tenant_no == tenant_no,
|
||||
PlVehicleRechargeApply.del_flag == 0,
|
||||
)
|
||||
if q:
|
||||
query = query.filter(
|
||||
or_(
|
||||
PlVehicleRechargeApply.apply_no.contains(q),
|
||||
PlVehicleRechargeApply.license_plate.contains(q),
|
||||
)
|
||||
)
|
||||
if recharge_type:
|
||||
query = query.filter(PlVehicleRechargeApply.recharge_type == recharge_type)
|
||||
if status:
|
||||
query = query.filter(PlVehicleRechargeApply.status == status)
|
||||
if apply_date_from:
|
||||
query = query.filter(PlVehicleRechargeApply.apply_date >= apply_date_from)
|
||||
if apply_date_to:
|
||||
query = query.filter(PlVehicleRechargeApply.apply_date <= apply_date_to)
|
||||
if applicant_id:
|
||||
query = query.filter(PlVehicleRechargeApply.applicant_id == applicant_id)
|
||||
|
||||
query = apply_pl_scope(query, PlVehicleRechargeApply, user, db, tenant_no)
|
||||
total = query.count()
|
||||
rows = (
|
||||
query.order_by(PlVehicleRechargeApply.create_time.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return [apply_to_read(row, db) for row in rows], total
|
||||
|
||||
|
||||
def get_apply(
|
||||
db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
||||
) -> VehicleRechargeApplyRead:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def create_apply(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
payload: VehicleRechargeApplyCreate,
|
||||
) -> VehicleRechargeApplyRead:
|
||||
license_plate = payload.license_plate.strip()
|
||||
_assert_vehicle_exists(db, tenant_no, license_plate)
|
||||
applicant_id = _resolve_applicant_id(db, tenant_no, payload.applicant_id, user)
|
||||
now = datetime.now()
|
||||
row = PlVehicleRechargeApply(
|
||||
id=generate_uuid(),
|
||||
apply_no=generate_apply_no(db),
|
||||
license_plate=license_plate,
|
||||
recharge_type=payload.recharge_type,
|
||||
apply_date=payload.apply_date,
|
||||
odometer_km=payload.odometer_km,
|
||||
attachment_urls=payload.attachment_urls,
|
||||
applicant_id=applicant_id,
|
||||
status="pending",
|
||||
tenant_no=tenant_no,
|
||||
remark=payload.remark,
|
||||
create_by=user.id,
|
||||
create_dept_id=user.dept_id or "",
|
||||
create_time=now,
|
||||
update_by=user.id,
|
||||
update_time=now,
|
||||
del_flag=0,
|
||||
)
|
||||
db.add(row)
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def update_apply(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
apply_id: str,
|
||||
payload: VehicleRechargeApplyUpdate,
|
||||
) -> VehicleRechargeApplyRead:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
if row.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待发放申请可编辑")
|
||||
license_plate = payload.license_plate.strip()
|
||||
_assert_vehicle_exists(db, tenant_no, license_plate)
|
||||
row.license_plate = license_plate
|
||||
row.recharge_type = payload.recharge_type
|
||||
row.apply_date = payload.apply_date
|
||||
row.odometer_km = payload.odometer_km
|
||||
row.attachment_urls = payload.attachment_urls
|
||||
row.applicant_id = _resolve_applicant_id(db, tenant_no, payload.applicant_id, user)
|
||||
row.remark = payload.remark
|
||||
row.update_by = user.id
|
||||
row.update_time = datetime.now()
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def cancel_apply(
|
||||
db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
||||
) -> VehicleRechargeApplyRead:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
if row.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待发放申请可作废")
|
||||
row.status = "cancelled"
|
||||
row.update_by = user.id
|
||||
row.update_time = datetime.now()
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def delete_applies(
|
||||
db: Session, tenant_no: str, user: DbUserContext, apply_ids: list[str]
|
||||
) -> None:
|
||||
for apply_id in apply_ids:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
if row.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待发放申请可删除")
|
||||
if _has_active_record(db, tenant_no, apply_id):
|
||||
raise HTTPException(status_code=400, detail="已关联发放记录的申请不可删除")
|
||||
row.del_flag = 1
|
||||
row.update_by = user.id
|
||||
row.update_time = datetime.now()
|
||||
db.commit()
|
||||
|
||||
|
||||
def list_pending_apply_options(
|
||||
db: Session, tenant_no: str, user: DbUserContext
|
||||
) -> list[dict]:
|
||||
query = db.query(PlVehicleRechargeApply).filter(
|
||||
PlVehicleRechargeApply.tenant_no == tenant_no,
|
||||
PlVehicleRechargeApply.del_flag == 0,
|
||||
PlVehicleRechargeApply.status == "pending",
|
||||
)
|
||||
query = apply_pl_scope(query, PlVehicleRechargeApply, user, db, tenant_no)
|
||||
rows = query.order_by(PlVehicleRechargeApply.create_time.desc()).all()
|
||||
return [
|
||||
{
|
||||
"id": row.id,
|
||||
"applyNo": row.apply_no,
|
||||
"licensePlate": row.license_plate,
|
||||
"rechargeType": row.recharge_type,
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
|
||||
def list_records(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
*,
|
||||
q: Optional[str] = None,
|
||||
recharge_type: Optional[str] = None,
|
||||
recharge_date_from: Optional[date] = None,
|
||||
recharge_date_to: Optional[date] = None,
|
||||
gas_station: Optional[str] = None,
|
||||
apply_no: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[VehicleRechargeRecordRead], int]:
|
||||
query = db.query(PlVehicleRechargeRecord).filter(
|
||||
PlVehicleRechargeRecord.tenant_no == tenant_no,
|
||||
PlVehicleRechargeRecord.del_flag == 0,
|
||||
)
|
||||
if q:
|
||||
query = query.filter(
|
||||
or_(
|
||||
PlVehicleRechargeRecord.recharge_order_no.contains(q),
|
||||
func.coalesce(PlVehicleRechargeRecord.license_plate, "").contains(q),
|
||||
func.coalesce(PlVehicleRechargeRecord.card_no, "").contains(q),
|
||||
)
|
||||
)
|
||||
if recharge_type:
|
||||
query = query.filter(PlVehicleRechargeRecord.recharge_type == recharge_type)
|
||||
if recharge_date_from:
|
||||
query = query.filter(PlVehicleRechargeRecord.recharge_date >= recharge_date_from)
|
||||
if recharge_date_to:
|
||||
query = query.filter(PlVehicleRechargeRecord.recharge_date <= recharge_date_to)
|
||||
if gas_station:
|
||||
query = query.filter(
|
||||
func.coalesce(PlVehicleRechargeRecord.gas_station, "").contains(gas_station)
|
||||
)
|
||||
if apply_no:
|
||||
query = query.join(
|
||||
PlVehicleRechargeApply,
|
||||
PlVehicleRechargeApply.id == PlVehicleRechargeRecord.apply_id,
|
||||
).filter(PlVehicleRechargeApply.apply_no.contains(apply_no))
|
||||
|
||||
query = apply_pl_scope(query, PlVehicleRechargeRecord, user, db, tenant_no)
|
||||
total = query.count()
|
||||
rows = (
|
||||
query.order_by(PlVehicleRechargeRecord.create_time.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return [record_to_read(row, db) for row in rows], total
|
||||
|
||||
|
||||
def board_item_to_read(
|
||||
apply_row: PlVehicleRechargeApply,
|
||||
record_row: Optional[PlVehicleRechargeRecord],
|
||||
db: Session,
|
||||
) -> VehicleRechargeDisburseBoardItem:
|
||||
return VehicleRechargeDisburseBoardItem(
|
||||
applyId=apply_row.id,
|
||||
applyNo=apply_row.apply_no,
|
||||
licensePlate=apply_row.license_plate,
|
||||
rechargeType=apply_row.recharge_type,
|
||||
applyDate=apply_row.apply_date,
|
||||
odometerKm=apply_row.odometer_km,
|
||||
applicantName=_resolve_user_name(db, apply_row.applicant_id),
|
||||
applyStatus=apply_row.status,
|
||||
applyRemark=apply_row.remark,
|
||||
recordId=record_row.id if record_row else None,
|
||||
rechargeOrderNo=record_row.recharge_order_no if record_row else None,
|
||||
rechargeDate=record_row.recharge_date if record_row else None,
|
||||
cardNo=record_row.card_no if record_row else None,
|
||||
gasStation=record_row.gas_station if record_row else None,
|
||||
amount=record_row.amount if record_row else None,
|
||||
recordCreateByName=_resolve_user_name(db, record_row.create_by) if record_row else None,
|
||||
recordRemark=record_row.remark if record_row else None,
|
||||
)
|
||||
|
||||
|
||||
def list_disburse_board(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
*,
|
||||
q: Optional[str] = None,
|
||||
recharge_type: Optional[str] = None,
|
||||
apply_status: Optional[str] = None,
|
||||
apply_date_from: Optional[date] = None,
|
||||
apply_date_to: Optional[date] = None,
|
||||
recharge_date_from: Optional[date] = None,
|
||||
recharge_date_to: Optional[date] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[VehicleRechargeDisburseBoardItem], int]:
|
||||
query = (
|
||||
db.query(PlVehicleRechargeApply, PlVehicleRechargeRecord)
|
||||
.outerjoin(
|
||||
PlVehicleRechargeRecord,
|
||||
and_(
|
||||
PlVehicleRechargeRecord.apply_id == PlVehicleRechargeApply.id,
|
||||
PlVehicleRechargeRecord.tenant_no == tenant_no,
|
||||
PlVehicleRechargeRecord.del_flag == 0,
|
||||
),
|
||||
)
|
||||
.filter(
|
||||
PlVehicleRechargeApply.tenant_no == tenant_no,
|
||||
PlVehicleRechargeApply.del_flag == 0,
|
||||
)
|
||||
)
|
||||
if q:
|
||||
query = query.filter(
|
||||
or_(
|
||||
PlVehicleRechargeApply.apply_no.contains(q),
|
||||
PlVehicleRechargeApply.license_plate.contains(q),
|
||||
func.coalesce(PlVehicleRechargeRecord.recharge_order_no, "").contains(q),
|
||||
func.coalesce(PlVehicleRechargeRecord.card_no, "").contains(q),
|
||||
)
|
||||
)
|
||||
if recharge_type:
|
||||
query = query.filter(PlVehicleRechargeApply.recharge_type == recharge_type)
|
||||
if apply_status:
|
||||
query = query.filter(PlVehicleRechargeApply.status == apply_status)
|
||||
if apply_date_from:
|
||||
query = query.filter(PlVehicleRechargeApply.apply_date >= apply_date_from)
|
||||
if apply_date_to:
|
||||
query = query.filter(PlVehicleRechargeApply.apply_date <= apply_date_to)
|
||||
if recharge_date_from:
|
||||
query = query.filter(PlVehicleRechargeRecord.recharge_date >= recharge_date_from)
|
||||
if recharge_date_to:
|
||||
query = query.filter(PlVehicleRechargeRecord.recharge_date <= recharge_date_to)
|
||||
|
||||
query = apply_pl_scope(query, PlVehicleRechargeApply, user, db, tenant_no)
|
||||
total = query.count()
|
||||
rows = (
|
||||
query.order_by(PlVehicleRechargeApply.create_time.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return [board_item_to_read(apply_row, record_row, db) for apply_row, record_row in rows], total
|
||||
|
||||
|
||||
def get_record(
|
||||
db: Session, tenant_no: str, user: DbUserContext, record_id: str
|
||||
) -> VehicleRechargeRecordRead:
|
||||
row = _get_record_row(db, tenant_no, record_id, user)
|
||||
return record_to_read(row, db)
|
||||
|
||||
|
||||
def create_record(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
payload: VehicleRechargeRecordCreate,
|
||||
) -> VehicleRechargeRecordRead:
|
||||
apply_row, license_plate, recharge_type = _resolve_apply_for_record(
|
||||
db, tenant_no, payload, user
|
||||
)
|
||||
now = datetime.now()
|
||||
row = PlVehicleRechargeRecord(
|
||||
id=generate_uuid(),
|
||||
recharge_order_no=generate_recharge_order_no(db),
|
||||
recharge_date=payload.recharge_date,
|
||||
recharge_type=recharge_type,
|
||||
card_no=(payload.card_no or "").strip() or None,
|
||||
gas_station=(payload.gas_station or "").strip() or None,
|
||||
amount=payload.amount,
|
||||
attachment_urls=payload.attachment_urls,
|
||||
apply_id=apply_row.id if apply_row else None,
|
||||
license_plate=license_plate,
|
||||
tenant_no=tenant_no,
|
||||
remark=payload.remark,
|
||||
create_by=user.id,
|
||||
create_dept_id=user.dept_id or "",
|
||||
create_time=now,
|
||||
update_by=user.id,
|
||||
update_time=now,
|
||||
del_flag=0,
|
||||
)
|
||||
db.add(row)
|
||||
if apply_row:
|
||||
apply_row.status = "disbursed"
|
||||
apply_row.update_by = user.id
|
||||
apply_row.update_time = now
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return record_to_read(row, db)
|
||||
|
||||
|
||||
def update_record(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
record_id: str,
|
||||
payload: VehicleRechargeRecordUpdate,
|
||||
) -> VehicleRechargeRecordRead:
|
||||
row = _get_record_row(db, tenant_no, record_id, user)
|
||||
old_apply_id = row.apply_id
|
||||
apply_row, license_plate, recharge_type = _resolve_apply_for_record(
|
||||
db, tenant_no, payload, user, current_record_id=record_id
|
||||
)
|
||||
now = datetime.now()
|
||||
|
||||
if old_apply_id and old_apply_id != (apply_row.id if apply_row else None):
|
||||
old_apply = (
|
||||
db.query(PlVehicleRechargeApply)
|
||||
.filter(
|
||||
PlVehicleRechargeApply.id == old_apply_id,
|
||||
PlVehicleRechargeApply.tenant_no == tenant_no,
|
||||
PlVehicleRechargeApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if old_apply:
|
||||
old_apply.status = "pending"
|
||||
old_apply.update_by = user.id
|
||||
old_apply.update_time = now
|
||||
|
||||
row.recharge_date = payload.recharge_date
|
||||
row.recharge_type = recharge_type
|
||||
row.card_no = (payload.card_no or "").strip() or None
|
||||
row.gas_station = (payload.gas_station or "").strip() or None
|
||||
row.amount = payload.amount
|
||||
row.attachment_urls = payload.attachment_urls
|
||||
row.apply_id = apply_row.id if apply_row else None
|
||||
row.license_plate = license_plate
|
||||
row.remark = payload.remark
|
||||
row.update_by = user.id
|
||||
row.update_time = now
|
||||
|
||||
if apply_row:
|
||||
apply_row.status = "disbursed"
|
||||
apply_row.update_by = user.id
|
||||
apply_row.update_time = now
|
||||
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return record_to_read(row, db)
|
||||
|
||||
|
||||
def delete_records(
|
||||
db: Session, tenant_no: str, user: DbUserContext, record_ids: list[str]
|
||||
) -> None:
|
||||
now = datetime.now()
|
||||
for record_id in record_ids:
|
||||
row = _get_record_row(db, tenant_no, record_id, user)
|
||||
apply_id = row.apply_id
|
||||
row.del_flag = 1
|
||||
row.update_by = user.id
|
||||
row.update_time = now
|
||||
if apply_id:
|
||||
apply = (
|
||||
db.query(PlVehicleRechargeApply)
|
||||
.filter(
|
||||
PlVehicleRechargeApply.id == apply_id,
|
||||
PlVehicleRechargeApply.tenant_no == tenant_no,
|
||||
PlVehicleRechargeApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if apply and apply.status == "disbursed":
|
||||
apply.status = "pending"
|
||||
apply.update_by = user.id
|
||||
apply.update_time = now
|
||||
db.commit()
|
||||
@@ -0,0 +1,594 @@
|
||||
"""车辆尿素业务逻辑"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.db_auth import DbUserContext
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import SysUser
|
||||
from app.models.pl_models import PlVehicle, PlVehicleUreaApply, PlVehicleUreaDisburse
|
||||
from app.schemas.pl.vehicle_urea import (
|
||||
VehicleUreaApplyCreate,
|
||||
VehicleUreaApplyRead,
|
||||
VehicleUreaApplyUpdate,
|
||||
VehicleUreaDisburseBoardItem,
|
||||
VehicleUreaDisburseCreate,
|
||||
VehicleUreaDisburseRead,
|
||||
VehicleUreaDisburseUpdate,
|
||||
)
|
||||
from app.services.pl_data_scope import apply_pl_scope, assert_pl_row_in_scope
|
||||
|
||||
|
||||
def _generate_seq_no(db: Session, model, column_attr: str, prefix: str) -> str:
|
||||
column = getattr(model, column_attr)
|
||||
last = (
|
||||
db.query(model)
|
||||
.filter(column.like(f"{prefix}%"))
|
||||
.order_by(column.desc())
|
||||
.first()
|
||||
)
|
||||
seq = int(getattr(last, column_attr)[-6:]) + 1 if last else 1
|
||||
return f"{prefix}{seq:06d}"
|
||||
|
||||
|
||||
def generate_apply_no(db: Session) -> str:
|
||||
return _generate_seq_no(
|
||||
db, PlVehicleUreaApply, "apply_no", f"SXNS{date.today():%Y%m%d}"
|
||||
)
|
||||
|
||||
|
||||
def generate_disburse_no(db: Session) -> str:
|
||||
return _generate_seq_no(
|
||||
db, PlVehicleUreaDisburse, "disburse_no", f"SXUF{date.today():%Y%m%d}"
|
||||
)
|
||||
|
||||
|
||||
def _resolve_user_name(db: Session, user_id: Optional[str]) -> Optional[str]:
|
||||
if not user_id:
|
||||
return None
|
||||
row = (
|
||||
db.query(SysUser)
|
||||
.filter(SysUser.id == user_id, SysUser.del_flag == 0)
|
||||
.first()
|
||||
)
|
||||
return row.nick_name or row.user_no if row else None
|
||||
|
||||
|
||||
def _assert_vehicle_exists(db: Session, tenant_no: str, license_plate: str) -> None:
|
||||
exists = (
|
||||
db.query(PlVehicle.id)
|
||||
.filter(
|
||||
PlVehicle.tenant_no == tenant_no,
|
||||
PlVehicle.license_plate == license_plate,
|
||||
PlVehicle.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not exists:
|
||||
raise HTTPException(status_code=400, detail="车牌号不存在")
|
||||
|
||||
|
||||
def _resolve_user_id(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user_id: Optional[str],
|
||||
user: DbUserContext,
|
||||
*,
|
||||
field_label: str = "用户",
|
||||
) -> str:
|
||||
target = (user_id or "").strip() or user.id
|
||||
row = (
|
||||
db.query(SysUser.id)
|
||||
.filter(
|
||||
SysUser.id == target,
|
||||
SysUser.tenant_no == tenant_no,
|
||||
SysUser.del_flag == 0,
|
||||
SysUser.status == "1",
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(status_code=400, detail=f"{field_label}不存在或已停用")
|
||||
return target
|
||||
|
||||
|
||||
def _validate_apply_dates(apply_date: date, warehouse_in_date: date) -> None:
|
||||
if apply_date > warehouse_in_date:
|
||||
raise HTTPException(status_code=400, detail="申请时间不能晚于入库时间")
|
||||
|
||||
|
||||
def _get_apply_row(
|
||||
db: Session, tenant_no: str, apply_id: str, user: DbUserContext
|
||||
) -> PlVehicleUreaApply:
|
||||
row = (
|
||||
db.query(PlVehicleUreaApply)
|
||||
.filter(
|
||||
PlVehicleUreaApply.id == apply_id,
|
||||
PlVehicleUreaApply.tenant_no == tenant_no,
|
||||
PlVehicleUreaApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="申请单不存在")
|
||||
assert_pl_row_in_scope(db, row, PlVehicleUreaApply, user, tenant_no)
|
||||
return row
|
||||
|
||||
|
||||
def _get_disburse_row(
|
||||
db: Session, tenant_no: str, disburse_id: str, user: DbUserContext
|
||||
) -> PlVehicleUreaDisburse:
|
||||
row = (
|
||||
db.query(PlVehicleUreaDisburse)
|
||||
.filter(
|
||||
PlVehicleUreaDisburse.id == disburse_id,
|
||||
PlVehicleUreaDisburse.tenant_no == tenant_no,
|
||||
PlVehicleUreaDisburse.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="发放登记不存在")
|
||||
assert_pl_row_in_scope(db, row, PlVehicleUreaDisburse, user, tenant_no)
|
||||
return row
|
||||
|
||||
|
||||
def _has_active_disburse(db: Session, tenant_no: str, apply_id: str) -> bool:
|
||||
return (
|
||||
db.query(PlVehicleUreaDisburse.id)
|
||||
.filter(
|
||||
PlVehicleUreaDisburse.apply_id == apply_id,
|
||||
PlVehicleUreaDisburse.tenant_no == tenant_no,
|
||||
PlVehicleUreaDisburse.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
is not None
|
||||
)
|
||||
|
||||
|
||||
def apply_to_read(row: PlVehicleUreaApply, db: Session) -> VehicleUreaApplyRead:
|
||||
return VehicleUreaApplyRead(
|
||||
id=row.id,
|
||||
applyNo=row.apply_no,
|
||||
licensePlate=row.license_plate,
|
||||
applicantId=row.applicant_id,
|
||||
applicantName=_resolve_user_name(db, row.applicant_id),
|
||||
applyDate=row.apply_date,
|
||||
warehouseInDate=row.warehouse_in_date,
|
||||
unit=row.unit,
|
||||
quantity=row.quantity,
|
||||
applyAmount=row.apply_amount,
|
||||
attachmentUrls=row.attachment_urls,
|
||||
status=row.status,
|
||||
remark=row.remark,
|
||||
createTime=row.create_time,
|
||||
updateTime=row.update_time,
|
||||
)
|
||||
|
||||
|
||||
def disburse_to_read(
|
||||
row: PlVehicleUreaDisburse, db: Session, apply_no: Optional[str] = None
|
||||
) -> VehicleUreaDisburseRead:
|
||||
if apply_no is None and row.apply_id:
|
||||
apply = (
|
||||
db.query(PlVehicleUreaApply.apply_no)
|
||||
.filter(
|
||||
PlVehicleUreaApply.id == row.apply_id,
|
||||
PlVehicleUreaApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
apply_no = apply.apply_no if apply else None
|
||||
return VehicleUreaDisburseRead(
|
||||
id=row.id,
|
||||
disburseNo=row.disburse_no,
|
||||
applyId=row.apply_id,
|
||||
applyNo=apply_no,
|
||||
licensePlate=row.license_plate,
|
||||
amount=row.amount,
|
||||
handlerId=row.handler_id,
|
||||
handlerName=_resolve_user_name(db, row.handler_id),
|
||||
disburseDate=row.disburse_date,
|
||||
attachmentUrls=row.attachment_urls,
|
||||
remark=row.remark,
|
||||
createBy=row.create_by,
|
||||
createByName=_resolve_user_name(db, row.create_by),
|
||||
createTime=row.create_time,
|
||||
updateTime=row.update_time,
|
||||
)
|
||||
|
||||
|
||||
def _resolve_apply_for_disburse(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
payload: VehicleUreaDisburseCreate | VehicleUreaDisburseUpdate,
|
||||
user: DbUserContext,
|
||||
*,
|
||||
current_disburse_id: Optional[str] = None,
|
||||
) -> tuple[Optional[PlVehicleUreaApply], str]:
|
||||
license_plate = (payload.license_plate or "").strip()
|
||||
|
||||
if payload.apply_id:
|
||||
apply = _get_apply_row(db, tenant_no, payload.apply_id, user)
|
||||
if apply.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待发放申请可关联")
|
||||
existing = (
|
||||
db.query(PlVehicleUreaDisburse)
|
||||
.filter(
|
||||
PlVehicleUreaDisburse.apply_id == apply.id,
|
||||
PlVehicleUreaDisburse.tenant_no == tenant_no,
|
||||
PlVehicleUreaDisburse.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if existing and existing.id != current_disburse_id:
|
||||
raise HTTPException(status_code=400, detail="该申请单已被关联")
|
||||
return apply, apply.license_plate
|
||||
|
||||
if not license_plate:
|
||||
raise HTTPException(status_code=400, detail="请填写车牌号或关联申请单")
|
||||
_assert_vehicle_exists(db, tenant_no, license_plate)
|
||||
return None, license_plate
|
||||
|
||||
|
||||
def list_applies(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
*,
|
||||
q: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
apply_date_from: Optional[date] = None,
|
||||
apply_date_to: Optional[date] = None,
|
||||
applicant_id: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[VehicleUreaApplyRead], int]:
|
||||
query = db.query(PlVehicleUreaApply).filter(
|
||||
PlVehicleUreaApply.tenant_no == tenant_no,
|
||||
PlVehicleUreaApply.del_flag == 0,
|
||||
)
|
||||
if q:
|
||||
query = query.filter(
|
||||
or_(
|
||||
PlVehicleUreaApply.apply_no.contains(q),
|
||||
PlVehicleUreaApply.license_plate.contains(q),
|
||||
)
|
||||
)
|
||||
if status:
|
||||
query = query.filter(PlVehicleUreaApply.status == status)
|
||||
if apply_date_from:
|
||||
query = query.filter(PlVehicleUreaApply.apply_date >= apply_date_from)
|
||||
if apply_date_to:
|
||||
query = query.filter(PlVehicleUreaApply.apply_date <= apply_date_to)
|
||||
if applicant_id:
|
||||
query = query.filter(PlVehicleUreaApply.applicant_id == applicant_id)
|
||||
|
||||
query = apply_pl_scope(query, PlVehicleUreaApply, user, db, tenant_no)
|
||||
total = query.count()
|
||||
rows = (
|
||||
query.order_by(PlVehicleUreaApply.create_time.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return [apply_to_read(row, db) for row in rows], total
|
||||
|
||||
|
||||
def get_apply(
|
||||
db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
||||
) -> VehicleUreaApplyRead:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def create_apply(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
payload: VehicleUreaApplyCreate,
|
||||
) -> VehicleUreaApplyRead:
|
||||
license_plate = payload.license_plate.strip()
|
||||
_assert_vehicle_exists(db, tenant_no, license_plate)
|
||||
_validate_apply_dates(payload.apply_date, payload.warehouse_in_date)
|
||||
applicant_id = _resolve_user_id(db, tenant_no, payload.applicant_id, user, field_label="申请人")
|
||||
unit = (payload.unit or "").strip() or "吨桶"
|
||||
now = datetime.now()
|
||||
row = PlVehicleUreaApply(
|
||||
id=generate_uuid(),
|
||||
apply_no=generate_apply_no(db),
|
||||
license_plate=license_plate,
|
||||
applicant_id=applicant_id,
|
||||
apply_date=payload.apply_date,
|
||||
warehouse_in_date=payload.warehouse_in_date,
|
||||
unit=unit,
|
||||
quantity=payload.quantity,
|
||||
apply_amount=payload.apply_amount,
|
||||
attachment_urls=payload.attachment_urls,
|
||||
status="pending",
|
||||
tenant_no=tenant_no,
|
||||
remark=payload.remark,
|
||||
create_by=user.id,
|
||||
create_dept_id=user.dept_id or "",
|
||||
create_time=now,
|
||||
update_by=user.id,
|
||||
update_time=now,
|
||||
del_flag=0,
|
||||
)
|
||||
db.add(row)
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def update_apply(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
apply_id: str,
|
||||
payload: VehicleUreaApplyUpdate,
|
||||
) -> VehicleUreaApplyRead:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
if row.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待发放申请可编辑")
|
||||
license_plate = payload.license_plate.strip()
|
||||
_assert_vehicle_exists(db, tenant_no, license_plate)
|
||||
_validate_apply_dates(payload.apply_date, payload.warehouse_in_date)
|
||||
unit = (payload.unit or "").strip() or "吨桶"
|
||||
row.license_plate = license_plate
|
||||
row.applicant_id = _resolve_user_id(db, tenant_no, payload.applicant_id, user, field_label="申请人")
|
||||
row.apply_date = payload.apply_date
|
||||
row.warehouse_in_date = payload.warehouse_in_date
|
||||
row.unit = unit
|
||||
row.quantity = payload.quantity
|
||||
row.apply_amount = payload.apply_amount
|
||||
row.attachment_urls = payload.attachment_urls
|
||||
row.remark = payload.remark
|
||||
row.update_by = user.id
|
||||
row.update_time = datetime.now()
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def cancel_apply(
|
||||
db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
||||
) -> VehicleUreaApplyRead:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
if row.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待发放申请可作废")
|
||||
row.status = "cancelled"
|
||||
row.update_by = user.id
|
||||
row.update_time = datetime.now()
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return apply_to_read(row, db)
|
||||
|
||||
|
||||
def delete_applies(
|
||||
db: Session, tenant_no: str, user: DbUserContext, apply_ids: list[str]
|
||||
) -> None:
|
||||
for apply_id in apply_ids:
|
||||
row = _get_apply_row(db, tenant_no, apply_id, user)
|
||||
if row.status != "pending":
|
||||
raise HTTPException(status_code=400, detail="仅待发放申请可删除")
|
||||
if _has_active_disburse(db, tenant_no, apply_id):
|
||||
raise HTTPException(status_code=400, detail="已关联发放记录的申请不可删除")
|
||||
row.del_flag = 1
|
||||
row.update_by = user.id
|
||||
row.update_time = datetime.now()
|
||||
db.commit()
|
||||
|
||||
|
||||
def board_item_to_read(
|
||||
apply_row: PlVehicleUreaApply,
|
||||
disburse_row: Optional[PlVehicleUreaDisburse],
|
||||
db: Session,
|
||||
) -> VehicleUreaDisburseBoardItem:
|
||||
return VehicleUreaDisburseBoardItem(
|
||||
applyId=apply_row.id,
|
||||
applyNo=apply_row.apply_no,
|
||||
licensePlate=apply_row.license_plate,
|
||||
applyDate=apply_row.apply_date,
|
||||
warehouseInDate=apply_row.warehouse_in_date,
|
||||
unit=apply_row.unit,
|
||||
quantity=apply_row.quantity,
|
||||
applyAmount=apply_row.apply_amount,
|
||||
applicantName=_resolve_user_name(db, apply_row.applicant_id),
|
||||
applyStatus=apply_row.status,
|
||||
applyRemark=apply_row.remark,
|
||||
disburseId=disburse_row.id if disburse_row else None,
|
||||
disburseNo=disburse_row.disburse_no if disburse_row else None,
|
||||
disburseDate=disburse_row.disburse_date if disburse_row else None,
|
||||
amount=disburse_row.amount if disburse_row else None,
|
||||
handlerName=_resolve_user_name(db, disburse_row.handler_id) if disburse_row else None,
|
||||
disburseRemark=disburse_row.remark if disburse_row else None,
|
||||
)
|
||||
|
||||
|
||||
def list_disburse_board(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
*,
|
||||
q: Optional[str] = None,
|
||||
apply_status: Optional[str] = None,
|
||||
apply_date_from: Optional[date] = None,
|
||||
apply_date_to: Optional[date] = None,
|
||||
disburse_date_from: Optional[date] = None,
|
||||
disburse_date_to: Optional[date] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[VehicleUreaDisburseBoardItem], int]:
|
||||
query = (
|
||||
db.query(PlVehicleUreaApply, PlVehicleUreaDisburse)
|
||||
.outerjoin(
|
||||
PlVehicleUreaDisburse,
|
||||
and_(
|
||||
PlVehicleUreaDisburse.apply_id == PlVehicleUreaApply.id,
|
||||
PlVehicleUreaDisburse.tenant_no == tenant_no,
|
||||
PlVehicleUreaDisburse.del_flag == 0,
|
||||
),
|
||||
)
|
||||
.filter(
|
||||
PlVehicleUreaApply.tenant_no == tenant_no,
|
||||
PlVehicleUreaApply.del_flag == 0,
|
||||
)
|
||||
)
|
||||
if q:
|
||||
query = query.filter(
|
||||
or_(
|
||||
PlVehicleUreaApply.apply_no.contains(q),
|
||||
PlVehicleUreaApply.license_plate.contains(q),
|
||||
func.coalesce(PlVehicleUreaDisburse.disburse_no, "").contains(q),
|
||||
)
|
||||
)
|
||||
if apply_status:
|
||||
query = query.filter(PlVehicleUreaApply.status == apply_status)
|
||||
if apply_date_from:
|
||||
query = query.filter(PlVehicleUreaApply.apply_date >= apply_date_from)
|
||||
if apply_date_to:
|
||||
query = query.filter(PlVehicleUreaApply.apply_date <= apply_date_to)
|
||||
if disburse_date_from:
|
||||
query = query.filter(PlVehicleUreaDisburse.disburse_date >= disburse_date_from)
|
||||
if disburse_date_to:
|
||||
query = query.filter(PlVehicleUreaDisburse.disburse_date <= disburse_date_to)
|
||||
|
||||
query = apply_pl_scope(query, PlVehicleUreaApply, user, db, tenant_no)
|
||||
total = query.count()
|
||||
rows = (
|
||||
query.order_by(PlVehicleUreaApply.create_time.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return [
|
||||
board_item_to_read(apply_row, disburse_row, db)
|
||||
for apply_row, disburse_row in rows
|
||||
], total
|
||||
|
||||
|
||||
def get_disburse(
|
||||
db: Session, tenant_no: str, user: DbUserContext, disburse_id: str
|
||||
) -> VehicleUreaDisburseRead:
|
||||
row = _get_disburse_row(db, tenant_no, disburse_id, user)
|
||||
return disburse_to_read(row, db)
|
||||
|
||||
|
||||
def create_disburse(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
payload: VehicleUreaDisburseCreate,
|
||||
) -> VehicleUreaDisburseRead:
|
||||
apply_row, license_plate = _resolve_apply_for_disburse(db, tenant_no, payload, user)
|
||||
handler_id = _resolve_user_id(db, tenant_no, payload.handler_id, user, field_label="处理人")
|
||||
now = datetime.now()
|
||||
row = PlVehicleUreaDisburse(
|
||||
id=generate_uuid(),
|
||||
disburse_no=generate_disburse_no(db),
|
||||
apply_id=apply_row.id if apply_row else None,
|
||||
license_plate=license_plate,
|
||||
amount=payload.amount,
|
||||
handler_id=handler_id,
|
||||
disburse_date=payload.disburse_date,
|
||||
attachment_urls=payload.attachment_urls,
|
||||
tenant_no=tenant_no,
|
||||
remark=payload.remark,
|
||||
create_by=user.id,
|
||||
create_dept_id=user.dept_id or "",
|
||||
create_time=now,
|
||||
update_by=user.id,
|
||||
update_time=now,
|
||||
del_flag=0,
|
||||
)
|
||||
db.add(row)
|
||||
if apply_row:
|
||||
apply_row.status = "disbursed"
|
||||
apply_row.update_by = user.id
|
||||
apply_row.update_time = now
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return disburse_to_read(row, db)
|
||||
|
||||
|
||||
def update_disburse(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
user: DbUserContext,
|
||||
disburse_id: str,
|
||||
payload: VehicleUreaDisburseUpdate,
|
||||
) -> VehicleUreaDisburseRead:
|
||||
row = _get_disburse_row(db, tenant_no, disburse_id, user)
|
||||
old_apply_id = row.apply_id
|
||||
apply_row, license_plate = _resolve_apply_for_disburse(
|
||||
db, tenant_no, payload, user, current_disburse_id=disburse_id
|
||||
)
|
||||
handler_id = _resolve_user_id(db, tenant_no, payload.handler_id, user, field_label="处理人")
|
||||
now = datetime.now()
|
||||
|
||||
if old_apply_id and old_apply_id != (apply_row.id if apply_row else None):
|
||||
old_apply = (
|
||||
db.query(PlVehicleUreaApply)
|
||||
.filter(
|
||||
PlVehicleUreaApply.id == old_apply_id,
|
||||
PlVehicleUreaApply.tenant_no == tenant_no,
|
||||
PlVehicleUreaApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if old_apply:
|
||||
old_apply.status = "pending"
|
||||
old_apply.update_by = user.id
|
||||
old_apply.update_time = now
|
||||
|
||||
row.apply_id = apply_row.id if apply_row else None
|
||||
row.license_plate = license_plate
|
||||
row.amount = payload.amount
|
||||
row.handler_id = handler_id
|
||||
row.disburse_date = payload.disburse_date
|
||||
row.attachment_urls = payload.attachment_urls
|
||||
row.remark = payload.remark
|
||||
row.update_by = user.id
|
||||
row.update_time = now
|
||||
|
||||
if apply_row:
|
||||
apply_row.status = "disbursed"
|
||||
apply_row.update_by = user.id
|
||||
apply_row.update_time = now
|
||||
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return disburse_to_read(row, db)
|
||||
|
||||
|
||||
def delete_disburses(
|
||||
db: Session, tenant_no: str, user: DbUserContext, disburse_ids: list[str]
|
||||
) -> None:
|
||||
now = datetime.now()
|
||||
for disburse_id in disburse_ids:
|
||||
row = _get_disburse_row(db, tenant_no, disburse_id, user)
|
||||
apply_id = row.apply_id
|
||||
row.del_flag = 1
|
||||
row.update_by = user.id
|
||||
row.update_time = now
|
||||
if apply_id:
|
||||
apply = (
|
||||
db.query(PlVehicleUreaApply)
|
||||
.filter(
|
||||
PlVehicleUreaApply.id == apply_id,
|
||||
PlVehicleUreaApply.tenant_no == tenant_no,
|
||||
PlVehicleUreaApply.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if apply and apply.status == "disbursed":
|
||||
apply.status = "pending"
|
||||
apply.update_by = user.id
|
||||
apply.update_time = now
|
||||
db.commit()
|
||||
@@ -84,22 +84,28 @@
|
||||
"82": "Community 82",
|
||||
"83": "Community 83",
|
||||
"84": "Community 84",
|
||||
"85": "Community 85",
|
||||
"86": "Community 86",
|
||||
"87": "Community 87",
|
||||
"88": "Community 88",
|
||||
"89": "Community 89",
|
||||
"90": "Community 90",
|
||||
"91": "Community 91",
|
||||
"92": "Community 92",
|
||||
"93": "Community 93",
|
||||
"94": "Community 94",
|
||||
"95": "Community 95",
|
||||
"96": "Community 96",
|
||||
"97": "Community 97",
|
||||
"98": "Community 98",
|
||||
"99": "Community 99",
|
||||
"100": "Community 100",
|
||||
"101": "Community 101",
|
||||
"102": "Community 102",
|
||||
"103": "Community 103",
|
||||
"104": "Community 104",
|
||||
"105": "Community 105",
|
||||
"107": "Community 107",
|
||||
"108": "Community 108",
|
||||
"109": "Community 109",
|
||||
"110": "Community 110",
|
||||
"112": "Community 112",
|
||||
"113": "Community 113",
|
||||
@@ -112,16 +118,24 @@
|
||||
"120": "Community 120",
|
||||
"121": "Community 121",
|
||||
"122": "Community 122",
|
||||
"124": "Community 124",
|
||||
"125": "Community 125",
|
||||
"126": "Community 126",
|
||||
"127": "Community 127",
|
||||
"128": "Community 128",
|
||||
"129": "Community 129",
|
||||
"131": "Community 131",
|
||||
"132": "Community 132",
|
||||
"133": "Community 133",
|
||||
"134": "Community 134",
|
||||
"135": "Community 135",
|
||||
"136": "Community 136",
|
||||
"137": "Community 137",
|
||||
"138": "Community 138",
|
||||
"139": "Community 139",
|
||||
"140": "Community 140",
|
||||
"142": "Community 142",
|
||||
"143": "Community 143",
|
||||
"149": "Community 149"
|
||||
"145": "Community 145",
|
||||
"146": "Community 146",
|
||||
"147": "Community 147"
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
# Graph Report - backend (2026-06-28)
|
||||
# Graph Report - backend (2026-06-29)
|
||||
|
||||
## Corpus Check
|
||||
- 208 files · ~145,658 words
|
||||
- 223 files · ~157,012 words
|
||||
- Verdict: corpus is large enough that graph structure adds value.
|
||||
|
||||
## Summary
|
||||
- 4328 nodes · 31993 edges · 125 communities (121 shown, 4 thin omitted)
|
||||
- Extraction: 50% EXTRACTED · 50% INFERRED · 0% AMBIGUOUS · INFERRED: 16138 edges (avg confidence: 0.5)
|
||||
- 4858 nodes · 34385 edges · 139 communities (138 shown, 1 thin omitted)
|
||||
- Extraction: 50% EXTRACTED · 50% INFERRED · 0% AMBIGUOUS · INFERRED: 17062 edges (avg confidence: 0.5)
|
||||
- Token cost: 0 input · 0 output
|
||||
|
||||
## Graph Freshness
|
||||
- Built from commit: `ee531e9c`
|
||||
- Built from commit: `493f8d67`
|
||||
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
|
||||
- Run `graphify update .` after code changes (no API cost).
|
||||
|
||||
@@ -100,48 +100,62 @@
|
||||
- [[_COMMUNITY_Community 82|Community 82]]
|
||||
- [[_COMMUNITY_Community 83|Community 83]]
|
||||
- [[_COMMUNITY_Community 84|Community 84]]
|
||||
- [[_COMMUNITY_Community 85|Community 85]]
|
||||
- [[_COMMUNITY_Community 86|Community 86]]
|
||||
- [[_COMMUNITY_Community 87|Community 87]]
|
||||
- [[_COMMUNITY_Community 88|Community 88]]
|
||||
- [[_COMMUNITY_Community 89|Community 89]]
|
||||
- [[_COMMUNITY_Community 90|Community 90]]
|
||||
- [[_COMMUNITY_Community 91|Community 91]]
|
||||
- [[_COMMUNITY_Community 92|Community 92]]
|
||||
- [[_COMMUNITY_Community 93|Community 93]]
|
||||
- [[_COMMUNITY_Community 94|Community 94]]
|
||||
- [[_COMMUNITY_Community 95|Community 95]]
|
||||
- [[_COMMUNITY_Community 96|Community 96]]
|
||||
- [[_COMMUNITY_Community 97|Community 97]]
|
||||
- [[_COMMUNITY_Community 98|Community 98]]
|
||||
- [[_COMMUNITY_Community 99|Community 99]]
|
||||
- [[_COMMUNITY_Community 100|Community 100]]
|
||||
- [[_COMMUNITY_Community 101|Community 101]]
|
||||
- [[_COMMUNITY_Community 102|Community 102]]
|
||||
- [[_COMMUNITY_Community 103|Community 103]]
|
||||
- [[_COMMUNITY_Community 104|Community 104]]
|
||||
- [[_COMMUNITY_Community 105|Community 105]]
|
||||
- [[_COMMUNITY_Community 107|Community 107]]
|
||||
- [[_COMMUNITY_Community 108|Community 108]]
|
||||
- [[_COMMUNITY_Community 109|Community 109]]
|
||||
- [[_COMMUNITY_Community 110|Community 110]]
|
||||
- [[_COMMUNITY_Community 112|Community 112]]
|
||||
- [[_COMMUNITY_Community 122|Community 122]]
|
||||
- [[_COMMUNITY_Community 124|Community 124]]
|
||||
- [[_COMMUNITY_Community 125|Community 125]]
|
||||
- [[_COMMUNITY_Community 126|Community 126]]
|
||||
- [[_COMMUNITY_Community 127|Community 127]]
|
||||
- [[_COMMUNITY_Community 128|Community 128]]
|
||||
- [[_COMMUNITY_Community 129|Community 129]]
|
||||
- [[_COMMUNITY_Community 131|Community 131]]
|
||||
- [[_COMMUNITY_Community 132|Community 132]]
|
||||
- [[_COMMUNITY_Community 133|Community 133]]
|
||||
- [[_COMMUNITY_Community 134|Community 134]]
|
||||
- [[_COMMUNITY_Community 135|Community 135]]
|
||||
- [[_COMMUNITY_Community 136|Community 136]]
|
||||
- [[_COMMUNITY_Community 137|Community 137]]
|
||||
- [[_COMMUNITY_Community 138|Community 138]]
|
||||
- [[_COMMUNITY_Community 139|Community 139]]
|
||||
- [[_COMMUNITY_Community 140|Community 140]]
|
||||
- [[_COMMUNITY_Community 143|Community 143]]
|
||||
- [[_COMMUNITY_Community 149|Community 149]]
|
||||
- [[_COMMUNITY_Community 145|Community 145]]
|
||||
- [[_COMMUNITY_Community 146|Community 146]]
|
||||
- [[_COMMUNITY_Community 147|Community 147]]
|
||||
|
||||
## God Nodes (most connected - your core abstractions)
|
||||
1. `Session` - 1123 edges
|
||||
2. `BaseModel` - 383 edges
|
||||
2. `BaseModel` - 412 edges
|
||||
3. `int` - 336 edges
|
||||
4. `Any` - 331 edges
|
||||
5. `get_tenant_no()` - 284 edges
|
||||
6. `success_response()` - 258 edges
|
||||
7. `Session` - 257 edges
|
||||
8. `bool` - 255 edges
|
||||
9. `error_response()` - 243 edges
|
||||
10. `SysUser` - 232 edges
|
||||
5. `get_tenant_no()` - 326 edges
|
||||
6. `SysUser` - 281 edges
|
||||
7. `DbUserContext` - 267 edges
|
||||
8. `success_response()` - 258 edges
|
||||
9. `Session` - 257 edges
|
||||
10. `bool` - 255 edges
|
||||
|
||||
## Surprising Connections (you probably didn't know these)
|
||||
- `bool` --uses--> `DbUserContext` [INFERRED]
|
||||
@@ -155,15 +169,15 @@
|
||||
- `int` --uses--> `LocalStorage` [INFERRED]
|
||||
scripts/run_db_auto_generate_plans.py → app/core/storage.py
|
||||
|
||||
## Communities (125 total, 4 thin omitted)
|
||||
## Communities (139 total, 1 thin omitted)
|
||||
|
||||
### Community 0 - "Community 0"
|
||||
Cohesion: 0.07
|
||||
Nodes (52): DbUserContext, Session, str, PlDocumentReminder, PlInventoryTransaction, PlInventoryWarning, 运输审批记录表 pl_transport_approval, 运费结算表 pl_freight_settlement (+44 more)
|
||||
Cohesion: 0.13
|
||||
Nodes (102): date, DbUserContext, int, Session, str, DocumentReminderRead, DocumentReminderUpdate, InventoryLedgerResponse (+94 more)
|
||||
|
||||
### Community 1 - "Community 1"
|
||||
Cohesion: 0.15
|
||||
Nodes (26): operlog_detail(), operlog_list(), 操作日志分页列表(基于 API 请求日志适配 RuoYi OperLog 结构), 操作日志分页列表(基于 API 请求日志适配 RuoYi OperLog 结构), _user_info(), Session, Any, bool (+18 more)
|
||||
Cohesion: 0.04
|
||||
Nodes (48): 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll (+40 more)
|
||||
|
||||
### Community 2 - "Community 2"
|
||||
Cohesion: 0.36
|
||||
@@ -171,215 +185,215 @@ Nodes (218): Any, bool, str, UploadFile, CRM 线索管理 Pydantic Schemas, CRM
|
||||
|
||||
### Community 3 - "Community 3"
|
||||
Cohesion: 0.07
|
||||
Nodes (169): _apply_default_invoice_info_to_customers(), category_add(), category_delete(), category_tree(), category_update(), clue_assign(), clue_check_convert(), clue_check_duplicate() (+161 more)
|
||||
Nodes (172): _apply_default_invoice_info_to_customers(), category_add(), category_delete(), category_tree(), category_update(), clue_assign(), clue_check_convert(), clue_check_duplicate() (+164 more)
|
||||
|
||||
### Community 4 - "Community 4"
|
||||
Cohesion: 0.09
|
||||
Nodes (149): DbInboundOutbound, DbUserContext, InboundOutboundRead, Session, str, bool, DbPurchasePlan, DbUserContext (+141 more)
|
||||
Cohesion: 0.21
|
||||
Nodes (35): DbInboundOutbound, DbUserContext, Session, str, CreateLinkForPlanPayload, _apply_inventory_product_update(), _apply_inventory_update(), _base_query() (+27 more)
|
||||
|
||||
### Community 5 - "Community 5"
|
||||
Cohesion: 0.08
|
||||
Nodes (36): _error_json(), lifespan(), _mask_secret(), Exception, int, Request, str, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin (+28 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (34): _error_json(), lifespan(), _mask_secret(), Exception, int, Request, str, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin (+26 more)
|
||||
|
||||
### Community 6 - "Community 6"
|
||||
Cohesion: 0.07
|
||||
Nodes (98): attachment_download_url(), AttachmentDownloadUrlBody, create_dept(), create_dict_data(), create_dict_type(), create_menu(), create_post(), create_role_management() (+90 more)
|
||||
Cohesion: 0.12
|
||||
Nodes (62): Any, bool, date, int, Session, str, Any, bool (+54 more)
|
||||
|
||||
### Community 7 - "Community 7"
|
||||
Cohesion: 0.17
|
||||
Nodes (34): Any, bool, int, Session, str, SysUser, create_user(), get_user() (+26 more)
|
||||
Cohesion: 0.14
|
||||
Nodes (39): Any, bool, int, Session, str, SysUser, create_user(), get_user() (+31 more)
|
||||
|
||||
### Community 8 - "Community 8"
|
||||
Cohesion: 0.43
|
||||
Nodes (13): Any, Session, str, _customer_base_query(), get_follow_statistics(), get_industry_statistics(), get_level_statistics(), get_overview_statistics() (+5 more)
|
||||
Cohesion: 0.16
|
||||
Nodes (33): Any, bool, int, Session, str, Any, Session, str (+25 more)
|
||||
|
||||
### Community 9 - "Community 9"
|
||||
Cohesion: 0.23
|
||||
Nodes (10): Any, bool, int, Session, str, camel_to_snake(), CRUDBase, get_model_attr() (+2 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (22): bool, bool, Any, bool, int, Session, str, bool (+14 more)
|
||||
|
||||
### Community 10 - "Community 10"
|
||||
Cohesion: 0.38
|
||||
Nodes (15): Any, bool, int, Session, str, create_post(), get_post(), list_posts() (+7 more)
|
||||
Cohesion: 0.17
|
||||
Nodes (20): Any, int, str, get_oper_log_meta(), OperLogMeta, 请求级操作日志元数据(ContextVar), 供异步落库线程使用,避免 ContextVar 跨线程丢失。, set_oper_log_meta() (+12 more)
|
||||
|
||||
### Community 11 - "Community 11"
|
||||
Cohesion: 0.17
|
||||
Nodes (33): Any, bool, BxUserContext, BxWorkflowNode, float, int, ReimburseApplyOut, Session (+25 more)
|
||||
Cohesion: 0.16
|
||||
Nodes (36): Any, bool, BxUserContext, BxWorkflowNode, float, int, ReimburseApplyOut, Session (+28 more)
|
||||
|
||||
### Community 12 - "Community 12"
|
||||
Cohesion: 0.16
|
||||
Nodes (39): DbUserContext, int, Session, str, FreightSettlementApprovalActionBody, FreightSettlementRead, approve_settlement(), approve_settlement_legacy() (+31 more)
|
||||
Cohesion: 0.13
|
||||
Nodes (79): date, DbUserContext, int, Session, str, VehicleUreaApplyCreate, VehicleUreaApplyRead, VehicleUreaApplyUpdate (+71 more)
|
||||
|
||||
### Community 13 - "Community 13"
|
||||
Cohesion: 0.10
|
||||
Nodes (17): Exception, bool, bytes, int, str, BytesIO, BaseStorage, convert_heic_to_jpeg() (+9 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (17): Exception, bytes, int, Path, str, BytesIO, BaseStorage, convert_heic_to_jpeg() (+9 more)
|
||||
|
||||
### Community 14 - "Community 14"
|
||||
Cohesion: 0.09
|
||||
Nodes (55): Any, bool, datetime, int, Session, str, _advance_contract_after_approval(), approve_contract() (+47 more)
|
||||
Nodes (61): Any, bool, CrmContract, datetime, int, Query, Session, str (+53 more)
|
||||
|
||||
### Community 15 - "Community 15"
|
||||
Cohesion: 0.18
|
||||
Nodes (24): bool, date, datetime, int, Session, str, ReimburseApplyOut, allocate_reimburse_title_seq() (+16 more)
|
||||
Cohesion: 0.14
|
||||
Nodes (28): str, bool, date, datetime, int, Session, str, filter_current_cycle_approvals() (+20 more)
|
||||
|
||||
### Community 16 - "Community 16"
|
||||
Cohesion: 0.02
|
||||
Nodes (175): CRM 报价单 API 路由 — 挂接到 crm.router, str, str, CRM 合同 Pydantic Schemas — 对照 CrmContractBo / CrmContractVo, CRM 客户管理 Pydantic Schemas, CRM 报价单 Pydantic Schemas, BaseModel, AdvanceApplyCreate (+167 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (40): int, LeaveCreate, LeaveUpdate, AttachmentUploadResponse, DeleteInput, IdInput, PaginationParams, PaginationResult (+32 more)
|
||||
|
||||
### Community 17 - "Community 17"
|
||||
Cohesion: 0.18
|
||||
Nodes (50): Any, CRM 商机 CRUD(主表 + 阶段/赢输单/统计/看板/漏斗), CrmOpportunity, date, datetime, Decimal, int, Session (+42 more)
|
||||
Cohesion: 0.19
|
||||
Nodes (50): Any, Any, CRM 商机 CRUD(主表 + 阶段/赢输单/统计/看板/漏斗), CrmOpportunity, date, datetime, Decimal, int (+42 more)
|
||||
|
||||
### Community 18 - "Community 18"
|
||||
Cohesion: 0.13
|
||||
Nodes (27): Any, bool, BxUserContext, BxWorkflowNode, float, int, Session, str (+19 more)
|
||||
Cohesion: 0.15
|
||||
Nodes (24): Any, bool, BxUserContext, BxWorkflowNode, float, int, Session, str (+16 more)
|
||||
|
||||
### Community 19 - "Community 19"
|
||||
Cohesion: 0.03
|
||||
Nodes (161): register_quote_routes(), Any, bool, int, Session, str, Any, bool (+153 more)
|
||||
Nodes (182): register_quote_routes(), Any, bool, int, Session, str, Any, bool (+174 more)
|
||||
|
||||
### Community 20 - "Community 20"
|
||||
Cohesion: 0.33
|
||||
Nodes (30): AdvanceApplyCreate, AdvanceApplyOut, AdvanceApplyUpdate, AdvanceApprovalCreate, BxUserContext, int, Session, str (+22 more)
|
||||
Cohesion: 0.35
|
||||
Nodes (30): BxUserContext, int, Session, str, approve_reimburse(), approved_by_me(), _build_details_data(), create_reimburse() (+22 more)
|
||||
|
||||
### Community 21 - "Community 21"
|
||||
Cohesion: 0.07
|
||||
Nodes (113): DbUserContext, 运输计划管理 - 基础数据 API(承运商、车辆、人员), PlVehicle, Session, str, VehicleCreate, VehicleRead, VehicleUpdate (+105 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (95): DbUserContext, 运输计划管理 - 基础数据 API(承运商、车辆、人员), PlVehicle, Session, str, VehicleRead, date, 运输计划管理 - 基础数据 Schema(承运商、车辆、人员、证书) (+87 more)
|
||||
|
||||
### Community 22 - "Community 22"
|
||||
Cohesion: 0.41
|
||||
Nodes (13): BxUserContext, Session, str, UploadFile, bind_biz(), delete_attachment(), download_file(), list_by_biz() (+5 more)
|
||||
Cohesion: 0.22
|
||||
Nodes (29): Any, int, Session, str, create_instance(), create_process_def(), create_task(), get_all_tasks_for_business() (+21 more)
|
||||
|
||||
### Community 23 - "Community 23"
|
||||
Cohesion: 0.24
|
||||
Nodes (36): AdvanceIssueRequest, BxUserContext, int, ReimburseApplyOut, Session, str, datetime, str (+28 more)
|
||||
Cohesion: 0.19
|
||||
Nodes (40): AdvanceIssueRequest, BxUserContext, int, ReimburseApplyOut, Session, str, str, BatchOffsetRequest (+32 more)
|
||||
|
||||
### Community 24 - "Community 24"
|
||||
Cohesion: 0.11
|
||||
Nodes (36): Session, str, Any, bool, int, Session, str, Session (+28 more)
|
||||
Cohesion: 0.14
|
||||
Nodes (84): date, DbUserContext, int, Session, str, VehicleMaintenanceApplyCreate, VehicleMaintenanceApplyRead, VehicleMaintenanceApplyReminder (+76 more)
|
||||
|
||||
### Community 25 - "Community 25"
|
||||
Cohesion: 0.16
|
||||
Nodes (12): float, int, str, 微信消息通知服务 支持微信小程序模板消息推送 使用方式: 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 (+4 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (98): attachment_download_url(), AttachmentDownloadUrlBody, create_dept(), create_dict_data(), create_dict_type(), create_menu(), create_post(), create_role_management() (+90 more)
|
||||
|
||||
### Community 26 - "Community 26"
|
||||
Cohesion: 0.13
|
||||
Nodes (15): Any, bool, int, str, get_wechat_pay_client(), get_wechat_pay_v2(), 微信支付工具模块 支持微信支付 V3 API 文档参考: https://pay.weixin.qq.com/docs/v3/apis/index.html, 统一下单接口 (V3) Args: out_trade_no: 商户订单号 total_amo (+7 more)
|
||||
Cohesion: 0.21
|
||||
Nodes (18): bool, int, Redis, Request, str, _claims_from_auth(), _coerce_post_method_override_to_get(), _get_redis_client() (+10 more)
|
||||
|
||||
### Community 27 - "Community 27"
|
||||
Cohesion: 0.12
|
||||
Nodes (54): Any, bool, int, Session, str, Any, bool, CRM 线索 CRUD(线索主表 + 转化/分配/无效/等级操作) (+46 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (57): Any, bool, int, Session, str, Any, bool, CRM 线索 CRUD(线索主表 + 转化/分配/无效/等级操作) (+49 more)
|
||||
|
||||
### Community 28 - "Community 28"
|
||||
Cohesion: 0.09
|
||||
Nodes (41): 运输计划管理 - 基础数据 Schema(承运商、车辆、人员、证书), CarrierBase, CarrierCreate, CarrierRead, CarrierUpdate, DriverBase, DriverCreate, DriverRead (+33 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (40): date, int, Session, str, CertRead, CertRow, PlCert, PlDocumentReminder (+32 more)
|
||||
|
||||
### Community 29 - "Community 29"
|
||||
Cohesion: 0.10
|
||||
Nodes (36): Any, bool, CRM 供方信息 CRUD — 对照 CrmSupplierInfoServiceImpl.java, int, Session, str, generate_contract_no(), Java: generateContractNo — {供方编码}/X-JT + yyyyMMdd + 3位流水 (+28 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (43): invoice_info_list(), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list (+35 more)
|
||||
|
||||
### Community 30 - "Community 30"
|
||||
Cohesion: 0.23
|
||||
Nodes (46): BxUserContext, int, Session, str, BxUserContext, Session, str, ApiResponse (+38 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (43): 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove (+35 more)
|
||||
|
||||
### Community 31 - "Community 31"
|
||||
Cohesion: 0.34
|
||||
Nodes (19): int, PlTransport, Session, str, UploadFile, _build_public_page(), _format_file_size(), _format_plan_date() (+11 more)
|
||||
|
||||
### Community 32 - "Community 32"
|
||||
Cohesion: 0.12
|
||||
Nodes (17): invoice_info_list(), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价) (+9 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (96): date, DbUserContext, int, Session, str, VehicleRechargeApplyCreate, VehicleRechargeApplyRead, VehicleRechargeApplyUpdate (+88 more)
|
||||
|
||||
### Community 33 - "Community 33"
|
||||
Cohesion: 0.12
|
||||
Nodes (60): Any, bool, int, Session, str, Any, bool, CRM 客户 CRUD(客户主表 + 转移/成交/公海池操作) (+52 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (55): pool_rule_toggle(), Any, int, Session, str, Any, bool, CRM 公海池规则 CRUD + 规则执行逻辑 (+47 more)
|
||||
|
||||
### Community 34 - "Community 34"
|
||||
Cohesion: 0.23
|
||||
Nodes (28): Engine, build_id_maps(), clip_text(), count_rows(), export_mapping(), fetch_rows(), legacy_id_to_str(), main() (+20 more)
|
||||
Cohesion: 0.22
|
||||
Nodes (29): Engine, Path, build_id_maps(), clip_text(), count_rows(), export_mapping(), fetch_rows(), legacy_id_to_str() (+21 more)
|
||||
|
||||
### Community 35 - "Community 35"
|
||||
Cohesion: 0.38
|
||||
Nodes (15): Any, bool, int, Session, str, create_site(), get_site(), list_sites() (+7 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (37): str, AttachmentOut, ReimburseApplyCreate, Config, PageResult, ResponseModel, ContactByCustomerBody, ContactDetailBody (+29 more)
|
||||
|
||||
### Community 36 - "Community 36"
|
||||
Cohesion: 0.13
|
||||
Nodes (45): pool_rule_toggle(), Any, int, Session, str, Any, bool, CRM 公海池规则 CRUD + 规则执行逻辑 (+37 more)
|
||||
Cohesion: 0.12
|
||||
Nodes (26): API 路由包 — 山峡化工生产一体化门户, create_leave(), delete_leave(), get_leave(), _leave_local_callback(), leave_workflow_callback(), list_leaves(), submit_leave() (+18 more)
|
||||
|
||||
### Community 37 - "Community 37"
|
||||
Cohesion: 0.33
|
||||
Nodes (14): Any, int, Session, str, create_dict_data(), get_dict_data(), list_dict_data(), list_dict_data_options() (+6 more)
|
||||
Cohesion: 0.31
|
||||
Nodes (15): Any, int, Session, str, assert_row_deletable_by_creator(), create_dict_data(), get_dict_data(), list_dict_data() (+7 more)
|
||||
|
||||
### Community 38 - "Community 38"
|
||||
Cohesion: 0.21
|
||||
Nodes (18): bool, int, Redis, Request, str, _claims_from_auth(), _coerce_post_method_override_to_get(), _get_redis_client() (+10 more)
|
||||
Cohesion: 0.15
|
||||
Nodes (14): Any, int, str, get_wechat_pay_client(), get_wechat_pay_v2(), 微信支付工具模块 支持微信支付 V3 API 文档参考: https://pay.weixin.qq.com/docs/v3/apis/index.html, 统一下单接口 (V3) Args: out_trade_no: 商户订单号 total_amo, 申请退款 Args: out_trade_no: 商户订单号 total_amount: 订单 (+6 more)
|
||||
|
||||
### Community 39 - "Community 39"
|
||||
Cohesion: 0.43
|
||||
Nodes (7): datetime, int, str, generate_random_password(), get_current_time(), get_file_size_display(), parse_datetime()
|
||||
Cohesion: 0.13
|
||||
Nodes (33): datetime, int, str, date, datetime, str, format_datetime(), generate_id() (+25 more)
|
||||
|
||||
### Community 40 - "Community 40"
|
||||
Cohesion: 0.16
|
||||
Nodes (32): Any, bool, Decimal, int, Session, str, batch_insert_quote_products(), calculate_subtotal() (+24 more)
|
||||
Cohesion: 0.13
|
||||
Nodes (37): Any, bool, Decimal, int, Session, str, batch_insert_quote_products(), calculate_subtotal() (+29 more)
|
||||
|
||||
### Community 41 - "Community 41"
|
||||
Cohesion: 0.25
|
||||
Nodes (18): BxUserContext, Session, str, bool, BxUserContext, Session, str, create_expense_item() (+10 more)
|
||||
Cohesion: 0.14
|
||||
Nodes (11): Any, Session, str, _auto_fill_create_dept_id(), get_db(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID, get_db_current_user_optional(), 地磅模块认证适配:门户 JWT 用户 → db 业务上下文。 (+3 more)
|
||||
|
||||
### Community 42 - "Community 42"
|
||||
Cohesion: 0.08
|
||||
Nodes (25): 技术栈, 1. 创建虚拟环境, 项目结构, 1. 创建虚拟环境, 2. 安装依赖, 2. 安装依赖, 3. 配置环境变量, 快速开始 (+17 more)
|
||||
|
||||
### Community 43 - "Community 43"
|
||||
Cohesion: 0.13
|
||||
Nodes (32): Any, int, str, str, Any, str, BusinessException, InvalidStatusTransitionException (+24 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (68): Any, int, str, Any, int, Session, str, Any (+60 more)
|
||||
|
||||
### Community 44 - "Community 44"
|
||||
Cohesion: 0.25
|
||||
Nodes (23): Any, bool, int, Session, str, create_role(), get_role(), get_role_data_scope() (+15 more)
|
||||
Cohesion: 0.15
|
||||
Nodes (33): CRM 报价单 API 路由 — 挂接到 crm.router, CRM 报价单 Pydantic Schemas, BaseModel, QuoteAcceptBody, QuoteApprovalApproveBody, QuoteApprovalListBody, QuoteApprovalRefreshNamesBody, QuoteApprovalWithdrawBody (+25 more)
|
||||
|
||||
### Community 45 - "Community 45"
|
||||
Cohesion: 0.25
|
||||
Nodes (8): create_from_opportunity(), _opportunity_products_to_contract_products(), Java: createFromOpportunity — 返回合同 id, Java: createFromOpportunity — 返回合同 id, Java: createFromOpportunity — 返回合同 id, Java: createFromOpportunity — 返回合同 id, Java: createFromOpportunity — 返回合同 id, Java: createFromOpportunity — 返回合同 id
|
||||
Cohesion: 0.32
|
||||
Nodes (22): Any, CrmOpportunity, Decimal, int, Session, str, batch_add_opportunity_products(), _calc_subtotal() (+14 more)
|
||||
|
||||
### Community 46 - "Community 46"
|
||||
Cohesion: 0.08
|
||||
Nodes (37): Any, bool, str, Decimal, str, str, Any, bool (+29 more)
|
||||
Cohesion: 0.19
|
||||
Nodes (18): str, Any, bool, Session, str, CRM 模块 Excel 导出列定义(字段与字典类型对齐 Java *Vo @ExcelDictFormat)。, _yes_no(), _build_workbook() (+10 more)
|
||||
|
||||
### Community 47 - "Community 47"
|
||||
Cohesion: 0.11
|
||||
Nodes (70): ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody, 审批意见:前端传 commentText,兼容 comment。, 审批意见:前端传 commentText,兼容 comment。, StartInstanceBody, TaskActionBody, Any (+62 more)
|
||||
Cohesion: 0.24
|
||||
Nodes (24): 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() (+16 more)
|
||||
|
||||
### Community 48 - "Community 48"
|
||||
Cohesion: 0.25
|
||||
Nodes (8): Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval, withdraw_contract_approval()
|
||||
Cohesion: 0.39
|
||||
Nodes (8): bool, BxUserContext, Session, str, 按业务类型获取工作流;已知类型不存在时自动创建空定义(无节点)。, 按业务类型获取工作流;已知类型不存在时自动创建空定义(无节点)。, WorkflowService, BxWorkflowDef
|
||||
|
||||
### Community 49 - "Community 49"
|
||||
Cohesion: 0.43
|
||||
Nodes (7): _avatar_url_field(), _build_user_info_payload(), _jwt_user_payload(), 与 web-admin `userInfo` 字段约定一致。, 与 web-admin `userInfo` 字段约定一致。, str, SysUser
|
||||
Cohesion: 0.07
|
||||
Nodes (46): CRM 合同 API 路由 — GET/PUT/DELETE/POST 对齐原 Java CRM, CRM 合同 Pydantic Schemas — 对照 CrmContractBo / CrmContractVo, ContractApprovalActionBody, ContractApprovalApproveBody, ContractApprovalByContractBody, ContractApprovalExportBody, ContractApprovalListBody, ContractApprovalListQuery (+38 more)
|
||||
|
||||
### Community 50 - "Community 50"
|
||||
Cohesion: 0.06
|
||||
Nodes (150): date, DbUserContext, int, PlanCreateFull, PlPlan, Session, str, DbInboundOutbound (+142 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (105): bool, PlCarrier, VehicleCreate, VehicleUpdate, bool, Decimal, int, PlanCreateFull (+97 more)
|
||||
|
||||
### Community 51 - "Community 51"
|
||||
Cohesion: 0.08
|
||||
Nodes (88): FreightSettlementDisburseBody, FreightSettlementSubmitApprovalBody, FreightSettlementUpdate, Any, bool, datetime, FreightSettlementDisburseBody, FreightSettlementSubmitApprovalBody (+80 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (167): DbUserContext, FreightSettlementDisburseBody, FreightSettlementSubmitApprovalBody, FreightSettlementUpdate, int, Session, str, Any (+159 more)
|
||||
|
||||
### Community 52 - "Community 52"
|
||||
Cohesion: 0.11
|
||||
Nodes (58): CrmContract, Query, Any, bool, CrmContractProductPriceTier, date, Decimal, int (+50 more)
|
||||
Nodes (46): Decimal, str, Any, bool, CrmContractProductPriceTier, date, Decimal, int (+38 more)
|
||||
|
||||
### Community 53 - "Community 53"
|
||||
Cohesion: 0.50
|
||||
Nodes (52): date, int, DocumentReminderRead, DocumentReminderUpdate, FreightSettlementCreate, FreightSettlementListResponse, FreightSettlementUpdate, FreightSettlementWithTransports (+44 more)
|
||||
Cohesion: 0.18
|
||||
Nodes (19): bytes, int, str, download_file_bytes(), file_stream(), get_access_url(), get_download_url(), get_preview_url() (+11 more)
|
||||
|
||||
### Community 54 - "Community 54"
|
||||
Cohesion: 0.03
|
||||
Nodes (67): 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list (+59 more)
|
||||
Cohesion: 0.22
|
||||
Nodes (17): Any, bool, Session, str, list_share_logs(), _parse_user_agent(), CRM 报价单分享访问日志 CRUD — 对照 CrmQuoteShareLogServiceImpl.java, Java: statisticsVisits (+9 more)
|
||||
|
||||
### Community 55 - "Community 55"
|
||||
Cohesion: 0.29
|
||||
Nodes (7): download_contract_pdf(), Java: downloadContractPdf — 返回合同文件 URL/路径, Java: batchAssignOwner, Java: downloadContractPdf — 返回合同文件 URL/路径, Java: downloadContractPdf — 返回合同文件 URL/路径, Java: downloadContractPdf — 返回合同文件 URL/路径, Java: downloadContractPdf — 返回合同文件 URL/路径
|
||||
Cohesion: 0.28
|
||||
Nodes (22): bool, CRM 产品模块 CRUD(产品分类 + 产品 + 附件), Session, str, barcode_exists(), category_code_exists(), create_category(), create_product() (+14 more)
|
||||
|
||||
### Community 56 - "Community 56"
|
||||
Cohesion: 0.26
|
||||
@@ -387,75 +401,79 @@ Nodes (22): Any, bool, datetime, int, Session, str, BizSampleCode, batch_generat
|
||||
|
||||
### Community 57 - "Community 57"
|
||||
Cohesion: 0.10
|
||||
Nodes (20): product_delete(), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids) (+12 more)
|
||||
Nodes (21): product_delete(), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids) (+13 more)
|
||||
|
||||
### Community 58 - "Community 58"
|
||||
Cohesion: 0.37
|
||||
Nodes (17): check_env(), check_python(), create_venv(), init_env(), install_dependencies(), log_error(), log_info(), log_success() (+9 more)
|
||||
|
||||
### Community 59 - "Community 59"
|
||||
Cohesion: 0.34
|
||||
Nodes (16): Any, bool, int, Session, str, create_tenant(), get_tenant(), list_tenants() (+8 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (22): bool, date, Session, str, get_last_month_range(), month_range(), 地磅计划按月自动生成 — 迁移自 simple-dibang-xiaoshou/auto-create-plan/auto_create_monthly_pla, 按月自动生成采购计划。 - 默认仅在每月 1 日执行(force=True 时跳过该限制,供手动触发验收) - 遍历所有租户,按上月计划模板生 (+14 more)
|
||||
|
||||
### Community 60 - "Community 60"
|
||||
Cohesion: 0.18
|
||||
Nodes (11): generate_contract_no(), Java: generateContractNo — {供方编码}/X-JT + yyyyMMdd + 3位流水, Java: generateContractNo — {供方编码}/X-JT + yyyyMMdd + 3位流水, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号}, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号}, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号}, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号}, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号} (+3 more)
|
||||
|
||||
### Community 61 - "Community 61"
|
||||
Cohesion: 0.12
|
||||
Nodes (46): Session, str, bool, date, DbInboundOutbound, DbPurchasePlan, float, PlPlan (+38 more)
|
||||
|
||||
### Community 62 - "Community 62"
|
||||
Cohesion: 0.12
|
||||
Nodes (45): bool, DbInboundOutbound, int, PublicInventoryFeedback, PublicInventoryPageRead, Session, str, UploadFile (+37 more)
|
||||
|
||||
### Community 63 - "Community 63"
|
||||
Cohesion: 0.19
|
||||
Nodes (48): AdvanceApplyCreate, AdvanceApplyOut, AdvanceApplyUpdate, AdvanceApprovalCreate, BxUserContext, int, Session, str (+40 more)
|
||||
|
||||
### Community 64 - "Community 64"
|
||||
Cohesion: 0.19
|
||||
Nodes (20): int, str, Any, bool, int, Session, str, business_type_from_path() (+12 more)
|
||||
|
||||
### Community 65 - "Community 65"
|
||||
Cohesion: 0.28
|
||||
Nodes (8): date, datetime, str, _date_to_str(), _dt_to_str(), PagedQuery, PagedResponse, bx 原系统 API 响应包装(code=0 / msg / data.rows),与门户 success_response 分离。
|
||||
|
||||
### Community 66 - "Community 66"
|
||||
Cohesion: 0.34
|
||||
Nodes (16): Any, bool, int, Session, str, create_dict_type(), dict_type_code_exists(), get_dict_type() (+8 more)
|
||||
|
||||
### Community 61 - "Community 61"
|
||||
Cohesion: 0.24
|
||||
Nodes (24): 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() (+16 more)
|
||||
|
||||
### Community 62 - "Community 62"
|
||||
Cohesion: 0.10
|
||||
Nodes (49): bool, DbInboundOutbound, int, PublicInventoryFeedback, PublicInventoryPageRead, Session, str, UploadFile (+41 more)
|
||||
|
||||
### Community 63 - "Community 63"
|
||||
Cohesion: 0.07
|
||||
Nodes (63): bool, date, Session, str, Session, str, bool, date (+55 more)
|
||||
|
||||
### Community 64 - "Community 64"
|
||||
Cohesion: 0.33
|
||||
Nodes (15): Any, int, Session, str, BizLeaveRequest, create_leave(), get_leave(), list_leaves() (+7 more)
|
||||
|
||||
### Community 65 - "Community 65"
|
||||
Cohesion: 0.29
|
||||
Nodes (10): datetime, int, Session, str, BizNoKind, allocate_next_biz_no(), _format_biz_no(), _prefix_for() (+2 more)
|
||||
|
||||
### Community 66 - "Community 66"
|
||||
Cohesion: 0.31
|
||||
Nodes (11): bool, BxUserContext, BxWorkflowNode, int, Session, str, 按业务类型获取工作流;已知类型不存在时自动创建空定义(无节点)。, 按业务类型获取工作流;已知类型不存在时自动创建空定义(无节点)。 (+3 more)
|
||||
|
||||
### Community 67 - "Community 67"
|
||||
Cohesion: 0.14
|
||||
Nodes (24): Any, bool, Session, str, Java: viewByShareCode, Java: viewByShareCode, Java: viewByShareCode, Java: viewByShareCode (+16 more)
|
||||
Cohesion: 0.16
|
||||
Nodes (12): float, int, str, 微信消息通知服务 支持微信小程序模板消息推送 使用方式: 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 (+4 more)
|
||||
|
||||
### Community 68 - "Community 68"
|
||||
Cohesion: 0.40
|
||||
Nodes (3): API 路由包 — 山峡化工生产一体化门户, get_menu_list(), 运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,14 个一级菜单)
|
||||
Cohesion: 0.25
|
||||
Nodes (18): BxUserContext, Session, str, bool, BxUserContext, Session, str, create_expense_item() (+10 more)
|
||||
|
||||
### Community 69 - "Community 69"
|
||||
Cohesion: 0.35
|
||||
Nodes (20): DbUserContext, float, Session, str, DashboardCargoStat, DashboardPartnerCargoStat, DashboardStats, _cargo_stats() (+12 more)
|
||||
Cohesion: 0.27
|
||||
Nodes (24): DbUserContext, float, Session, str, Session, str, DashboardCargoStat, DashboardPartnerCargoStat (+16 more)
|
||||
|
||||
### Community 70 - "Community 70"
|
||||
Cohesion: 0.29
|
||||
Nodes (14): is_valid_dict_type(), load_portal_baseline(), load_sx(), load_zkxw(), main(), map_tag_type(), normalize_row(), parse_insert() (+6 more)
|
||||
|
||||
### Community 71 - "Community 71"
|
||||
Cohesion: 0.25
|
||||
Nodes (11): check_customer_protect_time(), _execute_single_rule(), _get_all_tenant_nos(), pool_recovery_job(), 公海池自动回收任务(由 APScheduler 调度) 遍历所有租户的启用规则,执行自动回收。, 每天凌晨1点:检查保护期到期客户,跟进次数不足则退回公海, 每天早上9点:保护期到期提醒(3天内到期且跟进不足), remind_customer_recycle() (+3 more)
|
||||
Cohesion: 0.22
|
||||
Nodes (19): Any, bool, str, _actor_ids(), _actor_names(), assert_rows_deletable_by_creator(), can_delete_by_creator(), is_super_admin() (+11 more)
|
||||
|
||||
### Community 72 - "Community 72"
|
||||
Cohesion: 0.34
|
||||
Nodes (19): check_python(), create_venv(), ensure_dirs(), ensure_valid_venv(), ensure_venv_and_runtime(), install_dependencies(), is_running(), log_error() (+11 more)
|
||||
|
||||
### Community 73 - "Community 73"
|
||||
Cohesion: 0.13
|
||||
Nodes (39): Any, bool, date, int, Session, str, Session, str (+31 more)
|
||||
Cohesion: 0.30
|
||||
Nodes (14): Any, bool, bytes, int, str, bytes, ApiCryptoError, _b64url_decode() (+6 more)
|
||||
|
||||
### Community 74 - "Community 74"
|
||||
Cohesion: 0.22
|
||||
Nodes (12): Any, bool, str, WfProcessInstance, _LocalCallbackFn, _dispatch_local(), 工作流回调服务 —— 流程状态变更时通知业务系统, 注册本地回调函数。当 callback_url 以 path 开头时直接调用 fn。 (+4 more)
|
||||
|
||||
### Community 75 - "Community 75"
|
||||
Cohesion: 0.05
|
||||
Nodes (152): Any, CRM 合同 API 路由 — GET/PUT/DELETE/POST 对齐原 Java CRM, register_contract_routes(), CRM 合同子模块路由 — 变更/模板/审批独立页, register_contract_sub_routes(), Any, bool, CRM 合同 CRUD — 对照 CrmContractServiceImpl.java(列表/详情 P1) (+144 more)
|
||||
Nodes (135): date, DbUserContext, int, PlanCreateFull, PlPlan, Session, str, date (+127 more)
|
||||
|
||||
### Community 76 - "Community 76"
|
||||
Cohesion: 0.18
|
||||
@@ -466,116 +484,168 @@ Cohesion: 0.60
|
||||
Nodes (10): Any, Session, str, _apply_create_range(), _base_query(), get_clue_level_statistics(), get_clue_overview_statistics(), get_clue_owner_statistics() (+2 more)
|
||||
|
||||
### Community 78 - "Community 78"
|
||||
Cohesion: 0.05
|
||||
Nodes (101): Any, bool, CRM 产品模块 CRUD(产品分类 + 产品 + 附件), int, Session, str, SysOss, int (+93 more)
|
||||
Cohesion: 0.30
|
||||
Nodes (21): Any, bool, CrmOpportunity, datetime, int, Session, str, ensure_crm_create_by_row_in_scope() (+13 more)
|
||||
|
||||
### Community 79 - "Community 79"
|
||||
Cohesion: 0.36
|
||||
Nodes (10): add_scope_before_order(), ensure_import(), patch_base_data(), patch_get_helper(), patch_inventory(), patch_plan(), patch_transport(), str (+2 more)
|
||||
|
||||
### Community 80 - "Community 80"
|
||||
Cohesion: 0.24
|
||||
Nodes (10): get_current_user_profile(), get_web_auth_menu_codes(), public_quote_share(), 获取当前用户信息(与 sys_user 字段对应)。, 获取当前用户信息(与 sys_user 字段对应)。, 获取当前用户菜单权限标识(sys_menu.perms,经角色继承)。, 获取当前用户菜单权限标识(sys_menu.perms,经角色继承)。, 公开分享查看报价单 — Java: GET /share/{shareCode}(无 JWT) (+2 more)
|
||||
Cohesion: 0.14
|
||||
Nodes (46): ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody, 审批意见:前端传 commentText,兼容 comment。, 审批意见:前端传 commentText,兼容 comment。, StartInstanceBody, TaskActionBody, Any (+38 more)
|
||||
|
||||
### Community 81 - "Community 81"
|
||||
Cohesion: 0.31
|
||||
Nodes (20): Any, bool, CrmOpportunity, datetime, int, Session, str, create_opportunity_follow() (+12 more)
|
||||
Cohesion: 0.34
|
||||
Nodes (16): Any, bool, int, Session, str, create_tenant(), get_tenant(), list_tenants() (+8 more)
|
||||
|
||||
### Community 82 - "Community 82"
|
||||
Cohesion: 0.29
|
||||
Nodes (9): AST, expr, field_has_alias(), iter_field_assignments(), main(), bool, int, str (+1 more)
|
||||
|
||||
### Community 83 - "Community 83"
|
||||
Cohesion: 0.22
|
||||
Nodes (9): create_from_quote(), _quote_products_to_contract_products(), Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id (+1 more)
|
||||
Cohesion: 0.18
|
||||
Nodes (9): 报销(bx)API 路由聚合 — REST 风格,协议对齐 bx 原系统。, 报销(bx)Pydantic 模型 — 响应协议与 bx 原系统保持一致, bx_health(), 报销(bx)API 路由聚合 — REST 风格,协议对齐 bx 原系统。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。 (+1 more)
|
||||
|
||||
### Community 84 - "Community 84"
|
||||
Cohesion: 0.29
|
||||
Nodes (7): generate_template_no(), Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}
|
||||
Cohesion: 0.25
|
||||
Nodes (8): generate_template_no(), Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}
|
||||
|
||||
### Community 85 - "Community 85"
|
||||
Cohesion: 0.22
|
||||
Nodes (57): InboundOutboundRead, bool, DbPurchasePlan, DbUserContext, PurchasePlanRead, PurchasePlanWithFillLink, Session, str (+49 more)
|
||||
|
||||
### Community 86 - "Community 86"
|
||||
Cohesion: 0.03
|
||||
Nodes (199): register_contract_routes(), CRM 合同子模块路由 — 变更/模板/审批独立页, register_contract_sub_routes(), Any, bool, CRM 合同 CRUD — 对照 CrmContractServiceImpl.java(列表/详情 P1), CrmContract, datetime (+191 more)
|
||||
|
||||
### Community 87 - "Community 87"
|
||||
Cohesion: 0.15
|
||||
Nodes (27): create_leave(), delete_leave(), get_leave(), _leave_local_callback(), leave_workflow_callback(), list_leaves(), submit_leave(), update_leave() (+19 more)
|
||||
Cohesion: 0.13
|
||||
Nodes (18): Any, _build_category_tree(), _collect_category_with_ancestors(), list_category_tree(), list_oss_by_biz(), list_product_options(), _oss_row_to_dict(), 获取产品分类树(嵌套 children,供树形表格/树选择器使用) (+10 more)
|
||||
|
||||
### Community 88 - "Community 88"
|
||||
Cohesion: 0.18
|
||||
Nodes (19): bytes, int, str, download_file_bytes(), file_stream(), get_access_url(), get_download_url(), get_preview_url() (+11 more)
|
||||
Cohesion: 0.29
|
||||
Nodes (16): Any, int, Session, str, BizLeaveRequest, create_leave(), get_leave(), list_leaves() (+8 more)
|
||||
|
||||
### Community 89 - "Community 89"
|
||||
Cohesion: 0.32
|
||||
Nodes (22): Any, CrmOpportunity, Decimal, int, Session, str, batch_add_opportunity_products(), _calc_subtotal() (+14 more)
|
||||
Cohesion: 0.33
|
||||
Nodes (10): Any, int, Session, str, list_ethnics(), 民族码表 CRUD(全局 sys_ethnic), _row_to_dict(), 民族码表 sys_ethnic(国标 GB/T 3304,全局) (+2 more)
|
||||
|
||||
### Community 90 - "Community 90"
|
||||
Cohesion: 0.17
|
||||
Nodes (23): Any, bool, bytes, int, Request, str, bool, int (+15 more)
|
||||
Cohesion: 0.16
|
||||
Nodes (25): Any, bool, bytes, int, Request, str, bool, int (+17 more)
|
||||
|
||||
### Community 91 - "Community 91"
|
||||
Cohesion: 0.32
|
||||
Nodes (7): str, eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci()
|
||||
|
||||
### Community 94 - "Community 94"
|
||||
### Community 92 - "Community 92"
|
||||
Cohesion: 0.20
|
||||
Nodes (10): generate_contract_name(), Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName (+2 more)
|
||||
Nodes (10): create_from_quote(), _quote_products_to_contract_products(), Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id (+2 more)
|
||||
|
||||
### Community 93 - "Community 93"
|
||||
Cohesion: 0.22
|
||||
Nodes (8): ChangePasswordRequest, Config, LoginRequest, LoginResponse, Pydantic schemas - 用户认证, ResetPasswordRequest, UserCreate, UserResponse
|
||||
|
||||
### Community 94 - "Community 94"
|
||||
Cohesion: 0.18
|
||||
Nodes (11): generate_contract_name(), Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName (+3 more)
|
||||
|
||||
### Community 95 - "Community 95"
|
||||
Cohesion: 0.18
|
||||
Nodes (9): 报销(bx)API 路由聚合 — REST 风格,协议对齐 bx 原系统。, 报销(bx)Pydantic 模型 — 响应协议与 bx 原系统保持一致, bx_health(), 报销(bx)API 路由聚合 — REST 风格,协议对齐 bx 原系统。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。 (+1 more)
|
||||
Cohesion: 0.22
|
||||
Nodes (9): _category_row_to_dict(), list_categories(), 分类 ORM 对象转 dict(camelCase 键名), 分类 ORM 对象转 dict(camelCase 键名), 分类 ORM 对象转 dict(camelCase 键名), 分类 ORM 对象转 dict(camelCase 键名), 获取分类(扁平列表,支持名称模糊与状态筛选), 获取分类(扁平列表,支持名称模糊与状态筛选) (+1 more)
|
||||
|
||||
### Community 96 - "Community 96"
|
||||
Cohesion: 0.38
|
||||
Nodes (15): Any, bool, int, Session, str, create_post(), get_post(), list_posts() (+7 more)
|
||||
|
||||
### Community 97 - "Community 97"
|
||||
Cohesion: 0.28
|
||||
Nodes (13): int, Session, str, _allocate_inbound_outbound_id(), _ensure_sequence_row(), max_numeric_inbound_outbound_id(), next_inbound_outbound_id(), 出入库单 id 生成 — 租户内纯数字自增,并发安全。 (+5 more)
|
||||
Cohesion: 0.23
|
||||
Nodes (14): int, Session, str, _allocate_inbound_outbound_id(), _ensure_sequence_row(), max_numeric_inbound_outbound_id(), next_inbound_outbound_id(), 出入库单 id 生成 — 租户内纯数字自增,并发安全。 (+6 more)
|
||||
|
||||
### Community 98 - "Community 98"
|
||||
Cohesion: 0.37
|
||||
Nodes (16): Any, bool, int, Session, str, create_site(), get_site(), list_sites() (+8 more)
|
||||
|
||||
### Community 99 - "Community 99"
|
||||
Cohesion: 0.30
|
||||
Nodes (14): Any, bool, bytes, int, str, bytes, ApiCryptoError, _b64url_decode() (+6 more)
|
||||
Cohesion: 0.33
|
||||
Nodes (3): 地磅 API 路由聚合 — REST 风格,协议对齐原 simple-dibang-xiaoshou。, 地磅(db)Pydantic schemas — 对齐原 simple-dibang-xiaoshou 契约。, 地磅 API 路由聚合 — REST 风格,协议对齐原 simple-dibang-xiaoshou。
|
||||
|
||||
### Community 100 - "Community 100"
|
||||
Cohesion: 0.25
|
||||
Nodes (23): Any, bool, int, Session, str, create_role(), get_role(), get_role_data_scope() (+15 more)
|
||||
|
||||
### Community 101 - "Community 101"
|
||||
Cohesion: 0.40
|
||||
Nodes (3): BaseSettings, Config, Settings
|
||||
|
||||
### Community 102 - "Community 102"
|
||||
Cohesion: 0.15
|
||||
Nodes (13): _generate_plan_no(), 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD###### (+5 more)
|
||||
Cohesion: 0.34
|
||||
Nodes (23): DbUserContext, DbWeighingSlip, Session, str, weighing_to_read(), apply_product_snapshot_to_weighing(), _apply_weighing_product_update(), _apply_weighing_update() (+15 more)
|
||||
|
||||
### Community 103 - "Community 103"
|
||||
Cohesion: 0.12
|
||||
Nodes (87): bool, DbUserContext, float, int, PlPlan, PlTransport, Session, str (+79 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (99): bool, DbUserContext, float, int, PlPlan, PlTransport, Session, str (+91 more)
|
||||
|
||||
### Community 104 - "Community 104"
|
||||
Cohesion: 0.15
|
||||
Nodes (35): logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/shanxia2.0.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, Web 端登录:账号为 sys_user.user_no,密码为 sys_user.password(bcrypt)。, Web 端登录:账号为 sys_user.user_no,密码为 sys_user.password(bcrypt)。, refresh_token(), web_login() (+27 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (50): get_current_user_profile(), get_web_auth_menu_codes(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/shanxia2.0.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, 获取当前用户信息(与 sys_user 字段对应)。 (+42 more)
|
||||
|
||||
### Community 105 - "Community 105"
|
||||
Cohesion: 0.17
|
||||
Nodes (12): 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove, 删除供方 — Java: CrmSupplierInfoController.remove (+4 more)
|
||||
|
||||
### Community 107 - "Community 107"
|
||||
Cohesion: 0.33
|
||||
Nodes (10): Any, int, Session, str, list_ethnics(), 民族码表 CRUD(全局 sys_ethnic), _row_to_dict(), 民族码表 sys_ethnic(国标 GB/T 3304,全局) (+2 more)
|
||||
Nodes (5): CRM 客户管理 Pydantic Schemas, CustomerAssignPoolBody, CustomerCheckDuplicateBody, CustomerDetailBody, CRM 客户 CRUD(客户主表 + 转移/成交/公海池操作)
|
||||
|
||||
### Community 108 - "Community 108"
|
||||
Cohesion: 0.34
|
||||
Nodes (16): Any, int, Session, str, _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept() (+8 more)
|
||||
### Community 109 - "Community 109"
|
||||
Cohesion: 0.27
|
||||
Nodes (11): Any, bool, str, decrypt_login_payload(), ensure_initialized(), extract_login_credentials(), get_public_pem(), 登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。 (+3 more)
|
||||
|
||||
### Community 112 - "Community 112"
|
||||
Cohesion: 0.25
|
||||
Nodes (8): batch_submit_contract_approval(), Java: batchSubmitApproval, Java: batchSubmitApproval, Java: batchSubmitApproval, Java: batchSubmitApproval, Java: batchSubmitApproval, Java: batchSubmitApproval, Java: batchSubmitApproval
|
||||
Cohesion: 0.14
|
||||
Nodes (16): Session, str, Session, str, get_user_menu_codes(), 菜单 code 权限守卫(V0.3:通过 sys_user_role → sys_role_menu → sys_menu.perms), 读取用户授权的菜单权限标识集合(与前端 meta.menuCode / perms 对齐)。, 接口菜单权限守卫。super_admin 直接放行。 (+8 more)
|
||||
|
||||
### Community 122 - "Community 122"
|
||||
Cohesion: 0.24
|
||||
Nodes (20): Session, str, Any, bool, Session, str, apply_crm_owner_scope(), ensure_crm_customer_linked_row_in_scope() (+12 more)
|
||||
|
||||
### Community 124 - "Community 124"
|
||||
Cohesion: 0.41
|
||||
Nodes (13): BxUserContext, Session, str, UploadFile, bind_biz(), delete_attachment(), download_file(), list_by_biz() (+5 more)
|
||||
|
||||
### Community 125 - "Community 125"
|
||||
Cohesion: 0.40
|
||||
Nodes (5): bool, str, get_password_hash(), 验证密码(哈希非 bcrypt 或损坏时返回 False,避免抛错), verify_password()
|
||||
|
||||
### Community 126 - "Community 126"
|
||||
Cohesion: 0.43
|
||||
Nodes (7): _avatar_url_field(), _build_user_info_payload(), _jwt_user_payload(), 与 web-admin `userInfo` 字段约定一致。, 与 web-admin `userInfo` 字段约定一致。, str, SysUser
|
||||
|
||||
### Community 127 - "Community 127"
|
||||
Cohesion: 0.29
|
||||
Nodes (10): datetime, int, Session, str, BizNoKind, allocate_next_biz_no(), _format_biz_no(), _prefix_for() (+2 more)
|
||||
|
||||
### Community 128 - "Community 128"
|
||||
Cohesion: 0.40
|
||||
Nodes (10): SysOss, int, Session, str, SysOss, create_oss(), create_oss(), get_oss_by_url() (+2 more)
|
||||
|
||||
### Community 129 - "Community 129"
|
||||
Cohesion: 0.38
|
||||
Nodes (6): date, datetime, str, _date_to_str(), _dt_to_str(), bx 原系统 API 响应包装(code=0 / msg / data.rows),与门户 success_response 分离。
|
||||
Cohesion: 0.40
|
||||
Nodes (3): AdvanceApplyCreate, AdvanceApplyUpdate, _coerce_optional_id()
|
||||
|
||||
### Community 131 - "Community 131"
|
||||
Cohesion: 0.11
|
||||
Nodes (33): bool, Session, str, _append_user_scope_conditions(), apply_data_scope(), assert_row_in_data_scope(), _collect_scope_ids(), _get_child_dept_ids() (+25 more)
|
||||
Nodes (34): bool, Session, str, _append_user_scope_conditions(), apply_data_scope(), assert_row_in_data_scope(), _collect_scope_ids(), _get_child_dept_ids() (+26 more)
|
||||
|
||||
### Community 132 - "Community 132"
|
||||
Cohesion: 0.50
|
||||
Nodes (4): Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), web_login_rsa_public_key()
|
||||
|
||||
### Community 133 - "Community 133"
|
||||
Cohesion: 0.25
|
||||
Nodes (8): Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签, renew_contract()
|
||||
Cohesion: 0.18
|
||||
Nodes (11): generate_product_no(), 生成产品编号(格式:CP + yyyyMMdd + 3位序号,如 CP20260530001), 生成产品编号(格式:CP + yyyyMMdd + 3位序号,如 CP20260530001), 生成产品编号(格式:CP + yyyyMMdd + 3位序号,如 CP20260530001), 生成产品编号(格式:CP + yyyyMMdd + 3位序号,如 CP20260530001), 生成产品编号(格式:CP + yyyyMMdd + 3位序号,如 CP20260530001), 生成产品编号(格式:CP + yyyyMMdd + 3位序号,如 CP20260530001), 生成产品编号(格式:CP + yyyyMMdd + 3位序号,如 CP20260530001) (+3 more)
|
||||
|
||||
### Community 134 - "Community 134"
|
||||
Cohesion: 0.38
|
||||
Nodes (4): str, _coerce_id(), ExpenseItemCreate, ExpenseItemUpdate
|
||||
|
||||
### Community 135 - "Community 135"
|
||||
Cohesion: 0.25
|
||||
@@ -583,38 +653,50 @@ Nodes (7): 1. 上传到服务器, 2. 服务器首次启动, 3. 日常运维, 4.
|
||||
|
||||
### Community 136 - "Community 136"
|
||||
Cohesion: 0.11
|
||||
Nodes (33): int, Any, str, bool, BxUserContext, int, Session, str (+25 more)
|
||||
Nodes (32): Any, str, bool, BxUserContext, int, Session, str, Any (+24 more)
|
||||
|
||||
### Community 137 - "Community 137"
|
||||
Cohesion: 0.18
|
||||
Nodes (11): _get_quote_item_field(), Java: resolveProductIdForQuoteItem(CrmProduct 无 tax_rate,跳过税率匹配), Java: resolveProductIdForQuoteItem(CrmProduct 无 tax_rate,跳过税率匹配), Java: resolveProductIdForQuoteItem(CrmProduct 无 tax_rate,跳过税率匹配), Java: resolveProductIdForQuoteItem(CrmProduct 无 tax_rate,跳过税率匹配), Java: resolveProductIdForQuoteItem(CrmProduct 无 tax_rate,跳过税率匹配), Java: resolveProductIdForQuoteItem(CrmProduct 无 tax_rate,跳过税率匹配), Java: resolveProductIdForQuoteItem(CrmProduct 无 tax_rate,跳过税率匹配) (+3 more)
|
||||
|
||||
### Community 138 - "Community 138"
|
||||
Cohesion: 0.39
|
||||
Nodes (6): _inject_user_info_after_tenant(), patch_contract_routes(), patch_quote_routes(), patch_route_file(), Path, str
|
||||
|
||||
### Community 139 - "Community 139"
|
||||
Cohesion: 0.33
|
||||
Nodes (3): 地磅 API 路由聚合 — REST 风格,协议对齐原 simple-dibang-xiaoshou。, 地磅(db)Pydantic schemas — 对齐原 simple-dibang-xiaoshou 契约。, 地磅 API 路由聚合 — REST 风格,协议对齐原 simple-dibang-xiaoshou。
|
||||
Cohesion: 0.20
|
||||
Nodes (10): int, list_products(), _product_row_to_dict(), 产品 ORM 对象转 dict(camelCase 键名), 产品 ORM 对象转 dict(camelCase 键名), 产品 ORM 对象转 dict(camelCase 键名), 产品 ORM 对象转 dict(camelCase 键名), 产品 ORM 对象转 dict(camelCase 键名) (+2 more)
|
||||
|
||||
### Community 140 - "Community 140"
|
||||
Cohesion: 0.83
|
||||
Nodes (3): _add_user_info_kwarg(), patch_file(), str
|
||||
|
||||
### Community 143 - "Community 143"
|
||||
Cohesion: 0.67
|
||||
Nodes (3): Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), web_login_rsa_public_key()
|
||||
### Community 145 - "Community 145"
|
||||
Cohesion: 0.33
|
||||
Nodes (5): _coerce_optional_id(), WorkflowDefCreate, WorkflowDefUpdate, WorkflowNodeCreate, WorkflowNodeOut
|
||||
|
||||
### Community 146 - "Community 146"
|
||||
Cohesion: 0.20
|
||||
Nodes (10): generate_auto_product_no(), Java: generateAutoProductNo — SXWL + 8位随机大写字符, Java: generateAutoProductNo — SXWL + 8位随机大写字符, Java: generateAutoProductNo — SXWL + 8位随机大写字符, Java: generateAutoProductNo — SXWL + 8位随机大写字符, Java: generateAutoProductNo — SXWL + 8位随机大写字符, Java: generateAutoProductNo — SXWL + 8位随机大写字符, Java: generateAutoProductNo — SXWL + 8位随机大写字符 (+2 more)
|
||||
|
||||
### Community 147 - "Community 147"
|
||||
Cohesion: 0.25
|
||||
Nodes (8): generate_barcode(), 生成唯一条码(纯数字:yyyyMMdd + 6位序号,如 20260606000001), 生成唯一条码(纯数字:yyyyMMdd + 6位序号,如 20260606000001), 生成唯一条码(纯数字:yyyyMMdd + 6位序号,如 20260606000001), 生成唯一条码(纯数字:yyyyMMdd + 6位序号,如 20260606000001), 生成唯一条码(纯数字:yyyyMMdd + 6位序号,如 20260606000001), 生成唯一条码(纯数字:yyyyMMdd + 6位序号,如 20260606000001), 生成唯一条码(纯数字:yyyyMMdd + 6位序号,如 20260606000001)
|
||||
|
||||
## Knowledge Gaps
|
||||
- **64 isolated node(s):** `Session`, `bool`, `bool`, `Config`, `bool` (+59 more)
|
||||
- **66 isolated node(s):** `Session`, `bool`, `bool`, `Config`, `bool` (+61 more)
|
||||
These have ≤1 connection - possible missing edges or undocumented components.
|
||||
- **4 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
- **1 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
|
||||
## Suggested Questions
|
||||
_Questions this graph is uniquely positioned to answer:_
|
||||
|
||||
- **Why does `Session` connect `Community 3` to `Community 1`, `Community 2`, `Community 131`, `Community 4`, `Community 133`, `Community 6`, `Community 7`, `Community 136`, `Community 8`, `Community 9`, `Community 11`, `Community 10`, `Community 12`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 27`, `Community 29`, `Community 30`, `Community 32`, `Community 33`, `Community 35`, `Community 36`, `Community 37`, `Community 40`, `Community 41`, `Community 44`, `Community 45`, `Community 46`, `Community 47`, `Community 48`, `Community 50`, `Community 51`, `Community 52`, `Community 53`, `Community 54`, `Community 55`, `Community 56`, `Community 57`, `Community 59`, `Community 60`, `Community 61`, `Community 63`, `Community 64`, `Community 65`, `Community 66`, `Community 67`, `Community 69`, `Community 73`, `Community 74`, `Community 77`, `Community 78`, `Community 80`, `Community 81`, `Community 83`, `Community 84`, `Community 87`, `Community 89`, `Community 90`, `Community 97`, `Community 102`, `Community 103`, `Community 104`, `Community 105`, `Community 107`, `Community 108`, `Community 112`?**
|
||||
_High betweenness centrality (0.237) - this node is a cross-community bridge._
|
||||
- **Why does `BaseModel` connect `Community 16` to `Community 0`, `Community 1`, `Community 2`, `Community 4`, `Community 6`, `Community 11`, `Community 12`, `Community 15`, `Community 20`, `Community 21`, `Community 149`, `Community 23`, `Community 28`, `Community 30`, `Community 31`, `Community 41`, `Community 47`, `Community 50`, `Community 53`, `Community 62`, `Community 69`, `Community 74`, `Community 87`, `Community 103`?**
|
||||
_High betweenness centrality (0.060) - this node is a cross-community bridge._
|
||||
- **Why does `generate_uuid()` connect `Community 63` to `Community 0`, `Community 4`, `Community 7`, `Community 136`, `Community 10`, `Community 11`, `Community 12`, `Community 14`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `Community 21`, `Community 24`, `Community 27`, `Community 29`, `Community 33`, `Community 35`, `Community 36`, `Community 37`, `Community 39`, `Community 40`, `Community 43`, `Community 44`, `Community 47`, `Community 50`, `Community 51`, `Community 52`, `Community 56`, `Community 59`, `Community 60`, `Community 64`, `Community 65`, `Community 66`, `Community 67`, `Community 71`, `Community 73`, `Community 74`, `Community 78`, `Community 81`, `Community 89`, `Community 97`, `Community 103`, `Community 104`, `Community 108`?**
|
||||
_High betweenness centrality (0.042) - this node is a cross-community bridge._
|
||||
- **Why does `Session` connect `Community 3` to `Community 1`, `Community 2`, `Community 4`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 11`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 22`, `Community 23`, `Community 25`, `Community 27`, `Community 29`, `Community 30`, `Community 33`, `Community 35`, `Community 36`, `Community 37`, `Community 39`, `Community 40`, `Community 41`, `Community 43`, `Community 45`, `Community 46`, `Community 47`, `Community 48`, `Community 50`, `Community 51`, `Community 52`, `Community 54`, `Community 55`, `Community 56`, `Community 57`, `Community 59`, `Community 60`, `Community 61`, `Community 63`, `Community 64`, `Community 66`, `Community 68`, `Community 69`, `Community 75`, `Community 77`, `Community 78`, `Community 80`, `Community 81`, `Community 84`, `Community 85`, `Community 86`, `Community 87`, `Community 88`, `Community 89`, `Community 90`, `Community 92`, `Community 95`, `Community 96`, `Community 97`, `Community 98`, `Community 100`, `Community 102`, `Community 103`, `Community 104`, `Community 112`, `Community 122`, `Community 124`, `Community 127`, `Community 128`, `Community 131`, `Community 133`, `Community 136`, `Community 137`, `Community 139`, `Community 146`, `Community 147`?**
|
||||
_High betweenness centrality (0.212) - this node is a cross-community bridge._
|
||||
- **Why does `BaseModel` connect `Community 35` to `Community 0`, `Community 129`, `Community 2`, `Community 4`, `Community 134`, `Community 11`, `Community 12`, `Community 16`, `Community 145`, `Community 20`, `Community 21`, `Community 23`, `Community 24`, `Community 25`, `Community 28`, `Community 31`, `Community 32`, `Community 36`, `Community 44`, `Community 49`, `Community 50`, `Community 51`, `Community 62`, `Community 63`, `Community 65`, `Community 68`, `Community 69`, `Community 75`, `Community 80`, `Community 85`, `Community 93`, `Community 102`, `Community 103`, `Community 105`?**
|
||||
_High betweenness centrality (0.066) - this node is a cross-community bridge._
|
||||
- **Why does `SysUser` connect `Community 80` to `Community 2`, `Community 131`, `Community 3`, `Community 6`, `Community 7`, `Community 9`, `Community 11`, `Community 12`, `Community 14`, `Community 17`, `Community 19`, `Community 22`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 32`, `Community 33`, `Community 39`, `Community 43`, `Community 47`, `Community 50`, `Community 51`, `Community 52`, `Community 56`, `Community 64`, `Community 86`, `Community 98`, `Community 103`, `Community 104`, `Community 126`?**
|
||||
_High betweenness centrality (0.050) - this node is a cross-community bridge._
|
||||
- **Are the 251 inferred relationships involving `str` (e.g. with `AdvanceApplyCreate` and `AdvanceApplyOut`) actually correct?**
|
||||
_`str` has 251 INFERRED edges - model-reasoned connections that need verification._
|
||||
- **Are the 248 inferred relationships involving `Session` (e.g. with `AdvanceApplyCreate` and `AdvanceApplyOut`) actually correct?**
|
||||
@@ -622,4 +704,4 @@ _Questions this graph is uniquely positioned to answer:_
|
||||
- **Are the 140 inferred relationships involving `int` (e.g. with `AdvanceApplyCreate` and `AdvanceApplyOut`) actually correct?**
|
||||
_`int` has 140 INFERRED edges - model-reasoned connections that need verification._
|
||||
- **What connects `数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误`, `未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin`, `每月 1 日凌晨执行:按上月计划模板生成本月计划。` to the rest of the system?**
|
||||
_995 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
_1184 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
backend/graphify-out/cache/ast/008550fe8efce5310afd3d41d944cee4b68d71763a587583f8832855480477f7.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_role_py", "label": "role.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L1"}, {"id": "crud_role_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L21"}, {"id": "sysrole", "label": "SysRole", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L21"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L21"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L21"}, {"id": "crud_role_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L36"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L36"}, {"id": "crud_role_list_roles", "label": "list_roles()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L47"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L47"}, {"id": "crud_role_get_role", "label": "get_role()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L82"}, {"id": "crud_role_role_name_exists", "label": "role_name_exists()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L90"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L90"}, {"id": "crud_role_create_role", "label": "create_role()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L104"}, {"id": "crud_role_update_role", "label": "update_role()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L141"}, {"id": "crud_role_soft_delete_role", "label": "soft_delete_role()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L178"}, {"id": "crud_role_get_role_menu_ids", "label": "get_role_menu_ids()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L201"}, {"id": "crud_role_update_role_menus", "label": "update_role_menus()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L210"}, {"id": "crud_role_get_role_data_scope", "label": "get_role_data_scope()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L245"}, {"id": "crud_role_update_role_data_scope", "label": "update_role_data_scope()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L254"}, {"id": "crud_role_rationale_1", "label": "\u89d2\u8272 CRUD\uff08sys_role / sys_role_menu / sys_role_data_scope_detail\uff09", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L1"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_role_py", "target": "math", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_role_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L5", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_role_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L6", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_role_py", "target": "sqlalchemy", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/role.py", "source_location": "L8", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_role_py", "target": "sqlalchemy_orm", "relation"Line truncated
|
||||
backend/graphify-out/cache/ast/00a80272a9af1e937d9c81b8af3b7dc4ec9210501096ee150b34fb4e89dd53f1.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L1"}, {"id": "pl_init_rationale_1", "label": "\u8fd0\u8f93\u8ba1\u5212\u7ba1\u7406 - Pydantic Schema", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_zlwu_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": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_plan", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L28", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_transport", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L44", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_freight", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L57", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_inventory", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L69", "weight": 1.0}, {"source": "pl_init_rationale_1", "target": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
|
||||
backend/graphify-out/cache/ast/0192db8ac9c3de42fe8ee58324c77111e152bf4c35fd15c0b16467fe243712f0.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_tenant_py", "label": "tenant.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L1"}, {"id": "crud_tenant_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L17"}, {"id": "systenant", "label": "SysTenant", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L17"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L17"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L17"}, {"id": "crud_tenant_list_tenants", "label": "list_tenants()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L32"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L32"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L32"}, {"id": "crud_tenant_get_tenant", "label": "get_tenant()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L64"}, {"id": "crud_tenant_tenant_no_exists", "label": "tenant_no_exists()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L72"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L72"}, {"id": "crud_tenant_create_tenant", "label": "create_tenant()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L79"}, {"id": "crud_tenant_update_tenant", "label": "update_tenant()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L116"}, {"id": "crud_tenant_soft_delete_tenant", "label": "soft_delete_tenant()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L158"}, {"id": "crud_tenant_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L171"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_tenant_py", "target": "math", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_tenant_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L5", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_tenant_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L6", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_tenant_py", "target": "sqlalchemy", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L8", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_tenant_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L9", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_tenant_py", "target": "app_core_config", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L11", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_tenant_py", "target": "app_core_delete_guard", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/tenant.py", "source_location": "L12", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_tenant_py", "target": "app_core_utils", "relation": "imports_from", "context": "import", "confidence": "EXTLine truncated
|
||||
backend/graphify-out/cache/ast/01f974f182660ec4f215bcacc31eddadd7899d60d95aa19a61046579a994512a.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_models_models_py", "label": "models.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L1"}, {"id": "models_models_userrole", "label": "UserRole", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L11"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "models_models_sysuser", "label": "SysUser", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L29"}, {"id": "base", "label": "Base", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "models_models_sysrole", "label": "SysRole", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L56"}, {"id": "models_models_sysuserrole", "label": "SysUserRole", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L77"}, {"id": "models_models_sysrolemenu", "label": "SysRoleMenu", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L97"}, {"id": "models_models_sysroledatascopedetail", "label": "SysRoleDataScopeDetail", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L117"}, {"id": "models_models_sysmenu", "label": "SysMenu", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L138"}, {"id": "models_models_sysdept", "label": "SysDept", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L165"}, {"id": "models_models_systenant", "label": "SysTenant", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L189"}, {"id": "models_models_syssite", "label": "SysSite", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L210"}, {"id": "models_models_syspost", "label": "SysPost", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L236"}, {"id": "models_models_sysuserpost", "label": "SysUserPost", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L257"}, {"id": "models_models_sysdicttype", "label": "SysDictType", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L282"}, {"id": "models_models_sysethnic", "label": "SysEthnic", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L302"}, {"id": "models_models_sysdictdata", "label": "SysDictData", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L322"}, {"id": "models_models_sysapirequestlog", "label": "SysApiRequestLog", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L354"}, {"id": "models_models_sysapirequestloghis", "label": "SysApiRequestLogHis", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L386"}, {"id": "models_models_bizleaverequest", "label": "BizLeaveRequest", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L428"}, {"id": "models_models_bizattachment", "label": "BizAttachment", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L458"}, {"id": "models_models_wfprocessdef", "label": "WfProcessDef", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L488"}, {"id": "models_models_wfprocessversion", "label": "WfProcessVersion", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L509"}, {"id": "models_models_wfnode", "label": "WfNode", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L531"}, {"id": "models_models_wftransition", "label": "WfTransition", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_location": "L554"}, {"id": "models_models_wfprocessinstance", "label": "WfProcessInstance", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py", "source_lLine truncated
|
||||
backend/graphify-out/cache/ast/02be7d668369881dd370344a8c045b06393c6cdc69a133fc053f5d8c4a5e8529.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_admin_py", "label": "admin.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L1"}, {"id": "api_admin_list_roles_for_select", "label": "list_roles_for_select()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L42"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L42"}, {"id": "api_admin_list_users_for_select", "label": "list_users_for_select()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L74"}, {"id": "api_admin_list_roles_for_designer", "label": "list_roles_for_designer()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L97"}, {"id": "api_admin_tenantcreatebody", "label": "TenantCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L117"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "api_admin_tenantupdatebody", "label": "TenantUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L128"}, {"id": "api_admin_list_tenants", "label": "list_tenants()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L140"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L140"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L140"}, {"id": "api_admin_create_tenant", "label": "create_tenant()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L161"}, {"id": "api_admin_update_tenant", "label": "update_tenant()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L183"}, {"id": "api_admin_delete_tenant", "label": "delete_tenant()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L208"}, {"id": "api_admin_get_tenant_no", "label": "_get_tenant_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L225"}, {"id": "api_admin_usercreatebody", "label": "UserCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L238"}, {"id": "api_admin_userupdatebody", "label": "UserUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L255"}, {"id": "api_admin_list_users", "label": "list_users()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L271"}, {"id": "api_admin_get_user_by_id", "label": "get_user_by_id()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L315"}, {"id": "api_admin_create_user", "label": "create_user()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L335"}, {"id": "api_admin_update_user", "label": "update_user()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L368"}, {"id": "api_admin_delete_user", "label": "delete_user()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L398"}, {"id": "api_admin_reset_user_password", "label": "reset_user_password()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L411"}, {"id": "api_admin_dicttypecreatebody", "label": "DictTypeCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L428"}, {"id": "api_admin_dicttypeupdatebody", "label": "DictTypeUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L437"}, {"id": "api_admin_list_dict_types", "label": "list_dict_types()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py", "source_location": "L447"}, {"id": "api_admin_create_dict_type", "label": "create_dict_type()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/Line truncated
|
||||
backend/graphify-out/cache/ast/03d389e791e5fac9a5339d1259d342952fced97712c3657e8f791426de32743b.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_vehicle_urea_service_py", "label": "pl_vehicle_urea_service.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L1"}, {"id": "services_pl_vehicle_urea_service_generate_seq_no", "label": "_generate_seq_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L27"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L27"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L27"}, {"id": "services_pl_vehicle_urea_service_generate_apply_no", "label": "generate_apply_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L39"}, {"id": "services_pl_vehicle_urea_service_generate_disburse_no", "label": "generate_disburse_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L45"}, {"id": "services_pl_vehicle_urea_service_resolve_user_name", "label": "_resolve_user_name()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L51"}, {"id": "services_pl_vehicle_urea_service_assert_vehicle_exists", "label": "_assert_vehicle_exists()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L62"}, {"id": "services_pl_vehicle_urea_service_resolve_user_id", "label": "_resolve_user_id()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L76"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L76"}, {"id": "services_pl_vehicle_urea_service_validate_apply_dates", "label": "_validate_apply_dates()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L100"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L100"}, {"id": "services_pl_vehicle_urea_service_get_apply_row", "label": "_get_apply_row()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L105"}, {"id": "plvehicleureaapply", "label": "PlVehicleUreaApply", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L105"}, {"id": "services_pl_vehicle_urea_service_get_disburse_row", "label": "_get_disburse_row()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L123"}, {"id": "plvehicleureadisburse", "label": "PlVehicleUreaDisburse", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L123"}, {"id": "services_pl_vehicle_urea_service_has_active_disburse", "label": "_has_active_disburse()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L141"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L141"}, {"id": "services_pl_vehicle_urea_service_apply_to_read", "label": "apply_to_read()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L154"}, {"id": "vehicleureaapplyread", "label": "VehicleUreaApplyRead", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L154"}, {"id": "services_pl_vehicle_urea_service_disburse_to_read", "label": "disburse_to_read()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L174"}, {"id": "vehicleureadisburseread", "label": "VehicleUreaDisburseRead", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py", "source_location": "L174"}, {"id":Line truncated
|
||||
backend/graphify-out/cache/ast/046f6158240da836baabd74cf39709425e05fae6ec137442bc5ba17c33faf47a.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L1"}, {"id": "pl_init_rationale_1", "label": "\u8fd0\u8f93\u8ba1\u5212\u7ba1\u7406 - Pydantic Schema", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_zlwu_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": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_plan", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L29", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_transport", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L45", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_freight", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L58", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "target": "app_schemas_pl_inventory", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L70", "weight": 1.0}, {"source": "pl_init_rationale_1", "target": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_pl_init_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/pl/__init__.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
|
||||
backend/graphify-out/cache/ast/0586702734f693ee099af38d407c71cc0a840153f93174699c5a5201e0a9acbe.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_monitor_oper_log_py", "label": "oper_log.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L1"}, {"id": "monitor_oper_log_oper_status", "label": "_oper_status()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L16"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L16"}, {"id": "monitor_oper_log_row_to_oper_log", "label": "_row_to_oper_log()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L20"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L20"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L20"}, {"id": "monitor_oper_log_apply_oper_log_scope", "label": "_apply_oper_log_scope()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L56"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L56"}, {"id": "monitor_oper_log_oper_log_row_in_scope", "label": "_oper_log_row_in_scope()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L102"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L102"}, {"id": "monitor_oper_log_list_oper_logs", "label": "list_oper_logs()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L112"}, {"id": "monitor_oper_log_get_oper_log", "label": "get_oper_log()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L180"}, {"id": "monitor_oper_log_rationale_1", "label": "\u64cd\u4f5c\u65e5\u5fd7\uff1a\u57fa\u4e8e sys_api_request_log \u9002\u914d RuoYi OperLog \u89c6\u56fe", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L1"}, {"id": "monitor_oper_log_rationale_57", "label": "\u64cd\u4f5c\u65e5\u5fd7\u6309 oper_user_id\uff08user_id/create_by\uff09\u5e94\u7528\u6570\u636e\u6743\u9650\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L57"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_monitor_oper_log_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_monitor_oper_log_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L5", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_monitor_oper_log_py", "target": "sqlalchemy", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L7", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_monitor_oper_log_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L8", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_monitor_oper_log_py", "target": "app_core_oper_log_infer", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L10", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_monitor_oper_log_py", "target": "app_models_models", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/monitor/oper_log.py", "source_location": "L11", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_monitor_oper_log_py"Line truncated
|
||||
backend/graphify-out/cache/ast/064c356f3f0fbabc454311445f02d375b965c2c055ddc51386d4dad50ac3337a.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_core_oper_log_context_py", "label": "oper_log_context.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L1"}, {"id": "core_oper_log_context_operlogmeta", "label": "OperLogMeta", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L10"}, {"id": "core_oper_log_context_set_oper_log_meta", "label": "set_oper_log_meta()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L20"}, {"id": "core_oper_log_context_get_oper_log_meta", "label": "get_oper_log_meta()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L24"}, {"id": "core_oper_log_context_clear_oper_log_meta", "label": "clear_oper_log_meta()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L28"}, {"id": "core_oper_log_context_snapshot_oper_log_meta", "label": "snapshot_oper_log_meta()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L32"}, {"id": "core_oper_log_context_rationale_1", "label": "\u8bf7\u6c42\u7ea7\u64cd\u4f5c\u65e5\u5fd7\u5143\u6570\u636e\uff08ContextVar\uff09", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L1"}, {"id": "core_oper_log_context_rationale_33", "label": "\u4f9b\u5f02\u6b65\u843d\u5e93\u7ebf\u7a0b\u4f7f\u7528\uff0c\u907f\u514d ContextVar \u8de8\u7ebf\u7a0b\u4e22\u5931\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L33"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_oper_log_context_py", "target": "contextvars", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_oper_log_context_py", "target": "dataclasses", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L5", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_oper_log_context_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L6", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_oper_log_context_py", "target": "core_oper_log_context_operlogmeta", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L10", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_oper_log_context_py", "target": "core_oper_log_context_set_oper_log_meta", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L20", "weight": 1.0}, {"source": "core_oper_log_context_set_oper_log_meta", "target": "core_oper_log_context_operlogmeta", "relation": "references", "context": "parameter_type", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L20", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_oper_log_context_py", "target": "core_oper_log_context_get_oper_log_meta", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L24", "weight": 1.0}, {"source": "core_oper_log_context_get_oper_log_meta", "target": "core_oper_log_context_operlogmeta", "relation": "references", "context": "generic_arg", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L24", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_oper_log_context_py", "target": "core_oper_log_context_clear_oper_log_meta", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/oper_log_context.py", "source_location": "L28", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_oper_log_context_py", "target": "core_oper_log_context_snapshot_oper_log_meta", "relation": "contains", "confLine truncated
|
||||
backend/graphify-out/cache/ast/07c2c8bcf9675e37d9a4b066c0071b8e3e63d6c2a0ac314c754b2dd47c0d8539.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_db_plans_py", "label": "plans.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L1"}, {"id": "db_plans_base_query", "label": "_base_query()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L45"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L45"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L45"}, {"id": "db_plans_list_plans", "label": "list_plans()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L53"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L53"}, {"id": "purchaseplanwithfilllink", "label": "PurchasePlanWithFillLink", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L53"}, {"id": "db_plans_generate_plan_no", "label": "_generate_plan_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L191"}, {"id": "db_plans_create_plan", "label": "create_plan()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L198"}, {"id": "purchaseplancreate", "label": "PurchasePlanCreate", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L198"}, {"id": "purchaseplanread", "label": "PurchasePlanRead", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L198"}, {"id": "db_plans_autogeneratemonthlyresult", "label": "AutoGenerateMonthlyResult", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L241"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "db_plans_run_auto_generate_monthly", "label": "run_auto_generate_monthly()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L248"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L248"}, {"id": "db_plans_get_plan", "label": "get_plan()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L262"}, {"id": "db_plans_update_plan", "label": "update_plan()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L273"}, {"id": "purchaseplanupdate", "label": "PurchasePlanUpdate", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L273"}, {"id": "db_plans_delete_plan", "label": "delete_plan()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L291"}, {"id": "dbpurchaseplan", "label": "DbPurchasePlan", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L303"}, {"id": "db_plans_get_plan_by_id_only", "label": "_get_plan_by_id_only()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L320"}, {"id": "db_plans_apply_plan_product_update", "label": "_apply_plan_product_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L334"}, {"id": "db_plans_apply_plan_update", "label": "_apply_plan_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L350"}, {"id": "db_plans_planrelateddata", "label": "PlanRelatedData", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L371"}, {"id": "db_plans_get_plan_related", "label": "get_plan_related()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L377"}, {"id": "db_plans_thirdpartyplanpagedata", "label": "ThirdPartyPlanPageData", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/plans.py", "source_location": "L418"}, {"id": "db_plans_get_third_party_plan_page", "label": "get_third_party_plan_page()", "file_Line truncated
|
||||
backend/graphify-out/cache/ast/0a947967a485bc11cf332d199b5e7a56adff996a9bf959af0b2e0552510a9cb3.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_py", "label": "crm.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L1"}, {"id": "api_crm_category_tree", "label": "category_tree()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L326"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L326"}, {"id": "api_crm_customer_list", "label": "customer_list()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L357"}, {"id": "customerlistbody", "label": "CustomerListBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L357"}, {"id": "api_crm_customer_detail", "label": "customer_detail()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L391"}, {"id": "api_crm_customer_create", "label": "customer_create()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L417"}, {"id": "customercreatebody", "label": "CustomerCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L417"}, {"id": "api_crm_customer_update", "label": "customer_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L446"}, {"id": "customerupdatebody", "label": "CustomerUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L446"}, {"id": "api_crm_customer_delete", "label": "customer_delete()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L472"}, {"id": "customerdeletebody", "label": "CustomerDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L472"}, {"id": "api_crm_customer_generate_no", "label": "customer_generate_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L500"}, {"id": "api_crm_category_add", "label": "category_add()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L516"}, {"id": "productcategorycreatebody", "label": "ProductCategoryCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L516"}, {"id": "api_crm_category_update", "label": "category_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L536"}, {"id": "productcategoryupdatebody", "label": "ProductCategoryUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L536"}, {"id": "api_crm_category_delete", "label": "category_delete()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L555"}, {"id": "categorydeletebody", "label": "CategoryDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L555"}, {"id": "api_crm_product_list", "label": "product_list()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L574"}, {"id": "productlistbody", "label": "ProductListBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L574"}, {"id": "api_crm_product_detail", "label": "product_detail()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L610"}, {"id": "productdetailbody", "label": "ProductDetailBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L610"}, {"id": "api_crm_product_add", "label": "product_add()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L631"}, {"id": "productcreatebody", "label": "ProductCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L631"}, {"id": "api_crm_product_update", "label": "product_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L655"}, {"id": "productupdatebody", "label": "ProductUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.Line truncated
|
||||
backend/graphify-out/cache/ast/0b66714e36609688f7ce79ecda0679d20df115d2d64a0dff1cc0fc76df7dca6b.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_supplier_info_py", "label": "supplier_info.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L1"}, {"id": "crm_supplier_info_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L36"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L36"}, {"id": "crm_supplier_info_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L42"}, {"id": "crmsupplierinfo", "label": "CrmSupplierInfo", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L42"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L42"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L42"}, {"id": "crm_supplier_info_apply_list_filters", "label": "_apply_list_filters()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L65"}, {"id": "crm_supplier_info_list_supplier_info", "label": "list_supplier_info()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L74"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L74"}, {"id": "crm_supplier_info_list_all_supplier_info", "label": "list_all_supplier_info()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L102"}, {"id": "crm_supplier_info_get_supplier_info", "label": "get_supplier_info()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L122"}, {"id": "crm_supplier_info_create_supplier_info", "label": "create_supplier_info()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L138"}, {"id": "crm_supplier_info_update_supplier_info", "label": "update_supplier_info()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L176"}, {"id": "crm_supplier_info_soft_delete_supplier_info", "label": "soft_delete_supplier_info()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L212"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L212"}, {"id": "crm_supplier_info_rationale_1", "label": "CRM \u4f9b\u65b9\u4fe1\u606f CRUD \u2014 \u5bf9\u7167 CrmSupplierInfoServiceImpl.java", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L1"}, {"id": "crm_supplier_info_rationale_43", "label": "ORM \u2192 camelCase\uff08\u5355\u70b9\u771f\u76f8\uff09", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L43"}, {"id": "crm_supplier_info_rationale_66", "label": "Java: buildQueryWrapper", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L66"}, {"id": "crm_supplier_info_rationale_110", "label": "Java: queryList\uff08\u4e0d\u5206\u9875\uff0c\u62a5\u4ef7\u5355\u4e0b\u62c9\uff09", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L110"}, {"id": "crm_supplier_info_rationale_220", "label": "Java: deleteWithValidByIds\uff08\u95e8\u6237\u8f6f\u5220\uff09", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L220"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_supplier_info_py", "target": "math", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/supplier_info.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_supplier_info_py", "target":Line truncated
|
||||
backend/graphify-out/cache/ast/0b84c32828939e6102bb3647691fa8f04b1f68e0e0e9f28127374cc44e19a11c.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_core_api_request_log_middleware_py", "label": "api_request_log_middleware.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L1"}, {"id": "core_api_request_log_middleware_truncate", "label": "_truncate()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L37"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L37"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L37"}, {"id": "core_api_request_log_middleware_truncate_utf8_bytes", "label": "_truncate_utf8_bytes()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L49"}, {"id": "core_api_request_log_middleware_redact_obj", "label": "_redact_obj()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L65"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L65"}, {"id": "core_api_request_log_middleware_format_body_bytes", "label": "_format_body_bytes()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L84"}, {"id": "bytes", "label": "bytes", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L84"}, {"id": "core_api_request_log_middleware_claims_from_auth", "label": "_claims_from_auth()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L111"}, {"id": "core_api_request_log_middleware_should_skip_exclude", "label": "_should_skip_exclude()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L137"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L137"}, {"id": "core_api_request_log_middleware_should_skip", "label": "_should_skip()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L150"}, {"id": "request", "label": "Request", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L150"}, {"id": "core_api_request_log_middleware_persist_log", "label": "_persist_log()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L163"}, {"id": "core_api_request_log_middleware_schedule_persist_log", "label": "_schedule_persist_log()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L224"}, {"id": "core_api_request_log_middleware_apirequestlogmiddleware", "label": "ApiRequestLogMiddleware", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L247"}, {"id": "basehttpmiddleware", "label": "BaseHTTPMiddleware", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "core_api_request_log_middleware_apirequestlogmiddleware_dispatch", "label": ".dispatch()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L248"}, {"id": "core_api_request_log_middleware_rationale_1", "label": "\u7edf\u4e00\u8bb0\u5f55 API \u8bf7\u6c42/\u54cd\u5e94\u51fa\u5165\u53c2\u7684\u4e2d\u95f4\u4ef6", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L1"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_api_request_log_middleware_py", "target": "asyncio", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py", "source_location": "L2", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_api_request_log_middleware_py", "target": "json", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/Line truncated
|
||||
backend/graphify-out/cache/ast/0c1e630117fede22616c445ea1348e75546f20932900a6a6551d840340b82ce9.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_vehicle_maintenance_service_py", "label": "pl_vehicle_maintenance_service.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L1"}, {"id": "services_pl_vehicle_maintenance_service_generate_seq_no", "label": "_generate_seq_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L32"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L32"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L32"}, {"id": "services_pl_vehicle_maintenance_service_generate_apply_no", "label": "generate_apply_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L44"}, {"id": "services_pl_vehicle_maintenance_service_generate_process_no", "label": "generate_process_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L50"}, {"id": "services_pl_vehicle_maintenance_service_resolve_user_name", "label": "_resolve_user_name()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L59"}, {"id": "services_pl_vehicle_maintenance_service_assert_vehicle_exists", "label": "_assert_vehicle_exists()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L70"}, {"id": "services_pl_vehicle_maintenance_service_resolve_user_id", "label": "_resolve_user_id()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L84"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L84"}, {"id": "services_pl_vehicle_maintenance_service_validate_apply_dates", "label": "_validate_apply_dates()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L108"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L108"}, {"id": "services_pl_vehicle_maintenance_service_get_apply_row", "label": "_get_apply_row()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L119"}, {"id": "plvehiclemaintenanceapply", "label": "PlVehicleMaintenanceApply", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L119"}, {"id": "services_pl_vehicle_maintenance_service_get_process_row", "label": "_get_process_row()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L137"}, {"id": "plvehiclemaintenanceprocess", "label": "PlVehicleMaintenanceProcess", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L137"}, {"id": "services_pl_vehicle_maintenance_service_has_active_process", "label": "_has_active_process()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L155"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L155"}, {"id": "services_pl_vehicle_maintenance_service_apply_to_read", "label": "apply_to_read()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L168"}, {"id": "vehiclemaintenanceapplyread", "label": "VehicleMaintenanceApplyRead", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maintenance_service.py", "source_location": "L168"}, {"id": "services_pl_vehicle_maintenance_service_process_to_read", "label": "process_to_read()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_maLine truncated
|
||||
backend/graphify-out/cache/ast/0f0275c56a43603e69a030dd1d284cfc7982a7636423cfff6d0e692eb4631cb1.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_data_scope_py", "label": "pl_data_scope.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L1"}, {"id": "services_pl_data_scope_apply_pl_scope", "label": "apply_pl_scope()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L14"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L14"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L14"}, {"id": "services_pl_data_scope_assert_pl_row_in_scope", "label": "assert_pl_row_in_scope()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L19"}, {"id": "services_pl_data_scope_rationale_1", "label": "PL \u6a21\u5757\u6570\u636e\u6743\u9650\u8f85\u52a9\uff08create_by / create_dept_id\uff0c\u65e0 owner_field\uff09\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L1"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_data_scope_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_data_scope_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L6", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_data_scope_py", "target": "app_core_data_scope", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L8", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_data_scope_py", "target": "app_core_db_auth", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L11", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_data_scope_py", "target": "services_pl_data_scope_apply_pl_scope", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L14", "weight": 1.0}, {"source": "services_pl_data_scope_apply_pl_scope", "target": "session", "relation": "references", "context": "parameter_type", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L14", "weight": 1.0}, {"source": "services_pl_data_scope_apply_pl_scope", "target": "str", "relation": "references", "context": "parameter_type", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L14", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_data_scope_py", "target": "services_pl_data_scope_assert_pl_row_in_scope", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L19", "weight": 1.0}, {"source": "services_pl_data_scope_assert_pl_row_in_scope", "target": "session", "relation": "references", "context": "parameter_type", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L19", "weight": 1.0}, {"source": "services_pl_data_scope_assert_pl_row_in_scope", "target": "str", "relation": "references", "context": "parameter_type", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L19", "weight": 1.0}, {"source": "services_pl_data_scope_rationale_1", "target": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_data_scope_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_data_scope.py", "source_location": "L1", "weight": 1.0}], "raw_calls": [{"caller_nid": "services_pl_data_scope_apply_pl_scope", "callee": "apply_data_scope", "is_member_call": false, "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/serviLine truncated
|
||||
backend/graphify-out/cache/ast/0f0677d06d513e591e9fbb4d305ad3ff63020d32dae09209bafd115ab09d5ee0.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_pl_freight_py", "label": "freight.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L1"}, {"id": "pl_freight_generate_settlement_no", "label": "_generate_settlement_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L29"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L29"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L29"}, {"id": "pl_freight_list_unsettled_plans", "label": "list_unsettled_plans()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L48"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L48"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L48"}, {"id": "unsettledplanlistresponse", "label": "UnsettledPlanListResponse", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L48"}, {"id": "pl_freight_list_settlements", "label": "list_settlements()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L71"}, {"id": "freightsettlementlistresponse", "label": "FreightSettlementListResponse", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L71"}, {"id": "pl_freight_create_settlement", "label": "create_settlement()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L98"}, {"id": "freightsettlementcreate", "label": "FreightSettlementCreate", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L98"}, {"id": "freightsettlementread", "label": "FreightSettlementRead", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L98"}, {"id": "pl_freight_get_settlement", "label": "get_settlement()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L117"}, {"id": "freightsettlementwithtransports", "label": "FreightSettlementWithTransports", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L117"}, {"id": "pl_freight_update_settlement", "label": "update_settlement()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L130"}, {"id": "freightsettlementupdate", "label": "FreightSettlementUpdate", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L130"}, {"id": "pl_freight_delete_settlement", "label": "delete_settlement()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L143"}, {"id": "pl_freight_submit_settlement_approval", "label": "submit_settlement_approval()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L155"}, {"id": "freightsettlementsubmitapprovalbody", "label": "FreightSettlementSubmitApprovalBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L155"}, {"id": "pl_freight_process_settlement_approval", "label": "process_settlement_approval()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L173"}, {"id": "freightsettlementapprovalactionbody", "label": "FreightSettlementApprovalActionBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L173"}, {"id": "pl_freight_withdraw_settlement_approval", "label": "withdraw_settlement_approval()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L193"}, {"id": "pl_freight_get_settlement_approval_flow", "label": "get_settlement_approval_flow()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/freight.py", "source_location": "L205"}, {"id": "pl_freight_disburse_settlement", "label": "disburse_settlement()", "file_Line truncated
|
||||
backend/graphify-out/cache/ast/132d2dfe1922a1558395fd11642a772d80612004a5acf735abdcf6b9386877b2.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_contact_py", "label": "contact.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L1"}, {"id": "crm_contact_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L20"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L20"}, {"id": "crm_contact_parse_optional_date", "label": "_parse_optional_date()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L30"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L30"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L30"}, {"id": "crm_contact_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L48"}, {"id": "crmcontact", "label": "CrmContact", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L48"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L48"}, {"id": "crm_contact_list_contacts", "label": "list_contacts()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L77"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L77"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L77"}, {"id": "crm_contact_list_contacts_by_customer", "label": "list_contacts_by_customer()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L153"}, {"id": "crm_contact_get_contact", "label": "get_contact()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L182"}, {"id": "crm_contact_create_contact", "label": "create_contact()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L193"}, {"id": "crm_contact_update_contact", "label": "update_contact()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L254"}, {"id": "crm_contact_set_primary_contact", "label": "set_primary_contact()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L295"}, {"id": "crm_contact_soft_delete_contact", "label": "soft_delete_contact()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L324"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_contact_py", "target": "math", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_contact_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L5", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_contact_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L6", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_contact_py", "target": "sqlalchemy", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L8", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_contact_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py", "source_location": "L9", "weight": 1.0}, {"source": "users_zlwu_work_00coLine truncated
|
||||
backend/graphify-out/cache/ast/1423ba01c1cdf0a3347d778b18f72fff1e7367f201425d6474b3a08cf93a255a.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_pl_plan_py", "label": "plan.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L1"}, {"id": "pl_plan_generate_plan_no", "label": "_generate_plan_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L66"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L66"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L66"}, {"id": "pl_plan_determine_plan_type", "label": "_determine_plan_type()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L88"}, {"id": "pl_plan_get_plan_type_label", "label": "_get_plan_type_label()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L93"}, {"id": "pl_plan_generate_notify_message", "label": "_generate_notify_message()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L102"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L102"}, {"id": "pl_plan_list_plans", "label": "list_plans()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L123"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L123"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L123"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L123"}, {"id": "planlistresponse", "label": "PlanListResponse", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L123"}, {"id": "pl_plan_count_plans_by_contract", "label": "count_plans_by_contract()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L224"}, {"id": "pl_plan_create_plan", "label": "create_plan()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L252"}, {"id": "plancreate", "label": "PlanCreate", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L252"}, {"id": "planread", "label": "PlanRead", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L252"}, {"id": "pl_plan_create_plan_full", "label": "create_plan_full()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L331"}, {"id": "plancreatefull", "label": "PlanCreateFull", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L331"}, {"id": "planwithproducts", "label": "PlanWithProducts", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L331"}, {"id": "pl_plan_create_from_contract", "label": "create_from_contract()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L435"}, {"id": "createfromcontractrequest", "label": "CreateFromContractRequest", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L435"}, {"id": "pl_plan_get_plan", "label": "get_plan()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L544"}, {"id": "pl_plan_update_plan", "label": "update_plan()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L587"}, {"id": "planupdate", "label": "PlanUpdate", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L587"}, {"id": "pl_plan_patch_plan_product_certs", "label": "patch_plan_product_certs()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/pl/plan.py", "source_location": "L696"}, {"id": "planproductcertsupdate", "label": "PlanProductCertsUpdate", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/Line truncated
|
||||
backend/graphify-out/cache/ast/14f4705ed9ca680e3c345357bf70c62bcfe77670d8da8f0911b5d0fa5ca26e55.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_db_inventory_py", "label": "inventory.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L1"}, {"id": "db_inventory_base_query", "label": "_base_query()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L47"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L47"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L47"}, {"id": "db_inventory_generate_batch_no", "label": "_generate_batch_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L54"}, {"id": "db_inventory_resolve_tenant_no", "label": "_resolve_tenant_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L74"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L74"}, {"id": "db_inventory_get_inventory_by_id", "label": "_get_inventory_by_id()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L92"}, {"id": "dbinboundoutbound", "label": "DbInboundOutbound", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L92"}, {"id": "db_inventory_get_inventory", "label": "_get_inventory()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L106"}, {"id": "db_inventory_apply_inventory_product_update", "label": "_apply_inventory_product_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L123"}, {"id": "db_inventory_apply_inventory_update", "label": "_apply_inventory_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L139"}, {"id": "db_inventory_maybe_promote_third_party_status", "label": "_maybe_promote_third_party_status()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L157"}, {"id": "db_inventory_list_inventory", "label": "list_inventory()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L168"}, {"id": "inboundoutboundread", "label": "InboundOutboundRead", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L168"}, {"id": "db_inventory_create_inventory", "label": "create_inventory()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L273"}, {"id": "inboundoutboundcreate", "label": "InboundOutboundCreate", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L273"}, {"id": "db_inventory_create_link_for_plan", "label": "create_link_for_plan()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L321"}, {"id": "createlinkforplanpayload", "label": "CreateLinkForPlanPayload", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L321"}, {"id": "db_inventory_get_inventory_site_url", "label": "get_inventory_site_url()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L372"}, {"id": "inventorysiteurlread", "label": "InventorySiteUrlRead", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L372"}, {"id": "db_inventory_update_inventory", "label": "update_inventory()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L405"}, {"id": "inboundoutboundupdate", "label": "InboundOutboundUpdate", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L405"}, {"id": "db_inventory_delete_inventory", "label": "delete_inventory()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/db/inventory.py", "source_location": "L423"}, {"id": "db_inventory_inventoryrelateddata", "label": "InventoryRelatedData", "file_type": "coLine truncated
|
||||
backend/graphify-out/cache/ast/15321fe1df77a555af579474434e3b401bd042555d19ea81a066ef754915612b.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_services_bx_expense_item_py", "label": "expense_item.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L1"}, {"id": "bx_expense_item_expenseitemservice", "label": "ExpenseItemService", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L14"}, {"id": "bx_expense_item_expenseitemservice_tenant_no", "label": "._tenant_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L16"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L16"}, {"id": "bxusercontext", "label": "BxUserContext", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L16"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L16"}, {"id": "bx_expense_item_expenseitemservice_get_by_id", "label": ".get_by_id()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L20"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L20"}, {"id": "bxexpenseitem", "label": "BxExpenseItem", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L20"}, {"id": "bx_expense_item_expenseitemservice_create", "label": ".create()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L33"}, {"id": "bx_expense_item_expenseitemservice_update", "label": ".update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L61"}, {"id": "bx_expense_item_expenseitemservice_soft_delete", "label": ".soft_delete()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L90"}, {"id": "bx_expense_item_expenseitemservice_get_item_tree", "label": ".get_item_tree()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L97"}, {"id": "bx_expense_item_expenseitemservice_build_tree", "label": "._build_tree()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L108"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_bx_expense_item_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L3", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_bx_expense_item_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L5", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_bx_expense_item_py", "target": "app_core_bx_auth", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L7", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_bx_expense_item_py", "target": "app_core_tenant_context", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L8", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_bx_expense_item_py", "target": "app_models_bx_models", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L9", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_bx_expense_item_py", "target": "app_services_bx_base", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/bx/expense_item.py", "source_location": "L10", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_bx_expense_iLine truncated
|
||||
backend/graphify-out/cache/ast/1815f562321ca024b218866cdc3a06aff546922779c4385e2eea77bfa3f7c5b2.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_py", "label": "crm.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L1"}, {"id": "api_crm_category_tree", "label": "category_tree()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L324"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L324"}, {"id": "api_crm_customer_list", "label": "customer_list()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L352"}, {"id": "customerlistbody", "label": "CustomerListBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L352"}, {"id": "api_crm_customer_detail", "label": "customer_detail()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L386"}, {"id": "api_crm_customer_create", "label": "customer_create()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L412"}, {"id": "customercreatebody", "label": "CustomerCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L412"}, {"id": "api_crm_customer_update", "label": "customer_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L441"}, {"id": "customerupdatebody", "label": "CustomerUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L441"}, {"id": "api_crm_customer_delete", "label": "customer_delete()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L467"}, {"id": "customerdeletebody", "label": "CustomerDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L467"}, {"id": "api_crm_customer_generate_no", "label": "customer_generate_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L495"}, {"id": "api_crm_category_add", "label": "category_add()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L511"}, {"id": "productcategorycreatebody", "label": "ProductCategoryCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L511"}, {"id": "api_crm_category_update", "label": "category_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L530"}, {"id": "productcategoryupdatebody", "label": "ProductCategoryUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L530"}, {"id": "api_crm_category_delete", "label": "category_delete()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L549"}, {"id": "categorydeletebody", "label": "CategoryDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L549"}, {"id": "api_crm_product_list", "label": "product_list()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L568"}, {"id": "productlistbody", "label": "ProductListBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L568"}, {"id": "api_crm_product_detail", "label": "product_detail()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L602"}, {"id": "productdetailbody", "label": "ProductDetailBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L602"}, {"id": "api_crm_product_add", "label": "product_add()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L620"}, {"id": "productcreatebody", "label": "ProductCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L620"}, {"id": "api_crm_product_update", "label": "product_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L643"}, {"id": "productupdatebody", "label": "ProductUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.Line truncated
|
||||
Loaded 100 of 406 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user