Przejdź do głównej zawartości

Wyszukiwanie jest dostępne tylko w buildach produkcyjnych. Spróbuj zbudować i uruchomić aplikację, aby przetestować lokalnie.

Przegląd platformy

Platforma DealerCRM to ekosystem aplikacji dla dealerów i importerów samochodowych. Centralnym elementem jest DealerID — Identity Provider (OAuth2 / OIDC), z którego wszystkie pozostałe aplikacje korzystają jako jedynego źródła tożsamości.

AppStatusRepoRola
DealerID✅ żyjeapps/dealerid (Symfony PHP)IDP. Tożsamości, OAuth2 server, ekran zgody, dashboard /account/apps. Nic poza tym.
DealerCRM✅ żyjeapps/crm-api + apps/crm-frontendCRM dla dealerów. Wystawiany jako N Applications (gezet.localhost, gezet-renault.localhost, staff.localhost …) — każda z własnym brandingiem i filtrem danych. Pierwsza apka — referencja integracji.
DealerBMS🟡 planowanyapps/bms-* (TBD)Backoffice management (procesy serwisowe / magazynowe). Będzie konsumować DealerID + model application.
DealerService🟡 planowanyapps/service-* (TBD)Aplikacja dla serwisów (przyjęcia, naprawy, fakturowanie).
DealerAnalytics🟡 planowanyapps/analytics-* (TBD)Hurtownia / BI / raportowanie cross-app.
Portal Klienta🟡 planowanyapps/portal-* (TBD)Portal end-user dla klientów dealera (ich umowy, historia, kontakt z opiekunem).

Termin „Application” w CRM: to instancja aplikacji CRM osiągalna pod konkretnym hostemgezet.localhost (kind=dealer_main), gezet-renault.localhost (kind=importer_network), renault.localhost (kind=importer_main), staff.localhost (kind=staff). Nie mylić z “aplikacją partnerską” / “klientem OAuth2” rejestrowanym w DealerID (oauth2_client) — patrz Rejestracja aplikacji i Multi-tenancy: Aplikacje.

Kluczowa zasada: każda kolejna aplikacja platformy musi używać DealerID jako jedynego dostawcy tożsamości. Bez własnych baz haseł, bez własnego SSO. To jest twarda decyzja architektoniczna i nie podlega negocjacjom — patrz Rejestracja aplikacji.

graph TB
    subgraph IDP["🪪 DealerID — IDP"]
        DID_AUTH["/authorize<br/>/token<br/>/userinfo"]
        DID_ACCOUNT["/account/apps<br/>(dashboard usera)"]
        DID_DB[("app_user<br/>oauth2_client<br/>oauth2_access_token")]
    end

    subgraph CRM["📋 DealerCRM Applications (referencyjna apka)"]
        CRM_BFF["crm-frontend<br/>(Next.js BFF + UI)<br/>host → Application"]
        CRM_API["crm-api<br/>(Symfony, modular monolith)<br/>resolve Application z Host header"]
        CRM_DB[("iam_user, organization,<br/>application (host → org, kind,<br/>importer_id), lead, ...")]
        APP_INSTANCES["Application instances:<br/>• gezet.localhost (dealer_main)<br/>• gezet-renault.localhost (importer_network)<br/>• renault.localhost (importer_main)<br/>• staff.localhost (staff)"]
    end

    subgraph FUTURE["🟡 Planowane apki"]
        BMS["DealerBMS"]
        SVC["DealerService"]
        ANALYTICS["DealerAnalytics"]
        PORTAL["Portal Klienta"]
    end

    USER([👤 User<br/>pracownik dealera<br/>lub staff Grupy Dealer])

    USER -->|"1. login<br/>(form / Google / Microsoft)"| DID_AUTH
    USER -->|"2. wybiera Application"| DID_ACCOUNT
    USER -->|"3. /authorize → callback<br/>na host wybranej Application"| CRM_BFF
    CRM_BFF -->|"4. code → token"| DID_AUTH
    CRM_BFF -->|"5. /api/* (Bearer)<br/>+ forward Host header"| CRM_API
    CRM_API -->|"6. JWT verify (RS256)<br/>+ /userinfo"| DID_AUTH
    CRM_API --> APP_INSTANCES
    APP_INSTANCES --> CRM_DB
    DID_AUTH --> DID_DB

    DID_ACCOUNT -.->|"server-to-server<br/>(scoped JWT, app.directory.read)"| CRM_API
    DID_ACCOUNT -.-> BMS
    DID_ACCOUNT -.-> SVC
    DID_ACCOUNT -.-> ANALYTICS
    DID_ACCOUNT -.-> PORTAL

    BMS -.-> DID_AUTH
    SVC -.-> DID_AUTH
    ANALYTICS -.-> DID_AUTH
    PORTAL -.-> DID_AUTH

    classDef done fill:#dcfce7,stroke:#16a34a,color:#14532d;
    classDef wip fill:#fef9c3,stroke:#ca8a04,color:#713f12;
    class IDP,CRM done;
    class FUTURE,BMS,SVC,ANALYTICS,PORTAL wip;
