Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/juescoryisus/QualityDocD/llms.txt

Use this file to discover all available pages before exploring further.

Every document in QualityDocD follows a structured lifecycle designed to enforce quality controls before content becomes official. From the moment an author saves a first draft to the point a document is superseded by a newer version, the system tracks every state change, records it in both the SQL Server audit log and the PostgreSQL audit_entries table, and syncs metadata to MongoDB. Understanding the lifecycle means understanding who can act at each stage — and what gates must pass before a document reaches the hands of the people who rely on it.

The Seven States

The DocumentStatus enum in Document.cs defines exactly seven states. Each state has a precise meaning and a limited set of valid successors.

Draft

Initial draft. The document has been created but not yet submitted for review. Authors can freely edit content, replace file attachments, and update metadata. CanSubmitForReview() returns true in this state.

UnderReview

The author submitted the document and assigned one or more reviewers. Each reviewer holds a DocumentApproval record in Pending status. The document is locked for editing until a decision is made.

PendingChanges

At least one reviewer called RequestChanges. The document is returned to the author for revision. All previous DocumentApproval records are cleared when the author resubmits. CanSubmitForReview() returns true in this state.

UnderSecondReview

The author applied the requested changes and resubmitted. A fresh set of DocumentApproval records is created. IsInReview() returns true for both UnderReview and UnderSecondReview.

Approved

Every assigned reviewer has set their ApprovalStatus to Approved. The document’s ApprovedAt timestamp is stamped. The document is now the authoritative version. IsFinal() returns true.

Rejected

A reviewer definitively rejected the document. The RejectedAt timestamp is stamped and IsFinal() returns true. A rejection is permanent — a new document version must be created to continue.

Obsolete

An Admin or Manager has superseded the document by marking it obsolete, typically after a newer version has been approved. IsFinal() returns true. This state can also be reached directly from Approved.

State Summary Table

StatusCanSubmitForReview()IsInReview()IsFinal()
Drafttruefalsefalse
UnderReviewfalsetruefalse
PendingChangestruefalsefalse
UnderSecondReviewfalsetruefalse
Approvedfalsefalsetrue
Rejectedfalsefalsetrue
Obsoletefalsefalsetrue

State Transitions

The following table lists every valid transition in the system, the action that triggers it, and the role required to perform that action.
FromToTriggerWho Can Trigger
DraftUnderReviewAuthor submits for review with at least one reviewer assignedAny authenticated user (document owner)
UnderReviewApprovedAll assigned reviewers approveAdmin, Manager, Reviewer
UnderReviewRejectedAny reviewer rejects (with mandatory comment)Admin, Manager, Reviewer
UnderReviewPendingChangesAny reviewer requests changes (with mandatory comment)Admin, Manager, Reviewer
UnderSecondReviewApprovedAll assigned reviewers approveAdmin, Manager, Reviewer
UnderSecondReviewRejectedAny reviewer rejects (with mandatory comment)Admin, Manager, Reviewer
UnderSecondReviewPendingChangesAny reviewer requests changes (with mandatory comment)Admin, Manager, Reviewer
PendingChangesUnderSecondReviewAuthor resubmits after applying changesAny authenticated user (document owner)
ApprovedObsoleteAdmin or Manager marks the document obsoleteAdmin, Manager
RejectedObsoleteAdmin or Manager marks the document obsoleteAdmin, Manager
Final states — Approved, Rejected, and Obsolete — cannot transition back to any earlier state. IsFinal() returning true means the document’s lifecycle is closed. To continue work on a rejected or obsolete document, create a new document version with incremented Version number.

Domain Helper Methods

The Document class exposes three boolean helpers that the service layer and controller use as guards before executing any state-changing operation.
public bool CanSubmitForReview() =>
    Status is DocumentStatus.Draft or DocumentStatus.PendingChanges;
Called in DocumentService.SubmitForReviewAsync() before creating DocumentApproval records. If the document is in any other state the service returns an error and no database changes are made.
public bool IsInReview() =>
    Status is DocumentStatus.UnderReview or DocumentStatus.UnderSecondReview;
Used as a precondition guard in ApproveAsync(), RejectAsync(), and RequestChangesAsync(). Reviewer actions are only valid while the document is in an active review state.
public bool IsFinal() =>
    Status is DocumentStatus.Approved or DocumentStatus.Rejected or DocumentStatus.Obsolete;
Used to prevent edits and re-submissions on closed documents. The Edit controller action returns an error if Status is not "Draft" or "PendingChanges", which aligns with the inverse of this helper.

Per-Reviewer Approval Status

Each reviewer’s decision is stored in a separate DocumentApproval row. The ApprovalStatus enum tracks the individual verdict independently from the document-level DocumentStatus.
ApprovalStatusMeaning
PendingThe reviewer has been assigned but has not yet acted. This is the default when a DocumentApproval record is created.
ApprovedThe reviewer approved the document without observations. When all reviewers in a round reach Approved, the document moves to DocumentStatus.Approved.
RejectedThe reviewer definitively rejected the document. The document moves immediately to DocumentStatus.Rejected regardless of other reviewers’ pending decisions.
RequestChangesThe reviewer requires revisions before approving. The document moves to DocumentStatus.PendingChanges. A non-empty Comments field is mandatory.

Multi-Reviewer Approval Logic

When a document has multiple reviewers, ApproveAsync() checks whether every other approval record has already reached ApprovalStatus.Approved before promoting the document:
var allApproved = doc.Approvals
    .Where(a => a.Id != approval.Id)
    .All(a => a.Status == ApprovalStatus.Approved);

if (allApproved)
{
    doc.Status = DocumentStatus.Approved;
    doc.ApprovedAt = DateTime.UtcNow;
}
If other reviewers are still pending, the current reviewer’s DocumentApproval is saved as Approved but the document status remains UnderReview or UnderSecondReview until the last reviewer acts.

The PendingChanges Loop

The change-request cycle is a first-class feature of the lifecycle, allowing iterative improvement without creating a new document version.
1

Reviewer calls RequestChanges

A reviewer with role Admin, Manager, or Reviewer submits the RequestChanges action with a mandatory comment. DocumentService.RequestChangesAsync() sets the reviewer’s ApprovalStatus to RequestChanges, then sets DocumentStatus to PendingChanges.
2

Author edits the document

The document is unlocked for editing (CanSubmitForReview() returns true). The author updates the content and optionally replaces the file attachment (which increments Version).
3

Author resubmits

The author calls SubmitForReview again, assigning reviewers for the second round. Because doc.Status == DocumentStatus.PendingChanges, SubmitForReviewAsync() clears all previous DocumentApproval records and creates fresh ones. The document advances to UnderSecondReview.
4

Reviewers act on UnderSecondReview

The approval process repeats. If all reviewers approve, the document reaches Approved. If a reviewer requests changes again, the loop restarts from PendingChanges.

Allowed File Attachments

DocumentService enforces the following file extensions via the AllowedExtensions set. Attempting to upload a file with any other extension returns a validation error before any database record is created or updated.
ExtensionType
.pdfPortable Document Format
.docx / .docMicrosoft Word
.xlsx / .xlsMicrosoft Excel
.pptx / .pptMicrosoft PowerPoint
File uploads are stored under wwwroot/uploads/ with a Guid-based filename to prevent collisions. The original filename is preserved in the OriginalFileName field and returned to the user on download.

Build docs developers (and LLMs) love