diff --git a/src/webparts/requestPortal/features/myRequests/myRequests.service.ts b/src/webparts/requestPortal/features/myRequests/myRequests.service.ts index 39f33e9..99b606c 100644 --- a/src/webparts/requestPortal/features/myRequests/myRequests.service.ts +++ b/src/webparts/requestPortal/features/myRequests/myRequests.service.ts @@ -38,18 +38,22 @@ const OWN_REST_TOP = 200; // ApproverOWSUSER в схеме поиска не находилось (разбирались 2026-08-06). // Суффикс — тип исходного поля: OWSUSER для Person/Group. const MP_APPROVER = 'TansuSiteApproverOWSUSER'; -// Статус строки маршрута — Choice-поле, отсюда суффикс OWSCHCS (подтверждено -// отладочным перебором 2026-08-06; у Person-полей он OWSUSER, см. MP_APPROVER). -const MP_STATUS = 'RequestStatusOWSCHCS'; +// Статус СТРОКИ МАРШРУТА (колонка ApprovalStatus списка «— согласование») — +// Choice-поле, отсюда суффикс OWSCHCS (подтверждено отладочным перебором +// 2026-08-06; у Person-полей он OWSUSER, см. MP_APPROVER). Колонку не путать с +// RequestStatus списка ДАННЫХ — это статус ЗАЯВКИ целиком, у него своя +// managed property (см. MP_TICKET_STATUS ниже). +const MP_STATUS = 'ApprovalStatusOWSCHCS'; -// То же самое для Choice-колонки «Статус» списка ДАННЫХ (внутреннее имя Status, -// см. forms/*/provisioning.json) — по ней отбираются «Доработка» и фильтр по -// статусу в «Моих заявках». Кириллица в значении проблемы не создаёт: KQL — не -// $filter (там она действительно подводит, см. indexApprovalsForType), и по -// MP_STATUS выше точно так же ищутся русские статусы строк маршрута. +// То же самое для Choice-колонки «Статус» списка ДАННЫХ (внутреннее имя +// RequestStatus, см. forms/*/provisioning.json) — по ней отбираются +// «Доработка» и фильтр по статусу в «Моих заявках». Кириллица в значении +// проблемы не создаёт: KQL — не $filter (там она действительно подводит, см. +// indexApprovalsForType), и по MP_STATUS выше точно так же ищутся русские +// статусы строк маршрута. // Если отбор по статусу вдруг начнёт стабильно возвращать 0 — проверять надо в // первую очередь ЭТО имя в схеме поиска (см. предупреждение в searchOwnRequests). -const MP_TICKET_STATUS = 'StatusOWSCHCS'; +const MP_TICKET_STATUS = 'RequestStatusOWSCHCS'; export function currentUserEmail(context: WebPartContext): string { return (context.pageContext.user.email || '').toLowerCase(); @@ -99,11 +103,11 @@ const MY_TURN_STATUSES: string[] = [REQUEST_STATUS.onApproval, REQUEST_STATUS.on // (иначе на клиенте нечем было посчитать «чья очередь»). Теперь очередь // проставляется при создании/решении (см. approvalList.service.ts/createSteps, // approverDecision.service.ts/decide) — «чья сейчас очередь» это просто -// значение колонки Status, поэтому здесь достаточно ОДНОГО отфильтрованного -// запроса: ApproverId (+ Status для 'myTurn'). Список согласования больше НЕ -// сканируется целиком, независимо от его размера. +// значение колонки ApprovalStatus, поэтому здесь достаточно ОДНОГО +// отфильтрованного запроса: ApproverId (+ ApprovalStatus для 'myTurn'). +// Список согласования больше НЕ сканируется целиком, независимо от его размера. // -// Заявку (Title/Status/Created) читаем сразу через expand по Lookup-полю +// Заявку (Title/RequestStatus/Created) читаем сразу через expand по Lookup-полю // Ticket — отдельный запрос к списку данных нужен только за Author // (двухуровневый expand Ticket/Author у SharePoint REST ненадёжен), и то — // ТОЛЬКО для строк текущей страницы (см. hydrateAuthors ниже), а не для всего @@ -227,7 +231,7 @@ export async function hydratePendingAuthors( } // Лёгкий индекс совпадающих заявок для одного типа: строки согласования по -// ApproverId → Title/Status/Created заявки отдельным запросом к списку ДАННЫХ +// ApproverId → Title/RequestStatus/Created заявки отдельным запросом к списку ДАННЫХ // (НЕ через expand по Lookup-полю Ticket — SharePoint REST не отдаёт так // произвольные поля связанного элемента, только через Person-поля вроде // Author; попытка `expand('Ticket').select('Ticket/Status', ...)` падает с 400). @@ -267,8 +271,8 @@ async function indexApprovalsForType( // SharePoint REST — резолвим email в числовой Id пользователя (как и везде в // проекте, см. ensureUser) и фильтруем по TansuSiteApproverId. // - // RequestStatus в $filter НЕ используем: подтверждено на практике — фильтр - // вида `RequestStatus eq 'На согласовании'` (кириллица в $filter) молча + // ApprovalStatus в $filter НЕ используем: подтверждено на практике — фильтр + // вида `ApprovalStatus eq 'На согласовании'` (кириллица в $filter) молча // возвращает 0 строк, даже когда без него те же строки находятся с этим же // значением статуса. Поэтому фильтруем только по TansuSiteApproverId // (числовое значение, проблем нет), а статус сравниваем в JS после @@ -289,7 +293,7 @@ async function indexApprovalsForType( const cutoffIso = new Date(Date.now() - RECENT_MS).toISOString(); const recentRows = (await approvalList.items .filter(`TansuSiteApproverId eq ${approver.Id} and Modified ge '${cutoffIso}'`) - .select('Id', 'TicketId', 'RequestStatus') + .select('Id', 'TicketId', 'ApprovalStatus') .top(2000)()) as Array>; // Статусы уходят в KQL: сервер сразу отдаёт только нужные строки, @@ -300,7 +304,7 @@ async function indexApprovalsForType( // REST-довесок берётся по Modified без учёта статуса (кириллица в $filter // ненадёжна, см. выше) — отбор по статусу делаем здесь, на клиенте. - stepRows = allRows.filter((r) => statuses.indexOf(r.RequestStatus as string) !== -1); + stepRows = allRows.filter((r) => statuses.indexOf(r.ApprovalStatus as string) !== -1); } else { // 'involved' — используется ТОЛЬКО для текстового поиска (см. // getPendingApprovalIndex/searchMyRequests) — здесь важна не мгновенная @@ -328,7 +332,7 @@ async function indexApprovalsForType( stepRows.forEach((r) => { const ticketId = r.TicketId as number | undefined; if (ticketId && !stepStatusByTicket.has(ticketId)) { - stepStatusByTicket.set(ticketId, (r.RequestStatus as string) || ''); + stepStatusByTicket.set(ticketId, (r.ApprovalStatus as string) || ''); } }); if (ticketIds.length === 0) { @@ -341,7 +345,7 @@ async function indexApprovalsForType( const ticketFilter = ticketIds.map((id) => `Id eq ${id}`).join(' or '); const ticketRows = (await list.items .filter(ticketFilter) - .select('Id', 'Title', 'Status', 'Created') + .select('Id', 'Title', 'RequestStatus', 'Created') .top(ticketIds.length)()) as Array>; const rows = ticketRows.map((r) => ({ @@ -430,7 +434,7 @@ async function getInvolvedRowsViaSearch( return (await approvalList.items .filter(ids.map((id) => `Id eq ${id}`).join(' or ')) - .select('Id', 'TicketId', 'RequestStatus') + .select('Id', 'TicketId', 'ApprovalStatus') .top(ids.length)()) as Array>; } catch (e) { console.error( @@ -573,7 +577,7 @@ async function loadByIds( const rows = (await sp.web .getList(type.listId as string) .items.filter(ids.map((id) => `Id eq ${id}`).join(' or ')) - .select('Id', 'Title', 'Status', 'Created', 'Author/EMail', 'Author/Title') + .select('Id', 'Title', 'RequestStatus', 'Created', 'Author/EMail', 'Author/Title') .expand('Author') .top(ids.length)()) as Array>; rows.forEach((r) => { @@ -599,7 +603,7 @@ async function getOwnRowsForType(context: WebPartContext, type: IRequestType): P const rows = (await sp.web .getList(type.listId as string) .items.filter(`AuthorId eq ${me.Id}`) - .select('Id', 'Title', 'Status', 'Created', 'Author/EMail', 'Author/Title') + .select('Id', 'Title', 'RequestStatus', 'Created', 'Author/EMail', 'Author/Title') .expand('Author') .orderBy('Created', false) .top(OWN_REST_TOP)()) as Array>; @@ -861,7 +865,7 @@ function toMyRequest( return { id: r.Id as number, title: (r.Title as string) || '', - status: (r.Status as string) || '', + status: (r.RequestStatus as string) || '', authorEmail: author?.EMail || '', authorName: author?.Title || '', created: (r.Created as string) || '', diff --git a/src/webparts/requestPortal/forms/RecruitingHire/approval/provisioning.json b/src/webparts/requestPortal/forms/RecruitingHire/approval/provisioning.json index 45a8b4f..90b6141 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/approval/provisioning.json +++ b/src/webparts/requestPortal/forms/RecruitingHire/approval/provisioning.json @@ -2,7 +2,7 @@ "dataListTitle": "Прием на работу — согласование", "columns": [ { "internalName": "TansuSiteApprover", "title": "Согласующий", "type": "User", "userSelectionMode": "PeopleOnly" }, - { "internalName": "RequestStatus", "title": "Статус", "type": "Choice", "choices": ["Черновик", "Ждёт очереди", "На согласовании", "На исполнении", "Согласовано", "Выполнено", "Отклонено", "Возвращена на доработку"] }, + { "internalName": "ApprovalStatus", "title": "Статус", "type": "Choice", "choices": ["Черновик", "Ждёт очереди", "На согласовании", "На исполнении", "Согласовано", "Выполнено", "Отклонено", "Возвращена на доработку"] }, { "internalName": "Ticket", "title": "Заявка", "type": "Lookup", "lookupList": "Прием на работу — данные", "lookupShowField": "Title" }, { "internalName": "Date", "title": "Дата", "type": "DateTime" }, { "internalName": "Comment", "title": "Комментарий", "type": "Note" }, diff --git a/src/webparts/requestPortal/forms/RecruitingHire/load.ts b/src/webparts/requestPortal/forms/RecruitingHire/load.ts index b8d21fb..8a69434 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/load.ts +++ b/src/webparts/requestPortal/forms/RecruitingHire/load.ts @@ -21,7 +21,7 @@ export async function loadHireRequest(itemId: number): Promise { const item = (await sp.web.lists .getByTitle(LIST_TITLE) .items.getById(itemId) - .select(...SCALAR_FIELDS, 'Status')()) as Record; + .select(...SCALAR_FIELDS, 'RequestStatus')()) as Record; const data: IFormData = { ...EMPTY_DATA }; const writable = data as unknown as Record; @@ -37,5 +37,5 @@ export async function loadHireRequest(itemId: number): Promise { } }); - return { data, status: (item.Status as string) || '' }; + return { data, status: (item.RequestStatus as string) || '' }; } diff --git a/src/webparts/requestPortal/forms/RecruitingHire/provisioning.json b/src/webparts/requestPortal/forms/RecruitingHire/provisioning.json index ec43431..9032984 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/provisioning.json +++ b/src/webparts/requestPortal/forms/RecruitingHire/provisioning.json @@ -2,7 +2,7 @@ "dataListTitle": "Прием на работу — данные", "columns": [ { "internalName": "RequestUniqueCode", "title": "Уникальный код заявки", "type": "Text" }, - { "internalName": "Status", "title": "Статус", "type": "Choice", "choices": ["Черновик", "На согласовании", "Согласовано", "Отклонено", "Возвращена на доработку", "Выполнение", "Закрыта"] }, + { "internalName": "RequestStatus", "title": "Статус", "type": "Choice", "choices": ["Черновик", "На согласовании", "Согласовано", "Отклонено", "Возвращена на доработку", "Выполнение", "Закрыта"] }, { "internalName": "Phone", "title": "Телефон", "type": "Text" }, { "internalName": "HireType", "title": "Тип приёма", "type": "Choice", "choices": ["Новый сотрудник", "Вместо уволенного"] }, diff --git a/src/webparts/requestPortal/forms/RecruitingHire/submit.ts b/src/webparts/requestPortal/forms/RecruitingHire/submit.ts index 0014161..2a4b257 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/submit.ts +++ b/src/webparts/requestPortal/forms/RecruitingHire/submit.ts @@ -23,7 +23,7 @@ function buildTitle(data: IFormData): string { /** Поля элемента списка данных из модели формы (текст/выбор/даты/числа). */ function buildItem(data: IFormData, status: string): Record { - const item: Record = { Title: buildTitle(data), Status: status }; + const item: Record = { Title: buildTitle(data), RequestStatus: status }; (Object.keys(data) as (keyof IFormData)[]).forEach((key) => { const value = data[key]; diff --git a/src/webparts/requestPortal/forms/WorkplaceSetup/approval/provisioning.json b/src/webparts/requestPortal/forms/WorkplaceSetup/approval/provisioning.json index e9b0968..2ab403f 100644 --- a/src/webparts/requestPortal/forms/WorkplaceSetup/approval/provisioning.json +++ b/src/webparts/requestPortal/forms/WorkplaceSetup/approval/provisioning.json @@ -2,7 +2,7 @@ "dataListTitle": "Организация рабочего места — согласование", "columns": [ { "internalName": "TansuSiteApprover", "title": "Согласующий", "type": "User", "userSelectionMode": "PeopleOnly" }, - { "internalName": "RequestStatus", "title": "Статус", "type": "Choice", "choices": ["Черновик", "Ждёт очереди", "На согласовании", "На исполнении", "Согласовано", "Выполнено", "Отклонено", "Возвращена на доработку"] }, + { "internalName": "ApprovalStatus", "title": "Статус", "type": "Choice", "choices": ["Черновик", "Ждёт очереди", "На согласовании", "На исполнении", "Согласовано", "Выполнено", "Отклонено", "Возвращена на доработку"] }, { "internalName": "Ticket", "title": "Заявка", "type": "Lookup", "lookupList": "Организация рабочего места — данные", "lookupShowField": "Title" }, { "internalName": "Date", "title": "Дата", "type": "DateTime" }, { "internalName": "Comment", "title": "Комментарий", "type": "Note" }, diff --git a/src/webparts/requestPortal/forms/WorkplaceSetup/load.ts b/src/webparts/requestPortal/forms/WorkplaceSetup/load.ts index 0505621..0ac02aa 100644 --- a/src/webparts/requestPortal/forms/WorkplaceSetup/load.ts +++ b/src/webparts/requestPortal/forms/WorkplaceSetup/load.ts @@ -18,7 +18,7 @@ export async function loadWorkplaceRequest(itemId: number): Promise; + .select(...SCALAR_FIELDS, 'NeedsEquipment', 'RequestStatus')()) as Record; const data: IFormData = { ...EMPTY_DATA }; const writable = data as unknown as Record; @@ -36,5 +36,5 @@ export async function loadWorkplaceRequest(itemId: number): Promise { - const item: Record = { Title: buildTitle(data), Status: status }; + const item: Record = { Title: buildTitle(data), RequestStatus: status }; (Object.keys(data) as (keyof IFormData)[]).forEach((key) => { const value = data[key]; diff --git a/src/webparts/requestPortal/shared/approval/approvalList.service.ts b/src/webparts/requestPortal/shared/approval/approvalList.service.ts index 6e504a8..d77b7de 100644 --- a/src/webparts/requestPortal/shared/approval/approvalList.service.ts +++ b/src/webparts/requestPortal/shared/approval/approvalList.service.ts @@ -12,12 +12,18 @@ import { APPROVAL_PHASE, REQUEST_STATUS } from './models/statuses'; // Внутренние имена колонок списка согласования (создаются провижинингом). const FIELDS = { - // Внутренние имена с префиксом группы колонок сайта (TansuSite) и «Request» — - // так они заведены в SharePoint. Из них SharePoint выводит имена managed - // properties для поиска: TansuSiteApproverOWSUSER и RequestStatusOWSCHCS - // (см. MP_APPROVER/MP_STATUS в features/myRequests/myRequests.service.ts). + // Внутренние имена с префиксом группы колонок сайта (TansuSite) там, где он + // есть, — так они заведены в SharePoint. Из них SharePoint выводит имена + // managed properties для поиска: TansuSiteApproverOWSUSER и + // ApprovalStatusOWSCHCS (см. MP_APPROVER/MP_STATUS в + // features/myRequests/myRequests.service.ts). + // + // ВАЖНО: колонка называется ApprovalStatus, а не Status и не RequestStatus — + // это статус СТРОКИ маршрута (список «— согласование»); статус ЗАЯВКИ в + // списке «— данных» лежит в отдельной колонке RequestStatus (см. + // models/statuses.ts — там оба набора значений сведены в одну картину). approver: 'TansuSiteApprover', // «Согласующий» (User) → TansuSiteApproverId - status: 'RequestStatus', // «Статус» (Choice) + status: 'ApprovalStatus', // «Статус» (Choice) строки маршрута ticket: 'Ticket', // «Заявка» (Lookup) → TicketId date: 'Date', // «Дата» (DateTime) comment: 'Comment', // «Комментарий» (Text) diff --git a/src/webparts/requestPortal/shared/approval/approverDecision.service.ts b/src/webparts/requestPortal/shared/approval/approverDecision.service.ts index 40dcd4b..78d1c89 100644 --- a/src/webparts/requestPortal/shared/approval/approverDecision.service.ts +++ b/src/webparts/requestPortal/shared/approval/approverDecision.service.ts @@ -127,7 +127,7 @@ export class ApproverService { private async updateRequestStatus(id: number, status: string): Promise { await getSP().web.lists.getByTitle(this.dataListTitle).items.getById(id).update({ - Status: status, + RequestStatus: status, }); } } diff --git a/src/webparts/requestPortal/shared/approval/models/statuses.ts b/src/webparts/requestPortal/shared/approval/models/statuses.ts index 8aa551c..95c484b 100644 --- a/src/webparts/requestPortal/shared/approval/models/statuses.ts +++ b/src/webparts/requestPortal/shared/approval/models/statuses.ts @@ -45,16 +45,21 @@ export function isEditableStatus(status: string | undefined): boolean { // Наборов значений ДВА, и они не совпадают — это разные сущности: // -// 1) статус ЗАЯВКИ — колонка Status в списке «… — данные». Пишется формой -// (Черновик/На согласовании) и computeRequestStatus после каждого решения. -// «Ждёт очереди»/«На исполнении»/«Выполнено» здесь не бывают: это состояния -// отдельного шага маршрута, а не заявки целиком. +// 1) статус ЗАЯВКИ — колонка RequestStatus в списке «… — данные». Пишется +// формой (Черновик/На согласовании) и computeRequestStatus после каждого +// решения. «Ждёт очереди»/«На исполнении»/«Выполнено» здесь не бывают: это +// состояния отдельного шага маршрута, а не заявки целиком. // -// 2) статус СТРОКИ МАРШРУТА — колонка RequestStatus в списке «… — согласование». +// 2) статус СТРОКИ МАРШРУТА — колонка ApprovalStatus в списке «… — согласование». // Пишется createSteps/activateNextQueued/setDecision. «Выполнение»/«Закрыта» // здесь не бывают: это агрегатные состояния заявки, у отдельного шага // смысла не имеют. // +// Обе колонки переименованы (были Status в списке данных и RequestStatus в +// списке согласования соответственно) — переименование внутреннего имени в +// SharePoint не трогает уже сохранённые значения, отдельная миграция данных +// не нужна. +// // Держать их в синхроне с forms/*/provisioning.json и forms/*/approval/provisioning.json. export const REQUEST_STATUS_CHOICES: string[] = [ REQUEST_STATUS.draft,