88ce1d498e
Tests / frontend-test (push) Successful in 1m24s
Заявка заказывалась свободным текстом, без привязки к реальным рецептам завода. Теперь БСУ выбирается из API bsu.tnsu.kz (BsuId/BsuName в шапке), а класс смеси в позиции — из рецептов этого БСУ (formulaId + снимок состава, т.к. на заводе рецепт могут поменять). Состав показывается плашками. Если API не ответил, отправку не блокируем — ручной ввод. Новые колонки BsuId/BsuName нужно провизионировать до деплоя. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
252 lines
16 KiB
TypeScript
252 lines
16 KiB
TypeScript
/**
|
||
* Сайт SharePoint, где живут списки данных портала.
|
||
* Портал (web part) может размещаться на другом сайте (например, на главной),
|
||
* поэтому клиент PnPjs всегда нацеливаем на этот адрес явно.
|
||
*
|
||
* TODO: при необходимости вынести в свойство web part (property pane),
|
||
* чтобы не хардкодить и переключать между dev/prod-сайтами.
|
||
*/
|
||
export const DATA_SITE_URL = 'https://tnsukz.sharepoint.com/sites/requests';
|
||
|
||
/**
|
||
* Инструкция пользователя по порталу (docx на сайте базы знаний) — ссылка в
|
||
* боковом меню (см. components/RequestPortal.tsx), открывается в новой вкладке.
|
||
*/
|
||
export const USER_GUIDE_URL =
|
||
'https://tnsukz.sharepoint.com/:w:/r/sites/knowledge/DocLib8/%D0%98%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D0%B8%20%D0%BA%20IT%20%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0%D0%BC/%D0%98%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D1%8F%20-%20%D0%9F%D0%BE%D1%80%D1%82%D0%B0%D0%BB%20%D0%B7%D0%B0%D1%8F%D0%B2%D0%BE%D0%BA.docx?d=wce77e8a0d19d49b5b2fcdf13828e8bb3&csf=1&web=1&e=p9V3hU';
|
||
|
||
/**
|
||
* Страница портала в SharePoint — ссылка «Открыть портал в браузере» в
|
||
* объявлении для Teams (см. components/TeamsBrowserNotice). Внутри Teams
|
||
* портал свой адрес не знает (вкладка Teams — служебная страница), поэтому задан явно.
|
||
*/
|
||
export const PORTAL_PAGE_URL = `${DATA_SITE_URL}/SitePages/${encodeURIComponent('Портал-Заявок')}.aspx`;
|
||
|
||
/**
|
||
* Внешний approval-route API (маршруты согласования по ролям).
|
||
* Авторизация — AadHttpClient (SSO текущего пользователя) по clientId
|
||
* зарегистрированного AAD-приложения. Разрешение объявлено в
|
||
* config/package-solution.json (webApiPermissionRequests) и одобряется
|
||
* в Центре администрирования SharePoint (API access) при развёртывании.
|
||
*/
|
||
export const APPROVAL_API_CLIENT_ID = '9232d9e2-9807-4753-808f-07250e5474b0';
|
||
|
||
/** Прод — то же самое значение, что default в свойстве approvalApiBaseUrl веб-части. */
|
||
export const APPROVAL_API_BASE_URL_PROD = 'https://api.tnsu.kz/approval-route';
|
||
/** Локальный backend — порт из tnsu-approval-route/backend/.../launchSettings.json. */
|
||
export const APPROVAL_API_BASE_URL_LOCAL = 'http://localhost:5080';
|
||
|
||
// Настраивается свойством веб-части approvalApiBaseUrl (селект в панели свойств,
|
||
// см. RequestPortalWebPart.render) — тот же приём, что и у debugUserQuery/
|
||
// setImpersonatedIdentity (shared/impersonation.ts): модульный синглтон, а не
|
||
// проброс параметром через все вызовы approvalRoute.service.ts, потому что эти
|
||
// вызовы разбросаны по формам и не имеют общего входа, через который можно
|
||
// было бы прокинуть контекст.
|
||
let _approvalApiBaseUrl = APPROVAL_API_BASE_URL_PROD;
|
||
|
||
export function setApprovalApiBaseUrl(url: string): void {
|
||
_approvalApiBaseUrl = url || APPROVAL_API_BASE_URL_PROD;
|
||
}
|
||
|
||
export function getApprovalApiBaseUrl(): string {
|
||
return _approvalApiBaseUrl;
|
||
}
|
||
|
||
/**
|
||
* API БСУ (бетонно-смесительные узлы и их рецепты) — справочник для «Заявки на бетон».
|
||
* Токен — того же AAD-приложения, что у approval-route и ЭДО (проверено по aud рабочего
|
||
* токена). Если у БСУ появится своё приложение — поменять только эту константу и объявить
|
||
* доступ в package-solution.json (webApiPermissionRequests).
|
||
*/
|
||
export const BSU_API_CLIENT_ID = APPROVAL_API_CLIENT_ID;
|
||
export const BSU_API_BASE_URL = 'https://bsu.tnsu.kz/api/v1';
|
||
|
||
/**
|
||
* ЭДО (TANSU ЭДО) — подписание документов. Токен берётся для того же AAD-приложения, что и
|
||
* у approval-route и справочников: бэкенд ЭДО принимает токены сотрудников этого приложения
|
||
* (Auth:Entra:Audience). Если ЭДО переедет на своё приложение — поменять только эту константу
|
||
* и объявить доступ в package-solution.json (webApiPermissionRequests).
|
||
*/
|
||
export const EDO_API_CLIENT_ID = APPROVAL_API_CLIENT_ID;
|
||
|
||
/** Локальный бэкенд ЭДО — порт из launchSettings.json (https). */
|
||
export const EDO_API_BASE_URL_LOCAL = 'https://localhost:7016';
|
||
/** Стейджинг ЭДО. Сертификат самоподписанный: браузер должен ему доверять, иначе запрос не уйдёт. */
|
||
export const EDO_API_BASE_URL_STAGE = 'https://10.0.1.225:8443';
|
||
/** Прод ЭДО — значение по умолчанию. */
|
||
export const EDO_API_BASE_URL_PROD = 'https://edo.tnsu.kz';
|
||
|
||
// Настраивается свойством веб-части edoApiBaseUrl (селект в панели свойств) — тот же
|
||
// модульный синглтон, что и approvalApiBaseUrl выше.
|
||
let _edoApiBaseUrl = EDO_API_BASE_URL_PROD;
|
||
|
||
export function setEdoApiBaseUrl(url: string): void {
|
||
// Хвостовые слэши отбрасываем: адрес склеивается с путём «/api/...», а в панели
|
||
// свойств его могли вписать с «/» на конце.
|
||
_edoApiBaseUrl = (url || EDO_API_BASE_URL_PROD).trim().replace(/\/+$/, '');
|
||
}
|
||
|
||
export function getEdoApiBaseUrl(): string {
|
||
return _edoApiBaseUrl;
|
||
}
|
||
|
||
/**
|
||
* Отправка согласованного заявления на отпуск на подпись в ЭДО (forms/Vacation/VacationView).
|
||
* Временно выключена: решение последнего согласующего применяется без обращения к ЭДО.
|
||
* Чтобы вернуть — поставить true, остальной код отправки на месте.
|
||
*/
|
||
export const VACATION_EDO_SIGNING_ENABLED = false;
|
||
|
||
// Уведомления согласующим (поток Power Automate, см. approval/notify.service.ts).
|
||
// Настраивается свойствами веб-части notificationsEnabled / notifyOnlyTo — тот
|
||
// же модульный синглтон, что и у approvalApiBaseUrl выше.
|
||
// enabled — false выключает рассылку целиком;
|
||
// onlyTo — непустой адрес: письма уходят ТОЛЬКО если активный шаг назначен
|
||
// на него (обкатка рассылки); пусто — всем.
|
||
let _notificationsEnabled = true;
|
||
let _notifyOnlyTo = '';
|
||
|
||
export function setNotificationSettings(enabled: boolean, onlyTo: string): void {
|
||
_notificationsEnabled = enabled;
|
||
_notifyOnlyTo = (onlyTo || '').trim();
|
||
}
|
||
|
||
export function getNotificationsEnabled(): boolean {
|
||
return _notificationsEnabled;
|
||
}
|
||
|
||
export function getNotifyOnlyTo(): string {
|
||
return _notifyOnlyTo;
|
||
}
|
||
|
||
/**
|
||
* Справочники (Dictionary API) — тот же gateway api.tnsu.kz и тот же AAD-app
|
||
* (APPROVAL_API_CLIENT_ID), но другой путь/сервис. Используется, например, для
|
||
* GET /Dictionary/Departments.
|
||
*/
|
||
export const DICTIONARY_API_BASE_URL = 'https://api.tnsu.kz';
|
||
|
||
/**
|
||
* Проходы СКУД (Hikvision) текущего пользователя — тот же gateway и AAD-app,
|
||
* что у справочников. Сотрудника API определяет по токену, e-mail не передаём.
|
||
* Используется формой «Объяснительная по СКУД» (forms/SkudExplanation).
|
||
*/
|
||
export const SKUD_EVENTS_PATH = '/Hikvision/Events/CurrentUser';
|
||
|
||
/**
|
||
* Значения параметра `source` внешней ссылки (см. shared/routing, sourceSystem) —
|
||
* из какой системы открыли форму и, значит, куда отвечать ссылкой на заявку.
|
||
* Без source (или с неизвестным значением) — ссылка пришла из Dataverse.
|
||
*/
|
||
export const EXTERNAL_SOURCE = {
|
||
/** КПС: positionId — id позиции акта дефектов, ответ — в Inventory API. */
|
||
kps: 'kps'
|
||
} as const;
|
||
|
||
/**
|
||
* Inventory API (КПС) — приём ссылки на заявку «СЗ на перемещение ТМЦ» для
|
||
* позиции акта дефектов: POST {DICTIONARY_API_BASE_URL}<путь> с телом { url }.
|
||
* {partId} = positionId из внешней ссылки с source=kps. Тот же gateway
|
||
* api.tnsu.kz — авторизация та же, что у справочников (APPROVAL_API_CLIENT_ID).
|
||
*/
|
||
export const INVENTORY_TMC_TRANSFER_PATH = '/inventory/api/defect-acts/parts/{partId}/tmc-transfer';
|
||
|
||
/**
|
||
* Производственный календарь (праздничные выходные) — SP-список на корневом
|
||
* сайте, где живёт главная страница портала (portal-main-page читает его же).
|
||
* Недоступен — календарь СКУД просто не красит праздники.
|
||
*/
|
||
export const HOLIDAYS_SITE_URL = 'https://tnsukz.sharepoint.com';
|
||
export const HOLIDAYS_LIST_TITLE = 'Производственный Календарь';
|
||
|
||
/**
|
||
* Power Automate: поток оповещения согласующего/исполнителя.
|
||
* Триггер «When an HTTP request is received» защищён Entra («любой пользователь
|
||
* тенанта»), поэтому URL без подписи (`sig`), а вызываем через AadHttpClient с
|
||
* ресурсом Flow Service (обычный fetch → 401; так же решается CORS). Разрешение
|
||
* «Microsoft Flow Service / Flows.Read.All» объявлено в package-solution.json и
|
||
* одобряется в Центре администрирования SharePoint (API access) при развёртывании.
|
||
* Шлём тело `{ RowID: <id строки списка согласования> }` — поток сам находит по
|
||
* строке согласующего, статус и заявку и уведомляет. (Паттерн из kps-ct-form.)
|
||
*/
|
||
export const FLOW_RESOURCE = 'https://service.flow.microsoft.com/';
|
||
export const FLOW_NOTIFY_URL =
|
||
'https://eab3d2815f2beb42bf2e7ec1731708.92.environment.api.powerplatform.com:443/powerautomate/automations/direct/cu/07/workflows/b21516edf8a14287a65a39e4116dfe4f/triggers/manual/paths/invoke?api-version=1';
|
||
|
||
/**
|
||
* Power Automate: поток оповещения о новом сообщении в чате заявки (shared/chat).
|
||
* Тот же механизм вызова, что у FLOW_NOTIFY_URL (Entra-триггер, AadHttpClient с
|
||
* ресурсом FLOW_RESOURCE), но тело другое — получателей поток НЕ ищет сам, их
|
||
* передаёт портал (см. shared/chat/chatNotify.ts):
|
||
* отмеченным через «@» — «Вас упомянули в чате по заявке №…»,
|
||
* инициатору (если его не отметили) — «Новое сообщение в чате по вашей заявке №…».
|
||
*/
|
||
export const FLOW_CHAT_NOTIFY_URL =
|
||
'https://eab3d2815f2beb42bf2e7ec1731708.92.environment.api.powerplatform.com:443/powerautomate/automations/direct/cu/07/workflows/325d735d32de4e6989f4f06fc347e833/triggers/manual/paths/invoke?api-version=1';
|
||
|
||
// Чат заявки, режим проверки (свойство веб-части chatSelfNotify): письма уходят и
|
||
// о СВОИХ сообщениях, и себя можно отметить через «@». По умолчанию выключен —
|
||
// писать самому себе в обычной работе незачем.
|
||
let _chatSelfNotify = false;
|
||
|
||
export function setChatSelfNotify(enabled: boolean): void {
|
||
_chatSelfNotify = enabled;
|
||
}
|
||
|
||
export function getChatSelfNotify(): boolean {
|
||
return _chatSelfNotify;
|
||
}
|
||
|
||
/**
|
||
* Microsoft Dataverse (Power Platform) — рабочая среда «По умолчанию»
|
||
* (org95ca17d6.crm4.dynamics.com). NB: таблица cree9_demandline существует и в
|
||
* другой среде (tansu.crm5, из проекта stroy-bot) — писать нужно ИМЕННО в эту.
|
||
* Авторизация через AadHttpClient (SSO текущего пользователя): ресурс = URL
|
||
* среды, разрешение объявлено в package-solution.json (webApiPermissionRequests,
|
||
* scope user_impersonation) и одобряется в Центре администрирования SharePoint
|
||
* (API access) при развёртывании — тот же механизм, что у approval-route API.
|
||
*
|
||
* Используется формой «СЗ на перемещение ТМЦ»: после отправки на согласование
|
||
* в строку-источник (таблица cree9_demandline) записывается ссылка на заявку
|
||
* портала — см. shared/dataverse/demandLine.service.ts.
|
||
*/
|
||
export const DATAVERSE_RESOURCE_URL = 'https://org95ca17d6.crm4.dynamics.com';
|
||
export const DATAVERSE_API_VERSION = 'v9.2';
|
||
/**
|
||
* EntitySetName таблицы «Строка заявки» (LogicalName cree9_demandline).
|
||
* NB: значение выведено по конвенции Dataverse (logical + 's') — проверить в
|
||
* проде через EntityDefinitions(LogicalName='cree9_demandline')?$select=EntitySetName.
|
||
*/
|
||
export const DATAVERSE_DEMANDLINE_ENTITY_SET = 'cree9_demandlines';
|
||
/**
|
||
* Колонка, куда пишем ссылку на заявку портала (создана вручную в Dataverse,
|
||
* отображаемое имя «cree9_tmctransferurl», однострочный текст). Указана владельцами
|
||
* внешней системы; прежняя URL-колонка cr498_portalrequesturl больше не используется.
|
||
* NB: логическое имя с префиксом cr498_ (издатель среды org95ca17d6.crm4 по
|
||
* умолчанию) — так сгенерировалось при создании колонки.
|
||
* Ссылка на заявку ~200+ символов (кириллица в пути страницы кодируется), поэтому
|
||
* макс. длина колонки должна быть не меньше 500 — иначе PATCH отклонится.
|
||
*/
|
||
export const DATAVERSE_DEMANDLINE_LINK_FIELD = 'cr498_cree9_tmctransferurl';
|
||
/**
|
||
* Колонка «ID позиции ЛЗК» на «Строке заявки» — GUID строки cree9_lzkposition
|
||
* ХРАНИТСЯ ТЕКСТОМ (не lookup-связью), поэтому её нельзя $expand'нуть — только
|
||
* читать значение и адресовать таблицу lzk position отдельным запросом.
|
||
* Используется для определения проекта-получателя («Куда») в форме «СЗ на
|
||
* перемещение ТМЦ» — см. shared/dataverse/lzkPosition.service.ts.
|
||
*/
|
||
export const DATAVERSE_DEMANDLINE_LZKPOSITION_FIELD = 'cree9_lzkpositionid';
|
||
|
||
/**
|
||
* EntitySetName таблицы «lzk position» (LogicalName cree9_lzkposition) — выведено
|
||
* по той же конвенции (logical + 's'), что и DATAVERSE_DEMANDLINE_ENTITY_SET.
|
||
*/
|
||
export const DATAVERSE_LZKPOSITION_ENTITY_SET = 'cree9_lzkpositions';
|
||
/** Lookup-поле на lzk position, ссылающееся на родительскую запись LZK. */
|
||
export const DATAVERSE_LZKPOSITION_LZK_LOOKUP_FIELD = 'cree9_lzkid';
|
||
|
||
/** EntitySetName таблицы «LZK» (LogicalName cree9_lzk). */
|
||
export const DATAVERSE_LZK_ENTITY_SET = 'cree9_lzks';
|
||
/** Колонка «project id» на LZK — GUID проекта текстом, совпадает по формату с oid из /Project. */
|
||
export const DATAVERSE_LZK_PROJECT_FIELD = 'cree9_projectid';
|