更新登录页及优化相关功能

This commit is contained in:
焦龙言
2026-10-08 14:13:05 +08:00
parent 9a14b692e3
commit 56e646bcc1
139 changed files with 77587 additions and 811 deletions
@@ -1,7 +1,7 @@
from datetime import datetime, time, timedelta
from typing import Any
from sqlalchemy import ColumnElement, Select, and_, false, or_, select
from sqlalchemy import ColumnElement, Select, and_, case, false, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import aliased
from sqlalchemy.orm.attributes import InstrumentedAttribute
@@ -15,6 +15,7 @@ from module_business.entity.do.master_do import (
BizCustomerSite,
BizPallet,
BizProductionLine,
BizTunnel,
BizWarehouse,
)
from module_business.entity.do.operation_do import (
@@ -40,6 +41,7 @@ from module_business.entity.vo.report_vo import (
ReturnReportQueryModel,
ShipmentReportModel,
ShipmentReportQueryModel,
TunnelReportQueryModel,
)
from utils.page_util import PageUtil
@@ -84,6 +86,31 @@ class ReportDao:
"""保留空的可选 join,同时拒绝已存在的跨批次关联记录。"""
return or_(id_column.is_(None), batch_scope_condition(scope_column, scope))
@staticmethod
def _tunnel_conditions(
query: TunnelReportQueryModel,
trace_column: ColumnElement,
*tunnels: tuple[ColumnElement, Any],
) -> list[ColumnElement[bool]]:
"""历史事件可匹配变更前/后隧道;同一组编号、名称须命中同一个隧道。"""
conditions = []
if query.trace_no:
conditions.append(trace_column == query.trace_no)
matches = []
for id_column, tunnel in tunnels:
filters = []
if query.tunnel_id:
filters.append(id_column == query.tunnel_id)
if query.tunnel_code:
filters.append(tunnel.tunnel_code.like(f'%{query.tunnel_code}%'))
if query.tunnel_name:
filters.append(tunnel.tunnel_name.like(f'%{query.tunnel_name}%'))
if filters:
matches.append(and_(*filters))
if matches:
conditions.append(or_(*matches))
return conditions
@classmethod
def _or_scope_conditions(
cls,
@@ -158,6 +185,7 @@ class ReportDao:
current_warehouse = aliased(BizWarehouse, name='current_warehouse')
current_site = aliased(BizCustomerSite, name='current_site')
current_line = aliased(BizProductionLine, name='current_line')
current_tunnel = aliased(BizTunnel, name='current_tunnel')
active_filling = aliased(BizFillingRecord, name='active_filling')
conditions = [
@@ -167,7 +195,11 @@ class ReportDao:
cls._joined_scope_condition(current_warehouse.warehouse_id, current_warehouse.test_batch_id, scope),
cls._joined_scope_condition(current_site.site_id, current_site.test_batch_id, scope),
cls._joined_scope_condition(current_line.line_id, current_line.test_batch_id, scope),
cls._joined_scope_condition(current_tunnel.tunnel_id, current_tunnel.test_batch_id, scope),
]
conditions.extend(cls._tunnel_conditions(
query_object, BizPalletInventory.trace_no, (BizPalletInventory.tunnel_id, current_tunnel),
))
conditions.extend(
cls._or_scope_conditions(
(BizPalletInventory.warehouse_id, warehouse_ids),
@@ -214,6 +246,12 @@ class ReportDao:
active_filling.initial_packages.label('initial_packages'),
BizPalletInventory.remaining_packages.label('remaining_packages'),
BizPalletInventory.updated_at.label('updated_at'),
BizPalletInventory.tunnel_id.label('tunnel_id'),
current_tunnel.tunnel_code.label('tunnel_code'),
current_tunnel.tunnel_name.label('tunnel_name'),
BizPalletInventory.trace_no.label('trace_no'),
active_filling.production_date.label('production_date'),
active_filling.expiry_date.label('expiry_date'),
)
.select_from(BizPalletInventory)
.join(BizPallet, BizPallet.pallet_id == BizPalletInventory.pallet_id)
@@ -221,6 +259,7 @@ class ReportDao:
.outerjoin(current_warehouse, current_warehouse.warehouse_id == BizPalletInventory.warehouse_id)
.outerjoin(current_site, current_site.site_id == BizPalletInventory.site_id)
.outerjoin(current_line, current_line.line_id == BizPalletInventory.current_line_id)
.outerjoin(current_tunnel, current_tunnel.tunnel_id == BizPalletInventory.tunnel_id)
.where(*conditions)
.order_by(BizPalletInventory.pallet_id)
)
@@ -374,14 +413,21 @@ class ReportDao:
site_ids: list[int] | None = None,
) -> PageModel[ConsumptionReportModel]:
operator_user = aliased(SysUser, name='operator_user')
usage_tunnel = aliased(BizTunnel, name='usage_tunnel')
# 新记录优先使用业务快照;历史空值只回查该次使用引用的装填记录。
batch_no = func.coalesce(BizUsageRecord.batch_no_snapshot, BizFillingRecord.batch_no)
conditions = [
batch_scope_condition(BizUsageRecord.test_batch_id, scope),
batch_scope_condition(BizPallet.test_batch_id, scope),
batch_scope_condition(BizFillingRecord.test_batch_id, scope),
batch_scope_condition(BizCustomerSite.test_batch_id, scope),
batch_scope_condition(BizProductionLine.test_batch_id, scope),
cls._joined_scope_condition(BizProductionLine.line_id, BizProductionLine.test_batch_id, scope),
cls._joined_scope_condition(usage_tunnel.tunnel_id, usage_tunnel.test_batch_id, scope),
cls._joined_scope_condition(BizCustomer.customer_id, BizCustomer.test_batch_id, scope),
]
conditions.extend(cls._tunnel_conditions(
query_object, BizUsageRecord.trace_no, (BizUsageRecord.tunnel_id, usage_tunnel),
))
site_scope = cls._scope_condition(BizUsageRecord.site_id, site_ids)
if site_scope is not None:
conditions.append(site_scope)
@@ -396,7 +442,7 @@ class ReportDao:
if query_object.strain_id:
conditions.append(BizFillingRecord.strain_id == query_object.strain_id)
if query_object.batch_no:
conditions.append(BizFillingRecord.batch_no.like(f'%{query_object.batch_no}%'))
conditions.append(batch_no.like(f'%{query_object.batch_no}%'))
conditions.extend(
cls._time_conditions(
BizUsageRecord.occurred_at,
@@ -421,17 +467,25 @@ class ReportDao:
BizFillingRecord.strain_id.label('strain_id'),
BizFillingRecord.strain_code_snapshot.label('strain_code'),
BizFillingRecord.strain_name_snapshot.label('strain_name'),
BizFillingRecord.batch_no.label('batch_no'),
batch_no.label('batch_no'),
BizUsageRecord.used_packages.label('used_packages'),
BizUsageRecord.remaining_packages_after.label('remaining_packages_after'),
BizUsageRecord.operator_id.label('operator_id'),
operator_user.user_name.label('operator_name'),
BizUsageRecord.tunnel_id.label('tunnel_id'),
usage_tunnel.tunnel_code.label('tunnel_code'),
usage_tunnel.tunnel_name.label('tunnel_name'),
BizUsageRecord.trace_no.label('trace_no'),
BizUsageRecord.batch_no_snapshot.label('batch_no_snapshot'),
BizFillingRecord.production_date.label('production_date'),
BizFillingRecord.expiry_date.label('expiry_date'),
)
.select_from(BizUsageRecord)
.join(BizPallet, BizPallet.pallet_id == BizUsageRecord.pallet_id)
.join(BizFillingRecord, BizFillingRecord.filling_id == BizUsageRecord.active_filling_id)
.join(BizCustomerSite, BizCustomerSite.site_id == BizUsageRecord.site_id)
.join(BizProductionLine, BizProductionLine.line_id == BizUsageRecord.line_id)
.outerjoin(BizProductionLine, BizProductionLine.line_id == BizUsageRecord.line_id)
.outerjoin(usage_tunnel, usage_tunnel.tunnel_id == BizUsageRecord.tunnel_id)
.outerjoin(BizCustomer, BizCustomer.customer_id == BizCustomerSite.customer_id)
.outerjoin(operator_user, operator_user.user_id == BizUsageRecord.operator_id)
.where(*conditions)
@@ -450,13 +504,23 @@ class ReportDao:
) -> PageModel[ReturnReportModel]:
source_site = aliased(BizCustomerSite, name='source_site')
target_warehouse = aliased(BizWarehouse, name='target_warehouse')
return_tunnel = aliased(BizTunnel, name='return_tunnel')
return_filling = aliased(BizFillingRecord, name='return_filling')
batch_no = func.coalesce(BizReturnItem.batch_no_snapshot, return_filling.batch_no)
conditions = [
batch_scope_condition(BizReturnOrder.test_batch_id, scope),
batch_scope_condition(BizReturnItem.test_batch_id, scope),
batch_scope_condition(BizPallet.test_batch_id, scope),
cls._joined_scope_condition(source_site.site_id, source_site.test_batch_id, scope),
cls._joined_scope_condition(target_warehouse.warehouse_id, target_warehouse.test_batch_id, scope),
cls._joined_scope_condition(return_tunnel.tunnel_id, return_tunnel.test_batch_id, scope),
cls._joined_scope_condition(return_filling.filling_id, return_filling.test_batch_id, scope),
]
conditions.extend(cls._tunnel_conditions(
query_object, BizReturnItem.trace_no_snapshot, (BizReturnItem.tunnel_id_snapshot, return_tunnel),
))
if query_object.batch_no:
conditions.append(batch_no.like(f'%{query_object.batch_no}%'))
conditions.extend(
cls._or_scope_conditions(
(BizReturnOrder.source_site_id, site_ids),
@@ -496,12 +560,22 @@ class ReportDao:
BizPallet.pallet_code.label('pallet_code'),
BizReturnItem.active_filling_id_snapshot.label('active_filling_id_snapshot'),
BizReturnItem.remaining_packages_snapshot.label('remaining_packages_snapshot'),
BizReturnItem.tunnel_id_snapshot.label('tunnel_id'),
return_tunnel.tunnel_code.label('tunnel_code'),
return_tunnel.tunnel_name.label('tunnel_name'),
BizReturnItem.trace_no_snapshot.label('trace_no'),
BizReturnItem.batch_no_snapshot.label('batch_no_snapshot'),
batch_no.label('batch_no'),
return_filling.production_date.label('production_date'),
return_filling.expiry_date.label('expiry_date'),
)
.select_from(BizReturnOrder)
.join(BizReturnItem, BizReturnItem.return_id == BizReturnOrder.return_id)
.join(BizPallet, BizPallet.pallet_id == BizReturnItem.pallet_id)
.outerjoin(source_site, source_site.site_id == BizReturnOrder.source_site_id)
.outerjoin(target_warehouse, target_warehouse.warehouse_id == BizReturnOrder.target_warehouse_id)
.outerjoin(return_tunnel, return_tunnel.tunnel_id == BizReturnItem.tunnel_id_snapshot)
.outerjoin(return_filling, return_filling.filling_id == BizReturnItem.active_filling_id_snapshot)
.where(*conditions)
.order_by(BizReturnOrder.return_id, BizReturnItem.return_item_id)
)
@@ -522,6 +596,41 @@ class ReportDao:
after_site = aliased(BizCustomerSite, name='after_site')
before_line = aliased(BizProductionLine, name='before_line')
after_line = aliased(BizProductionLine, name='after_line')
before_tunnel = aliased(BizTunnel, name='before_tunnel')
after_tunnel = aliased(BizTunnel, name='after_tunnel')
trace_return_item = aliased(BizReturnItem, name='trace_return_item')
trace_usage = aliased(BizUsageRecord, name='trace_usage')
trace_shipment_item = aliased(BizShipmentItem, name='trace_shipment_item')
trace_filling = aliased(BizFillingRecord, name='trace_filling')
prior_filling_event = aliased(BizPalletEvent, name='prior_filling_event')
# 入隧事件来源是隧道而非装填记录,按托盘事件序号回查当时已发生的最后一次装填。
# 其他事件只使用来源单据的装填引用;引用缺失时保留空日期,不借用后续重装填。
prior_filling_id = (
select(prior_filling_event.source_id)
.where(
prior_filling_event.pallet_id == BizPalletEvent.pallet_id,
prior_filling_event.event_seq < BizPalletEvent.event_seq,
prior_filling_event.event_type == PalletEventType.FILLING_CONFIRMED.value,
prior_filling_event.source_type == 'FILLING',
batch_scope_condition(prior_filling_event.test_batch_id, scope),
)
.order_by(prior_filling_event.event_seq.desc())
.limit(1)
.correlate(BizPalletEvent)
.scalar_subquery()
)
trace_filling_id = case(
(BizPalletEvent.source_type == 'FILLING', BizPalletEvent.source_id),
(BizPalletEvent.source_type == 'USAGE', trace_usage.active_filling_id),
(BizPalletEvent.source_type == 'RETURN', trace_return_item.active_filling_id_snapshot),
(BizPalletEvent.source_type == 'SHIPMENT', trace_shipment_item.active_filling_id),
(BizPalletEvent.event_type == PalletEventType.TUNNEL_ENTERED.value, prior_filling_id),
else_=None,
)
# 部分退回事件未保存隧道;只从同单同托盘的冻结退回项补读,不读取当前库存。
before_tunnel_id = func.coalesce(BizPalletEvent.before_tunnel_id, trace_return_item.tunnel_id_snapshot)
trace_no = func.coalesce(BizPalletEvent.trace_no, trace_return_item.trace_no_snapshot)
batch_no = func.coalesce(BizPalletEvent.batch_no_snapshot, trace_return_item.batch_no_snapshot)
operator_user = aliased(SysUser, name='operator_user')
conditions = [
batch_scope_condition(BizPalletEvent.test_batch_id, scope),
@@ -532,7 +641,20 @@ class ReportDao:
cls._joined_scope_condition(after_site.site_id, after_site.test_batch_id, scope),
cls._joined_scope_condition(before_line.line_id, before_line.test_batch_id, scope),
cls._joined_scope_condition(after_line.line_id, after_line.test_batch_id, scope),
cls._joined_scope_condition(before_tunnel.tunnel_id, before_tunnel.test_batch_id, scope),
cls._joined_scope_condition(after_tunnel.tunnel_id, after_tunnel.test_batch_id, scope),
cls._joined_scope_condition(trace_return_item.return_item_id, trace_return_item.test_batch_id, scope),
cls._joined_scope_condition(trace_usage.usage_id, trace_usage.test_batch_id, scope),
cls._joined_scope_condition(trace_shipment_item.shipment_item_id, trace_shipment_item.test_batch_id, scope),
cls._joined_scope_condition(trace_filling.filling_id, trace_filling.test_batch_id, scope),
]
conditions.extend(cls._tunnel_conditions(
query_object, trace_no,
(before_tunnel_id, before_tunnel),
(BizPalletEvent.after_tunnel_id, after_tunnel),
))
if query_object.batch_no:
conditions.append(batch_no.like(f'%{query_object.batch_no}%'))
conditions.extend(cls._pallet_trace_scope_conditions(warehouse_ids, site_ids))
if query_object.pallet_code:
conditions.append(BizPallet.pallet_code.like(f'%{query_object.pallet_code}%'))
@@ -574,6 +696,19 @@ class ReportDao:
BizPalletEvent.operator_id.label('operator_id'),
operator_user.user_name.label('operator_name'),
BizPalletEvent.occurred_at.label('occurred_at'),
func.coalesce(BizPalletEvent.after_tunnel_id, before_tunnel_id).label('tunnel_id'),
func.coalesce(after_tunnel.tunnel_code, before_tunnel.tunnel_code).label('tunnel_code'),
func.coalesce(after_tunnel.tunnel_name, before_tunnel.tunnel_name).label('tunnel_name'),
trace_no.label('trace_no'),
batch_no.label('batch_no_snapshot'),
before_tunnel_id.label('before_tunnel_id'),
before_tunnel.tunnel_code.label('before_tunnel_code'),
before_tunnel.tunnel_name.label('before_tunnel_name'),
BizPalletEvent.after_tunnel_id.label('after_tunnel_id'),
after_tunnel.tunnel_code.label('after_tunnel_code'),
after_tunnel.tunnel_name.label('after_tunnel_name'),
trace_filling.production_date.label('production_date'),
trace_filling.expiry_date.label('expiry_date'),
)
.select_from(BizPalletEvent)
.join(BizPallet, BizPallet.pallet_id == BizPalletEvent.pallet_id)
@@ -583,6 +718,27 @@ class ReportDao:
.outerjoin(after_site, after_site.site_id == BizPalletEvent.after_site_id)
.outerjoin(before_line, before_line.line_id == BizPalletEvent.before_line_id)
.outerjoin(after_line, after_line.line_id == BizPalletEvent.after_line_id)
.outerjoin(trace_return_item, and_(
BizPalletEvent.source_type == 'RETURN',
trace_return_item.return_id == BizPalletEvent.source_id,
trace_return_item.pallet_id == BizPalletEvent.pallet_id,
))
.outerjoin(trace_usage, and_(
BizPalletEvent.source_type == 'USAGE',
trace_usage.usage_id == BizPalletEvent.source_id,
trace_usage.pallet_id == BizPalletEvent.pallet_id,
))
.outerjoin(trace_shipment_item, and_(
BizPalletEvent.source_type == 'SHIPMENT',
trace_shipment_item.shipment_id == BizPalletEvent.source_id,
trace_shipment_item.pallet_id == BizPalletEvent.pallet_id,
))
.outerjoin(trace_filling, and_(
trace_filling.filling_id == trace_filling_id,
trace_filling.pallet_id == BizPalletEvent.pallet_id,
))
.outerjoin(before_tunnel, before_tunnel.tunnel_id == before_tunnel_id)
.outerjoin(after_tunnel, after_tunnel.tunnel_id == BizPalletEvent.after_tunnel_id)
.outerjoin(operator_user, operator_user.user_id == BizPalletEvent.operator_id)
.where(*conditions)
.order_by(BizPalletEvent.occurred_at, BizPalletEvent.event_id)