flowchart LR
    H1["gezet.localhost"] --> A1["Application<br/>kind=dealer_main"]
    H2["gezet-renault.localhost"] --> A2["Application<br/>kind=importer_network<br/>importer_id=Renault"]
    H3["renault.localhost"] --> A3["Application<br/>kind=importer_main"]
    H4["staff.localhost"] --> A4["Application<br/>kind=staff"]

    A1 -->|organization_id| O1["Gezet (dealer)"]
    A2 -->|organization_id| O1
    A2 -.->|importer_id<br/>(branding source)| O2["Renault Polska (importer)"]
    A3 -->|organization_id| O2
    A4 -->|organization_id| O3["DealerCRM Platform Org"]

    O1 -->|branding_skin_slug| S1["default"]
    O2 -->|branding_skin_slug| S2["renault"]
    O3 -->|branding_skin_slug| S3["staff"]
  • Serwer OAuth2 (league/oauth2-server-bundle) — endpointy /authorize, /token, /userinfo.
  • Logowanie: form_login z hasłem (argon2id), SSO Google Workspace, SSO Microsoft Entra ID.
  • Ekran zgody — pokazywany dla third-party klientów. First-party klienci (flaga oauth2_client.is_first_party) mają auto-approve.
  • Auto-provisioning internal staff — gdy domena emaila pasuje do app.sso.internal_domains (grupadealer.pl), Google SSO tworzy konto z is_internal_staff = TRUE. Patrz SSO Google + auto-provisioning.
  • Dashboard /account/apps — agreguje karty aplikacji wołając każdy oauth2_client.app_directory_url server-to-server.
  • Implementacja OAuth2 client w swojej apce (PKCE obowiązkowe). Patrz Flow OAuth2.
  • Walidacja Bearer JWT w swoim API (RS256, publiczny klucz DealerID).
  • Endpoint /api/dealerid/user-apps (opcjonalnie — jeśli chcesz, by user widział twoją apkę na dashboardzie DealerID). Patrz App Directory — implementacja.
  • Tabela membership w twojej apce — nie polegaj na DealerID, że user „należy do twojej apki”. DealerID trzyma tylko tożsamości. Czy ktoś ma dostęp do BMS — to decyzja BMS.
  • DealerID nie ma wiedzy o tym, kto jest userem w CRM/BMS. Pyta tylko: „kto to jest?” (email + name + is_internal_staff).
  • Każda apka platformy ma własną tabelę membership. Bycie zarejestrowanym w DealerID ≠ dostęp do CRM.
  • Każda apka musi sama provisionować swojego usera przy pierwszym loginie (lazy provision na podstawie emaila z claimu sub).
  • Pracownicy Grupy Dealer logują się wyłącznie przez Google Workspace. Hasło + Microsoft są zablokowane dla is_internal_staff.