fix: «за что списание» — переиспользовать стандартный комментарий решения
Правка предыдущего коммита: там для «за что конкретно» завёл отдельное поле WriteOffComment (с собственной колонкой в списке) — избыточно, раз на экране уже есть стандартный комментарий решения («Ваше решение» в DecisionPanel), которым и так сопровождают Согласовать/Вернуть/Отклонить. Теперь: DecisionPanel получил два новых пропа — commentRequired (сверх обычного «обязателен при возврате/отклонении» — можно потребовать его на ЛЮБОЕ решение) и commentPlaceholder (переопределить текст подсказки). ClearanceView передаёt их, когда есть незаблокированная сумма к списанию с непустым значением (writeOffTouchedCategories) — комментарий обязателен, а плейсхолдер называет, за какие категории писать («Введите, за что сумма: ОС, ТМЦ»). Записывается он туда же, где и всегда — в строку маршрута этого согласующего (Comment списка согласования), без нового поля на данных заявки. WriteOffComment (поле, столбец в манифесте, вся связанная логика) убран. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -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` | Перемещение сотрудников | Отдел кадров | — | — | — | — | ⏳ заглушка (плитка есть, форма не сделана) |
|
||||
|
||||
|
||||
@@ -169,12 +169,19 @@ const ClearanceView: React.FC<IClearanceViewProps> = ({ context, itemId, formKey
|
||||
[canEditWriteOffs, lockedWriteOffs]
|
||||
);
|
||||
|
||||
// Категории, чью сумму заполняют ПРЯМО СЕЙЧАС (редактируема и не пуста) — от
|
||||
// них зависит, обязателен ли комментарий решения (DecisionPanel, «Ваше
|
||||
// решение») и что подставить в его плейсхолдер. «За что конкретно» здесь не
|
||||
// хранится отдельным полем — вместо этого требуем стандартный комментарий
|
||||
// решения, который и так сопровождает Согласовать/Вернуть/Отклонить (см.
|
||||
// commentRequired/commentPlaceholder у DecisionPanel ниже).
|
||||
const writeOffTouchedCategories = WRITE_OFF_CATEGORIES.filter(
|
||||
(cat) => editableWriteOffKeys.has(cat.key) && data[cat.amountField].trim() !== ''
|
||||
);
|
||||
|
||||
// Пишем ТОЛЬКО те отметки, на которые у согласующего есть права: иначе он
|
||||
// затёр бы чужие поля значениями, которые видел у себя на экране. Суммы к
|
||||
// списанию — только НЕПУСТЫЕ и только среди editableWriteOffKeys; комментарий
|
||||
// ОДИН на все суммы (IFormData.WriteOffComment, не пара на каждую категорию)
|
||||
// — если хоть одна из редактируемых сумм не пуста, комментарий обязателен
|
||||
// (бросает ДО записи чего бы то ни было).
|
||||
// списанию — только НЕПУСТЫЕ и только среди editableWriteOffKeys.
|
||||
const saveApproverFields = useCallback(async (): Promise<void> => {
|
||||
const fields: Partial<IApproverEditableFields> = {};
|
||||
if (hrRecordsEditable) {
|
||||
@@ -185,26 +192,16 @@ const ClearanceView: React.FC<IClearanceViewProps> = ({ context, itemId, formKey
|
||||
fields.ParkingCard = data.ParkingCard;
|
||||
}
|
||||
|
||||
const touched = WRITE_OFF_CATEGORIES.filter(
|
||||
(cat) => editableWriteOffKeys.has(cat.key) && data[cat.amountField].trim() !== ''
|
||||
);
|
||||
const writeOffFields: Partial<IWriteOffFields> = {};
|
||||
if (touched.length > 0) {
|
||||
const comment = data.WriteOffComment.trim();
|
||||
if (!comment) {
|
||||
throw new Error('Укажите комментарий — за что списываются суммы.');
|
||||
}
|
||||
for (const cat of touched) {
|
||||
writeOffFields[cat.amountField] = data[cat.amountField];
|
||||
}
|
||||
writeOffFields.WriteOffComment = comment;
|
||||
for (const cat of writeOffTouchedCategories) {
|
||||
writeOffFields[cat.amountField] = data[cat.amountField];
|
||||
}
|
||||
|
||||
await updateApproverFields(itemId, fields, listId);
|
||||
if (Object.keys(writeOffFields).length > 0) {
|
||||
await updateWriteOffFields(itemId, writeOffFields, listId);
|
||||
}
|
||||
}, [itemId, listId, hrRecordsEditable, parkingEditable, editableWriteOffKeys, data]);
|
||||
}, [itemId, listId, hrRecordsEditable, parkingEditable, writeOffTouchedCategories, data]);
|
||||
|
||||
const handleSaveFields = async (): Promise<void> => {
|
||||
setSaveState('saving');
|
||||
@@ -316,6 +313,17 @@ const ClearanceView: React.FC<IClearanceViewProps> = ({ context, itemId, formKey
|
||||
// Отметки сохраняются вместе с решением — иначе согласующий, забывший
|
||||
// нажать «Сохранить», согласовал бы заявку со старыми значениями.
|
||||
onBeforeDecide={canEditAnything ? saveApproverFields : undefined}
|
||||
// Заполнил сумму к списанию — комментарий решения обязателен (не
|
||||
// только при возврате/отклонении, как обычно): это и есть «за что
|
||||
// конкретно», отдельного поля под него нет (см. writeOffTouchedCategories).
|
||||
commentRequired={writeOffTouchedCategories.length > 0}
|
||||
commentPlaceholder={
|
||||
writeOffTouchedCategories.length > 0
|
||||
? `Введите, за что сумма${
|
||||
writeOffTouchedCategories.length > 1 ? 'ы' : ''
|
||||
}: ${writeOffTouchedCategories.map((c) => c.label).join(', ')}`
|
||||
: undefined
|
||||
}
|
||||
onDecided={(newStatus) => {
|
||||
setStatus(newStatus);
|
||||
setDecisionSeq((n) => n + 1);
|
||||
|
||||
@@ -58,7 +58,7 @@ RequestTypeId» в `docs/forms.md`). Тип уже зарегистрирова
|
||||
6 НЕЗАВИСИМЫХ сумм, компактной сеткой (короткие подписи, обычная 2-колоночная
|
||||
сетка секции), колонки `WriteOff{Assets,Ppe,Accountable,Training,Nma,Tmc}Amount`
|
||||
(см. `model.ts WRITE_OFF_CATEGORIES`): «ОС», «СИЗ», «Подотчётная сумма»,
|
||||
«Обучение», «НМА», «ТМЦ». Плюс ОДИН общий комментарий на всех — `WriteOffComment`.
|
||||
«Обучение», «НМА», «ТМЦ».
|
||||
|
||||
- **Не привязаны к должности**, в отличие от отметок выше: заполнить может ЛЮБОЙ
|
||||
согласующий, у которого сейчас активный шаг (`ClearanceView.canEditWriteOffs`,
|
||||
@@ -68,26 +68,27 @@ RequestTypeId» в `docs/forms.md`). Тип уже зарегистрирова
|
||||
Признак блокировки — сама непустая сумма на МОМЕНТ ЗАГРУЗКИ страницы
|
||||
(`ClearanceView.lockedWriteOffs`, снимок при загрузке, а не реактивно от
|
||||
текущего `data` — иначе поле блокировалось бы прямо во время набора цифр).
|
||||
- **Комментарий («за что конкретно») — ОДИН на ВСЕ суммы**, не пара на каждую
|
||||
категорию: одна Note-колонка (`WriteOffComment`), не шесть. Read-only, пока
|
||||
нечего заполнять (показывает то, что уже написали раньше); становится
|
||||
редактируемым и ОБЯЗАТЕЛЬНЫМ ровно тогда, когда хоть одна редактируемая сумма
|
||||
не пуста (`writeOffTouchedCategories` в `RequestSection`) — `saveApproverFields`
|
||||
бросает ДО записи чего бы то ни было, если сумма есть, а комментарий пуст.
|
||||
Плейсхолдер называет категории, для которых он сейчас пишется («Введите, за
|
||||
что сумма: ОС, ТМЦ»). Следующий согласующий, заполняющий ДРУГУЮ сумму, увидит
|
||||
и сможет дописать то, что уже есть в поле, — это обычный редактируемый текст,
|
||||
без автоматического версионирования/склейки.
|
||||
- Сохраняются той же кнопкой «Сохранить» (`reviewBar`), что и остальные отметки,
|
||||
и тем же `onBeforeDecide` — при решении несохранённые суммы уйдут вместе с ним.
|
||||
- **«За что конкретно» — своего поля НЕТ.** Заполнил хоть одну сумму
|
||||
(`ClearanceView.writeOffTouchedCategories`) — становится обязательным
|
||||
СТАНДАРТНЫЙ комментарий решения: то самое поле «Комментарий» в блоке «Ваше
|
||||
решение» (`DecisionPanel`), которым и так сопровождают Согласовать/Вернуть/
|
||||
Отклонить — обычно обязательное только при возврате/отклонении, но не здесь
|
||||
(`commentRequired` у `DecisionPanel`, см. её же новый проп). Плейсхолдер в этот
|
||||
момент называет категории, которые нужно объяснить («Введите, за что сумма:
|
||||
ОС, ТМЦ», см. `commentPlaceholder`). Комментарий уходит в СТРОКУ маршрута этого
|
||||
согласующего (колонка `Comment` списка согласования) — как обычно, отдельного
|
||||
поля под него на данных заявки не заводили.
|
||||
- Суммы сохраняются кнопкой «Сохранить» (`reviewBar`) и тем же `onBeforeDecide`
|
||||
— при решении несохранённые суммы уйдут вместе с ним, ДО проверки
|
||||
обязательности комментария (см. `DecisionPanel.decide`).
|
||||
- Это ОТДЕЛЬНЫЙ механизм от `TrainingAmount` («Обучение: сумма» выше, привязана к
|
||||
КДП) — сознательно не объединялись, чтобы не менять уже установившееся
|
||||
поведение существующего поля.
|
||||
|
||||
## Секции формы
|
||||
RequestSection (сотрудник · должность · подразделение · дата расторжения · сумма
|
||||
обучения · парковочная карта · карта СКУД · суммы к списанию (компактно) + общий
|
||||
комментарий · ссылка на заявление)
|
||||
обучения · парковочная карта · карта СКУД · суммы к списанию (компактно) ·
|
||||
ссылка на заявление)
|
||||
|
||||
## Точки входа
|
||||
- rptype: `clearance-form`
|
||||
|
||||
@@ -44,7 +44,6 @@ export async function loadClearanceRequest(itemId: number, listId?: string): Pro
|
||||
'WriteOffTrainingAmount',
|
||||
'WriteOffNmaAmount',
|
||||
'WriteOffTmcAmount',
|
||||
'WriteOffComment',
|
||||
'RequestStatus',
|
||||
'RequestUniqueCode',
|
||||
'Employee/Title',
|
||||
@@ -73,8 +72,7 @@ export async function loadClearanceRequest(itemId: number, listId?: string): Pro
|
||||
WriteOffAccountableAmount: numberField(item.WriteOffAccountableAmount),
|
||||
WriteOffTrainingAmount: numberField(item.WriteOffTrainingAmount),
|
||||
WriteOffNmaAmount: numberField(item.WriteOffNmaAmount),
|
||||
WriteOffTmcAmount: numberField(item.WriteOffTmcAmount),
|
||||
WriteOffComment: (item.WriteOffComment as string) || ''
|
||||
WriteOffTmcAmount: numberField(item.WriteOffTmcAmount)
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -46,6 +46,13 @@ export interface IFormData {
|
||||
* ClearanceView.canEditWriteOffs), но как только сумма непустая — блокируется
|
||||
* НАВСЕГДА, для всех (включая того, кто её ввёл). Признак блокировки — сама
|
||||
* непустая сумма, отдельного флага в списке нет.
|
||||
*
|
||||
* «За что конкретно» — своего поля НЕТ: заполнил хоть одну сумму — становится
|
||||
* обязательным СТАНДАРТНЫЙ комментарий решения (DecisionPanel, «Ваше решение»),
|
||||
* тот же, каким и так сопровождают Согласовать/Вернуть/Отклонить. Он уже
|
||||
* хранится построчно в списке согласования (колонка Comment на шаге этого
|
||||
* согласующего) — заводить для него отдельное поле на данных заявки незачем
|
||||
* (см. ClearanceView — commentRequired/commentPlaceholder у DecisionPanel).
|
||||
*/
|
||||
WriteOffAssetsAmount: string;
|
||||
WriteOffPpeAmount: string;
|
||||
@@ -53,14 +60,6 @@ export interface IFormData {
|
||||
WriteOffTrainingAmount: string;
|
||||
WriteOffNmaAmount: string;
|
||||
WriteOffTmcAmount: string;
|
||||
/**
|
||||
* Комментарий «за что конкретно» — ОДИН на ВСЕ суммы к списанию (не пара на
|
||||
* каждую категорию — раньше было так, но колонок-комментариев на каждую
|
||||
* категорию не завели: одного общего достаточно). Обязателен, если хоть одна
|
||||
* из РЕДАКТИРУЕМЫХ сумм не пуста (см. RequestSection.writeOffTouchedCategories),
|
||||
* и до этого момента read-only — показывает то, что уже написали раньше.
|
||||
*/
|
||||
WriteOffComment: string;
|
||||
}
|
||||
|
||||
/** Имена всех 6 сумм к списанию — см. IFormData.WriteOff*Amount. */
|
||||
@@ -124,8 +123,7 @@ export const EMPTY_DATA: IFormData = {
|
||||
WriteOffAccountableAmount: '',
|
||||
WriteOffTrainingAmount: '',
|
||||
WriteOffNmaAmount: '',
|
||||
WriteOffTmcAmount: '',
|
||||
WriteOffComment: ''
|
||||
WriteOffTmcAmount: ''
|
||||
};
|
||||
|
||||
/** Плейсхолдер для поля выбора сотрудника. */
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
{ "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 },
|
||||
{ "internalName": "WriteOffComment", "title": "Комментарий к списанию", "type": "Note" }
|
||||
{ "internalName": "WriteOffTmcAmount", "title": "Сумма к списанию: ТМЦ", "type": "Number", "decimals": 2 }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { CollapseSection, FormField, TextArea, TextInput, MoneyInput, Toggle, PeopleField, OrganizationField, LinkField } from '../../../shared/ui';
|
||||
import { CollapseSection, FormField, TextInput, MoneyInput, Toggle, PeopleField, OrganizationField, LinkField } from '../../../shared/ui';
|
||||
import { ISectionProps, EMPLOYEE_PLACEHOLDER, WRITE_OFF_CATEGORIES } from '../model';
|
||||
|
||||
// Подразделение подтягивается автоматически при выборе сотрудника (см.
|
||||
@@ -32,13 +32,6 @@ export const RequestSection: React.FC<IRequestSectionProps> = ({
|
||||
parkingEditable,
|
||||
writeOffEditableKeys
|
||||
}) => {
|
||||
// Категории, чью сумму заполняют ПРЯМО СЕЙЧАС (редактируема и не пуста) —
|
||||
// от них зависит, показывать ли общий комментарий и что подставить в его
|
||||
// плейсхолдер (см. IFormData.WriteOffComment в model.ts).
|
||||
const writeOffTouchedCategories = writeOffEditableKeys
|
||||
? WRITE_OFF_CATEGORIES.filter((cat) => writeOffEditableKeys.has(cat.key) && data[cat.amountField].trim() !== '')
|
||||
: [];
|
||||
|
||||
return (
|
||||
<CollapseSection title="Обходной лист" icon="people">
|
||||
<FormField label="Сотрудник" htmlFor="Employee" required full>
|
||||
@@ -112,50 +105,24 @@ export const RequestSection: React.FC<IRequestSectionProps> = ({
|
||||
от отметок выше) — но как только сумма указана, блокируется навсегда
|
||||
для всех, поэтому у незаблокированных (writeOffEditableKeys.has(cat.key))
|
||||
есть readOnly={false}, у остальных — обычное поведение FormMode
|
||||
(просмотр — заблокировано и так). Комментарий — ОДИН на ВСЕ суммы
|
||||
(не пара на каждую категорию, см. IFormData.WriteOffComment). */}
|
||||
{writeOffEditableKeys && (
|
||||
<>
|
||||
{WRITE_OFF_CATEGORIES.map((cat) => {
|
||||
const editable = writeOffEditableKeys.has(cat.key);
|
||||
return (
|
||||
<FormField key={cat.key} label={cat.label} htmlFor={cat.amountField}>
|
||||
<MoneyInput
|
||||
id={cat.amountField}
|
||||
value={data[cat.amountField]}
|
||||
onChange={(v) => set(cat.amountField, v)}
|
||||
readOnly={editable ? false : undefined}
|
||||
/>
|
||||
</FormField>
|
||||
);
|
||||
})}
|
||||
{/* Read-only, если сейчас нечего заполнять (нет свежих сумм) — просто
|
||||
показывает, что написали раньше; редактируема и обязательна
|
||||
ровно тогда, когда есть непустая сумма, которую надо объяснить. */}
|
||||
{(writeOffTouchedCategories.length > 0 || data.WriteOffComment) && (
|
||||
<FormField
|
||||
label="Комментарий: за что списание"
|
||||
htmlFor="WriteOffComment"
|
||||
required={writeOffTouchedCategories.length > 0}
|
||||
full
|
||||
>
|
||||
<TextArea
|
||||
id="WriteOffComment"
|
||||
value={data.WriteOffComment}
|
||||
onChange={(v) => set('WriteOffComment', v)}
|
||||
readOnly={writeOffTouchedCategories.length > 0 ? false : true}
|
||||
placeholder={
|
||||
writeOffTouchedCategories.length > 0
|
||||
? `Введите, за что сумма${
|
||||
writeOffTouchedCategories.length > 1 ? 'ы' : ''
|
||||
}: ${writeOffTouchedCategories.map((c) => c.label).join(', ')}`
|
||||
: undefined
|
||||
}
|
||||
(просмотр — заблокировано и так). «За что конкретно» — своего поля
|
||||
здесь нет: заполнил хоть одну сумму — обязательным становится
|
||||
СТАНДАРТНЫЙ комментарий решения в DecisionPanel («Ваше решение»), см.
|
||||
ClearanceView (commentRequired/commentPlaceholder). */}
|
||||
{writeOffEditableKeys &&
|
||||
WRITE_OFF_CATEGORIES.map((cat) => {
|
||||
const editable = writeOffEditableKeys.has(cat.key);
|
||||
return (
|
||||
<FormField key={cat.key} label={cat.label} htmlFor={cat.amountField}>
|
||||
<MoneyInput
|
||||
id={cat.amountField}
|
||||
value={data[cat.amountField]}
|
||||
onChange={(v) => set(cat.amountField, v)}
|
||||
readOnly={editable ? false : undefined}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
{/* Есть только у листов, созданных автоматически по согласованному заявлению
|
||||
на увольнение (см. forms/Dismissal/createClearance.ts). */}
|
||||
<LinkField
|
||||
|
||||
@@ -3,18 +3,17 @@ 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.WriteOffComment. */
|
||||
export type IWriteOffFields = Pick<IFormData, WriteOffFieldKey | 'WriteOffComment'>;
|
||||
/** 6 сумм к списанию — см. model.ts WRITE_OFF_CATEGORIES. «За что конкретно» — не здесь, см. IFormData. */
|
||||
export type IWriteOffFields = Pick<IFormData, WriteOffFieldKey>;
|
||||
|
||||
/**
|
||||
* Записать сумму(ы) к списанию и/или общий комментарий. Пишет РОВНО переданные
|
||||
* поля — как updateApproverFields, каждое независимо.
|
||||
* Записать сумму(ы) к списанию. Пишет РОВНО переданные поля — как
|
||||
* updateApproverFields, каждая независимо.
|
||||
*
|
||||
* ВАЖНО: вызывающая сторона (ClearanceView) обязана передавать сюда ТОЛЬКО ещё
|
||||
* не заблокированные суммы (см. IFormData.WriteOff* — блокировка по непустой
|
||||
* сумме) и проверенный (непустой, если хоть одна сумма заполняется) комментарий
|
||||
* — эта функция сама повторно ничего не проверяет и молча перезапишет то, что
|
||||
* ей передали.
|
||||
* сумме) — эта функция сама повторно ничего не проверяет и молча перезапишет
|
||||
* то, что ей передали.
|
||||
*/
|
||||
export async function updateWriteOffFields(
|
||||
id: number,
|
||||
|
||||
@@ -33,6 +33,17 @@ interface IDecisionPanelProps {
|
||||
search?: (query: string) => Promise<ISiteUser[]>;
|
||||
/** Вызывается после успешного переназначения (обновить экран/матрицу). */
|
||||
onDelegated?: () => void;
|
||||
/**
|
||||
* Дополнительное условие обязательности комментария — СВЕРХ обычного
|
||||
* «возврат/отклонение». Форме есть что объяснять не только при отказе:
|
||||
* например, «Обходной лист» требует его на ЛЮБОЕ решение, если согласующий
|
||||
* только что заполнил сумму к списанию — своего поля «за что конкретно» у
|
||||
* формы нет, вместо него используется этот самый комментарий (см.
|
||||
* ClearanceView.writeOffTouchedCategories).
|
||||
*/
|
||||
commentRequired?: boolean;
|
||||
/** Переопределить плейсхолдер поля комментария (по умолчанию — про возврат/отклонение). */
|
||||
commentPlaceholder?: string;
|
||||
}
|
||||
|
||||
// Панель решения текущего шага: согласующий (Согласовать/Вернуть/Отклонить) или
|
||||
@@ -45,6 +56,8 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
onBeforeDecide,
|
||||
search,
|
||||
onDelegated,
|
||||
commentRequired,
|
||||
commentPlaceholder,
|
||||
}) => {
|
||||
const [checking, setChecking] = useState<boolean>(true);
|
||||
const [pendingRow, setPendingRow] = useState<IPendingRow | undefined>(undefined);
|
||||
@@ -84,10 +97,15 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
if (!pendingRow) {
|
||||
return;
|
||||
}
|
||||
if ((decision === 'return' || decision === 'reject') && comment.trim().length === 0) {
|
||||
const commentEmpty = comment.trim().length === 0;
|
||||
if (commentEmpty && (decision === 'return' || decision === 'reject')) {
|
||||
setError('Для возврата или отклонения укажите комментарий.');
|
||||
return;
|
||||
}
|
||||
if (commentEmpty && commentRequired) {
|
||||
setError('Укажите комментарий.');
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
setSubmitting(true);
|
||||
try {
|
||||
@@ -300,12 +318,12 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
<div className={styles.panel}>
|
||||
<div className={styles.title}>Ваше решение</div>
|
||||
{error && <Alert variant="err">{error}</Alert>}
|
||||
<FormField label="Комментарий" htmlFor="decisionComment">
|
||||
<FormField label="Комментарий" htmlFor="decisionComment" required={commentRequired}>
|
||||
<TextArea
|
||||
id="decisionComment"
|
||||
value={comment}
|
||||
onChange={setComment}
|
||||
placeholder="Комментарий (обязателен при возврате или отклонении)"
|
||||
placeholder={commentPlaceholder || 'Комментарий (обязателен при возврате или отклонении)'}
|
||||
/>
|
||||
</FormField>
|
||||
<div className={styles.actions}>
|
||||
|
||||
Reference in New Issue
Block a user