Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/7
This commit was merged in pull request #7.
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
useMatrixEditor,
|
||||
getPlannedMatrix,
|
||||
notifyActiveStep,
|
||||
useRequestTypeId,
|
||||
REQUEST_STATUS,
|
||||
isEditableStatus,
|
||||
IApproverToWrite,
|
||||
@@ -32,13 +33,13 @@ import {
|
||||
deleteAttachment,
|
||||
IAttachmentInfo
|
||||
} from '../../shared/attachments/attachments.service';
|
||||
import { effectiveUserEmail } from '../../shared/impersonation';
|
||||
import { IRequestFormProps } from '../../models/types';
|
||||
import { IFormData, EMPTY_DATA, SetField } from './model';
|
||||
import {
|
||||
saveTicketRequest,
|
||||
setPlannerTaskId,
|
||||
ticket1cApprovalService,
|
||||
TICKET1C_REQUEST_TYPE_ID,
|
||||
DATA_LIST_TITLE,
|
||||
APPROVAL_LIST_TITLE
|
||||
} from './submit';
|
||||
@@ -132,7 +133,11 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({ requestType, context, onCan
|
||||
}, [editId]);
|
||||
|
||||
const approval = useMemo(() => ticket1cApprovalService(), []);
|
||||
const userEmail = context.pageContext.user.email;
|
||||
const userEmail = effectiveUserEmail(context);
|
||||
// pageUrl формы (requestType.id совпадает с ним по конвенции портала, см.
|
||||
// data/requests.ts) → числовой RequestTypeId, нужный approval-route API.
|
||||
const requestTypeIdState = useRequestTypeId(context, requestType.id);
|
||||
const requestTypeId = requestTypeIdState.id;
|
||||
|
||||
// Маршрут полностью и однозначно определяется видом работ — настроен в админке
|
||||
// маршрутов (условные шаги по WorkType, см. fieldValues), а не выбирается
|
||||
@@ -143,16 +148,16 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({ requestType, context, onCan
|
||||
() =>
|
||||
editId
|
||||
? approval.getMatrixForRequest(editId)
|
||||
: getPlannedMatrix(context, TICKET1C_REQUEST_TYPE_ID, userEmail, approval, 'Approval', null, [
|
||||
: getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, [
|
||||
{ fieldName: 'WorkType', value: data.WorkType }
|
||||
]),
|
||||
[context, userEmail, approval, editId, data.WorkType]
|
||||
[context, userEmail, approval, editId, data.WorkType, requestTypeId]
|
||||
);
|
||||
const matrix = useMatrixEditor(
|
||||
loadMatrix,
|
||||
`${userEmail}-${editId ?? 'new'}-${data.WorkType}`,
|
||||
false,
|
||||
!!editId || !!data.WorkType,
|
||||
!!editId || (!!data.WorkType && !!requestTypeId),
|
||||
(login) => approval.getJobTitle(login)
|
||||
);
|
||||
|
||||
@@ -163,10 +168,14 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({ requestType, context, onCan
|
||||
data.DB1C &&
|
||||
data.Question.trim()
|
||||
);
|
||||
const validate = (): string | undefined =>
|
||||
isValid
|
||||
? undefined
|
||||
: 'Заполните обязательные поля: наименование задачи, вид работ, организация, база 1С, вопрос.';
|
||||
const validate = (): string | undefined => {
|
||||
if (!isValid) {
|
||||
return 'Заполните обязательные поля: наименование задачи, вид работ, организация, база 1С, вопрос.';
|
||||
}
|
||||
if (requestTypeIdState.error) return `Не удалось определить тип заявки: ${requestTypeIdState.error}`;
|
||||
if (!requestTypeId) return 'Тип заявки ещё определяется — подождите немного и повторите.';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const toApprovers = (rows: typeof matrix.rows): IApproverToWrite[] =>
|
||||
rows.map((r) => ({
|
||||
@@ -190,6 +199,13 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({ requestType, context, onCan
|
||||
};
|
||||
|
||||
const save = (intendedStatus: string): (() => Promise<void>) => async (): Promise<void> => {
|
||||
// validate()/validateSubmit() уже гарантировали requestTypeId к моменту вызова
|
||||
// (см. useRequestSubmit.run — валидирует ДО save), но TS этого через границу
|
||||
// замыкания не видит — локальный const и явная проверка снимают both проблемы.
|
||||
const rtId = requestTypeId;
|
||||
if (!rtId) {
|
||||
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
||||
}
|
||||
// Пустой маршрут — согласование не требуется по правилам этого вида работ.
|
||||
const hasApprovers = matrix.rows.length > 0;
|
||||
const finalStatus =
|
||||
@@ -220,7 +236,7 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({ requestType, context, onCan
|
||||
|
||||
if (finalStatus === REQUEST_STATUS.onApproval) {
|
||||
await notifyActiveStep(context, approval, id, {
|
||||
requestTypeId: TICKET1C_REQUEST_TYPE_ID,
|
||||
requestTypeId: rtId,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
ApprovalMatrix,
|
||||
useMatrixEditor,
|
||||
notifyActiveStep,
|
||||
useRequestTypeId,
|
||||
isEditableStatus,
|
||||
REQUEST_STATUS
|
||||
} from '../../shared/approval';
|
||||
@@ -32,7 +33,6 @@ import {
|
||||
ticket1cApprovalService,
|
||||
getPlannerTaskId,
|
||||
setPlannerTaskId,
|
||||
TICKET1C_REQUEST_TYPE_ID,
|
||||
DATA_LIST_TITLE,
|
||||
APPROVAL_LIST_TITLE
|
||||
} from './submit';
|
||||
@@ -44,6 +44,8 @@ import { ContactSection } from './sections/ContactSection';
|
||||
interface ITicket1CViewProps {
|
||||
context: WebPartContext;
|
||||
itemId: number;
|
||||
/** pageUrl формы — см. IApprovalViewProps в forms/viewRegistry.ts. */
|
||||
formKey: string;
|
||||
}
|
||||
|
||||
/** Текст карточки Planner из данных заявки; rejection — автор и причина отклонения, если есть. */
|
||||
@@ -75,7 +77,7 @@ function buildPlannerNotes(
|
||||
}
|
||||
|
||||
// Заявка в отдел 1С только для просмотра (режим согласующего).
|
||||
const Ticket1CView: React.FC<ITicket1CViewProps> = ({ context, itemId }) => {
|
||||
const Ticket1CView: React.FC<ITicket1CViewProps> = ({ context, itemId, formKey }) => {
|
||||
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
||||
const [status, setStatus] = useState<string>('');
|
||||
const [code, setCode] = useState<string>('');
|
||||
@@ -83,6 +85,7 @@ const Ticket1CView: React.FC<ITicket1CViewProps> = ({ context, itemId }) => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const { id: requestTypeId } = useRequestTypeId(context, formKey);
|
||||
const set: SetField = (key, value) => setData((prev) => ({ ...prev, [key]: value }));
|
||||
|
||||
const approverService = useMemo(
|
||||
@@ -172,13 +175,22 @@ const Ticket1CView: React.FC<ITicket1CViewProps> = ({ context, itemId }) => {
|
||||
|
||||
const sectionProps = { data, set, context };
|
||||
|
||||
const notify = (): void => {
|
||||
if (!requestTypeId) return;
|
||||
notifyActiveStep(context, approval, itemId, {
|
||||
requestTypeId,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE
|
||||
}).catch(() => undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={`${styles.toolbar} rp-no-print`}>
|
||||
{isEditableStatus(status) && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => navigate({ mode: 'edit', nav: 'new', formKey: 'it-1c-ticket', itemId })}
|
||||
onClick={() => navigate({ mode: 'edit', nav: 'new', formKey, itemId })}
|
||||
>
|
||||
<Icon name="edit" size="sm" />
|
||||
Редактировать
|
||||
@@ -224,11 +236,7 @@ const Ticket1CView: React.FC<ITicket1CViewProps> = ({ context, itemId }) => {
|
||||
setStatus(newStatus);
|
||||
setDecisionSeq((n) => n + 1);
|
||||
// Очередь сдвинулась — уведомляем следующий активный шаг.
|
||||
notifyActiveStep(context, approval, itemId, {
|
||||
requestTypeId: TICKET1C_REQUEST_TYPE_ID,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE
|
||||
}).catch(() => undefined);
|
||||
notify();
|
||||
|
||||
if (newStatus === REQUEST_STATUS.approved || newStatus === REQUEST_STATUS.rejected) {
|
||||
handoffToPlanner(newStatus, data, code).catch((e) =>
|
||||
@@ -238,11 +246,7 @@ const Ticket1CView: React.FC<ITicket1CViewProps> = ({ context, itemId }) => {
|
||||
}}
|
||||
onDelegated={() => {
|
||||
setDecisionSeq((n) => n + 1);
|
||||
notifyActiveStep(context, approval, itemId, {
|
||||
requestTypeId: TICKET1C_REQUEST_TYPE_ID,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE
|
||||
}).catch(() => undefined);
|
||||
notify();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,14 +5,9 @@ import { IFormData } from './model';
|
||||
|
||||
export const DATA_LIST_TITLE = 'Заявка в отдел 1С — данные';
|
||||
export const APPROVAL_LIST_TITLE = 'Заявка в отдел 1С — согласование';
|
||||
// Тип заявки для кода заявки, резолва маршрута (resolveRoute) и уведомлений
|
||||
// (notifyActiveStep) — подтверждён через GET /api/request-types: id 10,
|
||||
// name "Заявка в отдел 1С" (не путать с «Маршрут #8» — это id самого маршрута
|
||||
// в админке, отдельная сущность от id типа заявки).
|
||||
export const TICKET1C_REQUEST_TYPE_ID = 10;
|
||||
// Префикс уникального кода заявки — буквенная метка «1С» вместо числового id
|
||||
// типа (10). Только для отображения/идентификации; на резолв маршрута и
|
||||
// уведомления (там нужен числовой TICKET1C_REQUEST_TYPE_ID) не влияет.
|
||||
// типа заявки. Числовой RequestTypeId (для резолва маршрута/уведомлений)
|
||||
// больше не хардкодится — см. useRequestTypeId в Form.tsx/View.tsx.
|
||||
export const TICKET1C_CODE_PREFIX = '1С';
|
||||
|
||||
/** Экземпляр сервиса списка согласования этой формы. */
|
||||
|
||||
Reference in New Issue
Block a user