This commit is contained in:
2026-03-17 23:35:37 -05:00
parent b02b764b2f
commit 66d8814d89
10 changed files with 27 additions and 59 deletions

View File

@@ -7,7 +7,7 @@ This file is the running release and change log for CODEXIUM. Keep it updated wh
### Added ### Added
- Project cockpit section on project detail pages for commercial, supply, execution, delivery, purchasing, readiness-risk, and project cost snapshot rollups, plus direct launch paths into prefilled work-order and purchase-order follow-through and a chronological project activity timeline - Project cockpit section on project detail pages for commercial, supply, execution, delivery, purchasing, readiness-risk, and project cost snapshot rollups, plus direct launch paths into prefilled work-order and purchase-order follow-through and a chronological project activity timeline
- Planning workbench replacing the old one-note gantt screen with mode switching, dense exception rail, heatmap load view, agenda view, focus drawer, and gantt as one lens instead of the entire planner - Planning workbench replacing the old one-note planning screen with mode switching, dense exception rail, heatmap load view, agenda view, and focus drawer
- Project milestones with status, due dates, notes, and edit-time sequencing inside the project workflow - Project milestones with status, due dates, notes, and edit-time sequencing inside the project workflow
- Project-side milestone and work-order rollups surfaced on project list and detail pages - Project-side milestone and work-order rollups surfaced on project list and detail pages
- Inventory SKU master builder with family-level sequence codes, branch-aware taxonomy management, and generated SKU previews on the item form - Inventory SKU master builder with family-level sequence codes, branch-aware taxonomy management, and generated SKU previews on the item form
@@ -48,8 +48,8 @@ This file is the running release and change log for CODEXIUM. Keep it updated wh
- Manual inventory reservations plus automatic work-order-driven component reservations - Manual inventory reservations plus automatic work-order-driven component reservations
- Reserved and available stock visibility on inventory item detail and stock-by-location views - Reserved and available stock visibility on inventory item detail and stock-by-location views
- Manufacturing stations with queue-day definitions and item-level station/time operation templates - Manufacturing stations with queue-day definitions and item-level station/time operation templates
- Automatic work-order operation plans copied from buildable item routing into planning/gantt - Automatic work-order operation plans copied from buildable item routing into the planning workbench
- Live planning gantt timelines backed by active projects and open manufacturing work orders - Live planning workbench timelines backed by active projects and open manufacturing work orders
- Planning summary metrics and exception cards for overdue or at-risk project/manufacturing schedule items - Planning summary metrics and exception cards for overdue or at-risk project/manufacturing schedule items
- Sales approval actions with approved-by/approved-at stamps on quotes and sales orders - Sales approval actions with approved-by/approved-at stamps on quotes and sales orders
- Automatic sales-document revision history with authored reasons and per-revision snapshots - Automatic sales-document revision history with authored reasons and per-revision snapshots
@@ -79,7 +79,7 @@ This file is the running release and change log for CODEXIUM. Keep it updated wh
- JWT authentication now validates against persisted session records and inactive users lose access immediately instead of waiting for token expiry - JWT authentication now validates against persisted session records and inactive users lose access immediately instead of waiting for token expiry
- The dashboard now treats Projects as a live first-class module alongside CRM, inventory, sales, and shipping - The dashboard now treats Projects as a live first-class module alongside CRM, inventory, sales, and shipping
- The dashboard now treats Manufacturing as a live first-class module alongside CRM, inventory, sales, shipping, and projects - The dashboard now treats Manufacturing as a live first-class module alongside CRM, inventory, sales, shipping, and projects
- The dashboard now treats Planning as a live first-class module with direct gantt access from the landing page - The dashboard now treats Planning as a live first-class module with direct workbench access from the landing page
- Inventory control now distinguishes on-hand, reserved, and available stock instead of treating all positive stock as fully free - Inventory control now distinguishes on-hand, reserved, and available stock instead of treating all positive stock as fully free
- Manufacturing and inventory now share a routing-driven workflow where assemblies/manufactured parts define station/time templates and work orders inherit them automatically - Manufacturing and inventory now share a routing-driven workflow where assemblies/manufactured parts define station/time templates and work orders inherit them automatically
- Sales quote and sales-order detail pages now surface approval state and revision history directly in the operational workflow - Sales quote and sales-order detail pages now surface approval state and revision history directly in the operational workflow

