新增财务申请时间类型筛选
This commit is contained in:
@@ -513,6 +513,9 @@ export type FinanceRequestListInput = {
|
|||||||
/** 申请时间区间(YYYY-MM-DD 含全天,或 YYYY-MM-DD HH:mm:ss 精确到秒;上海时区);后台列表筛选使用。 */
|
/** 申请时间区间(YYYY-MM-DD 含全天,或 YYYY-MM-DD HH:mm:ss 精确到秒;上海时区);后台列表筛选使用。 */
|
||||||
createdFrom?: string
|
createdFrom?: string
|
||||||
createdTo?: string
|
createdTo?: string
|
||||||
|
/** 后台审核通过时间区间(上海时区);仅匹配已审核通过的申请。 */
|
||||||
|
reviewedFrom?: string
|
||||||
|
reviewedTo?: string
|
||||||
/** 收款渠道筛选:alipay / wechat */
|
/** 收款渠道筛选:alipay / wechat */
|
||||||
accountChannel?: string
|
accountChannel?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,6 +290,8 @@ export async function listWorkerFinanceRequests({
|
|||||||
keyword = '',
|
keyword = '',
|
||||||
createdFrom = '',
|
createdFrom = '',
|
||||||
createdTo = '',
|
createdTo = '',
|
||||||
|
reviewedFrom = '',
|
||||||
|
reviewedTo = '',
|
||||||
accountChannel = '',
|
accountChannel = '',
|
||||||
}: FinanceRequestListInput = {}): Promise<{ items: WorkerFinanceRequestRow[]; total: number }> {
|
}: FinanceRequestListInput = {}): Promise<{ items: WorkerFinanceRequestRow[]; total: number }> {
|
||||||
const { whereClause, params } = buildWorkerFinanceRequestWhere({
|
const { whereClause, params } = buildWorkerFinanceRequestWhere({
|
||||||
@@ -300,6 +302,8 @@ export async function listWorkerFinanceRequests({
|
|||||||
keyword,
|
keyword,
|
||||||
createdFrom,
|
createdFrom,
|
||||||
createdTo,
|
createdTo,
|
||||||
|
reviewedFrom,
|
||||||
|
reviewedTo,
|
||||||
accountChannel,
|
accountChannel,
|
||||||
})
|
})
|
||||||
const totalResult = await query<{ total: number }>(
|
const totalResult = await query<{ total: number }>(
|
||||||
@@ -335,6 +339,8 @@ export async function summarizeWorkerFinanceRequests(
|
|||||||
keyword: input.keyword || '',
|
keyword: input.keyword || '',
|
||||||
createdFrom: input.createdFrom || '',
|
createdFrom: input.createdFrom || '',
|
||||||
createdTo: input.createdTo || '',
|
createdTo: input.createdTo || '',
|
||||||
|
reviewedFrom: input.reviewedFrom || '',
|
||||||
|
reviewedTo: input.reviewedTo || '',
|
||||||
accountChannel: input.accountChannel || '',
|
accountChannel: input.accountChannel || '',
|
||||||
})
|
})
|
||||||
const result = await query<{ channel: string; total: number; amount: string | number }>(
|
const result = await query<{ channel: string; total: number; amount: string | number }>(
|
||||||
@@ -612,7 +618,7 @@ async function getWorkerFinanceRequestByIdWithClient(
|
|||||||
return result.rows[0] || null
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildWorkerFinanceRequestWhere({
|
export function buildWorkerFinanceRequestWhere({
|
||||||
requestId = 0,
|
requestId = 0,
|
||||||
workerId = 0,
|
workerId = 0,
|
||||||
status = '',
|
status = '',
|
||||||
@@ -620,6 +626,8 @@ function buildWorkerFinanceRequestWhere({
|
|||||||
keyword = '',
|
keyword = '',
|
||||||
createdFrom = '',
|
createdFrom = '',
|
||||||
createdTo = '',
|
createdTo = '',
|
||||||
|
reviewedFrom = '',
|
||||||
|
reviewedTo = '',
|
||||||
accountChannel = '',
|
accountChannel = '',
|
||||||
}: FinanceRequestListInput) {
|
}: FinanceRequestListInput) {
|
||||||
const filters: string[] = []
|
const filters: string[] = []
|
||||||
@@ -642,6 +650,22 @@ function buildWorkerFinanceRequestWhere({
|
|||||||
: `wfr.created_at < (($${params.length}::date + 1)::timestamp) AT TIME ZONE 'Asia/Shanghai'`,
|
: `wfr.created_at < (($${params.length}::date + 1)::timestamp) AT TIME ZONE 'Asia/Shanghai'`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (reviewedFrom || reviewedTo) {
|
||||||
|
// “审核通过时间”只适用于已通过的申请;待处理、拒绝和取消记录不参与该时间筛选。
|
||||||
|
filters.push(`wfr.status = 'approved'`)
|
||||||
|
}
|
||||||
|
if (reviewedFrom) {
|
||||||
|
params.push(reviewedFrom)
|
||||||
|
filters.push(`wfr.reviewed_at >= ($${params.length}::timestamp) AT TIME ZONE 'Asia/Shanghai'`)
|
||||||
|
}
|
||||||
|
if (reviewedTo) {
|
||||||
|
params.push(reviewedTo)
|
||||||
|
filters.push(
|
||||||
|
reviewedTo.length > 10
|
||||||
|
? `wfr.reviewed_at <= ($${params.length}::timestamp) AT TIME ZONE 'Asia/Shanghai'`
|
||||||
|
: `wfr.reviewed_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
-1
@@ -1,7 +1,32 @@
|
|||||||
import assert from 'node:assert/strict'
|
import assert from 'node:assert/strict'
|
||||||
import test from 'node:test'
|
import test from 'node:test'
|
||||||
|
|
||||||
import { resolveWithdrawApprovalPlan, resolveWithdrawRejectionPlan } from './worker-finance-repo.js'
|
import {
|
||||||
|
buildWorkerFinanceRequestWhere,
|
||||||
|
resolveWithdrawApprovalPlan,
|
||||||
|
resolveWithdrawRejectionPlan,
|
||||||
|
} from './worker-finance-repo.js'
|
||||||
|
|
||||||
|
test('资金申请列表支持按打手申请时间和后台审核通过时间筛选', () => {
|
||||||
|
const result = buildWorkerFinanceRequestWhere({
|
||||||
|
createdFrom: '2026-09-01 10:00:00',
|
||||||
|
createdTo: '2026-09-01 18:30:59',
|
||||||
|
reviewedFrom: '2026-09-02',
|
||||||
|
reviewedTo: '2026-09-03',
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepEqual(result.params, [
|
||||||
|
'2026-09-01 10:00:00',
|
||||||
|
'2026-09-01 18:30:59',
|
||||||
|
'2026-09-02',
|
||||||
|
'2026-09-03',
|
||||||
|
])
|
||||||
|
assert.match(result.whereClause, /wfr\.created_at >= \(\$1::timestamp\)/)
|
||||||
|
assert.match(result.whereClause, /wfr\.created_at <= \(\$2::timestamp\)/)
|
||||||
|
assert.match(result.whereClause, /wfr\.status = 'approved'/)
|
||||||
|
assert.match(result.whereClause, /wfr\.reviewed_at >= \(\$3::timestamp\)/)
|
||||||
|
assert.match(result.whereClause, /wfr\.reviewed_at < \(\(\$4::date \+ 1\)::timestamp\)/)
|
||||||
|
})
|
||||||
|
|
||||||
test('提现通过(新口径):申请时已冻结,确认打款只解冻不动可用余额', () => {
|
test('提现通过(新口径):申请时已冻结,确认打款只解冻不动可用余额', () => {
|
||||||
const plan = resolveWithdrawApprovalPlan({
|
const plan = resolveWithdrawApprovalPlan({
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ const FINANCE_EVENTS_CTE = `
|
|||||||
o.total_amount::bigint AS amount,
|
o.total_amount::bigint AS amount,
|
||||||
'订单收款'::text AS label,
|
'订单收款'::text AS label,
|
||||||
o.platform_order_id::text AS reference_no,
|
o.platform_order_id::text AS reference_no,
|
||||||
COALESCE(NULLIF(CONCAT_WS('/', NULLIF(o.provider, ''), NULLIF(o.platform, '')), ''), '其他')::text AS channel
|
COALESCE(NULLIF(CONCAT_WS('/', NULLIF(o.provider, ''), NULLIF(o.platform, '')), ''), '其他')::text AS channel,
|
||||||
|
o.created_at AS request_created_at,
|
||||||
|
NULL::timestamptz AS request_reviewed_at
|
||||||
FROM orders o
|
FROM orders o
|
||||||
WHERE o.pay_status IN ('paid', 'refunded')
|
WHERE o.pay_status IN ('paid', 'refunded')
|
||||||
AND o.total_amount > 0
|
AND o.total_amount > 0
|
||||||
@@ -37,7 +39,9 @@ const FINANCE_EVENTS_CTE = `
|
|||||||
o.total_amount::bigint,
|
o.total_amount::bigint,
|
||||||
'订单退款'::text,
|
'订单退款'::text,
|
||||||
o.platform_order_id::text,
|
o.platform_order_id::text,
|
||||||
COALESCE(NULLIF(CONCAT_WS('/', NULLIF(o.provider, ''), NULLIF(o.platform, '')), ''), '其他')::text
|
COALESCE(NULLIF(CONCAT_WS('/', NULLIF(o.provider, ''), NULLIF(o.platform, '')), ''), '其他')::text,
|
||||||
|
o.created_at,
|
||||||
|
NULL::timestamptz
|
||||||
FROM orders o
|
FROM orders o
|
||||||
WHERE o.pay_status = 'refunded'
|
WHERE o.pay_status = 'refunded'
|
||||||
AND o.total_amount > 0
|
AND o.total_amount > 0
|
||||||
@@ -53,7 +57,9 @@ const FINANCE_EVENTS_CTE = `
|
|||||||
CASE WHEN wl.ledger_type IN ('reward_settlement', 'sharing_reward')
|
CASE WHEN wl.ledger_type IN ('reward_settlement', 'sharing_reward')
|
||||||
THEN '打手报酬' ELSE '打手资金回收' END::text,
|
THEN '打手报酬' ELSE '打手资金回收' END::text,
|
||||||
COALESCE(wl.related_work_order_id::text, ''),
|
COALESCE(wl.related_work_order_id::text, ''),
|
||||||
'worker-platform'::text
|
'worker-platform'::text,
|
||||||
|
wl.created_at,
|
||||||
|
NULL::timestamptz
|
||||||
FROM worker_wallet_ledgers wl
|
FROM worker_wallet_ledgers wl
|
||||||
WHERE wl.ledger_type IN (
|
WHERE wl.ledger_type IN (
|
||||||
'reward_settlement', 'sharing_reward', 'after_sales_deposit_deduction',
|
'reward_settlement', 'sharing_reward', 'after_sales_deposit_deduction',
|
||||||
@@ -70,7 +76,9 @@ const FINANCE_EVENTS_CTE = `
|
|||||||
wfr.amount::bigint,
|
wfr.amount::bigint,
|
||||||
'打手提现'::text,
|
'打手提现'::text,
|
||||||
wfr.account_channel::text,
|
wfr.account_channel::text,
|
||||||
COALESCE(NULLIF(wfr.account_channel, ''), '其他')::text
|
COALESCE(NULLIF(wfr.account_channel, ''), '其他')::text,
|
||||||
|
wfr.created_at,
|
||||||
|
wfr.reviewed_at
|
||||||
FROM worker_finance_requests wfr
|
FROM worker_finance_requests wfr
|
||||||
WHERE wfr.request_type = 'withdraw'
|
WHERE wfr.request_type = 'withdraw'
|
||||||
AND wfr.status = 'approved'
|
AND wfr.status = 'approved'
|
||||||
@@ -86,7 +94,9 @@ const FINANCE_EVENTS_CTE = `
|
|||||||
wfr.amount::bigint,
|
wfr.amount::bigint,
|
||||||
'打手充值'::text,
|
'打手充值'::text,
|
||||||
wfr.account_channel::text,
|
wfr.account_channel::text,
|
||||||
COALESCE(NULLIF(wfr.account_channel, ''), '其他')::text
|
COALESCE(NULLIF(wfr.account_channel, ''), '其他')::text,
|
||||||
|
wfr.created_at,
|
||||||
|
wfr.reviewed_at
|
||||||
FROM worker_finance_requests wfr
|
FROM worker_finance_requests wfr
|
||||||
WHERE wfr.request_type = 'recharge'
|
WHERE wfr.request_type = 'recharge'
|
||||||
AND wfr.status = 'approved'
|
AND wfr.status = 'approved'
|
||||||
@@ -102,7 +112,9 @@ const FINANCE_EVENTS_CTE = `
|
|||||||
wasc.refunded_amount::bigint,
|
wasc.refunded_amount::bigint,
|
||||||
'售后追缴退还'::text,
|
'售后追缴退还'::text,
|
||||||
wasc.case_no::text,
|
wasc.case_no::text,
|
||||||
'worker-platform'::text
|
'worker-platform'::text,
|
||||||
|
wasc.created_at,
|
||||||
|
NULL::timestamptz
|
||||||
FROM worker_after_sales_cases wasc
|
FROM worker_after_sales_cases wasc
|
||||||
WHERE wasc.refunded_at IS NOT NULL
|
WHERE wasc.refunded_at IS NOT NULL
|
||||||
AND wasc.refunded_amount > 0
|
AND wasc.refunded_amount > 0
|
||||||
@@ -111,6 +123,8 @@ const FINANCE_EVENTS_CTE = `
|
|||||||
|
|
||||||
export async function getAdminFinanceSummary(payload: JsonObject = {}) {
|
export async function getAdminFinanceSummary(payload: JsonObject = {}) {
|
||||||
const range = resolveFinanceRange(payload, nowIso())
|
const range = resolveFinanceRange(payload, nowIso())
|
||||||
|
const dateType = normalizeFinanceDateType(payload.dateType)
|
||||||
|
const eventDateExpression = resolveFinanceEventDateExpression(dateType)
|
||||||
const [dailyResult, snapshotResult] = await Promise.all([
|
const [dailyResult, snapshotResult] = await Promise.all([
|
||||||
query<FinanceDailyRow>(
|
query<FinanceDailyRow>(
|
||||||
`${FINANCE_EVENTS_CTE}
|
`${FINANCE_EVENTS_CTE}
|
||||||
@@ -126,10 +140,10 @@ export async function getAdminFinanceSummary(payload: JsonObject = {}) {
|
|||||||
COALESCE(SUM(amount) FILTER (WHERE category = 'worker_recovery'), 0)::bigint AS worker_recovery_amount
|
COALESCE(SUM(amount) FILTER (WHERE category = 'worker_recovery'), 0)::bigint AS worker_recovery_amount
|
||||||
FROM generate_series($3::timestamptz, $4::timestamptz - interval '1 day', interval '1 day') day
|
FROM generate_series($3::timestamptz, $4::timestamptz - interval '1 day', interval '1 day') day
|
||||||
LEFT JOIN finance_events event
|
LEFT JOIN finance_events event
|
||||||
ON event.occurred_at >= $1::timestamptz
|
ON ${eventDateExpression} >= $1::timestamptz
|
||||||
AND event.occurred_at < $2::timestamptz
|
AND ${eventDateExpression} < $2::timestamptz
|
||||||
AND event.occurred_at >= day
|
AND ${eventDateExpression} >= day
|
||||||
AND event.occurred_at < day + interval '1 day'
|
AND ${eventDateExpression} < day + interval '1 day'
|
||||||
GROUP BY day
|
GROUP BY day
|
||||||
ORDER BY day DESC`,
|
ORDER BY day DESC`,
|
||||||
[range.from, range.to, range.bucketFrom, range.bucketTo],
|
[range.from, range.to, range.bucketFrom, range.bucketTo],
|
||||||
@@ -151,14 +165,14 @@ export async function getAdminFinanceSummary(payload: JsonObject = {}) {
|
|||||||
const channelDailyResult = await query<FinanceChannelDailyRow>(
|
const channelDailyResult = await query<FinanceChannelDailyRow>(
|
||||||
`${FINANCE_EVENTS_CTE}
|
`${FINANCE_EVENTS_CTE}
|
||||||
SELECT
|
SELECT
|
||||||
to_char(event.occurred_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM-DD') AS day,
|
to_char(${eventDateExpression} AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM-DD') AS day,
|
||||||
event.channel,
|
event.channel,
|
||||||
event.category,
|
event.category,
|
||||||
COUNT(*)::int AS total,
|
COUNT(*)::int AS total,
|
||||||
COALESCE(SUM(event.amount), 0)::bigint AS amount
|
COALESCE(SUM(event.amount), 0)::bigint AS amount
|
||||||
FROM finance_events event
|
FROM finance_events event
|
||||||
WHERE event.occurred_at >= $1::timestamptz
|
WHERE ${eventDateExpression} >= $1::timestamptz
|
||||||
AND event.occurred_at < $2::timestamptz
|
AND ${eventDateExpression} < $2::timestamptz
|
||||||
GROUP BY 1, 2, 3
|
GROUP BY 1, 2, 3
|
||||||
ORDER BY day DESC, event.channel ASC, event.category ASC`,
|
ORDER BY day DESC, event.channel ASC, event.category ASC`,
|
||||||
[range.from, range.to],
|
[range.from, range.to],
|
||||||
@@ -203,6 +217,8 @@ export async function getAdminFinanceSummary(payload: JsonObject = {}) {
|
|||||||
|
|
||||||
export async function listAdminFinanceTransactions(payload: JsonObject = {}) {
|
export async function listAdminFinanceTransactions(payload: JsonObject = {}) {
|
||||||
const range = resolveFinanceRange(payload, nowIso())
|
const range = resolveFinanceRange(payload, nowIso())
|
||||||
|
const dateType = normalizeFinanceDateType(payload.dateType)
|
||||||
|
const eventDateExpression = resolveFinanceEventDateExpression(dateType)
|
||||||
const page = normalizePositiveInt(payload.page, 1)
|
const page = normalizePositiveInt(payload.page, 1)
|
||||||
const pageSize = Math.min(normalizePositiveInt(payload.pageSize, 20), 100)
|
const pageSize = Math.min(normalizePositiveInt(payload.pageSize, 20), 100)
|
||||||
const category = String(payload.category || '').trim()
|
const category = String(payload.category || '').trim()
|
||||||
@@ -226,8 +242,8 @@ export async function listAdminFinanceTransactions(payload: JsonObject = {}) {
|
|||||||
`${FINANCE_EVENTS_CTE}
|
`${FINANCE_EVENTS_CTE}
|
||||||
SELECT COUNT(*)::int AS total
|
SELECT COUNT(*)::int AS total
|
||||||
FROM finance_events event
|
FROM finance_events event
|
||||||
WHERE event.occurred_at >= $1
|
WHERE ${eventDateExpression} >= $1
|
||||||
AND event.occurred_at < $2${categoryClause}${channelClause}`,
|
AND ${eventDateExpression} < $2${categoryClause}${channelClause}`,
|
||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
const itemsParams = [...params, pageSize, offset]
|
const itemsParams = [...params, pageSize, offset]
|
||||||
@@ -235,9 +251,9 @@ export async function listAdminFinanceTransactions(payload: JsonObject = {}) {
|
|||||||
`${FINANCE_EVENTS_CTE}
|
`${FINANCE_EVENTS_CTE}
|
||||||
SELECT category, source_id, occurred_at, amount, label, reference_no, channel
|
SELECT category, source_id, occurred_at, amount, label, reference_no, channel
|
||||||
FROM finance_events event
|
FROM finance_events event
|
||||||
WHERE event.occurred_at >= $1
|
WHERE ${eventDateExpression} >= $1
|
||||||
AND event.occurred_at < $2${categoryClause}${channelClause}
|
AND ${eventDateExpression} < $2${categoryClause}${channelClause}
|
||||||
ORDER BY event.occurred_at DESC, event.source_id DESC
|
ORDER BY ${eventDateExpression} DESC, event.source_id DESC
|
||||||
LIMIT $${itemsParams.length - 1} OFFSET $${itemsParams.length}`,
|
LIMIT $${itemsParams.length - 1} OFFSET $${itemsParams.length}`,
|
||||||
itemsParams,
|
itemsParams,
|
||||||
)
|
)
|
||||||
@@ -385,6 +401,18 @@ function normalizePositiveInt(value: unknown, fallback: number) {
|
|||||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeFinanceDateType(value: unknown): 'occurred' | 'created' | 'reviewed' {
|
||||||
|
const normalized = String(value || '').trim()
|
||||||
|
if (normalized === 'created' || normalized === 'reviewed') return normalized
|
||||||
|
return 'occurred'
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveFinanceEventDateExpression(dateType: 'occurred' | 'created' | 'reviewed') {
|
||||||
|
if (dateType === 'created') return 'COALESCE(event.request_created_at, event.occurred_at)'
|
||||||
|
if (dateType === 'reviewed') return 'event.request_reviewed_at'
|
||||||
|
return 'event.occurred_at'
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeDateTimeDate(value: string) {
|
function normalizeDateTimeDate(value: string) {
|
||||||
const match = value.trim().match(/^(\d{4}-\d{2}-\d{2})/)
|
const match = value.trim().match(/^(\d{4}-\d{2}-\d{2})/)
|
||||||
if (!match || Number.isNaN(Date.parse(`${match[1]}T00:00:00Z`))) return ''
|
if (!match || Number.isNaN(Date.parse(`${match[1]}T00:00:00Z`))) return ''
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ export async function listAdminWorkerFinanceRequests(query: JsonObject = {}) {
|
|||||||
const keyword = String(query.keyword || '').trim()
|
const keyword = String(query.keyword || '').trim()
|
||||||
const createdFrom = normalizeDateTimeString(query.createdFrom)
|
const createdFrom = normalizeDateTimeString(query.createdFrom)
|
||||||
const createdTo = normalizeDateTimeString(query.createdTo)
|
const createdTo = normalizeDateTimeString(query.createdTo)
|
||||||
|
const reviewedFrom = normalizeDateTimeString(query.reviewedFrom)
|
||||||
|
const reviewedTo = normalizeDateTimeString(query.reviewedTo)
|
||||||
const accountChannel = ['alipay', 'wechat'].includes(String(query.accountChannel || ''))
|
const accountChannel = ['alipay', 'wechat'].includes(String(query.accountChannel || ''))
|
||||||
? String(query.accountChannel)
|
? String(query.accountChannel)
|
||||||
: ''
|
: ''
|
||||||
@@ -180,6 +182,8 @@ export async function listAdminWorkerFinanceRequests(query: JsonObject = {}) {
|
|||||||
keyword,
|
keyword,
|
||||||
...(createdFrom ? { createdFrom } : {}),
|
...(createdFrom ? { createdFrom } : {}),
|
||||||
...(createdTo ? { createdTo } : {}),
|
...(createdTo ? { createdTo } : {}),
|
||||||
|
...(reviewedFrom ? { reviewedFrom } : {}),
|
||||||
|
...(reviewedTo ? { reviewedTo } : {}),
|
||||||
...(accountChannel ? { accountChannel } : {}),
|
...(accountChannel ? { accountChannel } : {}),
|
||||||
}
|
}
|
||||||
const [listResult, channels] = await Promise.all([
|
const [listResult, channels] = await Promise.all([
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ import { useState } from 'react'
|
|||||||
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
|
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
|
||||||
import PageHeader from '@/components/admin/PageHeader'
|
import PageHeader from '@/components/admin/PageHeader'
|
||||||
import dayjs from '@/lib/dayjs'
|
import dayjs from '@/lib/dayjs'
|
||||||
import { fetchAdminFinanceSummary, fetchAdminFinanceTransactions } from '@/services/admin'
|
import {
|
||||||
|
fetchAdminFinanceSummary,
|
||||||
|
fetchAdminFinanceTransactions,
|
||||||
|
type AdminFinanceDateType,
|
||||||
|
} from '@/services/admin'
|
||||||
import type {
|
import type {
|
||||||
AdminFinanceChannelDailyItem,
|
AdminFinanceChannelDailyItem,
|
||||||
AdminFinanceDailyItem,
|
AdminFinanceDailyItem,
|
||||||
@@ -30,7 +34,7 @@ import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admi
|
|||||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||||
import { formatMoney } from './panels/shared'
|
import { formatMoney } from './panels/shared'
|
||||||
|
|
||||||
type FinanceFilterForm = { dateRange?: unknown }
|
type FinanceFilterForm = { dateRange?: unknown; dateType?: AdminFinanceDateType }
|
||||||
|
|
||||||
const CATEGORY_OPTIONS = [
|
const CATEGORY_OPTIONS = [
|
||||||
{ value: '', label: '全部账目' },
|
{ value: '', label: '全部账目' },
|
||||||
@@ -45,7 +49,11 @@ const CATEGORY_OPTIONS = [
|
|||||||
|
|
||||||
export default function AdminFinancePage() {
|
export default function AdminFinancePage() {
|
||||||
const [form] = Form.useForm<FinanceFilterForm>()
|
const [form] = Form.useForm<FinanceFilterForm>()
|
||||||
const [filters, setFilters] = useState({ dateFrom: '', dateTo: '' })
|
const [filters, setFilters] = useState<{
|
||||||
|
dateFrom: string
|
||||||
|
dateTo: string
|
||||||
|
dateType: AdminFinanceDateType
|
||||||
|
}>({ dateFrom: '', dateTo: '', dateType: 'occurred' })
|
||||||
const [category, setCategory] = useState('')
|
const [category, setCategory] = useState('')
|
||||||
const [channel, setChannel] = useState('')
|
const [channel, setChannel] = useState('')
|
||||||
const [channelSummaryFilter, setChannelSummaryFilter] = useState('')
|
const [channelSummaryFilter, setChannelSummaryFilter] = useState('')
|
||||||
@@ -83,7 +91,7 @@ export default function AdminFinancePage() {
|
|||||||
? dayjs(range[1]).second(59).format('YYYY-MM-DD HH:mm:ss')
|
? dayjs(range[1]).second(59).format('YYYY-MM-DD HH:mm:ss')
|
||||||
: ''
|
: ''
|
||||||
setPage(1)
|
setPage(1)
|
||||||
setFilters({ dateFrom, dateTo })
|
setFilters({ dateFrom, dateTo, dateType: values.dateType || 'occurred' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetFilters() {
|
function resetFilters() {
|
||||||
@@ -92,7 +100,7 @@ export default function AdminFinancePage() {
|
|||||||
setChannel('')
|
setChannel('')
|
||||||
setChannelSummaryFilter('')
|
setChannelSummaryFilter('')
|
||||||
setPage(1)
|
setPage(1)
|
||||||
setFilters({ dateFrom: '', dateTo: '' })
|
setFilters({ dateFrom: '', dateTo: '', dateType: 'occurred' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const dailyColumns: TableColumnsType<AdminFinanceDailyItem> = [
|
const dailyColumns: TableColumnsType<AdminFinanceDailyItem> = [
|
||||||
@@ -161,7 +169,17 @@ export default function AdminFinancePage() {
|
|||||||
className="filter-form"
|
className="filter-form"
|
||||||
onFinish={applyFilters}
|
onFinish={applyFilters}
|
||||||
>
|
>
|
||||||
<Form.Item name="dateRange" label="账务日期">
|
<Form.Item name="dateType" label="时间类型" initialValue="occurred">
|
||||||
|
<Select
|
||||||
|
style={{ width: 160 }}
|
||||||
|
options={[
|
||||||
|
{ value: 'occurred', label: '账务发生时间' },
|
||||||
|
{ value: 'created', label: '打手申请时间' },
|
||||||
|
{ value: 'reviewed', label: '审核通过时间' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="dateRange" label="时间范围">
|
||||||
<AdminRangePicker showTime={{ format: 'HH:mm' }} format="YYYY-MM-DD HH:mm" />
|
<AdminRangePicker showTime={{ format: 'HH:mm' }} format="YYYY-MM-DD HH:mm" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ type FinanceConfigFormValues = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FinanceReviewAction = 'approved' | 'rejected' | 'cancelled'
|
type FinanceReviewAction = 'approved' | 'rejected' | 'cancelled'
|
||||||
|
type FinanceRequestDateType = 'created' | 'reviewed'
|
||||||
|
|
||||||
export default function FinancePanel() {
|
export default function FinancePanel() {
|
||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
@@ -74,7 +75,8 @@ 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 [requestDateType, setRequestDateType] = useState<FinanceRequestDateType>('created')
|
||||||
|
const [requestDateRange, setRequestDateRange] = useState<[string, string] | null>(null)
|
||||||
const [accountChannel, setAccountChannel] = useState('')
|
const [accountChannel, setAccountChannel] = useState('')
|
||||||
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)
|
||||||
@@ -116,8 +118,9 @@ export default function FinancePanel() {
|
|||||||
keyword,
|
keyword,
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
createdRange ? createdRange[0] : '',
|
requestDateType,
|
||||||
createdRange ? createdRange[1] : '',
|
requestDateRange ? requestDateRange[0] : '',
|
||||||
|
requestDateRange ? requestDateRange[1] : '',
|
||||||
accountChannel,
|
accountChannel,
|
||||||
],
|
],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
@@ -129,7 +132,11 @@ export default function FinancePanel() {
|
|||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
accountChannel,
|
accountChannel,
|
||||||
...(createdRange ? { createdFrom: createdRange[0], createdTo: createdRange[1] } : {}),
|
...(requestDateRange
|
||||||
|
? requestDateType === 'created'
|
||||||
|
? { createdFrom: requestDateRange[0], createdTo: requestDateRange[1] }
|
||||||
|
: { reviewedFrom: requestDateRange[0], reviewedTo: requestDateRange[1] }
|
||||||
|
: {}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const financeRequestsPagination = financeRequestsQuery.data?.data.pagination
|
const financeRequestsPagination = financeRequestsQuery.data?.data.pagination
|
||||||
@@ -507,16 +514,31 @@ export default function FinancePanel() {
|
|||||||
setPage(1)
|
setPage(1)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
value={requestDateType}
|
||||||
|
style={{ width: 150 }}
|
||||||
|
options={[
|
||||||
|
{ value: 'created', label: '申请提现时间' },
|
||||||
|
{ value: 'reviewed', label: '审核通过时间' },
|
||||||
|
]}
|
||||||
|
onChange={(nextType: FinanceRequestDateType) => {
|
||||||
|
setRequestDateType(nextType)
|
||||||
|
setPage(1)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<AdminRangePicker
|
<AdminRangePicker
|
||||||
|
placeholder={['请选择时间范围', '请选择时间范围']}
|
||||||
format="YYYY-MM-DD HH:mm"
|
format="YYYY-MM-DD HH:mm"
|
||||||
showTime={{
|
showTime={{
|
||||||
format: 'HH:mm',
|
format: 'HH:mm',
|
||||||
defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('23:59:59', 'HH:mm:ss')],
|
defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('23:59:59', 'HH:mm:ss')],
|
||||||
}}
|
}}
|
||||||
value={createdRange ? [dayjs(createdRange[0]), dayjs(createdRange[1])] : null}
|
value={
|
||||||
|
requestDateRange ? [dayjs(requestDateRange[0]), dayjs(requestDateRange[1])] : null
|
||||||
|
}
|
||||||
onChange={(dates) => {
|
onChange={(dates) => {
|
||||||
const [start, end] = dates || []
|
const [start, end] = dates || []
|
||||||
setCreatedRange(
|
setRequestDateRange(
|
||||||
start && end
|
start && end
|
||||||
? [
|
? [
|
||||||
// 起止各占满整分钟:开始 xx:00、结束 xx:59
|
// 起止各占满整分钟:开始 xx:00、结束 xx:59
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { apiGet } from '@/lib/http'
|
import { apiGet } from '@/lib/http'
|
||||||
import type { AdminFinanceSummary, AdminFinanceTransactionResult } from '@/types/admin/finance'
|
import type { AdminFinanceSummary, AdminFinanceTransactionResult } from '@/types/admin/finance'
|
||||||
|
|
||||||
export function fetchAdminFinanceSummary(params: { dateFrom?: string; dateTo?: string } = {}) {
|
export type AdminFinanceDateType = 'occurred' | 'created' | 'reviewed'
|
||||||
|
|
||||||
|
export function fetchAdminFinanceSummary(
|
||||||
|
params: { dateFrom?: string; dateTo?: string; dateType?: AdminFinanceDateType } = {},
|
||||||
|
) {
|
||||||
return apiGet<AdminFinanceSummary>('/api/v1/admin/finance/summary', params)
|
return apiGet<AdminFinanceSummary>('/api/v1/admin/finance/summary', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,6 +13,7 @@ export function fetchAdminFinanceTransactions(
|
|||||||
params: {
|
params: {
|
||||||
dateFrom?: string
|
dateFrom?: string
|
||||||
dateTo?: string
|
dateTo?: string
|
||||||
|
dateType?: AdminFinanceDateType
|
||||||
category?: string
|
category?: string
|
||||||
channel?: string
|
channel?: string
|
||||||
page?: number
|
page?: number
|
||||||
|
|||||||
Reference in New Issue
Block a user