From b4918769f108e5b7151a8bd2056c237d270b612c Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 22 Aug 2026 20:11:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BE=85=E8=A7=A3=E5=86=BB,?= =?UTF-8?q?=20=E5=B9=B3=E5=8F=B0=E6=9D=82=E9=A1=B9=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../work-order-deposit-query-repo.ts | 15 +- .../worker-finance-request-service.ts | 4 + .../pages/admin/panels/AnnouncementPanel.tsx | 164 +++++++++++++++++- .../admin/platform/AdminPlatformShopsPage.tsx | 63 +------ 5 files changed, 183 insertions(+), 64 deletions(-) diff --git a/.gitignore b/.gitignore index d2c7987f..f5c1cfdf 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ apps/backend/data/logs send_code/ .reasonix/ *.tsbuildinfo +.zcode/ \ No newline at end of file diff --git a/apps/backend/src/repositories/worker-platform/work-order-deposit-query-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-deposit-query-repo.ts index ffa65da4..ff77182c 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-deposit-query-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-deposit-query-repo.ts @@ -27,18 +27,29 @@ export async function sumPendingUnfreezeByOrderIds( export async function listDueDepositUnfreezes({ limit = 100, workerId = 0, -}: { limit?: number; workerId?: number } = {}): Promise { + retroactiveDays = -1, +}: { + limit?: number + workerId?: number + /** >=0 时按当前配置回溯:created_at + retroactiveDays 天也视为到期,配置调小后存量提前释放。 */ + retroactiveDays?: number +}): Promise { const params: unknown[] = [] let workerClause = '' if (workerId > 0) { params.push(workerId) workerClause = `AND worker_id = $${params.length}` } + let dueClause = 'unfreeze_at <= NOW()' + if (Number.isFinite(retroactiveDays) && retroactiveDays >= 0) { + params.push(Math.floor(retroactiveDays)) + dueClause = `(unfreeze_at <= NOW() OR created_at + ($${params.length} * INTERVAL '1 day') <= NOW())` + } params.push(limit) const result = await query( ` SELECT * FROM worker_deposit_unfreezes - WHERE status = 'pending' AND unfreeze_at <= NOW() ${workerClause} + WHERE status = 'pending' AND ${dueClause} ${workerClause} ORDER BY unfreeze_at ASC LIMIT $${params.length} `, diff --git a/apps/backend/src/services/worker-platform/worker-finance-request-service.ts b/apps/backend/src/services/worker-platform/worker-finance-request-service.ts index a3a35d3a..962780d0 100644 --- a/apps/backend/src/services/worker-platform/worker-finance-request-service.ts +++ b/apps/backend/src/services/worker-platform/worker-finance-request-service.ts @@ -217,9 +217,13 @@ export async function createWorkerWithdrawRequest( export async function settleDueDepositUnfreezes( options: { limit?: number; workerId?: number } = {}, ) { + // 到期判定按当前解冻天数回溯:管理员调小财务配置后,存量待解冻按新天数提前视为到期, + // 否则入队时的旧天数会让"扫描 0 笔"一直持续到原到期时间。 + const retroactiveDays = getWorkerFinanceConfig().depositUnfreezeDays const due = await listDueDepositUnfreezes({ limit: Math.max(1, Number(options.limit || 100)), workerId: Number(options.workerId || 0), + retroactiveDays, }) let processedCount = 0 for (const unfreeze of due) { diff --git a/apps/frontend/src/pages/admin/panels/AnnouncementPanel.tsx b/apps/frontend/src/pages/admin/panels/AnnouncementPanel.tsx index d7c49e1d..c4258f0c 100644 --- a/apps/frontend/src/pages/admin/panels/AnnouncementPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/AnnouncementPanel.tsx @@ -1,29 +1,191 @@ import { useQuery, useQueryClient } from '@tanstack/react-query' -import { App, Button, Card, Form, Input, Popconfirm, Space, Switch, Typography } from 'antd' +import { ReloadOutlined, SaveOutlined } from '@ant-design/icons' +import { + Alert, + App, + Button, + Card, + Form, + Input, + InputNumber, + Popconfirm, + Space, + Switch, + Tag, + Typography, +} from 'antd' import { useEffect, useState } from 'react' import { addAdminMockCustomShop, deleteAdminMockCustomShop, fetchAdminMockCustomShops, + fetchAdminScheduledJobsConfig, fetchAdminWorkerAnnouncement, renameAdminMockCustomShop, + runAdminScheduledJob, saveAdminWorkerAnnouncement, + saveAdminScheduledJobsConfig, } from '@/services/admin' +import type { AdminScheduledJobItem, AdminScheduledJobRuntimeState } from '@/types/admin' import type { WorkerAnnouncementConfig } from '@/types/worker-platform' +import { formatAdminDateTime } from '@/utils/admin-time' import HallConfigPanel from './HallConfigPanel' +function isDepositUnfreezeJob(job: AdminScheduledJobItem) { + return job.type === 'deposit_unfreeze' || job.id === 'deposit-unfreeze' +} + export default function AnnouncementPanel() { return ( + ) } +function DepositUnfreezeCard() { + const { message } = App.useApp() + const queryClient = useQueryClient() + const [saving, setSaving] = useState(false) + const [running, setRunning] = useState(false) + const [draft, setDraft] = useState(null) + + const jobsQuery = useQuery({ + queryKey: ['admin-scheduled-jobs'], + queryFn: fetchAdminScheduledJobsConfig, + }) + const source = jobsQuery.data?.data + const currentJob = source?.source.jobs.find(isDepositUnfreezeJob) || null + const runtime: AdminScheduledJobRuntimeState | null = + source?.runtime.find((item) => item.id === (draft?.id || currentJob?.id)) || null + + useEffect(() => { + setDraft(currentJob ? { ...currentJob } : null) + }, [currentJob]) + + function patchJob(patch: Partial) { + setDraft((prev) => (prev ? { ...prev, ...patch } : prev)) + } + + async function saveJob() { + if (!draft || !source) return + setSaving(true) + try { + await saveAdminScheduledJobsConfig({ + ...source.source, + jobs: source.source.jobs.map((item) => (item.id === draft.id ? draft : item)), + }) + await queryClient.invalidateQueries({ queryKey: ['admin-scheduled-jobs'] }) + message.success('押金解冻配置已保存') + } catch (error) { + message.error(error instanceof Error ? error.message : '保存押金解冻配置失败') + } finally { + setSaving(false) + } + } + + async function runNow() { + const jobId = draft?.id || currentJob?.id || '' + if (!jobId) return + setRunning(true) + try { + await runAdminScheduledJob(jobId) + await queryClient.invalidateQueries({ queryKey: ['admin-scheduled-jobs'] }) + message.success('押金解冻任务已执行') + } catch (error) { + message.error(error instanceof Error ? error.message : '执行押金解冻失败') + } finally { + setRunning(false) + } + } + + return ( + + + + + } + > + + 验收后的报酬与押金进入待解冻队列,到期自动转入打手可用余额; + 解冻天数与报酬延迟在「财务」面板配置,调小后存量待解冻也会按新天数提前到期。 + + {draft ? ( + + + patchJob({ enabled })} + /> + + {draft.enabled !== false ? '已启用' : '已停用'} + + + + 执行间隔(秒) + patchJob({ intervalSeconds: Number(value || 60) })} + /> + 每次解冻数量 + + patchJob({ config: { ...draft.config, scanLimit: Number(value || 100) } }) + } + /> + + + ) : ( + 加载中… + )} + {runtime ? ( + + ) : null} + + ) +} + function AnnouncementCard() { const { message } = App.useApp() const queryClient = useQueryClient() diff --git a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx index 30c18eff..1e783021 100644 --- a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx +++ b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx @@ -727,64 +727,8 @@ function ScheduledJobCard({ } if (job.type === 'deposit_unfreeze' || job.id === 'deposit-unfreeze') { - return ( - - onChange({ ...job, enabled })} - /> - {formatScheduledJobTitle(job)} - {job.enabled ? '已启用' : '已停用'} - - } - extra={ - - } - > - - 将验收报酬与押金一起进入待解冻,在到期后自动转入可用余额(默认验收后 3 天到账)。 - -
- onChange({ ...job, intervalSeconds })} - /> - onChange({ ...job, config: { ...job.config, scanLimit } })} - /> -
- {runtime ? ( - - ) : null} -
- ) + // 押金到期解冻配置已迁至 接单平台 → 杂项配置,这里不再渲染。 + return null } const accounts = job.config?.accounts || [] const defaultThreshold = Number(job.config?.assetThreshold ?? 500) @@ -1021,9 +965,6 @@ function formatScheduledJobTitle(job: AdminScheduledJobItem) { if (job.type === 'work_order_timeout' || job.id === 'work-order-timeout') { return '接单工单超时扫描' } - if (job.type === 'deposit_unfreeze' || job.id === 'deposit-unfreeze') { - return '押金到期解冻' - } return job.id || job.type || '定时任务' }