L17 Pakete: Stufen Basis/Profi, Lotse-Chat-Plätze und Kontingent
- Datenmodell: Tenant.tier (Default PROFI), lotseChatSeats, lotseChatHardLimit;
Tabelle lotse_chat_seats (RLS, TENANT_MODELS, pii-fields), Index für die
Monatszählung der Chat-Nachrichten (Migration 20260921100000_pakete)
- src/lib/plans.ts: Stufenregeln, 150 Chats je Platz, Mehrverbrauch in 100er-Paketen
- src/server/plan.ts: effektive Freischaltung = Stufe UND TenantModule, genutzt von
requireModule, assertModuleEnabled, API, Sync (Offline-Op → rejected mit Klartext),
Navigation, isLotseEnabled und planningAccess (Planung nur in Profi)
- Lotse-Chat: Platzprüfung (no_seat), Testphase ohne Platz, Kontingent mit
hartem Limit (quota_exhausted); Platzvergabe durch den Mandanten-Admin
- Betreiber: Stufe/Plätze/hartes Limit im Mandantendetail mit Bestätigung
und Plattform-Audit, Verbrauch laufender Monat/Vormonat, Stufe als Badge
- Demo-Seed: demo = Profi mit 3 Plätzen, demo2 = Basis
- Tests: test-pakete-{rules,gates,seats}
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+13
-5
@@ -23,6 +23,7 @@ import {
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
import type { ModuleKey } from "@/lib/modules";
|
||||
import type { PlanFeature } from "@/lib/plans";
|
||||
import type { Permission } from "@/server/rbac";
|
||||
|
||||
/**
|
||||
@@ -47,14 +48,16 @@ export interface NavItem {
|
||||
sub?: boolean;
|
||||
/** L13: active only on exactly this path (not on sub paths) */
|
||||
exact?: boolean;
|
||||
/** L17 Pakete: feature without own module that the package tier must include (planning) */
|
||||
feature?: PlanFeature;
|
||||
}
|
||||
|
||||
export const NAV_ITEMS: readonly NavItem[] = [
|
||||
{ href: "/dashboard", label: "dashboard", icon: LayoutDashboard, section: "main" },
|
||||
// L13 Planung: top-level entry with sub entries; backoffice (read_all) + team leads (report:approve_team, own crews read-only)
|
||||
{ href: "/planning", label: "planning", icon: CalendarRange, module: "work_orders", permissions: ["work_order:read_all", "report:approve_team"], section: "main" },
|
||||
{ href: "/planning", label: "planningBoard", icon: LayoutGrid, module: "work_orders", permissions: ["work_order:read_all", "report:approve_team"], section: "main", sub: true, exact: true },
|
||||
{ href: "/planning/live", label: "planningLive", icon: MapPinned, module: "work_orders", permissions: ["work_order:read_all", "report:approve_team"], section: "main", sub: true },
|
||||
{ href: "/planning", label: "planning", icon: CalendarRange, module: "work_orders", feature: "planning", permissions: ["work_order:read_all", "report:approve_team"], section: "main" },
|
||||
{ href: "/planning", label: "planningBoard", icon: LayoutGrid, module: "work_orders", feature: "planning", permissions: ["work_order:read_all", "report:approve_team"], section: "main", sub: true, exact: true },
|
||||
{ href: "/planning/live", label: "planningLive", icon: MapPinned, module: "work_orders", feature: "planning", permissions: ["work_order:read_all", "report:approve_team"], section: "main", sub: true },
|
||||
{
|
||||
href: "/work-orders",
|
||||
label: "workOrders",
|
||||
@@ -81,14 +84,19 @@ export const NAV_ITEMS: readonly NavItem[] = [
|
||||
{ href: "/settings/export", label: "dataExport", icon: Download, permissions: ["tenant:manage"], section: "admin" }, // L15
|
||||
];
|
||||
|
||||
/** Filtert die Navigation nach aktiven Modulen und Rechten der Session. */
|
||||
/**
|
||||
* Filtert die Navigation nach aktiven Modulen und Rechten der Session. `disabledModules` = effektiv
|
||||
* inaktive Module (L17: abgeschaltet ODER nicht in der Paketstufe), `lockedFeatures` = nicht enthaltene
|
||||
* Paket-Features (Planung).
|
||||
*/
|
||||
export function visibleNavItems(
|
||||
items: readonly NavItem[],
|
||||
opts: { disabledModules: ReadonlySet<string>; permissions: readonly string[] },
|
||||
opts: { disabledModules: ReadonlySet<string>; permissions: readonly string[]; lockedFeatures?: ReadonlySet<string> },
|
||||
): NavItem[] {
|
||||
return items.filter(
|
||||
(item) =>
|
||||
(!item.module || !opts.disabledModules.has(item.module)) &&
|
||||
(!item.feature || !opts.lockedFeatures?.has(item.feature)) &&
|
||||
(!item.permissions?.length || item.permissions.some((p) => opts.permissions.includes(p))),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user