feat: added unique request form code to forms
Tests / frontend-test (push) Successful in 1m7s

This commit is contained in:
Тимур Городецкий
2026-08-10 16:59:20 +05:00
parent 9aa8f88d66
commit dcf3f06121
12 changed files with 85 additions and 29 deletions
@@ -55,6 +55,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
// после первого сохранения.
const [requestId, setRequestId] = useState<number | undefined>(editId);
const [requestStatus, setRequestStatus] = useState<string | undefined>(undefined);
const [requestCode, setRequestCode] = useState<string | undefined>(undefined);
const submit = useRequestSubmit();
const readOnly = !isEditableStatus(requestStatus);
@@ -73,6 +74,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
if (alive) {
setData(loaded.data);
setRequestStatus(loaded.status);
setRequestCode(loaded.code || undefined);
setExistingAttachments(atts);
}
})
@@ -152,7 +154,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
};
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
const id = await saveHireRequest(
const { id, code } = await saveHireRequest(
requestId,
data,
toApprovers(matrix.rows),
@@ -161,6 +163,9 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
);
setRequestId(id);
setRequestStatus(status);
if (code) {
setRequestCode(code);
}
if (files.length > 0) {
await uploadAttachments(DATA_LIST_TITLE, id, files); // файлы — после создания элемента
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
@@ -218,7 +223,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
requestId || requestStatus ? (
<>
{requestStatus && <StatusBadge status={requestStatus} />}
{requestId && <RequestNumber id={requestId} />}
{requestCode && <RequestNumber code={requestCode} />}
</>
) : undefined
}
@@ -41,6 +41,7 @@ const NOOP_SET: SetField = () => undefined;
const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemId }) => {
const [data, setData] = useState<IFormData>(EMPTY_DATA);
const [status, setStatus] = useState<string>('');
const [code, setCode] = useState<string>('');
const [attachments, setAttachments] = useState<IAttachmentInfo[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
@@ -89,6 +90,7 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
if (alive) {
setData(loaded.data);
setStatus(loaded.status);
setCode(loaded.code);
setAttachments(files);
setError(null);
setLoading(false);
@@ -140,7 +142,7 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
<div className="rp-print-root">
<FormCard
title="Заявка на прием"
subtitle={`№${itemId}`}
subtitle={`№${code || itemId}`}
headerRight={status ? <StatusBadge status={status} /> : undefined}
>
<FormModeProvider readOnly>
@@ -13,6 +13,8 @@ const SCALAR_FIELDS: (keyof IFormData)[] = [
export interface ILoadedRequest {
data: IFormData;
status: string;
/** Уникальный код заявки; '' — если запись создана до появления поля. */
code: string;
}
/** Прочитать заявку «на прием» по id: модель формы + статус (для режима чтения). */
@@ -21,7 +23,7 @@ export async function loadHireRequest(itemId: number): Promise<ILoadedRequest> {
const item = (await sp.web.lists
.getByTitle(LIST_TITLE)
.items.getById(itemId)
.select(...SCALAR_FIELDS, 'Status')()) as Record<string, unknown>;
.select(...SCALAR_FIELDS, 'Status', 'RequestUniqueCode')()) as Record<string, unknown>;
const data: IFormData = { ...EMPTY_DATA };
const writable = data as unknown as Record<string, string>;
@@ -37,5 +39,5 @@ export async function loadHireRequest(itemId: number): Promise<ILoadedRequest> {
}
});
return { data, status: (item.Status as string) || '' };
return { data, status: (item.Status as string) || '', code: (item.RequestUniqueCode as string) || '' };
}
@@ -1,5 +1,6 @@
import { getSP } from '../../shared/pnp/pnpjs';
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
import { buildRequestUniqueCode } from '../../shared/requestCode';
import { IFormData } from './model';
export const DATA_LIST_TITLE = 'Прием на работу — данные';
@@ -37,12 +38,18 @@ function buildItem(data: IFormData, status: string): Record<string, unknown> {
return item;
}
export interface ISaveResult {
id: number;
/** Задан только при создании новой заявки (существующий код не меняется). */
code?: string;
}
/**
* Сохранить заявку «на прием»: контент + маршрут (согласующие + исполнители) со статусом.
* existingId задан → обновляем ту же запись (черновик/доработка), иначе создаём новую.
* status = «Черновик» (сохранение) или «На согласовании» (отправка).
* executors может быть пустым — тогда после согласования заявка станет «Согласовано».
* Возвращает Id заявки.
* Возвращает Id заявки (+ код при создании).
*/
export async function saveHireRequest(
existingId: number | undefined,
@@ -50,21 +57,23 @@ export async function saveHireRequest(
approvers: IApproverToWrite[],
executors: IApproverToWrite[],
status: string
): Promise<number> {
): Promise<ISaveResult> {
const list = getSP().web.lists.getByTitle(DATA_LIST_TITLE);
const item = buildItem(data, status);
let id: number;
let code: string | undefined;
if (existingId) {
await list.items.getById(existingId).update(item);
id = existingId;
} else {
const added = await list.items.add(item);
id = (added as { Id: number }).Id;
// Уникальный код заявки: пока = id (id известен только после создания).
await list.items.getById(id).update({ RequestUniqueCode: String(id) });
// Код известен только после создания элемента (нужен его Id).
code = buildRequestUniqueCode(HIRE_REQUEST_TYPE_ID, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
}
await writeRoute(hireApprovalService(), id, approvers, executors, status);
return id;
return { id, code };
}