View File

@@ -28,7 +28,7 @@ Current foundation scope includes:
- shipping shipments linked to sales orders with packing slips, shipping labels, bills of lading, and logistics attachments - shipping shipments linked to sales orders with packing slips, shipping labels, bills of lading, and logistics attachments
- projects with customer/commercial/shipment linkage, owners, due dates, milestones, rollups, notes, and attachments - projects with customer/commercial/shipment linkage, owners, due dates, milestones, rollups, notes, and attachments
- manufacturing work orders with project linkage, station-based operation templates, material issue posting, completion posting, and work-order attachments - manufacturing work orders with project linkage, station-based operation templates, material issue posting, completion posting, and work-order attachments
- planning workbench with live project/manufacturing schedule data, gantt lens, exception rail, heatmap load view, agenda view, and focus drawer - planning workbench with live project/manufacturing schedule data, exception rail, heatmap load view, agenda view, and focus drawer
- sales-order demand planning with multi-level BOM explosion, stock/open-supply netting, and build/buy recommendations - sales-order demand planning with multi-level BOM explosion, stock/open-supply netting, and build/buy recommendations
- planner-assisted conversion of demand-planning recommendations into prefilled work-order and purchase-order drafts - planner-assisted conversion of demand-planning recommendations into prefilled work-order and purchase-order drafts
- pegged WO/PO supply tracking back to sales demand with preferred-vendor sourcing on inventory items - pegged WO/PO supply tracking back to sales demand with preferred-vendor sourcing on inventory items
@@ -80,7 +80,7 @@ Dashboard direction:
- richer recent-activity widgets and exception queues are a planned QOL follow-up, not a separate landing-page redesign - richer recent-activity widgets and exception queues are a planned QOL follow-up, not a separate landing-page redesign
- projects now feed dashboard widgets for active programs, overdue work, and risk - projects now feed dashboard widgets for active programs, overdue work, and risk
- manufacturing now feeds dashboard widgets for released work, overdue orders, and execution load - manufacturing now feeds dashboard widgets for released work, overdue orders, and execution load
- planning now feeds live gantt scheduling from project and manufacturing records - planning now feeds the live workbench schedule from project and manufacturing records
- future project widgets should deepen milestones, shortages, and shipment readiness - future project widgets should deepen milestones, shortages, and shipment readiness
Navigation direction: Navigation direction:
@@ -106,7 +106,7 @@ Next expansion areas:
- Inventory: projects should reference item/BOM scope and later expose shortages or allocations - Inventory: projects should reference item/BOM scope and later expose shortages or allocations
- Purchasing: project material demand is now visible through linked PO, receipt, vendor, and outstanding-supply rollups, and should later expand into project-side purchasing actions - Purchasing: project material demand is now visible through linked PO, receipt, vendor, and outstanding-supply rollups, and should later expand into project-side purchasing actions
- Manufacturing: work orders should link back to projects without turning projects into the manufacturing module - Manufacturing: work orders should link back to projects without turning projects into the manufacturing module
- Planning: project milestones and execution dates should feed gantt scheduling and dependency views - Planning: project milestones and execution dates should feed workbench scheduling, dependency views, and richer planner drilldowns
## Manufacturing Direction ## Manufacturing Direction
@@ -126,7 +126,7 @@ Next expansion areas:
## Planning Direction ## Planning Direction
Planning is now the live scheduling and visibility layer over projects and manufacturing instead of a placeholder wrapper. The current slice ships a planning workbench backed by active projects, due-date milestones, linked work orders, standalone manufacturing queue visibility, exception rails, dense load heatmaps, focus-drawer inspection, agenda sequencing, and a gantt lens for timeline review. Planning is now the live scheduling and visibility layer over projects and manufacturing instead of a placeholder wrapper. The current slice ships a planning workbench backed by active projects, due-date milestones, linked work orders, standalone manufacturing queue visibility, exception rails, dense load heatmaps, focus-drawer inspection, and agenda sequencing.
Current interactions: Current interactions:

