feat(activity): implement P4 member self-service leave and ticket refund channel
这个提交包含在:
@@ -318,5 +318,62 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('9. 组局满员前「搭子自主退席与全额退款」与席位释放闭环验证 (P4 体验优化专项目标)', () => {
|
||||
it('MySQL 数据库必须支持 finance_records 表的 user_id 字段补丁', () => {
|
||||
const mysqlPath = path.join(rootDir, 'server', 'src', 'db', 'mysql.ts');
|
||||
const content = fs.readFileSync(mysqlPath, 'utf-8');
|
||||
expect(content).toContain('ALTER TABLE finance_records ADD COLUMN user_id');
|
||||
});
|
||||
|
||||
it('Repository leaveActivity 必须支持原路退款至用户钱包并记入资金流水', () => {
|
||||
const repoPath = path.join(rootDir, 'server', 'src', 'db', 'repository.ts');
|
||||
const content = fs.readFileSync(repoPath, 'utf-8');
|
||||
expect(content).toContain('async leaveActivity(id: string, userId: number)');
|
||||
expect(content).toContain('DELETE FROM participants WHERE id = ?');
|
||||
expect(content).toContain('UPDATE users SET balance = balance + ? WHERE id = ?');
|
||||
expect(content).toContain('成员退席原路退款');
|
||||
});
|
||||
|
||||
it('服务端路由必须提供 POST /api/activities/:id/leave 接口', () => {
|
||||
const serverIndexPath = path.join(rootDir, 'server', 'src', 'index.ts');
|
||||
const content = fs.readFileSync(serverIndexPath, 'utf-8');
|
||||
expect(content).toContain("app.post('/api/activities/:id/leave'");
|
||||
expect(content).toContain('leaveActivity');
|
||||
});
|
||||
|
||||
it('小程序活动详情页必须支持普通搭子申请退席全额退款与本人席位标识', () => {
|
||||
const actDetailPath = path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'detail.vue');
|
||||
const content = fs.readFileSync(actDetailPath, 'utf-8');
|
||||
expect(content).toContain('leaveActivity');
|
||||
expect(content).toContain('handleLeave');
|
||||
expect(content).toContain('申请退席 (全额退款)');
|
||||
expect(content).toContain('my-badge');
|
||||
expect(content).toContain('我的席位');
|
||||
});
|
||||
|
||||
it('小程序门票页必须支持未核销门票自助申请退票退款', () => {
|
||||
const myTicketPath = path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'my-ticket.vue');
|
||||
const content = fs.readFileSync(myTicketPath, 'utf-8');
|
||||
expect(content).toContain('leaveActivity');
|
||||
expect(content).toContain('handleCancelTicket');
|
||||
expect(content).toContain('申请退票退款 (释放席位)');
|
||||
});
|
||||
|
||||
it('所有 P4 新增代码严禁包含任何 Emoji 违规字符', () => {
|
||||
const p4Files = [
|
||||
path.join(rootDir, 'server', 'src', 'db', 'mysql.ts'),
|
||||
path.join(rootDir, 'server', 'src', 'db', 'repository.ts'),
|
||||
path.join(rootDir, 'server', 'src', 'index.ts'),
|
||||
path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'detail.vue'),
|
||||
path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'my-ticket.vue')
|
||||
];
|
||||
const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u;
|
||||
for (const f of p4Files) {
|
||||
const text = fs.readFileSync(f, 'utf-8');
|
||||
expect(emojiRegex.test(text)).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户