Document lifecycle management — Upload → Review → Transmittal → RFI → Archive
InfraTraq implements a comprehensive Document Flow & Control system managing the full document lifecycle: Upload → Review → Revision → Transmittal → Archive. The module handles drawings, specifications, RFIs, submittals, and correspondence with rigorous revision control using standardised review codes (A-Approved, B-Approved with Comments, C-Revise & Resubmit, D-Rejected).
| Status | Description | Allowed Actions | Next States |
|---|---|---|---|
| Draft | Document created with metadata, not yet submitted for review | Edit, Upload File, Submit for Review, Delete | Under Review |
| Under Review | Document submitted to internal reviewers awaiting response codes | Review (A/B/C/D), Add Comments | Approved, Revision Required |
| Approved | All reviewers responded Code A or B — document accepted | Create Transmittal, Distribute, Archive | Transmitted, Distributed |
| Revision Required | Review Code C or D received — author must revise and resubmit | Create New Revision, Edit | Draft (new revision) |
| Transmitted | Document included in a transmittal package sent externally | Track Client Response, View | Client Reviewed |
| Client Reviewed | Client response received with review code | Accept, Create Revision | Approved, Revision Required |
| Distributed | Approved document distributed to project team members | View, Track Distribution, Archive | Archived |
| Archived | Document moved to long-term archive with full history preserved | View, Audit | — |
| Superseded | Replaced by a newer revision — read-only historical record | View, Audit | — |
Document accepted, no changes required — proceed to distribution.
Accepted with minor comments — incorporate in next revision.
Significant issues found — revise and resubmit for review.
Document does not meet requirements — major rework required.
doc_id — PK, master register of all project documentsproject_id — FK → project.projectdoc_number, doc_title, doc_typediscipline — Civil, Structural, MEP, etc.current_revision, statusrevision_id — PK, revision history with file referencesdoc_id — FK → document.document_masterrevision_code — 00, 01, 02... then A, B, C...file_url, review_statusreview_id — PK, individual reviewer responsesdoc_id — FK → document.document_masterreviewer_id — FK → auth.userreview_status, review_code (A/B/C/D), commentstransmittal_id — PK, document packages sent between organisationsproject_id — FK → project.projecttransmittal_number, from_org, to_orgpurpose, statusid — PK, line items linking documents to transmittalstransmittal_id — FK → document.transmittaldoc_id — FK → document.document_masterrevision_coderfi_id — PK, Request for Information lifecycle trackingproject_id — FK → project.projectrfi_number, subjectraised_by — FK → auth.userassigned_to — FK → auth.userstatus, due_datesubmittal_id — PK, submittal packages for material/method approvalproject_id — FK → project.projectsubmittal_number, submittal_typereview_statusid — PK, line items linking documents to submittalssubmittal_id — FK → document.submittaldoc_id — FK → document.document_masterid — PK, discipline-wise drawing registerproject_id — FK → project.projectdrawing_number, disciplinecurrent_revision, statusid — PK, distribution log for compliance trackingdoc_id — FK → document.document_masterrevision_coderecipient_id — FK → auth.userdistributed_atAuthor creates a document_master record with document number, title, type (Drawing / Specification / Report / Letter), and discipline (Civil, Structural, MEP, etc.). File is uploaded to storage and linked via document_revision with initial revision code 00.
Document is submitted for internal review. document_review records are created for each assigned reviewer. Reviewers provide a review code (A/B/C/D) and comments. All reviews must be completed before the document proceeds. If any Code C or D is received, the document returns to the author for revision.
When revisions are needed, a new document_revision record is created with an incremented revision code (00 → 01 → 02 for preliminary, then A → B → C for issued-for-construction). The previous revision is auto-marked as superseded. The document_master.current_revision is updated to the latest.
Approved documents are packaged into a transmittal for external distribution. Each transmittal gets a unique number, lists from/to organisations, and includes one or more transmittal_item records linking specific documents at specific revisions. The transmittal is sent and tracked.
After transmittal, the system tracks client review status. Each document awaits a response code (A/B/C/D). Response due dates are monitored and overdue items are escalated. Client comments are recorded against the document_review for audit trail.
Requests for Information are raised via rfi with a subject, due date, and assigned respondent. The RFI moves through Raised → Assigned → Response → Closed. Overdue RFIs trigger escalation to the Project Manager. Responses may reference document revisions.
Material and method submittals are managed via submittal records. Each submittal has a type (Material / Shop Drawing / Method Statement), linked documents, and goes through the same A/B/C/D review code cycle. Approved submittals release materials or methods for construction use.
Approved documents are distributed to project team members based on discipline and role. Distribution is logged for compliance. Upon project completion or document obsolescence, documents are moved to the archive with full revision history, review trail, and transmittal records preserved for long-term retention.
class DocumentService { /** Create a new document with metadata and initial file upload */ async createDocument(projectId, metadata, file) { const docNumber = await this.generateDocNumber(projectId, metadata.discipline); const fileUrl = await StorageService.upload(file, 'documents'); const doc = await DocumentMaster.create({ project_id: projectId, doc_number: docNumber, doc_title: metadata.title, doc_type: metadata.type, discipline: metadata.discipline, current_revision: '00', status: 'draft' }); await DocumentRevision.create({ doc_id: doc.doc_id, revision_code: '00', file_url: fileUrl, review_status: 'pending' }); return doc; } /** Submit document for internal review by assigned reviewers */ async submitForReview(docId, reviewerIds) { const doc = await DocumentMaster.findById(docId); await doc.update({ status: 'under_review' }); const reviews = reviewerIds.map(rid => ({ doc_id: docId, reviewer_id: rid, review_status: 'pending', review_code: null, comments: null })); await DocumentReview.bulkCreate(reviews); await NotificationService.notify(reviewerIds, 'Document review assigned', doc); return reviews; } /** Process a reviewer's response with review code (A/B/C/D) */ async processReviewResponse(reviewId, code, comments) { await DocumentReview.update(reviewId, { review_code: code, comments, review_status: 'completed' }); const review = await DocumentReview.findById(reviewId); const allReviews = await DocumentReview.findAll({ doc_id: review.doc_id }); const allCompleted = allReviews.every(r => r.review_status === 'completed'); if (allCompleted) { const hasReject = allReviews.some(r => ['C', 'D'].includes(r.review_code)); if (hasReject) { await DocumentMaster.update(review.doc_id, { status: 'revision_required' }); await TaskService.createRevisionTask(review.doc_id); } else { await DocumentMaster.update(review.doc_id, { status: 'approved' }); } } } /** Create a new revision of an existing document */ async createRevision(docId, file, description) { const doc = await DocumentMaster.findById(docId); const newCode = this.nextRevisionCode(doc.current_revision); const fileUrl = await StorageService.upload(file, 'documents'); // Supersede previous revision await DocumentRevision.update( { doc_id: docId, revision_code: doc.current_revision }, { review_status: 'superseded' } ); const revision = await DocumentRevision.create({ doc_id: docId, revision_code: newCode, file_url: fileUrl, review_status: 'pending' }); await doc.update({ current_revision: newCode, status: 'draft' }); return revision; } /** Create a transmittal package for external distribution */ async createTransmittal(projectId, docIds, toOrg) { if (!docIds || docIds.length === 0) { throw new ValidationError('Transmittal must include at least one document'); } const transNumber = await this.generateTransmittalNumber(projectId); const transmittal = await Transmittal.create({ project_id: projectId, transmittal_number: transNumber, from_org: await ProjectService.getOrg(projectId), to_org: toOrg, purpose: 'For Review', status: 'issued' }); for (const docId of docIds) { const doc = await DocumentMaster.findById(docId); await TransmittalItem.create({ transmittal_id: transmittal.transmittal_id, doc_id: docId, revision_code: doc.current_revision }); } await NotificationService.notifyOrg(toOrg, 'Transmittal received', transmittal); return transmittal; } /** Raise a Request for Information */ async createRFI(projectId, subject, assignedTo) { const rfiNumber = await this.generateRFINumber(projectId); const rfi = await RFI.create({ project_id: projectId, rfi_number: rfiNumber, subject, raised_by: AuthService.currentUserId(), assigned_to: assignedTo, status: 'raised', due_date: DateUtil.addBusinessDays(new Date(), 7) }); await NotificationService.notify([assignedTo], 'New RFI assigned', rfi); return rfi; } /** Create a submittal package for material/method approval */ async createSubmittal(projectId, type, docIds) { const submittalNumber = await this.generateSubmittalNumber(projectId); const submittal = await Submittal.create({ project_id: projectId, submittal_number: submittalNumber, submittal_type: type, review_status: 'pending' }); for (const docId of docIds) { await SubmittalItem.create({ submittal_id: submittal.submittal_id, doc_id: docId }); } return submittal; } /** Distribute an approved document to specified recipients */ async distributeDocument(docId, recipientIds) { const doc = await DocumentMaster.findById(docId); if (doc.status !== 'approved') { throw new ValidationError('Only approved documents can be distributed'); } for (const recipientId of recipientIds) { await DocumentDistribution.create({ doc_id: docId, revision_code: doc.current_revision, recipient_id: recipientId, distributed_at: new Date() }); } await NotificationService.notify(recipientIds, 'Document distributed', doc); return { doc_id: docId, distributed_to: recipientIds.length }; } }
| Rule | Condition | Action |
|---|---|---|
| Unique Document Number | doc_number must be unique per discipline within a project | Block creation, suggest next available number |
| Revision Sequence | Revision codes must follow sequence: 00, 01, 02... then A, B, C... | Auto-generate next code, reject out-of-order submissions |
| Transmittal Minimum | Transmittal must include at least one document | Block creation if document list is empty |
| RFI Due Date Required | Every RFI must have a due date assigned | Default to 7 business days if not specified |
| Superseded Auto-Archive | Previous revision exists when new revision is uploaded | Auto-mark previous revision as superseded |
| File Size Limits | File exceeds maximum size per document type | Block upload; Drawings: 100MB, Specs: 50MB, Letters: 20MB |
| Naming Convention | Document number must match project naming template | Validate against regex pattern per project; reject non-conforming |
| Event | Source Table | Auto Action |
|---|---|---|
| Review Completed (Code A) | document.document_review | Update document status to Approved |
| Review Code C or D | document.document_review | Create revision task assigned to document author |
| Transmittal Sent | document.transmittal | Notify recipient organisation and log distribution |
| RFI Overdue | document.rfi | Escalate to Project Manager + send reminder to assignee |
| New Revision Uploaded | document.document_revision | Supersede previous revision and update master record |
| Submittal Approved | document.submittal | Release material/method for construction, notify site team |
PRJ-DIS-TYPE-NNNN)document_master on (project_id, discipline, status) and rfi on (project_id, status, due_date) for fast queries