feat: в чате заявки можно отметить тех, кому открыты «Все заявки» этого типа
Tests / frontend-test (pull_request) Successful in 2m4s
Tests / frontend-test (pull_request) Successful in 2m4s
Они открывают любую заявку типа и пишут в её чат — теперь попадают и в подсказку «@». Список берётся из админки approval-route (request-type-access), ФИО — из кадрового справочника. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,7 @@ export {
|
|||||||
getRequestTypeAccessEmails,
|
getRequestTypeAccessEmails,
|
||||||
resolveRoute
|
resolveRoute
|
||||||
} from './approvalRoute.service';
|
} from './approvalRoute.service';
|
||||||
export { hasRequestTypeAccess, accessibleAllRequestsTypes } from './requestTypeAccess';
|
export { hasRequestTypeAccess, accessibleAllRequestsTypes, emailsForType } from './requestTypeAccess';
|
||||||
export { useAccessibleAllRequestsFormKeys } from './useAccessibleAllRequestsFormKeys';
|
export { useAccessibleAllRequestsFormKeys } from './useAccessibleAllRequestsFormKeys';
|
||||||
export {
|
export {
|
||||||
initListPartitions,
|
initListPartitions,
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import { effectiveUserEmail } from '../impersonation';
|
|||||||
// кешируется, чтобы временный сбой сети не «залип» на весь сеанс.
|
// кешируется, чтобы временный сбой сети не «залип» на весь сеанс.
|
||||||
const cache = new Map<number, Promise<string[]>>();
|
const cache = new Map<number, Promise<string[]>>();
|
||||||
|
|
||||||
function emailsForType(context: WebPartContext, requestTypeId: number): Promise<string[]> {
|
/**
|
||||||
|
* Кому выдан доступ видеть ВСЕ заявки этого типа (раздел «Все заявки»). Кешируется
|
||||||
|
* на сеанс. Нужен не только для проверки доступа: эти люди могут писать в чат
|
||||||
|
* любой заявки типа, значит, их можно и отмечать в нём (см. shared/chat).
|
||||||
|
*/
|
||||||
|
export function emailsForType(context: WebPartContext, requestTypeId: number): Promise<string[]> {
|
||||||
let pending = cache.get(requestTypeId);
|
let pending = cache.get(requestTypeId);
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
pending = getRequestTypeAccessEmails(context, requestTypeId).catch((e) => {
|
pending = getRequestTypeAccessEmails(context, requestTypeId).catch((e) => {
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ import { IChatMessage } from './models/IChatMessage';
|
|||||||
import { buildCommentText, hasAttachments, toChatMessages } from './chatMessages';
|
import { buildCommentText, hasAttachments, toChatMessages } from './chatMessages';
|
||||||
import { chatFilesFolderUrl, uploadChatFiles } from './chatFiles.service';
|
import { chatFilesFolderUrl, uploadChatFiles } from './chatFiles.service';
|
||||||
import { IChatParticipant } from './chatMentions';
|
import { IChatParticipant } from './chatMentions';
|
||||||
|
import type { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||||
import { approvalServiceFor } from '../approval/approvalServiceFactory';
|
import { approvalServiceFor } from '../approval/approvalServiceFactory';
|
||||||
|
import { getRequestTypeIdByPageUrl } from '../approval/approvalRoute.service';
|
||||||
|
import { emailsForType } from '../approval/requestTypeAccess';
|
||||||
|
import { getEmployeeOrganizations } from '../dictionary';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Чат по заявке ПОВЕРХ штатных комментариев к строке списка.
|
* Чат по заявке ПОВЕРХ штатных комментариев к строке списка.
|
||||||
@@ -84,10 +88,34 @@ export interface IChatRequestInfo {
|
|||||||
code: string;
|
code: string;
|
||||||
title: string;
|
title: string;
|
||||||
initiator: IChatParticipant;
|
initiator: IChatParticipant;
|
||||||
/** Инициатор и все из маршрута (согласующие и исполнители), как есть — фильтрует buildParticipants. */
|
/**
|
||||||
|
* Инициатор, все из маршрута (согласующие и исполнители) и те, кому открыты
|
||||||
|
* «Все заявки» этого типа, — как есть, повторы убирает buildParticipants.
|
||||||
|
*/
|
||||||
people: IChatParticipant[];
|
people: IChatParticipant[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Люди с доступом ко «Всем заявкам» этого типа (админка approval-route): они
|
||||||
|
* открывают любую заявку типа и пишут в её чат, значит, их можно и отметить.
|
||||||
|
* ФИО — из кадрового справочника, не нашлось — показываем почту. Любой сбой —
|
||||||
|
* просто без них: отметки участников маршрута от этого не страдают.
|
||||||
|
*/
|
||||||
|
async function allRequestsViewers(context: WebPartContext, formKey: RequestTypeId): Promise<IChatParticipant[]> {
|
||||||
|
try {
|
||||||
|
const typeId = await getRequestTypeIdByPageUrl(context, formKey);
|
||||||
|
const emails = await emailsForType(context, typeId);
|
||||||
|
return await Promise.all(
|
||||||
|
emails.map(async (email) => {
|
||||||
|
const records = await getEmployeeOrganizations(context, email).catch(() => []);
|
||||||
|
return { name: records[0]?.fio || email, email };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Инициатор — автор строки списка данных: заявку создаёт он сам (подмена
|
* Инициатор — автор строки списка данных: заявку создаёт он сам (подмена
|
||||||
* личности на автора записи не влияет, см. RequestChat). Участники — строки
|
* личности на автора записи не влияет, см. RequestChat). Участники — строки
|
||||||
@@ -95,18 +123,20 @@ export interface IChatRequestInfo {
|
|||||||
* просто отметить можно только инициатора.
|
* просто отметить можно только инициатора.
|
||||||
*/
|
*/
|
||||||
export async function getChatRequestInfo(
|
export async function getChatRequestInfo(
|
||||||
|
context: WebPartContext,
|
||||||
formKey: RequestTypeId,
|
formKey: RequestTypeId,
|
||||||
itemId: number,
|
itemId: number,
|
||||||
listId: string | undefined
|
listId: string | undefined
|
||||||
): Promise<IChatRequestInfo> {
|
): Promise<IChatRequestInfo> {
|
||||||
const [item, matrix] = await Promise.all([
|
const [item, matrix, viewers] = await Promise.all([
|
||||||
resolveDataList(formKey, listId)
|
resolveDataList(formKey, listId)
|
||||||
.items.getById(itemId)
|
.items.getById(itemId)
|
||||||
.select('Title', 'RequestUniqueCode', 'Author/Title', 'Author/EMail')
|
.select('Title', 'RequestUniqueCode', 'Author/Title', 'Author/EMail')
|
||||||
.expand('Author')() as Promise<Record<string, unknown>>,
|
.expand('Author')() as Promise<Record<string, unknown>>,
|
||||||
approvalServiceFor(formKey, listId)
|
approvalServiceFor(formKey, listId)
|
||||||
.getMatrixForRequest(itemId)
|
.getMatrixForRequest(itemId)
|
||||||
.catch(() => [])
|
.catch(() => []),
|
||||||
|
allRequestsViewers(context, formKey)
|
||||||
]);
|
]);
|
||||||
const author = item.Author as { Title?: string; EMail?: string } | undefined;
|
const author = item.Author as { Title?: string; EMail?: string } | undefined;
|
||||||
const initiator = { name: author?.Title || '', email: author?.EMail || '' };
|
const initiator = { name: author?.Title || '', email: author?.EMail || '' };
|
||||||
@@ -114,6 +144,6 @@ export async function getChatRequestInfo(
|
|||||||
code: (item.RequestUniqueCode as string) || '',
|
code: (item.RequestUniqueCode as string) || '',
|
||||||
title: (item.Title as string) || '',
|
title: (item.Title as string) || '',
|
||||||
initiator,
|
initiator,
|
||||||
people: [initiator, ...matrix.map((r) => ({ name: r.who, email: r.email }))]
|
people: [initiator, ...matrix.map((r) => ({ name: r.who, email: r.email })), ...viewers]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function useRequestChat(
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let alive = true;
|
let alive = true;
|
||||||
setInfo(undefined);
|
setInfo(undefined);
|
||||||
getChatRequestInfo(formKey, itemId, listId)
|
getChatRequestInfo(context, formKey, itemId, listId)
|
||||||
.then((loaded) => {
|
.then((loaded) => {
|
||||||
if (alive) setInfo(loaded);
|
if (alive) setInfo(loaded);
|
||||||
})
|
})
|
||||||
@@ -77,7 +77,7 @@ export function useRequestChat(
|
|||||||
return () => {
|
return () => {
|
||||||
alive = false;
|
alive = false;
|
||||||
};
|
};
|
||||||
}, [formKey, itemId, listId]);
|
}, [context, formKey, itemId, listId]);
|
||||||
|
|
||||||
// В режиме проверки себя тоже можно отметить — отсекаем «себя» по пустому адресу.
|
// В режиме проверки себя тоже можно отметить — отсекаем «себя» по пустому адресу.
|
||||||
const participants = React.useMemo(
|
const participants = React.useMemo(
|
||||||
|
|||||||
Reference in New Issue
Block a user