feat(接单): 好友赠送冷却支持分钟单位
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
-- 064_work_order_gift_cooldown_minutes.sql —— 好友赠送冷却支持按分钟配置。
|
||||||
|
-- 小时与分钟两种单位互斥;保留原小时字段,历史模板与工单自动沿用小时配置。
|
||||||
|
|
||||||
|
ALTER TABLE work_product_rules
|
||||||
|
ADD COLUMN IF NOT EXISTS gift_cooldown_minutes INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE work_orders
|
||||||
|
ADD COLUMN IF NOT EXISTS gift_cooldown_minutes INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN work_product_rules.gift_cooldown_minutes
|
||||||
|
IS '好友赠送按分钟配置时的总分钟数(1-43200);非零时小时字段为 0,建单时拷贝到工单';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN work_orders.gift_cooldown_minutes
|
||||||
|
IS '好友赠送按分钟配置时的总分钟数(1-43200);非零时小时字段为 0,从模板拷贝';
|
||||||
@@ -4,6 +4,8 @@ import assert from 'node:assert/strict'
|
|||||||
import {
|
import {
|
||||||
isWorkOrderGiftDepositForfeitable,
|
isWorkOrderGiftDepositForfeitable,
|
||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
|
normalizeWorkOrderGiftCooldownHours,
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes,
|
||||||
resolveWorkOrderGiftPhase,
|
resolveWorkOrderGiftPhase,
|
||||||
WORK_ORDER_ACCEPTANCE_MODE,
|
WORK_ORDER_ACCEPTANCE_MODE,
|
||||||
WORK_ORDER_GIFT_PHASE,
|
WORK_ORDER_GIFT_PHASE,
|
||||||
@@ -58,3 +60,10 @@ test('赠送阶段按冷却到点实时推导 gift_ready', () => {
|
|||||||
assert.equal(normalizeWorkOrderAcceptanceMode('friend_gift'), 'friend_gift')
|
assert.equal(normalizeWorkOrderAcceptanceMode('friend_gift'), 'friend_gift')
|
||||||
assert.equal(normalizeWorkOrderAcceptanceMode(''), 'standard')
|
assert.equal(normalizeWorkOrderAcceptanceMode(''), 'standard')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('好友赠送按分钟冷却支持最长三十天并忽略负数', () => {
|
||||||
|
assert.equal(normalizeWorkOrderGiftCooldownHours(0), 0)
|
||||||
|
assert.equal(normalizeWorkOrderGiftCooldownMinutes(15), 15)
|
||||||
|
assert.equal(normalizeWorkOrderGiftCooldownMinutes(43_201), 43_200)
|
||||||
|
assert.equal(normalizeWorkOrderGiftCooldownMinutes(-1), 0)
|
||||||
|
})
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export type WorkOrderAcceptanceMode =
|
|||||||
|
|
||||||
export const DEFAULT_GIFT_COOLDOWN_HOURS = 72
|
export const DEFAULT_GIFT_COOLDOWN_HOURS = 72
|
||||||
export const MAX_GIFT_COOLDOWN_HOURS = 24 * 30
|
export const MAX_GIFT_COOLDOWN_HOURS = 24 * 30
|
||||||
|
export const MAX_GIFT_COOLDOWN_MINUTES = MAX_GIFT_COOLDOWN_HOURS * 60
|
||||||
|
|
||||||
/** 好友赠送模式的落库阶段;gift_ready 由 gift_available_at 到点后实时计算,不落库。 */
|
/** 好友赠送模式的落库阶段;gift_ready 由 gift_available_at 到点后实时计算,不落库。 */
|
||||||
export const WORK_ORDER_GIFT_PHASE = {
|
export const WORK_ORDER_GIFT_PHASE = {
|
||||||
@@ -33,11 +34,18 @@ export function isFriendGiftAcceptanceMode(value: unknown): boolean {
|
|||||||
return normalizeWorkOrderAcceptanceMode(value) === WORK_ORDER_ACCEPTANCE_MODE.FRIEND_GIFT
|
return normalizeWorkOrderAcceptanceMode(value) === WORK_ORDER_ACCEPTANCE_MODE.FRIEND_GIFT
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 冷却小时数收敛到 [0, 30 天],默认 3 天。 */
|
/** 冷却小时数收敛到 [0, 30 天],默认 3 天。0 用于选择按分钟配置。 */
|
||||||
export function normalizeWorkOrderGiftCooldownHours(value: unknown): number {
|
export function normalizeWorkOrderGiftCooldownHours(value: unknown): number {
|
||||||
const hours = Number(value)
|
const hours = Number(value)
|
||||||
if (!Number.isFinite(hours) || hours <= 0) return DEFAULT_GIFT_COOLDOWN_HOURS
|
if (!Number.isFinite(hours) || hours < 0) return DEFAULT_GIFT_COOLDOWN_HOURS
|
||||||
return Math.min(MAX_GIFT_COOLDOWN_HOURS, Math.max(1, Math.round(hours)))
|
return Math.min(MAX_GIFT_COOLDOWN_HOURS, Math.round(hours))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按分钟配置时的总分钟数,最大 30 天。 */
|
||||||
|
export function normalizeWorkOrderGiftCooldownMinutes(value: unknown): number {
|
||||||
|
const minutes = Number(value)
|
||||||
|
if (!Number.isFinite(minutes)) return 0
|
||||||
|
return Math.min(MAX_GIFT_COOLDOWN_MINUTES, Math.max(0, Math.round(minutes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkOrderGiftSource = {
|
export type WorkOrderGiftSource = {
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ export type WorkProductRuleRow = {
|
|||||||
timeout_policy: string
|
timeout_policy: string
|
||||||
acceptance_mode: string
|
acceptance_mode: string
|
||||||
gift_cooldown_hours: number
|
gift_cooldown_hours: number
|
||||||
|
gift_cooldown_minutes: number
|
||||||
requirement_json: string | Record<string, unknown>
|
requirement_json: string | Record<string, unknown>
|
||||||
match_json: string | Record<string, unknown>
|
match_json: string | Record<string, unknown>
|
||||||
sort_order: number
|
sort_order: number
|
||||||
@@ -378,6 +379,7 @@ export type WorkOrderRow = {
|
|||||||
friend_added_at: string | null
|
friend_added_at: string | null
|
||||||
gift_available_at: string | null
|
gift_available_at: string | null
|
||||||
gift_cooldown_hours: number
|
gift_cooldown_hours: number
|
||||||
|
gift_cooldown_minutes: number
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
/** 拼单聚合字段,由工单查询统一计算。 */
|
/** 拼单聚合字段,由工单查询统一计算。 */
|
||||||
@@ -546,6 +548,7 @@ export type CreateWorkOrderInput = {
|
|||||||
timeoutPolicy?: string
|
timeoutPolicy?: string
|
||||||
acceptanceMode?: string
|
acceptanceMode?: string
|
||||||
giftCooldownHours?: number
|
giftCooldownHours?: number
|
||||||
|
giftCooldownMinutes?: number
|
||||||
materialJson: string
|
materialJson: string
|
||||||
requirementJson: string
|
requirementJson: string
|
||||||
now: string
|
now: string
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { WorkOrderRow } from './types.js'
|
|||||||
import {
|
import {
|
||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
normalizeWorkOrderGiftCooldownHours,
|
normalizeWorkOrderGiftCooldownHours,
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes,
|
||||||
WORK_ORDER_ACCEPTANCE_MODE,
|
WORK_ORDER_ACCEPTANCE_MODE,
|
||||||
WORK_ORDER_GIFT_PHASE,
|
WORK_ORDER_GIFT_PHASE,
|
||||||
} from '../../domain/work-order-acceptance-mode.js'
|
} from '../../domain/work-order-acceptance-mode.js'
|
||||||
@@ -121,6 +122,7 @@ export async function confirmWorkOrderFriendAdded(input: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cooldownHours = normalizeWorkOrderGiftCooldownHours(workOrder.gift_cooldown_hours)
|
const cooldownHours = normalizeWorkOrderGiftCooldownHours(workOrder.gift_cooldown_hours)
|
||||||
|
const cooldownMinutes = normalizeWorkOrderGiftCooldownMinutes(workOrder.gift_cooldown_minutes)
|
||||||
// 参数必须显式转型:未知类型参数与 interval 做运算时 PostgreSQL 无法唯一解析操作符。
|
// 参数必须显式转型:未知类型参数与 interval 做运算时 PostgreSQL 无法唯一解析操作符。
|
||||||
await client.query(
|
await client.query(
|
||||||
`
|
`
|
||||||
@@ -128,16 +130,16 @@ export async function confirmWorkOrderFriendAdded(input: {
|
|||||||
SET
|
SET
|
||||||
gift_phase = 'friend_countdown',
|
gift_phase = 'friend_countdown',
|
||||||
friend_added_at = $1::timestamptz,
|
friend_added_at = $1::timestamptz,
|
||||||
gift_available_at = $1::timestamptz + ($2::int * INTERVAL '1 hour'),
|
gift_available_at = $1::timestamptz + ($2::int * INTERVAL '1 hour') + ($3::int * INTERVAL '1 minute'),
|
||||||
deadline_at = CASE
|
deadline_at = CASE
|
||||||
WHEN timeout_minutes > 0
|
WHEN timeout_minutes > 0
|
||||||
THEN $1::timestamptz + ($2::int * INTERVAL '1 hour') + (timeout_minutes * INTERVAL '1 minute')
|
THEN $1::timestamptz + ($2::int * INTERVAL '1 hour') + ($3::int * INTERVAL '1 minute') + (timeout_minutes * INTERVAL '1 minute')
|
||||||
ELSE NULL
|
ELSE NULL
|
||||||
END,
|
END,
|
||||||
updated_at = $1::timestamptz
|
updated_at = $1::timestamptz
|
||||||
WHERE id = $3
|
WHERE id = $4
|
||||||
`,
|
`,
|
||||||
[input.now, cooldownHours, input.workOrderId],
|
[input.now, cooldownHours, cooldownMinutes, input.workOrderId],
|
||||||
)
|
)
|
||||||
await createWorkOrderEventWithClient(client, {
|
await createWorkOrderEventWithClient(client, {
|
||||||
workOrderId: input.workOrderId,
|
workOrderId: input.workOrderId,
|
||||||
@@ -148,6 +150,7 @@ export async function confirmWorkOrderFriendAdded(input: {
|
|||||||
toStatus: workOrder.status,
|
toStatus: workOrder.status,
|
||||||
payloadJson: JSON.stringify({
|
payloadJson: JSON.stringify({
|
||||||
cooldownHours,
|
cooldownHours,
|
||||||
|
cooldownMinutes,
|
||||||
friendAddedAt: input.now,
|
friendAddedAt: input.now,
|
||||||
}),
|
}),
|
||||||
now: input.now,
|
now: input.now,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { getWorkOrderById } from './work-order-query-repo.js'
|
|||||||
import {
|
import {
|
||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
normalizeWorkOrderGiftCooldownHours,
|
normalizeWorkOrderGiftCooldownHours,
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes,
|
||||||
WORK_ORDER_ACCEPTANCE_MODE,
|
WORK_ORDER_ACCEPTANCE_MODE,
|
||||||
WORK_ORDER_GIFT_PHASE,
|
WORK_ORDER_GIFT_PHASE,
|
||||||
} from '../../domain/work-order-acceptance-mode.js'
|
} from '../../domain/work-order-acceptance-mode.js'
|
||||||
@@ -19,13 +20,13 @@ export async function createWorkOrder(input: CreateWorkOrderInput): Promise<Work
|
|||||||
product_name, category_id, status, reward_amount, required_deposit_amount,
|
product_name, category_id, status, reward_amount, required_deposit_amount,
|
||||||
deposit_threshold_amount, sharing_enabled, sharing_total_quantity,
|
deposit_threshold_amount, sharing_enabled, sharing_total_quantity,
|
||||||
sharing_unit_reward, timeout_minutes, timeout_policy,
|
sharing_unit_reward, timeout_minutes, timeout_policy,
|
||||||
acceptance_mode, gift_phase, gift_cooldown_hours,
|
acceptance_mode, gift_phase, gift_cooldown_hours, gift_cooldown_minutes,
|
||||||
material_json, requirement_json, created_at, updated_at
|
material_json, requirement_json, created_at, updated_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11,
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11,
|
||||||
$12, $13, $14, $15, $16, $17,
|
$12, $13, $14, $15, $16, $17,
|
||||||
$18, $19, $20,
|
$18, $19, $20, $21,
|
||||||
$21::jsonb, $22::jsonb, $23, $24
|
$22::jsonb, $23::jsonb, $24, $25
|
||||||
)
|
)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`,
|
`,
|
||||||
@@ -50,6 +51,7 @@ export async function createWorkOrder(input: CreateWorkOrderInput): Promise<Work
|
|||||||
acceptanceMode,
|
acceptanceMode,
|
||||||
isFriendGift ? WORK_ORDER_GIFT_PHASE.MATERIAL_REQUIRED : '',
|
isFriendGift ? WORK_ORDER_GIFT_PHASE.MATERIAL_REQUIRED : '',
|
||||||
normalizeWorkOrderGiftCooldownHours(input.giftCooldownHours),
|
normalizeWorkOrderGiftCooldownHours(input.giftCooldownHours),
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes(input.giftCooldownMinutes),
|
||||||
input.materialJson,
|
input.materialJson,
|
||||||
input.requirementJson,
|
input.requirementJson,
|
||||||
input.now,
|
input.now,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { toPositiveInteger } from './shared.js'
|
|||||||
import {
|
import {
|
||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
normalizeWorkOrderGiftCooldownHours,
|
normalizeWorkOrderGiftCooldownHours,
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes,
|
||||||
} from '../../domain/work-order-acceptance-mode.js'
|
} from '../../domain/work-order-acceptance-mode.js'
|
||||||
import type {
|
import type {
|
||||||
ProductRuleListInput,
|
ProductRuleListInput,
|
||||||
@@ -342,6 +343,7 @@ export async function upsertWorkProductRule(input: {
|
|||||||
timeoutPolicy?: string
|
timeoutPolicy?: string
|
||||||
acceptanceMode?: string
|
acceptanceMode?: string
|
||||||
giftCooldownHours?: number
|
giftCooldownHours?: number
|
||||||
|
giftCooldownMinutes?: number
|
||||||
requirementJson: string
|
requirementJson: string
|
||||||
matchJson?: string
|
matchJson?: string
|
||||||
sortOrder: number
|
sortOrder: number
|
||||||
@@ -356,7 +358,7 @@ export async function upsertWorkProductRule(input: {
|
|||||||
required_deposit_amount, deposit_threshold_amount,
|
required_deposit_amount, deposit_threshold_amount,
|
||||||
sharing_enabled, sharing_total_quantity, sharing_unit_reward, sharing_auto_from_order,
|
sharing_enabled, sharing_total_quantity, sharing_unit_reward, sharing_auto_from_order,
|
||||||
timeout_minutes, timeout_policy,
|
timeout_minutes, timeout_policy,
|
||||||
acceptance_mode, gift_cooldown_hours,
|
acceptance_mode, gift_cooldown_hours, gift_cooldown_minutes,
|
||||||
requirement_json, match_json,
|
requirement_json, match_json,
|
||||||
sort_order, created_at, updated_at
|
sort_order, created_at, updated_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
@@ -366,9 +368,9 @@ export async function upsertWorkProductRule(input: {
|
|||||||
$13, $14,
|
$13, $14,
|
||||||
$15, $16, $17, $18,
|
$15, $16, $17, $18,
|
||||||
$19, $20,
|
$19, $20,
|
||||||
$21, $22,
|
$21, $22, $23,
|
||||||
$23::jsonb, $24::jsonb,
|
$24::jsonb, $25::jsonb,
|
||||||
$25, $26, $27
|
$26, $27, $28
|
||||||
)
|
)
|
||||||
ON CONFLICT (rule_key) DO UPDATE
|
ON CONFLICT (rule_key) DO UPDATE
|
||||||
SET
|
SET
|
||||||
@@ -393,6 +395,7 @@ export async function upsertWorkProductRule(input: {
|
|||||||
timeout_policy = EXCLUDED.timeout_policy,
|
timeout_policy = EXCLUDED.timeout_policy,
|
||||||
acceptance_mode = EXCLUDED.acceptance_mode,
|
acceptance_mode = EXCLUDED.acceptance_mode,
|
||||||
gift_cooldown_hours = EXCLUDED.gift_cooldown_hours,
|
gift_cooldown_hours = EXCLUDED.gift_cooldown_hours,
|
||||||
|
gift_cooldown_minutes = EXCLUDED.gift_cooldown_minutes,
|
||||||
requirement_json = EXCLUDED.requirement_json,
|
requirement_json = EXCLUDED.requirement_json,
|
||||||
match_json = EXCLUDED.match_json,
|
match_json = EXCLUDED.match_json,
|
||||||
sort_order = EXCLUDED.sort_order,
|
sort_order = EXCLUDED.sort_order,
|
||||||
@@ -422,6 +425,7 @@ export async function upsertWorkProductRule(input: {
|
|||||||
String(input.timeoutPolicy || 'reopen').trim(),
|
String(input.timeoutPolicy || 'reopen').trim(),
|
||||||
normalizeWorkOrderAcceptanceMode(input.acceptanceMode),
|
normalizeWorkOrderAcceptanceMode(input.acceptanceMode),
|
||||||
normalizeWorkOrderGiftCooldownHours(input.giftCooldownHours),
|
normalizeWorkOrderGiftCooldownHours(input.giftCooldownHours),
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes(input.giftCooldownMinutes),
|
||||||
input.requirementJson,
|
input.requirementJson,
|
||||||
input.matchJson || '{}',
|
input.matchJson || '{}',
|
||||||
input.sortOrder,
|
input.sortOrder,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
|
|||||||
import {
|
import {
|
||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
normalizeWorkOrderGiftCooldownHours,
|
normalizeWorkOrderGiftCooldownHours,
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes,
|
||||||
} from '../../domain/work-order-acceptance-mode.js'
|
} from '../../domain/work-order-acceptance-mode.js'
|
||||||
import {
|
import {
|
||||||
createWorkOrder,
|
createWorkOrder,
|
||||||
@@ -114,6 +115,7 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
|
|||||||
timeoutPolicy: normalizeWorkOrderTimeoutPolicy(payload.timeoutPolicy),
|
timeoutPolicy: normalizeWorkOrderTimeoutPolicy(payload.timeoutPolicy),
|
||||||
acceptanceMode: normalizeWorkOrderAcceptanceMode(payload.acceptanceMode),
|
acceptanceMode: normalizeWorkOrderAcceptanceMode(payload.acceptanceMode),
|
||||||
giftCooldownHours: normalizeWorkOrderGiftCooldownHours(payload.giftCooldownHours),
|
giftCooldownHours: normalizeWorkOrderGiftCooldownHours(payload.giftCooldownHours),
|
||||||
|
giftCooldownMinutes: normalizeWorkOrderGiftCooldownMinutes(payload.giftCooldownMinutes),
|
||||||
materialJson: JSON.stringify(material),
|
materialJson: JSON.stringify(material),
|
||||||
requirementJson: JSON.stringify({ fields }),
|
requirementJson: JSON.stringify({ fields }),
|
||||||
now,
|
now,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import type { WorkOrderRow, WorkProductRuleRow } from '../../repositories/worker
|
|||||||
import {
|
import {
|
||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
normalizeWorkOrderGiftCooldownHours,
|
normalizeWorkOrderGiftCooldownHours,
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes,
|
||||||
WORK_ORDER_ACCEPTANCE_MODE,
|
WORK_ORDER_ACCEPTANCE_MODE,
|
||||||
} from '../../domain/work-order-acceptance-mode.js'
|
} from '../../domain/work-order-acceptance-mode.js'
|
||||||
import type { JsonObject } from '../../types/json.js'
|
import type { JsonObject } from '../../types/json.js'
|
||||||
@@ -374,6 +375,13 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
|
|||||||
payload.giftCooldownHours ?? payload.gift_cooldown_hours,
|
payload.giftCooldownHours ?? payload.gift_cooldown_hours,
|
||||||
)
|
)
|
||||||
: normalizeWorkOrderGiftCooldownHours(existingRule?.gift_cooldown_hours),
|
: normalizeWorkOrderGiftCooldownHours(existingRule?.gift_cooldown_hours),
|
||||||
|
giftCooldownMinutes:
|
||||||
|
hasOwnPayload(payload, 'giftCooldownMinutes') ||
|
||||||
|
hasOwnPayload(payload, 'gift_cooldown_minutes')
|
||||||
|
? normalizeWorkOrderGiftCooldownMinutes(
|
||||||
|
payload.giftCooldownMinutes ?? payload.gift_cooldown_minutes,
|
||||||
|
)
|
||||||
|
: normalizeWorkOrderGiftCooldownMinutes(existingRule?.gift_cooldown_minutes),
|
||||||
requirementJson: JSON.stringify({ fields }),
|
requirementJson: JSON.stringify({ fields }),
|
||||||
matchJson: JSON.stringify(match),
|
matchJson: JSON.stringify(match),
|
||||||
sortOrder: normalizeInteger(payload.sortOrder, 100),
|
sortOrder: normalizeInteger(payload.sortOrder, 100),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
isFriendGiftAcceptanceMode,
|
isFriendGiftAcceptanceMode,
|
||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
normalizeWorkOrderGiftCooldownHours,
|
normalizeWorkOrderGiftCooldownHours,
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes,
|
||||||
} from '../../domain/work-order-acceptance-mode.js'
|
} from '../../domain/work-order-acceptance-mode.js'
|
||||||
import {
|
import {
|
||||||
createWorkProductMatchLog,
|
createWorkProductMatchLog,
|
||||||
@@ -138,6 +139,7 @@ export async function syncWorkerOrdersForSourceOrder(
|
|||||||
timeoutPolicy: String(rule.timeout_policy || 'reopen').trim(),
|
timeoutPolicy: String(rule.timeout_policy || 'reopen').trim(),
|
||||||
acceptanceMode: normalizeWorkOrderAcceptanceMode(rule.acceptance_mode),
|
acceptanceMode: normalizeWorkOrderAcceptanceMode(rule.acceptance_mode),
|
||||||
giftCooldownHours: normalizeWorkOrderGiftCooldownHours(rule.gift_cooldown_hours),
|
giftCooldownHours: normalizeWorkOrderGiftCooldownHours(rule.gift_cooldown_hours),
|
||||||
|
giftCooldownMinutes: normalizeWorkOrderGiftCooldownMinutes(rule.gift_cooldown_minutes),
|
||||||
materialJson: JSON.stringify({
|
materialJson: JSON.stringify({
|
||||||
source: {
|
source: {
|
||||||
orderId: Number(order.id),
|
orderId: Number(order.id),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
isFriendGiftAcceptanceMode,
|
isFriendGiftAcceptanceMode,
|
||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
normalizeWorkOrderGiftCooldownHours,
|
normalizeWorkOrderGiftCooldownHours,
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes,
|
||||||
resolveWorkOrderGiftPhase,
|
resolveWorkOrderGiftPhase,
|
||||||
WORK_ORDER_ACCEPTANCE_MODE,
|
WORK_ORDER_ACCEPTANCE_MODE,
|
||||||
WORK_ORDER_GIFT_PHASE,
|
WORK_ORDER_GIFT_PHASE,
|
||||||
@@ -212,6 +213,7 @@ export function mapWorkOrderAdmin(workOrder: WorkOrderRow, shares?: WorkOrderSha
|
|||||||
phase: resolveWorkOrderGiftPhase(workOrder),
|
phase: resolveWorkOrderGiftPhase(workOrder),
|
||||||
rawPhase: giftPhase,
|
rawPhase: giftPhase,
|
||||||
cooldownHours: normalizeWorkOrderGiftCooldownHours(workOrder.gift_cooldown_hours),
|
cooldownHours: normalizeWorkOrderGiftCooldownHours(workOrder.gift_cooldown_hours),
|
||||||
|
cooldownMinutes: normalizeWorkOrderGiftCooldownMinutes(workOrder.gift_cooldown_minutes),
|
||||||
friendAddedAt: workOrder.friend_added_at,
|
friendAddedAt: workOrder.friend_added_at,
|
||||||
giftAvailableAt: workOrder.gift_available_at,
|
giftAvailableAt: workOrder.gift_available_at,
|
||||||
boosterMaterial: mapWorkOrderBoosterMaterial(workOrder.booster_material_json),
|
boosterMaterial: mapWorkOrderBoosterMaterial(workOrder.booster_material_json),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
normalizeWorkOrderGiftCooldownHours,
|
normalizeWorkOrderGiftCooldownHours,
|
||||||
|
normalizeWorkOrderGiftCooldownMinutes,
|
||||||
} from '../../domain/work-order-acceptance-mode.js'
|
} from '../../domain/work-order-acceptance-mode.js'
|
||||||
import type { JsonObject } from '../../types/json.js'
|
import type { JsonObject } from '../../types/json.js'
|
||||||
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
|
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
|
||||||
@@ -51,6 +52,7 @@ export function mapWorkProductRule(rule: WorkProductRuleRow | null | undefined)
|
|||||||
timeoutPolicy: String(rule.timeout_policy || 'reopen').trim(),
|
timeoutPolicy: String(rule.timeout_policy || 'reopen').trim(),
|
||||||
acceptanceMode: normalizeWorkOrderAcceptanceMode(rule.acceptance_mode),
|
acceptanceMode: normalizeWorkOrderAcceptanceMode(rule.acceptance_mode),
|
||||||
giftCooldownHours: normalizeWorkOrderGiftCooldownHours(rule.gift_cooldown_hours),
|
giftCooldownHours: normalizeWorkOrderGiftCooldownHours(rule.gift_cooldown_hours),
|
||||||
|
giftCooldownMinutes: normalizeWorkOrderGiftCooldownMinutes(rule.gift_cooldown_minutes),
|
||||||
requirement: {
|
requirement: {
|
||||||
fields: normalizeRequirementFields(requirement.fields),
|
fields: normalizeRequirementFields(requirement.fields),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -209,7 +209,11 @@ export default function ProductRulesPanel() {
|
|||||||
timeoutMinutes: rule.timeoutMinutes || 0,
|
timeoutMinutes: rule.timeoutMinutes || 0,
|
||||||
timeoutPolicy: rule.timeoutPolicy || 'reopen',
|
timeoutPolicy: rule.timeoutPolicy || 'reopen',
|
||||||
acceptanceMode: rule.acceptanceMode || 'standard',
|
acceptanceMode: rule.acceptanceMode || 'standard',
|
||||||
giftCooldownHours: rule.giftCooldownHours || 72,
|
giftCooldownUnit: Number(rule.giftCooldownMinutes || 0) > 0 ? 'minutes' : 'hours',
|
||||||
|
giftCooldownValue:
|
||||||
|
Number(rule.giftCooldownMinutes || 0) > 0
|
||||||
|
? Number(rule.giftCooldownMinutes || 0)
|
||||||
|
: Number(rule.giftCooldownHours ?? 72),
|
||||||
fieldsText: formatRuleFields(rule.requirement?.fields || []),
|
fieldsText: formatRuleFields(rule.requirement?.fields || []),
|
||||||
sortOrder: rule.sortOrder,
|
sortOrder: rule.sortOrder,
|
||||||
enabled: rule.enabled,
|
enabled: rule.enabled,
|
||||||
@@ -247,7 +251,8 @@ export default function ProductRulesPanel() {
|
|||||||
timeoutMinutes?: number
|
timeoutMinutes?: number
|
||||||
timeoutPolicy?: string
|
timeoutPolicy?: string
|
||||||
acceptanceMode?: string
|
acceptanceMode?: string
|
||||||
giftCooldownHours?: number
|
giftCooldownUnit?: 'hours' | 'minutes'
|
||||||
|
giftCooldownValue?: number
|
||||||
fieldsText?: string
|
fieldsText?: string
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
autoCreate?: boolean
|
autoCreate?: boolean
|
||||||
@@ -265,13 +270,17 @@ export default function ProductRulesPanel() {
|
|||||||
? Number(values.sharingTotalQuantity || 0) > 0 ||
|
? Number(values.sharingTotalQuantity || 0) > 0 ||
|
||||||
Number(values.sharingUnitReward || 0) > 0
|
Number(values.sharingUnitReward || 0) > 0
|
||||||
: hasManualSharingInput(values.sharingTotalQuantity, values.sharingUnitReward)
|
: hasManualSharingInput(values.sharingTotalQuantity, values.sharingUnitReward)
|
||||||
const { pricingMode: _pricingMode, ...payload } = values
|
const { pricingMode: _pricingMode, giftCooldownUnit, giftCooldownValue, ...payload } = values
|
||||||
const res = await saveAdminWorkProductRule({
|
const res = await saveAdminWorkProductRule({
|
||||||
...payload,
|
...payload,
|
||||||
ruleId: editingRule?.ruleId,
|
ruleId: editingRule?.ruleId,
|
||||||
ruleKey: editingRule?.ruleKey || '',
|
ruleKey: editingRule?.ruleKey || '',
|
||||||
unitPrice: unitMode ? Number(values.unitPrice || 0) : 0,
|
unitPrice: unitMode ? Number(values.unitPrice || 0) : 0,
|
||||||
acceptanceMode: isFriendGift ? 'friend_gift' : 'standard',
|
acceptanceMode: isFriendGift ? 'friend_gift' : 'standard',
|
||||||
|
giftCooldownHours:
|
||||||
|
isFriendGift && giftCooldownUnit !== 'minutes' ? Number(giftCooldownValue || 0) : 0,
|
||||||
|
giftCooldownMinutes:
|
||||||
|
isFriendGift && giftCooldownUnit === 'minutes' ? Number(giftCooldownValue || 0) : 0,
|
||||||
sharingEnabled: isFriendGift ? false : values.sharingEnabled === true,
|
sharingEnabled: isFriendGift ? false : values.sharingEnabled === true,
|
||||||
sharingAutoFromOrder:
|
sharingAutoFromOrder:
|
||||||
unitMode && values.sharingEnabled === true && !hasManualSharingOverride,
|
unitMode && values.sharingEnabled === true && !hasManualSharingOverride,
|
||||||
@@ -745,7 +754,8 @@ export default function ProductRulesPanel() {
|
|||||||
autoCreate: true,
|
autoCreate: true,
|
||||||
sharingEnabled: false,
|
sharingEnabled: false,
|
||||||
acceptanceMode: 'standard',
|
acceptanceMode: 'standard',
|
||||||
giftCooldownHours: 72,
|
giftCooldownUnit: 'hours',
|
||||||
|
giftCooldownValue: 72,
|
||||||
fieldsText:
|
fieldsText:
|
||||||
'gameId:游戏编号\ngameNickname:游戏昵称\nsystem:系统#安卓,苹果\nserverZone:区服#QQ区,微信区',
|
'gameId:游戏编号\ngameNickname:游戏昵称\nsystem:系统#安卓,苹果\nserverZone:区服#QQ区,微信区',
|
||||||
}}
|
}}
|
||||||
@@ -838,19 +848,47 @@ export default function ProductRulesPanel() {
|
|||||||
<Form.Item noStyle shouldUpdate>
|
<Form.Item noStyle shouldUpdate>
|
||||||
{({ getFieldValue }) =>
|
{({ getFieldValue }) =>
|
||||||
getFieldValue('acceptanceMode') === 'friend_gift' ? (
|
getFieldValue('acceptanceMode') === 'friend_gift' ? (
|
||||||
<Form.Item
|
<Form.Item label="赠送冷却" required>
|
||||||
label="赠送冷却"
|
<Space.Compact className="full-width">
|
||||||
name="giftCooldownHours"
|
<Form.Item name="giftCooldownUnit" noStyle>
|
||||||
rules={[{ required: true, message: '请填写冷却小时数' }]}
|
<Select
|
||||||
extra="确认加好友后需冷却该时长才能赠送(默认 72 小时 = 3 天)"
|
style={{ width: 100 }}
|
||||||
>
|
options={[
|
||||||
<InputNumber
|
{ value: 'hours', label: '按小时' },
|
||||||
min={1}
|
{ value: 'minutes', label: '按分钟' },
|
||||||
max={720}
|
]}
|
||||||
step={1}
|
/>
|
||||||
addonAfter="小时"
|
</Form.Item>
|
||||||
className="full-width"
|
<Form.Item
|
||||||
/>
|
noStyle
|
||||||
|
shouldUpdate={(previous, current) =>
|
||||||
|
previous.giftCooldownUnit !== current.giftCooldownUnit
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{({ getFieldValue }) => {
|
||||||
|
const unit = getFieldValue('giftCooldownUnit')
|
||||||
|
const isMinutes = unit === 'minutes'
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
name="giftCooldownValue"
|
||||||
|
noStyle
|
||||||
|
rules={[{ required: true, message: '请填写冷却时长' }]}
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
max={isMinutes ? 43200 : 720}
|
||||||
|
step={1}
|
||||||
|
addonAfter={isMinutes ? '分钟' : '小时'}
|
||||||
|
className="full-width"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</Space.Compact>
|
||||||
|
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
选择小时或分钟其中一种单位;最长 30 天(720 小时 / 43200 分钟)。
|
||||||
|
</Typography.Text>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
) : (
|
) : (
|
||||||
<Form.Item label={<span />} />
|
<Form.Item label={<span />} />
|
||||||
|
|||||||
@@ -861,6 +861,8 @@ export default function WorkOrdersPanel() {
|
|||||||
|
|
||||||
function confirmFriendAdded(row: WorkOrder) {
|
function confirmFriendAdded(row: WorkOrder) {
|
||||||
const cooldownHours = Number(row.gift?.cooldownHours || 72)
|
const cooldownHours = Number(row.gift?.cooldownHours || 72)
|
||||||
|
const cooldownMinutes = Number(row.gift?.cooldownMinutes || 0)
|
||||||
|
const cooldownLabel = cooldownMinutes > 0 ? `${cooldownMinutes} 分钟` : `${cooldownHours} 小时`
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: '确认已添加打手为游戏好友?',
|
title: '确认已添加打手为游戏好友?',
|
||||||
content: (
|
content: (
|
||||||
@@ -869,8 +871,8 @@ export default function WorkOrdersPanel() {
|
|||||||
请先核对打手提交的昵称 / ID / 区服与主页截图,确认已在游戏内互为好友后再点击确认。
|
请先核对打手提交的昵称 / ID / 区服与主页截图,确认已在游戏内互为好友后再点击确认。
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
<Typography.Paragraph>
|
<Typography.Paragraph>
|
||||||
确认后开始 {cooldownHours}{' '}
|
确认后开始 {cooldownLabel}
|
||||||
小时赠送冷却,冷却结束前打手不能提交赠送凭证;任务时限将顺延到冷却结束后重新起算。
|
赠送冷却,冷却结束前打手不能提交赠送凭证;任务时限将顺延到冷却结束后重新起算。
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -879,7 +881,7 @@ export default function WorkOrdersPanel() {
|
|||||||
onOk: () =>
|
onOk: () =>
|
||||||
runAction(
|
runAction(
|
||||||
() => confirmAdminWorkOrderFriend(row.workOrderId),
|
() => confirmAdminWorkOrderFriend(row.workOrderId),
|
||||||
`已确认加好友,${cooldownHours} 小时冷却开始`,
|
`已确认加好友,${cooldownLabel} 冷却开始`,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1652,7 +1654,9 @@ export default function WorkOrdersPanel() {
|
|||||||
<div className="worker-order-detail-stack">
|
<div className="worker-order-detail-stack">
|
||||||
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
|
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
|
||||||
<Descriptions.Item label="冷却时长">
|
<Descriptions.Item label="冷却时长">
|
||||||
{detailOrder.gift.cooldownHours} 小时
|
{detailOrder.gift.cooldownMinutes > 0
|
||||||
|
? `${detailOrder.gift.cooldownMinutes} 分钟`
|
||||||
|
: `${detailOrder.gift.cooldownHours} 小时`}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="打手资料提交">
|
<Descriptions.Item label="打手资料提交">
|
||||||
{formatAdminDateTime(detailOrder.gift.boosterMaterial?.submittedAt)}
|
{formatAdminDateTime(detailOrder.gift.boosterMaterial?.submittedAt)}
|
||||||
@@ -2399,20 +2403,20 @@ export default function WorkOrdersPanel() {
|
|||||||
) : (
|
) : (
|
||||||
<Typography.Text type="secondary">待提交</Typography.Text>
|
<Typography.Text type="secondary">待提交</Typography.Text>
|
||||||
)}
|
)}
|
||||||
{canCancelShare ? (
|
{canCancelShare ? (
|
||||||
<Button
|
<Button
|
||||||
danger
|
danger
|
||||||
size="small"
|
size="small"
|
||||||
icon={<UndoOutlined />}
|
icon={<UndoOutlined />}
|
||||||
title="只撤销当前参与者的拼单份额,其他参与者不受影响"
|
title="只撤销当前参与者的拼单份额,其他参与者不受影响"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCancelShare(share)
|
setCancelShare(share)
|
||||||
cancelShareForm.setFieldsValue({ reason: '' })
|
cancelShareForm.setFieldsValue({ reason: '' })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
单人撤单
|
单人撤单
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -207,7 +207,11 @@ function GiftProgressCard({ order }: { order: WorkOrder }) {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
|
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
|
||||||
<Descriptions.Item label="冷却时长">{gift.cooldownHours} 小时</Descriptions.Item>
|
<Descriptions.Item label="冷却时长">
|
||||||
|
{gift.cooldownMinutes > 0
|
||||||
|
? `${gift.cooldownMinutes} 分钟`
|
||||||
|
: `${gift.cooldownHours} 小时`}
|
||||||
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="已加好友">
|
<Descriptions.Item label="已加好友">
|
||||||
{formatDateTime(gift.friendAddedAt)}
|
{formatDateTime(gift.friendAddedAt)}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ export function saveAdminWorkProductRule(payload: {
|
|||||||
timeoutPolicy?: string
|
timeoutPolicy?: string
|
||||||
acceptanceMode?: 'standard' | 'friend_gift' | string
|
acceptanceMode?: 'standard' | 'friend_gift' | string
|
||||||
giftCooldownHours?: number
|
giftCooldownHours?: number
|
||||||
|
giftCooldownMinutes?: number
|
||||||
fieldsText?: string
|
fieldsText?: string
|
||||||
sortOrder?: number
|
sortOrder?: number
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -376,6 +376,8 @@ export type WorkProductRule = {
|
|||||||
acceptanceMode?: 'standard' | 'friend_gift' | string
|
acceptanceMode?: 'standard' | 'friend_gift' | string
|
||||||
/** 好友赠送冷却小时数(默认 72) */
|
/** 好友赠送冷却小时数(默认 72) */
|
||||||
giftCooldownHours?: number
|
giftCooldownHours?: number
|
||||||
|
/** 好友赠送按分钟配置时的总分钟数;非零时使用分钟单位。 */
|
||||||
|
giftCooldownMinutes?: number
|
||||||
sortOrder: number
|
sortOrder: number
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
@@ -514,6 +516,7 @@ export type WorkOrderGiftProgress = {
|
|||||||
phase: WorkOrderGiftPhase
|
phase: WorkOrderGiftPhase
|
||||||
rawPhase: string
|
rawPhase: string
|
||||||
cooldownHours: number
|
cooldownHours: number
|
||||||
|
cooldownMinutes: number
|
||||||
friendAddedAt: string | null
|
friendAddedAt: string | null
|
||||||
giftAvailableAt: string | null
|
giftAvailableAt: string | null
|
||||||
boosterMaterial: BoosterMaterial
|
boosterMaterial: BoosterMaterial
|
||||||
|
|||||||
Reference in New Issue
Block a user