View File

@@ -104,7 +104,7 @@ This file tracks work that still needs to be completed. Shipped phase history an
- Task dependencies, milestones, and progress updates - Task dependencies, milestones, and progress updates
- Manufacturing calendar views and bottleneck visibility - Manufacturing calendar views and bottleneck visibility
- Labor and machine scheduling support - Labor and machine scheduling support
- Theme-compliant gantt customization for light/dark mode - Theme-compliant workbench scheduling surfaces for light/dark mode
- Collapsible schedule groupings and saved planner views - Collapsible schedule groupings and saved planner views
- Drag-and-drop rescheduling improvements - Drag-and-drop rescheduling improvements
- Critical-path and overdue highlighting - Critical-path and overdue highlighting

View File

@@ -37,7 +37,7 @@ This file tracks roadmap phases, slices, and major foundations that have already
- Project cockpit section on detail pages for commercial, supply, execution, delivery, purchasing, readiness-risk, and cost-snapshot visibility, with direct launch paths into prefilled project work orders and demand-linked purchase orders plus a project activity timeline - Project cockpit section on detail pages for commercial, supply, execution, delivery, purchasing, readiness-risk, and cost-snapshot visibility, with direct launch paths into prefilled project work orders and demand-linked purchase orders plus a project activity timeline
- Project list/detail/create/edit workflows and dashboard program widgets - Project list/detail/create/edit workflows and dashboard program widgets
- Manufacturing foundation with work orders, project linkage, material issue posting, completion posting, and work-order attachments - Manufacturing foundation with work orders, project linkage, material issue posting, completion posting, and work-order attachments
- Manufacturing stations, item routing templates, and automatic work-order operation planning for gantt scheduling - Manufacturing stations, item routing templates, and automatic work-order operation planning for the workbench schedule
- Vendor invoice/supporting-document attachments directly on purchase orders - Vendor invoice/supporting-document attachments directly on purchase orders
- Vendor-detail purchasing visibility with recent purchase-order activity - Vendor-detail purchasing visibility with recent purchase-order activity
- Revision comparison UX for changed sales and purchasing documents, including purchase-order revision persistence - Revision comparison UX for changed sales and purchasing documents, including purchase-order revision persistence
@@ -54,8 +54,8 @@ This file tracks roadmap phases, slices, and major foundations that have already
- Theme persistence fixes and denser responsive workspace layouts - Theme persistence fixes and denser responsive workspace layouts
- Startup brand-theme hydration so Company Settings colors and font persist correctly across refresh - Startup brand-theme hydration so Company Settings colors and font persist correctly across refresh
- Full-site density normalization pass across active CRM, inventory, settings, dashboard, and login screens - Full-site density normalization pass across active CRM, inventory, settings, dashboard, and login screens
- Live planning gantt timelines driven by project and manufacturing data - Live planning workbench timelines driven by project and manufacturing data
- Planning workbench with gantt, heatmap, overview, and agenda modes plus exception rail and focus drawer - Planning workbench with heatmap, overview, and agenda modes plus exception rail and focus drawer
- Sales-order demand planning with multi-level BOM explosion, stock/open-supply netting, and build/buy recommendations - Sales-order demand planning with multi-level BOM explosion, stock/open-supply netting, and build/buy recommendations
- Multi-stage Docker packaging and migration-aware entrypoint - Multi-stage Docker packaging and migration-aware entrypoint
- Docker image validated locally with successful app startup and login flow - Docker image validated locally with successful app startup and login flow
@@ -87,7 +87,7 @@ This file tracks roadmap phases, slices, and major foundations that have already
### Phase 7: Planning and scheduling ### Phase 7: Planning and scheduling
- Live gantt schedule backed by active projects and open manufacturing work orders - Live workbench schedule backed by active projects and open manufacturing work orders
- Project due-date milestones, manufacturing sequencing links, and standalone work-queue visibility - Project due-date milestones, manufacturing sequencing links, and standalone work-queue visibility
- Planning exception queue for overdue or at-risk project/manufacturing schedule items - Planning exception queue for overdue or at-risk project/manufacturing schedule items

