Files
shanxia2.0/backend/app/services/pl_menu_badge_service.py
T
2026-10-02 09:28:00 +08:00

205 lines
7.4 KiB
Python

"""运输管理 - 侧栏菜单角标统计"""
from __future__ import annotations
from typing import Dict, List, Optional
from sqlalchemy.orm import Session
from app.models.bx_models import BxWorkflowNode
from app.models.pl_models import (
PlDocumentReminder,
PlFreightSettlement,
PlPlan,
PlVehicleMaintenanceApply,
PlVehicleRechargeApply,
PlVehicleUreaApply,
)
from app.services.pl_cert_service import scan_cert_expiry
from app.services.pl_data_scope import apply_pl_scope
from app.services.pl_freight_settlement_service import can_user_approve_settlement
MENU_CODE_DOCUMENT = "pl:document:list"
MENU_CODE_FREIGHT_APPROVAL = "pl:freight-approval:list"
MENU_CODE_FREIGHT_DISBURSE = "pl:freight-disburse:list"
MENU_CODE_RECHARGE_APPROVAL = "pl:vehicle-recharge-approval:list"
MENU_CODE_RECHARGE_RECORD = "pl:vehicle-recharge-record:list"
MENU_CODE_MAINT_PROCESS = "pl:vehicle-maintenance-process:list"
MENU_CODE_UREA_DISBURSE = "pl:vehicle-urea-disburse:list"
MENU_CODE_PLAN_CERT = "pl:plan-cert:list"
MENU_CODE_PLAN_ARCHIVE = "pl:plan-archive:list"
MENU_CODE_PLAN_CONTRACT = "pl:plan-contract:list"
SUPPORTED_MENU_CODES = {
MENU_CODE_DOCUMENT,
MENU_CODE_FREIGHT_APPROVAL,
MENU_CODE_FREIGHT_DISBURSE,
MENU_CODE_RECHARGE_APPROVAL,
MENU_CODE_RECHARGE_RECORD,
MENU_CODE_MAINT_PROCESS,
MENU_CODE_UREA_DISBURSE,
MENU_CODE_PLAN_CERT,
MENU_CODE_PLAN_ARCHIVE,
MENU_CODE_PLAN_CONTRACT,
}
def _count_pending_applies(db: Session, tenant_no: str, user, model, *extra_filters) -> int:
query = db.query(model).filter(
model.tenant_no == tenant_no,
model.del_flag == 0,
model.status == "pending",
*extra_filters,
)
query = apply_pl_scope(query, model, user, db, tenant_no)
return query.count()
def count_document_reminder_expired_pending(db: Session, tenant_no: str, user) -> int:
scan_cert_expiry(db, tenant_no)
db.flush()
query = db.query(PlDocumentReminder).filter(
PlDocumentReminder.tenant_no == tenant_no,
PlDocumentReminder.del_flag == 0,
PlDocumentReminder.status == "pending",
PlDocumentReminder.days_remaining <= 0,
)
query = apply_pl_scope(query, PlDocumentReminder, user, db, tenant_no)
return query.count()
def count_freight_approval_pending(db: Session, tenant_no: str, user_id: str) -> int:
if not user_id:
return 0
rows = (
db.query(PlFreightSettlement.id)
.filter(
PlFreightSettlement.tenant_no == tenant_no,
PlFreightSettlement.del_flag == 0,
PlFreightSettlement.settlement_status == "approving",
)
.all()
)
total = 0
for (settlement_id,) in rows:
if can_user_approve_settlement(db, settlement_id, user_id):
total += 1
return total
def count_plan_cert_pending(db: Session, tenant_no: str, user) -> int:
"""危化品证待办角标:未齐备且含危化产品的未归档计划(SQL EXISTS,避免全表拉入内存)。"""
from app.services.pl_plan_service import plan_has_hazard_product_exists
query = db.query(PlPlan).filter(
PlPlan.tenant_no == tenant_no,
PlPlan.del_flag == 0,
PlPlan.hazard_cert_ready == 0,
PlPlan.archive_status != "archived",
PlPlan.follow_status != "cancelled",
plan_has_hazard_product_exists(tenant_no),
)
query = apply_pl_scope(query, PlPlan, user, db, tenant_no)
return query.count()
def count_plan_archive_pending(db: Session, tenant_no: str, user) -> int:
"""待归档角标:与归档列表一致,仅统计已安排运输且未归档的计划。"""
query = db.query(PlPlan).filter(
PlPlan.tenant_no == tenant_no,
PlPlan.del_flag == 0,
PlPlan.archive_status == "unarchived",
PlPlan.follow_status != "cancelled",
PlPlan.transport_status == "arranged",
)
query = apply_pl_scope(query, PlPlan, user, db, tenant_no)
return query.count()
def count_plan_contract_gaps(db: Session, tenant_no: str, user) -> int:
from app.services.pl_plan_contract_gap import plan_has_contract_gap_exists
query = db.query(PlPlan).filter(
PlPlan.tenant_no == tenant_no,
PlPlan.del_flag == 0,
PlPlan.follow_status != "cancelled",
plan_has_contract_gap_exists(tenant_no),
)
query = apply_pl_scope(query, PlPlan, user, db, tenant_no)
return query.count()
def count_freight_disburse_pending(db: Session, tenant_no: str, user) -> int:
query = db.query(PlFreightSettlement).filter(
PlFreightSettlement.tenant_no == tenant_no,
PlFreightSettlement.del_flag == 0,
PlFreightSettlement.settlement_status == "approved",
)
query = apply_pl_scope(query, PlFreightSettlement, user, db, tenant_no)
return query.count()
def count_recharge_approval_pending(db: Session, tenant_no: str, user_id: str) -> int:
"""与充值审批待办列表一致:当前用户为当前节点审批人的审批中单据。"""
if not user_id:
return 0
return (
db.query(PlVehicleRechargeApply)
.join(
BxWorkflowNode,
BxWorkflowNode.id == PlVehicleRechargeApply.current_node,
)
.filter(
PlVehicleRechargeApply.tenant_no == tenant_no,
PlVehicleRechargeApply.del_flag == 0,
PlVehicleRechargeApply.status == "in_approval",
BxWorkflowNode.tenant_no == tenant_no,
BxWorkflowNode.del_flag == 0,
BxWorkflowNode.approver_id == user_id,
)
.count()
)
def build_menu_badge_counts(
db: Session,
*,
tenant_no: str,
user,
user_id: str,
menu_codes: Optional[List[str]] = None,
) -> Dict[str, int]:
requested = {c.strip() for c in (menu_codes or []) if c and c.strip()}
if not requested:
requested = set(SUPPORTED_MENU_CODES)
requested &= SUPPORTED_MENU_CODES
counts: Dict[str, int] = {}
if MENU_CODE_DOCUMENT in requested:
counts[MENU_CODE_DOCUMENT] = count_document_reminder_expired_pending(db, tenant_no, user)
if MENU_CODE_FREIGHT_APPROVAL in requested:
counts[MENU_CODE_FREIGHT_APPROVAL] = count_freight_approval_pending(db, tenant_no, user_id)
if MENU_CODE_FREIGHT_DISBURSE in requested:
counts[MENU_CODE_FREIGHT_DISBURSE] = count_freight_disburse_pending(db, tenant_no, user)
if MENU_CODE_RECHARGE_APPROVAL in requested:
counts[MENU_CODE_RECHARGE_APPROVAL] = count_recharge_approval_pending(
db, tenant_no, user_id
)
if MENU_CODE_RECHARGE_RECORD in requested:
counts[MENU_CODE_RECHARGE_RECORD] = _count_pending_applies(
db, tenant_no, user, PlVehicleRechargeApply
)
if MENU_CODE_MAINT_PROCESS in requested:
counts[MENU_CODE_MAINT_PROCESS] = _count_pending_applies(
db, tenant_no, user, PlVehicleMaintenanceApply
)
if MENU_CODE_UREA_DISBURSE in requested:
counts[MENU_CODE_UREA_DISBURSE] = _count_pending_applies(
db, tenant_no, user, PlVehicleUreaApply
)
if MENU_CODE_PLAN_CERT in requested:
counts[MENU_CODE_PLAN_CERT] = count_plan_cert_pending(db, tenant_no, user)
if MENU_CODE_PLAN_ARCHIVE in requested:
counts[MENU_CODE_PLAN_ARCHIVE] = count_plan_archive_pending(db, tenant_no, user)
if MENU_CODE_PLAN_CONTRACT in requested:
counts[MENU_CODE_PLAN_CONTRACT] = count_plan_contract_gaps(db, tenant_no, user)
return counts