From b071687a71b12d19058528f0a135aba43212803b Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sat, 25 Jul 2026 10:45:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(pl):=20=E4=B8=89=E6=96=B9=E8=BD=A6=E8=BE=86?= =?UTF-8?q?=E4=B8=8E=E4=BA=BA=E5=91=98=E4=BF=9D=E5=AD=98=E8=B7=B3=E8=BF=87?= =?UTF-8?q?=E5=BF=85=E5=A4=87=E8=AF=81=E4=B9=A6=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- backend/app/api/pl/base_data.py | 5 ++ .../app/services/pl_cert_required_service.py | 10 ++- .../tests/test_pl_tripartite_cert_gates.py | 67 +++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 backend/tests/test_pl_tripartite_cert_gates.py diff --git a/backend/app/api/pl/base_data.py b/backend/app/api/pl/base_data.py index 79fde35c9..851a1f9ef 100644 --- a/backend/app/api/pl/base_data.py +++ b/backend/app/api/pl/base_data.py @@ -706,6 +706,7 @@ def create_personnel( validate_personnel_required_certs( payload.cert_list, payload.personnel_type, + personnel_affiliation=payload.personnel_affiliation, subject_label=f"「{payload.personnel_name}」", ) @@ -770,6 +771,9 @@ def update_personnel( data = payload.model_dump(exclude_unset=True, exclude={"cert_list"}) effective_personnel_type = data.get("personnel_type", personnel.personnel_type) + effective_personnel_affiliation = data.get( + "personnel_affiliation", personnel.personnel_affiliation + ) effective_cert_rows = ( cert_list if cert_list is not None @@ -780,6 +784,7 @@ def update_personnel( validate_personnel_required_certs( effective_cert_rows, effective_personnel_type, + personnel_affiliation=effective_personnel_affiliation, subject_label=f"「{subject_name}」", ) diff --git a/backend/app/services/pl_cert_required_service.py b/backend/app/services/pl_cert_required_service.py index 51b410ea0..dbcb01591 100644 --- a/backend/app/services/pl_cert_required_service.py +++ b/backend/app/services/pl_cert_required_service.py @@ -153,8 +153,13 @@ def validate_personnel_required_certs( cert_rows: Sequence[CertRow | dict], personnel_type: str | None, *, + personnel_affiliation: str | None = None, subject_label: str = "", ) -> None: + affiliation = (personnel_affiliation or VEHICLE_TYPE_SELF).strip() + if affiliation == VEHICLE_TYPE_CARRIER: + return + ptype = (personnel_type or "").strip() if not ptype: raise HTTPException(status_code=400, detail="人员类型不能为空") @@ -301,10 +306,9 @@ def validate_vehicle_save_certs( vtype = (vehicle_type or "").strip() if vtype == VEHICLE_TYPE_SELF: validate_self_vehicle_required_certs(cert_rows, trailer_no=trailer_no) - elif vtype == VEHICLE_TYPE_CARRIER: - validate_carrier_vehicle_required_certs(cert_rows, trailer_no=trailer_no) if vtype in (VEHICLE_TYPE_SELF, VEHICLE_TYPE_CARRIER) and personnel_rows: validate_vehicle_personnel_affiliation( db, tenant_no, vtype, personnel_rows ) - validate_vehicle_personnel_cert_links(db, tenant_no, personnel_rows) + if vtype == VEHICLE_TYPE_SELF: + validate_vehicle_personnel_cert_links(db, tenant_no, personnel_rows) diff --git a/backend/tests/test_pl_tripartite_cert_gates.py b/backend/tests/test_pl_tripartite_cert_gates.py new file mode 100644 index 000000000..2c8d363ec --- /dev/null +++ b/backend/tests/test_pl_tripartite_cert_gates.py @@ -0,0 +1,67 @@ +"""三方不强制证书;山峡仍强制。""" +from __future__ import annotations + +import pytest +from fastapi import HTTPException + +from app.services.pl_cert_required_service import ( + validate_personnel_required_certs, + validate_vehicle_save_certs, +) + + +def test_carrier_personnel_skips_required_certs(): + validate_personnel_required_certs( + [], + "driver", + personnel_affiliation="carrier", + subject_label="「张三」", + ) + + +def test_self_personnel_still_requires_certs(): + with pytest.raises(HTTPException) as ei: + validate_personnel_required_certs( + [], + "driver", + personnel_affiliation="self", + subject_label="「李四」", + ) + assert ei.value.status_code == 400 + assert "必备证书" in ei.value.detail + + +def test_validate_vehicle_save_certs_carrier_skips_vehicle_and_personnel_certs(monkeypatch): + """carrier 车:不跑车辆必备、不跑关联人员证书;仍可跑归属。""" + called = {"self": 0, "carrier": 0, "links": 0} + + import app.services.pl_cert_required_service as svc + + monkeypatch.setattr( + svc, + "validate_self_vehicle_required_certs", + lambda *a, **k: called.__setitem__("self", called["self"] + 1), + ) + monkeypatch.setattr( + svc, + "validate_carrier_vehicle_required_certs", + lambda *a, **k: called.__setitem__("carrier", called["carrier"] + 1), + ) + monkeypatch.setattr( + svc, + "validate_vehicle_personnel_cert_links", + lambda *a, **k: called.__setitem__("links", called["links"] + 1), + ) + monkeypatch.setattr(svc, "validate_vehicle_personnel_affiliation", lambda *a, **k: None) + + validate_vehicle_save_certs( + db=None, + tenant_no="t1", + vehicle_type="carrier", + trailer_no="皖LP359", + cert_rows=[], + personnel_rows=[], + ) + assert called["carrier"] == 0 + assert called["self"] == 0 + assert called["links"] == 0