| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- """运输计划列表:批量计算「复制给司机 / 现场 / 磅单二维码」按钮显示标志。
- 语义与查看弹窗 / usePlPlanArrangeActions / build_site_records_for_arranged_plan 对齐:
- - 已安排且未取消,且存在司机链接或现场记录或地磅出入库关联时,显示「复制给司机」
- - 有现场链接记录(含运输单按段 site_access_token、出入库单现场令牌回退)时显示「复制给现场」
- - 有勾选生成地磅单的出入库关联(db_purchase_plan_id 非空)时显示「复制磅单二维码」
- """
- from __future__ import annotations
- from collections import defaultdict
- from dataclasses import dataclass
- from typing import Iterable
- from sqlalchemy.orm import Session
- from app.models.pl_models import (
- PlInboundOrder,
- PlOutboundOrder,
- PlPlan,
- PlPlanDelivery,
- PlPlanLoad,
- PlTransport,
- PlTransportDbLink,
- PlTransportSiteLink,
- )
- from app.services.pl_internal_customer import is_internal_customer_name
- @dataclass(frozen=True)
- class PlanArrangeCopyFlags:
- can_copy_to_driver: bool = False
- can_copy_to_site: bool = False
- can_copy_pound_qrcode: bool = False
- def _plan_requires_site_link_cached(
- plan: PlPlan,
- *,
- load_by_plan: dict[str, PlPlanLoad],
- deliveries_by_plan: dict[str, list[PlPlanDelivery]],
- ) -> bool:
- """与 plan_requires_site_link 语义一致,使用已批量加载的装/送货段。"""
- load = load_by_plan.get(plan.id)
- if load is not None:
- if is_internal_customer_name(load.customer_name):
- return True
- elif is_internal_customer_name(plan.supplier_name):
- return True
- deliveries = deliveries_by_plan.get(plan.id) or []
- if deliveries:
- return any(is_internal_customer_name(row.customer_name) for row in deliveries)
- return is_internal_customer_name(plan.customer_name) or is_internal_customer_name(
- plan.destination_name
- )
- def load_plan_arrange_copy_flags(
- db: Session,
- tenant_no: str,
- plans: Iterable[PlPlan],
- ) -> dict[str, PlanArrangeCopyFlags]:
- plan_list = list(plans)
- empty = PlanArrangeCopyFlags()
- result: dict[str, PlanArrangeCopyFlags] = {plan.id: empty for plan in plan_list}
- candidates = [
- plan
- for plan in plan_list
- if plan.transport_status == "arranged"
- and plan.follow_status != "cancelled"
- and (plan.transport_id or "").strip()
- ]
- if not candidates:
- return result
- plan_ids = [plan.id for plan in candidates]
- transport_ids = list({plan.transport_id for plan in candidates if plan.transport_id})
- transports = (
- db.query(PlTransport)
- .filter(
- PlTransport.id.in_(transport_ids),
- PlTransport.tenant_no == tenant_no,
- PlTransport.del_flag == 0,
- )
- .all()
- )
- transport_by_id = {
- row.id: row for row in transports if row.transport_status != "cancelled"
- }
- site_plan_ids: set[str] = {
- plan_id
- for (plan_id,) in db.query(PlTransportSiteLink.plan_id)
- .filter(
- PlTransportSiteLink.tenant_no == tenant_no,
- PlTransportSiteLink.plan_id.in_(plan_ids),
- PlTransportSiteLink.del_flag == 0,
- )
- .distinct()
- .all()
- }
- inventory_plan_ids: set[str] = set()
- dibang_plan_ids: set[str] = set()
- for plan_id, db_purchase_plan_id in (
- db.query(PlTransportDbLink.plan_id, PlTransportDbLink.db_purchase_plan_id)
- .filter(
- PlTransportDbLink.tenant_no == tenant_no,
- PlTransportDbLink.plan_id.in_(plan_ids),
- PlTransportDbLink.del_flag == 0,
- )
- .all()
- ):
- inventory_plan_ids.add(plan_id)
- if (db_purchase_plan_id or "").strip():
- dibang_plan_ids.add(plan_id)
- # 与 get_plan_arrange_info 一致:无 site_link 时,用未作废出入库单的现场令牌回退
- inbound_ids = [
- plan.id
- for plan in candidates
- if plan.plan_type == "inbound" and plan.id not in site_plan_ids
- ]
- if inbound_ids:
- for plan_id, site_access_token, status in (
- db.query(PlInboundOrder.plan_id, PlInboundOrder.site_access_token, PlInboundOrder.status)
- .filter(
- PlInboundOrder.tenant_no == tenant_no,
- PlInboundOrder.plan_id.in_(inbound_ids),
- PlInboundOrder.del_flag == 0,
- )
- .all()
- ):
- if status != "voided" and (site_access_token or "").strip():
- site_plan_ids.add(plan_id)
- outbound_ids = [
- plan.id
- for plan in candidates
- if plan.plan_type == "outbound" and plan.id not in site_plan_ids
- ]
- if outbound_ids:
- for plan_id, site_access_token, status in (
- db.query(
- PlOutboundOrder.plan_id, PlOutboundOrder.site_access_token, PlOutboundOrder.status
- )
- .filter(
- PlOutboundOrder.tenant_no == tenant_no,
- PlOutboundOrder.plan_id.in_(outbound_ids),
- PlOutboundOrder.del_flag == 0,
- )
- .all()
- ):
- if status != "voided" and (site_access_token or "").strip():
- site_plan_ids.add(plan_id)
- # 批量装/送货段,避免 plan_requires_site_link / plan_has_load_stop 逐计划打库
- need_site_fallback = [plan for plan in candidates if plan.id not in site_plan_ids]
- load_by_plan: dict[str, PlPlanLoad] = {}
- deliveries_by_plan: dict[str, list[PlPlanDelivery]] = defaultdict(list)
- if need_site_fallback:
- fallback_ids = [plan.id for plan in need_site_fallback]
- for row in (
- db.query(PlPlanLoad)
- .filter(
- PlPlanLoad.plan_id.in_(fallback_ids),
- PlPlanLoad.tenant_no == tenant_no,
- PlPlanLoad.del_flag == 0,
- )
- .all()
- ):
- load_by_plan[row.plan_id] = row
- for row in (
- db.query(PlPlanDelivery)
- .filter(
- PlPlanDelivery.plan_id.in_(fallback_ids),
- PlPlanDelivery.tenant_no == tenant_no,
- PlPlanDelivery.del_flag == 0,
- )
- .all()
- ):
- deliveries_by_plan[row.plan_id].append(row)
- # 与 build_site_records_for_arranged_plan 一致:无 site_link 行时,装货段 + 运输单 site_access_token
- for plan in need_site_fallback:
- if not _plan_requires_site_link_cached(
- plan,
- load_by_plan=load_by_plan,
- deliveries_by_plan=deliveries_by_plan,
- ):
- continue
- if plan.id not in load_by_plan:
- continue
- transport = transport_by_id.get(plan.transport_id or "")
- if not transport:
- continue
- if (transport.site_access_token or "").strip():
- site_plan_ids.add(plan.id)
- for plan in candidates:
- transport = transport_by_id.get(plan.transport_id or "")
- if not transport:
- continue
- has_driver = bool((transport.driver_access_token or "").strip())
- has_site = plan.id in site_plan_ids
- has_inventory = plan.id in inventory_plan_ids
- has_dibang = plan.id in dibang_plan_ids
- if not (has_driver or has_site or has_inventory):
- continue
- result[plan.id] = PlanArrangeCopyFlags(
- can_copy_to_driver=True,
- can_copy_to_site=has_site,
- can_copy_pound_qrcode=has_dibang,
- )
- return result
|