diff --git a/src/modules/assets/AssetsModule.tsx b/src/modules/assets/AssetsModule.tsx index 00e1415..eb19036 100644 --- a/src/modules/assets/AssetsModule.tsx +++ b/src/modules/assets/AssetsModule.tsx @@ -34,7 +34,7 @@ import { deriveInventoryView, deriveModalVehicleView, formatLocalDateTime, - getWeeklyFlowRange, + getRecentFlowRange, selectFlowDetails, type VehicleModalSelection, type FlowSelection, @@ -84,10 +84,10 @@ export default function AssetsModule() { const [error, setError] = useState(null); const [lastUpdate, setLastUpdate] = useState(() => formatLocalDateTime(new Date())); const [modalLoading, setModalLoading] = useState(false); - const [flowRange, setFlowRange] = useState(() => getWeeklyFlowRange()); + const [flowRange, setFlowRange] = useState(() => getRecentFlowRange()); const [flowStats, setFlowStats] = useState(null); const [flowLoading, setFlowLoading] = useState(false); - const [flowDailyExpanded, setFlowDailyExpanded] = useState(false); + const [flowDailyExpanded, setFlowDailyExpanded] = useState(true); const [selectedFlow, setSelectedFlow] = useState(null); // Dept/Region/Customer data diff --git a/src/modules/assets/components/AssetsHeader.tsx b/src/modules/assets/components/AssetsHeader.tsx index 008a9f8..9b690ca 100644 --- a/src/modules/assets/components/AssetsHeader.tsx +++ b/src/modules/assets/components/AssetsHeader.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React from 'react'; import { ChevronDown, Filter, Loader2, Search } from 'lucide-react'; import { motion } from 'motion/react'; import type { SubjectOption } from '../api'; @@ -30,42 +30,6 @@ const TABS = [ { id: 'customer', label: '按客户' }, ]; -function MarqueeBanner() { - const trackRef = useRef(null); - const innerRef = useRef(null); - const [overflow, setOverflow] = useState(false); - - useEffect(() => { - const check = () => { - if (!trackRef.current || !innerRef.current) return; - setOverflow(innerRef.current.scrollWidth > trackRef.current.clientWidth); - }; - check(); - const ro = new ResizeObserver(check); - ro.observe(trackRef.current!); - return () => ro.disconnect(); - }, []); - - const text = '车辆资产已于 2026 年 6 月 18 日完成“运营状态”与“业务关联”校验'; - - return ( -
-
-
- - {text} - - {overflow && ( - - {text} - - )} -
-
-
- ); -} - export function AssetsHeader({ activeTab, setActiveTab, @@ -246,16 +210,16 @@ export function AssetsHeader({ ))} {/* Status row */} -
+
- 最后更新: {lastUpdate} + 数据更新时间:{lastUpdate.slice(0, 16)}
+ + 运维台账每日统计口径:当日17:00
- {/* OneOS 迁移提示滚动条 */} - ); } diff --git a/src/modules/assets/components/overview/FlowDateRangePicker.tsx b/src/modules/assets/components/overview/FlowDateRangePicker.tsx index e323fa2..ed33dee 100644 --- a/src/modules/assets/components/overview/FlowDateRangePicker.tsx +++ b/src/modules/assets/components/overview/FlowDateRangePicker.tsx @@ -1,6 +1,6 @@ import { useRef, useState } from 'react'; import { ArrowRight, CalendarDays, Check, ChevronDown, ChevronLeft, ChevronRight, X } from 'lucide-react'; -import { getWeeklyFlowRange, type DateRange } from '../../model'; +import { getRecentFlowRange, getWeeklyFlowRange, type DateRange } from '../../model'; const weekdays = ['一', '二', '三', '四', '五', '六', '日']; const ymd = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; @@ -25,7 +25,7 @@ export function FlowDateRangePicker({ value, onChange }: { value: DateRange; onC { label: '今天', range: { start: today, end: today } }, { label: '昨天', range: { start: ymd(shiftDay(new Date(), -1)), end: ymd(shiftDay(new Date(), -1)) } }, { label: '业务周', range: getWeeklyFlowRange() }, - { label: '近 7 天', range: { start: ymd(shiftDay(new Date(), -6)), end: today } }, + { label: '近 7 天', range: getRecentFlowRange() }, { label: '近 30 天', range: { start: ymd(shiftDay(new Date(), -29)), end: today } }, ]; const open = () => { diff --git a/src/modules/assets/components/overview/FlowStatisticsCard.tsx b/src/modules/assets/components/overview/FlowStatisticsCard.tsx index a4f867d..181a85b 100644 --- a/src/modules/assets/components/overview/FlowStatisticsCard.tsx +++ b/src/modules/assets/components/overview/FlowStatisticsCard.tsx @@ -86,7 +86,7 @@ export function FlowStatisticsCard({ {flowLoading && (
正在加载交还车统计...
)} - {!flowLoading && flowStats?.daily.map((day) => ( + {!flowLoading && flowStats?.daily.slice().sort((a, b) => b.date.localeCompare(a.date)).map((day) => (
{day.date}
diff --git a/src/modules/assets/model.ts b/src/modules/assets/model.ts index 8b1330f..a80ad10 100644 --- a/src/modules/assets/model.ts +++ b/src/modules/assets/model.ts @@ -126,6 +126,13 @@ function addDays(date: Date, days: number): Date { } // 资产流转周报按周六至周五统计;周末查看时仍停留在刚结束的周五。 +export function getRecentFlowRange(referenceDate = new Date()): DateRange { + return { + start: formatLocalDate(addDays(referenceDate, -6)), + end: formatLocalDate(referenceDate), + }; +} + export function getWeeklyFlowRange(referenceDate = new Date()): DateRange { const day = referenceDate.getDay(); const end = day === 6 diff --git a/src/server/routes/mileage/vehicle-info.ts b/src/server/routes/mileage/vehicle-info.ts index 64fa258..d0b0f30 100644 --- a/src/server/routes/mileage/vehicle-info.ts +++ b/src/server/routes/mileage/vehicle-info.ts @@ -1,13 +1,14 @@ import pool from '../../db.js'; +import { LATEST_VEHICLE_LEASE_JOIN_SQL } from '../../vehicle-lease-sql.js'; import type { VehicleInfoRow } from './types.js'; /** 车辆关联信息 SQL(客户名、部门、经理、租赁状态、主体、项目、品牌) */ export const VEHICLE_INFO_SQL = `SELECT vi.plate_number AS plate, vi.vin AS vin, - COALESCE(c.customer_name, vor.customer_name, ci.customer_name) AS customer, - COALESCE(c.business_department_name, vor.business_dept) AS department, - COALESCE(c.business_manager_name, vor.business_manager) AS manager, + vor.customer_name AS customer, + vor.business_dept AS department, + vor.business_manager AS manager, CAST(COALESCE(c.business_manager_id, vi.business_id) AS CHAR) AS manager_id, CASE vs.operation_status WHEN '1' THEN '租赁' @@ -18,7 +19,7 @@ export const VEHICLE_INFO_SQL = `SELECT ELSE vs.operation_status END AS rent_status, NULLIF(vi.registered_ownership, '') AS entity, - COALESCE(c.project_name, vor.project_name) AS project, + vor.project_name AS project, COALESCE(vi_operation_city.NAME, NULLIF(vi.operation_city, '')) AS inventory_location, CASE vm.brand WHEN 'hyundai' THEN CASE WHEN vm.model LIKE '%帕力安%' OR vm.model LIKE '%冷链%' OR vm.model LIKE '%双飞翼%' THEN '帕力安牌' ELSE '现代' END @@ -50,21 +51,7 @@ LEFT JOIN vehicle_model vm LEFT JOIN common_district vi_operation_city ON vi_operation_city.CODE = vi.operation_city COLLATE utf8mb4_unicode_ci AND vi_operation_city.STATUS = 'VALID' -LEFT JOIN vehicle_lease_order_record vor - ON vor.vehicle_id = vi.id - AND vor.del_flag = '0' - AND vor.id = ( - SELECT MAX(vor2.id) - FROM vehicle_lease_order_record vor2 - WHERE vor2.vehicle_id = vi.id - AND vor2.del_flag = '0' - ) -LEFT JOIN vehicle_lease_contract_info c - ON c.order_id = vor.contract_id - AND c.del_flag = '0' -LEFT JOIN customer_info ci - ON ci.id = vi.customer_id - AND ci.del_flag = '0' +${LATEST_VEHICLE_LEASE_JOIN_SQL} WHERE vi.del_flag = '0' AND COALESCE(vs.operation_status, '') <> '5'`; diff --git a/src/server/routes/vehicles/repository.ts b/src/server/routes/vehicles/repository.ts index b2d23f6..aa10cc4 100644 --- a/src/server/routes/vehicles/repository.ts +++ b/src/server/routes/vehicles/repository.ts @@ -1,4 +1,5 @@ import pool from '../../db.js'; +import { LATEST_VEHICLE_LEASE_JOIN_SQL } from '../../vehicle-lease-sql.js'; import type { Vehicle, VehicleRow } from '../../types.js'; import { transformRow } from './model.js'; import type { WeeklyTruckIds } from './types.js'; @@ -80,13 +81,13 @@ export const MAIN_SQL = `SELECT ELSE vm.brand END AS 车辆品牌Label, c.id AS 合同ID, - COALESCE(c.contract_code, vor.contract_code, vi.contract_code) AS 合同编码, - COALESCE(c.customer_name, vor.customer_name, ci.customer_name) AS 客户名称, + vor.contract_code AS 合同编码, + vor.customer_name AS 客户名称, c.signing_company AS 合同归属公司, - COALESCE(c.business_department_name, vor.business_dept) AS 合同归属部门, + vor.business_dept AS 合同归属部门, NULLIF(vi.registered_ownership, '') AS 主体, - COALESCE(c.project_name, vor.project_name) AS 项目名称, - COALESCE(c.business_manager_name, vor.business_manager) AS 客户经理, + vor.project_name AS 项目名称, + vor.business_manager AS 客户经理, CAST(COALESCE(c.business_manager_id, vi.business_id) AS CHAR) AS 经理ID FROM vehicle_info vi LEFT JOIN vehicle_status vs @@ -113,21 +114,7 @@ LEFT JOIN common_district vi_city LEFT JOIN common_district vi_operation_city ON vi_operation_city.CODE = vi.operation_city COLLATE utf8mb4_unicode_ci AND vi_operation_city.STATUS = 'VALID' -LEFT JOIN vehicle_lease_order_record vor - ON vor.vehicle_id = vi.id - AND vor.del_flag = '0' - AND vor.id = ( - SELECT MAX(vor2.id) - FROM vehicle_lease_order_record vor2 - WHERE vor2.vehicle_id = vi.id - AND vor2.del_flag = '0' - ) -LEFT JOIN vehicle_lease_contract_info c - ON c.order_id = vor.contract_id - AND c.del_flag = '0' -LEFT JOIN customer_info ci - ON ci.id = vi.customer_id - AND ci.del_flag = '0' +${LATEST_VEHICLE_LEASE_JOIN_SQL} WHERE vi.del_flag = '0' AND COALESCE(vs.operation_status, '') <> '5'`; diff --git a/src/server/vehicle-lease-sql.ts b/src/server/vehicle-lease-sql.ts new file mode 100644 index 0000000..dde11a2 --- /dev/null +++ b/src/server/vehicle-lease-sql.ts @@ -0,0 +1,25 @@ +/** + * 与 OneOS VehicleInfoMapper.LatestVehicleLeaseJoin 保持一致。 + * vi 为车辆表;vor 为当前业务记录;c 为合同。 + * 还车会清空 vor 的客户与合同信息,调用方不得用历史档案补回空值。 + */ +export const LATEST_VEHICLE_LEASE_JOIN_SQL = `LEFT JOIN ( + SELECT ranked_record.* + FROM ( + SELECT source_record.*, + ROW_NUMBER() OVER ( + PARTITION BY source_record.vehicle_id + ORDER BY COALESCE( + source_record.last_delivery_time, + source_record.last_return_time, + source_record.update_time + ) DESC, source_record.id DESC + ) AS lease_rn + FROM vehicle_lease_order_record source_record + WHERE source_record.del_flag = '0' + ) ranked_record + WHERE ranked_record.lease_rn = 1 + ) vor ON vor.vehicle_id = vi.id +LEFT JOIN vehicle_lease_contract_info c + ON c.id = vor.contract_id + AND c.del_flag = '0'`;