This commit is contained in:
@@ -55,6 +55,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
|||||||
// после первого сохранения.
|
// после первого сохранения.
|
||||||
const [requestId, setRequestId] = useState<number | undefined>(editId);
|
const [requestId, setRequestId] = useState<number | undefined>(editId);
|
||||||
const [requestStatus, setRequestStatus] = useState<string | undefined>(undefined);
|
const [requestStatus, setRequestStatus] = useState<string | undefined>(undefined);
|
||||||
|
const [requestCode, setRequestCode] = useState<string | undefined>(undefined);
|
||||||
const submit = useRequestSubmit();
|
const submit = useRequestSubmit();
|
||||||
|
|
||||||
const readOnly = !isEditableStatus(requestStatus);
|
const readOnly = !isEditableStatus(requestStatus);
|
||||||
@@ -73,6 +74,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
|||||||
if (alive) {
|
if (alive) {
|
||||||
setData(loaded.data);
|
setData(loaded.data);
|
||||||
setRequestStatus(loaded.status);
|
setRequestStatus(loaded.status);
|
||||||
|
setRequestCode(loaded.code || undefined);
|
||||||
setExistingAttachments(atts);
|
setExistingAttachments(atts);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -152,7 +154,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
|||||||
};
|
};
|
||||||
|
|
||||||
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
|
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
|
||||||
const id = await saveHireRequest(
|
const { id, code } = await saveHireRequest(
|
||||||
requestId,
|
requestId,
|
||||||
data,
|
data,
|
||||||
toApprovers(matrix.rows),
|
toApprovers(matrix.rows),
|
||||||
@@ -161,6 +163,9 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
|||||||
);
|
);
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(status);
|
setRequestStatus(status);
|
||||||
|
if (code) {
|
||||||
|
setRequestCode(code);
|
||||||
|
}
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
await uploadAttachments(DATA_LIST_TITLE, id, files); // файлы — после создания элемента
|
await uploadAttachments(DATA_LIST_TITLE, id, files); // файлы — после создания элемента
|
||||||
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
||||||
@@ -218,7 +223,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
|||||||
requestId || requestStatus ? (
|
requestId || requestStatus ? (
|
||||||
<>
|
<>
|
||||||
{requestStatus && <StatusBadge status={requestStatus} />}
|
{requestStatus && <StatusBadge status={requestStatus} />}
|
||||||
{requestId && <RequestNumber id={requestId} />}
|
{requestCode && <RequestNumber code={requestCode} />}
|
||||||
</>
|
</>
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const NOOP_SET: SetField = () => undefined;
|
|||||||
const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemId }) => {
|
const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemId }) => {
|
||||||
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
||||||
const [status, setStatus] = useState<string>('');
|
const [status, setStatus] = useState<string>('');
|
||||||
|
const [code, setCode] = useState<string>('');
|
||||||
const [attachments, setAttachments] = useState<IAttachmentInfo[]>([]);
|
const [attachments, setAttachments] = useState<IAttachmentInfo[]>([]);
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -89,6 +90,7 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
|
|||||||
if (alive) {
|
if (alive) {
|
||||||
setData(loaded.data);
|
setData(loaded.data);
|
||||||
setStatus(loaded.status);
|
setStatus(loaded.status);
|
||||||
|
setCode(loaded.code);
|
||||||
setAttachments(files);
|
setAttachments(files);
|
||||||
setError(null);
|
setError(null);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -140,7 +142,7 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
|
|||||||
<div className="rp-print-root">
|
<div className="rp-print-root">
|
||||||
<FormCard
|
<FormCard
|
||||||
title="Заявка на прием"
|
title="Заявка на прием"
|
||||||
subtitle={`№${itemId}`}
|
subtitle={`№${code || itemId}`}
|
||||||
headerRight={status ? <StatusBadge status={status} /> : undefined}
|
headerRight={status ? <StatusBadge status={status} /> : undefined}
|
||||||
>
|
>
|
||||||
<FormModeProvider readOnly>
|
<FormModeProvider readOnly>
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const SCALAR_FIELDS: (keyof IFormData)[] = [
|
|||||||
export interface ILoadedRequest {
|
export interface ILoadedRequest {
|
||||||
data: IFormData;
|
data: IFormData;
|
||||||
status: string;
|
status: string;
|
||||||
|
/** Уникальный код заявки; '' — если запись создана до появления поля. */
|
||||||
|
code: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Прочитать заявку «на прием» по id: модель формы + статус (для режима чтения). */
|
/** Прочитать заявку «на прием» по id: модель формы + статус (для режима чтения). */
|
||||||
@@ -21,7 +23,7 @@ export async function loadHireRequest(itemId: number): Promise<ILoadedRequest> {
|
|||||||
const item = (await sp.web.lists
|
const item = (await sp.web.lists
|
||||||
.getByTitle(LIST_TITLE)
|
.getByTitle(LIST_TITLE)
|
||||||
.items.getById(itemId)
|
.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 data: IFormData = { ...EMPTY_DATA };
|
||||||
const writable = data as unknown as Record<string, string>;
|
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 { getSP } from '../../shared/pnp/pnpjs';
|
||||||
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
|
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
|
||||||
|
import { buildRequestUniqueCode } from '../../shared/requestCode';
|
||||||
import { IFormData } from './model';
|
import { IFormData } from './model';
|
||||||
|
|
||||||
export const DATA_LIST_TITLE = 'Прием на работу — данные';
|
export const DATA_LIST_TITLE = 'Прием на работу — данные';
|
||||||
@@ -37,12 +38,18 @@ function buildItem(data: IFormData, status: string): Record<string, unknown> {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISaveResult {
|
||||||
|
id: number;
|
||||||
|
/** Задан только при создании новой заявки (существующий код не меняется). */
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Сохранить заявку «на прием»: контент + маршрут (согласующие + исполнители) со статусом.
|
* Сохранить заявку «на прием»: контент + маршрут (согласующие + исполнители) со статусом.
|
||||||
* existingId задан → обновляем ту же запись (черновик/доработка), иначе создаём новую.
|
* existingId задан → обновляем ту же запись (черновик/доработка), иначе создаём новую.
|
||||||
* status = «Черновик» (сохранение) или «На согласовании» (отправка).
|
* status = «Черновик» (сохранение) или «На согласовании» (отправка).
|
||||||
* executors может быть пустым — тогда после согласования заявка станет «Согласовано».
|
* executors может быть пустым — тогда после согласования заявка станет «Согласовано».
|
||||||
* Возвращает Id заявки.
|
* Возвращает Id заявки (+ код при создании).
|
||||||
*/
|
*/
|
||||||
export async function saveHireRequest(
|
export async function saveHireRequest(
|
||||||
existingId: number | undefined,
|
existingId: number | undefined,
|
||||||
@@ -50,21 +57,23 @@ export async function saveHireRequest(
|
|||||||
approvers: IApproverToWrite[],
|
approvers: IApproverToWrite[],
|
||||||
executors: IApproverToWrite[],
|
executors: IApproverToWrite[],
|
||||||
status: string
|
status: string
|
||||||
): Promise<number> {
|
): Promise<ISaveResult> {
|
||||||
const list = getSP().web.lists.getByTitle(DATA_LIST_TITLE);
|
const list = getSP().web.lists.getByTitle(DATA_LIST_TITLE);
|
||||||
const item = buildItem(data, status);
|
const item = buildItem(data, status);
|
||||||
|
|
||||||
let id: number;
|
let id: number;
|
||||||
|
let code: string | undefined;
|
||||||
if (existingId) {
|
if (existingId) {
|
||||||
await list.items.getById(existingId).update(item);
|
await list.items.getById(existingId).update(item);
|
||||||
id = existingId;
|
id = existingId;
|
||||||
} else {
|
} else {
|
||||||
const added = await list.items.add(item);
|
const added = await list.items.add(item);
|
||||||
id = (added as { Id: number }).Id;
|
id = (added as { Id: number }).Id;
|
||||||
// Уникальный код заявки: пока = id (id известен только после создания).
|
// Код известен только после создания элемента (нужен его Id).
|
||||||
await list.items.getById(id).update({ RequestUniqueCode: String(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);
|
await writeRoute(hireApprovalService(), id, approvers, executors, status);
|
||||||
return id;
|
return { id, code };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({ requestType, con
|
|||||||
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
||||||
const [saving, setSaving] = useState<boolean>(false);
|
const [saving, setSaving] = useState<boolean>(false);
|
||||||
const [saved, setSaved] = useState<boolean>(false);
|
const [saved, setSaved] = useState<boolean>(false);
|
||||||
|
const [savedCode, setSavedCode] = useState<string | undefined>(undefined);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const set: SetField = (key, value) => {
|
const set: SetField = (key, value) => {
|
||||||
@@ -35,8 +36,9 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({ requestType, con
|
|||||||
setError(null);
|
setError(null);
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await submitRecruitingSelection(data);
|
const code = await submitRecruitingSelection(data);
|
||||||
setSaved(true);
|
setSaved(true);
|
||||||
|
setSavedCode(code);
|
||||||
if (onSaved) {
|
if (onSaved) {
|
||||||
onSaved();
|
onSaved();
|
||||||
}
|
}
|
||||||
@@ -57,7 +59,9 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({ requestType, con
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<FormCard title={requestType.title} subtitle="Категория: Отдел рекрутинга">
|
<FormCard title={requestType.title} subtitle="Категория: Отдел рекрутинга">
|
||||||
{saved && <Alert variant="ok">Заявка сохранена в SharePoint.</Alert>}
|
{saved && (
|
||||||
|
<Alert variant="ok">Заявка сохранена в SharePoint. Код заявки: №{savedCode}.</Alert>
|
||||||
|
)}
|
||||||
{error && <Alert variant="err">{error}</Alert>}
|
{error && <Alert variant="err">{error}</Alert>}
|
||||||
|
|
||||||
<ApplicationSection {...sectionProps} />
|
<ApplicationSection {...sectionProps} />
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { getSP } from '../../shared/pnp/pnpjs';
|
import { getSP } from '../../shared/pnp/pnpjs';
|
||||||
|
import { buildRequestUniqueCode } from '../../shared/requestCode';
|
||||||
import { IFormData } from './model';
|
import { IFormData } from './model';
|
||||||
|
|
||||||
const LIST_TITLE = 'Заявка на подбор — данные';
|
const LIST_TITLE = 'Заявка на подбор — данные';
|
||||||
|
// Тип заявки во внешнем approval-route API (GET /api/request-types → id 4).
|
||||||
|
const RECRUITING_SELECTION_REQUEST_TYPE_ID = 4;
|
||||||
|
|
||||||
// Поля-люди (один человек): логин → ensureUser → Id в колонку <Field>Id.
|
// Поля-люди (один человек): логин → ensureUser → Id в колонку <Field>Id.
|
||||||
const PERSON_FIELDS: (keyof IFormData)[] = ['Responsible', 'ProposedMentor', 'DirectManager'];
|
const PERSON_FIELDS: (keyof IFormData)[] = ['Responsible', 'ProposedMentor', 'DirectManager'];
|
||||||
@@ -16,8 +19,8 @@ async function resolveUserId(login: string): Promise<number> {
|
|||||||
return ensured.Id;
|
return ensured.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Записать заявку «на подбор» в список данных SharePoint. */
|
/** Записать заявку «на подбор» в список данных SharePoint. Возвращает уникальный код заявки. */
|
||||||
export async function submitRecruitingSelection(data: IFormData): Promise<void> {
|
export async function submitRecruitingSelection(data: IFormData): Promise<string> {
|
||||||
const sp = getSP();
|
const sp = getSP();
|
||||||
const item: Record<string, unknown> = {};
|
const item: Record<string, unknown> = {};
|
||||||
|
|
||||||
@@ -56,6 +59,8 @@ export async function submitRecruitingSelection(data: IFormData): Promise<void>
|
|||||||
|
|
||||||
const added = await sp.web.lists.getByTitle(LIST_TITLE).items.add(item);
|
const added = await sp.web.lists.getByTitle(LIST_TITLE).items.add(item);
|
||||||
const id = (added as { Id: number }).Id;
|
const id = (added as { Id: number }).Id;
|
||||||
// Уникальный код заявки: пока = id (id известен только после создания).
|
// Код известен только после создания элемента (нужен его Id).
|
||||||
await sp.web.lists.getByTitle(LIST_TITLE).items.getById(id).update({ RequestUniqueCode: String(id) });
|
const code = buildRequestUniqueCode(RECRUITING_SELECTION_REQUEST_TYPE_ID, id, new Date());
|
||||||
|
await sp.web.lists.getByTitle(LIST_TITLE).items.getById(id).update({ RequestUniqueCode: code });
|
||||||
|
return code;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
|||||||
// открытии на редактирование (editId) — сразу из загруженной записи.
|
// открытии на редактирование (editId) — сразу из загруженной записи.
|
||||||
const [requestId, setRequestId] = useState<number | undefined>(editId);
|
const [requestId, setRequestId] = useState<number | undefined>(editId);
|
||||||
const [requestStatus, setRequestStatus] = useState<string | undefined>(undefined);
|
const [requestStatus, setRequestStatus] = useState<string | undefined>(undefined);
|
||||||
|
const [requestCode, setRequestCode] = useState<string | undefined>(undefined);
|
||||||
const submit = useRequestSubmit();
|
const submit = useRequestSubmit();
|
||||||
|
|
||||||
// Редактировать можно новую/черновик/возвращённую/отклонённую; иначе только чтение.
|
// Редактировать можно новую/черновик/возвращённую/отклонённую; иначе только чтение.
|
||||||
@@ -74,6 +75,7 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
|||||||
if (alive) {
|
if (alive) {
|
||||||
setData(loaded.data);
|
setData(loaded.data);
|
||||||
setRequestStatus(loaded.status);
|
setRequestStatus(loaded.status);
|
||||||
|
setRequestCode(loaded.code || undefined);
|
||||||
setExistingAttachments(atts);
|
setExistingAttachments(atts);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -175,7 +177,7 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
|||||||
};
|
};
|
||||||
|
|
||||||
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
|
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
|
||||||
const id = await saveWorkplaceRequest(
|
const { id, code } = await saveWorkplaceRequest(
|
||||||
requestId,
|
requestId,
|
||||||
data,
|
data,
|
||||||
toApprovers(matrix.rows),
|
toApprovers(matrix.rows),
|
||||||
@@ -185,6 +187,9 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
|||||||
);
|
);
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(status);
|
setRequestStatus(status);
|
||||||
|
if (code) {
|
||||||
|
setRequestCode(code);
|
||||||
|
}
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
await uploadAttachments(DATA_LIST_TITLE, id, files);
|
await uploadAttachments(DATA_LIST_TITLE, id, files);
|
||||||
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
||||||
@@ -242,7 +247,7 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
|||||||
requestId || requestStatus ? (
|
requestId || requestStatus ? (
|
||||||
<>
|
<>
|
||||||
{requestStatus && <StatusBadge status={requestStatus} />}
|
{requestStatus && <StatusBadge status={requestStatus} />}
|
||||||
{requestId && <RequestNumber id={requestId} />}
|
{requestCode && <RequestNumber code={requestCode} />}
|
||||||
</>
|
</>
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ type SaveState = 'idle' | 'saving' | 'saved' | 'error';
|
|||||||
const WorkplaceSetupView: React.FC<IWorkplaceSetupViewProps> = ({ context, itemId }) => {
|
const WorkplaceSetupView: React.FC<IWorkplaceSetupViewProps> = ({ context, itemId }) => {
|
||||||
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
||||||
const [status, setStatus] = useState<string>('');
|
const [status, setStatus] = useState<string>('');
|
||||||
|
const [code, setCode] = useState<string>('');
|
||||||
const [attachments, setAttachments] = useState<IAttachmentInfo[]>([]);
|
const [attachments, setAttachments] = useState<IAttachmentInfo[]>([]);
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -101,6 +102,7 @@ const WorkplaceSetupView: React.FC<IWorkplaceSetupViewProps> = ({ context, itemI
|
|||||||
if (alive) {
|
if (alive) {
|
||||||
setData(loaded.data);
|
setData(loaded.data);
|
||||||
setStatus(loaded.status);
|
setStatus(loaded.status);
|
||||||
|
setCode(loaded.code);
|
||||||
setAttachments(files);
|
setAttachments(files);
|
||||||
setError(null);
|
setError(null);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -202,7 +204,7 @@ const WorkplaceSetupView: React.FC<IWorkplaceSetupViewProps> = ({ context, itemI
|
|||||||
<div className="rp-print-root">
|
<div className="rp-print-root">
|
||||||
<FormCard
|
<FormCard
|
||||||
title="Организация рабочего места"
|
title="Организация рабочего места"
|
||||||
subtitle={`№${itemId}`}
|
subtitle={`№${code || itemId}`}
|
||||||
headerRight={status ? <StatusBadge status={status} /> : undefined}
|
headerRight={status ? <StatusBadge status={status} /> : undefined}
|
||||||
>
|
>
|
||||||
<FormModeProvider readOnly>
|
<FormModeProvider readOnly>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const SCALAR_FIELDS: (keyof IFormData)[] = [
|
|||||||
export interface ILoadedRequest {
|
export interface ILoadedRequest {
|
||||||
data: IFormData;
|
data: IFormData;
|
||||||
status: string;
|
status: string;
|
||||||
|
/** Уникальный код заявки; '' — если запись создана до появления поля. */
|
||||||
|
code: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Прочитать заявку по id: модель формы + статус (для режима чтения). */
|
/** Прочитать заявку по id: модель формы + статус (для режима чтения). */
|
||||||
@@ -18,7 +20,7 @@ export async function loadWorkplaceRequest(itemId: number): Promise<ILoadedReque
|
|||||||
const item = (await getSP()
|
const item = (await getSP()
|
||||||
.web.lists.getByTitle(LIST_TITLE)
|
.web.lists.getByTitle(LIST_TITLE)
|
||||||
.items.getById(itemId)
|
.items.getById(itemId)
|
||||||
.select(...SCALAR_FIELDS, 'NeedsEquipment', 'Status')()) as Record<string, unknown>;
|
.select(...SCALAR_FIELDS, 'NeedsEquipment', 'Status', 'RequestUniqueCode')()) as Record<string, unknown>;
|
||||||
|
|
||||||
const data: IFormData = { ...EMPTY_DATA };
|
const data: IFormData = { ...EMPTY_DATA };
|
||||||
const writable = data as unknown as Record<string, string>;
|
const writable = data as unknown as Record<string, string>;
|
||||||
@@ -36,5 +38,5 @@ export async function loadWorkplaceRequest(itemId: number): Promise<ILoadedReque
|
|||||||
|
|
||||||
data.NeedsEquipment = item.NeedsEquipment === true;
|
data.NeedsEquipment = item.NeedsEquipment === true;
|
||||||
|
|
||||||
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 { getSP } from '../../shared/pnp/pnpjs';
|
||||||
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
|
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
|
||||||
|
import { buildRequestUniqueCode } from '../../shared/requestCode';
|
||||||
import { IFormData } from './model';
|
import { IFormData } from './model';
|
||||||
|
|
||||||
export const DATA_LIST_TITLE = 'Организация рабочего места — данные';
|
export const DATA_LIST_TITLE = 'Организация рабочего места — данные';
|
||||||
@@ -51,11 +52,17 @@ function buildItem(data: IFormData, status: string, sourceHireId?: number): Reco
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISaveResult {
|
||||||
|
id: number;
|
||||||
|
/** Задан только при создании новой заявки (существующий код не меняется). */
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Сохранить заявку «орг. рабочего места»: контент + маршрут (согласующие + исполнители).
|
* Сохранить заявку «орг. рабочего места»: контент + маршрут (согласующие + исполнители).
|
||||||
* existingId задан → обновляем ту же запись (черновик/доработка), иначе создаём новую.
|
* existingId задан → обновляем ту же запись (черновик/доработка), иначе создаём новую.
|
||||||
* Исполнители пишутся сразу — фаза исполнения стартует автоматически после согласования.
|
* Исполнители пишутся сразу — фаза исполнения стартует автоматически после согласования.
|
||||||
* sourceHireId — если заявка создана на основе «прием». Возвращает Id заявки.
|
* sourceHireId — если заявка создана на основе «прием». Возвращает Id заявки (+ код при создании).
|
||||||
*/
|
*/
|
||||||
export async function saveWorkplaceRequest(
|
export async function saveWorkplaceRequest(
|
||||||
existingId: number | undefined,
|
existingId: number | undefined,
|
||||||
@@ -64,21 +71,23 @@ export async function saveWorkplaceRequest(
|
|||||||
executors: IApproverToWrite[],
|
executors: IApproverToWrite[],
|
||||||
status: string,
|
status: string,
|
||||||
sourceHireId?: number
|
sourceHireId?: number
|
||||||
): Promise<number> {
|
): Promise<ISaveResult> {
|
||||||
const list = getSP().web.lists.getByTitle(DATA_LIST_TITLE);
|
const list = getSP().web.lists.getByTitle(DATA_LIST_TITLE);
|
||||||
const item = buildItem(data, status, sourceHireId);
|
const item = buildItem(data, status, sourceHireId);
|
||||||
|
|
||||||
let id: number;
|
let id: number;
|
||||||
|
let code: string | undefined;
|
||||||
if (existingId) {
|
if (existingId) {
|
||||||
await list.items.getById(existingId).update(item);
|
await list.items.getById(existingId).update(item);
|
||||||
id = existingId;
|
id = existingId;
|
||||||
} else {
|
} else {
|
||||||
const added = await list.items.add(item);
|
const added = await list.items.add(item);
|
||||||
id = (added as { Id: number }).Id;
|
id = (added as { Id: number }).Id;
|
||||||
// Уникальный код заявки: пока = id (id известен только после создания).
|
// Код известен только после создания элемента (нужен его Id).
|
||||||
await list.items.getById(id).update({ RequestUniqueCode: String(id) });
|
code = buildRequestUniqueCode(WORKPLACE_REQUEST_TYPE_ID, id, new Date());
|
||||||
|
await list.items.getById(id).update({ RequestUniqueCode: code });
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeRoute(workplaceApprovalService(), id, approvers, executors, status);
|
await writeRoute(workplaceApprovalService(), id, approvers, executors, status);
|
||||||
return id;
|
return { id, code };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Уникальный код заявки: {requestTypeId}-{YY}{MM}-{itemId}.
|
||||||
|
* requestTypeId — id типа заявки во внешнем approval-route API (GET /api/request-types).
|
||||||
|
* YY/MM — год и месяц подачи, itemId — Id элемента в списке данных. Пример: 1-2608-12.
|
||||||
|
*/
|
||||||
|
export function buildRequestUniqueCode(requestTypeId: number, itemId: number, submittedAt: Date): string {
|
||||||
|
const yy = String(submittedAt.getFullYear()).slice(-2);
|
||||||
|
const month = submittedAt.getMonth() + 1;
|
||||||
|
const mm = month < 10 ? `0${month}` : String(month);
|
||||||
|
return `${requestTypeId}-${yy}${mm}-${itemId}`;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import styles from './RequestNumber.module.scss';
|
import styles from './RequestNumber.module.scss';
|
||||||
|
|
||||||
/** Номер заявки (Id элемента списка) — плашка в правом углу шапки формы. */
|
/** Уникальный код заявки ({requestTypeId}-{YY}{MM}-{itemId}) — плашка в правом углу шапки формы. */
|
||||||
export const RequestNumber: React.FC<{ id: number }> = ({ id }) => (
|
export const RequestNumber: React.FC<{ code: string }> = ({ code }) => (
|
||||||
<span className={styles.badge}>№{id}</span>
|
<span className={styles.badge}>№{code}</span>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user