feat: в заявке 1С дата запрета по АТП оформляется только на базу АТП
При виде работ «Дата запрета (открытие/закрытие)» и выбранной базе АТП остальные базы снимаются и добавить их нельзя — дата запрета по АТП открывается/закрывается отдельно. Над полем «База 1С» при этом виде работ показывается предупреждение. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,13 @@ export const CHOICES_LOADING_PLACEHOLDER = 'Загрузка вариантов
|
|||||||
export const CHOICES_EMPTY_PLACEHOLDER = 'Нет вариантов — обратитесь к администратору';
|
export const CHOICES_EMPTY_PLACEHOLDER = 'Нет вариантов — обратитесь к администратору';
|
||||||
export const CHOICES_ERROR_PREFIX = 'Не удалось загрузить варианты полей';
|
export const CHOICES_ERROR_PREFIX = 'Не удалось загрузить варианты полей';
|
||||||
|
|
||||||
|
// Подсказка в поле «База 1С», когда сработало правило «дата запрета по АТП».
|
||||||
|
export const DB1C_ATP_ONLY_HINT = 'Для даты запрета по АТП другие базы не выбираются';
|
||||||
|
// Предупреждение над полем «База 1С» при виде работ «Дата запрета».
|
||||||
|
export const DB1C_ATP_ONLY_WARNING =
|
||||||
|
'Для даты запрета (открытие/закрытие) по базе АТП можно выбрать только АТП — ' +
|
||||||
|
'дополнительные базы недоступны.';
|
||||||
|
|
||||||
// Значения из property pane старой web part (см. живую страницу SharePoint).
|
// Значения из property pane старой web part (см. живую страницу SharePoint).
|
||||||
export const PLANNER_PLAN_ID = '168WqDV8CEKkW3cL6olCRZgACwYa';
|
export const PLANNER_PLAN_ID = '168WqDV8CEKkW3cL6olCRZgACwYa';
|
||||||
export const PLANNER_BUCKET_NAME = 'Входящие неотсортированные';
|
export const PLANNER_BUCKET_NAME = 'Входящие неотсортированные';
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { DB1C_ATP_OPTION, WORK_TYPE_BAN_DATE, restrictDatabases } from './model';
|
||||||
|
|
||||||
|
const ZUP = '1С: Зарплата и управление персоналом (ЗУП)';
|
||||||
|
|
||||||
|
describe('Ticket1C restrictDatabases', () => {
|
||||||
|
it('дата запрета + АТП — остаётся только АТП', () => {
|
||||||
|
expect(restrictDatabases(WORK_TYPE_BAN_DATE, [ZUP, DB1C_ATP_OPTION])).toEqual([DB1C_ATP_OPTION]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('дата запрета без АТП — базы не трогаем', () => {
|
||||||
|
expect(restrictDatabases(WORK_TYPE_BAN_DATE, [ZUP])).toEqual([ZUP]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('другой вид работ — АТП можно сочетать с другими базами', () => {
|
||||||
|
expect(restrictDatabases('Прочее', [ZUP, DB1C_ATP_OPTION])).toEqual([ZUP, DB1C_ATP_OPTION]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -44,6 +44,27 @@ export function hasOtherDatabase(data: IFormData): boolean {
|
|||||||
return data.DB1C.indexOf(DB1C_OTHER_OPTION) !== -1;
|
return data.DB1C.indexOf(DB1C_OTHER_OPTION) !== -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Варианты колонок SP, для которых действует правило «дата запрета по АТП —
|
||||||
|
* только одна база». Должны дословно совпадать с вариантами колонок
|
||||||
|
* WorkType и DB1C в SharePoint (варианты читаются из SP, см. useFieldChoices).
|
||||||
|
*/
|
||||||
|
export const WORK_TYPE_BAN_DATE = 'Дата запрета (открытие/закрытие)';
|
||||||
|
export const DB1C_ATP_OPTION = '1С: Автотранспортное предприятие (АТП)';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Дата запрета в АТП открывается/закрывается отдельно от остальных баз,
|
||||||
|
* поэтому в такой заявке база АТП может быть только одна.
|
||||||
|
*/
|
||||||
|
export function isAtpOnlyBanDate(workType: string, databases: string[]): boolean {
|
||||||
|
return workType === WORK_TYPE_BAN_DATE && databases.indexOf(DB1C_ATP_OPTION) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Применяет правило isAtpOnlyBanDate: при его срабатывании оставляет только АТП. */
|
||||||
|
export function restrictDatabases(workType: string, databases: string[]): string[] {
|
||||||
|
return isAtpOnlyBanDate(workType, databases) ? [DB1C_ATP_OPTION] : databases;
|
||||||
|
}
|
||||||
|
|
||||||
/** Обновление одного поля формы с сохранением типа значения. */
|
/** Обновление одного поля формы с сохранением типа значения. */
|
||||||
export type SetField = <K extends keyof IFormData>(key: K, value: IFormData[K]) => void;
|
export type SetField = <K extends keyof IFormData>(key: K, value: IFormData[K]) => void;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Строка на обе колонки сетки секции — как FormField с full.
|
||||||
|
.fullRow {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
@@ -1,10 +1,21 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import styles from './RequestSection.module.scss';
|
||||||
import { Alert, CollapseSection, FormField, MultiChoiceField, Select, TextArea, TextInput, ISelectOption } from '../../../shared/ui';
|
import { Alert, CollapseSection, FormField, MultiChoiceField, Select, TextArea, TextInput, ISelectOption } from '../../../shared/ui';
|
||||||
import { useFieldChoices, IFieldChoicesState } from '../../../shared/fieldChoices';
|
import { useFieldChoices, IFieldChoicesState } from '../../../shared/fieldChoices';
|
||||||
import { dataListTitle } from '../../../shared/listTitles';
|
import { dataListTitle } from '../../../shared/listTitles';
|
||||||
import { DB1C_OTHER_OPTION, FORM_ID, ISectionProps, hasOtherDatabase } from '../model';
|
import {
|
||||||
|
DB1C_OTHER_OPTION,
|
||||||
|
FORM_ID,
|
||||||
|
WORK_TYPE_BAN_DATE,
|
||||||
|
ISectionProps,
|
||||||
|
hasOtherDatabase,
|
||||||
|
isAtpOnlyBanDate,
|
||||||
|
restrictDatabases
|
||||||
|
} from '../model';
|
||||||
import {
|
import {
|
||||||
QUESTION_MAX,
|
QUESTION_MAX,
|
||||||
|
DB1C_ATP_ONLY_HINT,
|
||||||
|
DB1C_ATP_ONLY_WARNING,
|
||||||
CHOICES_LOADING_PLACEHOLDER,
|
CHOICES_LOADING_PLACEHOLDER,
|
||||||
CHOICES_EMPTY_PLACEHOLDER,
|
CHOICES_EMPTY_PLACEHOLDER,
|
||||||
CHOICES_ERROR_PREFIX
|
CHOICES_ERROR_PREFIX
|
||||||
@@ -30,6 +41,16 @@ export const RequestSection: React.FC<ISectionProps> = ({ data, set }) => {
|
|||||||
const organizations = useFieldChoices(listTitle, 'Organization');
|
const organizations = useFieldChoices(listTitle, 'Organization');
|
||||||
const databases = useFieldChoices(listTitle, 'DB1C');
|
const databases = useFieldChoices(listTitle, 'DB1C');
|
||||||
const choicesError = workTypes.error || organizations.error || databases.error;
|
const choicesError = workTypes.error || organizations.error || databases.error;
|
||||||
|
const isAtpOnly = isAtpOnlyBanDate(data.WorkType, data.DB1C);
|
||||||
|
|
||||||
|
// Правило «дата запрета по АТП» зависит и от вида работ, и от баз — поэтому
|
||||||
|
// применяется при смене любого из двух полей.
|
||||||
|
const setDatabases = (workType: string, values: string[]): void => {
|
||||||
|
const restricted = restrictDatabases(workType, values);
|
||||||
|
set('DB1C', restricted);
|
||||||
|
// Сняли «Другое» — вписанное название больше не относится к заявке.
|
||||||
|
if (restricted.indexOf(DB1C_OTHER_OPTION) === -1) set('DB1COther', '');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapseSection title="Заявка" icon="clipboard">
|
<CollapseSection title="Заявка" icon="clipboard">
|
||||||
@@ -41,7 +62,10 @@ export const RequestSection: React.FC<ISectionProps> = ({ data, set }) => {
|
|||||||
<Select
|
<Select
|
||||||
id="WorkType"
|
id="WorkType"
|
||||||
value={data.WorkType}
|
value={data.WorkType}
|
||||||
onChange={(v) => set('WorkType', v)}
|
onChange={(v) => {
|
||||||
|
set('WorkType', v);
|
||||||
|
setDatabases(v, data.DB1C);
|
||||||
|
}}
|
||||||
options={withCurrentValue(workTypes.options, data.WorkType)}
|
options={withCurrentValue(workTypes.options, data.WorkType)}
|
||||||
placeholder={choicesPlaceholder(workTypes, '— выберите —')}
|
placeholder={choicesPlaceholder(workTypes, '— выберите —')}
|
||||||
/>
|
/>
|
||||||
@@ -55,18 +79,22 @@ export const RequestSection: React.FC<ISectionProps> = ({ data, set }) => {
|
|||||||
placeholder={choicesPlaceholder(organizations, '— выберите —')}
|
placeholder={choicesPlaceholder(organizations, '— выберите —')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
{/* Предупреждаем заранее, ещё до выбора баз, чтобы правило не стало сюрпризом. */}
|
||||||
|
{data.WorkType === WORK_TYPE_BAN_DATE && (
|
||||||
|
<div className={styles.fullRow}>
|
||||||
|
<Alert variant="warn">{DB1C_ATP_ONLY_WARNING}</Alert>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{/* На всю ширину: чипы выбранных баз растят поле по высоте, и соседнее
|
{/* На всю ширину: чипы выбранных баз растят поле по высоте, и соседнее
|
||||||
поле в той же строке сетки съезжало бы относительно него. */}
|
поле в той же строке сетки съезжало бы относительно него. */}
|
||||||
<FormField label="База 1С" fieldKey="DB1C" required full>
|
<FormField label="База 1С" fieldKey="DB1C" required full>
|
||||||
<MultiChoiceField
|
<MultiChoiceField
|
||||||
value={data.DB1C}
|
value={data.DB1C}
|
||||||
onChange={(v) => {
|
onChange={(v) => setDatabases(data.WorkType, v)}
|
||||||
set('DB1C', v);
|
// Пустой список вариантов прячет выпадашку — добавить другую базу нельзя,
|
||||||
// Сняли «Другое» — вписанное название больше не относится к заявке.
|
// пока не уберут АТП или не сменят вид работ.
|
||||||
if (v.indexOf(DB1C_OTHER_OPTION) === -1) set('DB1COther', '');
|
options={isAtpOnly ? [] : databases.options}
|
||||||
}}
|
placeholder={isAtpOnly ? DB1C_ATP_ONLY_HINT : choicesPlaceholder(databases, 'Выберите одну или несколько баз')}
|
||||||
options={databases.options}
|
|
||||||
placeholder={choicesPlaceholder(databases, 'Выберите одну или несколько баз')}
|
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
{hasOtherDatabase(data) && (
|
{hasOtherDatabase(data) && (
|
||||||
|
|||||||
Reference in New Issue
Block a user