Feature/vacation edo signing (#23)
Tests / frontend-test (push) Successful in 1m24s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/23
This commit was merged in pull request #23.
This commit is contained in:
2026-10-05 08:53:18 +00:00
parent f8ab2a1d71
commit 637e51d7f9
85 changed files with 2152 additions and 171 deletions
@@ -0,0 +1,152 @@
import { ICompany } from '../dictionary/models/ICompany';
import { IEmployeeOrganization } from '../dictionary/models/IEmployeeOrganization';
import { describeEdoError } from './edoErrors';
import { splitFio } from './fio';
import { resolveSigningTargets } from './signingTarget';
const TANSU_ID = '9de064a4-44c8-88ef-11ec-8284930fad96';
function record(overrides: Partial<IEmployeeOrganization> = {}): IEmployeeOrganization {
return {
id: 'e1',
fio: 'Иванов Иван Иванович',
email: 'ivanov@tnsu.kz',
iin: '950515123452',
company: 'ТОО TANSU Construction',
companyId: TANSU_ID,
section: '',
departmentId: '',
position: 'Инженер',
mobile: '',
...overrides
};
}
const COMPANIES: ICompany[] = [
{ Id: TANSU_ID, Name: 'ТОО TANSU Construction', Bin: '021040005165' },
{ Id: '0a1b2c3d-0000-4000-8000-000000000002', Name: 'ТОО Без БИН', Bin: '' }
];
describe('Ф.И.О. по частям для ЭДО', () => {
it('три слова', () => {
expect(splitFio('Иванов Иван Иванович')).toEqual({ lastName: 'Иванов', firstName: 'Иван', middleName: 'Иванович' });
});
it('без отчества', () => {
expect(splitFio('Иванов Иван')).toEqual({ lastName: 'Иванов', firstName: 'Иван', middleName: '' });
});
it('лишние пробелы и «оглы» не теряются', () => {
expect(splitFio(' Алиев Тимур Расул оглы ')).toEqual({
lastName: 'Алиев',
firstName: 'Тимур',
middleName: 'Расул оглы'
});
});
it('одно слово или пусто — не разобрать', () => {
expect(splitFio('Иванов')).toBeUndefined();
expect(splitFio('')).toBeUndefined();
});
});
const STROY_ID = '0a1b2c3d-0000-4000-8000-000000000003';
const NO_BIN_ID = '0a1b2c3d-0000-4000-8000-000000000002';
describe('кому и по каким компаниям отправлять на подпись', () => {
const companies: ICompany[] = [...COMPANIES, { Id: STROY_ID, Name: 'ТОО TANSU Строй', Bin: '021040005166' }];
it('ИИН из справочника сотрудников, БИН из справочника компаний', () => {
const targets = resolveSigningTargets([record()], companies);
expect(targets.iin).toBe('950515123452');
expect(targets.name.lastName).toBe('Иванов');
expect(targets.companies).toEqual([{ bin: '021040005165', name: 'ТОО TANSU Construction', position: 'Инженер' }]);
expect(targets.skipped).toEqual([]);
});
// Заявку заполняют одну, а подписывают по каждой компании, где человек работает.
it('по документу на каждую компанию, со своей должностью', () => {
const targets = resolveSigningTargets(
[record(), record({ company: 'ТОО TANSU Строй', companyId: STROY_ID, position: 'Главный инженер' })],
companies
);
expect(targets.companies).toEqual([
{ bin: '021040005165', name: 'ТОО TANSU Construction', position: 'Инженер' },
{ bin: '021040005166', name: 'ТОО TANSU Строй', position: 'Главный инженер' }
]);
});
it('одна и та же компания дважды не попадает', () => {
const targets = resolveSigningTargets([record(), record({ position: 'Совместитель' })], companies);
expect(targets.companies).toHaveLength(1);
});
it('идентификаторы компаний сравниваются без учёта регистра', () => {
expect(resolveSigningTargets([record({ companyId: TANSU_ID.toUpperCase() })], companies).companies[0].bin).toBe(
'021040005165'
);
});
it('компания без БИН не мешает остальным — уходит в пропущенные с причиной', () => {
const targets = resolveSigningTargets(
[record({ company: 'ТОО Без БИН', companyId: NO_BIN_ID }), record()],
companies
);
expect(targets.companies.map((c) => c.bin)).toEqual(['021040005165']);
expect(targets.skipped).toEqual([{ name: 'ТОО Без БИН', reason: 'в справочнике компаний нет БИН' }]);
});
it('компании нет в справочнике — тоже пропущена', () => {
const targets = resolveSigningTargets(
[record({ company: 'ТОО Неизвестная', companyId: 'ffffffff-0000-4000-8000-000000000000' })],
companies
);
expect(targets.companies).toEqual([]);
expect(targets.skipped[0].name).toBe('ТОО Неизвестная');
});
it('нет записи о сотруднике — понятная причина', () => {
expect(() => resolveSigningTargets([], companies)).toThrow('нет в кадровом справочнике');
});
it('нет ИИН в карточке', () => {
expect(() => resolveSigningTargets([record({ iin: ' ' })], companies)).toThrow('не указан ИИН');
});
it('ФИО из одного слова не годится', () => {
expect(() => resolveSigningTargets([record({ fio: 'Иванов' })], companies)).toThrow('Ф.И.О.');
});
it('ни в одной записи нет компании', () => {
expect(() => resolveSigningTargets([record({ companyId: '' })], companies)).toThrow('не указана компания');
});
});
describe('текст ошибки ЭДО', () => {
it('берёт русский detail из ProblemDetails', () => {
const body = JSON.stringify({ title: 'Bad Request', detail: 'Для компании «X» не загружен сертификат Sigex.' });
expect(describeEdoError(400, body)).toBe('Для компании «X» не загружен сертификат Sigex.');
});
it('title, если detail нет', () => {
expect(describeEdoError(400, JSON.stringify({ title: 'Подписант не найден' }))).toBe('Подписант не найден');
});
it('ошибки проверки собираются в одну строку', () => {
const body = JSON.stringify({ errors: { Iin: ['ИИН неверный'], Email: ['Email неверный', 'Слишком длинный'] } });
expect(describeEdoError(400, body)).toBe('ИИН неверный; Email неверный; Слишком длинный');
});
it('не JSON — по статусу', () => {
expect(describeEdoError(502, '<html>Bad gateway</html>')).toBe('ЭДО ответил 502');
expect(describeEdoError(401, '')).toContain('401');
expect(describeEdoError(403, '')).toContain('403');
});
});