diff --git a/docs/forms.md b/docs/forms.md index 502a415..ea4ad27 100644 --- a/docs/forms.md +++ b/docs/forms.md @@ -45,7 +45,7 @@ | [`recruiting-referral`](../src/webparts/requestPortal/forms/Referral/README.md) | Реферальная рекомендация | Рекрутинг | да | да | да | кандидат — внешний (текстовые поля, не People); маршрут по рекомендателю; исполнитель переназначает на реального; условное уточнение «откуда знаете» | ✅ | | [`it-workplace-setup`](../src/webparts/requestPortal/forms/WorkplaceSetup/README.md) | Организация рабочего места | IT | да | да | да | префилл из «прием», поля ПО/доступы правит руководитель | ✅ | | [`it-1c-ticket`](../src/webparts/requestPortal/forms/Ticket1C/README.md) | Заявка в отдел 1С | IT | да | нет | да | контакты предзаполняются из Graph (/me); вместо фазы исполнения — карточка в Microsoft Planner (согласовано/сразу без согласования — рабочий бакет, отклонено — отдельный бакет) | ✅ | -| [`clearance-form`](../src/webparts/requestPortal/forms/Clearance/README.md) | Обходной лист | Отдел кадров | да | нет | да | подаётся HR за увольняемого (People-picker) либо создаётся сам по согласованному «Заявлению на увольнение»; условие «IsManager» резолвится по должности из самой заявки; да/нет-поля (парковка, СКУД); 6 независимых сумм к списанию компактной сеткой (ОС, СИЗ, подотчётная сумма, обучение, НМА, ТМЦ) — вводит любой активный согласующий, блокируется навсегда после первого ввода; заполнил хоть одну — общий комментарий «за что» становится обязательным | ✅ | +| [`clearance-form`](../src/webparts/requestPortal/forms/Clearance/README.md) | Обходной лист | Отдел кадров | да | нет | да | подаётся HR за увольняемого (People-picker) либо создаётся сам по согласованному «Заявлению на увольнение»; условие «IsManager» резолвится по должности из самой заявки; да/нет-поля (парковка, СКУД); 6 независимых сумм к списанию компактной сеткой (ОС, СИЗ, подотчётная сумма, обучение, НМА, ТМЦ) — вводит любой активный согласующий, блокируется навсегда после первого ввода; заполнил хоть одну — ОДИН общий комментарий «за что» становится обязательным | ✅ | | [`hr-dismissal`](../src/webparts/requestPortal/forms/Dismissal/README.md) | Заявление на увольнение | Отдел кадров | да | нет | да | совместители — таблица компаний с удалением строки (снимает и согласующего из матрицы); должность/организация из справочника; **после согласования сам создаёт обходной лист**; печать заблокирована до согласования обходного листа; «Лицо, принимающее ОС/НМА» — заполняет только непосредственный руководитель | ✅ | | `hr-transfer` | Перемещение сотрудников | Отдел кадров | — | — | — | — | ⏳ заглушка (плитка есть, форма не сделана) | diff --git a/src/webparts/requestPortal/forms/Clearance/ClearanceView.tsx b/src/webparts/requestPortal/forms/Clearance/ClearanceView.tsx index ff56c2d..f653f45 100644 --- a/src/webparts/requestPortal/forms/Clearance/ClearanceView.tsx +++ b/src/webparts/requestPortal/forms/Clearance/ClearanceView.tsx @@ -172,11 +172,9 @@ const ClearanceView: React.FC = ({ context, itemId, formKey // Пишем ТОЛЬКО те отметки, на которые у согласующего есть права: иначе он // затёр бы чужие поля значениями, которые видел у себя на экране. Суммы к // списанию — только НЕПУСТЫЕ и только среди editableWriteOffKeys; комментарий - // ОДИН на все суммы, заполняемые СЕЙЧАС (не пара на каждую, см. IFormData. - // WriteOffComment) — если хоть одна из них не пуста, комментарий обязателен - // (бросает ДО записи чего бы то ни было) и при записи дублируется в - // Comment-колонку КАЖДОЙ такой категории — так каждая остаётся - // прокомментированной в самом списке. + // ОДИН на все суммы (IFormData.WriteOffComment, не пара на каждую категорию) + // — если хоть одна из редактируемых сумм не пуста, комментарий обязателен + // (бросает ДО записи чего бы то ни было). const saveApproverFields = useCallback(async (): Promise => { const fields: Partial = {}; if (hrRecordsEditable) { @@ -198,8 +196,8 @@ const ClearanceView: React.FC = ({ context, itemId, formKey } for (const cat of touched) { writeOffFields[cat.amountField] = data[cat.amountField]; - writeOffFields[cat.commentField] = comment; } + writeOffFields.WriteOffComment = comment; } await updateApproverFields(itemId, fields, listId); diff --git a/src/webparts/requestPortal/forms/Clearance/README.md b/src/webparts/requestPortal/forms/Clearance/README.md index af8baaf..2deb6ab 100644 --- a/src/webparts/requestPortal/forms/Clearance/README.md +++ b/src/webparts/requestPortal/forms/Clearance/README.md @@ -55,10 +55,10 @@ RequestTypeId» в `docs/forms.md`). Тип уже зарегистрирова заведённых руками, ссылки нет и поле не рендерится. ## Суммы к списанию (согласование) -6 НЕЗАВИСИМЫХ сумм, компактной сеткой (короткие подписи, без комментария под -каждой — см. ниже), колонки `WriteOff{Assets,Ppe,Accountable,Training,Nma,Tmc} -{Amount,Comment}` (см. `model.ts WRITE_OFF_CATEGORIES`): «ОС», «СИЗ», «Подотчётная -сумма», «Обучение», «НМА», «ТМЦ». +6 НЕЗАВИСИМЫХ сумм, компактной сеткой (короткие подписи, обычная 2-колоночная +сетка секции), колонки `WriteOff{Assets,Ppe,Accountable,Training,Nma,Tmc}Amount` +(см. `model.ts WRITE_OFF_CATEGORIES`): «ОС», «СИЗ», «Подотчётная сумма», +«Обучение», «НМА», «ТМЦ». Плюс ОДИН общий комментарий на всех — `WriteOffComment`. - **Не привязаны к должности**, в отличие от отметок выше: заполнить может ЛЮБОЙ согласующий, у которого сейчас активный шаг (`ClearanceView.canEditWriteOffs`, @@ -68,15 +68,16 @@ RequestTypeId» в `docs/forms.md`). Тип уже зарегистрирова Признак блокировки — сама непустая сумма на МОМЕНТ ЗАГРУЗКИ страницы (`ClearanceView.lockedWriteOffs`, снимок при загрузке, а не реактивно от текущего `data` — иначе поле блокировалось бы прямо во время набора цифр). -- **Комментарий — ОДИН на все суммы, заполняемые в ЭТОМ сохранении** (не пара на - каждую категорию, как раньше): появляется под сеткой, только когда хоть одна - редактируемая сумма не пуста (`writeOffTouchedCategories` в `RequestSection`), - и обязателен в этот момент — `saveApproverFields` бросает ДО записи чего бы то - ни было, если сумма есть, а комментарий пуст. Плейсхолдер называет категории, - для которых он сейчас пишется («Введите, за что сумма: ОС, ТМЦ»). При записи - этот ОДИН текст дублируется в Comment-колонку КАЖДОЙ заполняемой сейчас - категории — так индивидуальный комментарий по категории в списке остаётся, а - форма не заставляет писать одно и то же под каждой суммой отдельно. +- **Комментарий («за что конкретно») — ОДИН на ВСЕ суммы**, не пара на каждую + категорию: одна Note-колонка (`WriteOffComment`), не шесть. Read-only, пока + нечего заполнять (показывает то, что уже написали раньше); становится + редактируемым и ОБЯЗАТЕЛЬНЫМ ровно тогда, когда хоть одна редактируемая сумма + не пуста (`writeOffTouchedCategories` в `RequestSection`) — `saveApproverFields` + бросает ДО записи чего бы то ни было, если сумма есть, а комментарий пуст. + Плейсхолдер называет категории, для которых он сейчас пишется («Введите, за + что сумма: ОС, ТМЦ»). Следующий согласующий, заполняющий ДРУГУЮ сумму, увидит + и сможет дописать то, что уже есть в поле, — это обычный редактируемый текст, + без автоматического версионирования/склейки. - Сохраняются той же кнопкой «Сохранить» (`reviewBar`), что и остальные отметки, и тем же `onBeforeDecide` — при решении несохранённые суммы уйдут вместе с ним. - Это ОТДЕЛЬНЫЙ механизм от `TrainingAmount` («Обучение: сумма» выше, привязана к diff --git a/src/webparts/requestPortal/forms/Clearance/load.ts b/src/webparts/requestPortal/forms/Clearance/load.ts index 8e95326..7fec964 100644 --- a/src/webparts/requestPortal/forms/Clearance/load.ts +++ b/src/webparts/requestPortal/forms/Clearance/load.ts @@ -39,17 +39,12 @@ export async function loadClearanceRequest(itemId: number, listId?: string): Pro 'Organization', 'DismissalUrl', 'WriteOffAssetsAmount', - 'WriteOffAssetsComment', 'WriteOffPpeAmount', - 'WriteOffPpeComment', 'WriteOffAccountableAmount', - 'WriteOffAccountableComment', 'WriteOffTrainingAmount', - 'WriteOffTrainingComment', 'WriteOffNmaAmount', - 'WriteOffNmaComment', 'WriteOffTmcAmount', - 'WriteOffTmcComment', + 'WriteOffComment', 'RequestStatus', 'RequestUniqueCode', 'Employee/Title', @@ -74,17 +69,12 @@ export async function loadClearanceRequest(itemId: number, listId?: string): Pro // Гиперссылка приходит объектом {Url, Description} — модели нужен только адрес. DismissalUrl: (item.DismissalUrl as { Url?: string } | undefined)?.Url || '', WriteOffAssetsAmount: numberField(item.WriteOffAssetsAmount), - WriteOffAssetsComment: (item.WriteOffAssetsComment as string) || '', WriteOffPpeAmount: numberField(item.WriteOffPpeAmount), - WriteOffPpeComment: (item.WriteOffPpeComment as string) || '', WriteOffAccountableAmount: numberField(item.WriteOffAccountableAmount), - WriteOffAccountableComment: (item.WriteOffAccountableComment as string) || '', WriteOffTrainingAmount: numberField(item.WriteOffTrainingAmount), - WriteOffTrainingComment: (item.WriteOffTrainingComment as string) || '', WriteOffNmaAmount: numberField(item.WriteOffNmaAmount), - WriteOffNmaComment: (item.WriteOffNmaComment as string) || '', WriteOffTmcAmount: numberField(item.WriteOffTmcAmount), - WriteOffTmcComment: (item.WriteOffTmcComment as string) || '' + WriteOffComment: (item.WriteOffComment as string) || '' }; return { diff --git a/src/webparts/requestPortal/forms/Clearance/model.ts b/src/webparts/requestPortal/forms/Clearance/model.ts index 8ce3f5a..6fc4912 100644 --- a/src/webparts/requestPortal/forms/Clearance/model.ts +++ b/src/webparts/requestPortal/forms/Clearance/model.ts @@ -46,80 +46,52 @@ export interface IFormData { * ClearanceView.canEditWriteOffs), но как только сумма непустая — блокируется * НАВСЕГДА, для всех (включая того, кто её ввёл). Признак блокировки — сама * непустая сумма, отдельного флага в списке нет. - * - * Комментарий («за что конкретно») — ОБЩИЙ на все суммы, вводимые ОДНИМ - * сохранением (WriteOffComment, ниже), не отдельная пара на каждую категорию: - * при записи он дублируется в Comment-колонку КАЖДОЙ категории, чью сумму - * заполнили именно сейчас (см. ClearanceView.saveApproverFields) — так каждая - * категория остаётся комментированной в самом списке, а форма не заставляет - * писать одно и то же по кругу под каждой суммой. */ WriteOffAssetsAmount: string; - WriteOffAssetsComment: string; WriteOffPpeAmount: string; - WriteOffPpeComment: string; WriteOffAccountableAmount: string; - WriteOffAccountableComment: string; WriteOffTrainingAmount: string; - WriteOffTrainingComment: string; WriteOffNmaAmount: string; - WriteOffNmaComment: string; WriteOffTmcAmount: string; - WriteOffTmcComment: string; /** - * Комментарий к сумме(ам), заполняемым ПРЯМО СЕЙЧАС (не из списка — вводится - * заново при каждом сохранении, см. комментарий выше). Обязателен, если хоть - * одна из редактируемых сумм не пуста (см. RequestSection). + * Комментарий «за что конкретно» — ОДИН на ВСЕ суммы к списанию (не пара на + * каждую категорию — раньше было так, но колонок-комментариев на каждую + * категорию не завели: одного общего достаточно). Обязателен, если хоть одна + * из РЕДАКТИРУЕМЫХ сумм не пуста (см. RequestSection.writeOffTouchedCategories), + * и до этого момента read-only — показывает то, что уже написали раньше. */ WriteOffComment: string; } -/** Имена всех 12 строковых полей сумм/комментариев к списанию — см. IFormData.WriteOff*. */ +/** Имена всех 6 сумм к списанию — см. IFormData.WriteOff*Amount. */ export type WriteOffFieldKey = | 'WriteOffAssetsAmount' - | 'WriteOffAssetsComment' | 'WriteOffPpeAmount' - | 'WriteOffPpeComment' | 'WriteOffAccountableAmount' - | 'WriteOffAccountableComment' | 'WriteOffTrainingAmount' - | 'WriteOffTrainingComment' | 'WriteOffNmaAmount' - | 'WriteOffNmaComment' - | 'WriteOffTmcAmount' - | 'WriteOffTmcComment'; + | 'WriteOffTmcAmount'; /** - * Одна из 6 независимых категорий списания — см. IFormData.WriteOff*. - * amountField/commentField сужены до WriteOffFieldKey (а не keyof IFormData - * целиком): у IFormData есть и булевы поля (ParkingCard/AccessCard), и - * `data[cat.amountField]` должен типизироваться строго строкой — иначе - * `.trim()` в валидации и передача в MoneyInput/TextArea не собираются. + * Одна из 6 независимых категорий списания — см. IFormData.WriteOff*Amount. + * amountField сужен до WriteOffFieldKey (а не keyof IFormData целиком): у + * IFormData есть и булевы поля (ParkingCard/AccessCard), и `data[cat.amountField]` + * должен типизироваться строго строкой — иначе `.trim()` в валидации и + * передача в MoneyInput не собираются. */ export interface IWriteOffCategory { key: string; label: string; amountField: WriteOffFieldKey; - commentField: WriteOffFieldKey; } export const WRITE_OFF_CATEGORIES: IWriteOffCategory[] = [ - { key: 'assets', label: 'ОС', amountField: 'WriteOffAssetsAmount', commentField: 'WriteOffAssetsComment' }, - { key: 'ppe', label: 'СИЗ', amountField: 'WriteOffPpeAmount', commentField: 'WriteOffPpeComment' }, - { - key: 'accountable', - label: 'Подотчётная сумма', - amountField: 'WriteOffAccountableAmount', - commentField: 'WriteOffAccountableComment' - }, - { - key: 'training', - label: 'Обучение', - amountField: 'WriteOffTrainingAmount', - commentField: 'WriteOffTrainingComment' - }, - { key: 'nma', label: 'НМА', amountField: 'WriteOffNmaAmount', commentField: 'WriteOffNmaComment' }, - { key: 'tmc', label: 'ТМЦ', amountField: 'WriteOffTmcAmount', commentField: 'WriteOffTmcComment' } + { key: 'assets', label: 'ОС', amountField: 'WriteOffAssetsAmount' }, + { key: 'ppe', label: 'СИЗ', amountField: 'WriteOffPpeAmount' }, + { key: 'accountable', label: 'Подотчётная сумма', amountField: 'WriteOffAccountableAmount' }, + { key: 'training', label: 'Обучение', amountField: 'WriteOffTrainingAmount' }, + { key: 'nma', label: 'НМА', amountField: 'WriteOffNmaAmount' }, + { key: 'tmc', label: 'ТМЦ', amountField: 'WriteOffTmcAmount' } ]; /** Обновление одного поля формы с сохранением типа значения. */ @@ -148,17 +120,11 @@ export const EMPTY_DATA: IFormData = { Organization: '', DismissalUrl: '', WriteOffAssetsAmount: '', - WriteOffAssetsComment: '', WriteOffPpeAmount: '', - WriteOffPpeComment: '', WriteOffAccountableAmount: '', - WriteOffAccountableComment: '', WriteOffTrainingAmount: '', - WriteOffTrainingComment: '', WriteOffNmaAmount: '', - WriteOffNmaComment: '', WriteOffTmcAmount: '', - WriteOffTmcComment: '', WriteOffComment: '' }; diff --git a/src/webparts/requestPortal/forms/Clearance/provisioning.json b/src/webparts/requestPortal/forms/Clearance/provisioning.json index 964aaea..ada4a96 100644 --- a/src/webparts/requestPortal/forms/Clearance/provisioning.json +++ b/src/webparts/requestPortal/forms/Clearance/provisioning.json @@ -17,16 +17,11 @@ { "internalName": "DismissalUrl", "title": "Ссылка на заявление на увольнение", "type": "Url" }, { "internalName": "WriteOffAssetsAmount", "title": "Сумма к списанию: ОС", "type": "Number", "decimals": 2 }, - { "internalName": "WriteOffAssetsComment", "title": "Комментарий: ОС", "type": "Note" }, { "internalName": "WriteOffPpeAmount", "title": "Сумма к списанию: СИЗ", "type": "Number", "decimals": 2 }, - { "internalName": "WriteOffPpeComment", "title": "Комментарий: СИЗ", "type": "Note" }, { "internalName": "WriteOffAccountableAmount", "title": "Сумма к списанию: подотчётная сумма", "type": "Number", "decimals": 2 }, - { "internalName": "WriteOffAccountableComment", "title": "Комментарий: подотчётная сумма", "type": "Note" }, { "internalName": "WriteOffTrainingAmount", "title": "Сумма к списанию: обучение", "type": "Number", "decimals": 2 }, - { "internalName": "WriteOffTrainingComment", "title": "Комментарий: обучение", "type": "Note" }, { "internalName": "WriteOffNmaAmount", "title": "Сумма к списанию: НМА", "type": "Number", "decimals": 2 }, - { "internalName": "WriteOffNmaComment", "title": "Комментарий: НМА", "type": "Note" }, { "internalName": "WriteOffTmcAmount", "title": "Сумма к списанию: ТМЦ", "type": "Number", "decimals": 2 }, - { "internalName": "WriteOffTmcComment", "title": "Комментарий: ТМЦ", "type": "Note" } + { "internalName": "WriteOffComment", "title": "Комментарий к списанию", "type": "Note" } ] } diff --git a/src/webparts/requestPortal/forms/Clearance/sections/RequestSection.tsx b/src/webparts/requestPortal/forms/Clearance/sections/RequestSection.tsx index 13f45b6..a51c4b0 100644 --- a/src/webparts/requestPortal/forms/Clearance/sections/RequestSection.tsx +++ b/src/webparts/requestPortal/forms/Clearance/sections/RequestSection.tsx @@ -112,8 +112,8 @@ export const RequestSection: React.FC = ({ от отметок выше) — но как только сумма указана, блокируется навсегда для всех, поэтому у незаблокированных (writeOffEditableKeys.has(cat.key)) есть readOnly={false}, у остальных — обычное поведение FormMode - (просмотр — заблокировано и так). Комментарий — ОДИН на все суммы, - вводимые сейчас (см. WRITE_OFF_CATEGORIES/WriteOffComment в model.ts). */} + (просмотр — заблокировано и так). Комментарий — ОДИН на ВСЕ суммы + (не пара на каждую категорию, см. IFormData.WriteOffComment). */} {writeOffEditableKeys && ( <> {WRITE_OFF_CATEGORIES.map((cat) => { @@ -129,15 +129,28 @@ export const RequestSection: React.FC = ({ ); })} - {writeOffTouchedCategories.length > 0 && ( - + {/* Read-only, если сейчас нечего заполнять (нет свежих сумм) — просто + показывает, что написали раньше; редактируема и обязательна + ровно тогда, когда есть непустая сумма, которую надо объяснить. */} + {(writeOffTouchedCategories.length > 0 || data.WriteOffComment) && ( + 0} + full + >