71 lines
3.8 KiB
Python
71 lines
3.8 KiB
Python
"""计划用证列表:按真实占用/结转状态分组,余额可用性由只读预览实时计算。"""
|
|
from datetime import date
|
|
from fastapi import HTTPException
|
|
from sqlalchemy import case, or_, cast, String
|
|
from app.models.pl_models import PlPlan, PlPlanProduct
|
|
from app.models.pl_quota_models import PlPlanCertUsage
|
|
from app.services.pl_data_scope import apply_pl_scope
|
|
|
|
STATUSES = ('not_required', 'needs_configuration', 'needs_validation', 'reserved', 'settled', 'exception')
|
|
|
|
|
|
def _filter_date(value):
|
|
if value in (None, ''):
|
|
return None
|
|
if isinstance(value, date):
|
|
return value
|
|
try:
|
|
return date.fromisoformat(value)
|
|
except (TypeError, ValueError):
|
|
raise HTTPException(400, '计划日期格式无效') from None
|
|
|
|
|
|
def list_plans(db, user, keyword='', status=None, page=1, page_size=20, *,
|
|
plan_no=None, license_plate=None, plan_date_from=None, plan_date_to=None,
|
|
quota_status_in=None):
|
|
if status and status not in STATUSES:
|
|
raise HTTPException(400, '未知用证状态')
|
|
selected_states = [value.strip() for value in (quota_status_in or '').split(',') if value.strip()]
|
|
if any(value not in STATUSES for value in selected_states):
|
|
raise HTTPException(400, '未知用证状态')
|
|
date_from, date_to = _filter_date(plan_date_from), _filter_date(plan_date_to)
|
|
if date_from and date_to and date_from > date_to:
|
|
raise HTTPException(400, '计划日期起始不能晚于结束')
|
|
toxic = db.query(PlPlanProduct.id).filter(PlPlanProduct.tenant_no == user.tenant_no,
|
|
PlPlanProduct.plan_id == PlPlan.id, PlPlanProduct.del_flag == 0, PlPlanProduct.is_toxic == 1).exists()
|
|
def usage(*states):
|
|
return db.query(PlPlanCertUsage.id).filter(PlPlanCertUsage.tenant_no == user.tenant_no,
|
|
PlPlanCertUsage.plan_id == PlPlan.id, PlPlanCertUsage.del_flag == 0,
|
|
PlPlanCertUsage.usage_status.in_(states)).exists()
|
|
error = db.query(PlPlanCertUsage.id).filter(PlPlanCertUsage.tenant_no == user.tenant_no,
|
|
PlPlanCertUsage.plan_id == PlPlan.id, PlPlanCertUsage.del_flag == 0,
|
|
PlPlanCertUsage.confirmation_snapshot['settlementError'].as_string().isnot(None),
|
|
PlPlanCertUsage.confirmation_snapshot['settlementError'].as_string() != '').exists()
|
|
state = case((~toxic, 'not_required'),
|
|
(or_(PlPlan.archive_status == 'exception', usage('exception'), error), 'exception'),
|
|
(usage('reserved', 'in_transit'), 'reserved'), (usage('settled'), 'settled'),
|
|
(or_(PlPlan.cert_configuration.is_(None), cast(PlPlan.cert_configuration, String).in_(['null', '{}'])), 'needs_configuration'),
|
|
else_='needs_validation')
|
|
query = db.query(PlPlan, state.label('quota_state')).filter(PlPlan.tenant_no == user.tenant_no,
|
|
PlPlan.del_flag == 0, PlPlan.follow_status != 'cancelled')
|
|
query = apply_pl_scope(query, PlPlan, user, db, user.tenant_no)
|
|
if keyword:
|
|
query = query.filter(PlPlan.plan_no.contains(keyword))
|
|
if status:
|
|
query = query.filter(state == status)
|
|
# 表头条件必须先于统计与分页,不能只过滤已加载的一页。
|
|
if plan_no:
|
|
query = query.filter(PlPlan.plan_no.contains(plan_no, autoescape=True))
|
|
if license_plate:
|
|
query = query.filter(PlPlan.license_plate.contains(license_plate, autoescape=True))
|
|
if date_from:
|
|
query = query.filter(PlPlan.plan_date >= date_from)
|
|
if date_to:
|
|
query = query.filter(PlPlan.plan_date <= date_to)
|
|
if selected_states:
|
|
query = query.filter(state.in_(selected_states))
|
|
total = query.count()
|
|
rows = query.order_by(PlPlan.create_time.desc(), PlPlan.id).offset((page-1)*page_size).limit(page_size).all()
|
|
return {'total': total, 'list': [{'id': p.id, 'planNo': p.plan_no, 'planDate': p.plan_date,
|
|
'licensePlate': p.license_plate, 'quotaStatus': value} for p, value in rows]}
|