View File

@@ -17,7 +17,7 @@ const links = [
{ to: "/shipping/shipments", label: "Shipments", icon: <ShipmentIcon /> }, { to: "/shipping/shipments", label: "Shipments", icon: <ShipmentIcon /> },
{ to: "/projects", label: "Projects", icon: <ProjectsIcon /> }, { to: "/projects", label: "Projects", icon: <ProjectsIcon /> },
{ to: "/manufacturing/work-orders", label: "Manufacturing", icon: <ManufacturingIcon /> }, { to: "/manufacturing/work-orders", label: "Manufacturing", icon: <ManufacturingIcon /> },
{ to: "/planning/gantt", label: "Gantt", icon: <GanttIcon /> }, { to: "/planning/workbench", label: "Workbench", icon: <WorkbenchIcon /> },
]; ];
function NavIcon({ children }: { children: ReactNode }) { function NavIcon({ children }: { children: ReactNode }) {
@@ -146,7 +146,7 @@ function ShipmentIcon() {
); );
} }
function GanttIcon() { function WorkbenchIcon() {
return ( return (
<NavIcon> <NavIcon>
<path d="M4 6h5" /> <path d="M4 6h5" />

View File

@@ -101,8 +101,8 @@ const ShipmentDetailPage = React.lazy(() =>
const ShipmentFormPage = React.lazy(() => const ShipmentFormPage = React.lazy(() =>
import("./modules/shipping/ShipmentFormPage").then((module) => ({ default: module.ShipmentFormPage })) import("./modules/shipping/ShipmentFormPage").then((module) => ({ default: module.ShipmentFormPage }))
); );
const GanttPage = React.lazy(() => const WorkbenchPage = React.lazy(() =>
import("./modules/gantt/GanttPage").then((module) => ({ default: module.GanttPage })) import("./modules/workbench/WorkbenchPage").then((module) => ({ default: module.WorkbenchPage }))
); );
const LandingPage = React.lazy(() => const LandingPage = React.lazy(() =>
import("./modules/landing/LandingPage").then((module) => ({ default: module.LandingPage })) import("./modules/landing/LandingPage").then((module) => ({ default: module.LandingPage }))
@@ -258,7 +258,10 @@ const router = createBrowserRouter([
}, },
{ {
element: <ProtectedRoute requiredPermissions={[permissions.ganttRead]} />, element: <ProtectedRoute requiredPermissions={[permissions.ganttRead]} />,
children: [{ path: "/planning/gantt", element: lazyElement(<GanttPage />) }], children: [
{ path: "/planning/workbench", element: lazyElement(<WorkbenchPage />) },
{ path: "/planning/gantt", element: <Navigate to="/planning/workbench" replace /> },
],
}, },
], ],
}, },

View File

@@ -761,7 +761,7 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) {
<div> <div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Manufacturing Routing</p> <p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Manufacturing Routing</p>
<h4 className="mt-2 text-lg font-bold text-text">Station and time template</h4> <h4 className="mt-2 text-lg font-bold text-text">Station and time template</h4>
<p className="mt-2 text-sm text-muted">These operations are copied automatically into work orders and drive gantt scheduling without manual planner task entry.</p> <p className="mt-2 text-sm text-muted">These operations are copied automatically into work orders and feed the planning workbench without manual planner task entry.</p>
</div> </div>
<button type="button" onClick={addOperation} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text"> <button type="button" onClick={addOperation} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
Add operation Add operation

View File

@@ -188,8 +188,8 @@ export function ProjectDetailPage() {
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Actionable Cockpit</p> <p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Actionable Cockpit</p>
<p className="mt-2 text-sm text-muted">Turn current exceptions into purchasing, manufacturing, and planning follow-through.</p> <p className="mt-2 text-sm text-muted">Turn current exceptions into purchasing, manufacturing, and planning follow-through.</p>
</div> </div>
<Link to="/planning/gantt" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text"> <Link to="/planning/workbench" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
Open gantt Open workbench
</Link> </Link>
</div> </div>
<div className="mt-5 grid gap-3 xl:grid-cols-2"> <div className="mt-5 grid gap-3 xl:grid-cols-2">

View File

@@ -1,14 +1,10 @@
import { Gantt } from "@svar-ui/react-gantt";
import "@svar-ui/react-gantt/style.css";
import type { DemandPlanningRollupDto, GanttTaskDto, PlanningExceptionDto, PlanningTimelineDto } from "@mrp/shared"; import type { DemandPlanningRollupDto, GanttTaskDto, PlanningExceptionDto, PlanningTimelineDto } from "@mrp/shared";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useAuth } from "../../auth/AuthProvider"; import { useAuth } from "../../auth/AuthProvider";
import { ApiError, api } from "../../lib/api"; import { ApiError, api } from "../../lib/api";
import { useTheme } from "../../theme/ThemeProvider"; type WorkbenchMode = "overview" | "heatmap" | "agenda";
type WorkbenchMode = "overview" | "gantt" | "heatmap" | "agenda";
type FocusRecord = { type FocusRecord = {
id: string; id: string;
title: string; title: string;
@@ -98,9 +94,8 @@ function buildFocusRecords(tasks: GanttTaskDto[]) {
})); }));
} }
export function GanttPage() { export function WorkbenchPage() {
const { token } = useAuth(); const { token } = useAuth();
const { mode } = useTheme();
const [timeline, setTimeline] = useState<PlanningTimelineDto | null>(null); const [timeline, setTimeline] = useState<PlanningTimelineDto | null>(null);
const [planningRollup, setPlanningRollup] = useState<DemandPlanningRollupDto | null>(null); const [planningRollup, setPlanningRollup] = useState<DemandPlanningRollupDto | null>(null);
const [status, setStatus] = useState("Loading live planning timeline..."); const [status, setStatus] = useState("Loading live planning timeline...");
@@ -126,15 +121,11 @@ export function GanttPage() {
}, [token]); }, [token]);
const tasks = timeline?.tasks ?? []; const tasks = timeline?.tasks ?? [];
const links = timeline?.links ?? [];
const summary = timeline?.summary; const summary = timeline?.summary;
const exceptions = timeline?.exceptions ?? []; const exceptions = timeline?.exceptions ?? [];
const focusRecords = useMemo(() => buildFocusRecords(tasks), [tasks]); const focusRecords = useMemo(() => buildFocusRecords(tasks), [tasks]);
const focusById = useMemo(() => new Map(focusRecords.map((record) => [record.id, record])), [focusRecords]); const focusById = useMemo(() => new Map(focusRecords.map((record) => [record.id, record])), [focusRecords]);
const selectedFocus = selectedFocusId ? focusById.get(selectedFocusId) ?? null : focusRecords[0] ?? null; const selectedFocus = selectedFocusId ? focusById.get(selectedFocusId) ?? null : focusRecords[0] ?? null;
const ganttCellHeight = 38;
const ganttScaleHeight = 54;
const ganttHeight = Math.max(520, tasks.length * ganttCellHeight + ganttScaleHeight);
const heatmap = useMemo(() => { const heatmap = useMemo(() => {
const start = summary ? startOfDay(new Date(summary.horizonStart)) : startOfDay(new Date()); const start = summary ? startOfDay(new Date(summary.horizonStart)) : startOfDay(new Date());
@@ -181,7 +172,6 @@ export function GanttPage() {
const modeOptions: Array<{ value: WorkbenchMode; label: string; detail: string }> = [ const modeOptions: Array<{ value: WorkbenchMode; label: string; detail: string }> = [
{ value: "overview", label: "Overview", detail: "Dense planner board" }, { value: "overview", label: "Overview", detail: "Dense planner board" },
{ value: "gantt", label: "Timeline", detail: "Classic gantt lens" },
{ value: "heatmap", label: "Heatmap", detail: "Load by day" }, { value: "heatmap", label: "Heatmap", detail: "Load by day" },
{ value: "agenda", label: "Agenda", detail: "Upcoming due flow" }, { value: "agenda", label: "Agenda", detail: "Upcoming due flow" },
]; ];
@@ -267,30 +257,6 @@ export function GanttPage() {
<div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel"> <div className="rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel">
{workbenchMode === "overview" ? <OverviewBoard focusRecords={focusRecords} onSelect={setSelectedFocusId} /> : null} {workbenchMode === "overview" ? <OverviewBoard focusRecords={focusRecords} onSelect={setSelectedFocusId} /> : null}
{workbenchMode === "gantt" ? (
<div className={`gantt-theme overflow-x-auto overflow-y-visible rounded-[18px] border border-line/70 bg-page/60 p-4 ${mode === "dark" ? "wx-willow-dark-theme" : "wx-willow-theme"}`}>
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Schedule Window</p>
<p className="mt-2 text-sm text-muted">{summary ? `${formatDate(summary.horizonStart)} through ${formatDate(summary.horizonEnd)}` : "Waiting for live schedule data."}</p>
</div>
<div className="rounded-2xl border border-line/70 bg-surface/80 px-3 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-muted">{tasks.length} schedule rows</div>
</div>
<div style={{ height: `${ganttHeight}px`, minWidth: "100%" }}>
<Gantt
tasks={tasks.map((task: GanttTaskDto) => ({
...task,
start: new Date(task.start),
end: new Date(task.end),
parent: task.parentId ?? undefined,
}))}
links={links}
cellHeight={ganttCellHeight}
scaleHeight={ganttScaleHeight}
/>
</div>
</div>
) : null}
{workbenchMode === "heatmap" ? <HeatmapBoard heatmap={heatmap} selectedDate={selectedHeatmapDate} onSelectDate={setSelectedHeatmapDate} /> : null} {workbenchMode === "heatmap" ? <HeatmapBoard heatmap={heatmap} selectedDate={selectedHeatmapDate} onSelectDate={setSelectedHeatmapDate} /> : null}
{workbenchMode === "agenda" ? <AgendaBoard records={agendaItems} onSelect={setSelectedFocusId} /> : null} {workbenchMode === "agenda" ? <AgendaBoard records={agendaItems} onSelect={setSelectedFocusId} /> : null}
</div> </div>
@@ -312,7 +278,6 @@ export function GanttPage() {
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{selectedFocus.detailHref ? <Link to={selectedFocus.detailHref} className="rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white">Open record</Link> : null} {selectedFocus.detailHref ? <Link to={selectedFocus.detailHref} className="rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white">Open record</Link> : null}
<button type="button" onClick={() => setWorkbenchMode("gantt")} className="rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">View in timeline</button>
<button type="button" onClick={() => setWorkbenchMode("heatmap")} className="rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">View load</button> <button type="button" onClick={() => setWorkbenchMode("heatmap")} className="rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">View load</button>
</div> </div>
</div> </div>

View File

@@ -17,7 +17,7 @@ const permissionDescriptions: Record<PermissionKey, string> = {
[permissions.manufacturingWrite]: "Manage manufacturing work orders and execution data", [permissions.manufacturingWrite]: "Manage manufacturing work orders and execution data",
[permissions.filesRead]: "View attached files", [permissions.filesRead]: "View attached files",
[permissions.filesWrite]: "Upload and manage attached files", [permissions.filesWrite]: "Upload and manage attached files",
[permissions.ganttRead]: "View gantt timelines", [permissions.ganttRead]: "View planning workbench",
[permissions.salesRead]: "View sales data", [permissions.salesRead]: "View sales data",
[permissions.salesWrite]: "Manage quotes and sales orders", [permissions.salesWrite]: "Manage quotes and sales orders",
[permissions.projectsRead]: "View projects and program records", [permissions.projectsRead]: "View projects and program records",