import { afterAll, beforeEach, describe, expect, it } from 'vitest'; import { autoAssign, createOrder, giveConsent, respondToOffer, retryAutoAssign, startClient, unassignOrder } from '../src/index.ts'; import { db, fixtures, makeCtx, NOW, slotAt, tgUser } from './helpers.ts'; let fx: Awaited>; beforeEach(async () => { fx = await fixtures(); // при равном счёте компания идёт раньше по алфавиту — в большинстве тестов проверяем частных исполнителей await db.providerCompany.update({ where: { id: fx.company.id }, data: { active: false } }); }); afterAll(() => db.$disconnect()); async function order(ctx: ReturnType['ctx'], tg: number, lang = 'ru') { const s = await startClient(ctx, tgUser(tg, lang)); await giveConsent(ctx, s.clientId); return createOrder(ctx, { clientId: s.clientId, serviceCode: 'cleaning', params: { cleaningType: 'regular', rooms: 2 }, scheduledAt: slotAt(2, 11), addressText: 'Pushkin 10', districtId: fx.kentron.id }); } const setting = (key: string, value: unknown) => db.setting.upsert({ where: { key }, create: { key, value: value as never }, update: { value: value as never } }); const history = async (orderId: string) => (await db.orderStatusHistory.findMany({ where: { orderId }, orderBy: { createdAt: 'asc' } })).map((h) => h.event).filter(Boolean); describe('автораспределение заказов', () => { it('новый заказ сразу предлагается лучшему: язык клиента и район; система — автор предложения', async () => { const { ctx, events } = makeCtx(NOW); const o = await order(ctx, 801); const res = await autoAssign(ctx, o.id); expect(res).toMatchObject({ result: 'offered', target: 'Maria' }); const offer = await db.orderOffer.findFirstOrThrow({ where: { orderId: o.id } }); expect(offer).toMatchObject({ providerId: fx.maria.id, status: 'OFFERED', createdById: null }); expect(events).toContainEqual({ type: 'offer.created', offerId: offer.id }); expect(await history(o.id)).toContain('autoOffered'); // пока предложение действует — не трогаем expect(await autoAssign(ctx, o.id)).toEqual({ result: 'skipped' }); }); it('отказ — следующему; кому уже предлагали, повторно не предлагаем', async () => { const { ctx } = makeCtx(NOW); const o = await order(ctx, 802); await autoAssign(ctx, o.id); const first = await db.orderOffer.findFirstOrThrow({ where: { orderId: o.id } }); await respondToOffer(ctx, { offerId: first.id, providerId: fx.maria.id, accept: false }); expect(await autoAssign(ctx, o.id)).toMatchObject({ result: 'offered', target: 'Ani' }); const second = await db.orderOffer.findFirstOrThrow({ where: { orderId: o.id, status: 'OFFERED' } }); await respondToOffer(ctx, { offerId: second.id, providerId: fx.ani.id, accept: false }); // больше свободных частных нет — компания включается и получает заказ await db.providerCompany.update({ where: { id: fx.company.id }, data: { active: true } }); expect(await autoAssign(ctx, o.id)).toMatchObject({ result: 'offered', target: 'Chisto' }); const offers = await db.orderOffer.findMany({ where: { orderId: o.id }, orderBy: { createdAt: 'asc' } }); expect(offers.map((x) => x.providerId ?? x.companyId)).toEqual([fx.maria.id, fx.ani.id, fx.company.id]); }); it('снятый с заказа исполнитель не получает его снова автоматически', async () => { const { ctx } = makeCtx(NOW); const o = await order(ctx, 803); await autoAssign(ctx, o.id); const offer = await db.orderOffer.findFirstOrThrow({ where: { orderId: o.id } }); await respondToOffer(ctx, { offerId: offer.id, providerId: fx.maria.id, accept: true }); const admin = await db.adminUser.create({ data: { name: 'Op', roles: ['SUPPORT'] } }); await unassignOrder(ctx, { orderId: o.id, adminId: admin.id }); const again = await autoAssign(ctx, o.id); expect(again.result).toBe('offered'); expect(again).not.toMatchObject({ target: 'Maria' }); }); it('лимит попыток: дальше вручную, команде сообщаем один раз', async () => { const { ctx } = makeCtx(NOW); await setting('assign.maxAutoOffers', 1); const o = await order(ctx, 804); await autoAssign(ctx, o.id); const offer = await db.orderOffer.findFirstOrThrow({ where: { orderId: o.id } }); await respondToOffer(ctx, { offerId: offer.id, providerId: fx.maria.id, accept: false }); expect(await autoAssign(ctx, o.id)).toEqual({ result: 'gaveUp', first: true }); expect(await autoAssign(ctx, o.id)).toEqual({ result: 'gaveUp', first: false }); expect((await history(o.id)).filter((e) => e === 'autoGaveUp')).toHaveLength(1); }); it('свободных нет — пометка один раз; повторная попытка воркера находит освободившегося', async () => { const { ctx } = makeCtx(NOW); const o = await order(ctx, 805); // все недоступны: исполнители отключены await db.provider.updateMany({ data: { active: false } }); expect(await autoAssign(ctx, o.id)).toEqual({ result: 'none', first: true }); expect(await autoAssign(ctx, o.id)).toEqual({ result: 'none', first: false }); await db.provider.update({ where: { id: fx.ani.id }, data: { active: true } }); const retried = await retryAutoAssign(ctx); expect(retried).toEqual([{ orderId: o.id, res: expect.objectContaining({ result: 'offered', target: 'Ani' }) }]); }); it('выключено в настройках — заказы ждут диспетчера', async () => { const { ctx } = makeCtx(NOW); await setting('assign.auto', false); const o = await order(ctx, 806); expect(await autoAssign(ctx, o.id)).toEqual({ result: 'off' }); expect(await retryAutoAssign(ctx)).toEqual([]); expect(await db.orderOffer.count({ where: { orderId: o.id } })).toBe(0); }); });