Documentation · Phase 5e

Revit CDE — technical documentation

Plugin for Autodesk Revit with approval system (CDE). Architecture, implementation, and remaining work. The document describes the technical side of the solution: solution structure, implementation phases, architectural decisions and plan for further work.

Current phase · Phase 5e · May 2026
01 — Overview

01System overview.

What it is

Revit CDE — plugin for Autodesk Revit, implementingCommon Data Environment (CDE)for the model approval process and project documentation. CDE principle: the model and its parts (views, sheets, schedules, and sheet sets) go through a formalized approval workflow with composition snapshots captured, an audit trail for every decision and a visible status directly in the Revit model.

Architecturally, the system consists of two major parts:

  • Revit-plugin— an extension for Revit (multi-target 2021–2027), including WPF-interface, composition scanners, a status system via Shared Parameters + Extensible Storage and an HTTP client for the server.
  • ASP. NET Core server— backend on. NET 8 with PostgreSQL and Entity Framework Core, JWT authorization and REST API. Implements the lifecycle of approval requests, maintains an event log, aggregates model-element statuses.

Target audience

  • Authors(BIM modelers and designers) — create models and submit elements for approval.
  • Approvers(GIP, GAP, client, reviewers) — make decisions based on submitted composition snapshots.
  • Project managers— manage participants and approval routes.
  • BIM coordinators and administrators— configure categories, protected parameters, and templates.

key implementation principles

  • Composite key<ProjectGuid, ElementUniqueId> — all Revit elements are identified by the pair «server project ID + UniqueId element». ProjectGuid is stored in Extensible Storage of the document.
  • Hybrid status storage— Shared Parameters (visible to users and exports) + Extensible Storage (the source of truth for the IUpdater, protection from external edits).
  • Imprint snapshotis created when the item is submitted for approval — captures the exact composition on moment solution, so later model changes clearly show what was approved and what was not.
  • Append-only log— all events (submission, decisions, withdrawal) are written to a non-deletable logApprovalEventfor audit.
  • Idempotent statuses— reapplying the same status is safe. This allows background syncu lose and transfer state without pain.
  • license toggle— MSBuild property License(default true), when-p:License=falsedefines the constantNO_LICENSEfor future commercial builds.
02 — Structure

02Project structure.

solution and projects

solutionRevitCDE.slncontains six projects:

ProjectTypeTargetFrameworkPurpose
RevitCDE.CoreC# librarynet48 / net7-net10-windowsCommon models, server API client, ES-skhemy of the document, identity, settings
RevitCDE.UIWPF librarynet48 / net7-net10-windowsWPF dialogs, MVVM, styles, controls
RevitCDE.LocalizationC# librarynet48 / net7-net10-windowsLocalization (resources)
RevitCDE.Domainlibrarynetstandard2.0POCO models domain area CDE:Approval*, Status enums,ImprintItem
RevitCDE.ServerASP.NET Corenet8.0Backend: API, EF Core, JWT, BCrypt, database migrations
RevitCDE.AddinRevit pluginnet48 / net7-net10-windowsIExternalApplication, ribbon commands,IUpdater'y, scanners, orchestration

Build configurations

The build is performed through MSBuild with per-Revit-version configurations:Debug_2021Debug_2027andRelease_2021Release_2027(plus standardDebug|x64andRelease|x64for server builds).

  • Domainis built in all configurations (it targets netstandard, and is not tied to a Revit version).
  • Serveris built only in standardDebug|x64 / Release|x64. The Server is not built under Revit configurations in the.sln — on it targets net8.0, there is nothing for it to do under Revit targets.
  • Addin / Core / UI / Localizationare built for the selected Revit version.

Scriptsbuild-2027.batandbuild-all.batbuild only the client part (Addin); The Server is launched separately viacd SRC\RevitCDE.Server && dotno run.

Project dependencies

Addin → Core, UI, Localization, Domain (via Core) UI → Core, Localization, Domain (via Core) Core → Domain, Localization Server → Domain (only!) Domain → (nothing, only Newtonsoft. Json)

