feat(M08-D): 补加盟申请与跟进运营

This commit is contained in:
Codex
2026-08-10 13:17:19 +08:00
parent e0e17159a7
commit aa4a6bd014
28 changed files with 894 additions and 13 deletions
+45
View File
@@ -0,0 +1,45 @@
import assert from 'node:assert/strict';
import { buildApp } from '../dist/app.js';
import { signAccessToken } from '../dist/auth/jwt.js';
const secret = 'test-only-franchise-management-secret-32';
const token = signAccessToken({ sub: '21', sid: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tid: '7', aid: '9', rv: 1 }, secret, 900);
let submitted;
let listInput;
let assignment;
let followUp;
const repository = {
async submitApplication(input) { submitted = input; return { applicationId: '101', applicationNo: 'FR101', idempotent: false }; },
async listApplications(input) { listInput = input; return { items: [], total: 0, page: input.page, pageSize: input.pageSize }; },
async getApplication(tenantId, id) { return { application: { id, tenantId }, followUps: [] }; },
async assignApplication(_actor, tenantId, id, assigneeUserId) { assignment = { tenantId, id, assigneeUserId }; return { applicationId: id, assigneeUserId }; },
async addFollowUp(_actor, tenantId, id, input) { followUp = { tenantId, id, input }; return { applicationId: id, followUpId: '301', status: input.status || 'NEW' }; }
};
const app = await buildApp({ franchise: {
repository,
platformConfig: { async resolveBootstrap(appId, tenantId) { return appId === 'wx-franchise' ? { tenantId: tenantId || '7' } : null; } },
authRepository: { async validateSession() { return { id: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tenantId: '7', platformAppId: '9', expiresAt: new Date(Date.now() + 60000), user: { id: '21', tenantId: '7', userType: 'STAFF', status: 'ACTIVE', roleVersion: 1, nickname: '', avatarUrl: '', phone: '' } }; } },
accessControl: { async getAccessProfile() { return { roles: ['TENANT_ADMIN'], capabilities: ['tenant.manage'], storeIds: [] }; } },
jwtSecret: secret
} });
const created = await app.inject({ method: 'POST', url: '/app-api/franchise-applications', headers: { 'x-wechat-appid': 'wx-franchise' }, payload: { city: '上海市', contactName: '张先生', contactPhone: '13800138000', message: '计划开店', clientRequestId: 'franchise-request-001' } });
assert.equal(created.statusCode, 201);
assert.equal(submitted.tenantId, '7');
assert.equal(submitted.contactPhone, '13800138000');
assert.equal('contactPhone' in (submitted.audit || {}), false);
const auth = { authorization: `Bearer ${token}` };
const listed = await app.inject({ method: 'GET', url: '/admin-api/franchise-applications?status=NEW&page=2&pageSize=10', headers: auth });
assert.equal(listed.statusCode, 200);
assert.deepEqual({ tenantId: listInput.tenantId, status: listInput.status, page: listInput.page }, { tenantId: '7', status: 'NEW', page: 2 });
const crossTenant = await app.inject({ method: 'GET', url: '/admin-api/franchise-applications?tenantId=8', headers: auth });
assert.equal(crossTenant.statusCode, 403);
const assigned = await app.inject({ method: 'PATCH', url: '/admin-api/franchise-applications/101/assignee', headers: auth, payload: { assigneeUserId: '22' } });
assert.equal(assigned.statusCode, 200);
assert.deepEqual(assignment, { tenantId: '7', id: '101', assigneeUserId: '22' });
const followed = await app.inject({ method: 'POST', url: '/admin-api/franchise-applications/101/follow-ups', headers: auth, payload: { followUpType: 'CALL', note: '已电话沟通', status: 'CONTACTED' } });
assert.equal(followed.statusCode, 201);
assert.equal(followUp.input.status, 'CONTACTED');
await app.close();
console.log('PASS: M08-D franchise submission, tenant scope, assignment and follow-up routes are present.');