76f266645d
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。 排除 node_modules、构建产物、安装包与 .env 密钥。 Co-authored-by: Cursor <cursoragent@cursor.com>
162 lines
7.1 KiB
TypeScript
162 lines
7.1 KiB
TypeScript
import {
|
|
ASSIGN_ROLE,
|
|
canAccessBidModule,
|
|
canAssignFetcher,
|
|
canCreateBid,
|
|
canCreateSpecialBid,
|
|
canFetchBid,
|
|
canFetchThisBid,
|
|
canManageDraft,
|
|
canManageThisDraft,
|
|
canSeeAllBids,
|
|
canSeeTerminated,
|
|
canTerminateBid,
|
|
canTerminateFrom,
|
|
currentHandlerNames,
|
|
isCommercialStage,
|
|
isSpecialBid,
|
|
isSpecialCoordinatorUser,
|
|
stageForStatus,
|
|
previousBidStatus,
|
|
inferLegacyRestoreStatus,
|
|
} from './bid-workflow';
|
|
|
|
describe('bid-workflow', () => {
|
|
it('maps status to review stage', () => {
|
|
expect(stageForStatus('TECH_INITIAL')).toBe('TECH_INITIAL');
|
|
expect(stageForStatus('TECH_FINAL')).toBe('TECH_FINAL');
|
|
expect(stageForStatus('REVIEW1')).toBe('REVIEW1');
|
|
expect(stageForStatus('REVIEW2')).toBe('REVIEW2');
|
|
expect(stageForStatus('FETCH')).toBeNull();
|
|
expect(stageForStatus('DRAFTING')).toBeNull();
|
|
});
|
|
|
|
it('treats commercial reject as bounce, not terminate', () => {
|
|
expect(isCommercialStage('REVIEW1')).toBe(true);
|
|
expect(isCommercialStage('REVIEW2')).toBe(true);
|
|
expect(isCommercialStage('TECH_INITIAL')).toBe(false);
|
|
});
|
|
|
|
it('lets every director see all bids', () => {
|
|
expect(canSeeAllBids(['rd_director'])).toBe(true);
|
|
expect(canSeeAllBids(['3d_director'])).toBe(true);
|
|
expect(canSeeAllBids(['video_director'])).toBe(true);
|
|
expect(canSeeAllBids(['material_director'])).toBe(true);
|
|
expect(canSeeAllBids(['biz_director'])).toBe(true);
|
|
expect(canSeeAllBids(['tech_director'])).toBe(true);
|
|
expect(canSeeAllBids(['owner'])).toBe(true);
|
|
expect(canSeeAllBids(['admin'])).toBe(true);
|
|
expect(canSeeAllBids(['biz_staff'])).toBe(true);
|
|
expect(canSeeAllBids(['employee'])).toBe(false);
|
|
expect(canSeeAllBids(['pm'])).toBe(false);
|
|
});
|
|
|
|
it('hides terminated bids from ordinary employees', () => {
|
|
expect(canSeeTerminated(['employee'], ['office'])).toBe(false);
|
|
expect(canSeeTerminated(['hr'], ['office', 'hr'])).toBe(false);
|
|
expect(canSeeTerminated(['finance'], ['bid:result'])).toBe(false);
|
|
expect(canSeeTerminated(['biz_staff'], ['bid:screening'])).toBe(false);
|
|
expect(canSeeTerminated(['biz_director'], ['bid:terminated'])).toBe(true);
|
|
expect(canSeeTerminated(['tech_director'], ['bid'])).toBe(true);
|
|
expect(canSeeTerminated(['owner'], [])).toBe(true);
|
|
expect(canSeeTerminated(['admin'], [])).toBe(true);
|
|
});
|
|
|
|
it('restricts create / fetch / terminate by role', () => {
|
|
expect(canCreateBid(['owner'])).toBe(true);
|
|
expect(canCreateBid(['tech_director'])).toBe(false);
|
|
expect(canCreateBid(['employee'], ['bid:screening'])).toBe(true);
|
|
expect(canFetchBid(['biz_staff'])).toBe(true);
|
|
expect(canFetchBid(['tech_director'])).toBe(false);
|
|
expect(canTerminateBid(['biz_director'])).toBe(true);
|
|
expect(canTerminateBid(['biz_staff'])).toBe(false);
|
|
expect(canManageDraft(['biz_director'])).toBe(true);
|
|
expect(canManageDraft(['biz_staff'])).toBe(false);
|
|
});
|
|
|
|
it('keeps special screening on named coordinators', () => {
|
|
expect(isSpecialBid('SPECIAL')).toBe(true);
|
|
expect(isSpecialBid('FLOW')).toBe(false);
|
|
expect(canCreateSpecialBid(['owner'])).toBe(true);
|
|
expect(canCreateSpecialBid(['biz_director'])).toBe(false);
|
|
expect(isSpecialCoordinatorUser({ username: 'liuyunze', displayName: '刘运泽' })).toBe(true);
|
|
expect(isSpecialCoordinatorUser({ username: 'chencheng', displayName: '陈铖' })).toBe(true);
|
|
expect(isSpecialCoordinatorUser({ username: 'other', displayName: '其他人' })).toBe(false);
|
|
const coordinators = [{ userId: 'liu', role: ASSIGN_ROLE.COORDINATOR }];
|
|
const fetchers = [{ userId: 'staff', role: ASSIGN_ROLE.FETCHER }];
|
|
expect(canAssignFetcher(['3d_director'], 'SPECIAL', coordinators, 'liu', 'FETCH')).toBe(true);
|
|
expect(canAssignFetcher(['3d_director'], 'SPECIAL', coordinators, 'other', 'FETCH')).toBe(false);
|
|
expect(canAssignFetcher(['biz_director'], 'FLOW', [], 'x', 'FETCH')).toBe(true);
|
|
expect(canAssignFetcher(['biz_staff'], 'FLOW', [], 'x', 'FETCH')).toBe(false);
|
|
expect(canFetchThisBid(['employee'], 'SPECIAL', fetchers, 'staff')).toBe(true);
|
|
expect(canFetchThisBid(['biz_staff'], 'SPECIAL', fetchers, 'other')).toBe(false);
|
|
expect(canFetchThisBid(['biz_staff'], 'FLOW', [], 'other')).toBe(false);
|
|
expect(canFetchThisBid(['employee'], 'FLOW', fetchers, 'staff')).toBe(true);
|
|
expect(canManageThisDraft(['3d_director'], 'SPECIAL', coordinators, 'liu')).toBe(true);
|
|
expect(canManageThisDraft(['biz_director'], 'SPECIAL', coordinators, 'biz')).toBe(false);
|
|
expect(canManageThisDraft(['biz_director'], 'FLOW', [], 'biz')).toBe(true);
|
|
});
|
|
|
|
it('allows terminate until execution, not after open', () => {
|
|
expect(canTerminateFrom('TECH_INITIAL')).toBe(true);
|
|
expect(canTerminateFrom('DRAFTING')).toBe(true);
|
|
expect(canTerminateFrom('LIST')).toBe(true);
|
|
expect(canTerminateFrom('PENDING')).toBe(false);
|
|
expect(canTerminateFrom('WON')).toBe(false);
|
|
});
|
|
|
|
it('shows the person who should act on the current step', () => {
|
|
expect(
|
|
currentHandlerNames({
|
|
status: 'TECH_INITIAL',
|
|
reviewCycle: 1,
|
|
createdBy: { displayName: '潘兴辉' },
|
|
reviewers: [
|
|
{ stage: 'TECH_INITIAL', result: 'PENDING', cycle: 1, user: { displayName: '戴永康' } },
|
|
],
|
|
}),
|
|
).toBe('戴永康');
|
|
expect(
|
|
currentHandlerNames({
|
|
status: 'FETCH',
|
|
assignees: [{ role: 'FETCHER', user: { displayName: '何烨' } }],
|
|
createdBy: { displayName: '潘兴辉' },
|
|
}),
|
|
).toBe('何烨');
|
|
expect(
|
|
currentHandlerNames({
|
|
status: 'DRAFTING',
|
|
assignees: [{ role: 'DRAFTER', user: { displayName: '张婧妍' } }],
|
|
}),
|
|
).toBe('张婧妍');
|
|
expect(currentHandlerNames({ status: 'PRINT', createdBy: { displayName: '潘兴辉' } })).toBe('商务部');
|
|
expect(currentHandlerNames({ status: 'FETCH', createdBy: { displayName: '潘兴辉' } })).toBe('待派取标人');
|
|
});
|
|
|
|
it('rolls back one process step, except special fetch', () => {
|
|
expect(previousBidStatus('FETCH', 'FLOW')).toBe('TECH_INITIAL');
|
|
expect(previousBidStatus('FETCH', 'SPECIAL')).toBeNull();
|
|
expect(previousBidStatus('TECH_FINAL')).toBe('FETCH');
|
|
expect(previousBidStatus('DRAFTING')).toBe('TECH_FINAL');
|
|
expect(previousBidStatus('REVIEW1')).toBe('DRAFTING');
|
|
expect(previousBidStatus('REVIEW2')).toBe('REVIEW1');
|
|
expect(previousBidStatus('PRINT')).toBe('REVIEW2');
|
|
expect(previousBidStatus('LIST')).toBe('PRINT');
|
|
expect(previousBidStatus('PENDING')).toBe('LIST');
|
|
expect(previousBidStatus('TECH_INITIAL')).toBeNull();
|
|
expect(previousBidStatus('WON')).toBeNull();
|
|
});
|
|
|
|
it('infers the restore step for legacy terminated records without an action log', () => {
|
|
expect(inferLegacyRestoreStatus([], [])).toBe('DRAFTING');
|
|
expect(
|
|
inferLegacyRestoreStatus(
|
|
[{ stage: 'TECH_INITIAL', result: 'APPROVED' }],
|
|
[{ role: 'FETCHER' }, { role: 'DRAFTER' }],
|
|
),
|
|
).toBe('DRAFTING');
|
|
expect(inferLegacyRestoreStatus([], [{ role: 'BIDDER' }])).toBe('PENDING');
|
|
expect(inferLegacyRestoreStatus([{ stage: 'REVIEW2', result: 'PENDING' }], [])).toBe('REVIEW2');
|
|
});
|
|
});
|