вызов метода взятия партиции перед сабмитом
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
|||||||
StatusBadge,
|
StatusBadge,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
getPlannedMatrix,
|
getPlannedMatrix,
|
||||||
|
getCurrentListPartition,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
REQUEST_STATUS,
|
REQUEST_STATUS,
|
||||||
@@ -189,6 +190,21 @@ const ClearanceForm: React.FC<IRequestFormProps> = ({
|
|||||||
if (!rtId) {
|
if (!rtId) {
|
||||||
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
||||||
}
|
}
|
||||||
|
// requestId уже задан → редактируем существующую заявку, пишем в ту же
|
||||||
|
// партицию (editListId). Иначе — создаём новую, и партицию для записи
|
||||||
|
// спрашиваем у approval-route ПРЯМО СЕЙЧАС (а не берём из кеша onInit):
|
||||||
|
// это единственный запрос, который проверяет порог элементов текущего
|
||||||
|
// списка и ставит в очередь создание следующей партиции при достижении —
|
||||||
|
// без него архивация никогда не сработает. Сбой запроса не должен
|
||||||
|
// блокировать подачу заявки — откатываемся на список по конвенции имён.
|
||||||
|
let writeListId = editListId;
|
||||||
|
if (!requestId) {
|
||||||
|
try {
|
||||||
|
writeListId = (await getCurrentListPartition(context, rtId)).listId;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Не удалось получить текущую партицию списка — используется список по конвенции имён', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
const { id, code } = await saveClearanceRequest(
|
const { id, code } = await saveClearanceRequest(
|
||||||
requestId,
|
requestId,
|
||||||
rtId,
|
rtId,
|
||||||
@@ -196,7 +212,7 @@ const ClearanceForm: React.FC<IRequestFormProps> = ({
|
|||||||
toApprovers(matrix.rows),
|
toApprovers(matrix.rows),
|
||||||
toApprovers(executors.rows),
|
toApprovers(executors.rows),
|
||||||
status,
|
status,
|
||||||
editListId
|
writeListId
|
||||||
);
|
);
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(status);
|
setRequestStatus(status);
|
||||||
@@ -207,13 +223,14 @@ const ClearanceForm: React.FC<IRequestFormProps> = ({
|
|||||||
onSaved();
|
onSaved();
|
||||||
}
|
}
|
||||||
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
||||||
// Имена списков — той же партиции, что и сама заявка (editListId), не текущей:
|
// Имена списков — той же партиции, куда фактически записали (writeListId),
|
||||||
// поток ищет строку маршрута именно в НЕЙ.
|
// не обязательно текущей на момент рендера формы: поток ищет строку
|
||||||
|
// маршрута именно в НЕЙ.
|
||||||
if (status === REQUEST_STATUS.onApproval) {
|
if (status === REQUEST_STATUS.onApproval) {
|
||||||
await notifyActiveStep(context, approval, id, {
|
await notifyActiveStep(context, approval, id, {
|
||||||
requestTypeId: rtId,
|
requestTypeId: rtId,
|
||||||
dataListName: editListId ? dataListTitleForListId(FORM_ID, editListId) : dataListTitle(FORM_ID),
|
dataListName: writeListId ? dataListTitleForListId(FORM_ID, writeListId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: editListId ? approvalListTitleForListId(FORM_ID, editListId) : approvalListTitle(FORM_ID)
|
approvalListName: writeListId ? approvalListTitleForListId(FORM_ID, writeListId) : approvalListTitle(FORM_ID)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -94,10 +94,13 @@ export interface ISaveResult {
|
|||||||
* existingId задан → обновляем ту же запись (черновик/доработка), иначе создаём новую.
|
* existingId задан → обновляем ту же запись (черновик/доработка), иначе создаём новую.
|
||||||
* Возвращает Id заявки (+ код при создании).
|
* Возвращает Id заявки (+ код при создании).
|
||||||
*
|
*
|
||||||
* listId — партиция ОБНОВЛЯЕМОЙ заявки (см. loadClearanceRequest). Имеет смысл
|
* listId — партиция, в которую фактически пишем: при редактировании — партиция
|
||||||
* только вместе с existingId: новая заявка всегда создаётся в ТЕКУЩЕЙ партиции —
|
* ОБНОВЛЯЕМОЙ заявки (см. loadClearanceRequest), при создании — партиция,
|
||||||
* писать новые заявки в архив незачем и нельзя (approval-route переключает
|
* полученная от approval-route API непосредственно перед вызовом (см.
|
||||||
* текущую партицию сам, по своему порогу элементов).
|
* getCurrentListPartition в ClearanceForm.tsx) — именно этот вызов, а не кеш из
|
||||||
|
* onInit, проверяет порог элементов и запускает архивацию следующей партиции.
|
||||||
|
* Не задан (сбой запроса за текущей партицией) → откат на список по конвенции
|
||||||
|
* имён, как до появления партиционирования.
|
||||||
*/
|
*/
|
||||||
export async function saveClearanceRequest(
|
export async function saveClearanceRequest(
|
||||||
existingId: number | undefined,
|
existingId: number | undefined,
|
||||||
@@ -108,8 +111,7 @@ export async function saveClearanceRequest(
|
|||||||
status: string,
|
status: string,
|
||||||
listId?: string
|
listId?: string
|
||||||
): Promise<ISaveResult> {
|
): Promise<ISaveResult> {
|
||||||
const list =
|
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
||||||
existingId && listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
|
||||||
const employee = await ensureEmployee(data.Employee);
|
const employee = await ensureEmployee(data.Employee);
|
||||||
const item = buildItem(data, status, employee.Id, employee.Title || data.Employee);
|
const item = buildItem(data, status, employee.Id, employee.Title || data.Employee);
|
||||||
|
|
||||||
@@ -126,6 +128,6 @@ export async function saveClearanceRequest(
|
|||||||
await list.items.getById(id).update({ RequestUniqueCode: code });
|
await list.items.getById(id).update({ RequestUniqueCode: code });
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeRoute(clearanceApprovalService(existingId ? listId : undefined), id, approvers, executors, status);
|
await writeRoute(clearanceApprovalService(listId), id, approvers, executors, status);
|
||||||
return { id, code };
|
return { id, code };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
StatusBadge,
|
StatusBadge,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
getPlannedMatrix,
|
getPlannedMatrix,
|
||||||
|
getCurrentListPartition,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
REQUEST_STATUS,
|
REQUEST_STATUS,
|
||||||
@@ -186,6 +187,21 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({
|
|||||||
if (!rtId) {
|
if (!rtId) {
|
||||||
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
||||||
}
|
}
|
||||||
|
// requestId уже задан → редактируем существующую заявку, пишем в ту же
|
||||||
|
// партицию (editListId). Иначе — создаём новую, и партицию для записи
|
||||||
|
// спрашиваем у approval-route ПРЯМО СЕЙЧАС (а не берём из кеша onInit):
|
||||||
|
// это единственный запрос, который проверяет порог элементов текущего
|
||||||
|
// списка и ставит в очередь создание следующей партиции при достижении —
|
||||||
|
// без него архивация никогда не сработает. Сбой запроса не должен
|
||||||
|
// блокировать подачу заявки — откатываемся на список по конвенции имён.
|
||||||
|
let writeListId = editListId;
|
||||||
|
if (!requestId) {
|
||||||
|
try {
|
||||||
|
writeListId = (await getCurrentListPartition(context, rtId)).listId;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Не удалось получить текущую партицию списка — используется список по конвенции имён', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
const { id, code } = await saveHireRequest(
|
const { id, code } = await saveHireRequest(
|
||||||
requestId,
|
requestId,
|
||||||
rtId,
|
rtId,
|
||||||
@@ -193,7 +209,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({
|
|||||||
toApprovers(matrix.rows),
|
toApprovers(matrix.rows),
|
||||||
toApprovers(executors.rows),
|
toApprovers(executors.rows),
|
||||||
status,
|
status,
|
||||||
editListId
|
writeListId
|
||||||
);
|
);
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(status);
|
setRequestStatus(status);
|
||||||
@@ -201,21 +217,22 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({
|
|||||||
setRequestCode(code);
|
setRequestCode(code);
|
||||||
}
|
}
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
await uploadAttachments(editListId || dataListTitle(FORM_ID), id, files); // файлы — после создания элемента
|
await uploadAttachments(writeListId || dataListTitle(FORM_ID), id, files); // файлы — после создания элемента
|
||||||
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
||||||
setExistingAttachments(await getAttachments(editListId || dataListTitle(FORM_ID), id)); // показать добавленные
|
setExistingAttachments(await getAttachments(writeListId || dataListTitle(FORM_ID), id)); // показать добавленные
|
||||||
}
|
}
|
||||||
if (onSaved) {
|
if (onSaved) {
|
||||||
onSaved();
|
onSaved();
|
||||||
}
|
}
|
||||||
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
||||||
// Имена списков — той же партиции, что и сама заявка (editListId), не текущей:
|
// Имена списков — той же партиции, куда фактически записали (writeListId),
|
||||||
// поток ищет строку маршрута именно в НЕЙ.
|
// не обязательно текущей на момент рендера формы: поток ищет строку
|
||||||
|
// маршрута именно в НЕЙ.
|
||||||
if (status === REQUEST_STATUS.onApproval) {
|
if (status === REQUEST_STATUS.onApproval) {
|
||||||
await notifyActiveStep(context, approval, id, {
|
await notifyActiveStep(context, approval, id, {
|
||||||
requestTypeId: rtId,
|
requestTypeId: rtId,
|
||||||
dataListName: editListId ? dataListTitleForListId(FORM_ID, editListId) : dataListTitle(FORM_ID),
|
dataListName: writeListId ? dataListTitleForListId(FORM_ID, writeListId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: editListId ? approvalListTitleForListId(FORM_ID, editListId) : approvalListTitle(FORM_ID),
|
approvalListName: writeListId ? approvalListTitleForListId(FORM_ID, writeListId) : approvalListTitle(FORM_ID),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,10 +55,13 @@ export interface ISaveResult {
|
|||||||
* executors может быть пустым — тогда после согласования заявка станет «Согласовано».
|
* executors может быть пустым — тогда после согласования заявка станет «Согласовано».
|
||||||
* Возвращает Id заявки (+ код при создании).
|
* Возвращает Id заявки (+ код при создании).
|
||||||
*
|
*
|
||||||
* listId — партиция ОБНОВЛЯЕМОЙ заявки (см. loadHireRequest). Имеет смысл
|
* listId — партиция, в которую фактически пишем: при редактировании — партиция
|
||||||
* только вместе с existingId: новая заявка всегда создаётся в ТЕКУЩЕЙ партиции —
|
* ОБНОВЛЯЕМОЙ заявки (см. loadHireRequest), при создании — партиция, полученная
|
||||||
* писать новые заявки в архив незачем и нельзя (approval-route переключает
|
* от approval-route API непосредственно перед вызовом (см. getCurrentListPartition
|
||||||
* текущую партицию сам, по своему порогу элементов).
|
* в RecruitingHireForm.tsx) — именно этот вызов, а не кеш из onInit, проверяет
|
||||||
|
* порог элементов и запускает архивацию следующей партиции.
|
||||||
|
* Не задан (сбой запроса за текущей партицией) → откат на список по конвенции
|
||||||
|
* имён, как до появления партиционирования.
|
||||||
*/
|
*/
|
||||||
export async function saveHireRequest(
|
export async function saveHireRequest(
|
||||||
existingId: number | undefined,
|
existingId: number | undefined,
|
||||||
@@ -69,8 +72,7 @@ export async function saveHireRequest(
|
|||||||
status: string,
|
status: string,
|
||||||
listId?: string
|
listId?: string
|
||||||
): Promise<ISaveResult> {
|
): Promise<ISaveResult> {
|
||||||
const list =
|
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
||||||
existingId && listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
|
||||||
const item = buildItem(data, status);
|
const item = buildItem(data, status);
|
||||||
|
|
||||||
let id: number;
|
let id: number;
|
||||||
@@ -86,6 +88,6 @@ export async function saveHireRequest(
|
|||||||
await list.items.getById(id).update({ RequestUniqueCode: code });
|
await list.items.getById(id).update({ RequestUniqueCode: code });
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeRoute(hireApprovalService(existingId ? listId : undefined), id, approvers, executors, status);
|
await writeRoute(hireApprovalService(listId), id, approvers, executors, status);
|
||||||
return { id, code };
|
return { id, code };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
StatusBadge,
|
StatusBadge,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
getPlannedMatrix,
|
getPlannedMatrix,
|
||||||
|
getCurrentListPartition,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
REQUEST_STATUS,
|
REQUEST_STATUS,
|
||||||
@@ -160,13 +161,28 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
|
|||||||
if (!rtId) {
|
if (!rtId) {
|
||||||
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
||||||
}
|
}
|
||||||
|
// requestId уже задан → редактируем существующую заявку, пишем в ту же
|
||||||
|
// партицию (editListId). Иначе — создаём новую, и партицию для записи
|
||||||
|
// спрашиваем у approval-route ПРЯМО СЕЙЧАС (а не берём из кеша onInit):
|
||||||
|
// это единственный запрос, который проверяет порог элементов текущего
|
||||||
|
// списка и ставит в очередь создание следующей партиции при достижении —
|
||||||
|
// без него архивация никогда не сработает. Сбой запроса не должен
|
||||||
|
// блокировать подачу заявки — откатываемся на список по конвенции имён.
|
||||||
|
let writeListId = editListId;
|
||||||
|
if (!requestId) {
|
||||||
|
try {
|
||||||
|
writeListId = (await getCurrentListPartition(context, rtId)).listId;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Не удалось получить текущую партицию списка — используется список по конвенции имён', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
const { id, code } = await saveSelectionRequest(
|
const { id, code } = await saveSelectionRequest(
|
||||||
requestId,
|
requestId,
|
||||||
rtId,
|
rtId,
|
||||||
data,
|
data,
|
||||||
toApprovers(matrix.rows),
|
toApprovers(matrix.rows),
|
||||||
status,
|
status,
|
||||||
editListId
|
writeListId
|
||||||
);
|
);
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(status);
|
setRequestStatus(status);
|
||||||
@@ -174,21 +190,22 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
|
|||||||
setRequestCode(code);
|
setRequestCode(code);
|
||||||
}
|
}
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
await uploadAttachments(editListId || dataListTitle(FORM_ID), id, files); // файлы — после создания элемента
|
await uploadAttachments(writeListId || dataListTitle(FORM_ID), id, files); // файлы — после создания элемента
|
||||||
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
||||||
setExistingAttachments(await getAttachments(editListId || dataListTitle(FORM_ID), id));
|
setExistingAttachments(await getAttachments(writeListId || dataListTitle(FORM_ID), id));
|
||||||
}
|
}
|
||||||
if (onSaved) {
|
if (onSaved) {
|
||||||
onSaved();
|
onSaved();
|
||||||
}
|
}
|
||||||
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
||||||
// Имена списков — той же партиции, что и сама заявка (editListId), не текущей:
|
// Имена списков — той же партиции, куда фактически записали (writeListId),
|
||||||
// поток ищет строку маршрута именно в НЕЙ.
|
// не обязательно текущей на момент рендера формы: поток ищет строку
|
||||||
|
// маршрута именно в НЕЙ.
|
||||||
if (status === REQUEST_STATUS.onApproval) {
|
if (status === REQUEST_STATUS.onApproval) {
|
||||||
await notifyActiveStep(context, approval, id, {
|
await notifyActiveStep(context, approval, id, {
|
||||||
requestTypeId: rtId,
|
requestTypeId: rtId,
|
||||||
dataListName: editListId ? dataListTitleForListId(FORM_ID, editListId) : dataListTitle(FORM_ID),
|
dataListName: writeListId ? dataListTitleForListId(FORM_ID, writeListId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: editListId ? approvalListTitleForListId(FORM_ID, editListId) : approvalListTitle(FORM_ID)
|
approvalListName: writeListId ? approvalListTitleForListId(FORM_ID, writeListId) : approvalListTitle(FORM_ID)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,10 +98,13 @@ export interface ISaveResult {
|
|||||||
* status = «Черновик» (сохранение) или «На согласовании» (отправка). Фазы исполнения
|
* status = «Черновик» (сохранение) или «На согласовании» (отправка). Фазы исполнения
|
||||||
* нет — writeRoute вызывается с пустым списком исполнителей. Возвращает Id (+ код при создании).
|
* нет — writeRoute вызывается с пустым списком исполнителей. Возвращает Id (+ код при создании).
|
||||||
*
|
*
|
||||||
* listId — партиция ОБНОВЛЯЕМОЙ заявки (см. loadSelectionRequest). Имеет смысл
|
* listId — партиция, в которую фактически пишем: при редактировании — партиция
|
||||||
* только вместе с existingId: новая заявка всегда создаётся в ТЕКУЩЕЙ партиции —
|
* ОБНОВЛЯЕМОЙ заявки (см. loadSelectionRequest), при создании — партиция,
|
||||||
* писать новые заявки в архив незачем и нельзя (approval-route переключает
|
* полученная от approval-route API непосредственно перед вызовом (см.
|
||||||
* текущую партицию сам, по своему порогу элементов).
|
* getCurrentListPartition в RecruitingSelectionForm.tsx) — именно этот вызов,
|
||||||
|
* а не кеш из onInit, проверяет порог элементов и запускает архивацию
|
||||||
|
* следующей партиции. Не задан (сбой запроса за текущей партицией) → откат
|
||||||
|
* на список по конвенции имён, как до появления партиционирования.
|
||||||
*/
|
*/
|
||||||
export async function saveSelectionRequest(
|
export async function saveSelectionRequest(
|
||||||
existingId: number | undefined,
|
existingId: number | undefined,
|
||||||
@@ -111,8 +114,7 @@ export async function saveSelectionRequest(
|
|||||||
status: string,
|
status: string,
|
||||||
listId?: string
|
listId?: string
|
||||||
): Promise<ISaveResult> {
|
): Promise<ISaveResult> {
|
||||||
const list =
|
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
||||||
existingId && listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
|
||||||
const item = buildScalarBody(data);
|
const item = buildScalarBody(data);
|
||||||
item.RequestStatus = status;
|
item.RequestStatus = status;
|
||||||
await applyPeople(item, data);
|
await applyPeople(item, data);
|
||||||
@@ -130,6 +132,6 @@ export async function saveSelectionRequest(
|
|||||||
await list.items.getById(id).update({ RequestUniqueCode: code });
|
await list.items.getById(id).update({ RequestUniqueCode: code });
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeRoute(selectionApprovalService(existingId ? listId : undefined), id, approvers, [], status);
|
await writeRoute(selectionApprovalService(listId), id, approvers, [], status);
|
||||||
return { id, code };
|
return { id, code };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
StatusBadge,
|
StatusBadge,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
getPlannedMatrix,
|
getPlannedMatrix,
|
||||||
|
getCurrentListPartition,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
REQUEST_STATUS,
|
REQUEST_STATUS,
|
||||||
@@ -225,12 +226,28 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
|
|||||||
? REQUEST_STATUS.onApproval
|
? REQUEST_STATUS.onApproval
|
||||||
: REQUEST_STATUS.approved;
|
: REQUEST_STATUS.approved;
|
||||||
|
|
||||||
|
// requestId уже задан → редактируем существующую заявку, пишем в ту же
|
||||||
|
// партицию (editListId). Иначе — создаём новую, и партицию для записи
|
||||||
|
// спрашиваем у approval-route ПРЯМО СЕЙЧАС (а не берём из кеша onInit):
|
||||||
|
// это единственный запрос, который проверяет порог элементов текущего
|
||||||
|
// списка и ставит в очередь создание следующей партиции при достижении —
|
||||||
|
// без него архивация никогда не сработает. Сбой запроса не должен
|
||||||
|
// блокировать подачу заявки — откатываемся на список по конвенции имён.
|
||||||
|
let writeListId = editListId;
|
||||||
|
if (!requestId) {
|
||||||
|
try {
|
||||||
|
writeListId = (await getCurrentListPartition(context, rtId)).listId;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Не удалось получить текущую партицию списка — используется список по конвенции имён', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { id, code } = await saveTicketRequest(
|
const { id, code } = await saveTicketRequest(
|
||||||
requestId,
|
requestId,
|
||||||
data,
|
data,
|
||||||
toApprovers(matrix.rows),
|
toApprovers(matrix.rows),
|
||||||
finalStatus,
|
finalStatus,
|
||||||
editListId
|
writeListId
|
||||||
);
|
);
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(finalStatus);
|
setRequestStatus(finalStatus);
|
||||||
@@ -240,9 +257,9 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
|
|||||||
|
|
||||||
let attachments = existingAttachments;
|
let attachments = existingAttachments;
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
await uploadAttachments(editListId || dataListTitle(FORM_ID), id, files);
|
await uploadAttachments(writeListId || dataListTitle(FORM_ID), id, files);
|
||||||
setFiles([]);
|
setFiles([]);
|
||||||
attachments = await getAttachments(editListId || dataListTitle(FORM_ID), id);
|
attachments = await getAttachments(writeListId || dataListTitle(FORM_ID), id);
|
||||||
setExistingAttachments(attachments);
|
setExistingAttachments(attachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,12 +268,13 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (finalStatus === REQUEST_STATUS.onApproval) {
|
if (finalStatus === REQUEST_STATUS.onApproval) {
|
||||||
// Имена списков — той же партиции, что и сама заявка (editListId), не
|
// Имена списков — той же партиции, куда фактически записали (writeListId),
|
||||||
// текущей: поток ищет строку маршрута именно в НЕЙ.
|
// не обязательно текущей на момент рендера формы: поток ищет строку
|
||||||
|
// маршрута именно в НЕЙ.
|
||||||
await notifyActiveStep(context, approval, id, {
|
await notifyActiveStep(context, approval, id, {
|
||||||
requestTypeId: rtId,
|
requestTypeId: rtId,
|
||||||
dataListName: editListId ? dataListTitleForListId(FORM_ID, editListId) : dataListTitle(FORM_ID),
|
dataListName: writeListId ? dataListTitleForListId(FORM_ID, writeListId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: editListId ? approvalListTitleForListId(FORM_ID, editListId) : approvalListTitle(FORM_ID)
|
approvalListName: writeListId ? approvalListTitleForListId(FORM_ID, writeListId) : approvalListTitle(FORM_ID)
|
||||||
});
|
});
|
||||||
} else if (finalStatus === REQUEST_STATUS.approved) {
|
} else if (finalStatus === REQUEST_STATUS.approved) {
|
||||||
// Согласование не требуется для этого вида работ — сразу передаём в отдел 1С.
|
// Согласование не требуется для этого вида работ — сразу передаём в отдел 1С.
|
||||||
@@ -268,7 +286,7 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
|
|||||||
attachments,
|
attachments,
|
||||||
webAbsoluteUrl: context.pageContext.web.absoluteUrl
|
webAbsoluteUrl: context.pageContext.web.absoluteUrl
|
||||||
});
|
});
|
||||||
await setPlannerTaskId(id, taskId, editListId);
|
await setPlannerTaskId(id, taskId, writeListId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -48,10 +48,13 @@ export interface ISaveResult {
|
|||||||
* Фазы исполнения нет: после согласования заявка уходит в Planner, а не исполнителю
|
* Фазы исполнения нет: после согласования заявка уходит в Planner, а не исполнителю
|
||||||
* из матрицы. Возвращает Id заявки (+ код при создании).
|
* из матрицы. Возвращает Id заявки (+ код при создании).
|
||||||
*
|
*
|
||||||
* listId — партиция ОБНОВЛЯЕМОЙ заявки (см. loadTicketRequest). Имеет смысл
|
* listId — партиция, в которую фактически пишем: при редактировании — партиция
|
||||||
* только вместе с existingId: новая заявка всегда создаётся в ТЕКУЩЕЙ партиции —
|
* ОБНОВЛЯЕМОЙ заявки (см. loadTicketRequest), при создании — партиция,
|
||||||
* писать новые заявки в архив незачем и нельзя (approval-route переключает
|
* полученная от approval-route API непосредственно перед вызовом (см.
|
||||||
* текущую партицию сам, по своему порогу элементов).
|
* getCurrentListPartition в Ticket1CForm.tsx) — именно этот вызов, а не кеш из
|
||||||
|
* onInit, проверяет порог элементов и запускает архивацию следующей партиции.
|
||||||
|
* Не задан (сбой запроса за текущей партицией) → откат на список по конвенции
|
||||||
|
* имён, как до появления партиционирования.
|
||||||
*/
|
*/
|
||||||
export async function saveTicketRequest(
|
export async function saveTicketRequest(
|
||||||
existingId: number | undefined,
|
existingId: number | undefined,
|
||||||
@@ -60,8 +63,7 @@ export async function saveTicketRequest(
|
|||||||
status: string,
|
status: string,
|
||||||
listId?: string
|
listId?: string
|
||||||
): Promise<ISaveResult> {
|
): Promise<ISaveResult> {
|
||||||
const list =
|
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
||||||
existingId && listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
|
||||||
const item = buildItem(data, status);
|
const item = buildItem(data, status);
|
||||||
|
|
||||||
let id: number;
|
let id: number;
|
||||||
@@ -77,7 +79,7 @@ export async function saveTicketRequest(
|
|||||||
await list.items.getById(id).update({ RequestUniqueCode: code });
|
await list.items.getById(id).update({ RequestUniqueCode: code });
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeRoute(ticket1cApprovalService(existingId ? listId : undefined), id, approvers, [], status);
|
await writeRoute(ticket1cApprovalService(listId), id, approvers, [], status);
|
||||||
return { id, code };
|
return { id, code };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import {
|
|||||||
isEditableStatus,
|
isEditableStatus,
|
||||||
IApproverToWrite,
|
IApproverToWrite,
|
||||||
APPROVAL_PHASE,
|
APPROVAL_PHASE,
|
||||||
findUnassignedStep
|
findUnassignedStep,
|
||||||
|
getCurrentListPartition
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
import {
|
import {
|
||||||
uploadAttachments,
|
uploadAttachments,
|
||||||
@@ -202,6 +203,17 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({
|
|||||||
if (!rtId) {
|
if (!rtId) {
|
||||||
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
||||||
}
|
}
|
||||||
|
// Новая заявка — резолвим ТЕКУЩУЮ партицию через approval-route (заодно
|
||||||
|
// выполняется проверка порога и создание следующей архивной партиции при
|
||||||
|
// необходимости); при редактировании остаёмся в партиции самой заявки.
|
||||||
|
let writeListId = editListId;
|
||||||
|
if (!requestId) {
|
||||||
|
try {
|
||||||
|
writeListId = (await getCurrentListPartition(context, rtId)).listId;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Не удалось получить текущую партицию списка — используется список по конвенции имён', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
const { id, code } = await saveTmcTransferRequest(
|
const { id, code } = await saveTmcTransferRequest(
|
||||||
requestId,
|
requestId,
|
||||||
rtId,
|
rtId,
|
||||||
@@ -209,7 +221,7 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({
|
|||||||
toApprovers(matrix.rows),
|
toApprovers(matrix.rows),
|
||||||
toApprovers(executors.rows),
|
toApprovers(executors.rows),
|
||||||
status,
|
status,
|
||||||
editListId
|
writeListId
|
||||||
);
|
);
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(status);
|
setRequestStatus(status);
|
||||||
@@ -217,21 +229,22 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({
|
|||||||
setRequestCode(code);
|
setRequestCode(code);
|
||||||
}
|
}
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
await uploadAttachments(editListId || dataListTitle(FORM_ID), id, files);
|
await uploadAttachments(writeListId || dataListTitle(FORM_ID), id, files);
|
||||||
setFiles([]);
|
setFiles([]);
|
||||||
setExistingAttachments(await getAttachments(editListId || dataListTitle(FORM_ID), id));
|
setExistingAttachments(await getAttachments(writeListId || dataListTitle(FORM_ID), id));
|
||||||
}
|
}
|
||||||
if (onSaved) {
|
if (onSaved) {
|
||||||
onSaved();
|
onSaved();
|
||||||
}
|
}
|
||||||
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
||||||
// Имена списков — той же партиции, что и сама заявка (editListId), не текущей:
|
// Имена списков — той же партиции, куда только что записали (writeListId),
|
||||||
// поток ищет строку маршрута именно в НЕЙ.
|
// а не editListId: для новой заявки это может быть только что созданная
|
||||||
|
// текущая партиция, отличная от editListId (который тут undefined).
|
||||||
if (status === REQUEST_STATUS.onApproval) {
|
if (status === REQUEST_STATUS.onApproval) {
|
||||||
await notifyActiveStep(context, approval, id, {
|
await notifyActiveStep(context, approval, id, {
|
||||||
requestTypeId: rtId,
|
requestTypeId: rtId,
|
||||||
dataListName: editListId ? dataListTitleForListId(FORM_ID, editListId) : dataListTitle(FORM_ID),
|
dataListName: writeListId ? dataListTitleForListId(FORM_ID, writeListId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: editListId ? approvalListTitleForListId(FORM_ID, editListId) : approvalListTitle(FORM_ID)
|
approvalListName: writeListId ? approvalListTitleForListId(FORM_ID, writeListId) : approvalListTitle(FORM_ID)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,10 +54,11 @@ export interface ISaveResult {
|
|||||||
* existingId задан → обновляем ту же запись (черновик/доработка), иначе создаём новую.
|
* existingId задан → обновляем ту же запись (черновик/доработка), иначе создаём новую.
|
||||||
* Возвращает Id заявки (+ уникальный код при создании).
|
* Возвращает Id заявки (+ уникальный код при создании).
|
||||||
*
|
*
|
||||||
* listId — партиция ОБНОВЛЯЕМОЙ заявки (см. loadTmcTransferRequest). Имеет смысл
|
* listId — партиция, в которую пишем: для обновления существующей заявки —
|
||||||
* только вместе с existingId: новая заявка всегда создаётся в ТЕКУЩЕЙ партиции —
|
* партиция ЭТОЙ заявки (см. loadTmcTransferRequest), для новой — ТЕКУЩАЯ
|
||||||
* писать новые заявки в архив незачем и нельзя (approval-route переключает
|
* партиция (см. getCurrentListPartition), которую вызывающая сторона резолвит
|
||||||
* текущую партицию сам, по своему порогу элементов).
|
* заранее и передаёт сюда, а не текущая-по-умолчанию: именно на этом резолве
|
||||||
|
* держится проверка порога и создание следующей архивной партиции.
|
||||||
*/
|
*/
|
||||||
export async function saveTmcTransferRequest(
|
export async function saveTmcTransferRequest(
|
||||||
existingId: number | undefined,
|
existingId: number | undefined,
|
||||||
@@ -68,8 +69,7 @@ export async function saveTmcTransferRequest(
|
|||||||
status: string,
|
status: string,
|
||||||
listId?: string
|
listId?: string
|
||||||
): Promise<ISaveResult> {
|
): Promise<ISaveResult> {
|
||||||
const list =
|
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
||||||
existingId && listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
|
||||||
const item = buildItem(data, status);
|
const item = buildItem(data, status);
|
||||||
|
|
||||||
let id: number;
|
let id: number;
|
||||||
@@ -85,6 +85,6 @@ export async function saveTmcTransferRequest(
|
|||||||
await list.items.getById(id).update({ RequestUniqueCode: code });
|
await list.items.getById(id).update({ RequestUniqueCode: code });
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeRoute(tmcTransferApprovalService(existingId ? listId : undefined), id, approvers, executors, status);
|
await writeRoute(tmcTransferApprovalService(listId), id, approvers, executors, status);
|
||||||
return { id, code };
|
return { id, code };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import {
|
|||||||
REQUEST_STATUS,
|
REQUEST_STATUS,
|
||||||
isEditableStatus,
|
isEditableStatus,
|
||||||
IApproverToWrite,
|
IApproverToWrite,
|
||||||
findUnassignedStep
|
findUnassignedStep,
|
||||||
|
getCurrentListPartition
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
import { IRequestFormProps } from '../../models/types';
|
import { IRequestFormProps } from '../../models/types';
|
||||||
import {
|
import {
|
||||||
@@ -176,13 +177,24 @@ const UnpaidLeaveForm: React.FC<IRequestFormProps> = ({
|
|||||||
if (!rtId) {
|
if (!rtId) {
|
||||||
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
||||||
}
|
}
|
||||||
|
// Новая заявка пишется в ТЕКУЩУЮ партицию — запрашиваем её у approval-route
|
||||||
|
// API непосредственно перед записью (не кеш из onInit): именно этот вызов
|
||||||
|
// проверяет порог элементов и запускает архивацию следующей партиции.
|
||||||
|
let writeListId = editListId;
|
||||||
|
if (!requestId) {
|
||||||
|
try {
|
||||||
|
writeListId = (await getCurrentListPartition(context, rtId)).listId;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Не удалось получить текущую партицию списка — используется список по конвенции имён', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
const { id, code } = await saveUnpaidLeaveRequest(
|
const { id, code } = await saveUnpaidLeaveRequest(
|
||||||
requestId,
|
requestId,
|
||||||
rtId,
|
rtId,
|
||||||
data,
|
data,
|
||||||
toApprovers(matrix.rows),
|
toApprovers(matrix.rows),
|
||||||
status,
|
status,
|
||||||
editListId
|
writeListId
|
||||||
);
|
);
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(status);
|
setRequestStatus(status);
|
||||||
@@ -193,13 +205,13 @@ const UnpaidLeaveForm: React.FC<IRequestFormProps> = ({
|
|||||||
onSaved();
|
onSaved();
|
||||||
}
|
}
|
||||||
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
||||||
// Имена списков — той же партиции, что и сама заявка (editListId), не текущей:
|
// Имена списков — той же партиции, куда фактически записана заявка (writeListId),
|
||||||
// поток ищет строку маршрута именно в НЕЙ.
|
// не текущей: поток ищет строку маршрута именно в НЕЙ.
|
||||||
if (status === REQUEST_STATUS.onApproval) {
|
if (status === REQUEST_STATUS.onApproval) {
|
||||||
await notifyActiveStep(context, approval, id, {
|
await notifyActiveStep(context, approval, id, {
|
||||||
requestTypeId: rtId,
|
requestTypeId: rtId,
|
||||||
dataListName: editListId ? dataListTitleForListId(FORM_ID, editListId) : dataListTitle(FORM_ID),
|
dataListName: writeListId ? dataListTitleForListId(FORM_ID, writeListId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: editListId ? approvalListTitleForListId(FORM_ID, editListId) : approvalListTitle(FORM_ID)
|
approvalListName: writeListId ? approvalListTitleForListId(FORM_ID, writeListId) : approvalListTitle(FORM_ID)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -89,8 +89,7 @@ export async function saveUnpaidLeaveRequest(
|
|||||||
status: string,
|
status: string,
|
||||||
listId?: string
|
listId?: string
|
||||||
): Promise<ISaveResult> {
|
): Promise<ISaveResult> {
|
||||||
const list =
|
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
||||||
existingId && listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
|
||||||
const employee = await ensureUser(data.Employee);
|
const employee = await ensureUser(data.Employee);
|
||||||
// Замещающий необязателен: резолвим только если выбран.
|
// Замещающий необязателен: резолвим только если выбран.
|
||||||
const substituteId = data.Substitute ? (await ensureUser(data.Substitute)).Id : undefined;
|
const substituteId = data.Substitute ? (await ensureUser(data.Substitute)).Id : undefined;
|
||||||
@@ -109,6 +108,6 @@ export async function saveUnpaidLeaveRequest(
|
|||||||
await list.items.getById(id).update({ RequestUniqueCode: code });
|
await list.items.getById(id).update({ RequestUniqueCode: code });
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeRoute(unpaidLeaveApprovalService(existingId ? listId : undefined), id, approvers, [], status);
|
await writeRoute(unpaidLeaveApprovalService(listId), id, approvers, [], status);
|
||||||
return { id, code };
|
return { id, code };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
StatusBadge,
|
StatusBadge,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
getPlannedMatrix,
|
getPlannedMatrix,
|
||||||
|
getCurrentListPartition,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
REQUEST_STATUS,
|
REQUEST_STATUS,
|
||||||
@@ -173,7 +174,29 @@ const VacationForm: React.FC<IRequestFormProps> = ({
|
|||||||
if (!rtId) {
|
if (!rtId) {
|
||||||
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
||||||
}
|
}
|
||||||
const { id, code } = await saveVacationRequest(requestId, rtId, data, toApprovers(matrix.rows), status, editListId);
|
// requestId уже задан → редактируем существующую заявку, пишем в ту же
|
||||||
|
// партицию (editListId). Иначе — создаём новую, и партицию для записи
|
||||||
|
// спрашиваем у approval-route ПРЯМО СЕЙЧАС (а не берём из кеша onInit):
|
||||||
|
// это единственный запрос, который проверяет порог элементов текущего
|
||||||
|
// списка и ставит в очередь создание следующей партиции при достижении —
|
||||||
|
// без него архивация никогда не сработает. Сбой запроса не должен
|
||||||
|
// блокировать подачу заявки — откатываемся на список по конвенции имён.
|
||||||
|
let writeListId = editListId;
|
||||||
|
if (!requestId) {
|
||||||
|
try {
|
||||||
|
writeListId = (await getCurrentListPartition(context, rtId)).listId;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Не удалось получить текущую партицию списка — используется список по конвенции имён', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { id, code } = await saveVacationRequest(
|
||||||
|
requestId,
|
||||||
|
rtId,
|
||||||
|
data,
|
||||||
|
toApprovers(matrix.rows),
|
||||||
|
status,
|
||||||
|
writeListId
|
||||||
|
);
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(status);
|
setRequestStatus(status);
|
||||||
if (code) {
|
if (code) {
|
||||||
@@ -183,13 +206,14 @@ const VacationForm: React.FC<IRequestFormProps> = ({
|
|||||||
onSaved();
|
onSaved();
|
||||||
}
|
}
|
||||||
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
||||||
// Имена списков — той же партиции, что и сама заявка (editListId), не текущей:
|
// Имена списков — той же партиции, куда фактически записали (writeListId),
|
||||||
// поток ищет строку маршрута именно в НЕЙ.
|
// не обязательно текущей на момент рендера формы: поток ищет строку
|
||||||
|
// маршрута именно в НЕЙ.
|
||||||
if (status === REQUEST_STATUS.onApproval) {
|
if (status === REQUEST_STATUS.onApproval) {
|
||||||
await notifyActiveStep(context, approval, id, {
|
await notifyActiveStep(context, approval, id, {
|
||||||
requestTypeId: rtId,
|
requestTypeId: rtId,
|
||||||
dataListName: editListId ? dataListTitleForListId(FORM_ID, editListId) : dataListTitle(FORM_ID),
|
dataListName: writeListId ? dataListTitleForListId(FORM_ID, writeListId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: editListId ? approvalListTitleForListId(FORM_ID, editListId) : approvalListTitle(FORM_ID)
|
approvalListName: writeListId ? approvalListTitleForListId(FORM_ID, writeListId) : approvalListTitle(FORM_ID)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -67,10 +67,13 @@ export interface ISaveResult {
|
|||||||
* Фазы исполнения у отпуска нет — writeRoute вызывается с пустым списком исполнителей.
|
* Фазы исполнения у отпуска нет — writeRoute вызывается с пустым списком исполнителей.
|
||||||
* Возвращает Id заявки (+ код при создании).
|
* Возвращает Id заявки (+ код при создании).
|
||||||
*
|
*
|
||||||
* listId — партиция ОБНОВЛЯЕМОЙ заявки (см. loadVacationRequest). Имеет смысл
|
* listId — партиция, в которую фактически пишем: при редактировании — партиция
|
||||||
* только вместе с existingId: новая заявка всегда создаётся в ТЕКУЩЕЙ партиции —
|
* ОБНОВЛЯЕМОЙ заявки (см. loadVacationRequest), при создании — партиция,
|
||||||
* писать новые заявки в архив незачем и нельзя (approval-route переключает
|
* полученная от approval-route API непосредственно перед вызовом (см.
|
||||||
* текущую партицию сам, по своему порогу элементов).
|
* getCurrentListPartition в VacationForm.tsx) — именно этот вызов, а не кеш из
|
||||||
|
* onInit, проверяет порог элементов и запускает архивацию следующей партиции.
|
||||||
|
* Не задан (сбой запроса за текущей партицией) → откат на список по конвенции
|
||||||
|
* имён, как до появления партиционирования.
|
||||||
*/
|
*/
|
||||||
export async function saveVacationRequest(
|
export async function saveVacationRequest(
|
||||||
existingId: number | undefined,
|
existingId: number | undefined,
|
||||||
@@ -80,8 +83,7 @@ export async function saveVacationRequest(
|
|||||||
status: string,
|
status: string,
|
||||||
listId?: string
|
listId?: string
|
||||||
): Promise<ISaveResult> {
|
): Promise<ISaveResult> {
|
||||||
const list =
|
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
||||||
existingId && listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
|
||||||
const employee = await ensureEmployee(data.Employee);
|
const employee = await ensureEmployee(data.Employee);
|
||||||
const item = buildItem(data, status, employee.Id, employee.Title || data.Employee);
|
const item = buildItem(data, status, employee.Id, employee.Title || data.Employee);
|
||||||
|
|
||||||
@@ -98,6 +100,6 @@ export async function saveVacationRequest(
|
|||||||
await list.items.getById(id).update({ RequestUniqueCode: code });
|
await list.items.getById(id).update({ RequestUniqueCode: code });
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeRoute(vacationApprovalService(existingId ? listId : undefined), id, approvers, [], status);
|
await writeRoute(vacationApprovalService(listId), id, approvers, [], status);
|
||||||
return { id, code };
|
return { id, code };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
StatusBadge,
|
StatusBadge,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
getPlannedMatrix,
|
getPlannedMatrix,
|
||||||
|
getCurrentListPartition,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
REQUEST_STATUS,
|
REQUEST_STATUS,
|
||||||
@@ -210,6 +211,21 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({
|
|||||||
if (!rtId) {
|
if (!rtId) {
|
||||||
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
throw new Error('Тип заявки не определён — обновите страницу и повторите.');
|
||||||
}
|
}
|
||||||
|
// requestId уже задан → редактируем существующую заявку, пишем в ту же
|
||||||
|
// партицию (editListId). Иначе — создаём новую, и партицию для записи
|
||||||
|
// спрашиваем у approval-route ПРЯМО СЕЙЧАС (а не берём из кеша onInit):
|
||||||
|
// это единственный запрос, который проверяет порог элементов текущего
|
||||||
|
// списка и ставит в очередь создание следующей партиции при достижении —
|
||||||
|
// без него архивация никогда не сработает. Сбой запроса не должен
|
||||||
|
// блокировать подачу заявки — откатываемся на список по конвенции имён.
|
||||||
|
let writeListId = editListId;
|
||||||
|
if (!requestId) {
|
||||||
|
try {
|
||||||
|
writeListId = (await getCurrentListPartition(context, rtId)).listId;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Не удалось получить текущую партицию списка — используется список по конвенции имён', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
const { id, code } = await saveWorkplaceRequest(
|
const { id, code } = await saveWorkplaceRequest(
|
||||||
requestId,
|
requestId,
|
||||||
rtId,
|
rtId,
|
||||||
@@ -218,7 +234,7 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({
|
|||||||
toApprovers(executors.rows),
|
toApprovers(executors.rows),
|
||||||
status,
|
status,
|
||||||
sourceId,
|
sourceId,
|
||||||
editListId
|
writeListId
|
||||||
);
|
);
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(status);
|
setRequestStatus(status);
|
||||||
@@ -226,21 +242,22 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({
|
|||||||
setRequestCode(code);
|
setRequestCode(code);
|
||||||
}
|
}
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
await uploadAttachments(editListId || dataListTitle(FORM_ID), id, files);
|
await uploadAttachments(writeListId || dataListTitle(FORM_ID), id, files);
|
||||||
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
||||||
setExistingAttachments(await getAttachments(editListId || dataListTitle(FORM_ID), id)); // показать добавленные
|
setExistingAttachments(await getAttachments(writeListId || dataListTitle(FORM_ID), id)); // показать добавленные
|
||||||
}
|
}
|
||||||
if (onSaved) {
|
if (onSaved) {
|
||||||
onSaved();
|
onSaved();
|
||||||
}
|
}
|
||||||
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
// Отправка на согласование — уведомляем первый активный шаг (поток оповещения).
|
||||||
// Имена списков — той же партиции, что и сама заявка (editListId), не текущей:
|
// Имена списков — той же партиции, куда фактически записали (writeListId),
|
||||||
// поток ищет строку маршрута именно в НЕЙ.
|
// не обязательно текущей на момент рендера формы: поток ищет строку
|
||||||
|
// маршрута именно в НЕЙ.
|
||||||
if (status === REQUEST_STATUS.onApproval) {
|
if (status === REQUEST_STATUS.onApproval) {
|
||||||
await notifyActiveStep(context, approval, id, {
|
await notifyActiveStep(context, approval, id, {
|
||||||
requestTypeId: rtId,
|
requestTypeId: rtId,
|
||||||
dataListName: editListId ? dataListTitleForListId(FORM_ID, editListId) : dataListTitle(FORM_ID),
|
dataListName: writeListId ? dataListTitleForListId(FORM_ID, writeListId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: editListId ? approvalListTitleForListId(FORM_ID, editListId) : approvalListTitle(FORM_ID),
|
approvalListName: writeListId ? approvalListTitleForListId(FORM_ID, writeListId) : approvalListTitle(FORM_ID),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -74,10 +74,13 @@ export interface ISaveResult {
|
|||||||
* Исполнители пишутся сразу — фаза исполнения стартует автоматически после согласования.
|
* Исполнители пишутся сразу — фаза исполнения стартует автоматически после согласования.
|
||||||
* sourceHireId — если заявка создана на основе «прием». Возвращает Id заявки (+ код при создании).
|
* sourceHireId — если заявка создана на основе «прием». Возвращает Id заявки (+ код при создании).
|
||||||
*
|
*
|
||||||
* listId — партиция ОБНОВЛЯЕМОЙ заявки (см. loadWorkplaceRequest). Имеет смысл
|
* listId — партиция, в которую фактически пишем: при редактировании — партиция
|
||||||
* только вместе с existingId: новая заявка всегда создаётся в ТЕКУЩЕЙ партиции —
|
* ОБНОВЛЯЕМОЙ заявки (см. loadWorkplaceRequest), при создании — партиция,
|
||||||
* писать новые заявки в архив незачем и нельзя (approval-route переключает
|
* полученная от approval-route API непосредственно перед вызовом (см.
|
||||||
* текущую партицию сам, по своему порогу элементов).
|
* getCurrentListPartition в WorkplaceSetupForm.tsx) — именно этот вызов, а не
|
||||||
|
* кеш из onInit, проверяет порог элементов и запускает архивацию следующей
|
||||||
|
* партиции. Не задан (сбой запроса за текущей партицией) → откат на список по
|
||||||
|
* конвенции имён, как до появления партиционирования.
|
||||||
*/
|
*/
|
||||||
export async function saveWorkplaceRequest(
|
export async function saveWorkplaceRequest(
|
||||||
existingId: number | undefined,
|
existingId: number | undefined,
|
||||||
@@ -89,8 +92,7 @@ export async function saveWorkplaceRequest(
|
|||||||
sourceHireId?: number,
|
sourceHireId?: number,
|
||||||
listId?: string
|
listId?: string
|
||||||
): Promise<ISaveResult> {
|
): Promise<ISaveResult> {
|
||||||
const list =
|
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
||||||
existingId && listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
|
|
||||||
const item = buildItem(data, status, sourceHireId);
|
const item = buildItem(data, status, sourceHireId);
|
||||||
|
|
||||||
let id: number;
|
let id: number;
|
||||||
@@ -106,6 +108,6 @@ export async function saveWorkplaceRequest(
|
|||||||
await list.items.getById(id).update({ RequestUniqueCode: code });
|
await list.items.getById(id).update({ RequestUniqueCode: code });
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeRoute(workplaceApprovalService(existingId ? listId : undefined), id, approvers, executors, status);
|
await writeRoute(workplaceApprovalService(listId), id, approvers, executors, status);
|
||||||
return { id, code };
|
return { id, code };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { WebPartContext } from '@microsoft/sp-webpart-base';
|
|||||||
import { APPROVAL_API_CLIENT_ID, getApprovalApiBaseUrl } from '../config';
|
import { APPROVAL_API_CLIENT_ID, getApprovalApiBaseUrl } from '../config';
|
||||||
import {
|
import {
|
||||||
ApprovalStatus,
|
ApprovalStatus,
|
||||||
|
ICurrentListPartition,
|
||||||
IApiResponse,
|
IApiResponse,
|
||||||
IRequestType,
|
IRequestType,
|
||||||
IRequestTypeListSeries,
|
IRequestTypeListSeries,
|
||||||
@@ -54,6 +55,30 @@ export async function getListPartitions(context: WebPartContext): Promise<IReque
|
|||||||
return body.data ?? [];
|
return body.data ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/request-types/{id}/lists/current — партиция, в которую нужно писать
|
||||||
|
* НОВУЮ заявку этого типа. Вызывать перед КАЖДЫМ созданием, не кешировать: этот
|
||||||
|
* запрос заодно проверяет порог элементов на бэкенде и ставит в очередь
|
||||||
|
* создание следующей партиции при достижении — без него архивация никогда не
|
||||||
|
* запустится, сколько бы заявок ни создали (bulk-эндпоинт lists, который
|
||||||
|
* портал и так грузит при onInit, только читает реестр и порог не проверяет).
|
||||||
|
*/
|
||||||
|
export async function getCurrentListPartition(
|
||||||
|
context: WebPartContext,
|
||||||
|
requestTypeId: number
|
||||||
|
): Promise<ICurrentListPartition> {
|
||||||
|
const c = await client(context);
|
||||||
|
const response: HttpClientResponse = await c.get(
|
||||||
|
`${getApprovalApiBaseUrl()}/api/request-types/${requestTypeId}/lists/current`,
|
||||||
|
AadHttpClient.configurations.v1
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`request-types/${requestTypeId}/lists/current: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const body: IApiResponse<ICurrentListPartition> = await response.json();
|
||||||
|
return body.data;
|
||||||
|
}
|
||||||
|
|
||||||
// Кеш на уровне МОДУЛЯ (не useRef/useState) — список типов заявок админ меняет
|
// Кеш на уровне МОДУЛЯ (не useRef/useState) — список типов заявок админ меняет
|
||||||
// редко, а без кеша каждая форма и каждый переход в режим согласующего дёргали
|
// редко, а без кеша каждая форма и каждый переход в режим согласующего дёргали
|
||||||
// бы /api/request-types заново. Живёт до перезагрузки страницы — тот же принцип,
|
// бы /api/request-types заново. Живёт до перезагрузки страницы — тот же принцип,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type { IApprovalRow } from './models/IApprovalRow';
|
|||||||
export type {
|
export type {
|
||||||
ApprovalStatus,
|
ApprovalStatus,
|
||||||
IListPartition,
|
IListPartition,
|
||||||
|
ICurrentListPartition,
|
||||||
IRequestType,
|
IRequestType,
|
||||||
IRequestTypeListSeries,
|
IRequestTypeListSeries,
|
||||||
IResolvedRoute,
|
IResolvedRoute,
|
||||||
@@ -21,7 +22,13 @@ export type {
|
|||||||
IResolveFieldValue,
|
IResolveFieldValue,
|
||||||
} from './models/IApprovalRouteApi';
|
} from './models/IApprovalRouteApi';
|
||||||
|
|
||||||
export { getRequestTypes, getRequestTypeIdByPageUrl, getListPartitions, resolveRoute } from './approvalRoute.service';
|
export {
|
||||||
|
getRequestTypes,
|
||||||
|
getRequestTypeIdByPageUrl,
|
||||||
|
getListPartitions,
|
||||||
|
getCurrentListPartition,
|
||||||
|
resolveRoute
|
||||||
|
} from './approvalRoute.service';
|
||||||
export {
|
export {
|
||||||
initListPartitions,
|
initListPartitions,
|
||||||
listPartitionsLoaded,
|
listPartitionsLoaded,
|
||||||
|
|||||||
@@ -50,6 +50,23 @@ export interface IRequestTypeListSeries {
|
|||||||
partitions: IListPartition[];
|
partitions: IListPartition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/request-types/{id}/lists/current → { data: ICurrentListPartition }
|
||||||
|
*
|
||||||
|
* Партиция, в которую нужно писать НОВУЮ заявку. Не чистый GET на бэкенде —
|
||||||
|
* этот же запрос проверяет порог элементов и при необходимости ставит в
|
||||||
|
* очередь создание следующей партиции, поэтому его нужно звать перед КАЖДЫМ
|
||||||
|
* созданием заявки, а не читать из кеша (см. shared/approval/listPartitions.ts,
|
||||||
|
* который для этого не подходит — грузится один раз при onInit).
|
||||||
|
*/
|
||||||
|
export interface ICurrentListPartition {
|
||||||
|
requestTypeId: number;
|
||||||
|
number: number;
|
||||||
|
siteUrl: string;
|
||||||
|
listId: string;
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Условие видимости шага маршрута (настраивается в админке маршрутов —
|
// Условие видимости шага маршрута (настраивается в админке маршрутов —
|
||||||
// «Показывать шаг только при выполнении условия», сравнение «Поле списка»/
|
// «Показывать шаг только при выполнении условия», сравнение «Поле списка»/
|
||||||
// «Оператор»/«Значение»). fieldName — внутреннее имя колонки списка (напр.
|
// «Оператор»/«Значение»). fieldName — внутреннее имя колонки списка (напр.
|
||||||
|
|||||||
Reference in New Issue
Block a user