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:
@@ -17,6 +17,7 @@ import {
|
||||
useMatrixEditor,
|
||||
getPlannedMatrix,
|
||||
notifyActiveStep,
|
||||
useRequestTypeId,
|
||||
REQUEST_STATUS,
|
||||
isEditableStatus,
|
||||
IApproverToWrite,
|
||||
@@ -24,13 +25,7 @@ import {
|
||||
} from '../../shared/approval';
|
||||
import { IRequestFormProps } from '../../models/types';
|
||||
import { IFormData, EMPTY_DATA, SetField } from './model';
|
||||
import {
|
||||
saveVacationRequest,
|
||||
vacationApprovalService,
|
||||
VACATION_REQUEST_TYPE_ID,
|
||||
DATA_LIST_TITLE,
|
||||
APPROVAL_LIST_TITLE
|
||||
} from './submit';
|
||||
import { saveVacationRequest, vacationApprovalService, DATA_LIST_TITLE, APPROVAL_LIST_TITLE } from './submit';
|
||||
import { loadVacationRequest } from './load';
|
||||
import { RequestSection } from './sections/RequestSection';
|
||||
|
||||
@@ -45,6 +40,10 @@ const VacationForm: React.FC<IRequestFormProps> = ({ requestType, context, onCan
|
||||
const set: SetField = (key, value) => setData((prev) => ({ ...prev, [key]: value }));
|
||||
|
||||
const approval = useMemo(() => vacationApprovalService(), []);
|
||||
// pageUrl формы (requestType.id совпадает с ним по конвенции портала, см.
|
||||
// data/requests.ts) → числовой RequestTypeId, нужный approval-route API.
|
||||
const requestTypeIdState = useRequestTypeId(context, requestType.id);
|
||||
const requestTypeId = requestTypeIdState.id;
|
||||
|
||||
// Открыта СВОЯ заявка на редактирование (черновик/возврат) — подгружаем содержимое и статус.
|
||||
useEffect(() => {
|
||||
@@ -112,14 +111,14 @@ const VacationForm: React.FC<IRequestFormProps> = ({ requestType, context, onCan
|
||||
() =>
|
||||
editId
|
||||
? approval.getMatrixForRequest(editId)
|
||||
: getPlannedMatrix(context, VACATION_REQUEST_TYPE_ID, data.Employee, approval, 'Approval'),
|
||||
[context, approval, editId, data.Employee]
|
||||
: getPlannedMatrix(context, requestTypeId as number, data.Employee, approval, 'Approval'),
|
||||
[context, approval, editId, data.Employee, requestTypeId]
|
||||
);
|
||||
const matrix = useMatrixEditor(
|
||||
loadMatrix,
|
||||
`${data.Employee}-${editId ?? 'new'}`,
|
||||
!readOnly,
|
||||
!!editId || !!data.Employee,
|
||||
!!editId || (!!data.Employee && !!requestTypeId),
|
||||
(login) => approval.getJobTitle(login)
|
||||
);
|
||||
|
||||
@@ -131,8 +130,12 @@ const VacationForm: React.FC<IRequestFormProps> = ({ requestType, context, onCan
|
||||
Number.isFinite(daysNum) &&
|
||||
daysNum > 0
|
||||
);
|
||||
const validate = (): string | undefined =>
|
||||
isValid ? undefined : 'Заполните обязательные поля: сотрудник, должность, дата начала, количество дней (> 0).';
|
||||
const validate = (): string | undefined => {
|
||||
if (!isValid) return 'Заполните обязательные поля: сотрудник, должность, дата начала, количество дней (> 0).';
|
||||
if (requestTypeIdState.error) return `Не удалось определить тип заявки: ${requestTypeIdState.error}`;
|
||||
if (!requestTypeId) return 'Тип заявки ещё определяется — подождите немного и повторите.';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const toApprovers = (rows: typeof matrix.rows): IApproverToWrite[] =>
|
||||
rows.map((r) => ({
|
||||
@@ -147,7 +150,14 @@ const VacationForm: React.FC<IRequestFormProps> = ({ requestType, context, onCan
|
||||
}));
|
||||
|
||||
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
|
||||
const { id, code } = await saveVacationRequest(requestId, data, toApprovers(matrix.rows), status);
|
||||
// validate()/validateSubmit() уже гарантировали requestTypeId к моменту вызова
|
||||
// (см. useRequestSubmit.run — валидирует ДО save), но TS этого через границу
|
||||
// замыкания не видит — локальный const и явная проверка снимают both проблемы.
|
||||
const rtId = requestTypeId;
|
||||
if (!rtId) {
|
||||
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
||||
}
|
||||
const { id, code } = await saveVacationRequest(requestId, rtId, data, toApprovers(matrix.rows), status);
|
||||
setRequestId(id);
|
||||
setRequestStatus(status);
|
||||
if (code) {
|
||||
@@ -159,7 +169,7 @@ const VacationForm: React.FC<IRequestFormProps> = ({ requestType, context, onCan
|
||||
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
||||
if (status === REQUEST_STATUS.onApproval) {
|
||||
await notifyActiveStep(context, approval, id, {
|
||||
requestTypeId: VACATION_REQUEST_TYPE_ID,
|
||||
requestTypeId: rtId,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE
|
||||
});
|
||||
|
||||
@@ -9,21 +9,19 @@ import {
|
||||
StatusBadge,
|
||||
ApprovalMatrix,
|
||||
useMatrixEditor,
|
||||
notifyActiveStep
|
||||
notifyActiveStep,
|
||||
useRequestTypeId
|
||||
} from '../../shared/approval';
|
||||
import { IFormData, EMPTY_DATA, SetField } from './model';
|
||||
import { loadVacationRequest } from './load';
|
||||
import {
|
||||
vacationApprovalService,
|
||||
VACATION_REQUEST_TYPE_ID,
|
||||
DATA_LIST_TITLE,
|
||||
APPROVAL_LIST_TITLE
|
||||
} from './submit';
|
||||
import { vacationApprovalService, DATA_LIST_TITLE, APPROVAL_LIST_TITLE } from './submit';
|
||||
import { RequestSection } from './sections/RequestSection';
|
||||
|
||||
interface IVacationViewProps {
|
||||
context: WebPartContext;
|
||||
itemId: number;
|
||||
/** pageUrl формы — см. IApprovalViewProps в forms/viewRegistry.ts. */
|
||||
formKey: string;
|
||||
}
|
||||
|
||||
// В режиме чтения поля не редактируются, поэтому обновление — пустышка.
|
||||
@@ -32,7 +30,7 @@ const NOOP_SET: SetField = () => undefined;
|
||||
// Заявка на отпуск только для просмотра (режим согласующего). Переиспользует ту
|
||||
// же секцию, что и форма подачи; редактирование выключено через
|
||||
// <FormModeProvider readOnly> — поля сами становятся неизменяемыми.
|
||||
const VacationView: React.FC<IVacationViewProps> = ({ context, itemId }) => {
|
||||
const VacationView: React.FC<IVacationViewProps> = ({ context, itemId, formKey }) => {
|
||||
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
||||
const [status, setStatus] = useState<string>('');
|
||||
const [code, setCode] = useState<string>('');
|
||||
@@ -40,6 +38,7 @@ const VacationView: React.FC<IVacationViewProps> = ({ context, itemId }) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const approval = useMemo(() => vacationApprovalService(), []);
|
||||
const { id: requestTypeId } = useRequestTypeId(context, formKey);
|
||||
const approverService = useMemo(() => new ApproverService(approval, DATA_LIST_TITLE), [approval]);
|
||||
|
||||
// Матрица маршрута из фактических строк заявки (read-only). decisionSeq в
|
||||
@@ -88,8 +87,12 @@ const VacationView: React.FC<IVacationViewProps> = ({ context, itemId }) => {
|
||||
const sectionProps = { data, set: NOOP_SET, context };
|
||||
|
||||
const notify = (): void => {
|
||||
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
||||
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
||||
// весь UI ради необязательного оповещения.
|
||||
if (!requestTypeId) return;
|
||||
notifyActiveStep(context, approval, itemId, {
|
||||
requestTypeId: VACATION_REQUEST_TYPE_ID,
|
||||
requestTypeId,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE
|
||||
}).catch(() => undefined);
|
||||
|
||||
@@ -5,11 +5,6 @@ import { IFormData } from './model';
|
||||
|
||||
export const DATA_LIST_TITLE = 'Заявка на отпуск — данные';
|
||||
export const APPROVAL_LIST_TITLE = 'Заявка на отпуск — согласование';
|
||||
// Тип заявки «отпуск» во внешнем approval-route API (GET /api/request-types → id 11,
|
||||
// name «Заявка на отпуск», pageUrl «hr-vacation»). Захардкожен, как
|
||||
// HIRE_REQUEST_TYPE_ID=5 в RecruitingHire. NB: в старой web part pto-tracker было 1 —
|
||||
// при заведении типа в админке портала он получил новый id 11, берём его.
|
||||
export const VACATION_REQUEST_TYPE_ID = 11;
|
||||
|
||||
/** Экземпляр сервиса списка согласования этой формы. */
|
||||
export function vacationApprovalService(): ApprovalListService {
|
||||
@@ -67,6 +62,7 @@ export interface ISaveResult {
|
||||
*/
|
||||
export async function saveVacationRequest(
|
||||
existingId: number | undefined,
|
||||
requestTypeId: number,
|
||||
data: IFormData,
|
||||
approvers: IApproverToWrite[],
|
||||
status: string
|
||||
@@ -84,7 +80,7 @@ export async function saveVacationRequest(
|
||||
const added = await list.items.add(item);
|
||||
id = (added as { Id: number }).Id;
|
||||
// Код известен только после создания элемента (нужен его Id).
|
||||
code = buildRequestUniqueCode(VACATION_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