feat: v3.4.12 ticket iteration
Refund rollback, winery T+3, multi withdraw, mini-user store detail, dev plan batch edit and WeCom dispatch, support ticket edit/attachments/batch status, package imageUrl. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -251,7 +251,6 @@ describe('validateStoreWithdraw', () => {
|
||||
requestAmount: 200,
|
||||
todayApplied: 0,
|
||||
dailyLimit: 5000,
|
||||
hasPendingRequest: false,
|
||||
hasBankAccount: true,
|
||||
};
|
||||
|
||||
@@ -261,8 +260,7 @@ describe('validateStoreWithdraw', () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects pending request / missing bank / over available', () => {
|
||||
expect(validateStoreWithdraw({ ...base, hasPendingRequest: true }).ok).toBe(false);
|
||||
it('rejects missing bank / over available', () => {
|
||||
expect(validateStoreWithdraw({ ...base, hasBankAccount: false }).ok).toBe(false);
|
||||
expect(validateStoreWithdraw({ ...base, requestAmount: 1001 }).ok).toBe(false);
|
||||
});
|
||||
|
||||
@@ -105,20 +105,16 @@ export type ValidateStoreWithdrawInput = {
|
||||
requestAmount: number;
|
||||
todayApplied: number;
|
||||
dailyLimit: number;
|
||||
hasPendingRequest: boolean;
|
||||
hasBankAccount: boolean;
|
||||
};
|
||||
|
||||
/** 门店未出账提现护栏(FIN-002 单日上限 + 幂等/账户) */
|
||||
/** 门店未出账提现护栏(FIN-002 单日上限 + 账户) */
|
||||
export function validateStoreWithdraw(
|
||||
input: ValidateStoreWithdrawInput,
|
||||
): { ok: boolean; message?: string } {
|
||||
if (!input.hasBankAccount) {
|
||||
return { ok: false, message: '请先完善入驻收款账户后再提现' };
|
||||
}
|
||||
if (input.hasPendingRequest) {
|
||||
return { ok: false, message: '已有待审核提现申请,请等待处理完成' };
|
||||
}
|
||||
if (!(input.requestAmount > 0)) {
|
||||
return { ok: false, message: '提现金额必须大于 0' };
|
||||
}
|
||||
@@ -354,3 +350,4 @@ export function orderTabToStatuses(tab: string): string[] | undefined {
|
||||
|
||||
export * from './city-partner';
|
||||
export * from './dev-plan';
|
||||
export * from './support-ticket';
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { validateSupportTicketStatusTransition } from './support-ticket';
|
||||
|
||||
describe('validateSupportTicketStatusTransition', () => {
|
||||
it('allows DEVELOPING -> TESTING', () => {
|
||||
expect(
|
||||
validateSupportTicketStatusTransition('DEVELOPING', 'TESTING', {
|
||||
linkedTaskCount: 1,
|
||||
isSuperAdmin: true,
|
||||
}).ok,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('blocks PENDING_REVIEW -> DEVELOPING without tasks', () => {
|
||||
expect(
|
||||
validateSupportTicketStatusTransition('PENDING_REVIEW', 'DEVELOPING', {
|
||||
linkedTaskCount: 0,
|
||||
isSuperAdmin: true,
|
||||
}).ok,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('blocks rollback PASSED -> DEVELOPING', () => {
|
||||
expect(
|
||||
validateSupportTicketStatusTransition('PASSED', 'DEVELOPING', {
|
||||
linkedTaskCount: 1,
|
||||
isSuperAdmin: true,
|
||||
}).ok,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('requires super admin and reason for REJECTED', () => {
|
||||
expect(
|
||||
validateSupportTicketStatusTransition('DEVELOPING', 'REJECTED', {
|
||||
linkedTaskCount: 1,
|
||||
isSuperAdmin: false,
|
||||
rejectReason: 'x',
|
||||
}).ok,
|
||||
).toBe(false);
|
||||
expect(
|
||||
validateSupportTicketStatusTransition('DEVELOPING', 'REJECTED', {
|
||||
linkedTaskCount: 1,
|
||||
isSuperAdmin: true,
|
||||
rejectReason: '',
|
||||
}).ok,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
export type SupportTicketStatus =
|
||||
| 'PENDING_REVIEW'
|
||||
| 'REJECTED'
|
||||
| 'DEVELOPING'
|
||||
| 'TESTING'
|
||||
| 'PASSED';
|
||||
|
||||
export type SupportTicketStatusTransitionContext = {
|
||||
linkedTaskCount: number;
|
||||
isSuperAdmin: boolean;
|
||||
rejectReason?: string;
|
||||
};
|
||||
|
||||
const FORWARD_TRANSITIONS: Record<SupportTicketStatus, SupportTicketStatus[]> = {
|
||||
PENDING_REVIEW: ['DEVELOPING', 'REJECTED'],
|
||||
DEVELOPING: ['TESTING', 'REJECTED'],
|
||||
TESTING: ['PASSED', 'REJECTED'],
|
||||
REJECTED: [],
|
||||
PASSED: [],
|
||||
};
|
||||
|
||||
/** 技术支持工单批量/人工改状态校验(禁止回退) */
|
||||
export function validateSupportTicketStatusTransition(
|
||||
from: SupportTicketStatus,
|
||||
to: SupportTicketStatus,
|
||||
ctx: SupportTicketStatusTransitionContext,
|
||||
): { ok: boolean; message?: string } {
|
||||
if (from === to) return { ok: true };
|
||||
|
||||
const allowed = FORWARD_TRANSITIONS[from] ?? [];
|
||||
if (!allowed.includes(to)) {
|
||||
return { ok: false, message: `不允许从「${from}」变更为「${to}」` };
|
||||
}
|
||||
|
||||
if (to === 'REJECTED') {
|
||||
if (!ctx.isSuperAdmin) return { ok: false, message: '仅最高管理员可驳回工单' };
|
||||
if (!ctx.rejectReason?.trim()) return { ok: false, message: '请填写驳回理由' };
|
||||
}
|
||||
|
||||
if (from === 'PENDING_REVIEW' && to === 'DEVELOPING' && ctx.linkedTaskCount < 1) {
|
||||
return { ok: false, message: '待评审工单转入开发须至少 1 条关联开发任务' };
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
@@ -202,6 +202,18 @@ export interface DevPlanTaskDispatchInput {
|
||||
|
||||
|
||||
|
||||
export interface DevPlanTaskBatchUpdateInput {
|
||||
|
||||
taskIds: string[];
|
||||
|
||||
status?: DevPlanTaskStatusDto;
|
||||
|
||||
versionId?: string | null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface DevPlanLinkedTaskSummary {
|
||||
|
||||
id: string;
|
||||
|
||||
@@ -63,7 +63,6 @@ export interface StoreWithdrawSummaryDto {
|
||||
remainingDailyLimit: number;
|
||||
isPrimary: boolean;
|
||||
hasBankAccount: boolean;
|
||||
hasPendingRequest: boolean;
|
||||
bankAccount?: StoreWithdrawBankAccountDto | null;
|
||||
}
|
||||
|
||||
@@ -184,6 +183,9 @@ export const STORE_SETTLEMENT_DEFAULT_RATE = 0.6;
|
||||
/** 酒厂账单结算比例(酒单实付 × 比例),暂定 30% */
|
||||
export const WINERY_SETTLEMENT_RATE = 0.3;
|
||||
|
||||
/** 酒厂账单出账滞后自然日(T+3:支付日 + 3 天后纳入账单) */
|
||||
export const WINERY_SETTLEMENT_LAG_DAYS = 3;
|
||||
|
||||
export type { LogisticsSettlementMethod } from './enums';
|
||||
export { LOGISTICS_SETTLEMENT_METHOD_LABELS } from './enums';
|
||||
import type { LogisticsSettlementMethod } from './enums';
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface StorePackageItemDto {
|
||||
dishes: string;
|
||||
usableTime?: string | null;
|
||||
otherNotes?: string | null;
|
||||
imageUrl?: string | null;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface SupportTicketDto {
|
||||
reviewerName?: string | null;
|
||||
reviewedAt?: string | null;
|
||||
remark?: string | null;
|
||||
attachmentUrls?: string[] | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
completedAt?: string | null;
|
||||
@@ -57,6 +58,15 @@ export interface CreateSupportTicketRequest {
|
||||
title: string;
|
||||
content?: string;
|
||||
remark?: string;
|
||||
attachmentUrls?: string[];
|
||||
}
|
||||
|
||||
export interface UpdateSupportTicketRequest {
|
||||
ticketType?: SupportTicketTypeDto;
|
||||
title?: string;
|
||||
content?: string;
|
||||
remark?: string;
|
||||
attachmentUrls?: string[];
|
||||
}
|
||||
|
||||
export interface RejectSupportTicketRequest {
|
||||
@@ -73,6 +83,8 @@ export interface ReviewSupportTicketRequest {
|
||||
rejectReason?: string;
|
||||
note?: string;
|
||||
tasks?: CreateDevPlanTaskFromTicketInput[];
|
||||
dispatchToWecom?: boolean;
|
||||
dispatchSupplement?: string;
|
||||
}
|
||||
|
||||
export interface SupportTicketLinkedTaskDto {
|
||||
@@ -108,3 +120,10 @@ export interface BatchReviewConfirmItem {
|
||||
export interface BatchReviewConfirmRequest {
|
||||
items: BatchReviewConfirmItem[];
|
||||
}
|
||||
|
||||
export interface BatchUpdateSupportTicketStatusRequest {
|
||||
ticketIds: string[];
|
||||
status: SupportTicketStatusDto;
|
||||
rejectReason?: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user