接单工单与资金申请列表支持创建时间区间筛选

This commit is contained in:
yml2213
2026-08-22 09:48:09 +08:00
parent f1e219d1ef
commit 3618df74c5
8 changed files with 104 additions and 2 deletions
@@ -414,6 +414,9 @@ export type ListInput = {
sort?: 'id_desc' | 'worker_claimed_at_desc' sort?: 'id_desc' | 'worker_claimed_at_desc'
/** 后台使用拼单聚合业务状态;大厅和打手列表使用原始生命周期。 */ /** 后台使用拼单聚合业务状态;大厅和打手列表使用原始生命周期。 */
useOperationalStatus?: boolean useOperationalStatus?: boolean
/** 创建时间区间(YYYY-MM-DD,上海时区,含起止日);后台列表筛选使用。 */
createdFrom?: string
createdTo?: string
} }
export type WorkOrderStatistics = { export type WorkOrderStatistics = {
@@ -465,6 +468,9 @@ export type FinanceRequestListInput = {
status?: string status?: string
requestType?: string requestType?: string
keyword?: string keyword?: string
/** 申请时间区间(YYYY-MM-DD,上海时区,含起止日);后台列表筛选使用。 */
createdFrom?: string
createdTo?: string
} }
export type CreateWorkerInput = { export type CreateWorkerInput = {
@@ -170,6 +170,8 @@ export async function listWorkOrders({
useOperationalStatus = false, useOperationalStatus = false,
pinnedFirst = false, pinnedFirst = false,
sort = 'id_desc', sort = 'id_desc',
createdFrom = '',
createdTo = '',
}: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> { }: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> {
const effectiveStatuses = statuses.length > 0 ? statuses : status.trim() ? [status.trim()] : [] const effectiveStatuses = statuses.length > 0 ? statuses : status.trim() ? [status.trim()] : []
const { joins, whereClause, params } = buildWorkOrderWhere({ const { joins, whereClause, params } = buildWorkOrderWhere({
@@ -185,6 +187,8 @@ export async function listWorkOrders({
excludeFilledSharing, excludeFilledSharing,
vipEvidencePending, vipEvidencePending,
useOperationalStatus, useOperationalStatus,
createdFrom,
createdTo,
}) })
const totalResult = await query<{ total: number }>( const totalResult = await query<{ total: number }>(
` `
@@ -519,10 +523,24 @@ export function buildWorkOrderWhere({
excludeFilledSharing = false, excludeFilledSharing = false,
vipEvidencePending = false, vipEvidencePending = false,
useOperationalStatus = false, useOperationalStatus = false,
createdFrom = '',
createdTo = '',
}: ListInput) { }: ListInput) {
const filters: string[] = [] const filters: string[] = []
const params: unknown[] = [] const params: unknown[] = []
const joins: string[] = [] const joins: string[] = []
if (createdFrom) {
params.push(createdFrom)
filters.push(
`wo.created_at >= ($${params.length}::date)::timestamp AT TIME ZONE 'Asia/Shanghai'`,
)
}
if (createdTo) {
params.push(createdTo)
filters.push(
`wo.created_at < (($${params.length}::date + 1)::timestamp AT TIME ZONE 'Asia/Shanghai')`,
)
}
let workerSharePlaceholder = '' let workerSharePlaceholder = ''
if (excludeFilledSharing) { if (excludeFilledSharing) {
@@ -203,6 +203,8 @@ export async function listWorkerFinanceRequests({
status = '', status = '',
requestType = '', requestType = '',
keyword = '', keyword = '',
createdFrom = '',
createdTo = '',
}: FinanceRequestListInput = {}): Promise<{ items: WorkerFinanceRequestRow[]; total: number }> { }: FinanceRequestListInput = {}): Promise<{ items: WorkerFinanceRequestRow[]; total: number }> {
const { whereClause, params } = buildWorkerFinanceRequestWhere({ const { whereClause, params } = buildWorkerFinanceRequestWhere({
requestId, requestId,
@@ -210,6 +212,8 @@ export async function listWorkerFinanceRequests({
status, status,
requestType, requestType,
keyword, keyword,
createdFrom,
createdTo,
}) })
const totalResult = await query<{ total: number }>( const totalResult = await query<{ total: number }>(
`SELECT COUNT(*)::int AS total FROM worker_finance_requests wfr `SELECT COUNT(*)::int AS total FROM worker_finance_requests wfr
@@ -415,9 +419,23 @@ function buildWorkerFinanceRequestWhere({
status = '', status = '',
requestType = '', requestType = '',
keyword = '', keyword = '',
createdFrom = '',
createdTo = '',
}: FinanceRequestListInput) { }: FinanceRequestListInput) {
const filters: string[] = [] const filters: string[] = []
const params: unknown[] = [] const params: unknown[] = []
if (createdFrom) {
params.push(createdFrom)
filters.push(
`wfr.created_at >= ($${params.length}::date)::timestamp AT TIME ZONE 'Asia/Shanghai'`,
)
}
if (createdTo) {
params.push(createdTo)
filters.push(
`wfr.created_at < (($${params.length}::date + 1)::timestamp AT TIME ZONE 'Asia/Shanghai')`,
)
}
if (requestId) { if (requestId) {
params.push(requestId) params.push(requestId)
filters.push(`wfr.id = $${params.length}`) filters.push(`wfr.id = $${params.length}`)
@@ -26,6 +26,12 @@ export function normalizeDateQuery(rawValue: unknown, endOfDay = false) {
return normalized return normalized
} }
/** 校验并归一化 YYYY-MM-DD 日期参数,非法或空值返回空字符串。 */
export function normalizeDateString(rawValue: unknown) {
const normalized = String(rawValue || '').trim()
return /^\d{4}-\d{2}-\d{2}$/.test(normalized) ? normalized : ''
}
export function safeParseJson(rawText: unknown): JsonObject { export function safeParseJson(rawText: unknown): JsonObject {
if (rawText && typeof rawText === 'object') { if (rawText && typeof rawText === 'object') {
return rawText return rawText
@@ -6,7 +6,11 @@ import {
sumPendingUnfreezeByOrderIds, sumPendingUnfreezeByOrderIds,
} from '../../repositories/worker-platform/index.js' } from '../../repositories/worker-platform/index.js'
import type { JsonObject } from '../../types/json.js' import type { JsonObject } from '../../types/json.js'
import { normalizePage, normalizePageSize } from '../admin/admin-query-utils.js' import {
normalizeDateString,
normalizePage,
normalizePageSize,
} from '../admin/admin-query-utils.js'
import { import {
mapWorkOrderAdmin, mapWorkOrderAdmin,
mapWorkOrderEvents, mapWorkOrderEvents,
@@ -32,6 +36,8 @@ export async function listAdminWorkOrders(query: JsonObject = {}) {
.filter((levelId): levelId is number => levelId !== null) .filter((levelId): levelId is number => levelId !== null)
const statuses = normalizeStatuses(query.status) const statuses = normalizeStatuses(query.status)
const vipEvidencePending = normalizeBoolean(query.vipEvidencePending, false) const vipEvidencePending = normalizeBoolean(query.vipEvidencePending, false)
const createdFrom = normalizeDateString(query.createdFrom)
const createdTo = normalizeDateString(query.createdTo)
const filters = { const filters = {
statuses, statuses,
workOrderId, workOrderId,
@@ -40,6 +46,8 @@ export async function listAdminWorkOrders(query: JsonObject = {}) {
workerLevelIds, workerLevelIds,
vipEvidencePending, vipEvidencePending,
useOperationalStatus: true, useOperationalStatus: true,
...(createdFrom ? { createdFrom } : {}),
...(createdTo ? { createdTo } : {}),
} }
const { items, total } = await listWorkOrders({ page, pageSize, ...filters }) const { items, total } = await listWorkOrders({ page, pageSize, ...filters })
const statistics = await getWorkOrderStatistics(filters) const statistics = await getWorkOrderStatistics(filters)
@@ -10,7 +10,12 @@ import type { WorkerWithdrawalAccountRow } from '../../repositories/worker-platf
import type { JsonObject } from '../../types/json.js' import type { JsonObject } from '../../types/json.js'
import { createHttpError } from '../../utils/http.js' import { createHttpError } from '../../utils/http.js'
import { nowIso } from '../../utils/time.js' import { nowIso } from '../../utils/time.js'
import { normalizePage, normalizePageSize, safeParseJson } from '../admin/admin-query-utils.js' import {
normalizeDateString,
normalizePage,
normalizePageSize,
safeParseJson,
} from '../admin/admin-query-utils.js'
import { resolveAdminNotificationEntity } from '../admin/admin-notification-service.js' import { resolveAdminNotificationEntity } from '../admin/admin-notification-service.js'
import { refreshUploadedFileUrls } from '../file-storage/file-storage-service.js' import { refreshUploadedFileUrls } from '../file-storage/file-storage-service.js'
import { import {
@@ -162,6 +167,8 @@ export async function listAdminWorkerFinanceRequests(query: JsonObject = {}) {
const status = normalizeFinanceRequestStatus(query.status) const status = normalizeFinanceRequestStatus(query.status)
const requestType = normalizeFinanceRequestType(query.requestType) const requestType = normalizeFinanceRequestType(query.requestType)
const keyword = String(query.keyword || '').trim() const keyword = String(query.keyword || '').trim()
const createdFrom = normalizeDateString(query.createdFrom)
const createdTo = normalizeDateString(query.createdTo)
const { items, total } = await listWorkerFinanceRequests({ const { items, total } = await listWorkerFinanceRequests({
page, page,
pageSize, pageSize,
@@ -169,6 +176,8 @@ export async function listAdminWorkerFinanceRequests(query: JsonObject = {}) {
status, status,
requestType, requestType,
keyword, keyword,
...(createdFrom ? { createdFrom } : {}),
...(createdTo ? { createdTo } : {}),
}) })
return { return {
items: items.map(mapFinanceRequest), items: items.map(mapFinanceRequest),
@@ -34,6 +34,8 @@ import type {
WorkerFinanceConfig, WorkerFinanceConfig,
WorkerFinanceRequest, WorkerFinanceRequest,
} from '@/types/worker-platform' } from '@/types/worker-platform'
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
import dayjs, { ADMIN_DATE_FORMAT } from '@/lib/dayjs'
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination' import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
import { formatAdminDateTime } from '@/utils/admin-time' import { formatAdminDateTime } from '@/utils/admin-time'
import { asRecord, formatMoney } from './shared' import { asRecord, formatMoney } from './shared'
@@ -69,6 +71,7 @@ export default function FinancePanel() {
const [requestType, setRequestType] = useState('') const [requestType, setRequestType] = useState('')
const [keywordInput, setKeywordInput] = useState('') const [keywordInput, setKeywordInput] = useState('')
const [keyword, setKeyword] = useState('') const [keyword, setKeyword] = useState('')
const [createdRange, setCreatedRange] = useState<[string, string] | null>(null)
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE) const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
const [savingConfig, setSavingConfig] = useState(false) const [savingConfig, setSavingConfig] = useState(false)
@@ -109,6 +112,8 @@ export default function FinancePanel() {
keyword, keyword,
page, page,
pageSize, pageSize,
createdRange ? createdRange[0] : '',
createdRange ? createdRange[1] : '',
], ],
queryFn: () => queryFn: () =>
fetchAdminWorkerFinanceRequests({ fetchAdminWorkerFinanceRequests({
@@ -118,6 +123,7 @@ export default function FinancePanel() {
keyword, keyword,
page, page,
pageSize, pageSize,
...(createdRange ? { createdFrom: createdRange[0], createdTo: createdRange[1] } : {}),
}), }),
}) })
const financeRequestsPagination = financeRequestsQuery.data?.data.pagination const financeRequestsPagination = financeRequestsQuery.data?.data.pagination
@@ -466,6 +472,18 @@ export default function FinancePanel() {
setPage(1) setPage(1)
}} }}
/> />
<AdminRangePicker
value={createdRange ? [dayjs(createdRange[0]), dayjs(createdRange[1])] : null}
onChange={(dates) => {
const [start, end] = dates || []
setCreatedRange(
start && end
? [start.format(ADMIN_DATE_FORMAT), end.format(ADMIN_DATE_FORMAT)]
: null,
)
setPage(1)
}}
/>
<Button <Button
icon={<ReloadOutlined />} icon={<ReloadOutlined />}
loading={financeRequestsQuery.isFetching} loading={financeRequestsQuery.isFetching}
@@ -67,6 +67,8 @@ import {
updateAdminWorkOrderSharing, updateAdminWorkOrderSharing,
} from '@/services/admin' } from '@/services/admin'
import type { UploadedFile, WorkOrder, WorkOrderShare } from '@/types/worker-platform' import type { UploadedFile, WorkOrder, WorkOrderShare } from '@/types/worker-platform'
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
import dayjs, { ADMIN_DATE_FORMAT } from '@/lib/dayjs'
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination' import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
import { hasAdminRole } from '@/utils/admin-auth' import { hasAdminRole } from '@/utils/admin-auth'
import { formatAdminDateTime } from '@/utils/admin-time' import { formatAdminDateTime } from '@/utils/admin-time'
@@ -139,6 +141,7 @@ export default function WorkOrdersPanel() {
const [statuses, setStatuses] = useState<string[]>([]) const [statuses, setStatuses] = useState<string[]>([])
const [targetWorkOrderId, setTargetWorkOrderId] = useState<number | undefined>() const [targetWorkOrderId, setTargetWorkOrderId] = useState<number | undefined>()
const [keyword, setKeyword] = useState('') const [keyword, setKeyword] = useState('')
const [createdRange, setCreatedRange] = useState<[string, string] | null>(null)
const [keywordInput, setKeywordInput] = useState('') const [keywordInput, setKeywordInput] = useState('')
const [workerId, setWorkerId] = useState<number | undefined>() const [workerId, setWorkerId] = useState<number | undefined>()
const [workerLevelIds, setWorkerLevelIds] = useState<number[]>([]) const [workerLevelIds, setWorkerLevelIds] = useState<number[]>([])
@@ -219,6 +222,8 @@ export default function WorkOrdersPanel() {
targetWorkOrderId, targetWorkOrderId,
page, page,
pageSize, pageSize,
createdRange ? createdRange[0] : '',
createdRange ? createdRange[1] : '',
], ],
queryFn: () => queryFn: () =>
fetchAdminWorkOrders({ fetchAdminWorkOrders({
@@ -232,6 +237,7 @@ export default function WorkOrdersPanel() {
showWorkerLevelFilter && workerLevelIds.length > 0 ? workerLevelIds.join(',') : undefined, showWorkerLevelFilter && workerLevelIds.length > 0 ? workerLevelIds.join(',') : undefined,
page, page,
pageSize, pageSize,
...(createdRange ? { createdFrom: createdRange[0], createdTo: createdRange[1] } : {}),
}), }),
}) })
const ordersPagination = ordersQuery.data?.data.pagination const ordersPagination = ordersQuery.data?.data.pagination
@@ -587,6 +593,7 @@ export default function WorkOrdersPanel() {
setStatuses([]) setStatuses([])
setWorkerId(undefined) setWorkerId(undefined)
setWorkerLevelIds([]) setWorkerLevelIds([])
setCreatedRange(null)
setPage(1) setPage(1)
} }
@@ -1094,6 +1101,18 @@ export default function WorkOrdersPanel() {
}} }}
/> />
) : null} ) : null}
<AdminRangePicker
value={createdRange ? [dayjs(createdRange[0]), dayjs(createdRange[1])] : null}
onChange={(dates) => {
const [start, end] = dates || []
setCreatedRange(
start && end
? [start.format(ADMIN_DATE_FORMAT), end.format(ADMIN_DATE_FORMAT)]
: null,
)
setPage(1)
}}
/>
<Button type="primary" icon={<SearchOutlined />} onClick={searchOrders}> <Button type="primary" icon={<SearchOutlined />} onClick={searchOrders}>
</Button> </Button>