Lieferanten-/IT-Service-Cockpit direkt auf der Asset-Seite öffnen

Statt beim Klick auf ein Lieferanten-/IT-Service-Asset zur Lieferanten-Seite zu
wechseln, wird das Fach-Cockpit jetzt als Popup auf /assets gerendert; Schließen,
Bearbeiten, Speichern und Löschen bleiben auf der Asset-Seite.

- backHref-Prop an SupplierDetail/Edit- und ServiceDetail/Edit-Modal: alle
  Schließen-/Bearbeiten-/Abbrechen-Links leiten sich davon ab (Default /suppliers)
- withQuery-Helper (lib/supplier) für korrekte ?/&-Verkettung
- updateSupplier/updateService lesen returnTo (verstecktes Feld) und leiten dorthin
  zurück; deleteSupplier/deleteService bekommen returnTo-Parameter
- Kind-Actions revalidieren zusätzlich /assets, damit das Popup dort aktuell bleibt
- SUPPLIER_INCLUDE/SERVICE_INCLUDE in lib/supplier-include ausgelagert und von
  beiden Seiten genutzt
- assets/page.tsx bestimmt die Popup-Art vorab (supplier/service/asset) und rendert
  das passende Cockpit mit backHref="/assets"; profillose Alt-Assets bleiben Assets

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 13:55:46 +02:00
co-authored by Claude Opus 4.8
parent db710c43cf
commit 2706424c08
8 changed files with 172 additions and 98 deletions
+65 -29
View File
@@ -1,5 +1,4 @@
import Link from "next/link";
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { Plus, Search } from "lucide-react";
import type { AssetType } from "@prisma/client";
@@ -22,6 +21,17 @@ import {
AssetDetailModal,
AssetEditModal,
} from "@/components/asset-modals";
import {
SupplierDetailModal,
SupplierEditModal,
type SupplierAssetDetail,
} from "@/components/supplier-modals";
import {
ServiceDetailModal,
ServiceEditModal,
type ServiceAssetDetail,
} from "@/components/service-modals";
import { SUPPLIER_INCLUDE, SERVICE_INCLUDE } from "@/lib/supplier-include";
import {
Table,
TableBody,
@@ -35,22 +45,6 @@ const ASSET_TYPES = ["INFORMATION", "SYSTEM", "APPLICATION", "LOCATION", "SUPPLI
const STATUS_TONE = { ACTIVE: "ok", PLANNED: "info", RETIRED: "mut" } as const;
/**
* Lieferanten- und IT-Service-Assets werden in ihrem Fach-Cockpit geöffnet
* (sofern ein Fachprofil existiert), alle übrigen Assets in der normalen
* Asset-Detailansicht.
*/
function assetDetailHref(asset: {
id: string;
type: AssetType;
supplierProfile: { id: string } | null;
serviceProfile: { id: string } | null;
}) {
if (asset.type === "SUPPLIER" && asset.supplierProfile) return `/suppliers?detail=${asset.id}`;
if (asset.type === "IT_SERVICE" && asset.serviceProfile) return `/suppliers?tab=services&detail=${asset.id}`;
return `/assets?detail=${asset.id}`;
}
export default async function AssetsPage({
searchParams,
}: {
@@ -103,13 +97,52 @@ export default async function AssetsPage({
// Popup-Zustand aus searchParams: ?detail= (read-only), ?edit=, ?new=1
const modalId = params.edit && canWrite ? params.edit : params.detail;
const modalAsset = modalId
const isEdit = Boolean(params.edit && canWrite);
// Art des Popups vorab bestimmen: Lieferanten & IT-Services mit Fachprofil
// werden direkt hier als Fach-Cockpit gerendert (backHref="/assets"), sodass
// man auf der Asset-Seite bleibt; profillose Alt-Assets bleiben normale Assets.
const modalMeta = modalId
? await db.asset.findUnique({
where: { id: modalId },
include: {
owner: { select: { name: true } },
select: {
type: true,
supplierProfile: { select: { id: true } },
serviceProfile: { select: { id: true } },
},
})
: null;
const modalKind: "supplier" | "service" | "asset" | null = !modalMeta
? null
: modalMeta.type === "SUPPLIER" && modalMeta.supplierProfile
? "supplier"
: modalMeta.type === "IT_SERVICE" && modalMeta.serviceProfile
? "service"
: "asset";
const supplierModal =
modalKind === "supplier"
? ((await db.asset.findUnique({ where: { id: modalId! }, include: SUPPLIER_INCLUDE })) as SupplierAssetDetail | null)
: null;
const serviceModal =
modalKind === "service"
? ((await db.asset.findUnique({ where: { id: modalId! }, include: SERVICE_INCLUDE })) as ServiceAssetDetail | null)
: null;
const providers =
modalKind === "service" && isEdit
? await db.asset.findMany({
where: { type: "SUPPLIER", supplierProfile: { isNot: null } },
select: { id: true, name: true },
orderBy: { name: "asc" },
})
: [];
const modalAsset =
modalKind === "asset"
? await db.asset.findUnique({
where: { id: modalId! },
include: {
owner: { select: { name: true } },
relationsFrom: {
include: {
relatedAsset: {
@@ -134,13 +167,6 @@ export default async function AssetsPage({
})
: null;
// Lieferanten/IT-Services im Fach-Cockpit öffnen, auch bei direktem Aufruf
// (nur wenn ein Fachprofil existiert; profillose Alt-Assets bleiben im Asset-Detail)
if (modalAsset && ((modalAsset.type === "SUPPLIER" && modalAsset.supplierProfile) || (modalAsset.type === "IT_SERVICE" && modalAsset.serviceProfile))) {
const base = modalAsset.type === "IT_SERVICE" ? "/suppliers?tab=services" : "/suppliers";
redirect(`${base}${base.includes("?") ? "&" : "?"}${params.edit && canWrite ? "edit" : "detail"}=${modalAsset.id}`);
}
const users =
canWrite && (params.edit || params.new)
? await db.user.findMany({
@@ -247,7 +273,7 @@ export default async function AssetsPage({
{assets.map((asset) => (
<TableRow key={asset.id}>
<TableCell>
<Link href={assetDetailHref(asset)} className="font-bold hover:underline">
<Link href={`/assets?detail=${asset.id}`} className="font-bold hover:underline">
{asset.name}
</Link>
</TableCell>
@@ -274,7 +300,17 @@ export default async function AssetsPage({
</Table>
</div>
{modalAsset && params.edit && canWrite ? (
{/* Lieferanten- & IT-Service-Cockpits werden hier direkt gerendert
(backHref="/assets"), damit man auf der Asset-Seite bleibt. */}
{supplierModal && isEdit ? (
<SupplierEditModal supplier={supplierModal} backHref="/assets" />
) : supplierModal ? (
<SupplierDetailModal supplier={supplierModal} canWrite={canWrite} backHref="/assets" />
) : serviceModal && isEdit ? (
<ServiceEditModal service={serviceModal} providers={providers} backHref="/assets" />
) : serviceModal ? (
<ServiceDetailModal service={serviceModal} canWrite={canWrite} backHref="/assets" />
) : modalAsset && isEdit ? (
<AssetEditModal asset={modalAsset} users={users} otherAssets={otherAssets} />
) : modalAsset ? (
<AssetDetailModal asset={modalAsset} canWrite={canWrite} />
+1 -23
View File
@@ -31,6 +31,7 @@ import {
CONFORMITY_LABEL,
TARGET_MATURITY,
} from "@/lib/supplier";
import { SUPPLIER_INCLUDE, SERVICE_INCLUDE } from "@/lib/supplier-include";
import {
Table,
TableBody,
@@ -40,29 +41,6 @@ import {
TableRow,
} from "@/components/ui/table";
const SUPPLIER_INCLUDE = {
supplierProfile: true,
contracts: true,
ndas: true,
evidence: true,
assessments: true,
subcontractors: true,
decisions: true,
maturity: true,
riskAssets: { include: { risk: { select: { id: true, refNo: true, title: true, score: true } } } },
relationsFrom: { include: { relatedAsset: { select: { name: true, confidentiality: true, integrity: true, availability: true } } } },
relationsTo: { include: { asset: { select: { name: true, confidentiality: true, integrity: true, availability: true } } } },
} as const;
const SERVICE_INCLUDE = {
serviceProfile: { include: { provider: { select: { id: true, name: true } } } },
raci: { orderBy: { controlRef: "asc" } },
riskAssets: { include: { risk: { select: { id: true, refNo: true, title: true, score: true } } } },
relationsFrom: { include: { relatedAsset: { select: { id: true, name: true, type: true, confidentiality: true, integrity: true, availability: true } } } },
relationsTo: { include: { asset: { select: { id: true, name: true } } } },
processAssets: { include: { process: { select: { id: true, name: true } } } },
} as const;
export default async function SuppliersPage({
searchParams,
}: {
+13 -10
View File
@@ -17,7 +17,7 @@ import { Textarea } from "@/components/ui/textarea";
import { Modal } from "@/components/modal";
import { SegmentedRating } from "@/components/segmented-rating";
import { CiaBadge, CiaLegend, CriticalityPill, Pill, Tag } from "@/components/mockup-ui";
import { serviceRef } from "@/lib/supplier";
import { serviceRef, withQuery } from "@/lib/supplier";
import { riskLevel, riskRef, RISK_PILL_TONE } from "@/lib/risk";
import { ISA_CONTROLS } from "@/lib/isa-controls";
@@ -37,7 +37,7 @@ const RACI_TONE = { PROVIDER: "info", US: "violet", SHARED: "warn" } as const;
/* ─────────────────────── Detail-Cockpit ─────────────────────── */
export async function ServiceDetailModal({ service, canWrite }: { service: ServiceAssetDetail; canWrite: boolean }) {
export async function ServiceDetailModal({ service, canWrite, backHref = "/suppliers?tab=services" }: { service: ServiceAssetDetail; canWrite: boolean; backHref?: string }) {
const t = await getTranslations("services");
const ta = await getTranslations("assets");
const tParty = await getTranslations("raciParty");
@@ -60,16 +60,16 @@ export async function ServiceDetailModal({ service, canWrite }: { service: Servi
<CriticalityPill level={p.criticality} label={tCrit(String(p.criticality))} />
</span>
}
closeHref="/suppliers?tab=services"
closeHref={backHref}
closeLabel={tp("close")}
footer={
<>
{canWrite && (
<Button variant="outline" nativeButton={false} render={<Link href={`/suppliers?tab=services&edit=${service.id}`} />}>
<Button variant="outline" nativeButton={false} render={<Link href={withQuery(backHref, "edit", service.id)} />}>
<Pencil className="size-4" /> {tc("edit")}
</Button>
)}
<Button nativeButton={false} render={<Link href="/suppliers?tab=services" />}>{tp("close")}</Button>
<Button nativeButton={false} render={<Link href={backHref} />}>{tp("close")}</Button>
</>
}
>
@@ -334,10 +334,11 @@ export async function ServiceCreateModal({ providers }: { providers: { id: strin
);
}
export async function ServiceEditModal({ service, providers }: { service: ServiceAssetDetail; providers: { id: string; name: string }[] }) {
export async function ServiceEditModal({ service, providers, backHref = "/suppliers?tab=services" }: { service: ServiceAssetDetail; providers: { id: string; name: string }[]; backHref?: string }) {
const t = await getTranslations("services");
const tc = await getTranslations("common");
const FORM = "service-edit";
const detailHref = withQuery(backHref, "detail", service.id);
return (
<Modal
title={t("editTitle")}
@@ -346,22 +347,24 @@ export async function ServiceEditModal({ service, providers }: { service: Servic
<details className="relative">
<summary className="grid size-8 cursor-pointer list-none place-items-center rounded-md text-muted-foreground hover:bg-muted [&::-webkit-details-marker]:hidden"><Trash2 className="size-4" /></summary>
<div className="shadow-card absolute right-0 z-20 mt-1 w-48 rounded-xl border bg-card p-1.5">
<form action={deleteService.bind(null, service.id)}>
<form action={deleteService.bind(null, service.id, backHref)}>
<button type="submit" className="flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm text-destructive hover:bg-destructive/10"><Trash2 className="size-4" /> {tc("delete")}</button>
</form>
</div>
</details>
}
closeHref={`/suppliers?tab=services&detail=${service.id}`}
closeHref={detailHref}
closeLabel={t("close")}
footer={
<>
<Button variant="outline" nativeButton={false} render={<Link href={`/suppliers?tab=services&detail=${service.id}`} />}>{tc("cancel")}</Button>
<Button variant="outline" nativeButton={false} render={<Link href={detailHref} />}>{tc("cancel")}</Button>
<Button type="submit" form={FORM}>{tc("save")}</Button>
</>
}
>
<form id={FORM} action={updateService.bind(null, service.id)} className="hidden" />
<form id={FORM} action={updateService.bind(null, service.id)} className="hidden">
<input type="hidden" name="returnTo" value={backHref} />
</form>
<div className="p-5">
<ServiceFields service={service} providers={providers} formId={FORM} />
</div>
+16 -11
View File
@@ -25,6 +25,7 @@ import { Pill } from "@/components/mockup-ui";
import { SupplierRequirements } from "@/components/supplier-cockpit";
import {
supplierRef,
withQuery,
SUPPLIER_LIFECYCLE_TONE,
protectionLevel,
PROTECTION_LABEL,
@@ -80,7 +81,7 @@ function contextOf(s: SupplierAssetDetail) {
/* ─────────────────────── Cockpit (Detail) ─────────────────────── */
export async function SupplierDetailModal({ supplier, canWrite }: { supplier: SupplierAssetDetail; canWrite: boolean }) {
export async function SupplierDetailModal({ supplier, canWrite, backHref = "/suppliers" }: { supplier: SupplierAssetDetail; canWrite: boolean; backHref?: string }) {
const t = await getTranslations("suppliers");
const tLife = await getTranslations("supplierStatus");
const tEKind = await getTranslations("evidenceKind");
@@ -94,8 +95,9 @@ export async function SupplierDetailModal({ supplier, canWrite }: { supplier: Su
const computed = computedMaturity(level, ctx);
const reqs = REQUIREMENTS.map((r) => ({ key: r.key, label: r.label, control: r.control, tier: r.tier, theme: r.theme, met: r.met(ctx) }));
const gateEvidenceHref = `/suppliers?edit=${supplier.id}`;
const gateDecisionHref = `/suppliers?edit=${supplier.id}`;
const editHref = withQuery(backHref, "edit", supplier.id);
const gateEvidenceHref = editHref;
const gateDecisionHref = editHref;
return (
<Modal
@@ -108,16 +110,16 @@ export async function SupplierDetailModal({ supplier, canWrite }: { supplier: Su
<Pill tone={CONFORMITY_TONE[conf]}>{CONFORMITY_LABEL[conf]}</Pill>
</span>
}
closeHref="/suppliers"
closeHref={backHref}
closeLabel={t("close")}
footer={
<>
{canWrite && (
<Button variant="outline" nativeButton={false} render={<Link href={`/suppliers?edit=${supplier.id}`} />}>
<Button variant="outline" nativeButton={false} render={<Link href={editHref} />}>
<Pencil className="size-4" /> {tc("edit")}
</Button>
)}
<Button nativeButton={false} render={<Link href="/suppliers" />}>{t("close")}</Button>
<Button nativeButton={false} render={<Link href={backHref} />}>{t("close")}</Button>
</>
}
>
@@ -311,12 +313,13 @@ export async function SupplierCreateModal() {
/* ─────────────────────── Bearbeiten ─────────────────────── */
export async function SupplierEditModal({ supplier }: { supplier: SupplierAssetDetail }) {
export async function SupplierEditModal({ supplier, backHref = "/suppliers" }: { supplier: SupplierAssetDetail; backHref?: string }) {
const t = await getTranslations("suppliers");
const tc = await getTranslations("common");
const tEKind = await getTranslations("evidenceKind");
const tAType = await getTranslations("assessmentType");
const FORM = "supplier-edit";
const detailHref = withQuery(backHref, "detail", supplier.id);
const disc =
"cursor-pointer list-none rounded-lg border border-[var(--panel-brd)] bg-[var(--elevated)] px-3 py-1.5 text-[12.5px] font-semibold text-muted-foreground hover:text-foreground [&::-webkit-details-marker]:hidden";
@@ -328,22 +331,24 @@ export async function SupplierEditModal({ supplier }: { supplier: SupplierAssetD
<details className="relative">
<summary className="grid size-8 cursor-pointer list-none place-items-center rounded-md text-muted-foreground hover:bg-muted [&::-webkit-details-marker]:hidden"><Trash2 className="size-4" /></summary>
<div className="shadow-card absolute right-0 z-20 mt-1 w-48 rounded-xl border bg-card p-1.5">
<form action={deleteSupplier.bind(null, supplier.id)}>
<form action={deleteSupplier.bind(null, supplier.id, backHref)}>
<button type="submit" className="flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm text-destructive hover:bg-destructive/10"><Trash2 className="size-4" /> {tc("delete")}</button>
</form>
</div>
</details>
}
closeHref={`/suppliers?detail=${supplier.id}`}
closeHref={detailHref}
closeLabel={t("close")}
footer={
<>
<Button variant="outline" nativeButton={false} render={<Link href={`/suppliers?detail=${supplier.id}`} />}>{tc("cancel")}</Button>
<Button variant="outline" nativeButton={false} render={<Link href={detailHref} />}>{tc("cancel")}</Button>
<Button type="submit" form={FORM}>{tc("save")}</Button>
</>
}
>
<form id={FORM} action={updateSupplier.bind(null, supplier.id)} className="hidden" />
<form id={FORM} action={updateSupplier.bind(null, supplier.id)} className="hidden">
<input type="hidden" name="returnTo" value={backHref} />
</form>
<div className="space-y-6 p-5">
<SupplierFields supplier={supplier} formId={FORM} />
+29
View File
@@ -0,0 +1,29 @@
import type { Prisma } from "@prisma/client";
/**
* Gemeinsame Prisma-Includes für das Lieferanten- und IT-Service-Cockpit.
* Werden sowohl auf /suppliers als auch auf /assets verwendet, damit die
* Fach-Popups an beiden Stellen mit identischer Datenform gerendert werden.
*/
export const SUPPLIER_INCLUDE = {
supplierProfile: true,
contracts: true,
ndas: true,
evidence: true,
assessments: true,
subcontractors: true,
decisions: true,
maturity: true,
riskAssets: { include: { risk: { select: { id: true, refNo: true, title: true, score: true } } } },
relationsFrom: { include: { relatedAsset: { select: { name: true, confidentiality: true, integrity: true, availability: true } } } },
relationsTo: { include: { asset: { select: { name: true, confidentiality: true, integrity: true, availability: true } } } },
} satisfies Prisma.AssetInclude;
export const SERVICE_INCLUDE = {
serviceProfile: { include: { provider: { select: { id: true, name: true } } } },
raci: { orderBy: { controlRef: "asc" } },
riskAssets: { include: { risk: { select: { id: true, refNo: true, title: true, score: true } } } },
relationsFrom: { include: { relatedAsset: { select: { id: true, name: true, type: true, confidentiality: true, integrity: true, availability: true } } } },
relationsTo: { include: { asset: { select: { id: true, name: true } } } },
processAssets: { include: { process: { select: { id: true, name: true } } } },
} satisfies Prisma.AssetInclude;
+9
View File
@@ -11,6 +11,15 @@ export function serviceRef(refNo: number): string {
return `S-${String(refNo).padStart(3, "0")}`;
}
/**
* Hängt einen Query-Parameter an einen Basis-Pfad an und wählt automatisch
* das richtige Trennzeichen (`?` oder `&`). Erlaubt, Detail-/Bearbeiten-Popups
* sowohl auf /suppliers als auch auf /assets zu öffnen (backHref-Muster).
*/
export function withQuery(base: string, key: string, val: string): string {
return `${base}${base.includes("?") ? "&" : "?"}${key}=${val}`;
}
export const SUPPLIER_LIFECYCLE_TONE = {
ACTIVE: "ok",
ONBOARDING: "info",
+8 -3
View File
@@ -7,6 +7,7 @@ import { requireSession } from "@/server/auth";
import { dbForTenant, prisma } from "@/server/db";
import { requirePermission } from "@/server/rbac";
import { writeAuditLog } from "@/server/audit";
import { withQuery } from "@/lib/supplier";
const level = z.coerce.number().int().min(1).max(4);
@@ -73,10 +74,11 @@ export async function updateService(assetId: string, formData: FormData) {
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "it_service", entityId: assetId, before, after: { ...asset, ...profile } });
revalidatePath("/suppliers");
revalidatePath("/assets");
redirect(`/suppliers?tab=services&detail=${assetId}`);
const returnTo = (formData.get("returnTo") as string) || "/suppliers?tab=services";
redirect(withQuery(returnTo, "detail", assetId));
}
export async function deleteService(assetId: string) {
export async function deleteService(assetId: string, returnTo: string = "/suppliers?tab=services") {
const session = await requireSession();
requirePermission(session, "supplier:write");
const db = dbForTenant(session.user.tenantId);
@@ -86,7 +88,7 @@ export async function deleteService(assetId: string) {
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "delete", entity: "it_service", entityId: assetId, before });
revalidatePath("/suppliers");
revalidatePath("/assets");
redirect("/suppliers?tab=services");
redirect(returnTo);
}
export async function addRaci(assetId: string, formData: FormData) {
@@ -107,6 +109,7 @@ export async function addRaci(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "service_raci", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
/** Verantwortung einer RACI-Zeile per Klick ändern (PROVIDER/US/SHARED durchschalten). */
@@ -120,6 +123,7 @@ export async function cycleRaci(assetId: string, raciId: string) {
await db.serviceControlResponsibility.update({ where: { id: raciId }, data: { responsibility: next[row.responsibility] } });
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "service_raci", entityId: assetId, after: { responsibility: next[row.responsibility] } });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function deleteRaci(assetId: string, raciId: string) {
@@ -129,4 +133,5 @@ export async function deleteRaci(assetId: string, raciId: string) {
await db.serviceControlResponsibility.delete({ where: { id: raciId } });
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "delete", entity: "service_raci", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
+12 -3
View File
@@ -7,6 +7,7 @@ import { requireSession } from "@/server/auth";
import { dbForTenant, prisma } from "@/server/db";
import { requirePermission } from "@/server/rbac";
import { writeAuditLog } from "@/server/audit";
import { withQuery } from "@/lib/supplier";
const level = z.coerce.number().int().min(1).max(4);
@@ -110,10 +111,11 @@ export async function updateSupplier(assetId: string, formData: FormData) {
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "supplier", entityId: assetId, before, after: { ...asset, ...profile } });
revalidatePath("/suppliers");
revalidatePath("/assets");
redirect(`/suppliers?detail=${assetId}`);
const returnTo = (formData.get("returnTo") as string) || "/suppliers";
redirect(withQuery(returnTo, "detail", assetId));
}
export async function deleteSupplier(assetId: string) {
export async function deleteSupplier(assetId: string, returnTo: string = "/suppliers") {
const session = await requireSession();
requirePermission(session, "supplier:write");
const db = dbForTenant(session.user.tenantId);
@@ -123,7 +125,7 @@ export async function deleteSupplier(assetId: string) {
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "delete", entity: "supplier", entityId: assetId, before });
revalidatePath("/suppliers");
revalidatePath("/assets");
redirect("/suppliers");
redirect(returnTo);
}
async function ensure(session: Awaited<ReturnType<typeof requireSession>>, assetId: string) {
@@ -154,6 +156,7 @@ export async function addContract(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "contract", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function addNda(assetId: string, formData: FormData) {
@@ -174,6 +177,7 @@ export async function addNda(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "nda", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function addEvidence(assetId: string, formData: FormData) {
@@ -193,6 +197,7 @@ export async function addEvidence(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "supplier_evidence", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function addAssessment(assetId: string, formData: FormData) {
@@ -212,6 +217,7 @@ export async function addAssessment(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "supplier_assessment", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function addDecision(assetId: string, formData: FormData) {
@@ -230,6 +236,7 @@ export async function addDecision(assetId: string, formData: FormData) {
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "create", entity: "management_decision", entityId: assetId });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
/** Gate-Kompensation: echtes Risiko im Risikomodul anlegen und mit dem Lieferanten verknüpfen. */
@@ -282,6 +289,7 @@ export async function approveMaturity(assetId: string, computed: number, formDat
});
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "maturity_assessment", entityId: assetId, after: { computed, isbValue, justification } });
revalidatePath("/suppliers");
revalidatePath("/assets");
}
export async function deleteChild(
@@ -303,4 +311,5 @@ export async function deleteChild(
await map[kind].delete({ where: { id } });
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "delete", entity: `supplier_${kind}`, entityId: id });
revalidatePath("/suppliers");
revalidatePath("/assets");
}