diff --git a/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx b/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx
index 16b439d..1fdbe95 100644
--- a/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx
+++ b/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx
@@ -8,8 +8,8 @@ import {
getLinkedTypes,
getOwnRequests,
getOwnRequestsByStatus,
+ hydrateDecisionComments,
hydratePendingAuthors,
- hydrateReturnComments,
searchOwnRequests,
} from './myRequests.service';
import { IMyRequest } from './models/IMyRequest';
@@ -117,26 +117,45 @@ function statusOptionClassName(status: string): string {
// его в фильтре, и колонка вырождается в тот же столбец одинаковых плашек,
// поэтому она зависит не от одного вида, а ещё и от фильтра.
//
-// «Причина возврата» — там, где показаны ТОЛЬКО возвращённые заявки: для них
-// это главное, ради чего в список и заходят (что именно просят исправить).
+// Статус решения, ОБЩИЙ для всех строк раздела, — если он есть, показывать его
+// колонкой незачем (столбец одинаковых плашек), зато полезен комментарий
+// согласующего: почему вернули или почему отклонили. Именно за этим в такие
+// списки и заходят.
+function decisionStatusFor(view: ListView, statusFilter: string): string | undefined {
+ if (view === 'rework') return REQUEST_STATUS.returned;
+ if (view === 'own:rejected') return REQUEST_STATUS.rejected;
+ if (
+ view === 'own:created' &&
+ (statusFilter === REQUEST_STATUS.returned || statusFilter === REQUEST_STATUS.rejected)
+ ) {
+ return statusFilter;
+ }
+ return undefined;
+}
+
interface ITableColumns {
author: boolean;
status: boolean;
- returnComment: boolean;
+ /** Заголовок колонки с комментарием; undefined — колонки нет. */
+ commentLabel?: string;
}
function columnsFor(view: ListView, statusFilter: string): ITableColumns {
- const returnedOnly =
- view === 'rework' || (view === 'own:created' && statusFilter === REQUEST_STATUS.returned);
+ const decisionStatus = decisionStatusFor(view, statusFilter);
+ const commentLabel =
+ decisionStatus === REQUEST_STATUS.rejected
+ ? 'Причина отклонения'
+ : decisionStatus === REQUEST_STATUS.returned
+ ? 'Причина возврата'
+ : undefined;
- if (view === 'approvals' || view === 'execution') {
- return { author: true, status: false, returnComment: false };
- }
- if (view === 'rework') return { author: false, status: false, returnComment: true };
- if (view === 'own:created') {
- return { author: false, status: !statusFilter, returnComment: returnedOnly };
- }
- return { author: true, status: true, returnComment: false };
+ if (view === 'approvals' || view === 'execution') return { author: true, status: false };
+ if (view === 'rework') return { author: false, status: false, commentLabel };
+ if (view === 'own:created') return { author: false, status: !statusFilter, commentLabel };
+ // История решений: заявка чужая, поэтому нужен автор. Статус — только если он
+ // не предопределён самой вкладкой («Отклонённые мной» — это всегда «Отклонено»,
+ // а вот у согласованных/выполненных заявка могла уйти дальше по маршруту).
+ return { author: true, status: !decisionStatus, commentLabel };
}
export interface IRequestsTableViewProps {
@@ -188,7 +207,7 @@ const Table: React.FC<{
Дата |
{columns.author && Автор | }
{columns.status && Статус | }
- {columns.returnComment && Причина возврата | }
+ {columns.commentLabel && {columns.commentLabel} | }
@@ -221,9 +240,9 @@ const Table: React.FC<{
{r.status ? : '—'}
)}
- {columns.returnComment && (
+ {columns.commentLabel && (
- {r.returnComment || Без комментария}
+ {r.decisionComment || Без комментария}
|
)}
@@ -341,18 +360,12 @@ export const RequestsTableView: React.FC = ({
const q = debouncedQuery.trim();
- const fetchPage = async (): Promise => {
+ const fetchRows = async (): Promise => {
// «Доработка» — тот же список, что «Инициированные», просто со статусом,
// зашитым в раздел, а не выбранным в фильтре.
if (view === 'rework' || (view === 'own:created' && statusFilter)) {
const status = view === 'rework' ? REQUEST_STATUS.returned : statusFilter;
- const result = await getOwnRequestsByStatus(context, types, status, page, debouncedQuery);
- // Причина возврата живёт в строке маршрута — дочитываем только для
- // видимой страницы и только там, где колонка показывается.
- if (status === REQUEST_STATUS.returned) {
- await hydrateReturnComments(context, types, result.rows);
- }
- return result;
+ return getOwnRequestsByStatus(context, types, status, page, debouncedQuery);
}
if (view === 'own:created') {
@@ -378,6 +391,19 @@ export const RequestsTableView: React.FC = ({
return { rows: pageRows, totalRows: filtered.length };
};
+ // Комментарий к решению дочитываем ОДНИМ местом на все ветки: «Доработка» и
+ // «Отклонённые мной» приходят разными путями (по автору и по строкам
+ // маршрута соответственно), а колонка у них одинаковая по смыслу. И только
+ // для видимой страницы — как и автор в hydratePendingAuthors.
+ const fetchPage = async (): Promise => {
+ const result = await fetchRows();
+ const decisionStatus = decisionStatusFor(view, statusFilter);
+ if (decisionStatus) {
+ await hydrateDecisionComments(context, types, result.rows, decisionStatus);
+ }
+ return result;
+ };
+
const load = async (): Promise => {
try {
const fresh = await fetchPage();
diff --git a/src/webparts/requestPortal/features/myRequests/models/IMyRequest.ts b/src/webparts/requestPortal/features/myRequests/models/IMyRequest.ts
index b07f51d..d53c5ba 100644
--- a/src/webparts/requestPortal/features/myRequests/models/IMyRequest.ts
+++ b/src/webparts/requestPortal/features/myRequests/models/IMyRequest.ts
@@ -23,9 +23,10 @@ export interface IMyRequest {
*/
myStepStatus?: string;
/**
- * Комментарий согласующего, вернувшего заявку на доработку. Живёт не в списке
- * данных, а в строке МАРШРУТА, поэтому дочитывается отдельно и только там,
- * где нужен (см. hydrateReturnComments).
+ * Комментарий согласующего к решению — причина возврата на доработку или
+ * отклонения (какое именно решение, задаёт вызывающая сторона). Живёт не в
+ * списке данных, а в строке МАРШРУТА, поэтому дочитывается отдельно и только
+ * там, где показывается (см. hydrateDecisionComments).
*/
- returnComment?: string;
+ decisionComment?: string;
}
diff --git a/src/webparts/requestPortal/features/myRequests/myRequests.service.ts b/src/webparts/requestPortal/features/myRequests/myRequests.service.ts
index 09ebc51..fa24ad2 100644
--- a/src/webparts/requestPortal/features/myRequests/myRequests.service.ts
+++ b/src/webparts/requestPortal/features/myRequests/myRequests.service.ts
@@ -801,22 +801,29 @@ async function approvalListFor(
// Сколько строк маршрута максимум забираем за раз при дочитывании комментариев:
// на странице ≤20 заявок, у каждой обычно единицы шагов — запас многократный.
-const RETURN_COMMENT_ROW_LIMIT = 2000;
+const DECISION_COMMENT_ROW_LIMIT = 2000;
/**
- * Проставляет returnComment — комментарий согласующего, вернувшего заявку на
- * доработку. Причина возврата хранится в СТРОКЕ МАРШРУТА (колонка Comment
- * списка «— согласование»), а не в самой заявке, поэтому её нельзя получить
- * тем же запросом, что и список: нужен отдельный проход по спискам согласования.
+ * Проставляет decisionComment — комментарий согласующего к его решению по
+ * заявке: причину возврата на доработку либо отклонения (что именно искать,
+ * задаёт decisionStatus). Комментарий хранится в СТРОКЕ МАРШРУТА (колонка
+ * Comment списка «— согласование»), а не в самой заявке, поэтому его нельзя
+ * получить тем же запросом, что и список: нужен отдельный проход по спискам
+ * согласования.
+ *
+ * Одного статуса достаточно, чтобы найти нужную строку, и для «своих
+ * возвращённых» (вернул кто-то другой), и для «отклонённых мной»: и возврат, и
+ * отклонение останавливают маршрут, поэтому такая строка по заявке одна.
*
* Вызывается ТОЛЬКО там, где причина показывается (см. RequestsTableView) —
* счётчику в меню, например, она не нужна, и лишние запросы ему ни к чему.
* Мутирует переданные строки — как и hydrateAuthors рядом.
*/
-export async function hydrateReturnComments(
+export async function hydrateDecisionComments(
context: WebPartContext,
types: IRequestType[],
- rows: IMyRequest[]
+ rows: IMyRequest[],
+ decisionStatus: string
): Promise {
if (rows.length === 0) return;
@@ -844,27 +851,28 @@ export async function hydrateReturnComments(
const stepRows = (await approvalList.items
.filter(typeRows.map((r) => `TicketId eq ${r.id}`).join(' or '))
.select('TicketId', 'ApprovalStatus', 'Comment')
- .top(RETURN_COMMENT_ROW_LIMIT)()) as Array>;
+ .top(DECISION_COMMENT_ROW_LIMIT)()) as Array>;
const commentByTicket = new Map();
stepRows.forEach((r) => {
- if (r.ApprovalStatus !== REQUEST_STATUS.returned) return;
+ if (r.ApprovalStatus !== decisionStatus) return;
const ticketId = r.TicketId as number | undefined;
const comment = ((r.Comment as string) || '').trim();
- // Возврат по заявке один (при переотправке маршрут переписывается
- // целиком, см. writeRoute) — но если строк вдруг несколько, берём
- // первую непустую, а не последнюю пустую.
+ // Такая строка по заявке одна (и возврат, и отклонение останавливают
+ // маршрут, а при переотправке он переписывается целиком — см.
+ // writeRoute), но если их вдруг несколько, берём первую НЕПУСТУЮ,
+ // а не последнюю пустую.
if (ticketId && comment && !commentByTicket.has(ticketId)) {
commentByTicket.set(ticketId, comment);
}
});
typeRows.forEach((r) => {
- r.returnComment = commentByTicket.get(r.id);
+ r.decisionComment = commentByTicket.get(r.id);
});
} catch (e) {
console.error(
- `[Текущие заявки] «${type.name}»: не удалось прочитать причину возврата:`,
+ `[Текущие заявки] «${type.name}»: не удалось прочитать комментарий к решению:`,
(e as Error).message
);
}
diff --git a/src/webparts/requestPortal/features/myRequests/pageCache.test.ts b/src/webparts/requestPortal/features/myRequests/pageCache.test.ts
index 153ad1c..26c4421 100644
--- a/src/webparts/requestPortal/features/myRequests/pageCache.test.ts
+++ b/src/webparts/requestPortal/features/myRequests/pageCache.test.ts
@@ -99,15 +99,15 @@ describe('pageSignature', () => {
expect(pageSignature(before)).toBe(pageSignature(after));
});
- it('изменение причины возврата меняет подпись — она видна в таблице «Доработка»', () => {
- const before = page({ rows: [request({ returnComment: 'Исправьте должность' })] });
- const after = page({ rows: [request({ returnComment: 'Приложите скан' })] });
+ it('изменение комментария к решению меняет подпись — он показан колонкой «Причина возврата/отклонения»', () => {
+ const before = page({ rows: [request({ decisionComment: 'Исправьте должность' })] });
+ const after = page({ rows: [request({ decisionComment: 'Приложите скан' })] });
expect(pageSignature(before)).not.toBe(pageSignature(after));
});
- it('появление причины возврата у строки без неё тоже меняет подпись', () => {
+ it('появление комментария к решению у строки без него тоже меняет подпись', () => {
const before = page({ rows: [request()] });
- const after = page({ rows: [request({ returnComment: 'Исправьте должность' })] });
+ const after = page({ rows: [request({ decisionComment: 'Исправьте должность' })] });
expect(pageSignature(before)).not.toBe(pageSignature(after));
});
diff --git a/src/webparts/requestPortal/features/myRequests/pageCache.ts b/src/webparts/requestPortal/features/myRequests/pageCache.ts
index 52d1569..7d0fbb9 100644
--- a/src/webparts/requestPortal/features/myRequests/pageCache.ts
+++ b/src/webparts/requestPortal/features/myRequests/pageCache.ts
@@ -39,7 +39,7 @@ export function writePage(key: string, page: IPage): void {
export function pageSignature(page: IPage): string {
return `${page.totalRows}|${page.rows
.map((r) =>
- [r.requestTypeId, r.id, r.title, r.status, r.created, r.authorName, r.returnComment ?? ''].join('~')
+ [r.requestTypeId, r.id, r.title, r.status, r.created, r.authorName, r.decisionComment ?? ''].join('~')
)
.join(';')}`;
}
diff --git a/src/webparts/requestPortal/shared/routing.ts b/src/webparts/requestPortal/shared/routing.ts
index 6496c66..6793eac 100644
--- a/src/webparts/requestPortal/shared/routing.ts
+++ b/src/webparts/requestPortal/shared/routing.ts
@@ -60,6 +60,8 @@ export interface IRoute {
ownStatus?: string;
/** Номер страницы таблицы (1-based). 1 — значение по умолчанию, в адрес не пишется. */
page?: number;
+ /** Текст поиска по таблице. Пустой — значение по умолчанию, в адрес не пишется. */
+ query?: string;
}
const ROUTE_CHANGE_EVENT = 'rp-route-change';