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,
|
||||
@@ -33,15 +34,10 @@ 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 {
|
||||
saveTmcTransferRequest,
|
||||
tmcTransferApprovalService,
|
||||
TMC_TRANSFER_REQUEST_TYPE_ID,
|
||||
DATA_LIST_TITLE,
|
||||
APPROVAL_LIST_TITLE
|
||||
} from './submit';
|
||||
import { saveTmcTransferRequest, tmcTransferApprovalService, DATA_LIST_TITLE, APPROVAL_LIST_TITLE } from './submit';
|
||||
import { loadTmcTransferRequest } from './load';
|
||||
import { HeaderSection } from './sections/HeaderSection';
|
||||
import { ItemsSection } from './sections/ItemsSection';
|
||||
@@ -68,7 +64,10 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({ requestType, context, on
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
RequesterEmail: user.email || '',
|
||||
// email — через effectiveUserEmail (учитывает отладочную подмену личности);
|
||||
// displayName подменить нечем, справочник ФИО тут не читается — при
|
||||
// подмене имя останется настоящим, для отладки это неважно.
|
||||
RequesterEmail: effectiveUserEmail(context),
|
||||
AuthorName: user.displayName || '',
|
||||
RequestDate: prev.RequestDate || today
|
||||
}));
|
||||
@@ -100,7 +99,11 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({ requestType, context, on
|
||||
}, [editId]);
|
||||
|
||||
const approval = useMemo(() => tmcTransferApprovalService(), []);
|
||||
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;
|
||||
// У новой заявки маршрут ПЛАНИРУЕТСЯ (resolve по внешнему API), у открытой на
|
||||
// редактирование — читается из уже записанных строк.
|
||||
// projectId = oid проекта-источника: по нему бэкенд подставляет согласующего
|
||||
@@ -110,18 +113,18 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({ requestType, context, on
|
||||
// Маршрут согласования зависит от проекта-источника («Откуда»): пока он не
|
||||
// выбран, resolve вернёт 404 — не грузим, показываем заглушку. У открытой на
|
||||
// редактирование заявки маршрут уже записан, источник не нужен.
|
||||
const matrixReady = !!editId || !!projectId;
|
||||
const matrixReady = !!editId || (!!projectId && !!requestTypeId);
|
||||
// Исполнитель определяется проектом-получателем («Куда»): показываем блок с
|
||||
// заглушкой, пока «Куда» не выбрано. Источник тоже нужен, иначе resolve 404.
|
||||
const executorsReady = !!editId || (!!projectId && !!targetProjectId);
|
||||
const executorsReady = !!editId || (!!projectId && !!targetProjectId && !!requestTypeId);
|
||||
const loadMatrix = useCallback(
|
||||
() =>
|
||||
editId
|
||||
? approval
|
||||
.getMatrixForRequest(editId)
|
||||
.then((rows) => rows.filter((r) => r.phase !== APPROVAL_PHASE.execution))
|
||||
: getPlannedMatrix(context, TMC_TRANSFER_REQUEST_TYPE_ID, userEmail, approval, 'Approval', null, undefined, projectId),
|
||||
[context, userEmail, approval, editId, projectId]
|
||||
: getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Approval', null, undefined, projectId),
|
||||
[context, userEmail, approval, editId, projectId, requestTypeId]
|
||||
);
|
||||
const matrix = useMatrixEditor(loadMatrix, `${userEmail}-${editId ?? 'new'}-${projectId}`, !readOnly, matrixReady, (login) =>
|
||||
approval.getJobTitle(login)
|
||||
@@ -134,8 +137,8 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({ requestType, context, on
|
||||
? approval
|
||||
.getMatrixForRequest(editId)
|
||||
.then((rows) => rows.filter((r) => r.phase === APPROVAL_PHASE.execution))
|
||||
: getPlannedMatrix(context, TMC_TRANSFER_REQUEST_TYPE_ID, userEmail, approval, 'Execution', null, undefined, projectId),
|
||||
[context, userEmail, approval, editId, projectId]
|
||||
: getPlannedMatrix(context, requestTypeId as number, userEmail, approval, 'Execution', null, undefined, projectId),
|
||||
[context, userEmail, approval, editId, projectId, requestTypeId]
|
||||
);
|
||||
const executors = useMatrixEditor(
|
||||
loadExecutors,
|
||||
@@ -148,10 +151,14 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({ requestType, context, on
|
||||
const hasItems = data.Items.some((i) => i.name.trim().length > 0);
|
||||
const isValid =
|
||||
data.SourceProject.trim().length > 0 && data.TargetProject.trim().length > 0 && hasItems;
|
||||
const validate = (): string | undefined =>
|
||||
isValid
|
||||
? undefined
|
||||
: 'Заполните обязательные поля: проект-источник, проект-получатель и хотя бы одну позицию ТМЦ.';
|
||||
const validate = (): string | undefined => {
|
||||
if (!isValid) {
|
||||
return 'Заполните обязательные поля: проект-источник, проект-получатель и хотя бы одну позицию ТМЦ.';
|
||||
}
|
||||
if (requestTypeIdState.error) return `Не удалось определить тип заявки: ${requestTypeIdState.error}`;
|
||||
if (!requestTypeId) return 'Тип заявки ещё определяется — подождите немного и повторите.';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const toApprovers = (rows: typeof matrix.rows): IApproverToWrite[] =>
|
||||
rows.map((r) => ({
|
||||
@@ -172,8 +179,16 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({ requestType, context, on
|
||||
};
|
||||
|
||||
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
|
||||
// validate()/validateSubmit() уже гарантировали requestTypeId к моменту вызова
|
||||
// (см. useRequestSubmit.run — валидирует ДО save), но TS этого через границу
|
||||
// замыкания не видит — локальный const и явная проверка снимают both проблемы.
|
||||
const rtId = requestTypeId;
|
||||
if (!rtId) {
|
||||
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
||||
}
|
||||
const { id, code } = await saveTmcTransferRequest(
|
||||
requestId,
|
||||
rtId,
|
||||
data,
|
||||
toApprovers(matrix.rows),
|
||||
toApprovers(executors.rows),
|
||||
@@ -194,7 +209,7 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({ requestType, context, on
|
||||
}
|
||||
if (status === REQUEST_STATUS.onApproval) {
|
||||
await notifyActiveStep(context, approval, id, {
|
||||
requestTypeId: TMC_TRANSFER_REQUEST_TYPE_ID,
|
||||
requestTypeId: rtId,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE
|
||||
});
|
||||
|
||||
@@ -22,28 +22,26 @@ import {
|
||||
ApprovalMatrix,
|
||||
useMatrixEditor,
|
||||
notifyActiveStep,
|
||||
useRequestTypeId,
|
||||
isEditableStatus,
|
||||
APPROVAL_PHASE
|
||||
} from '../../shared/approval';
|
||||
import { getAttachments, IAttachmentInfo } from '../../shared/attachments/attachments.service';
|
||||
import { IFormData, EMPTY_DATA, SetField } from './model';
|
||||
import { loadTmcTransferRequest } from './load';
|
||||
import {
|
||||
tmcTransferApprovalService,
|
||||
TMC_TRANSFER_REQUEST_TYPE_ID,
|
||||
DATA_LIST_TITLE,
|
||||
APPROVAL_LIST_TITLE
|
||||
} from './submit';
|
||||
import { tmcTransferApprovalService, DATA_LIST_TITLE, APPROVAL_LIST_TITLE } from './submit';
|
||||
import { HeaderSection } from './sections/HeaderSection';
|
||||
import { ItemsSection } from './sections/ItemsSection';
|
||||
|
||||
interface ITmcTransferViewProps {
|
||||
context: WebPartContext;
|
||||
itemId: number;
|
||||
/** pageUrl формы — см. IApprovalViewProps в forms/viewRegistry.ts. */
|
||||
formKey: string;
|
||||
}
|
||||
|
||||
// Заявка «СЗ на перемещение ТМЦ» только для просмотра (режим согласующего).
|
||||
const TmcTransferView: React.FC<ITmcTransferViewProps> = ({ context, itemId }) => {
|
||||
const TmcTransferView: React.FC<ITmcTransferViewProps> = ({ context, itemId, formKey }) => {
|
||||
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
||||
const [status, setStatus] = useState<string>('');
|
||||
const [code, setCode] = useState<string>('');
|
||||
@@ -51,6 +49,7 @@ const TmcTransferView: React.FC<ITmcTransferViewProps> = ({ context, itemId }) =
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const { id: requestTypeId } = useRequestTypeId(context, formKey);
|
||||
// В просмотре форма read-only, но секции ждут set — даём no-op.
|
||||
const set: SetField = () => undefined;
|
||||
|
||||
@@ -120,13 +119,22 @@ const TmcTransferView: React.FC<ITmcTransferViewProps> = ({ 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: 'wh-tmc-transfer', itemId })}
|
||||
onClick={() => navigate({ mode: 'edit', nav: 'new', formKey, itemId })}
|
||||
>
|
||||
<Icon name="edit" size="sm" />
|
||||
Редактировать
|
||||
@@ -177,19 +185,11 @@ const TmcTransferView: React.FC<ITmcTransferViewProps> = ({ context, itemId }) =
|
||||
onDecided={(newStatus) => {
|
||||
setStatus(newStatus);
|
||||
setDecisionSeq((n) => n + 1);
|
||||
notifyActiveStep(context, approval, itemId, {
|
||||
requestTypeId: TMC_TRANSFER_REQUEST_TYPE_ID,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE
|
||||
}).catch(() => undefined);
|
||||
notify();
|
||||
}}
|
||||
onDelegated={() => {
|
||||
setDecisionSeq((n) => n + 1);
|
||||
notifyActiveStep(context, approval, itemId, {
|
||||
requestTypeId: TMC_TRANSFER_REQUEST_TYPE_ID,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE
|
||||
}).catch(() => undefined);
|
||||
notify();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,9 +6,6 @@ import { IFormData } from './model';
|
||||
|
||||
export const DATA_LIST_TITLE = 'СЗ на перемещение ТМЦ — данные';
|
||||
export const APPROVAL_LIST_TITLE = 'СЗ на перемещение ТМЦ — согласование';
|
||||
// Тип заявки во внешнем approval-route API — подтверждён через GET /api/request-types:
|
||||
// id 12, name «Заявка на перемещение», pageUrl «wh-tmc-transfer», section «Снабжение».
|
||||
export const TMC_TRANSFER_REQUEST_TYPE_ID = 12;
|
||||
|
||||
/** Экземпляр сервиса списка согласования этой формы. */
|
||||
export function tmcTransferApprovalService(): ApprovalListService {
|
||||
@@ -52,6 +49,7 @@ export interface ISaveResult {
|
||||
*/
|
||||
export async function saveTmcTransferRequest(
|
||||
existingId: number | undefined,
|
||||
requestTypeId: number,
|
||||
data: IFormData,
|
||||
approvers: IApproverToWrite[],
|
||||
executors: IApproverToWrite[],
|
||||
@@ -69,7 +67,7 @@ export async function saveTmcTransferRequest(
|
||||
const added = await list.items.add(item);
|
||||
id = (added as { Id: number }).Id;
|
||||
// Код известен только после создания элемента (нужен его Id).
|
||||
code = buildRequestUniqueCode(TMC_TRANSFER_REQUEST_TYPE_ID, id, new Date());
|
||||
code = buildRequestUniqueCode(requestTypeId, id, new Date());
|
||||
await list.items.getById(id).update({ RequestUniqueCode: code });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user