key separation: the server does NOT physically reference Core. This enforces client/server isolation: types from Core (with Revit-dependencies, identity, ES-binders) are not needed and are unavailable on the server. Common types moved to Domain (netstandard, without dependencies except Newtonsoft. Json).

Namespace Domain-classes is kept asRevitCDE.Core.Models.Approvalintentionally — so moving types from Core to Domain does not require changing all call sites.

File layout

SRC/ ├── RevitCDE. Core/ │ ├── Models/ — ApprovalRequest (legacy chat-side), AppSettings, ServerSettings,... │ ├── Identity/ — UserProfile (local identity.json) │ └── Server/ — ApiContracts (DTO), RevitCdeApiClient, TokenSession, │ DocumentProjectBinder, DocumentApprovalSyncState ├── RevitCDE. UI/ │ ├── Controls/ — stili, custom controls │ ├── Dialogs/ — WPF-okna (login, pikery) │ │ └── Server/ — Login, ProjectPicker, SubmitApproval, RouteConfiguration │ ├── Mvvm/ — ViewModel'y │ └── Windows/ — main plugin floating windows ├── RevitCDE. Localization/ — resources lokalizatsii ├── RevitCDE. Domain/ │ └── Models/Approval/ — POCO domain area: │ ApprovalRequest, ApprovalRequestStage, ApprovalRouteMode, │ ApprovalEvent/EventType, ProjectElement(Status)/Kind, │ ModelElement(Status), Snapshot/Item, Imprint*, │ ProtectedParameterRule, ElementFingerprint ├── RevitCDE. Server/ │ ├── Auth/ — JWT, BCrypt, ClaimsPrincipal extensions │ ├── Bootstrap/ — BootstrapAdminHostedService (sozdayot admin when starte) │ ├── Controllers/ — REST endpoints │ ├── Data/ — DbContext │ ├── Dtos/ — servernye DTO │ └── Entities/ — User, Project, ProjectMembership, UserRole └── RevitCDE. Addin/ ├── Commands/ — IExternalCommand: SubmitApproval, ServerLogin, ServerRefresh,... ├── Scanning/ — ImprintScanner, ControlledCategoryCatalog ├── Server/ — ServerCoordinator, SubmitApprovalWorkflow, ApprovalSyncService, │ ApplyServerStatusHandler ├── Status/ — StatusWriter, ElementStatusMirror, StatusGuardUpdater, │ StatusBindingService, SharedParamFileService, ProjectElementDirtyStore └── RevitCDEApp.cs — IExternalApplication, ribbon, lifecycle
03 — Implementation

03What has been done: phases 1–5e.

