Files
shanxia2.0/backend/app/services/pl_quota_overview.py
T

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]}