Files
request-portal/src/webparts/requestPortal/shared/edo/signingTarget.ts
T
gorbenko.pavel 7f5f59fc81 feat: заявление на отпуск — по файлу на каждую компанию совместителя одним запросом в ЭДО
Сотрудник заполняет заявку один раз, а в ЭДО уходит по документу на каждую компанию, где он
работает: своя шапка (компания, должность) и свой БИН в массиве file_company_bins. Подписант один —
сам сотрудник, от имени физлица. Запрос один на все файлы: принимается целиком или отклоняется
целиком, повтор безопасен. Компания без БИН в справочнике пропускается и показывается в сообщении.

Удалять согласующих при составлении может редактор маршрута (во всех формах, не только в трёх).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 14:48:33 +05:00

88 lines
4.0 KiB
TypeScript

import { ICompany } from '../dictionary/models/ICompany';
import { IEmployeeOrganization } from '../dictionary/models/IEmployeeOrganization';
import { IFioParts, splitFio } from './fio';
/** Одна компания, от имени которой подписывается документ. */
export interface ISigningCompany {
/** БИН компании: от её имени документ идёт в Sigex. */
bin: string;
name: string;
/** Должность сотрудника именно в этой компании. */
position: string;
}
/** Компания сотрудника, по которой документ отправить нельзя, и почему. */
export interface ISkippedCompany {
name: string;
reason: string;
}
/** Кто подписывает и по каким компаниям. */
export interface ISigningTargets {
iin: string;
name: IFioParts;
/** По документу на каждую компанию, где у сотрудника есть БИН в справочнике. */
companies: ISigningCompany[];
/** Компании, по которым документ не отправить (нет БИН в справочнике и т. п.). */
skipped: ISkippedCompany[];
}
/**
* Определяет подписанта и компании по справочникам api.tnsu.kz: ИИН и ФИО — из Employee/ByEmail,
* БИН — из Dictionary/Companies по CompanyId.
*
* Сотрудник может работать в нескольких компаниях (на один e-mail — несколько записей), и заявление
* подписывается по каждой из них отдельно: у каждой компании свой документ со своей должностью.
* Одна и та же компания дважды не попадает. Компания без БИН не роняет остальные — она уходит в
* skipped с причиной. Ошибка бросается, только если подписать нельзя вовсе: нет человека, ИИН, ФИО
* или ни одной компании.
*/
export function resolveSigningTargets(records: IEmployeeOrganization[], companies: ICompany[]): ISigningTargets {
if (records.length === 0) {
throw new Error('сотрудника нет в кадровом справочнике — ИИН для подписания взять неоткуда');
}
// Человек один на все записи: ИИН и ФИО берём из первой, где они есть.
const iin = (records.find((r) => (r.iin || '').trim())?.iin || '').trim();
if (!iin) {
throw new Error('в кадровом справочнике у сотрудника не указан ИИН');
}
const fio = records.find((r) => (r.fio || '').trim())?.fio || '';
const name = splitFio(fio);
if (!name) {
throw new Error(`не удалось разобрать Ф.И.О. «${fio}» на фамилию и имя`);
}
const byId = new Map(companies.map((c) => [c.Id.toLowerCase(), c]));
const result: ISigningCompany[] = [];
const skipped: ISkippedCompany[] = [];
const seen = new Set<string>();
for (const record of records) {
if (!record.companyId) continue;
const company = byId.get(record.companyId.toLowerCase());
const bin = (company?.Bin || '').trim();
const companyName = company?.Name || record.company;
if (!bin) {
if (!seen.has(`name:${companyName}`)) {
seen.add(`name:${companyName}`);
skipped.push({ name: companyName, reason: 'в справочнике компаний нет БИН' });
}
continue;
}
if (seen.has(bin)) continue;
seen.add(bin);
result.push({ bin, name: companyName, position: record.position });
}
if (result.length === 0 && skipped.length === 0) {
throw new Error('в карточке сотрудника не указана компания');
}
return { iin, name, companies: result, skipped };
}