Implementation shla poetapno (commit'ami 1, 2, 3, 4a, 4b, 4c, 4d-1, 4d-2, 5a, 5b, 5c-1, 5c-2, 5d, 5e). Nizhe — detalnoe description kazhdogo sloya.

Phase 1

Rasterizator and fundament models

Goal: prepare infrastructure for later phases.

What has been done

  • RevitContentPickerDialog — WPF-content selection dialog in Revit-modeli for tests and scripts.
  • Rasterizatsiya: view rendering to bitmap for preview images.
  • UserProfilereceived the fieldPosition(user position for signatures in the log).
  • Created the foundation Approval-models in Domain: enums statusov and basic POCO classes.

Arkhitekturnoe solution

  • Composite key<ProjectGuid, ElementUniqueId>for element identification. ProjectGuid is stored in Extensible Storage of the document Revit (separate schemeDocumentProjectBindingwith GUIDCDE40001-0001-...).
  • Dolzhnost (Position) sokhranyaetsya as snapshot kopiey in each log event, chtoby when pereimenovanii / uvolnenii userya history ostavalas chitaemoy.
Phase 2

Skaner composition (ImprintScanner)

Goal: collect a stable imprint (CompositeHash) composition vida, lista, spetsifikatsii or podshivki.

What has been done

  • ImprintScanner~220 strok — konstruktornew ImprintScanner(doc, settings), metodScanProjectElement(elementId) → ImprintResult.
  • FilteredElementCollector(doc, viewId)sobiraet vidimye on proektnom elemente instances of controlled categories.
  • ControlledCategoryCatalog — centralized listBuiltInCategory, which podlezhat soglasovaniyu (walls, doors, windows, structures, MEP,...).
  • ImprintItem = UID + CategoryId + ProtectedHash(hash zaschischyonnykh parametrov element).
  • CompositeHash = SHA256(sorted UID|ProtectedHash) — deterministic imprint of the entire composition.

Arkhitekturnoe solution

  • Stable hash: when sortirovke UID khesh ne zavisit ot poryadka dobavleniya elementov in Revit. Reordering elements does not change CompositeHash.
  • ProtectedHashincludes only parameters fromProtectedParameterRule(width, height, material type and t.p.) — izmenenie tsveta or kommentariya ne schitaetsya «a composition change».
  • CategoryOST_StructuralRebarandOST_SpaceTagsare excluded — oni ne suschestvuyut in Revit 2027 (Autodesk moved them). Compatibility with Revit 2021–2027 is maintained through#if REVIT2021||REVIT2022direktivy.
Phase 3

Statuses in Revit

Goal: pokazyvat useryu in Revit aktualnyy status soglasovaniya kazhdogo element.

What has been done

  • StatusParameters — five Shared Parameters:RevitCDE_Status, RevitCDE_ApprovedAt, RevitCDE_ApprovedBy, RevitCDE_ApproverPosition, RevitCDE_ServerElementId.
  • SharedParamFileService — management faylom shared parameters (sozdayot when otsutstvii, migriruet when obnovlenii).
  • StatusBindingService — binding parametrov k kategoriyam modeli (BindingMap).
  • ElementStatusMirror — Extensible Storage scheme (GUIDCDE20002-0001-...) with tenevoy kopiey status. Kanon forIUpdater'a; zaschischyon ot poddelki.
  • StatusGuardUpdater (IUpdater) — sledit za izmeneniyami parametrov and rolls back «ruchnye» pravkiRevitCDE_*-parametrov (if kto-to ikh menyaet vne CDE-flow).
  • StatusWriter — unified tochka zapisi:SetStatus(el, status, approvedAtUtc, approvedByDisplayName, approverPosition, serverElementId), SetStatusOnly, Clear. Idempotenten.
  • ProjectElementDirtyStore — Extensible Storage (GUID CDE20002-0001-...,002) for flaga «gryaznyy» element proekta.
  • ModelElementStatusLabels — dvustoronniy mappingenum ↔ stroka(en + ru lokali).

Arkhitekturnoe solution

  • Gibrid Shared Params + Extensible Storage.Shared params vidny useryu in Properties-paneli, popadayut in schedule, eksportiruyutsya. Extensible Storage — closeoe khranilische pod zaschitoyIUpdater'a, ne redaktiruetsya vruchnuyu. When raskhozhdenii kanon — ES.
  • BindingMapfor raznykh Revit-versiy:#if REVIT2021||REVIT2022ispolzuetBuiltInParameterGroup.PG_IDENTITY_DATA, for 2023+ —GroupTypeId.IdentityData(novyy ForgeTypeId API).
  • «Gryaznyy» — otdelnyy bool flag, ne status. State «izmenyon after soglasovaniya» = (Approved AND Dirty=true), this kompozitsiya, a ne alternativnyy status. Yavnoe khranenie allows legko podsvetit «trebuet perepodachi».
Phase 4

Server

server backend podnyat in tri commit:4a (skeleton), 4b (auth + projects), 4c(CDE dannye).

Phase 4a — Domain + skeleton server

  • RevitCDE.Domainvydelen as otdelnyynostandard2.0proekt with 16 POCO-faylami. Zavisimosti — only Newtonsoft.Json 13.0.3.
  • RevitCDE.Serversozdan on ASP.NET Core 8.0.10. Slushaet port 5180. Health endpoint/health/live.
  • PostgreSQL via Npgsql 8.0.10. ConnectionString inappsettings.json.
  • EF Core 8.0.10 + Npgsql.EFCore — DbContext RevitCdeDbContext.

Phase 4b — Auth + projects

  • BCrypt. Net-Next 4.0.3 for paroley.
  • Microsoft. AspNetCore. Authentication. JwtBearer 8.0.10 + System. IdentityModel. Tokens. Jwt 8.0.2 for JWT.
  • BootstrapAdminHostedService — when pervom starte server sozdayot polzovatelya withIsGlobalAdmin=true. Kredy dev:admin / admin12345(fromBootstrapSettings).
  • UserRole enum (Viewer=0, Author=1, Approver=2, Manager=3, Admin=4) andProjectMembershipentity with unikalnym indeksom(UserId, ProjectId).
  • Per-project authorization:ProjectScopedControllerBasedayotRequireReadAccessAsync, RequireWriteAccessAsync, RequireManageAccessAsync.
  • Endpoints: /api/auth/login, /api/users (CRUD), /api/projects (CRUD), /api/projects/{id}/members.

Phase 4c — CDE-dannye

  • ProjectElement / ModelElement / ProjectElementSnapshot+Items / ApprovalEvent — all POCO in Domain, EF-konfiguratsiya in DbContext.
  • REST: project-elements (list, get, bulk upsert), model-elements (to zhe, bulk until 5000), snapshots (list by PE, create with rezolvom UID→ServerId), events (append-only, list with filtrami).
  • If insnapshot.itemsest UniqueId, ne zaregistrirovannyy viaPOST /model-elements, server returns 400 withCreateSnapshotProblemResponseand spiskom unresolved. Klient upsert'it nedostayuschie and povtoryaet request.

Phase 4d — Klient servernogo API

  • RevitCdeApiClient~330 strok — HTTP-client with JWT-inzhektsiey, obrabotkoy 401 (chistit TokenSession), kastomnymApiClientException.
  • TokenSession — in-memory token, FIO, rol. Ne persistitsya mezhdu zapuskami plugin (namerenno — polzovatel loginitsya zanovo).
  • DocumentProjectBinder — Extensible Storage scheme (GUIDCDE40001-0001-...) for svyazi Revit-of the document with servernym proektom.
  • ServerCoordinator — orkestrator Login → ProjectPicker → Bind. Knopki ribbona «Privyazat k proektu» and «Vyyti».
  • WPF dialogsServerLoginDialog, ServerProjectPickerDialog. Owner ustanavlivaetsya viaWindowInteropHelper(dlg).Owner = uiapp.MainWindowHandlefor korrektnogo z-order poverkh Revit.
Phase 5

Workflow soglasovaniy

This samaya bolshaya faza, razbita on five kommitov (5a→5e) and sostavlyaet osnovu CDE-funktsionalnosti.

Phase 5a — Servernaya infrastruktura zayavok

  • ApprovalRequest entity + ApprovalRequestStatus enum (Pending/Approved/ApprovedWithComments/Rejected/Cancelled). 5 endpoint'ov: List, GetById, Create, Decide, Cancel.
  • Atomarnaya podacha: sozdanie snapshot + items + ApprovalRequest + ApprovalEvent (ProjectElementSubmitted) + perevodPE.StatusinOnApproval — vsyo in one tranzaktsii BD.
  • On oneProjectElementodnovremenno mozhet byt only onePending-zayavka (409 Conflict when popytke sozdat vtoruyu).
  • When finale with Approved/AwC:PE.LastFingerprintServerId = SnapshotServerId, ApprovedAt = now, IsDirty = false. WhenRejectedthese polya ne trogayutsya (mogli ukazyvat on predyduschee uspeshnoe soglasovanie).

Phase 5b — Klientskaya podacha

  • SubmitApprovalWorkflow — orkestrator: scan → upsert PE → upsert ME (chanki by 5000) → POST /approval-requests.
  • Auto-retry onSnapshotCreationFailedException: if mezhdu nashim upsert'om and sozdaniem zayavki kto-to udalil element — again registriruem nedostayuschie and probuem rovno one raz.
  • SubmitApprovalDialog(WPF) — dialog podachi with russkoy plyuralizatsiey («1 element / 2-4 element / 5+ elementov»).
  • SubmitApprovalCommandIExternalCommandon ribbone «Podat on soglasovanie». Aktivnyy list/vid/speka →TryDetermineKindby tipuView/ViewSheet/ViewSchedule.
  • UI-project ne zavisit ot Addin:SubmitFunc-delegat peredayotsya as callback from Command → Dialog.

Phase 5c — Fonovaya synchronizatsiya resheniy

Razbita on 5c-1 (infrastruktura) and 5c-2 (polirovka).

  • ApplyServerStatusHandler(IExternalEventHandler) — ochered job'ov and primenenie in one Revit-tranzaktsii via StatusWriter. Cross-document protection: checkjob.ProjectId == binding.ProjectId.
  • ApprovalSyncService— DispatcherTimer 30 sek on UI-thread'e. Async-metody dyorgayutawait(UI otzyvchiv). Reentrant-flag zaschischaet ot nalozheniya tikov. On each tik:ListEvents (since=lastSyncAt)→ filtr finalnykh tipov →GetApprovalRequest + GetSnapshotfor kazhdoy → enqueue +ExternalEvent.Raise().
  • MappingApprovalRequestStatus → ModelElementStatus: Approved → Approved, ApprovedWithComments → AwC, Rejected/Cancelled → nothing ne pishetsya(mogli byt soglasovany by drugomu listu — lomat nelzya).
  • DocumentApprovalSyncState — ES-scheme (GUIDCDE40002-0001-...) for persistentlastSyncAt. Per-document state, chitaetsya on smene of the document. After restarta Revit opros ne lezet za 24h nazad vpustuyu.
  • TaskDialog so svodkoy resheniy after Apply: «Polucheny rezultaty soglasovaniya: • Soglasovano: 3, • Soglasovano with zamechaniyami: 1. Obnovleno elementov modeli: 47».
  • Button lenty «Refresh statusy» (ServerRefreshSyncCommand) — ruchnoy trigger vne 30-sekundnogo tika.
  • TriggerNow()callssya after uspeshnogo login and after submit for nemedlennoy obratnoy svyazi in testirovanii.

Phase 5d — Mnogostadiynye routes Sequential + ME-agregatsiya

  • ApprovalRequestStage entity: ServerId, ApprovalRequestId (FK), StageIndex (0..N-1), ApproverUserId. Snapshot FIO/dolzhnosti naznachennogo. Status:Pending → Approved/AwC/Rejected. Decision-polya zapolnyayutsya when zakrytii stadii.
  • ApprovalRequestpoluchil polyaHasStages (bool), CurrentStageIndex (int, -1 for legacy), Stages (List). Cascade-delete ot zayavki k stadiyam.
  • Unikalnyy indeks(ApprovalRequestId, StageIndex)and indeks(ApproverUserId, Status)for bystrogo pending-for-me zaprosa.
  • Endpoint GET /pending-for-me — zayavki gde ya approver on tekuschey active stadii.
  • Decide rasshiren: for multi-stage onlycurrentStage.ApproverUserIdor GlobalAdmin mozhet decide. Reject on lyuboy stadii → final Rejected. Approved/AwC on ne-afterdney →CurrentStageIndex++, sobytieStageApproved. On afterdney →ComputeAggregateFinalStatus(AwC if khot one stadiya with zamechaniyami, inache Approved).
  • ME-agregatsiya— when finale with Approved/AwC server obnovlyaetModelElement.Statusfor vsekh ME from snapshot'a by pravilu: AwC dominiruet (if khot one storona AwC, itog AwC, inache incoming). UserId soglasuyuschego dobavlyaetsya inApproverUserIds(without dubley).
  • Validatsiya stages when sozdanii: uniqueapproverUserId, all Approver+ project members (or GlobalAdmin).

Phase 5e — Parallelnye routes + UI piker

  • ApprovalRouteMode enum: Sequential (default) / Parallel.
  • Parallel mode: all stages are active at the same time.CurrentStageIndexostayotsya-1. each assigned approver makes a decision independently. Any rejection — immediately finalizes the request as Rejected. When all are Approved/AwC — aggregate final status.
  • Decide vetvlenie: for Parallel poisk stadii = lyubaya Pending gde approver = actor (or lyubaya for GlobalAdmin). Finalizatsiya Parallel = «all stadii ne-Pending».
  • PendingForMe obnovlyon: edinyy predicateparallel || stage.StageIndex == request.CurrentStageIndex.
  • RouteConfigurationDialog(novyy WPF-dialog) — dvukhkolonochnyy piker «Available project approvers» ↔ «Route (ordered)». Radio Sequential/Parallel. In Parallel-mode Up/Down buttons are disabled (order does not matter).
  • Filtering Approver+: only te project members, u kogo rol Approver, Manager or Admin (Viewer and Author will not appear).
  • SubmitApprovalDialogpoluchil button «Configure route…» and current configuration summary. DelegatSubmitFuncstal(comment, route, ct), novyyMembersLoaderFuncfor pikera.
  • Restoring missing entities: during session rebuild it was discovered thatProject.cs, ProjectMembership.csandUserRoleenum were physically missing from the file system (only referenced), because the Server project was not built viabuild-2027.bat. Restored — nowdotno runinRevitCDE.Serveractually starts.
04 — Architecture

04Architectural decisions and rationale.

4.1Why Domain is a separate project

Until Phase 4a all Approval-klassy were located inRevitCDE.Core. When the server was introduced it targets net8.0, the problem: Core references WPF, identity, ES-binders with Revit-dependencies. The server cannot and should not import all of that.

solution: extractnostandard2.0Domain project without dependencies except Newtonsoft. Json. In nego only moved there POCO-klassy (ApprovalEvent, ApprovalRequest, ProjectElement, ModelElement, Snapshot/Items, Imprint*, Statusenums). Core references Domain via ProjectReference, Server tozhe.

Namespace Domain-classes is kept asRevitCDE.Core.Models.Approvalintentionally — all callsite'y in kode (using RevitCDE.Core.Models.Approval) continue to work without changes.

4.2Why PostgreSQL

  • Free and open-source, excellent support for Guid (uuidtip).
  • Native array support (uuid[], int[] — used forModelElement.ApproverUserIds).
  • JSON field support (JSONB) — will be useful in Phase 7 for flexible payloads'ov sobytiy.
  • Good performance EF Core via Npgsql. EntityFrameworkCore. PostgreSQL.
  • A standard choice for server applications on. NET Core, simpler than configuring SQL Server in Linux.

4.3Why snapshot sozdayotsya in moment podachi, a ne in moment solution

Alternative design: the approver looks at «current» view composition in the moment they click «Approve». Rejected because:

  • Mezhdu podachey and resheniem days may pass — the composition may change without the approver knowing.
  • Soglasuyuschiy must review and decide based on a specific fixed state — otherwise formal approval has no meaning.
  • CompositeHashsnimka can be compared with the currentCompositeHashvida and automatically detect «changed after submission».

4.4Why hybrid Shared Parameters + Extensible Storage

Shared Params without ES — unsafe: a user can change through the UIRevitCDE_Statusu soglasovannoy steny. Adding IUpdater does not help: Revit calls Updater only whenOnDocumentChanged, and the user sees the change immediately.

Only ES without Shared Params — invisible: the user will not see the status in Properties-paneli and cannot add a column «Status» in spetsifikatsiyu.

Gibrid: Shared Params are visible and work normally. ES — closed shadow storage, in kotoroe pishet Only StatusWriter in ramkakh authorized transactions.StatusGuardUpdatercompares Shared Params with ES and on mismatch (= manual edit) rolls back Shared Params on znachenie ES, and writes an eventStatusTamperAttemptin zhurnal.

4.5Why DispatcherTimer instead of a background task

Background-task (Task.Run) on ThreadPool — has no access to the Revit API. Revit API can only be called on the UI thread'e, and only in ramkakh tranzaktsii, and only when Revit is idle.

DispatcherTimer runs on the UI thread'e prilozheniya — my automatically on correct thread'e.async/awaitfrees the thread during the HTTP request (the UI remains responsive), continuation returns back. After pollinging'a my Enqueue jobs andRaise()ExternalEvent — Revit will call our Execute when idle.

4.6Why ME aggregation on the server instead of the client

Phase 5c-1 performed mapping on the client: poluchilirequest.Status=Approved→ pishem vsem ME from snapshot'aModelElementStatus.Approved. Problem:

  • For ME, which approved in several snapshots'akh (on raznykh listakh) — needed agregatsiya. the client does not know the history of other approvals.
  • If only chto odobrili snapshot A, a snapshot B (with etim zhe ME) was previously approved with comments — finalME.Statusmust be AwC (the worst status dominates). This requires knowledge of the past.
  • clients of different users may receive events in different order — without server aggregation they may arrive at different results.

Phase 5d moved aggregation to the server: when finale zayavki with Approved/AwC the server iterates through all ME snapshot'a and applies the aggregation rule (AwC dominiruet). the client then simply reads finalME.Status.

4.7Why Sequential — default, Parallel — opt-in

  • Sequential — this «normal» workflow soglasovaniya: lid → GIP → client.
  • Parallel useful kogda nuzhno sobrat odobrenie several independent parties (naprimer MEP + konstruktiv + OV — ni one ne dolzhen zhdat drugogo).
  • Default Sequential = compatibility with Phase 5d (old requestsRouteMode=0= Sequential ostayutsya workt as and ranshe).

4.8Why one user ne mozhet byt on dvukh stadiyakh one zayavki

Validatsiya inValidateAndFetchStagesAsync. Rationale:

  • Sequential: «lid → lid → client» loses meaning (the user approves for themselves).
  • Parallel: the same approver on two stages = either a typo, libo an attempt to obtain «double approval» ot odnogo cheloveka (meaningless).
  • If potrebuetsya business scenario «one chelovek za dve roles» — we can remove validation, no this budet eskalatsiya / delegirovanie, which better modeled separately.

4.9Why cancel does not close stages

When cancel request stages remain inPending. UI otlichaet ikh byrequest.Status == Cancelled(«frozen»). Alternative — setCancelledon kazhduyu stadiyu — rejected, potomu chto:

  • Cancel = withdrawal by the initiator, not an approver decision. Stadii ne «otkazali» — oni «did not have time to review».
  • Semantically Pending-stadiya in Cancelled-zayavke = «had to be reviewed, no ne uspeli».
  • If the author submits a new request with temi zhe soglasuyuschimi — ikh stadii budut cleanPending.
05 — Roadmap

05What remains.

Phase 6 · next

Approver web portal

The most important next phase. Approvers (GIP, GAP, zakazchik) usually do NOT work in Revit — u nikh no license and no navyka. Currently oni are forced to make decisions via Swagger UI or via third-party integration.

  • Separate frontend: probably React + Vite + TypeScript (could be Blazor WebAssembly if khotim. NET-only stack).
  • Pages: login, my projects, dashbord (my pending requests), request card (detali + snapshot + decision buttons), history projecta, event log.
  • Extensions API: veroyatno needed endpoint for preview images snapshot'ov (render vida); extended filter on/events; bulk-decisions (Approve all pending).
  • SignalR-push for realtime-notifikatsiy (new request received → soglasuyuschiy sees a notification without refreshing).
  • Avtorizatsiya: tot zhe JWT chto u plugin. Login flow:/api/auth/login → token → store in localStorage / session-cookie.
  • In planakh takzhe: history zayavok odnogo PE (timeline vsekh stages vsekh zayavok).
Phase 7

CDE documents (workflow WIP / Shared / Published / Archived)

  • Soglasovanie on urovne dokumentov in CDE-smysle (section 4.5 plana). PDF/DWG as artefakty, transmittals, RFI, sections documentation.
  • DocumentSetentity, statusy WIP → Shared → Published → Archived with perekhodami.
  • Versionnost dokumentov and diff mezhdu versionsi.
  • Internal chat / kommentarii by of the documentm.
  • This a separate area, bound k BIM-razdelu ne silno — separate modulee with its own APIs/controllers.
Phase 8

Extensions

  • Linked models — support soglasovaniya via svyazannye modeli (RVT links). CurrentlyIsLinkeddokument is simply ignored.
  • BCF import/export — industrialnyy standart for issue communication between BIM tools.
  • Power BI integration — reporting and dashboards by soglasovaniyu.
  • Integratsiya with MS Teams / Outlook — notifications o novykh zayavkakh.
Backlog

Not included in Phase 5 — server side

  • server recalculation ME when retro-Reject. Currently if pozzhe vyyasnilos, chto the approval was wrong and zayavku rejected —ME.Statusuzhe Approved, is not recalculated.
  • ApproverUserIdson ModelElement rastyot without cleanup. For dolgo zhivuschikh proektov with tysyachami zayavok may grow too large. Needed TTL or periodic cleanup.
  • Unikalnost «one Pending on PE» is checked in kode viaAnyAsync, ne via partial unique index PostgreSQL. Under load two simultaneous submissions may slip through. Melkiy race; protection viaCREATE UNIQUE INDEX... WHERE status='Pending'dobavim in next commit.
  • Bulk endpoint /api/projects/{id}/approval-requests/by-idsfor umensheniya N+1 zaprosov inApprovalSyncService(seychas on kazhdoe finalnoe sobytie — 2 GET'a).
Backlog

Not included in Phase 5 — clientskaya storona

  • UI approver UI in the plugin — spisok pending-for-me + button approve/reject. Questionable feature: soglasuyuschie obychno workyut in brauzere, a ne in Revit. Skoree total realizuem only in Phase 6 web-kabinoe.
  • Podacha vsey podshivki tselikom — separate command «Podat podshivku», dialog with predprosmotrom kazhdogo lista.ImprintScannerpodshivki already supports.
  • Beydzh pending-zayavok in ribbone («u vas 3 ozhidayut»). Trebuet kastomnykh LargeImage-tekstur, deferred.
  • Persistent sokhranenie last used route in ES of the document — minor UX, not critical.
  • Progress indicator for bulk-upsert ME when large sheet sets (10000+ elementov require several round trips'ov).
Backlog

Mnogostadiynye routes

  • Route templates — preset «standartnyy marshrut» = zaranee predefined sequence soglasuyuschikh. Nuzhno for typical projects gde marshrut odinakovyy for vsekh listov.
  • Delegirovanie stadiy — Approver A peredayot svoyu stadiyu Approver'u B (vacation, illness). Trebuet separate endpoint and audit.
  • Eskalatsiya — if stadiya Pending bolshe N days, automatic manager notification.
  • Uslovnye routes (if-this-then-that) — naprimer «if est kriticheskie zamechaniya, dobavlyaetsya etap GIPa». Ochen complex, ne in plane.
Backlog

Synchronization and infrastructure

  • SignalR/WebSocket push instead of polling. Currently zaderzhka until 30 sek between approver decision and the status appearing in Revit.
  • Reapply whenOnDocumentOpened. If otkryli model, in kotoroy there were pending requests long ago and za this vremya soglasuyuschiy prinyal solution — oni will be pulled in in InitialBacklog (24h). Rasshirenie via persistent state.
  • Background authorization error. If token expires, poller vidit 401 and TokenSession is cleared. UI-indikatsii seychas no; user uvidit when sleduyuschem clicks.
Tech debt

Technical debt

  • Testy.Currently there are no automated tests yet. Nuzhny as minimum: integration tests for server endpointss'ov (TestHost + test database), unit tests on StatusWriter, ImprintScanner, agregatsiyu.
  • Logging structured.Currently Microsoft. Extensions. Logging with defoltnym provayderom. For production needed Serilog with output in file + JSON for parsinga.
  • Health checks./health/liveest, no no/health/ready(DB connectivity, migrations applied).
  • API versioning.All endpoint'y/api/...without versions. Dobavit/api/v1/...for buduschey compatibilityi.
  • Rate limitingon endpoint'akh login and create-request. Protection from brute force and spam.
  • Localization.UI on russkom khardkodom. For intl-projectov nuzhno perevodit viaRevitCDE.Localization.Resources.
Docs

Documentation

  • Konos-userskiy gayd (PDF) — step-by-step scenario «as podat on soglasovanie» with screenshots Revit-interface.
  • Gayd for soglasuyuschego — poka via Swagger; after Phase 6 — for web-kabinoa.
  • DevOps-documentation — as podnyat server from iskhodnikov (Docker compose: postgres + server), as apply migrations, as configureappsettings.jsonfor production.
  • API reference — Swagger UI khorosh for razrabotchikov no for integrators needed separate document with description kazhdogo endpoint'a, error codes, examples.