feat: changed get time logic to approvers
This commit is contained in:
@@ -130,7 +130,7 @@ export class ApprovalListService {
|
||||
if (queuedRows.length === 0) return;
|
||||
const minOrder = Math.min(...queuedRows.map((r) => r.order));
|
||||
await Promise.all(
|
||||
queuedRows.filter((r) => r.order === minOrder).map((r) => this.setStatus(r.id, activeStatus))
|
||||
queuedRows.filter((r) => r.order === minOrder).map((r) => this.setActive(r.id, activeStatus))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -146,6 +146,17 @@ export class ApprovalListService {
|
||||
await this.list().items.getById(rowId).update({ [FIELDS.status]: status });
|
||||
}
|
||||
|
||||
// Активация строки: статус + отметка времени, КОГДА строка стала текущей
|
||||
// («получил на согласование/исполнение»). Отличается от setStatus именно
|
||||
// датой — использовать только на переходе в активный статус, не для отката в
|
||||
// «Ждёт очереди». Дату решения позже перезапишет setDecision. См.
|
||||
// activateNextQueued (единая точка активации: отправка, одобрение, делегат).
|
||||
private async setActive(rowId: number, status: string): Promise<void> {
|
||||
await this.list()
|
||||
.items.getById(rowId)
|
||||
.update({ [FIELDS.status]: status, [FIELDS.date]: new Date().toISOString() });
|
||||
}
|
||||
|
||||
// Добавляет одну строку согласования с заданным Order (для делегата).
|
||||
// delegatedById — Id пользователя, который выполнил переназначение (не строки
|
||||
// из маршрута — у них делегата нет). Он сохраняет право переназначить эту
|
||||
@@ -262,10 +273,14 @@ export class ApprovalListService {
|
||||
}
|
||||
|
||||
// Должность пользователя из профиля SharePoint (SPS-JobTitle). '' если недоступно.
|
||||
// Принимает и клеймовый логин (i:0#.f|membership|…), и голый e-mail: пикер
|
||||
// сотрудника отдаёт именно e-mail (см. pickedLogin), а getPropertiesFor ждёт
|
||||
// account name — приводим e-mail к клеймовому виду так же, как ensure().
|
||||
public async getJobTitle(loginName: string): Promise<string> {
|
||||
if (!loginName) return '';
|
||||
const account = loginName.indexOf('|') >= 0 ? loginName : `i:0#.f|membership|${loginName}`;
|
||||
try {
|
||||
const props = (await getSP().profiles.getPropertiesFor(loginName)) as {
|
||||
const props = (await getSP().profiles.getPropertiesFor(account)) as {
|
||||
UserProfileProperties?: unknown;
|
||||
};
|
||||
const raw = props.UserProfileProperties;
|
||||
@@ -326,11 +341,17 @@ export class ApprovalListService {
|
||||
return getSP().web.ensureUser(login);
|
||||
}
|
||||
|
||||
// Формат «дд.мм.гггг чч:мм» в казахстанском поясе (UTC+5) — жёстко, а не по
|
||||
// поясу браузера: время должно совпадать у всех участников. В SharePoint дата
|
||||
// хранится в UTC (setDecision/setActive пишут toISOString); Казахстан без
|
||||
// перехода на летнее время, поэтому достаточно прибавить 5 часов и читать
|
||||
// UTC-компоненты (getUTC*), не завися от локального пояса среды.
|
||||
private formatDate(iso: string): string {
|
||||
if (!iso) return '';
|
||||
const d = new Date(iso);
|
||||
const dd = d.getDate() < 10 ? `0${d.getDate()}` : `${d.getDate()}`;
|
||||
const mm = d.getMonth() + 1 < 10 ? `0${d.getMonth() + 1}` : `${d.getMonth() + 1}`;
|
||||
return `${dd}.${mm}.${d.getFullYear()}`;
|
||||
const KZ_OFFSET_MS = 5 * 60 * 60 * 1000;
|
||||
const d = new Date(new Date(iso).getTime() + KZ_OFFSET_MS);
|
||||
const p = (n: number): string => (n < 10 ? `0${n}` : `${n}`);
|
||||
const date = `${p(d.getUTCDate())}.${p(d.getUTCMonth() + 1)}.${d.getUTCFullYear()}`;
|
||||
return `${date} ${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,12 +41,16 @@ export async function resolveRoute(
|
||||
userEmail: string,
|
||||
status: ApprovalStatus = 'Approval',
|
||||
departmentId: string | null = null,
|
||||
fieldValues: IResolveFieldValue[] = []
|
||||
fieldValues: IResolveFieldValue[] = [],
|
||||
projectId: string | null = null
|
||||
): Promise<IResolvedRoute> {
|
||||
const c = await client(context);
|
||||
const url = `${APPROVAL_API_BASE_URL}/api/template-routes/resolve`;
|
||||
|
||||
// Эндпоинт сменился с GET (query) на POST (JSON-тело).
|
||||
// projectId (uuid проекта) — для форм, где согласующий зависит от проекта
|
||||
// (напр. «руководитель проекта-источника» в СЗ на перемещение ТМЦ): бэкенд по
|
||||
// нему подставляет исполнителя роли. Формы без привязки к проекту шлют null.
|
||||
const response: HttpClientResponse = await c.post(url, AadHttpClient.configurations.v1, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -55,7 +59,7 @@ export async function resolveRoute(
|
||||
body: JSON.stringify({
|
||||
requestTypeId,
|
||||
userEmail,
|
||||
projectId: null,
|
||||
projectId: projectId || null,
|
||||
status,
|
||||
fieldValues,
|
||||
departmentId: departmentId || null
|
||||
|
||||
@@ -16,10 +16,11 @@ export async function getPlannedMatrix(
|
||||
approval: ApprovalListService,
|
||||
status: ApprovalStatus = 'Approval',
|
||||
departmentId?: string | null,
|
||||
fieldValues?: IResolveFieldValue[]
|
||||
fieldValues?: IResolveFieldValue[],
|
||||
projectId?: string | null
|
||||
): Promise<IMatrixRow[]> {
|
||||
if (!userEmail) return [];
|
||||
const route = await resolveRoute(context, requestTypeId, userEmail, status, departmentId, fieldValues);
|
||||
const route = await resolveRoute(context, requestTypeId, userEmail, status, departmentId, fieldValues, projectId);
|
||||
const phase = status === 'Execution' ? APPROVAL_PHASE.execution : APPROVAL_PHASE.approval;
|
||||
const ordered = route.steps.slice().sort((a, b) => a.orderNo - b.orderNo);
|
||||
return Promise.all(
|
||||
|
||||
@@ -18,6 +18,15 @@ export interface IApprovalMatrixProps {
|
||||
emptyText?: string;
|
||||
/** Плейсхолдер пикера добавления (только если editor.editable). */
|
||||
pickerPlaceholder?: string;
|
||||
/**
|
||||
* Режим ожидания: маршрут ещё нельзя построить (не выбрана зависимость,
|
||||
* напр. проект). Показываем только заголовок и информационную заглушку —
|
||||
* без обращения к API, таблицы и пикера. Состояние loading/error/rows
|
||||
* редактора при этом игнорируется.
|
||||
*/
|
||||
waiting?: boolean;
|
||||
/** Текст заглушки в режиме waiting. */
|
||||
waitingText?: string;
|
||||
}
|
||||
|
||||
/** Таблица маршрута (согласование или исполнение) + (если editable) правка состава. */
|
||||
@@ -28,6 +37,8 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
|
||||
roleLabel = 'Согласующий',
|
||||
emptyText = 'Согласующие не заданы.',
|
||||
pickerPlaceholder = 'Добавить согласующего: начните вводить ФИО…',
|
||||
waiting = false,
|
||||
waitingText = '',
|
||||
}) => {
|
||||
const { rows, loading, error, editable, blocks } = editor;
|
||||
|
||||
@@ -86,12 +97,13 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
|
||||
return (
|
||||
<div className={styles.matrixWrap}>
|
||||
<div className={styles.matrixTitle}>{title}</div>
|
||||
{loading && <div className={styles.matrixNote}>Загрузка маршрута…</div>}
|
||||
{!loading && error && <div className={styles.matrixNote}>Маршрут недоступен: {error}</div>}
|
||||
{!loading && !error && rows.length === 0 && (
|
||||
{waiting && <div className={styles.matrixNote}>{waitingText}</div>}
|
||||
{!waiting && loading && <div className={styles.matrixNote}>Загрузка маршрута…</div>}
|
||||
{!waiting && !loading && error && <div className={styles.matrixNote}>Маршрут недоступен: {error}</div>}
|
||||
{!waiting && !loading && !error && rows.length === 0 && (
|
||||
<div className={styles.matrixNote}>{emptyText}</div>
|
||||
)}
|
||||
{!loading && !error && rows.length > 0 && (
|
||||
{!waiting && !loading && !error && rows.length > 0 && (
|
||||
<div className={styles.matrixScroll}>
|
||||
<table className={styles.matrix}>
|
||||
<thead>
|
||||
@@ -193,7 +205,7 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
{editable && !loading && (
|
||||
{editable && !waiting && !loading && (
|
||||
<div className={styles.matrixPicker}>
|
||||
<UserPicker search={search} onPick={addApprover} placeholder={pickerPlaceholder} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user