feat: implement governed project portfolio
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
apiFetch,
|
||||
apiPath,
|
||||
type ApiSettings
|
||||
} from "@govoplan/core-webui";
|
||||
|
||||
|
||||
export type ProjectObjectKind = "portfolio" | "project" | "milestone";
|
||||
|
||||
export type ProjectSubjectRef = {
|
||||
kind: string;
|
||||
id: string;
|
||||
label?: string | null;
|
||||
};
|
||||
|
||||
export type ProjectRecord = {
|
||||
tenant_id: string;
|
||||
object_kind: ProjectObjectKind;
|
||||
object_id: string;
|
||||
object_key: string;
|
||||
revision: number;
|
||||
title: string;
|
||||
state: string;
|
||||
description?: string | null;
|
||||
visibility: "tenant" | "restricted";
|
||||
parent_kind?: ProjectObjectKind | null;
|
||||
parent_id?: string | null;
|
||||
starts_at?: string | null;
|
||||
due_at?: string | null;
|
||||
recorded_at: string;
|
||||
change_reason: string;
|
||||
owner?: ProjectSubjectRef | null;
|
||||
memberships: Array<Record<string, unknown>>;
|
||||
outcomes: Array<{ key: string; title: string; description?: string | null }>;
|
||||
benefits: Array<{ key: string; title: string; target?: string | null }>;
|
||||
dependencies: Array<Record<string, unknown>>;
|
||||
capacity_assumptions: Array<Record<string, unknown>>;
|
||||
change_impacts: Array<Record<string, unknown>>;
|
||||
benefit_reviews: Array<Record<string, unknown>>;
|
||||
resource_links: Array<Record<string, unknown>>;
|
||||
external_references: Array<Record<string, unknown>>;
|
||||
metadata: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type ProjectListResponse = {
|
||||
objects: ProjectRecord[];
|
||||
total: number;
|
||||
offset: number;
|
||||
limit: number;
|
||||
};
|
||||
|
||||
export function listProjectObjects(
|
||||
settings: ApiSettings,
|
||||
options: {
|
||||
objectKinds?: ProjectObjectKind[];
|
||||
states?: string[];
|
||||
parentKind?: ProjectObjectKind;
|
||||
parentId?: string;
|
||||
query?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
},
|
||||
signal?: AbortSignal
|
||||
): Promise<ProjectListResponse> {
|
||||
return apiFetch(settings, apiPath("/api/v1/projects/objects", {
|
||||
object_kind: options.objectKinds,
|
||||
state: options.states,
|
||||
parent_kind: options.parentKind,
|
||||
parent_id: options.parentId,
|
||||
query: options.query,
|
||||
offset: options.offset,
|
||||
limit: options.limit
|
||||
}), { signal });
|
||||
}
|
||||
|
||||
export function createProjectObject(
|
||||
settings: ApiSettings,
|
||||
record: ProjectRecord,
|
||||
idempotencyKey: string
|
||||
): Promise<ProjectRecord> {
|
||||
return apiFetch(settings, "/api/v1/projects/objects", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ record, idempotency_key: idempotencyKey })
|
||||
});
|
||||
}
|
||||
|
||||
export function updateProjectObject(
|
||||
settings: ApiSettings,
|
||||
record: ProjectRecord,
|
||||
changes: Record<string, unknown>,
|
||||
changeReason: string
|
||||
): Promise<ProjectRecord> {
|
||||
return apiFetch(
|
||||
settings,
|
||||
`/api/v1/projects/objects/${encodeURIComponent(record.object_kind)}/${encodeURIComponent(record.object_id)}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({
|
||||
expected_revision: record.revision,
|
||||
recorded_at: new Date().toISOString(),
|
||||
change_reason: changeReason,
|
||||
idempotency_key: crypto.randomUUID(),
|
||||
changes
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user