feat: ручной ввод базы 1С при выборе варианта «Другое»
Если нужной базы нет в списке, заявитель отмечает «Другое» и вписывает название в обязательное поле. Текст хранится в отдельной колонке DB1COther, показывается в просмотре и попадает в карточку Planner. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@ import {
|
|||||||
dataListTitle,
|
dataListTitle,
|
||||||
dataListTitleForListId
|
dataListTitleForListId
|
||||||
} from '../../shared/listTitles';
|
} from '../../shared/listTitles';
|
||||||
import { FORM_ID, IFormData, EMPTY_DATA, SetField } from './model';
|
import { FORM_ID, IFormData, EMPTY_DATA, SetField, hasOtherDatabase } from './model';
|
||||||
import { saveTicketRequest, ticket1cApprovalService } from './submit';
|
import { saveTicketRequest, ticket1cApprovalService } from './submit';
|
||||||
import { loadTicketRequest } from './load';
|
import { loadTicketRequest } from './load';
|
||||||
import { getApplicantProfile } from './services/profile.service';
|
import { getApplicantProfile } from './services/profile.service';
|
||||||
@@ -222,17 +222,20 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
|
|||||||
);
|
);
|
||||||
const matrixEditor = useMemo(() => ({ ...matrix, rows: matrixRows }), [matrix, matrixRows]);
|
const matrixEditor = useMemo(() => ({ ...matrix, rows: matrixRows }), [matrix, matrixRows]);
|
||||||
|
|
||||||
const isValid = !!(
|
const isOtherDatabaseMissing = hasOtherDatabase(data) && !data.DB1COther.trim();
|
||||||
|
const areRequiredFilled = !!(
|
||||||
data.TaskName.trim() &&
|
data.TaskName.trim() &&
|
||||||
data.WorkType &&
|
data.WorkType &&
|
||||||
data.Organization &&
|
data.Organization &&
|
||||||
data.DB1C.length > 0 &&
|
data.DB1C.length > 0 &&
|
||||||
data.Question.trim()
|
data.Question.trim()
|
||||||
);
|
);
|
||||||
|
const isValid = areRequiredFilled && !isOtherDatabaseMissing;
|
||||||
const validate = (): string | undefined => {
|
const validate = (): string | undefined => {
|
||||||
if (!isValid) {
|
if (!areRequiredFilled) {
|
||||||
return 'Заполните обязательные поля: наименование задачи, вид работ, организация, база 1С, вопрос.';
|
return 'Заполните обязательные поля: наименование задачи, вид работ, организация, база 1С, вопрос.';
|
||||||
}
|
}
|
||||||
|
if (isOtherDatabaseMissing) return 'Выбрана база 1С «Другое» — укажите её название.';
|
||||||
if (requestTypeIdState.error) return `Не удалось определить тип заявки: ${requestTypeIdState.error}`;
|
if (requestTypeIdState.error) return `Не удалось определить тип заявки: ${requestTypeIdState.error}`;
|
||||||
if (!requestTypeId) return 'Тип заявки ещё определяется — подождите немного и повторите.';
|
if (!requestTypeId) return 'Тип заявки ещё определяется — подождите немного и повторите.';
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
dataListTitle,
|
dataListTitle,
|
||||||
dataListTitleForListId
|
dataListTitleForListId
|
||||||
} from '../../shared/listTitles';
|
} from '../../shared/listTitles';
|
||||||
import { FORM_ID, IFormData, EMPTY_DATA, SetField } from './model';
|
import { FORM_ID, IFormData, EMPTY_DATA, SetField, hasOtherDatabase } from './model';
|
||||||
import { loadTicketRequest } from './load';
|
import { loadTicketRequest } from './load';
|
||||||
import { ticket1cApprovalService, getPlannerTaskId, setPlannerTaskId } from './submit';
|
import { ticket1cApprovalService, getPlannerTaskId, setPlannerTaskId } from './submit';
|
||||||
import { PlannerTicketService } from './services/plannerTicket.service';
|
import { PlannerTicketService } from './services/plannerTicket.service';
|
||||||
@@ -69,6 +69,7 @@ function buildPlannerNotes(
|
|||||||
lines.push(`Отдел: ${data.Department}`);
|
lines.push(`Отдел: ${data.Department}`);
|
||||||
lines.push(`Должность: ${data.Position}`);
|
lines.push(`Должность: ${data.Position}`);
|
||||||
lines.push(`База 1С: ${data.DB1C.join(', ')}`);
|
lines.push(`База 1С: ${data.DB1C.join(', ')}`);
|
||||||
|
if (hasOtherDatabase(data)) lines.push(`Другая база 1С: ${data.DB1COther}`);
|
||||||
lines.push(`Виды работ: ${data.WorkType}`);
|
lines.push(`Виды работ: ${data.WorkType}`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('Вопрос:');
|
lines.push('Вопрос:');
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { FORM_ID, IFormData, EMPTY_DATA } from './model';
|
|||||||
// TaskName пишется/читается через системную колонку Title (см. provisioning.json/titleColumnName).
|
// TaskName пишется/читается через системную колонку Title (см. provisioning.json/titleColumnName).
|
||||||
// DB1C — отдельно, это множественный выбор (см. toStringArray).
|
// DB1C — отдельно, это множественный выбор (см. toStringArray).
|
||||||
const SCALAR_FIELDS: (keyof IFormData)[] = [
|
const SCALAR_FIELDS: (keyof IFormData)[] = [
|
||||||
'WorkType', 'Organization', 'FIO', 'Email', 'Phone', 'Department', 'Position', 'Manager', 'Question',
|
'WorkType', 'Organization', 'DB1COther', 'FIO', 'Email', 'Phone', 'Department', 'Position', 'Manager', 'Question',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export interface IFormData {
|
|||||||
Organization: string;
|
Organization: string;
|
||||||
/** Базы 1С — множественный выбор (колонка MultiChoice), минимум одна. */
|
/** Базы 1С — множественный выбор (колонка MultiChoice), минимум одна. */
|
||||||
DB1C: string[];
|
DB1C: string[];
|
||||||
|
/** Название базы вручную — обязательно, только если в DB1C отмечено «Другое». */
|
||||||
|
DB1COther: string;
|
||||||
FIO: string;
|
FIO: string;
|
||||||
Email: string;
|
Email: string;
|
||||||
Phone: string;
|
Phone: string;
|
||||||
@@ -32,6 +34,16 @@ export interface IFormData {
|
|||||||
Question: string;
|
Question: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вариант колонки «База 1С», при выборе которого заявитель вписывает базу сам
|
||||||
|
* (поле DB1COther). Должен дословно совпадать с вариантом в колонке SP.
|
||||||
|
*/
|
||||||
|
export const DB1C_OTHER_OPTION = 'Другое';
|
||||||
|
|
||||||
|
export function hasOtherDatabase(data: IFormData): boolean {
|
||||||
|
return data.DB1C.indexOf(DB1C_OTHER_OPTION) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
/** Обновление одного поля формы с сохранением типа значения. */
|
/** Обновление одного поля формы с сохранением типа значения. */
|
||||||
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;
|
||||||
|
|
||||||
@@ -47,6 +59,7 @@ export const EMPTY_DATA: IFormData = {
|
|||||||
WorkType: '',
|
WorkType: '',
|
||||||
Organization: '',
|
Organization: '',
|
||||||
DB1C: [],
|
DB1C: [],
|
||||||
|
DB1COther: '',
|
||||||
FIO: '',
|
FIO: '',
|
||||||
Email: '',
|
Email: '',
|
||||||
Phone: '',
|
Phone: '',
|
||||||
|
|||||||
@@ -39,8 +39,10 @@
|
|||||||
"1С: Бухгалтерия ГеоТрек",
|
"1С: Бухгалтерия ГеоТрек",
|
||||||
"1С: Бухгалтерия Монтажник-Т",
|
"1С: Бухгалтерия Монтажник-Т",
|
||||||
"1С: Бухгалтерия ОСН Беркут",
|
"1С: Бухгалтерия ОСН Беркут",
|
||||||
"1С: Бухгалтерия CT Global"
|
"1С: Бухгалтерия CT Global",
|
||||||
|
"Другое"
|
||||||
] },
|
] },
|
||||||
|
{ "internalName": "DB1COther", "title": "База 1С (другое)", "type": "Text" },
|
||||||
|
|
||||||
{ "internalName": "FIO", "title": "Ф.И.О.", "type": "Text" },
|
{ "internalName": "FIO", "title": "Ф.И.О.", "type": "Text" },
|
||||||
{ "internalName": "Email", "title": "Email", "type": "Text" },
|
{ "internalName": "Email", "title": "Email", "type": "Text" },
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
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 { FORM_ID, ISectionProps } from '../model';
|
import { DB1C_OTHER_OPTION, FORM_ID, ISectionProps, hasOtherDatabase } from '../model';
|
||||||
import {
|
import {
|
||||||
QUESTION_MAX,
|
QUESTION_MAX,
|
||||||
CHOICES_LOADING_PLACEHOLDER,
|
CHOICES_LOADING_PLACEHOLDER,
|
||||||
@@ -55,14 +55,30 @@ export const RequestSection: React.FC<ISectionProps> = ({ data, set }) => {
|
|||||||
placeholder={choicesPlaceholder(organizations, '— выберите —')}
|
placeholder={choicesPlaceholder(organizations, '— выберите —')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="База 1С" fieldKey="DB1C" required>
|
{/* На всю ширину: чипы выбранных баз растят поле по высоте, и соседнее
|
||||||
|
поле в той же строке сетки съезжало бы относительно него. */}
|
||||||
|
<FormField label="База 1С" fieldKey="DB1C" required full>
|
||||||
<MultiChoiceField
|
<MultiChoiceField
|
||||||
value={data.DB1C}
|
value={data.DB1C}
|
||||||
onChange={(v) => set('DB1C', v)}
|
onChange={(v) => {
|
||||||
|
set('DB1C', v);
|
||||||
|
// Сняли «Другое» — вписанное название больше не относится к заявке.
|
||||||
|
if (v.indexOf(DB1C_OTHER_OPTION) === -1) set('DB1COther', '');
|
||||||
|
}}
|
||||||
options={databases.options}
|
options={databases.options}
|
||||||
placeholder={choicesPlaceholder(databases, 'Выберите одну или несколько баз')}
|
placeholder={choicesPlaceholder(databases, 'Выберите одну или несколько баз')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
{hasOtherDatabase(data) && (
|
||||||
|
<FormField label="Другая база 1С" htmlFor="DB1COther" required full>
|
||||||
|
<TextInput
|
||||||
|
id="DB1COther"
|
||||||
|
value={data.DB1COther}
|
||||||
|
onChange={(v) => set('DB1COther', v)}
|
||||||
|
placeholder="Укажите название базы"
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
)}
|
||||||
<FormField label={`Вопрос (${data.Question.length}/${QUESTION_MAX})`} htmlFor="Question" required full>
|
<FormField label={`Вопрос (${data.Question.length}/${QUESTION_MAX})`} htmlFor="Question" required full>
|
||||||
<TextArea
|
<TextArea
|
||||||
id="Question"
|
id="Question"
|
||||||
|
|||||||
Reference in New Issue
Block a user