fix: «Обучение: сумма» — та же доступность, но пишет в WriteOffTrainingAmount
Правка предыдущего коммита: там «Обучение» ошибочно стало одной из WRITE_OFF_CATEGORIES (доступно любому активному согласующему, блокируется навсегда) — доступность должна была остаться КАК БЫЛА (только КДП, без блокировки), просто колонка хранения меняется на WriteOffTrainingAmount. Откат: WRITE_OFF_CATEGORIES снова 5 категорий (без «Обучение»). TrainingAmount остаётся отдельным полем формы с прежней доступностью (hrRecordsEditable) — просто updateApproverFields/buildItem пишут его в колонку WriteOffTrainingAmount, а load.ts читает оттуда же. Одноимённая колонка TrainingAmount в схеме списка больше не используется (как и другие столбцы, из которых код этой сессии перестал писать/читать). Визуально TrainingAmount остаётся под общим заголовком «Суммы к списанию» (из прошлого коммита) — только права и правило блокировки у него свои. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,11 +59,16 @@ RequestTypeId» в `docs/forms.md`). Тип уже зарегистрирова
|
||||
`.writeOffLabel`) объединяет визуально ДВЕ РАЗНЫЕ по правам вещи, идущие сразу
|
||||
за подписью:
|
||||
|
||||
1. **`TrainingAmount`** («Обучение: сумма») — уже существовавшее поле, правит
|
||||
1. **`TrainingAmount`** («Обучение: сумма») — доступность КАК И РАНЬШЕ: правит
|
||||
только КДП (`hrRecordsEditable`, `approverRights.ts`), сумма НЕ блокируется
|
||||
после ввода. Заголовок рисуется независимо от него (`writeOffEditableKeys &&
|
||||
<div>…</div>`, а само поле — БЕЗ этого условия), поэтому на форме подачи
|
||||
(`ClearanceForm`, `writeOffEditableKeys` не передан) поле видно и без заголовка.
|
||||
после ввода, никак не участвует в WRITE_OFF_CATEGORIES/`writeOffTouchedCategories`
|
||||
(не требует комментария решения). Изменилась ТОЛЬКО колонка хранения — пишется
|
||||
и читается через `WriteOffTrainingAmount` (`ClearanceView.saveApproverFields`,
|
||||
`submit.ts.buildItem`, `load.ts`), а не через одноимённую `TrainingAmount` —
|
||||
та осталась в схеме списка неиспользуемой. Заголовок рисуется независимо от
|
||||
поля (`writeOffEditableKeys && <div>…</div>`, а само поле — БЕЗ этого условия),
|
||||
поэтому на форме подачи (`ClearanceForm`, `writeOffEditableKeys` не передан)
|
||||
поле видно и без заголовка.
|
||||
2. **5 НЕЗАВИСИМЫХ сумм**, компактной сеткой (короткие подписи, обычная
|
||||
2-колоночная сетка секции), колонки `WriteOff{Assets,Ppe,Accountable,Nma,
|
||||
Tmc}Amount` (см. `model.ts WRITE_OFF_CATEGORIES`): «ОС», «СИЗ», «Подотчётная
|
||||
|
||||
@@ -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 || '',
|
||||
|
||||
@@ -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: ''
|
||||
};
|
||||
|
||||
@@ -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 }
|
||||
]
|
||||
|
||||
@@ -90,24 +90,22 @@ export const RequestSection: React.FC<IRequestSectionProps> = ({
|
||||
readOnly={hrRecordsEditable ? false : undefined}
|
||||
/>
|
||||
</FormField>
|
||||
{/* Суммы к списанию — 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 && <div className={styles.writeOffLabel}>Суммы к списанию</div>}
|
||||
<FormField label="Обучение: сумма" htmlFor="TrainingAmount">
|
||||
<MoneyInput
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ISaveItemResult, resolveDataList, saveRequestItem } from '../../shared/
|
||||
import { ensureEmployee } from '../../shared/employee';
|
||||
import { FORM_ID, IFormData, WriteOffFieldKey } from './model';
|
||||
|
||||
/** 6 сумм к списанию — см. model.ts WRITE_OFF_CATEGORIES. «За что конкретно» — не здесь, см. IFormData. */
|
||||
/** 5 сумм к списанию — см. model.ts WRITE_OFF_CATEGORIES. «За что конкретно» — не здесь, см. IFormData. */
|
||||
export type IWriteOffFields = Pick<IFormData, WriteOffFieldKey>;
|
||||
|
||||
/**
|
||||
@@ -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<string, unknown> {
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user