diff --git a/src/webparts/requestPortal/components/RequestPortal.tsx b/src/webparts/requestPortal/components/RequestPortal.tsx index 786fcad..dd579e7 100644 --- a/src/webparts/requestPortal/components/RequestPortal.tsx +++ b/src/webparts/requestPortal/components/RequestPortal.tsx @@ -480,7 +480,8 @@ const RequestPortal: React.FC = ({ hasTeamsContext, context sourceId: route.sourceId, positionId: route.positionId, nomenclatureId: route.nomenclatureId, - storeName: route.storeName + storeName: route.storeName, + sourceSystem: route.sourceSystem } : undefined } diff --git a/src/webparts/requestPortal/features/submit/SubmitFeature.tsx b/src/webparts/requestPortal/features/submit/SubmitFeature.tsx index b2abeb3..4e63079 100644 --- a/src/webparts/requestPortal/features/submit/SubmitFeature.tsx +++ b/src/webparts/requestPortal/features/submit/SubmitFeature.tsx @@ -20,7 +20,14 @@ export interface ISubmitFeatureProps { /** Открыть сразу форму, минуя плитки — с предзаполнением (производная заявка, * sourceId задан) или без него (пустая новая форма конкретного типа). * positionId/nomenclatureId — параметры внешней ссылки (только форма ТМЦ). */ - initialForm?: { formKey: string; sourceId?: number; positionId?: string; nomenclatureId?: string; storeName?: string }; + initialForm?: { + formKey: string; + sourceId?: number; + positionId?: string; + nomenclatureId?: string; + storeName?: string; + sourceSystem?: string; + }; /** Открыть сразу форму на РЕДАКТИРОВАНИЕ существующей заявки (черновик/возвращена). */ editForm?: { formKey: string; itemId: number }; /** Пользователь выбрал плитку — сообщаем наверх, чтобы адрес страницы стал @@ -145,6 +152,7 @@ const SubmitFeature: React.FC = ({ positionId={external?.positionId} nomenclatureId={external?.nomenclatureId} storeName={external?.storeName} + sourceSystem={external?.sourceSystem} /> diff --git a/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx b/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx index 6331bb0..32a664c 100644 --- a/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx +++ b/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx @@ -91,7 +91,8 @@ const TmcTransferForm: React.FC = ({ onCodeLoaded, positionId, nomenclatureId, - storeName + storeName, + sourceSystem }) => { const [data, setData] = useState(EMPTY_DATA); const [files, setFiles] = useState([]); @@ -144,18 +145,30 @@ const TmcTransferForm: React.FC = ({ }; }, [editId, context]); - // Открытие по внешней ссылке (positionId + nomenclatureId в URL): запоминаем - // строку-источник и предзаполняем таблицу остатками номенклатуры по складам. - // Только для новой заявки — при редактировании позиции уже сохранены. + // Открытие по внешней ссылке: запоминаем, откуда пришла заявка — система + // (source), строка-источник (positionId) и склад. Строка нужна при отправке для + // обратной записи ссылки в Dataverse, система — чтобы знать, откуда заявка, + // склад — справочно (см. model.ts). Не зависит от nomenclatureId: ссылка может + // прийти и без товара. Только для новой заявки — у сохранённой всё уже в SP. + useEffect(() => { + if (editId || (!positionId && !storeName && !sourceSystem)) { + return; + } + setData((prev) => ({ + ...prev, + SourcePositionId: positionId || '', + SourceStoreName: storeName || '', + SourceSystem: sourceSystem || '' + })); + }, [editId, positionId, storeName, sourceSystem]); + + // Предзаполнение таблицы остатками номенклатуры по складам (nomenclatureId из + // внешней ссылки). nomenclatureId отдельно в шапке не храним — он едет в каждой + // позиции ItemsJson. Только для новой заявки — при редактировании позиции уже сохранены. useEffect(() => { if (editId || !nomenclatureId) { return; } - // Строку-источник и склад фиксируем сразу, независимо от ответа справочника: - // строка нужна при отправке для обратной записи ссылки в Dataverse, склад — - // только справочно для пользователя (см. SourceStoreName в model.ts). - // nomenclatureId отдельно в шапке не храним — он едет в каждой позиции ItemsJson. - setData((prev) => ({ ...prev, SourcePositionId: positionId || '', SourceStoreName: storeName || '' })); let alive = true; getNomenclature(context, nomenclatureId, storeName) .then((rows) => { @@ -175,7 +188,7 @@ const TmcTransferForm: React.FC = ({ return () => { alive = false; }; - }, [editId, nomenclatureId, positionId, storeName, context, setSubmitError]); + }, [editId, nomenclatureId, storeName, context, setSubmitError]); // Проект-получатель («Куда») по той же внешней ссылке: строка-источник в // Dataverse хранит ID позиции ЛЗК, у которой есть родительская запись LZK diff --git a/src/webparts/requestPortal/forms/TmcTransfer/load.ts b/src/webparts/requestPortal/forms/TmcTransfer/load.ts index 1e3b99e..47969d4 100644 --- a/src/webparts/requestPortal/forms/TmcTransfer/load.ts +++ b/src/webparts/requestPortal/forms/TmcTransfer/load.ts @@ -53,6 +53,7 @@ export async function loadTmcTransferRequest(itemId: number, listId?: string): P 'ContactPhone', 'SourcePositionId', 'SourceStoreName', + 'SourceSystem', 'ItemsJson', 'RequestStatus', 'RequestUniqueCode' @@ -80,6 +81,7 @@ export async function loadTmcTransferRequest(itemId: number, listId?: string): P ContactPhone: (item.ContactPhone as string) || '', SourcePositionId: (item.SourcePositionId as string) || '', SourceStoreName: (item.SourceStoreName as string) || '', + SourceSystem: (item.SourceSystem as string) || '', Items: parseItems(item.ItemsJson) }; diff --git a/src/webparts/requestPortal/forms/TmcTransfer/model.ts b/src/webparts/requestPortal/forms/TmcTransfer/model.ts index 6b0bcad..fbb283a 100644 --- a/src/webparts/requestPortal/forms/TmcTransfer/model.ts +++ b/src/webparts/requestPortal/forms/TmcTransfer/model.ts @@ -68,6 +68,12 @@ export interface IFormData { * именно списываются ТМЦ. '' — ссылка без фильтра по складу или заявка заведена вручную. */ SourceStoreName: string; + /** + * Система, из которой открыли форму по внешней ссылке — параметр `source` в URL + * (напр. 'kps'). Вместе с SourcePositionId однозначно указывает, откуда пришла + * заявка и куда отвечать. '' — заявка заведена вручную или ссылка без source. + */ + SourceSystem: string; // Табличная часть — позиции ТМЦ (сериализуется в ItemsJson). У строк из // справочника есть nomenclatureId (см. ILineItem), у дописанных вручную — нет. @@ -115,5 +121,6 @@ export const EMPTY_DATA: IFormData = { ContactPhone: '', SourcePositionId: '', SourceStoreName: '', + SourceSystem: '', Items: [] }; diff --git a/src/webparts/requestPortal/forms/TmcTransfer/provisioning.json b/src/webparts/requestPortal/forms/TmcTransfer/provisioning.json index baa4565..7307c8e 100644 --- a/src/webparts/requestPortal/forms/TmcTransfer/provisioning.json +++ b/src/webparts/requestPortal/forms/TmcTransfer/provisioning.json @@ -25,6 +25,7 @@ { "internalName": "SourcePositionId", "title": "Id строки-источника (Dataverse)", "type": "Text" }, { "internalName": "SourceStoreName", "title": "Склад-источник (внешняя ссылка)", "type": "Text" }, + { "internalName": "SourceSystem", "title": "Система-источник (внешняя ссылка)", "type": "Text" }, { "internalName": "ItemsJson", "title": "Позиции ТМЦ (JSON)", "type": "Note" } ] diff --git a/src/webparts/requestPortal/models/types.ts b/src/webparts/requestPortal/models/types.ts index d155b7e..5bb89d4 100644 --- a/src/webparts/requestPortal/models/types.ts +++ b/src/webparts/requestPortal/models/types.ts @@ -49,6 +49,8 @@ export interface IRequestFormProps { nomenclatureId?: string; /** Склад-фильтр для справочника номенклатуры (см. positionId). */ storeName?: string; + /** Система-источник внешней ссылки (параметр `source`, напр. 'kps'; см. positionId). */ + sourceSystem?: string; /** * Id СВОЕЙ заявки, открытой на редактирование (черновик / возвращённая / * отклонённая). В отличие от sourceId это не предзаполнение новой заявки, а diff --git a/src/webparts/requestPortal/shared/routing.test.ts b/src/webparts/requestPortal/shared/routing.test.ts index 5f0ec8a..f7535d9 100644 --- a/src/webparts/requestPortal/shared/routing.test.ts +++ b/src/webparts/requestPortal/shared/routing.test.ts @@ -129,6 +129,10 @@ describe('parseRoute', () => { }); }); + it('внешняя ссылка ТМЦ: source прокидывается в create как sourceSystem', () => { + expect(parseRoute('?rptype=wh-tmc-transfer&positionId=abc&source=kps').sourceSystem).toBe('kps'); + }); + it('positionId/nomenclatureId без rptype игнорируются — обычная подача', () => { // Внешние параметры имеют смысл только при открытии формы (rptype); сами по // себе не должны влиять на маршрут. diff --git a/src/webparts/requestPortal/shared/routing.ts b/src/webparts/requestPortal/shared/routing.ts index e8a2371..b45e0c7 100644 --- a/src/webparts/requestPortal/shared/routing.ts +++ b/src/webparts/requestPortal/shared/routing.ts @@ -86,6 +86,13 @@ export interface IRoute { * этому складу. Приходит по той же внешней ссылке; пусто — все склады. */ storeName?: string; + /** + * Система, из которой открыли форму по внешней ссылке (параметр `source`, + * напр. 'kps'). Сохраняется в заявке, чтобы знать, откуда она пришла и куда + * отвечать. Не путать с sourceId (rpsource) — тот про предзаполнение из + * другой заявки портала. + */ + sourceSystem?: string; /** Подвкладка «Моих заявок»; задана только при nav='own'. */ ownTab?: OwnTab; /** Фильтр по статусу заявки; задан только при nav='own' и ownTab='created'. */ @@ -378,6 +385,7 @@ export function parseRoute(search: string = window.location.search): IRoute { const positionId = params.get('positionId') ?? undefined; const nomenclatureId = params.get('nomenclatureId') ?? undefined; const storeName = params.get('storeName') ?? undefined; + const sourceSystem = params.get('source') ?? undefined; // Подвкладка и фильтр — состояние раздела «Мои заявки», поэтому вне его не // читаются вовсе (иначе чужой rptab из ссылки «протёк» бы в другой раздел). @@ -432,7 +440,7 @@ export function parseRoute(search: string = window.location.search): IRoute { // rpmode=approve без валидного rpid) должна откатываться на обычную подачу, // а не тихо превращаться в открытие пустой формы другого режима. if (formKey && mode !== 'approve' && mode !== 'edit') { - return { mode: 'create', nav: 'new', formKey, positionId, nomenclatureId, storeName }; + return { mode: 'create', nav: 'new', formKey, positionId, nomenclatureId, storeName, sourceSystem }; } return { mode: 'submit', nav, ownTab, ownStatus, allTypeId, page, query }; }