feat: ОС, СИЗ и подотчётная сумма — отдельные суммы к списанию

Было одной парой «ОС/СИЗ/подотчётная сумма» — стало тремя независимыми, всего
6 категорий (ОС, СИЗ, подотчётная сумма, обучение, НМА, ТМЦ). Списывают их
разные подразделения и за разное, поэтому и сумма с комментарием у каждой своя,
и блокируется каждая отдельно (правило «введено — больше не меняется» работает
на пару, а не на весь блок).

Существующая колонка WriteOffAssetsAmount/Comment переиспользована под «ОС»
(данных по ней ещё нет), добавлены WriteOffPpe* и WriteOffAccountable*.
Список типов полей теперь выводится из WriteOffFieldKey — IWriteOffFields в
submit.ts больше не дублирует их перечислением.

На тенанте: 4 новые колонки созданы провижинером; отображаемые имена ОС-пары
досинхронизированы отдельно — Provision-Lists.ps1 колонки только СОЗДАЁТ и
переименование в манифесте существующей колонке не применяет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-08-25 17:20:26 +05:00
parent 2d2194047b
commit 179999c02a
7 changed files with 43 additions and 27 deletions
+1 -1
View File
@@ -45,7 +45,7 @@
| [`recruiting-referral`](../src/webparts/requestPortal/forms/Referral/README.md) | Реферальная рекомендация | Рекрутинг | да | да | да | кандидат — внешний (текстовые поля, не People); маршрут по рекомендателю; исполнитель переназначает на реального; условное уточнение «откуда знаете» | ✅ | | [`recruiting-referral`](../src/webparts/requestPortal/forms/Referral/README.md) | Реферальная рекомендация | Рекрутинг | да | да | да | кандидат — внешний (текстовые поля, не People); маршрут по рекомендателю; исполнитель переназначает на реального; условное уточнение «откуда знаете» | ✅ |
| [`it-workplace-setup`](../src/webparts/requestPortal/forms/WorkplaceSetup/README.md) | Организация рабочего места | IT | да | да | да | префилл из «прием», поля ПО/доступы правит руководитель | ✅ | | [`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 (согласовано/сразу без согласования — рабочий бакет, отклонено — отдельный бакет) | ✅ | | [`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» резолвится по должности из самой заявки; да/нет-поля (парковка, СКУД); 4 независимые пары «сумма к списанию + комментарий» (ОС/СИЗ/подотчёт, обучение, НМА, ТМЦ) — вводит любой активный согласующий, блокируется навсегда после первого ввода | ✅ | | [`clearance-form`](../src/webparts/requestPortal/forms/Clearance/README.md) | Обходной лист | Отдел кадров | да | нет | да | подаётся HR за увольняемого (People-picker) либо создаётся сам по согласованному «Заявлению на увольнение»; условие «IsManager» резолвится по должности из самой заявки; да/нет-поля (парковка, СКУД); 6 независимых пар «сумма к списанию + комментарий» (ОС, СИЗ, подотчётная сумма, обучение, НМА, ТМЦ) — вводит любой активный согласующий, блокируется навсегда после первого ввода | ✅ |
| [`hr-dismissal`](../src/webparts/requestPortal/forms/Dismissal/README.md) | Заявление на увольнение | Отдел кадров | да | нет | да | совместители — таблица компаний с удалением строки (снимает и согласующего из матрицы); должность/организация из справочника; **после согласования сам создаёт обходной лист**; печать заблокирована до согласования обходного листа; «Лицо, принимающее ОС/НМА» — заполняет только непосредственный руководитель | ✅ | | [`hr-dismissal`](../src/webparts/requestPortal/forms/Dismissal/README.md) | Заявление на увольнение | Отдел кадров | да | нет | да | совместители — таблица компаний с удалением строки (снимает и согласующего из матрицы); должность/организация из справочника; **после согласования сам создаёт обходной лист**; печать заблокирована до согласования обходного листа; «Лицо, принимающее ОС/НМА» — заполняет только непосредственный руководитель | ✅ |
| `hr-transfer` | Перемещение сотрудников | Отдел кадров | — | — | — | — | ⏳ заглушка (плитка есть, форма не сделана) | | `hr-transfer` | Перемещение сотрудников | Отдел кадров | — | — | — | — | ⏳ заглушка (плитка есть, форма не сделана) |
@@ -55,9 +55,9 @@ RequestTypeId» в `docs/forms.md`). Тип уже зарегистрирова
заведённых руками, ссылки нет и поле не рендерится. заведённых руками, ссылки нет и поле не рендерится.
## Суммы к списанию (согласование) ## Суммы к списанию (согласование)
4 НЕЗАВИСИМЫЕ пары «сумма + обязательный комментарий (за что конкретно)», колонки 6 НЕЗАВИСИМЫХ пар «сумма + обязательный комментарий (за что конкретно)», колонки
`WriteOff{Assets,Training,Nma,Tmc}{Amount,Comment}` (см. `model.ts `WriteOff{Assets,Ppe,Accountable,Training,Nma,Tmc}{Amount,Comment}` (см. `model.ts
WRITE_OFF_CATEGORIES`): «ОС / СИЗ / подотчётная сумма», «Обучение», «НМА», «ТМЦ». WRITE_OFF_CATEGORIES`): «ОС», «СИЗ», «Подотчётная сумма», «Обучение», «НМА», «ТМЦ».
- **Не привязаны к должности**, в отличие от отметок выше: заполнить может ЛЮБОЙ - **Не привязаны к должности**, в отличие от отметок выше: заполнить может ЛЮБОЙ
согласующий, у которого сейчас активный шаг (`ClearanceView.canEditWriteOffs`, согласующий, у которого сейчас активный шаг (`ClearanceView.canEditWriteOffs`,
@@ -40,6 +40,10 @@ export async function loadClearanceRequest(itemId: number, listId?: string): Pro
'DismissalUrl', 'DismissalUrl',
'WriteOffAssetsAmount', 'WriteOffAssetsAmount',
'WriteOffAssetsComment', 'WriteOffAssetsComment',
'WriteOffPpeAmount',
'WriteOffPpeComment',
'WriteOffAccountableAmount',
'WriteOffAccountableComment',
'WriteOffTrainingAmount', 'WriteOffTrainingAmount',
'WriteOffTrainingComment', 'WriteOffTrainingComment',
'WriteOffNmaAmount', 'WriteOffNmaAmount',
@@ -71,6 +75,10 @@ export async function loadClearanceRequest(itemId: number, listId?: string): Pro
DismissalUrl: (item.DismissalUrl as { Url?: string } | undefined)?.Url || '', DismissalUrl: (item.DismissalUrl as { Url?: string } | undefined)?.Url || '',
WriteOffAssetsAmount: numberField(item.WriteOffAssetsAmount), WriteOffAssetsAmount: numberField(item.WriteOffAssetsAmount),
WriteOffAssetsComment: (item.WriteOffAssetsComment as string) || '', 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), WriteOffTrainingAmount: numberField(item.WriteOffTrainingAmount),
WriteOffTrainingComment: (item.WriteOffTrainingComment as string) || '', WriteOffTrainingComment: (item.WriteOffTrainingComment as string) || '',
WriteOffNmaAmount: numberField(item.WriteOffNmaAmount), WriteOffNmaAmount: numberField(item.WriteOffNmaAmount),
@@ -39,7 +39,7 @@ export interface IFormData {
DismissalUrl: string; DismissalUrl: string;
/** /**
* Суммы к списанию по обходному листу — 4 независимые пары (сумма + * Суммы к списанию по обходному листу — 6 независимых пар (сумма +
* обязательный комментарий, «за что конкретно»), см. WRITE_OFF_CATEGORIES. * обязательный комментарий, «за что конкретно»), см. WRITE_OFF_CATEGORIES.
* «Сырые» цифры без пробелов, как TrainingAmount (см. MoneyInput). Пусто = * «Сырые» цифры без пробелов, как TrainingAmount (см. MoneyInput). Пусто =
* не заполнено. КАЖДАЯ пара — независимая: заполнить может любой активный * не заполнено. КАЖДАЯ пара — независимая: заполнить может любой активный
@@ -49,6 +49,10 @@ export interface IFormData {
*/ */
WriteOffAssetsAmount: string; WriteOffAssetsAmount: string;
WriteOffAssetsComment: string; WriteOffAssetsComment: string;
WriteOffPpeAmount: string;
WriteOffPpeComment: string;
WriteOffAccountableAmount: string;
WriteOffAccountableComment: string;
WriteOffTrainingAmount: string; WriteOffTrainingAmount: string;
WriteOffTrainingComment: string; WriteOffTrainingComment: string;
WriteOffNmaAmount: string; WriteOffNmaAmount: string;
@@ -57,10 +61,14 @@ export interface IFormData {
WriteOffTmcComment: string; WriteOffTmcComment: string;
} }
/** Имена всех 8 строковых полей сумм/комментариев к списанию — см. IFormData.WriteOff*. */ /** Имена всех 12 строковых полей сумм/комментариев к списанию — см. IFormData.WriteOff*. */
export type WriteOffFieldKey = export type WriteOffFieldKey =
| 'WriteOffAssetsAmount' | 'WriteOffAssetsAmount'
| 'WriteOffAssetsComment' | 'WriteOffAssetsComment'
| 'WriteOffPpeAmount'
| 'WriteOffPpeComment'
| 'WriteOffAccountableAmount'
| 'WriteOffAccountableComment'
| 'WriteOffTrainingAmount' | 'WriteOffTrainingAmount'
| 'WriteOffTrainingComment' | 'WriteOffTrainingComment'
| 'WriteOffNmaAmount' | 'WriteOffNmaAmount'
@@ -69,7 +77,7 @@ export type WriteOffFieldKey =
| 'WriteOffTmcComment'; | 'WriteOffTmcComment';
/** /**
* Одна из 4 независимых категорий списания — см. IFormData.WriteOff*. * Одна из 6 независимых категорий списания — см. IFormData.WriteOff*.
* amountField/commentField сужены до WriteOffFieldKey (а не keyof IFormData * amountField/commentField сужены до WriteOffFieldKey (а не keyof IFormData
* целиком): у IFormData есть и булевы поля (ParkingCard/AccessCard), и * целиком): у IFormData есть и булевы поля (ParkingCard/AccessCard), и
* `data[cat.amountField]` должен типизироваться строго строкой — иначе * `data[cat.amountField]` должен типизироваться строго строкой — иначе
@@ -83,11 +91,13 @@ export interface IWriteOffCategory {
} }
export const WRITE_OFF_CATEGORIES: IWriteOffCategory[] = [ export const WRITE_OFF_CATEGORIES: IWriteOffCategory[] = [
{ key: 'assets', label: 'ОС', amountField: 'WriteOffAssetsAmount', commentField: 'WriteOffAssetsComment' },
{ key: 'ppe', label: 'СИЗ', amountField: 'WriteOffPpeAmount', commentField: 'WriteOffPpeComment' },
{ {
key: 'assets', key: 'accountable',
label: 'ОС / СИЗ / подотчётная сумма', label: 'Подотчётная сумма',
amountField: 'WriteOffAssetsAmount', amountField: 'WriteOffAccountableAmount',
commentField: 'WriteOffAssetsComment' commentField: 'WriteOffAccountableComment'
}, },
{ {
key: 'training', key: 'training',
@@ -126,6 +136,10 @@ export const EMPTY_DATA: IFormData = {
DismissalUrl: '', DismissalUrl: '',
WriteOffAssetsAmount: '', WriteOffAssetsAmount: '',
WriteOffAssetsComment: '', WriteOffAssetsComment: '',
WriteOffPpeAmount: '',
WriteOffPpeComment: '',
WriteOffAccountableAmount: '',
WriteOffAccountableComment: '',
WriteOffTrainingAmount: '', WriteOffTrainingAmount: '',
WriteOffTrainingComment: '', WriteOffTrainingComment: '',
WriteOffNmaAmount: '', WriteOffNmaAmount: '',
@@ -16,8 +16,12 @@
{ "internalName": "DismissalUrl", "title": "Ссылка на заявление на увольнение", "type": "Url" }, { "internalName": "DismissalUrl", "title": "Ссылка на заявление на увольнение", "type": "Url" },
{ "internalName": "WriteOffAssetsAmount", "title": "Сумма к списанию: ОС/СИЗ/подотчёт", "type": "Number", "decimals": 2 }, { "internalName": "WriteOffAssetsAmount", "title": "Сумма к списанию: ОС", "type": "Number", "decimals": 2 },
{ "internalName": "WriteOffAssetsComment", "title": "Комментарий: ОС/СИЗ/подотчёт", "type": "Note" }, { "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": "WriteOffTrainingAmount", "title": "Сумма к списанию: обучение", "type": "Number", "decimals": 2 },
{ "internalName": "WriteOffTrainingComment", "title": "Комментарий: обучение", "type": "Note" }, { "internalName": "WriteOffTrainingComment", "title": "Комментарий: обучение", "type": "Note" },
{ "internalName": "WriteOffNmaAmount", "title": "Сумма к списанию: НМА", "type": "Number", "decimals": 2 }, { "internalName": "WriteOffNmaAmount", "title": "Сумма к списанию: НМА", "type": "Number", "decimals": 2 },
@@ -98,7 +98,7 @@ export const RequestSection: React.FC<IRequestSectionProps> = ({
readOnly={hrRecordsEditable ? false : undefined} readOnly={hrRecordsEditable ? false : undefined}
/> />
</FormField> </FormField>
{/* Суммы к списанию — 4 независимые пары (сумма + обязательный комментарий, {/* Суммы к списанию — 6 независимых пар (сумма + обязательный комментарий,
за что конкретно). Каждую может заполнить ЛЮБОЙ активный согласующий за что конкретно). Каждую может заполнить ЛЮБОЙ активный согласующий
(не только профильный, в отличие от отметок выше) — но как только сумма (не только профильный, в отличие от отметок выше) — но как только сумма
указана, пара блокируется навсегда для всех, поэтому у незаблокированных указана, пара блокируется навсегда для всех, поэтому у незаблокированных
@@ -1,20 +1,10 @@
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval'; import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, resolveDataList, saveRequestItem } from '../../shared/dataList'; import { ISaveItemResult, resolveDataList, saveRequestItem } from '../../shared/dataList';
import { ensureEmployee } from '../../shared/employee'; import { ensureEmployee } from '../../shared/employee';
import { FORM_ID, IFormData } from './model'; import { FORM_ID, IFormData, WriteOffFieldKey } from './model';
/** Одна из 8 колонок сумм/комментариев к списанию — см. model.ts WRITE_OFF_CATEGORIES. */ /** Одна из 12 колонок сумм/комментариев к списанию — см. model.ts WRITE_OFF_CATEGORIES. */
export type IWriteOffFields = Pick< export type IWriteOffFields = Pick<IFormData, WriteOffFieldKey>;
IFormData,
| 'WriteOffAssetsAmount'
| 'WriteOffAssetsComment'
| 'WriteOffTrainingAmount'
| 'WriteOffTrainingComment'
| 'WriteOffNmaAmount'
| 'WriteOffNmaComment'
| 'WriteOffTmcAmount'
| 'WriteOffTmcComment'
>;
/** /**
* Записать сумму(ы)/комментарий(и) к списанию. Пишет РОВНО переданные поля — * Записать сумму(ы)/комментарий(и) к списанию. Пишет РОВНО переданные поля —