pcp-tgu-ops-ui: Ф6 ядро — draft/validate/promote в редакторе (replace), прямой прокси /api/pcp-tgu
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { TguApiError } from "../model/tguTypes";
|
||||
|
||||
/**
|
||||
* Вызовы версионирования/выдачи плана в pcp-tgu-service напрямую через Vite-прокси
|
||||
* (`/api/pcp-tgu` → :7011), как и pcp-mission напрямую на :7010. BFF не используется.
|
||||
*/
|
||||
const BASE = "/api/pcp-tgu";
|
||||
|
||||
/** Режим наполнения черновика; та же строка уходит в clone миссии. */
|
||||
export type DraftMode = "replace" | "fork-forward";
|
||||
|
||||
export type CreateDraftRequest = {
|
||||
mode: DraftMode;
|
||||
/** Начало окна целевой ЗРВ (UTC naive, ISO без зоны). Обязательно для fork-forward. */
|
||||
windowStart?: string;
|
||||
/** Конец окна целевой ЗРВ. Обязательно для fork-forward. */
|
||||
windowStop?: string;
|
||||
/** КПП целевой ЗРВ. Обязательно для fork-forward. */
|
||||
windowKppId?: string;
|
||||
};
|
||||
|
||||
export type CreateDraftResponse = {
|
||||
/** Идентификатор созданного черновика (= mission_id клон-миссии). */
|
||||
draftPlanId: string;
|
||||
};
|
||||
|
||||
export type PromoteDraftResponse = {
|
||||
processInstanceKey: number;
|
||||
processDefinitionKey: number;
|
||||
bpmnProcessId: string;
|
||||
version: number;
|
||||
planId: string;
|
||||
attemptId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Создаёт черновик плана (DRAFT) на основе базового плана
|
||||
* (pcp-tgu-service, POST /api/plans/{basePlanId}/draft). Триггерит клон миссии; возвращает
|
||||
* draftPlanId, в который адресуются правки (Ф3) и который применяется promote (Ф5).
|
||||
*/
|
||||
export async function createDraft(basePlanId: string, request: CreateDraftRequest): Promise<CreateDraftResponse> {
|
||||
return postJson<CreateDraftResponse>(
|
||||
`${BASE}/api/plans/${encodeURIComponent(basePlanId)}/draft`,
|
||||
request,
|
||||
"Не удалось создать черновик плана."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* «Применить» черновик: промоутит DRAFT в живую цепочку и выдаёт его
|
||||
* (pcp-tgu-service, POST /api/plans/{draftPlanId}/promote). Под оптимистичным локом базы и
|
||||
* гардом точки невозврата: 409 — база уехала / уже идёт выдача; 422 — не DRAFT / слот сместился /
|
||||
* заморожено; 404 — черновика нет.
|
||||
*/
|
||||
export async function promoteDraft(draftPlanId: string): Promise<PromoteDraftResponse> {
|
||||
return postJson<PromoteDraftResponse>(
|
||||
`${BASE}/api/plans/${encodeURIComponent(draftPlanId)}/promote`,
|
||||
undefined,
|
||||
"Не удалось применить черновик плана."
|
||||
);
|
||||
}
|
||||
|
||||
async function postJson<T>(url: string, body: unknown, fallbackMessage: string): Promise<T> {
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: body === undefined
|
||||
? { Accept: "application/json" }
|
||||
: { Accept: "application/json", "Content-Type": "application/json" },
|
||||
...(body === undefined ? {} : { body: JSON.stringify(body) })
|
||||
});
|
||||
} catch {
|
||||
throw new TguApiError("Не удалось подключиться к сервису планирования ТГУ.", "network");
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new TguApiError(extractErrorText(text) || fallbackMessage, "http", response.status);
|
||||
}
|
||||
if (!text.trim()) {
|
||||
return undefined as T;
|
||||
}
|
||||
return JSON.parse(text) as T;
|
||||
}
|
||||
|
||||
function extractErrorText(body: string): string {
|
||||
const trimmed = body.trim();
|
||||
if (!trimmed) return "";
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed) as Record<string, unknown>;
|
||||
for (const field of ["message", "error", "reason", "detail"]) {
|
||||
const value = parsed[field];
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return trimmed;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
Reference in New Issue
Block a user