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`) объединяет визуально ДВЕ РАЗНЫЕ по правам вещи, идущие сразу
|
`.writeOffLabel`) объединяет визуально ДВЕ РАЗНЫЕ по правам вещи, идущие сразу
|
||||||
за подписью:
|
за подписью:
|
||||||
|
|
||||||
1. **`TrainingAmount`** («Обучение: сумма») — уже существовавшее поле, правит
|
1. **`TrainingAmount`** («Обучение: сумма») — доступность КАК И РАНЬШЕ: правит
|
||||||
только КДП (`hrRecordsEditable`, `approverRights.ts`), сумма НЕ блокируется
|
только КДП (`hrRecordsEditable`, `approverRights.ts`), сумма НЕ блокируется
|
||||||
после ввода. Заголовок рисуется независимо от него (`writeOffEditableKeys &&
|
после ввода, никак не участвует в WRITE_OFF_CATEGORIES/`writeOffTouchedCategories`
|
||||||
<div>…</div>`, а само поле — БЕЗ этого условия), поэтому на форме подачи
|
(не требует комментария решения). Изменилась ТОЛЬКО колонка хранения — пишется
|
||||||
(`ClearanceForm`, `writeOffEditableKeys` не передан) поле видно и без заголовка.
|
и читается через `WriteOffTrainingAmount` (`ClearanceView.saveApproverFields`,
|
||||||
|
`submit.ts.buildItem`, `load.ts`), а не через одноимённую `TrainingAmount` —
|
||||||
|
та осталась в схеме списка неиспользуемой. Заголовок рисуется независимо от
|
||||||
|
поля (`writeOffEditableKeys && <div>…</div>`, а само поле — БЕЗ этого условия),
|
||||||
|
поэтому на форме подачи (`ClearanceForm`, `writeOffEditableKeys` не передан)
|
||||||
|
поле видно и без заголовка.
|
||||||
2. **5 НЕЗАВИСИМЫХ сумм**, компактной сеткой (короткие подписи, обычная
|
2. **5 НЕЗАВИСИМЫХ сумм**, компактной сеткой (короткие подписи, обычная
|
||||||
2-колоночная сетка секции), колонки `WriteOff{Assets,Ppe,Accountable,Nma,
|
2-колоночная сетка секции), колонки `WriteOff{Assets,Ppe,Accountable,Nma,
|
||||||
Tmc}Amount` (см. `model.ts WRITE_OFF_CATEGORIES`): «ОС», «СИЗ», «Подотчётная
|
Tmc}Amount` (см. `model.ts WRITE_OFF_CATEGORIES`): «ОС», «СИЗ», «Подотчётная
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ export async function loadClearanceRequest(itemId: number, listId?: string): Pro
|
|||||||
'TerminationDate',
|
'TerminationDate',
|
||||||
'ParkingCard',
|
'ParkingCard',
|
||||||
'AccessCard',
|
'AccessCard',
|
||||||
'TrainingAmount',
|
|
||||||
'Organization',
|
'Organization',
|
||||||
'DismissalUrl',
|
'DismissalUrl',
|
||||||
'WriteOffAssetsAmount',
|
'WriteOffAssetsAmount',
|
||||||
'WriteOffPpeAmount',
|
'WriteOffPpeAmount',
|
||||||
'WriteOffAccountableAmount',
|
'WriteOffAccountableAmount',
|
||||||
|
'WriteOffTrainingAmount',
|
||||||
'WriteOffNmaAmount',
|
'WriteOffNmaAmount',
|
||||||
'WriteOffTmcAmount',
|
'WriteOffTmcAmount',
|
||||||
'RequestStatus',
|
'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 employee = item.Employee as { Title?: string; EMail?: string } | undefined;
|
||||||
const termination = (item.TerminationDate as string) || '';
|
const termination = (item.TerminationDate as string) || '';
|
||||||
const amount = item.TrainingAmount;
|
|
||||||
|
|
||||||
const data: IFormData = {
|
const data: IFormData = {
|
||||||
...EMPTY_DATA,
|
...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
|
TerminationDate: termination.slice(0, 10), // ISO → YYYY-MM-DD для input type=date
|
||||||
ParkingCard: !!item.ParkingCard,
|
ParkingCard: !!item.ParkingCard,
|
||||||
AccessCard: !!item.AccessCard,
|
AccessCard: !!item.AccessCard,
|
||||||
TrainingAmount: amount === null || amount === undefined ? '' : String(amount),
|
// Хранится в WriteOffTrainingAmount, не в одноимённом TrainingAmount — см.
|
||||||
|
// IFormData.TrainingAmount.
|
||||||
|
TrainingAmount: numberField(item.WriteOffTrainingAmount),
|
||||||
Organization: (item.Organization as string) || '',
|
Organization: (item.Organization as string) || '',
|
||||||
// Гиперссылка приходит объектом {Url, Description} — модели нужен только адрес.
|
// Гиперссылка приходит объектом {Url, Description} — модели нужен только адрес.
|
||||||
DismissalUrl: (item.DismissalUrl as { Url?: string } | undefined)?.Url || '',
|
DismissalUrl: (item.DismissalUrl as { Url?: string } | undefined)?.Url || '',
|
||||||
|
|||||||
@@ -23,7 +23,17 @@ export interface IFormData {
|
|||||||
TerminationDate: string;
|
TerminationDate: string;
|
||||||
ParkingCard: boolean;
|
ParkingCard: boolean;
|
||||||
AccessCard: boolean;
|
AccessCard: boolean;
|
||||||
/** Сумма обучения — «сырые» цифры без пробелов (см. MoneyInput). Пусто = не указана. */
|
/**
|
||||||
|
* Сумма обучения — «сырые» цифры без пробелов (см. MoneyInput). Пусто = не
|
||||||
|
* указана. Доступность — КАК И РАНЬШЕ: правит только начальник КДП
|
||||||
|
* (`hrRecordsEditable`, `approverRights.ts`), не блокируется после ввода (в
|
||||||
|
* отличие от WRITE_OFF_CATEGORIES ниже). Единственное, что изменилось, —
|
||||||
|
* КОЛОНКА ХРАНЕНИЯ: пишется и читается через `WriteOffTrainingAmount` (см.
|
||||||
|
* ClearanceView.saveApproverFields/load.ts), а не через одноимённый
|
||||||
|
* `TrainingAmount` в списке — тот остался в схеме неиспользуемым (как и
|
||||||
|
* другие столбцы, из которых код этой сессии перестал писать/читать, см.
|
||||||
|
* README «Убрано поле…»).
|
||||||
|
*/
|
||||||
TrainingAmount: string;
|
TrainingAmount: string;
|
||||||
/**
|
/**
|
||||||
* Организации сотрудника (`\n`-joined «Компания — должность» на строку) —
|
* Организации сотрудника (`\n`-joined «Компания — должность» на строку) —
|
||||||
@@ -40,15 +50,12 @@ export interface IFormData {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Суммы к списанию по обходному листу — 5 независимых сумм (компактная сетка
|
* Суммы к списанию по обходному листу — 5 независимых сумм (компактная сетка
|
||||||
* полей, см. WRITE_OFF_CATEGORIES). Обучение сюда НЕ входит — для него уже
|
* полей, см. WRITE_OFF_CATEGORIES). Обучение сюда НЕ входит — у него другая
|
||||||
* есть отдельное поле `TrainingAmount` (см. выше) с ДРУГИМ поведением
|
* доступность (см. TrainingAmount выше): эти пять доступны ЛЮБОМУ активному
|
||||||
* (правит только КДП, не любой согласующий); заводить второе «Обучение» с
|
* согласующему и блокируются НАВСЕГДА после первого ввода (включая того, кто
|
||||||
* иным поведением рядом только путало (полностью совпадающие подписи).
|
* ввёл) — признак блокировки: сама непустая сумма, отдельного флага в
|
||||||
* «Сырые» цифры без пробелов, как TrainingAmount (см. MoneyInput). Пусто =
|
* списке нет. «Сырые» цифры без пробелов (см. MoneyInput). Пусто = не
|
||||||
* не заполнено. КАЖДАЯ — независимая: заполнить может любой активный
|
* заполнено.
|
||||||
* согласующий (см. ClearanceView.canEditWriteOffs), но как только сумма
|
|
||||||
* непустая — блокируется НАВСЕГДА, для всех (включая того, кто её ввёл).
|
|
||||||
* Признак блокировки — сама непустая сумма, отдельного флага в списке нет.
|
|
||||||
*
|
*
|
||||||
* «За что конкретно» — своего поля НЕТ: заполнил хоть одну сумму — становится
|
* «За что конкретно» — своего поля НЕТ: заполнил хоть одну сумму — становится
|
||||||
* обязательным СТАНДАРТНЫЙ комментарий решения (DecisionPanel, «Ваше решение»),
|
* обязательным СТАНДАРТНЫЙ комментарий решения (DecisionPanel, «Ваше решение»),
|
||||||
@@ -62,9 +69,16 @@ export interface IFormData {
|
|||||||
WriteOffAccountableAmount: string;
|
WriteOffAccountableAmount: string;
|
||||||
WriteOffNmaAmount: string;
|
WriteOffNmaAmount: string;
|
||||||
WriteOffTmcAmount: string;
|
WriteOffTmcAmount: string;
|
||||||
|
/**
|
||||||
|
* Колонка ХРАНЕНИЯ для TrainingAmount (см. выше) — НЕ участвует в
|
||||||
|
* WRITE_OFF_CATEGORIES/WriteOffFieldKey и в общем механизме «любой
|
||||||
|
* согласующий, блокировка навсегда»: доступность и незаблокируемость у
|
||||||
|
* «Обучение: сумма» остались как были, изменилась только SharePoint-колонка.
|
||||||
|
*/
|
||||||
|
WriteOffTrainingAmount: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Имена всех 5 сумм к списанию — см. IFormData.WriteOff*Amount. */
|
/** Имена 5 сумм к списанию (не считая WriteOffTrainingAmount — у него своя доступность) — см. IFormData.WriteOff*Amount. */
|
||||||
export type WriteOffFieldKey =
|
export type WriteOffFieldKey =
|
||||||
| 'WriteOffAssetsAmount'
|
| 'WriteOffAssetsAmount'
|
||||||
| 'WriteOffPpeAmount'
|
| 'WriteOffPpeAmount'
|
||||||
@@ -121,6 +135,7 @@ export const EMPTY_DATA: IFormData = {
|
|||||||
WriteOffAssetsAmount: '',
|
WriteOffAssetsAmount: '',
|
||||||
WriteOffPpeAmount: '',
|
WriteOffPpeAmount: '',
|
||||||
WriteOffAccountableAmount: '',
|
WriteOffAccountableAmount: '',
|
||||||
|
WriteOffTrainingAmount: '',
|
||||||
WriteOffNmaAmount: '',
|
WriteOffNmaAmount: '',
|
||||||
WriteOffTmcAmount: ''
|
WriteOffTmcAmount: ''
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
{ "internalName": "WriteOffAssetsAmount", "title": "Сумма к списанию: ОС", "type": "Number", "decimals": 2 },
|
{ "internalName": "WriteOffAssetsAmount", "title": "Сумма к списанию: ОС", "type": "Number", "decimals": 2 },
|
||||||
{ "internalName": "WriteOffPpeAmount", "title": "Сумма к списанию: СИЗ", "type": "Number", "decimals": 2 },
|
{ "internalName": "WriteOffPpeAmount", "title": "Сумма к списанию: СИЗ", "type": "Number", "decimals": 2 },
|
||||||
{ "internalName": "WriteOffAccountableAmount", "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": "WriteOffNmaAmount", "title": "Сумма к списанию: НМА", "type": "Number", "decimals": 2 },
|
||||||
{ "internalName": "WriteOffTmcAmount", "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}
|
readOnly={hrRecordsEditable ? false : undefined}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
{/* Суммы к списанию — TrainingAmount («Обучение: сумма», правит только
|
{/* Суммы к списанию — TrainingAmount («Обучение: сумма») ПОДВЕДЕНА под
|
||||||
КДП) ПОДВЕДЕНА под тот же заголовок, что и 5 сумм ниже (правит любой
|
тот же заголовок, что и 5 сумм ниже, — визуально одна группа «Суммы к
|
||||||
активный согласующий, см. WRITE_OFF_CATEGORIES) — визуально одна
|
списанию», хотя ДОСТУПНОСТЬ у них РАЗНАЯ:
|
||||||
группа «Суммы к списанию», хотя права на них разные. Без заголовка
|
- TrainingAmount — как раньше: правит только КДП (hrRecordsEditable),
|
||||||
«Обучение» из WRITE_OFF_CATEGORIES читалось бы дублем «Обучение:
|
без блокировки после ввода; хранится в WriteOffTrainingAmount (не
|
||||||
сумма» — их когда-то было два похожих поля, оставили одно (TrainingAmount,
|
одноимённой колонке, см. IFormData.TrainingAmount), но это деталь
|
||||||
см. README «Отдельный механизм от TrainingAmount»), но заголовок группы
|
хранения, на форму не влияет;
|
||||||
пригодился и без второго «Обучение».
|
- WRITE_OFF_CATEGORIES (5 сумм) — заполнить может ЛЮБОЙ активный
|
||||||
Явный readOnly={false} у TrainingAmount перебивает read-only контекст
|
согласующий (не только профильный), но как только сумма указана,
|
||||||
просмотра (профильный согласующий правит своё поле); undefined —
|
блокируется НАВСЕГДА для всех, поэтому у незаблокированных
|
||||||
обычное поведение из FormMode. У сумм из WRITE_OFF_CATEGORIES —
|
(writeOffEditableKeys.has(cat.key)) readOnly={false}, у остальных
|
||||||
заполнить может ЛЮБОЙ активный согласующий (не только профильный), но
|
— обычное поведение FormMode. «За что конкретно» у них — своего
|
||||||
как только сумма указана, блокируется навсегда для всех, поэтому у
|
поля нет: заполнил хоть одну сумму — обязательным становится
|
||||||
незаблокированных (writeOffEditableKeys.has(cat.key)) readOnly={false},
|
СТАНДАРТНЫЙ комментарий решения в DecisionPanel («Ваше решение»),
|
||||||
у остальных — обычное поведение FormMode. «За что конкретно» — своего
|
см. ClearanceView (commentRequired/commentPlaceholder). У
|
||||||
поля здесь нет: заполнил хоть одну сумму — обязательным становится
|
TrainingAmount этого правила нет — комментарий не требует. */}
|
||||||
СТАНДАРТНЫЙ комментарий решения в DecisionPanel («Ваше решение»), см.
|
|
||||||
ClearanceView (commentRequired/commentPlaceholder). */}
|
|
||||||
{writeOffEditableKeys && <div className={styles.writeOffLabel}>Суммы к списанию</div>}
|
{writeOffEditableKeys && <div className={styles.writeOffLabel}>Суммы к списанию</div>}
|
||||||
<FormField label="Обучение: сумма" htmlFor="TrainingAmount">
|
<FormField label="Обучение: сумма" htmlFor="TrainingAmount">
|
||||||
<MoneyInput
|
<MoneyInput
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ISaveItemResult, resolveDataList, saveRequestItem } from '../../shared/
|
|||||||
import { ensureEmployee } from '../../shared/employee';
|
import { ensureEmployee } from '../../shared/employee';
|
||||||
import { FORM_ID, IFormData, WriteOffFieldKey } from './model';
|
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>;
|
export type IWriteOffFields = Pick<IFormData, WriteOffFieldKey>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,6 +56,11 @@ export interface IApproverEditableFields {
|
|||||||
* Пишутся ровно переданные поля: у каждого отдела свой набор прав, и лишние
|
* Пишутся ровно переданные поля: у каждого отдела свой набор прав, и лишние
|
||||||
* ключи затёрли бы чужие отметки значениями из формы этого согласующего.
|
* ключи затёрли бы чужие отметки значениями из формы этого согласующего.
|
||||||
*
|
*
|
||||||
|
* fields.TrainingAmount пишется в колонку WriteOffTrainingAmount, а НЕ в
|
||||||
|
* одноимённый TrainingAmount — см. IFormData.TrainingAmount; доступность
|
||||||
|
* (только КДП, без блокировки навсегда) не изменилась, изменилась только
|
||||||
|
* колонка хранения.
|
||||||
|
*
|
||||||
* listId — партиция ОБНОВЛЯЕМОЙ заявки (см. loadClearanceRequest); не задан —
|
* listId — партиция ОБНОВЛЯЕМОЙ заявки (см. loadClearanceRequest); не задан —
|
||||||
* текущая партиция, как раньше.
|
* текущая партиция, как раньше.
|
||||||
*/
|
*/
|
||||||
@@ -69,7 +74,7 @@ export async function updateApproverFields(
|
|||||||
if (fields.ParkingCard !== undefined) item.ParkingCard = fields.ParkingCard;
|
if (fields.ParkingCard !== undefined) item.ParkingCard = fields.ParkingCard;
|
||||||
if (fields.TrainingAmount !== undefined) {
|
if (fields.TrainingAmount !== undefined) {
|
||||||
const amount = Number(fields.TrainingAmount);
|
const amount = Number(fields.TrainingAmount);
|
||||||
item.TrainingAmount =
|
item.WriteOffTrainingAmount =
|
||||||
Number.isFinite(amount) && fields.TrainingAmount.trim() !== '' ? amount : null;
|
Number.isFinite(amount) && fields.TrainingAmount.trim() !== '' ? amount : null;
|
||||||
}
|
}
|
||||||
if (Object.keys(item).length === 0) return;
|
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()}`;
|
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> {
|
function buildItem(data: IFormData, status: string, employeeId: number, employeeName: string): Record<string, unknown> {
|
||||||
const amount = Number(data.TrainingAmount);
|
const amount = Number(data.TrainingAmount);
|
||||||
return {
|
return {
|
||||||
@@ -99,7 +110,7 @@ function buildItem(data: IFormData, status: string, employeeId: number, employee
|
|||||||
TerminationDate: data.TerminationDate || null,
|
TerminationDate: data.TerminationDate || null,
|
||||||
ParkingCard: data.ParkingCard,
|
ParkingCard: data.ParkingCard,
|
||||||
AccessCard: data.AccessCard,
|
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
|
Organization: data.Organization || null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user