feat(M08-D): 补加盟申请与跟进运营
This commit is contained in:
@@ -16,6 +16,7 @@ import { RbacRepository } from '../dist/auth/rbac-repository.js';
|
||||
import { UserManagementRepository } from '../dist/auth/user-management-repository.js';
|
||||
import { StoreRoomRepository, StoreRoomError } from '../dist/stores/store-room-repository.js';
|
||||
import { ContentRepository, ContentError } from '../dist/content/content-repository.js';
|
||||
import { FranchiseRepository, FranchiseError } from '../dist/franchise/franchise-repository.js';
|
||||
import { StoreDiscoveryRepository } from '../dist/stores/store-discovery-repository.js';
|
||||
import { StoreAccessRepository, StoreAccessError } from '../dist/stores/access-repository.js';
|
||||
import { PricingRepository, PricingError } from '../dist/orders/pricing-repository.js';
|
||||
@@ -60,6 +61,8 @@ const expectedTables = [
|
||||
'qipai_device_status_snapshots',
|
||||
'qipai_devices',
|
||||
'qipai_direct_bookings',
|
||||
'qipai_franchise_applications',
|
||||
'qipai_franchise_follow_ups',
|
||||
'qipai_group_redemptions',
|
||||
'qipai_group_vouchers',
|
||||
'qipai_holiday_calendar',
|
||||
@@ -129,13 +132,13 @@ async function readMigrationVersions(pool) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT version, name
|
||||
FROM qipai_schema_migrations
|
||||
WHERE version IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
WHERE version IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ORDER BY version`,
|
||||
['2026061601', '2026061802', '2026061803', '2026061804',
|
||||
'2026061805', '2026061806', '2026061807', '2026061808', '2026061809',
|
||||
'2026061810', '2026061811', '2026062012', '2026062013', '2026062014',
|
||||
'2026062015', '2026062216', '2026062217', '2026062218', '2026062219',
|
||||
'2026062220', '2026081002']
|
||||
'2026062220', '2026081002', '2026081003']
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
@@ -1748,6 +1751,50 @@ async function assertContentManagement(pool, context) {
|
||||
);
|
||||
}
|
||||
|
||||
async function assertFranchiseManagement(pool, context) {
|
||||
const [adminRows] = await pool.query(
|
||||
`SELECT u.id FROM qipai_users u
|
||||
INNER JOIN qipai_user_roles ur ON ur.tenant_id = u.tenant_id AND ur.user_id = u.id
|
||||
INNER JOIN qipai_roles r ON r.id = ur.role_id AND r.tenant_id = ur.tenant_id
|
||||
WHERE u.tenant_id = ? AND r.code = 'TENANT_ADMIN' LIMIT 1`, [context.tenantId]
|
||||
);
|
||||
const adminId = String(adminRows[0].id);
|
||||
const access = await new RbacRepository(pool).getAccessProfile(context.tenantId, adminId);
|
||||
const actor = { tenantId: context.tenantId, userId: adminId, access,
|
||||
traceId: 'm08d-franchise-live', ip: '127.0.0.1', userAgent: 'M08-D franchise live test' };
|
||||
const repository = new FranchiseRepository(pool);
|
||||
const input = { tenantId: context.tenantId, city: '上海市', contactName: '测试联系人',
|
||||
contactPhone: '13800138000', message: '计划开设两家门店', source: 'MINIAPP',
|
||||
clientRequestId: 'm08d-franchise-idempotent', traceId: actor.traceId,
|
||||
ip: actor.ip, userAgent: actor.userAgent };
|
||||
const first = await repository.submitApplication(input);
|
||||
const duplicate = await repository.submitApplication(input);
|
||||
assert.equal(first.idempotent, false);
|
||||
assert.equal(duplicate.idempotent, true);
|
||||
assert.equal(duplicate.applicationId, first.applicationId);
|
||||
assert.equal((await repository.listApplications({ tenantId: context.tenantId,
|
||||
page: 1, pageSize: 20, status: 'NEW' })).total, 1);
|
||||
await assert.rejects(
|
||||
() => repository.assignApplication(actor, context.tenantId, first.applicationId, '999999999'),
|
||||
(error) => error instanceof FranchiseError && error.code === 'FRANCHISE_ASSIGNEE_INVALID'
|
||||
);
|
||||
await repository.assignApplication(actor, context.tenantId, first.applicationId, adminId);
|
||||
await repository.addFollowUp(actor, context.tenantId, first.applicationId, {
|
||||
followUpType: 'CALL', note: '已完成首次电话沟通', status: 'CONTACTED', nextFollowUpAt: null
|
||||
});
|
||||
await assert.rejects(() => repository.addFollowUp(actor, context.tenantId, first.applicationId, {
|
||||
followUpType: 'NOTE', note: '跳过资格确认', status: 'CONVERTED'
|
||||
}), (error) => error instanceof FranchiseError && error.code === 'FRANCHISE_STATUS_TRANSITION_INVALID');
|
||||
const detail = await repository.getApplication(context.tenantId, first.applicationId);
|
||||
assert.equal(detail.application.status, 'CONTACTED');
|
||||
assert.equal(detail.followUps.length, 2);
|
||||
const [auditRows] = await pool.query(
|
||||
`SELECT CAST(metadata AS CHAR) AS metadata FROM qipai_audit_logs
|
||||
WHERE tenant_id = ? AND resource_type = 'FRANCHISE_APPLICATION'`, [context.tenantId]
|
||||
);
|
||||
assert.equal(auditRows.some((row) => row.metadata.includes('13800138000')), false);
|
||||
}
|
||||
|
||||
async function assertDeviceTopology(pool, context) {
|
||||
const [adminRows] = await pool.query(
|
||||
`SELECT u.id FROM qipai_users u
|
||||
@@ -2026,7 +2073,8 @@ try {
|
||||
{ version: '2026062218', name: 'm05d_profit_sharing' },
|
||||
{ version: '2026062219', name: 'm06b_device_topology' },
|
||||
{ version: '2026062220', name: 'm06c_iot_messages' },
|
||||
{ version: '2026081002', name: 'm08d_content_asset_scope' }
|
||||
{ version: '2026081002', name: 'm08d_content_asset_scope' },
|
||||
{ version: '2026081003', name: 'm08d_franchise_leads' }
|
||||
]);
|
||||
await assertTaskDurability(pool);
|
||||
const loginContext = await assertPlatformTenantIsolation(pool);
|
||||
@@ -2034,6 +2082,7 @@ try {
|
||||
await assertUserManagement(pool, loginContext);
|
||||
await assertStoreRoomDomain(pool, loginContext);
|
||||
await assertContentManagement(pool, loginContext);
|
||||
await assertFranchiseManagement(pool, loginContext);
|
||||
await assertStoreDiscovery(pool, loginContext);
|
||||
await assertSceneAndWifiAccess(pool, loginContext);
|
||||
await assertPricingAndReservations(pool, loginContext);
|
||||
@@ -2077,7 +2126,8 @@ try {
|
||||
{ version: '2026062218', name: 'm05d_profit_sharing' },
|
||||
{ version: '2026062219', name: 'm06b_device_topology' },
|
||||
{ version: '2026062220', name: 'm06c_iot_messages' },
|
||||
{ version: '2026081002', name: 'm08d_content_asset_scope' }
|
||||
{ version: '2026081002', name: 'm08d_content_asset_scope' },
|
||||
{ version: '2026081003', name: 'm08d_franchise_leads' }
|
||||
]);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: second up and verify restored the schema.');
|
||||
|
||||
Reference in New Issue
Block a user