Обходной лист (#12)
Tests / backend-test (push) Successful in 1m38s
Tests / frontend-test (push) Successful in 47s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/12
This commit was merged in pull request #12.
This commit is contained in:
2026-08-17 05:43:52 +00:00
parent d076f4e98d
commit 48ede1420d
37 changed files with 1351 additions and 435 deletions
@@ -1,9 +1,16 @@
import { Employee } from '../models';
import { findEmployeeByEmail, toEmployeeOption } from './employee';
import { findEmployeeByEmail, matchEmployeesByPosition, toEmployeeOption } from './employee';
// Хелпер вместо повторения company/companies в каждом литерале — в тестах
// нет нужды в разных компаниях у одного сотрудника (это отдельно проверяется
// в matchEmployeesByPosition ниже).
function emp(fio: string, email: string, position: string, company = ''): Employee {
return { fio, email, position, company, companies: company ? [company] : [] };
}
const employees: Employee[] = [
{ fio: 'Горбенко Павел', email: 'gorbenko.pavel@tnsu.kz', position: 'Разработчик middle' },
{ fio: 'Соколов Давид', email: 'sokolov.david@tnsu.kz', position: 'Руководитель отдела' }
emp('Горбенко Павел', 'gorbenko.pavel@tnsu.kz', 'Разработчик middle', 'ТОО TANSU Construction'),
emp('Соколов Давид', 'sokolov.david@tnsu.kz', 'Руководитель отдела', 'ТОО TANSU Construction')
];
describe('findEmployeeByEmail', () => {
@@ -26,11 +33,74 @@ describe('findEmployeeByEmail', () => {
});
describe('toEmployeeOption', () => {
it('строит опцию для SearchableSelect из сотрудника', () => {
it('строит опцию для SearchableSelect из сотрудника: должность и email в подписи', () => {
expect(toEmployeeOption(employees[0])).toEqual({
value: 'gorbenko.pavel@tnsu.kz',
label: 'Горбенко Павел',
sublabel: 'gorbenko.pavel@tnsu.kz'
sublabel: 'Разработчик middle · gorbenko.pavel@tnsu.kz'
});
});
// Должность в справочнике заполнена не у всех — тогда подпись не должна
// начинаться с висящего разделителя.
it('без должности в подписи остаётся только email', () => {
expect(toEmployeeOption(emp('Без Должности', 'nobody@tnsu.kz', ''))).toEqual({
value: 'nobody@tnsu.kz',
label: 'Без Должности',
sublabel: 'nobody@tnsu.kz'
});
});
});
describe('matchEmployeesByPosition', () => {
const staff: Employee[] = [
emp('Плотникова Лариса', 'p@tnsu.kz', 'Генеральный директор', 'Компания А'),
emp('Самаева Асель', 's@tnsu.kz', 'Директор', 'Компания А'),
emp('Ким Николай', 'k@tnsu.kz', 'Управляющий директор', 'Компания Б'),
emp('Иванов Иван', 'i1@tnsu.kz', 'Инженер ПТО', 'Компания А'),
emp('Петров Пётр', 'i2@tnsu.kz', 'Инженер ПТО', 'Компания Б'),
emp('Без Должности', 'n@tnsu.kz', '', 'Компания А')
];
it('пустой ввод — ничего не подбирает', () => {
expect(matchEmployeesByPosition(staff, ' ')).toEqual({ exact: [], similar: [] });
});
// Бэкенд ищет точное совпадение должности, поэтому подсказка обязана считать
// так же — иначе обещала бы исполнителя там, где его не найдут.
it('точное совпадение не зависит от регистра и краевых пробелов', () => {
const result = matchEmployeesByPosition(staff, ' генеральный ДИРЕКТОР ');
expect(result.exact.map(e => e.fio)).toEqual(['Плотникова Лариса']);
});
it('одну должность в разных компаниях занимают несколько человек — возвращаются все', () => {
const result = matchEmployeesByPosition(staff, 'Инженер ПТО');
expect(result.exact.map(e => e.company)).toEqual(['Компания А', 'Компания Б']);
});
// 'директор' — сам по себе точное совпадение (должность Самаевой), а
// подстрокой входит ещё в две других — те и предлагаются похожими.
it('точное совпадение и похожие должности возвращаются одновременно', () => {
const result = matchEmployeesByPosition(staff, 'директор');
expect(result.exact.map(e => e.fio)).toEqual(['Самаева Асель']);
expect(result.similar.map(s => s.position)).toEqual(['Генеральный директор', 'Управляющий директор']);
});
// Список нужен, чтобы подставить точное написание — одинаковые строки в нём бесполезны.
it('похожие схлопываются по написанию и считают людей', () => {
expect(matchEmployeesByPosition(staff, 'инженер').similar).toEqual([
{ position: 'Инженер ПТО', fio: 'Иванов Иван', count: 2 }
]);
});
it('точное совпадение не дублируется в похожих', () => {
const result = matchEmployeesByPosition(staff, 'Директор');
expect(result.exact.map(e => e.fio)).toEqual(['Самаева Асель']);
expect(result.similar.some(s => s.position === 'Директор')).toBe(false);
});
it('сотрудники без должности не попадают в подбор', () => {
expect(matchEmployeesByPosition(staff, '').exact).toEqual([]);
expect(matchEmployeesByPosition(staff, 'директор').similar.some(s => s.position === '')).toBe(false);
});
});