diff --git a/src/webparts/requestPortal/forms/Clearance/README.md b/src/webparts/requestPortal/forms/Clearance/README.md index 9709ab2..073ff57 100644 --- a/src/webparts/requestPortal/forms/Clearance/README.md +++ b/src/webparts/requestPortal/forms/Clearance/README.md @@ -59,11 +59,16 @@ RequestTypeId» в `docs/forms.md`). Тип уже зарегистрирова `.writeOffLabel`) объединяет визуально ДВЕ РАЗНЫЕ по правам вещи, идущие сразу за подписью: -1. **`TrainingAmount`** («Обучение: сумма») — уже существовавшее поле, правит +1. **`TrainingAmount`** («Обучение: сумма») — доступность КАК И РАНЬШЕ: правит только КДП (`hrRecordsEditable`, `approverRights.ts`), сумма НЕ блокируется - после ввода. Заголовок рисуется независимо от него (`writeOffEditableKeys && -
…
`, а само поле — БЕЗ этого условия), поэтому на форме подачи - (`ClearanceForm`, `writeOffEditableKeys` не передан) поле видно и без заголовка. + после ввода, никак не участвует в WRITE_OFF_CATEGORIES/`writeOffTouchedCategories` + (не требует комментария решения). Изменилась ТОЛЬКО колонка хранения — пишется + и читается через `WriteOffTrainingAmount` (`ClearanceView.saveApproverFields`, + `submit.ts.buildItem`, `load.ts`), а не через одноимённую `TrainingAmount` — + та осталась в схеме списка неиспользуемой. Заголовок рисуется независимо от + поля (`writeOffEditableKeys &&
…
`, а само поле — БЕЗ этого условия), + поэтому на форме подачи (`ClearanceForm`, `writeOffEditableKeys` не передан) + поле видно и без заголовка. 2. **5 НЕЗАВИСИМЫХ сумм**, компактной сеткой (короткие подписи, обычная 2-колоночная сетка секции), колонки `WriteOff{Assets,Ppe,Accountable,Nma, Tmc}Amount` (см. `model.ts WRITE_OFF_CATEGORIES`): «ОС», «СИЗ», «Подотчётная diff --git a/src/webparts/requestPortal/forms/Clearance/load.ts b/src/webparts/requestPortal/forms/Clearance/load.ts index 5c390fa..cc647f1 100644 --- a/src/webparts/requestPortal/forms/Clearance/load.ts +++ b/src/webparts/requestPortal/forms/Clearance/load.ts @@ -35,12 +35,12 @@ export async function loadClearanceRequest(itemId: number, listId?: string): Pro 'TerminationDate', 'ParkingCard', 'AccessCard', - 'TrainingAmount', 'Organization', 'DismissalUrl', 'WriteOffAssetsAmount', 'WriteOffPpeAmount', 'WriteOffAccountableAmount', + 'WriteOffTrainingAmount', 'WriteOffNmaAmount', 'WriteOffTmcAmount', 'RequestStatus', @@ -52,7 +52,6 @@ export async function loadClearanceRequest(itemId: number, listId?: string): Pro const employee = item.Employee as { Title?: string; EMail?: string } | undefined; const termination = (item.TerminationDate as string) || ''; - const amount = item.TrainingAmount; const data: IFormData = { ...EMPTY_DATA, @@ -62,7 +61,9 @@ export async function loadClearanceRequest(itemId: number, listId?: string): Pro TerminationDate: termination.slice(0, 10), // ISO → YYYY-MM-DD для input type=date ParkingCard: !!item.ParkingCard, AccessCard: !!item.AccessCard, - TrainingAmount: amount === null || amount === undefined ? '' : String(amount), + // Хранится в WriteOffTrainingAmount, не в одноимённом TrainingAmount — см. + // IFormData.TrainingAmount. + TrainingAmount: numberField(item.WriteOffTrainingAmount), Organization: (item.Organization as string) || '', // Гиперссылка приходит объектом {Url, Description} — модели нужен только адрес. DismissalUrl: (item.DismissalUrl as { Url?: string } | undefined)?.Url || '', diff --git a/src/webparts/requestPortal/forms/Clearance/model.ts b/src/webparts/requestPortal/forms/Clearance/model.ts index 3e84256..2866c57 100644 --- a/src/webparts/requestPortal/forms/Clearance/model.ts +++ b/src/webparts/requestPortal/forms/Clearance/model.ts @@ -23,7 +23,17 @@ export interface IFormData { TerminationDate: string; ParkingCard: boolean; AccessCard: boolean; - /** Сумма обучения — «сырые» цифры без пробелов (см. MoneyInput). Пусто = не указана. */ + /** + * Сумма обучения — «сырые» цифры без пробелов (см. MoneyInput). Пусто = не + * указана. Доступность — КАК И РАНЬШЕ: правит только начальник КДП + * (`hrRecordsEditable`, `approverRights.ts`), не блокируется после ввода (в + * отличие от WRITE_OFF_CATEGORIES ниже). Единственное, что изменилось, — + * КОЛОНКА ХРАНЕНИЯ: пишется и читается через `WriteOffTrainingAmount` (см. + * ClearanceView.saveApproverFields/load.ts), а не через одноимённый + * `TrainingAmount` в списке — тот остался в схеме неиспользуемым (как и + * другие столбцы, из которых код этой сессии перестал писать/читать, см. + * README «Убрано поле…»). + */ TrainingAmount: string; /** * Организации сотрудника (`\n`-joined «Компания — должность» на строку) — @@ -40,15 +50,12 @@ export interface IFormData { /** * Суммы к списанию по обходному листу — 5 независимых сумм (компактная сетка - * полей, см. WRITE_OFF_CATEGORIES). Обучение сюда НЕ входит — для него уже - * есть отдельное поле `TrainingAmount` (см. выше) с ДРУГИМ поведением - * (правит только КДП, не любой согласующий); заводить второе «Обучение» с - * иным поведением рядом только путало (полностью совпадающие подписи). - * «Сырые» цифры без пробелов, как TrainingAmount (см. MoneyInput). Пусто = - * не заполнено. КАЖДАЯ — независимая: заполнить может любой активный - * согласующий (см. ClearanceView.canEditWriteOffs), но как только сумма - * непустая — блокируется НАВСЕГДА, для всех (включая того, кто её ввёл). - * Признак блокировки — сама непустая сумма, отдельного флага в списке нет. + * полей, см. WRITE_OFF_CATEGORIES). Обучение сюда НЕ входит — у него другая + * доступность (см. TrainingAmount выше): эти пять доступны ЛЮБОМУ активному + * согласующему и блокируются НАВСЕГДА после первого ввода (включая того, кто + * ввёл) — признак блокировки: сама непустая сумма, отдельного флага в + * списке нет. «Сырые» цифры без пробелов (см. MoneyInput). Пусто = не + * заполнено. * * «За что конкретно» — своего поля НЕТ: заполнил хоть одну сумму — становится * обязательным СТАНДАРТНЫЙ комментарий решения (DecisionPanel, «Ваше решение»), @@ -62,9 +69,16 @@ export interface IFormData { WriteOffAccountableAmount: string; WriteOffNmaAmount: string; WriteOffTmcAmount: string; + /** + * Колонка ХРАНЕНИЯ для TrainingAmount (см. выше) — НЕ участвует в + * WRITE_OFF_CATEGORIES/WriteOffFieldKey и в общем механизме «любой + * согласующий, блокировка навсегда»: доступность и незаблокируемость у + * «Обучение: сумма» остались как были, изменилась только SharePoint-колонка. + */ + WriteOffTrainingAmount: string; } -/** Имена всех 5 сумм к списанию — см. IFormData.WriteOff*Amount. */ +/** Имена 5 сумм к списанию (не считая WriteOffTrainingAmount — у него своя доступность) — см. IFormData.WriteOff*Amount. */ export type WriteOffFieldKey = | 'WriteOffAssetsAmount' | 'WriteOffPpeAmount' @@ -121,6 +135,7 @@ export const EMPTY_DATA: IFormData = { WriteOffAssetsAmount: '', WriteOffPpeAmount: '', WriteOffAccountableAmount: '', + WriteOffTrainingAmount: '', WriteOffNmaAmount: '', WriteOffTmcAmount: '' }; diff --git a/src/webparts/requestPortal/forms/Clearance/provisioning.json b/src/webparts/requestPortal/forms/Clearance/provisioning.json index 7c82054..d9b321c 100644 --- a/src/webparts/requestPortal/forms/Clearance/provisioning.json +++ b/src/webparts/requestPortal/forms/Clearance/provisioning.json @@ -19,6 +19,7 @@ { "internalName": "WriteOffAssetsAmount", "title": "Сумма к списанию: ОС", "type": "Number", "decimals": 2 }, { "internalName": "WriteOffPpeAmount", "title": "Сумма к списанию: СИЗ", "type": "Number", "decimals": 2 }, { "internalName": "WriteOffAccountableAmount", "title": "Сумма к списанию: подотчётная сумма", "type": "Number", "decimals": 2 }, + { "internalName": "WriteOffTrainingAmount", "title": "Сумма к списанию: обучение", "type": "Number", "decimals": 2 }, { "internalName": "WriteOffNmaAmount", "title": "Сумма к списанию: НМА", "type": "Number", "decimals": 2 }, { "internalName": "WriteOffTmcAmount", "title": "Сумма к списанию: ТМЦ", "type": "Number", "decimals": 2 } ] diff --git a/src/webparts/requestPortal/forms/Clearance/sections/RequestSection.tsx b/src/webparts/requestPortal/forms/Clearance/sections/RequestSection.tsx index 2dfca87..1043212 100644 --- a/src/webparts/requestPortal/forms/Clearance/sections/RequestSection.tsx +++ b/src/webparts/requestPortal/forms/Clearance/sections/RequestSection.tsx @@ -90,24 +90,22 @@ export const RequestSection: React.FC = ({ readOnly={hrRecordsEditable ? false : undefined} /> - {/* Суммы к списанию — TrainingAmount («Обучение: сумма», правит только - КДП) ПОДВЕДЕНА под тот же заголовок, что и 5 сумм ниже (правит любой - активный согласующий, см. WRITE_OFF_CATEGORIES) — визуально одна - группа «Суммы к списанию», хотя права на них разные. Без заголовка - «Обучение» из WRITE_OFF_CATEGORIES читалось бы дублем «Обучение: - сумма» — их когда-то было два похожих поля, оставили одно (TrainingAmount, - см. README «Отдельный механизм от TrainingAmount»), но заголовок группы - пригодился и без второго «Обучение». - Явный readOnly={false} у TrainingAmount перебивает read-only контекст - просмотра (профильный согласующий правит своё поле); undefined — - обычное поведение из FormMode. У сумм из WRITE_OFF_CATEGORIES — - заполнить может ЛЮБОЙ активный согласующий (не только профильный), но - как только сумма указана, блокируется навсегда для всех, поэтому у - незаблокированных (writeOffEditableKeys.has(cat.key)) readOnly={false}, - у остальных — обычное поведение FormMode. «За что конкретно» — своего - поля здесь нет: заполнил хоть одну сумму — обязательным становится - СТАНДАРТНЫЙ комментарий решения в DecisionPanel («Ваше решение»), см. - ClearanceView (commentRequired/commentPlaceholder). */} + {/* Суммы к списанию — TrainingAmount («Обучение: сумма») ПОДВЕДЕНА под + тот же заголовок, что и 5 сумм ниже, — визуально одна группа «Суммы к + списанию», хотя ДОСТУПНОСТЬ у них РАЗНАЯ: + - TrainingAmount — как раньше: правит только КДП (hrRecordsEditable), + без блокировки после ввода; хранится в WriteOffTrainingAmount (не + одноимённой колонке, см. IFormData.TrainingAmount), но это деталь + хранения, на форму не влияет; + - WRITE_OFF_CATEGORIES (5 сумм) — заполнить может ЛЮБОЙ активный + согласующий (не только профильный), но как только сумма указана, + блокируется НАВСЕГДА для всех, поэтому у незаблокированных + (writeOffEditableKeys.has(cat.key)) readOnly={false}, у остальных + — обычное поведение FormMode. «За что конкретно» у них — своего + поля нет: заполнил хоть одну сумму — обязательным становится + СТАНДАРТНЫЙ комментарий решения в DecisionPanel («Ваше решение»), + см. ClearanceView (commentRequired/commentPlaceholder). У + TrainingAmount этого правила нет — комментарий не требует. */} {writeOffEditableKeys &&
Суммы к списанию
} ; /** @@ -56,6 +56,11 @@ export interface IApproverEditableFields { * Пишутся ровно переданные поля: у каждого отдела свой набор прав, и лишние * ключи затёрли бы чужие отметки значениями из формы этого согласующего. * + * fields.TrainingAmount пишется в колонку WriteOffTrainingAmount, а НЕ в + * одноимённый TrainingAmount — см. IFormData.TrainingAmount; доступность + * (только КДП, без блокировки навсегда) не изменилась, изменилась только + * колонка хранения. + * * listId — партиция ОБНОВЛЯЕМОЙ заявки (см. loadClearanceRequest); не задан — * текущая партиция, как раньше. */ @@ -69,7 +74,7 @@ export async function updateApproverFields( if (fields.ParkingCard !== undefined) item.ParkingCard = fields.ParkingCard; if (fields.TrainingAmount !== undefined) { const amount = Number(fields.TrainingAmount); - item.TrainingAmount = + item.WriteOffTrainingAmount = Number.isFinite(amount) && fields.TrainingAmount.trim() !== '' ? amount : null; } if (Object.keys(item).length === 0) return; @@ -87,7 +92,13 @@ function buildTitle(employeeName: string): string { return `Обходной лист — ${employeeName} — ${pad2(now.getDate())}.${pad2(now.getMonth() + 1)}.${now.getFullYear()}`; } -/** Поля элемента списка данных из модели формы (+ Employee по Id). */ +/** + * Поля элемента списка данных из модели формы (+ Employee по Id). + * + * Обычно data.TrainingAmount тут пуст (заявитель его не заполняет, только КДП + * на своём шаге — см. updateApproverFields), но пишем на случай редактирования + * уже созданной заявки: колонка WriteOffTrainingAmount, та же, что и там. + */ function buildItem(data: IFormData, status: string, employeeId: number, employeeName: string): Record { const amount = Number(data.TrainingAmount); return { @@ -99,7 +110,7 @@ function buildItem(data: IFormData, status: string, employeeId: number, employee TerminationDate: data.TerminationDate || null, ParkingCard: data.ParkingCard, AccessCard: data.AccessCard, - TrainingAmount: Number.isFinite(amount) && data.TrainingAmount.trim() !== '' ? amount : null, + WriteOffTrainingAmount: Number.isFinite(amount) && data.TrainingAmount.trim() !== '' ? amount : null, Organization: data.Organization || null }; }