305 lines
9.4 KiB
Python
305 lines
9.4 KiB
Python
"""运输计划 - 证书 CRUD 与到期扫描"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
from typing import Optional
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.utils import generate_uuid
|
|
from app.models.pl_models import PlCert, PlDocumentReminder, PlPersonnel, PlVehicle
|
|
from app.schemas.pl.base_data import CertRead, CertRow
|
|
|
|
|
|
def list_owner_certs(
|
|
db: Session, tenant_no: str, owner_type: str, owner_id: str
|
|
) -> list[PlCert]:
|
|
return (
|
|
db.query(PlCert)
|
|
.filter(
|
|
PlCert.tenant_no == tenant_no,
|
|
PlCert.owner_type == owner_type,
|
|
PlCert.owner_id == owner_id,
|
|
PlCert.del_flag == 0,
|
|
)
|
|
.order_by(PlCert.order_num.asc(), PlCert.create_time.asc())
|
|
.all()
|
|
)
|
|
|
|
|
|
def list_owner_certs_batch(
|
|
db: Session, tenant_no: str, owner_type: str, owner_ids: list[str]
|
|
) -> dict[str, list[PlCert]]:
|
|
if not owner_ids:
|
|
return {}
|
|
rows = (
|
|
db.query(PlCert)
|
|
.filter(
|
|
PlCert.tenant_no == tenant_no,
|
|
PlCert.owner_type == owner_type,
|
|
PlCert.owner_id.in_(owner_ids),
|
|
PlCert.del_flag == 0,
|
|
)
|
|
.order_by(PlCert.order_num.asc(), PlCert.create_time.asc())
|
|
.all()
|
|
)
|
|
grouped: dict[str, list[PlCert]] = {}
|
|
for cert in rows:
|
|
grouped.setdefault(cert.owner_id, []).append(cert)
|
|
return grouped
|
|
|
|
|
|
def certs_to_read(certs: list[PlCert]) -> list[CertRead]:
|
|
return [
|
|
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,
|
|
remind_days=c.remind_days,
|
|
attachment_urls=c.attachment_urls,
|
|
owner_type=c.owner_type,
|
|
owner_id=c.owner_id,
|
|
status=c.status,
|
|
tenant_no=c.tenant_no,
|
|
create_time=c.create_time,
|
|
update_time=c.update_time,
|
|
)
|
|
for c in certs
|
|
]
|
|
|
|
|
|
def _normalize_cert_row(row: CertRow | dict) -> CertRow:
|
|
if isinstance(row, CertRow):
|
|
return row
|
|
return CertRow.model_validate(row)
|
|
|
|
|
|
def save_owner_certs(
|
|
db: Session,
|
|
owner_type: str,
|
|
owner_id: str,
|
|
cert_rows: list[CertRow | dict],
|
|
tenant_no: str,
|
|
*,
|
|
user_id: str,
|
|
dept_id: str,
|
|
) -> None:
|
|
"""按 cert_id upsert;未出现在列表中的旧证软删。"""
|
|
existing = list_owner_certs(db, tenant_no, owner_type, owner_id)
|
|
existing_map = {c.id: c for c in existing}
|
|
keep_ids: set[str] = set()
|
|
|
|
for idx, row in enumerate(cert_rows):
|
|
row = _normalize_cert_row(row)
|
|
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
|
|
cert.remind_days = row.remind_days
|
|
cert.attachment_urls = row.attachment_urls
|
|
cert.order_num = idx
|
|
cert.update_by = user_id
|
|
keep_ids.add(cert.id)
|
|
else:
|
|
cert = PlCert(
|
|
id=generate_uuid(),
|
|
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,
|
|
remind_days=row.remind_days,
|
|
attachment_urls=row.attachment_urls,
|
|
status="valid",
|
|
order_num=idx,
|
|
create_by=user_id,
|
|
create_dept_id=dept_id,
|
|
)
|
|
db.add(cert)
|
|
db.flush()
|
|
keep_ids.add(cert.id)
|
|
|
|
for cert in existing:
|
|
if cert.id not in keep_ids:
|
|
cert.del_flag = 1
|
|
cert.update_by = user_id
|
|
|
|
scan_cert_expiry(db, tenant_no)
|
|
|
|
|
|
def nearest_expire_date(
|
|
db: Session, tenant_no: str, owner_type: str, owner_id: str
|
|
) -> Optional[date]:
|
|
row = (
|
|
db.query(PlCert.expire_date)
|
|
.filter(
|
|
PlCert.tenant_no == tenant_no,
|
|
PlCert.owner_type == owner_type,
|
|
PlCert.owner_id == owner_id,
|
|
PlCert.del_flag == 0,
|
|
PlCert.expire_date.isnot(None),
|
|
)
|
|
.order_by(PlCert.expire_date.asc())
|
|
.first()
|
|
)
|
|
return row[0] if row else None
|
|
|
|
|
|
def _resolve_owner_label(
|
|
db: Session, tenant_no: str, owner_type: str, owner_id: str
|
|
) -> str:
|
|
if owner_type == "vehicle":
|
|
vehicle = (
|
|
db.query(PlVehicle)
|
|
.filter(
|
|
PlVehicle.id == owner_id,
|
|
PlVehicle.tenant_no == tenant_no,
|
|
PlVehicle.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
return vehicle.license_plate if vehicle else owner_id
|
|
|
|
personnel = (
|
|
db.query(PlPersonnel)
|
|
.filter(
|
|
PlPersonnel.id == owner_id,
|
|
PlPersonnel.tenant_no == tenant_no,
|
|
PlPersonnel.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
return personnel.personnel_name if personnel else owner_id
|
|
|
|
|
|
def _pending_reminder_for_cert(
|
|
db: Session, cert_id: str
|
|
) -> Optional[PlDocumentReminder]:
|
|
return (
|
|
db.query(PlDocumentReminder)
|
|
.filter(
|
|
PlDocumentReminder.cert_id == cert_id,
|
|
PlDocumentReminder.status == "pending",
|
|
PlDocumentReminder.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
|
|
def _resolve_pending_reminder(
|
|
reminder: PlDocumentReminder,
|
|
*,
|
|
remark: str,
|
|
expire_date: Optional[date] = None,
|
|
days_remaining: Optional[int] = None,
|
|
document_type: Optional[str] = None,
|
|
document_no: Optional[str] = None,
|
|
) -> None:
|
|
"""关闭待处理提醒:证件已处理/退出窗口后不再继续提醒。"""
|
|
reminder.status = "resolved"
|
|
reminder.remark = remark
|
|
if expire_date is not None:
|
|
reminder.expire_date = expire_date
|
|
if days_remaining is not None:
|
|
reminder.days_remaining = days_remaining
|
|
if document_type is not None:
|
|
reminder.document_type = document_type
|
|
if document_no is not None:
|
|
reminder.document_no = document_no
|
|
|
|
|
|
def scan_cert_expiry(db: Session, tenant_no: Optional[str] = None) -> int:
|
|
"""扫描 pl_cert 生成到期提醒;退出提醒窗口或证件失效时关闭旧 pending。"""
|
|
today = date.today()
|
|
query = db.query(PlCert).filter(
|
|
PlCert.del_flag == 0,
|
|
PlCert.expire_date.isnot(None),
|
|
PlCert.status == "valid",
|
|
)
|
|
if tenant_no:
|
|
query = query.filter(PlCert.tenant_no == tenant_no)
|
|
|
|
active_cert_ids: set[str] = set()
|
|
created_count = 0
|
|
for cert in query.all():
|
|
active_cert_ids.add(cert.id)
|
|
days_remaining = (cert.expire_date - today).days
|
|
existing = _pending_reminder_for_cert(db, cert.id)
|
|
|
|
# 尚未进入提醒窗口:关闭因旧有效期留下的 pending,等真正临近再提醒
|
|
if days_remaining > cert.remind_days:
|
|
if existing:
|
|
_resolve_pending_reminder(
|
|
existing,
|
|
remark="系统自动关闭:证件有效期已更新,已退出提醒窗口",
|
|
expire_date=cert.expire_date,
|
|
days_remaining=days_remaining,
|
|
document_type=cert.cert_name,
|
|
document_no=cert.cert_no,
|
|
)
|
|
continue
|
|
|
|
owner_label = _resolve_owner_label(
|
|
db, cert.tenant_no, cert.owner_type, cert.owner_id
|
|
)
|
|
driver_id = cert.owner_id if cert.owner_type == "personnel" else None
|
|
driver_name = owner_label if cert.owner_type == "personnel" else None
|
|
|
|
if existing:
|
|
existing.owner_type = cert.owner_type
|
|
existing.owner_id = cert.owner_id
|
|
existing.owner_label = owner_label
|
|
existing.driver_id = driver_id
|
|
existing.driver_name = driver_name
|
|
existing.document_type = cert.cert_name
|
|
existing.document_no = cert.cert_no
|
|
existing.expire_date = cert.expire_date
|
|
existing.days_remaining = days_remaining
|
|
continue
|
|
|
|
reminder = PlDocumentReminder(
|
|
id=generate_uuid(),
|
|
tenant_no=cert.tenant_no,
|
|
cert_id=cert.id,
|
|
owner_type=cert.owner_type,
|
|
owner_id=cert.owner_id,
|
|
owner_label=owner_label,
|
|
driver_id=driver_id,
|
|
driver_name=driver_name,
|
|
document_type=cert.cert_name,
|
|
document_no=cert.cert_no,
|
|
expire_date=cert.expire_date,
|
|
days_remaining=days_remaining,
|
|
status="pending",
|
|
create_by=cert.create_by or "system",
|
|
create_dept_id=cert.create_dept_id or "",
|
|
)
|
|
db.add(reminder)
|
|
created_count += 1
|
|
|
|
# 软删/作废/无有效期的证件:关闭仍挂着的 pending
|
|
stale_q = db.query(PlDocumentReminder).filter(
|
|
PlDocumentReminder.status == "pending",
|
|
PlDocumentReminder.del_flag == 0,
|
|
PlDocumentReminder.cert_id.isnot(None),
|
|
)
|
|
if tenant_no:
|
|
stale_q = stale_q.filter(PlDocumentReminder.tenant_no == tenant_no)
|
|
for reminder in stale_q.all():
|
|
if reminder.cert_id in active_cert_ids:
|
|
continue
|
|
_resolve_pending_reminder(
|
|
reminder,
|
|
remark="系统自动关闭:关联证件已删除或失效",
|
|
)
|
|
|
|
return created_count
|