Архивация списков #9

Merged
gorbenko.pavel merged 17 commits from archive into main 2026-08-19 09:07:53 +00:00
12 changed files with 189 additions and 295 deletions
Showing only changes of commit dc66cd1a06 - Show all commits
@@ -1,21 +1,15 @@
import { getSP } from '../../shared/pnp/pnpjs';
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
import { buildRequestUniqueCode } from '../../shared/requestCode';
import { approvalListTitle, approvalListTitleForListId, dataListTitle } from '../../shared/listTitles';
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, resolveDataList, saveRequestItem } from '../../shared/dataList';
import { ensureEmployee } from '../../shared/employee';
import { FORM_ID, IFormData } from './model';
/**
* Экземпляр сервиса списка согласования этой формы.
*
* listId — партиция КОНКРЕТНОЙ заявки (см. shared/approval/listPartitions.ts).
* Не задан — текущая партиция, как раньше. Список согласования ищется только по
* имени (нет прямой ссылки на него, в отличие от списка данных), поэтому для
* архивной партиции имя приходится резолвить через реестр — если реестр не
* знает про такой listId (устаревшая ссылка), резолвер бросит понятную ошибку,
* а не тихо откатится на текущую партицию.
* Экземпляр сервиса списка согласования этой формы — тонкая обёртка над
* approvalServiceFor (см. shared/approval/approvalServiceFactory.ts), под
* именем формы для читаемости вызывающего кода.
*/
export function clearanceApprovalService(listId?: string): ApprovalListService {
return new ApprovalListService(listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID));
return approvalServiceFor(FORM_ID, listId);
}
/** Отметки, которые проставляет не инициатор, а профильный согласующий (см. approverRights.ts). */
@@ -47,16 +41,10 @@ export async function updateApproverFields(
Number.isFinite(amount) && fields.TrainingAmount.trim() !== '' ? amount : null;
}
if (Object.keys(item).length === 0) return;
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
const list = resolveDataList(FORM_ID, listId);
await list.items.getById(id).update(item);
}
/** email / claims-логин → пользователь сайта (Id для Person-поля + Title для заголовка). */
async function ensureEmployee(login: string): Promise<{ Id: number; Title: string }> {
const full = login.indexOf('|') >= 0 ? login : `i:0#.f|membership|${login}`;
return getSP().web.ensureUser(full);
}
function pad2(n: number): string {
return n < 10 ? `0${n}` : `${n}`;
}
@@ -83,11 +71,7 @@ function buildItem(data: IFormData, status: string, employeeId: number, employee
};
}
export interface ISaveResult {
id: number;
/** Задан только при создании новой заявки (существующий код не меняется). */
code?: string;
}
export type ISaveResult = ISaveItemResult;
/**
* Сохранить обходной лист: содержимое + маршрут (согласование + исполнение).
@@ -111,23 +95,10 @@ export async function saveClearanceRequest(
status: string,
listId?: string
): Promise<ISaveResult> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
const employee = await ensureEmployee(data.Employee);
const item = buildItem(data, status, employee.Id, employee.Title || data.Employee);
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).
code = buildRequestUniqueCode(requestTypeId, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
}
await writeRoute(clearanceApprovalService(listId), id, approvers, executors, status);
return { id, code };
const result = await saveRequestItem(FORM_ID, existingId, listId, requestTypeId, item);
await writeRoute(clearanceApprovalService(listId), result.id, approvers, executors, status);
return result;
}
@@ -1,24 +1,17 @@
import { getSP } from '../../shared/pnp/pnpjs';
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
import { buildRequestUniqueCode } from '../../shared/requestCode';
import { approvalListTitle, approvalListTitleForListId, dataListTitle } from '../../shared/listTitles';
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, saveRequestItem } from '../../shared/dataList';
import { FORM_ID, IFormData } from './model';
// Числовые поля: пустая строка → null, иначе Number().
const NUMBER_FIELDS: (keyof IFormData)[] = ['PpeHeight', 'PpeShoeSize', 'SalaryProbation', 'SalaryAfterProbation'];
/**
* Экземпляр сервиса списка согласования этой формы.
*
* listId — партиция КОНКРЕТНОЙ заявки (см. shared/approval/listPartitions.ts).
* Не задан — текущая партиция, как раньше. Список согласования ищется только по
* имени (нет прямой ссылки на него, в отличие от списка данных), поэтому для
* архивной партиции имя приходится резолвить через реестр — если реестр не
* знает про такой listId (устаревшая ссылка), резолвер бросит понятную ошибку,
* а не тихо откатится на текущую партицию.
* Экземпляр сервиса списка согласования этой формы — тонкая обёртка над
* approvalServiceFor (см. shared/approval/approvalServiceFactory.ts), под
* именем формы для читаемости вызывающего кода.
*/
export function hireApprovalService(listId?: string): ApprovalListService {
return new ApprovalListService(listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID));
return approvalServiceFor(FORM_ID, listId);
}
/** Осмысленный заголовок элемента списка (системная колонка Title). */
@@ -42,11 +35,7 @@ function buildItem(data: IFormData, status: string): Record<string, unknown> {
return item;
}
export interface ISaveResult {
id: number;
/** Задан только при создании новой заявки (существующий код не меняется). */
code?: string;
}
export type ISaveResult = ISaveItemResult;
/**
* Сохранить заявку «на прием»: контент + маршрут (согласующие + исполнители) со статусом.
@@ -72,22 +61,9 @@ export async function saveHireRequest(
status: string,
listId?: string
): Promise<ISaveResult> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
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).
code = buildRequestUniqueCode(requestTypeId, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
}
await writeRoute(hireApprovalService(listId), id, approvers, executors, status);
return { id, code };
const result = await saveRequestItem(FORM_ID, existingId, listId, requestTypeId, item);
await writeRoute(hireApprovalService(listId), result.id, approvers, executors, status);
return result;
}
@@ -1,7 +1,6 @@
import { getSP } from '../../shared/pnp/pnpjs';
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
import { buildRequestUniqueCode } from '../../shared/requestCode';
import { approvalListTitle, approvalListTitleForListId, dataListTitle } from '../../shared/listTitles';
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, saveRequestItem } from '../../shared/dataList';
import { FORM_ID, IFormData } from './model';
// Поля-люди (один человек): логин → ensureUser → Id в колонку <Field>Id.
@@ -12,17 +11,12 @@ const MULTI_PERSON_FIELDS: (keyof IFormData)[] = ['HiringDecisionMaker'];
const NUMBER_FIELDS: (keyof IFormData)[] = ['OpenVacanciesCount', 'RateProbation', 'RateAfterProbation'];
/**
* Экземпляр сервиса списка согласования этой формы.
*
* listId — партиция КОНКРЕТНОЙ заявки (см. shared/approval/listPartitions.ts).
* Не задан — текущая партиция, как раньше. Список согласования ищется только по
* имени (нет прямой ссылки на него, в отличие от списка данных), поэтому для
* архивной партиции имя приходится резолвить через реестр — если реестр не
* знает про такой listId (устаревшая ссылка), резолвер бросит понятную ошибку,
* а не тихо откатится на текущую партицию.
* Экземпляр сервиса списка согласования этой формы — тонкая обёртка над
* approvalServiceFor (см. shared/approval/approvalServiceFactory.ts), под
* именем формы для читаемости вызывающего кода.
*/
export function selectionApprovalService(listId?: string): ApprovalListService {
return new ApprovalListService(listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID));
return approvalServiceFor(FORM_ID, listId);
}
/** Логин → числовой Id пользователя на текущем сайте. */
@@ -86,11 +80,7 @@ async function applyPeople(item: Record<string, unknown>, data: IFormData): Prom
}
}
export interface ISaveResult {
id: number;
/** Задан только при создании новой заявки (существующий код не меняется). */
code?: string;
}
export type ISaveResult = ISaveItemResult;
/**
* Сохранить заявку «на подбор»: содержимое + маршрут согласования.
@@ -114,24 +104,11 @@ export async function saveSelectionRequest(
status: string,
listId?: string
): Promise<ISaveResult> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
const item = buildScalarBody(data);
item.RequestStatus = status;
await applyPeople(item, data);
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).
code = buildRequestUniqueCode(requestTypeId, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
}
await writeRoute(selectionApprovalService(listId), id, approvers, [], status);
return { id, code };
const result = await saveRequestItem(FORM_ID, existingId, listId, requestTypeId, item);
await writeRoute(selectionApprovalService(listId), result.id, approvers, [], status);
return result;
}
@@ -1,7 +1,5 @@
import { getSP } from '../../shared/pnp/pnpjs';
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
import { buildRequestUniqueCode } from '../../shared/requestCode';
import { approvalListTitle, approvalListTitleForListId, dataListTitle } from '../../shared/listTitles';
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, resolveDataList, saveRequestItem } from '../../shared/dataList';
import { FORM_ID, IFormData } from './model';
// Префикс уникального кода заявки — буквенная метка «1С» вместо числового id
@@ -10,17 +8,12 @@ import { FORM_ID, IFormData } from './model';
export const TICKET1C_CODE_PREFIX = '1С';
/**
* Экземпляр сервиса списка согласования этой формы.
*
* listId — партиция КОНКРЕТНОЙ заявки (см. shared/approval/listPartitions.ts).
* Не задан — текущая партиция, как раньше. Список согласования ищется только по
* имени (нет прямой ссылки на него, в отличие от списка данных), поэтому для
* архивной партиции имя приходится резолвить через реестр — если реестр не
* знает про такой listId (устаревшая ссылка), резолвер бросит понятную ошибку,
* а не тихо откатится на текущую партицию.
* Экземпляр сервиса списка согласования этой формы — тонкая обёртка над
* approvalServiceFor (см. shared/approval/approvalServiceFactory.ts), под
* именем формы для читаемости вызывающего кода.
*/
export function ticket1cApprovalService(listId?: string): ApprovalListService {
return new ApprovalListService(listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID));
return approvalServiceFor(FORM_ID, listId);
}
/** Поля элемента списка данных из модели формы. TaskName пишется в системную колонку Title. */
@@ -36,11 +29,7 @@ function buildItem(data: IFormData, status: string): Record<string, unknown> {
return item;
}
export interface ISaveResult {
id: number;
/** Задан только при создании новой заявки (существующий код не меняется). */
code?: string;
}
export type ISaveResult = ISaveItemResult;
/**
* Сохранить заявку в отдел 1С: содержимое + маршрут согласования.
@@ -63,24 +52,11 @@ export async function saveTicketRequest(
status: string,
listId?: string
): Promise<ISaveResult> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
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).
code = buildRequestUniqueCode(TICKET1C_CODE_PREFIX, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
}
await writeRoute(ticket1cApprovalService(listId), id, approvers, [], status);
return { id, code };
const result = await saveRequestItem(FORM_ID, existingId, listId, TICKET1C_CODE_PREFIX, item);
await writeRoute(ticket1cApprovalService(listId), result.id, approvers, [], status);
return result;
}
/**
@@ -89,12 +65,12 @@ export async function saveTicketRequest(
* решение согласующего применяется из нескольких вкладок/сессий одновременно.
*/
export async function getPlannerTaskId(id: number, listId?: string): Promise<string | undefined> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
const list = resolveDataList(FORM_ID, listId);
const item = (await list.items.getById(id).select('PlannerTaskId')()) as { PlannerTaskId?: string };
return item.PlannerTaskId || undefined;
}
export async function setPlannerTaskId(id: number, taskId: string, listId?: string): Promise<void> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
const list = resolveDataList(FORM_ID, listId);
await list.items.getById(id).update({ PlannerTaskId: taskId });
}
@@ -1,22 +1,15 @@
import { getSP } from '../../shared/pnp/pnpjs';
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
import { buildRequestUniqueCode } from '../../shared/requestCode';
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, saveRequestItem } from '../../shared/dataList';
import { ILineItem } from '../../shared/ui';
import { approvalListTitle, approvalListTitleForListId, dataListTitle } from '../../shared/listTitles';
import { FORM_ID, IFormData } from './model';
/**
* Экземпляр сервиса списка согласования этой формы.
*
* listId — партиция КОНКРЕТНОЙ заявки (см. shared/approval/listPartitions.ts).
* Не задан — текущая партиция, как раньше. Список согласования ищется только по
* имени (нет прямой ссылки на него, в отличие от списка данных), поэтому для
* архивной партиции имя приходится резолвить через реестр — если реестр не
* знает про такой listId (устаревшая ссылка), резолвер бросит понятную ошибку,
* а не тихо откатится на текущую партицию.
* Экземпляр сервиса списка согласования этой формы — тонкая обёртка над
* approvalServiceFor (см. shared/approval/approvalServiceFactory.ts), под
* именем формы для читаемости вызывающего кода.
*/
export function tmcTransferApprovalService(listId?: string): ApprovalListService {
return new ApprovalListService(listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID));
return approvalServiceFor(FORM_ID, listId);
}
/** Осмысленный заголовок элемента (системная колонка Title). */
@@ -43,11 +36,7 @@ function buildItem(data: IFormData, status: string): Record<string, unknown> {
return item;
}
export interface ISaveResult {
id: number;
/** Задан только при создании новой заявки (существующий код не меняется). */
code?: string;
}
export type ISaveResult = ISaveItemResult;
/**
* Сохранить заявку «СЗ на перемещение ТМЦ»: контент + маршрут (согласующие + исполнители).
@@ -69,22 +58,9 @@ export async function saveTmcTransferRequest(
status: string,
listId?: string
): Promise<ISaveResult> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
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).
code = buildRequestUniqueCode(requestTypeId, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
}
await writeRoute(tmcTransferApprovalService(listId), id, approvers, executors, status);
return { id, code };
const result = await saveRequestItem(FORM_ID, existingId, listId, requestTypeId, item);
await writeRoute(tmcTransferApprovalService(listId), result.id, approvers, executors, status);
return result;
}
@@ -1,27 +1,15 @@
import { getSP } from '../../shared/pnp/pnpjs';
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
import { buildRequestUniqueCode } from '../../shared/requestCode';
import { approvalListTitle, approvalListTitleForListId, dataListTitle } from '../../shared/listTitles';
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, saveRequestItem } from '../../shared/dataList';
import { ensureEmployee } from '../../shared/employee';
import { FORM_ID, IFormData } from './model';
/**
* Экземпляр сервиса списка согласования этой формы.
*
* listId — партиция КОНКРЕТНОЙ заявки (см. shared/approval/listPartitions.ts).
* Не задан — текущая партиция, как раньше. Список согласования ищется только по
* имени (нет прямой ссылки на него, в отличие от списка данных), поэтому для
* архивной партиции имя приходится резолвить через реестр — если реестр не
* знает про такой listId (устаревшая ссылка), резолвер бросит понятную ошибку,
* а не тихо откатится на текущую партицию.
* Экземпляр сервиса списка согласования этой формы — тонкая обёртка над
* approvalServiceFor (см. shared/approval/approvalServiceFactory.ts), под
* именем формы для читаемости вызывающего кода.
*/
export function unpaidLeaveApprovalService(listId?: string): ApprovalListService {
return new ApprovalListService(listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID));
}
/** email / claims-логин → пользователь сайта (Id для Person-поля + Title для заголовка). */
async function ensureUser(login: string): Promise<{ Id: number; Title: string }> {
const full = login.indexOf('|') >= 0 ? login : `i:0#.f|membership|${login}`;
return getSP().web.ensureUser(full);
return approvalServiceFor(FORM_ID, listId);
}
function pad2(n: number): string {
@@ -64,11 +52,7 @@ function buildItem(
};
}
export interface ISaveResult {
id: number;
/** Задан только при создании новой заявки (существующий код не меняется). */
code?: string;
}
export type ISaveResult = ISaveItemResult;
/**
* Сохранить заявку без содержания: содержимое + маршрут согласования.
@@ -89,25 +73,12 @@ export async function saveUnpaidLeaveRequest(
status: string,
listId?: string
): Promise<ISaveResult> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
const employee = await ensureUser(data.Employee);
const employee = await ensureEmployee(data.Employee);
// Замещающий необязателен: резолвим только если выбран.
const substituteId = data.Substitute ? (await ensureUser(data.Substitute)).Id : undefined;
const substituteId = data.Substitute ? (await ensureEmployee(data.Substitute)).Id : undefined;
const item = buildItem(data, status, employee.Id, employee.Title || data.Employee, substituteId);
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).
code = buildRequestUniqueCode(requestTypeId, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
}
await writeRoute(unpaidLeaveApprovalService(listId), id, approvers, [], status);
return { id, code };
const result = await saveRequestItem(FORM_ID, existingId, listId, requestTypeId, item);
await writeRoute(unpaidLeaveApprovalService(listId), result.id, approvers, [], status);
return result;
}
@@ -1,27 +1,15 @@
import { getSP } from '../../shared/pnp/pnpjs';
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
import { buildRequestUniqueCode } from '../../shared/requestCode';
import { approvalListTitle, approvalListTitleForListId, dataListTitle } from '../../shared/listTitles';
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, saveRequestItem } from '../../shared/dataList';
import { ensureEmployee } from '../../shared/employee';
import { FORM_ID, IFormData } from './model';
/**
* Экземпляр сервиса списка согласования этой формы.
*
* listId — партиция КОНКРЕТНОЙ заявки (см. shared/approval/listPartitions.ts).
* Не задан — текущая партиция, как раньше. Список согласования ищется только по
* имени (нет прямой ссылки на него, в отличие от списка данных), поэтому для
* архивной партиции имя приходится резолвить через реестр — если реестр не
* знает про такой listId (устаревшая ссылка), резолвер бросит понятную ошибку,
* а не тихо откатится на текущую партицию.
* Экземпляр сервиса списка согласования этой формы — тонкая обёртка над
* approvalServiceFor (см. shared/approval/approvalServiceFactory.ts), под
* именем формы для читаемости вызывающего кода.
*/
export function vacationApprovalService(listId?: string): ApprovalListService {
return new ApprovalListService(listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID));
}
/** email / claims-логин → пользователь сайта (Id для Person-поля + Title для заголовка). */
async function ensureEmployee(login: string): Promise<{ Id: number; Title: string }> {
const full = login.indexOf('|') >= 0 ? login : `i:0#.f|membership|${login}`;
return getSP().web.ensureUser(full);
return approvalServiceFor(FORM_ID, listId);
}
function pad2(n: number): string {
@@ -55,11 +43,7 @@ function buildItem(data: IFormData, status: string, employeeId: number, employee
};
}
export interface ISaveResult {
id: number;
/** Задан только при создании новой заявки (существующий код не меняется). */
code?: string;
}
export type ISaveResult = ISaveItemResult;
/**
* Сохранить заявку на отпуск: содержимое + маршрут согласования.
@@ -83,23 +67,10 @@ export async function saveVacationRequest(
status: string,
listId?: string
): Promise<ISaveResult> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
const employee = await ensureEmployee(data.Employee);
const item = buildItem(data, status, employee.Id, employee.Title || data.Employee);
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).
code = buildRequestUniqueCode(requestTypeId, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
}
await writeRoute(vacationApprovalService(listId), id, approvers, [], status);
return { id, code };
const result = await saveRequestItem(FORM_ID, existingId, listId, requestTypeId, item);
await writeRoute(vacationApprovalService(listId), result.id, approvers, [], status);
return result;
}
@@ -1,21 +1,14 @@
import { getSP } from '../../shared/pnp/pnpjs';
import { ApprovalListService, IApproverToWrite, writeRoute } from '../../shared/approval';
import { buildRequestUniqueCode } from '../../shared/requestCode';
import { approvalListTitle, approvalListTitleForListId, dataListTitle } from '../../shared/listTitles';
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, resolveDataList, saveRequestItem } from '../../shared/dataList';
import { FORM_ID, IFormData } from './model';
/**
* Экземпляр сервиса списка согласования этой формы.
*
* listId — партиция КОНКРЕТНОЙ заявки (см. shared/approval/listPartitions.ts).
* Не задан — текущая партиция, как раньше. Список согласования ищется только по
* имени (нет прямой ссылки на него, в отличие от списка данных), поэтому для
* архивной партиции имя приходится резолвить через реестр — если реестр не
* знает про такой listId (устаревшая ссылка), резолвер бросит понятную ошибку,
* а не тихо откатится на текущую партицию.
* Экземпляр сервиса списка согласования этой формы — тонкая обёртка над
* approvalServiceFor (см. shared/approval/approvalServiceFactory.ts), под
* именем формы для читаемости вызывающего кода.
*/
export function workplaceApprovalService(listId?: string): ApprovalListService {
return new ApprovalListService(listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID));
return approvalServiceFor(FORM_ID, listId);
}
/**
@@ -30,7 +23,7 @@ export async function updateReviewFields(
fields: { Software: string; AccessRights: string },
listId?: string
): Promise<void> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
const list = resolveDataList(FORM_ID, listId);
await list.items.getById(id).update({
Software: fields.Software === '' ? null : fields.Software,
AccessRights: fields.AccessRights === '' ? null : fields.AccessRights,
@@ -62,11 +55,7 @@ function buildItem(data: IFormData, status: string, sourceHireId?: number): Reco
return item;
}
export interface ISaveResult {
id: number;
/** Задан только при создании новой заявки (существующий код не меняется). */
code?: string;
}
export type ISaveResult = ISaveItemResult;
/**
* Сохранить заявку «орг. рабочего места»: контент + маршрут (согласующие + исполнители).
@@ -92,22 +81,9 @@ export async function saveWorkplaceRequest(
sourceHireId?: number,
listId?: string
): Promise<ISaveResult> {
const list = listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(FORM_ID));
const item = buildItem(data, status, sourceHireId);
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).
code = buildRequestUniqueCode(requestTypeId, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
}
await writeRoute(workplaceApprovalService(listId), id, approvers, executors, status);
return { id, code };
const result = await saveRequestItem(FORM_ID, existingId, listId, requestTypeId, item);
await writeRoute(workplaceApprovalService(listId), result.id, approvers, executors, status);
return result;
}
@@ -0,0 +1,24 @@
import { RequestTypeId } from '../../models/types';
import { approvalListTitle, approvalListTitleForListId } from '../listTitles';
import { ApprovalListService } from './approvalList.service';
/**
* Экземпляр сервиса списка согласования формы.
*
* listId — партиция КОНКРЕТНОЙ заявки (см. shared/approval/listPartitions.ts).
* Не задан — текущая партиция, как раньше. Список согласования ищется только по
* имени (нет прямой ссылки на него, в отличие от списка данных), поэтому для
* архивной партиции имя приходится резолвить через реестр — если реестр не
* знает про такой listId (устаревшая ссылка), резолвер бросит понятную ошибку,
* а не тихо откатится на текущую партицию.
*
* Раньше каждая форма объявляла свою «xApprovalService» с телом-копией этой
* функции (8 одинаковых реализаций) — оставлены только тонкие обёртки под
* именем формы (см. submit.ts каждой формы), чтобы вызывающий код не терял
* читаемость («vacationApprovalService» вместо безликого параметра formId).
*/
export function approvalServiceFor(formId: RequestTypeId, listId?: string): ApprovalListService {
return new ApprovalListService(
listId ? approvalListTitleForListId(formId, listId) : approvalListTitle(formId)
);
}
@@ -42,6 +42,7 @@ export { useRequestTypeId } from './useRequestTypeId';
export type { IRequestTypeIdState } from './useRequestTypeId';
export { ApprovalListService } from './approvalList.service';
export type { IApproverToWrite } from './approvalList.service';
export { approvalServiceFor } from './approvalServiceFactory';
export { ApproverService } from './approverDecision.service';
export type { ApproverDecision, IPendingRow } from './approverDecision.service';
export { getPlannedMatrix, writeApprovers, writeRoute } from './approvalSubmission.service';
@@ -0,0 +1,62 @@
import '@pnp/sp/webs';
import '@pnp/sp/lists';
import '@pnp/sp/items';
import { IList } from '@pnp/sp/lists';
import { getSP } from './pnp/pnpjs';
import { buildRequestUniqueCode } from './requestCode';
import { dataListTitle } from './listTitles';
import { RequestTypeId } from '../models/types';
/**
* Список данных, в который фактически пишем: listId (конкретная партиция —
* либо ОБНОВЛЯЕМОЙ заявки, либо только что резолвленная текущая, см.
* getCurrentListPartition) или, если он не задан, список по конвенции имён
* (см. dataListTitle) — тот же откат, что действовал до партиционирования.
*
* Раньше эта строка (`listId ? getSP().web.getList(listId) : ...`) была
* скопирована в save/update-функции каждой формы и в submit.ts отдельных
* форм (Ticket1C — Planner, Clearance — updateApproverFields) — 8+ мест с
* одинаковым кодом. Расхождение здесь означало бы, что часть операций тихо
* пишет не в ту партицию.
*/
export function resolveDataList(formId: RequestTypeId, listId?: string): IList {
return listId ? getSP().web.getList(listId) : getSP().web.lists.getByTitle(dataListTitle(formId));
}
export interface ISaveItemResult {
id: number;
/** Задан только при создании новой заявки (существующий код не меняется). */
code?: string;
}
/**
* Общее ядро сохранения элемента списка данных: резолв списка, создание/обновление,
* генерация уникального кода заявки. Вынесено из saveXRequest каждой формы — сам
* набор полей (buildItem) и работа с маршрутом (writeRoute) остаются у формы,
* форма отличается только ими.
*
* existingId задан → обновляем ту же запись, иначе создаём новую.
* codePrefix — обычно числовой RequestTypeId, но может быть строкой-меткой
* (см. buildRequestUniqueCode, TICKET1C_CODE_PREFIX).
*/
export async function saveRequestItem(
formId: RequestTypeId,
existingId: number | undefined,
listId: string | undefined,
codePrefix: number | string,
item: Record<string, unknown>
): Promise<ISaveItemResult> {
const list = resolveDataList(formId, listId);
if (existingId) {
await list.items.getById(existingId).update(item);
return { id: existingId };
}
const added = await list.items.add(item);
const id = (added as { Id: number }).Id;
// Код известен только после создания элемента (нужен его Id).
const code = buildRequestUniqueCode(codePrefix, id, new Date());
await list.items.getById(id).update({ RequestUniqueCode: code });
return { id, code };
}
@@ -0,0 +1,13 @@
import '@pnp/sp/webs';
import '@pnp/sp/site-users/web';
import { getSP } from './pnp/pnpjs';
/**
* email / claims-логин → пользователь сайта (Id для Person-поля + Title для заголовка).
* Было скопировано в submit.ts нескольких форм с сотрудником-инициатором
* (Vacation, Clearance) — вынесено сюда, чтобы не разойтись при правке.
*/
export async function ensureEmployee(login: string): Promise<{ Id: number; Title: string }> {
const full = login.indexOf('|') >= 0 ? login : `i:0#.f|membership|${login}`;
return getSP().web.ensureUser(full);
}