fix: один согласующий не дублируется на шаге маршрута заявки в 1С
База 1С — мультивыбор, поэтому на одном шаге могут сработать сразу несколько строк матрицы с одним исполнителем (Гульжан и за ЗУП, и за АТП от финдепа). Он получал бы два уведомления и решал шаг дважды. Запланированный маршрут теперь оставляет одну строку на человека в пределах шага; один человек на разных шагах не схлопывается. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,7 @@ import { RequestSection } from './sections/RequestSection';
|
|||||||
import { ContactSection } from './sections/ContactSection';
|
import { ContactSection } from './sections/ContactSection';
|
||||||
import { useStableCallback } from '../../shared/useStableCallback';
|
import { useStableCallback } from '../../shared/useStableCallback';
|
||||||
import { applyManagerOverride, managerEmail } from './managerOverride';
|
import { applyManagerOverride, managerEmail } from './managerOverride';
|
||||||
|
import { dedupeRouteRows } from './dedupeRoute';
|
||||||
import { handoffToPlanner } from './plannerHandoff';
|
import { handoffToPlanner } from './plannerHandoff';
|
||||||
import { submitStatus } from './submitStatus';
|
import { submitStatus } from './submitStatus';
|
||||||
|
|
||||||
@@ -190,8 +191,10 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
|
|||||||
isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.WorkType && !!requestTypeId));
|
isOpenedRouteReady(editId, requestStatus, requestTypeId) && (!!editId || (!!data.WorkType && !!requestTypeId));
|
||||||
const loadMatrix = useCallback(
|
const loadMatrix = useCallback(
|
||||||
() =>
|
() =>
|
||||||
loadRoutePhase(approval, editId, requestStatus, 'Approval', () =>
|
loadRoutePhase(approval, editId, requestStatus, 'Approval', async () =>
|
||||||
getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, fieldValues)
|
dedupeRouteRows(
|
||||||
|
await getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, fieldValues)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
[context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId]
|
[context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId]
|
||||||
);
|
);
|
||||||
@@ -201,8 +204,10 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
|
|||||||
// в карточке Planner, в портале отмечает «Выполнено» (см. plannerHandoff.ts).
|
// в карточке Planner, в портале отмечает «Выполнено» (см. plannerHandoff.ts).
|
||||||
const loadExecutors = useCallback(
|
const loadExecutors = useCallback(
|
||||||
() =>
|
() =>
|
||||||
loadRoutePhase(approval, editId, requestStatus, 'Execution', () =>
|
loadRoutePhase(approval, editId, requestStatus, 'Execution', async () =>
|
||||||
getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, fieldValues)
|
dedupeRouteRows(
|
||||||
|
await getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, fieldValues)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
[context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId]
|
[context, userEmail, approval, editId, requestStatus, fieldValues, requestTypeId]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { IMatrixRow } from '../../shared/approval';
|
||||||
|
import { dedupeRouteRows } from './dedupeRoute';
|
||||||
|
|
||||||
|
function row(order: number, email: string): IMatrixRow {
|
||||||
|
return { order, who: email, email, position: '', status: '—', date: '', comment: '', allowReassign: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('dedupeRouteRows', () => {
|
||||||
|
it('схлопывает одного человека на одном шаге (ЗУП + АТП от финдепа)', () => {
|
||||||
|
const rows = [row(1, 'boss@tnsu.kz'), row(2, 'gulzhan@tnsu.kz'), row(2, 'Gulzhan@tnsu.kz')];
|
||||||
|
expect(dedupeRouteRows(rows).map((r) => r.email)).toEqual(['boss@tnsu.kz', 'gulzhan@tnsu.kz']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('оставляет разных людей на одном шаге', () => {
|
||||||
|
const rows = [row(2, 'gulzhan@tnsu.kz'), row(2, 'zhanat@tnsu.kz')];
|
||||||
|
expect(dedupeRouteRows(rows)).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('оставляет одного человека на разных шагах', () => {
|
||||||
|
const rows = [row(1, 'gulzhan@tnsu.kz'), row(2, 'gulzhan@tnsu.kz')];
|
||||||
|
expect(dedupeRouteRows(rows)).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('не трогает неназначенные строки', () => {
|
||||||
|
const rows = [row(2, ''), row(2, '')];
|
||||||
|
expect(dedupeRouteRows(rows)).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { IMatrixRow } from '../../shared/approval';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Убирает повторы одного и того же человека внутри одного шага маршрута.
|
||||||
|
*
|
||||||
|
* Зачем: «База 1С» — мультивыбор, и при нескольких базах на одном шаге могут
|
||||||
|
* сработать несколько строк матрицы с одним исполнителем (напр. Гульжан и за
|
||||||
|
* ЗУП, и за АТП от финдепа). Без схлопывания он получил бы две строки
|
||||||
|
* параллельного шага — два уведомления и два решения.
|
||||||
|
*
|
||||||
|
* Один человек на РАЗНЫХ шагах не схлопывается — это разные согласования.
|
||||||
|
* Строки без email (неназначенный шаг) не трогаем.
|
||||||
|
*/
|
||||||
|
export function dedupeRouteRows(rows: IMatrixRow[]): IMatrixRow[] {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
return rows.filter((row) => {
|
||||||
|
if (!row.email) return true;
|
||||||
|
const key = `${row.order}|${row.email.toLowerCase()}`;
|
||||||
|
if (seen.has(key)) return false;
|
||||||
|
seen.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user