End-to-end runnable skeleton for the OLIVER Sales Operations Platform — the V2 of the Scope Builder, broadened to a complete RFP-to-mobilization pipeline (intake → qualification → Q&A → match → ratecard → delivery model → efficiency → team shape → caveats → approval gates → pitch → post-win planning → downstream handoff). Backend (FastAPI + async SQLAlchemy + Alembic): - Models: app_users (with workflow_roles for approver routing), GMAL catalog ported from V1 (gmal_assets / roles / gmal_hours / service lines / role-level mappings), and the new state machine (opportunities, stage_states, stage_artifacts, approvals). - Initial Alembic migration creates 11 tables and 5 enum types using the postgresql.ENUM(create_type=False) pattern so the types aren't double-created when referenced from multiple columns. - Claude client defaults to claude-opus-4-7 with cost tracking + debug log; Azure SSO middleware ported as-is from V1. - Public /api/health round-trips a SELECT 1 to verify the DB is reachable. Frontend (React 18 + Vite + TanStack Query + MSAL + Mermaid): - Home page renders the canonical 17-stage flowchart (Mermaid) plus an enumerated stage card grid with the two approval gates highlighted. - React Router uses /osop basename to mirror the V1 /gsb/ deploy pattern; axios client targets /osop/api with MSAL token interceptor. Compose: - name: oliver-sales-ops-platform (so the project doesn't collide with the deploy-folder default per shared-server policy in CLAUDE.md). - Ports 5435 / 6380 / 8003 / 3011 to coexist with V1 on the same host. - Source mounts on backend (app/, alembic/, alembic.ini) and frontend (src/, configs) so dev iteration doesn't require rebuilds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
38 lines
1.5 KiB
Python
38 lines
1.5 KiB
Python
"""App users + role-based access control.
|
|
|
|
V2 extends the V1 viewer/editor/admin set with workflow-specific approver
|
|
roles needed by the 17-stage state machine (commercial, delivery, solution,
|
|
regional, deal_desk). Users can hold multiple workflow roles via the
|
|
`workflow_roles` text column (CSV) without changing their primary access role.
|
|
"""
|
|
|
|
import enum
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import String, DateTime, Enum, Text
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.database import Base
|
|
|
|
|
|
class UserRole(str, enum.Enum):
|
|
VIEWER = "viewer" # Read-only access to opportunities + exports
|
|
EDITOR = "editor" # Create/edit opportunities, run agents, build artifacts
|
|
ADMIN = "admin" # Full access: GMAL editor, user management, ingest, all approvals
|
|
|
|
|
|
class AppUser(Base):
|
|
__tablename__ = "app_users"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
|
|
name: Mapped[str | None] = mapped_column(String(255))
|
|
role: Mapped[UserRole] = mapped_column(Enum(UserRole), default=UserRole.EDITOR)
|
|
azure_oid: Mapped[str | None] = mapped_column(String(100))
|
|
|
|
# CSV of workflow approver roles this user can sign off on.
|
|
# Values: commercial | delivery | solution | regional | deal_desk
|
|
workflow_roles: Mapped[str | None] = mapped_column(Text)
|
|
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
last_login: Mapped[datetime | None] = mapped_column(DateTime)
|