Added status to the requirements

This commit is contained in:
gulimabr
2025-12-02 11:47:45 -03:00
parent 459ceaa162
commit 9428c4d2de
12 changed files with 340 additions and 15 deletions

View File

@@ -104,6 +104,19 @@ class Priority(Base):
requirements: Mapped[List["Requirement"]] = relationship("Requirement", back_populates="priority")
class RequirementStatus(Base):
"""Requirement lifecycle statuses (Draft, Regular, etc.)."""
__tablename__ = "requirement_statuses"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
status_code: Mapped[str] = mapped_column(String(20), nullable=False, unique=True)
status_name: Mapped[str] = mapped_column(Text, nullable=False)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Relationships
requirements: Mapped[List["Requirement"]] = relationship("Requirement", back_populates="status")
class Project(Base):
"""Projects - containers for requirements that can have multiple members."""
__tablename__ = "projects"
@@ -172,6 +185,12 @@ class Requirement(Base):
project_id: Mapped[int] = mapped_column(Integer, ForeignKey("projects.id", ondelete="CASCADE"), nullable=False)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
tag_id: Mapped[int] = mapped_column(Integer, ForeignKey("tags.id"), nullable=False)
status_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("requirement_statuses.id"),
nullable=False,
default=1 # Default to 'Draft' status
)
last_editor_id: Mapped[Optional[int]] = mapped_column(
Integer,
ForeignKey("users.id"),
@@ -210,6 +229,7 @@ class Requirement(Base):
foreign_keys=[last_editor_id]
)
tag: Mapped["Tag"] = relationship("Tag", back_populates="requirements")
status: Mapped["RequirementStatus"] = relationship("RequirementStatus", back_populates="requirements")
priority: Mapped[Optional["Priority"]] = relationship("Priority", back_populates="requirements")
groups: Mapped[List["Group"]] = relationship(
"Group",
@@ -239,6 +259,7 @@ class Requirement(Base):
Index("idx_req_tag", "tag_id"),
Index("idx_req_priority", "priority_id"),
Index("idx_req_user", "user_id"),
Index("idx_req_status", "status_id"),
)

View File

@@ -14,7 +14,8 @@ from src.models import (
RelationshipTypeResponse, RelationshipTypeCreateRequest, RelationshipTypeUpdateRequest,
RequirementLinkResponse, RequirementLinkCreateRequest, RequirementSearchResult,
RoleResponse, ProjectMemberResponse, UserRoleUpdateRequest, ROLE_DISPLAY_NAMES,
CommentResponse, CommentReplyResponse, CommentCreateRequest, ReplyCreateRequest
CommentResponse, CommentReplyResponse, CommentCreateRequest, ReplyCreateRequest,
RequirementStatusResponse
)
from src.controller import AuthController
from src.config import get_openid, get_settings
@@ -22,7 +23,8 @@ from src.database import init_db, close_db, get_db
from src.repositories import (
RoleRepository, GroupRepository, TagRepository, RequirementRepository,
PriorityRepository, ProjectRepository, ValidationStatusRepository, ValidationRepository,
RelationshipTypeRepository, RequirementLinkRepository, CommentRepository, ReplyRepository
RelationshipTypeRepository, RequirementLinkRepository, CommentRepository, ReplyRepository,
RequirementStatusRepository
)
import logging
@@ -54,6 +56,13 @@ async def lifespan(app: FastAPI):
await session.commit()
logger.info("Default roles ensured")
# Ensure default requirement statuses exist
async with AsyncSessionLocal() as session:
req_status_repo = RequirementStatusRepository(session)
await req_status_repo.ensure_default_statuses_exist()
await session.commit()
logger.info("Default requirement statuses ensured")
# Ensure default validation statuses exist
async with AsyncSessionLocal() as session:
await session.execute(
@@ -277,6 +286,23 @@ async def get_priorities(db: AsyncSession = Depends(get_db)):
return [PriorityResponse.model_validate(p) for p in priorities]
# ===========================================
# Requirement Statuses Endpoints
# ===========================================
@app.get("/api/requirement-statuses", response_model=List[RequirementStatusResponse])
async def get_requirement_statuses(db: AsyncSession = Depends(get_db)):
"""
Get all requirement lifecycle statuses (Draft, Regular, etc.).
Returns:
List of all requirement statuses.
"""
status_repo = RequirementStatusRepository(db)
statuses = await status_repo.get_all()
return [RequirementStatusResponse.model_validate(s) for s in statuses]
# ===========================================
# Projects Endpoints
# ===========================================
@@ -803,6 +829,11 @@ def _build_requirement_response(req) -> RequirementResponse:
if req.last_editor:
last_editor_username = _get_display_name(req.last_editor)
# Get requirement lifecycle status
status_response = None
if req.status:
status_response = RequirementStatusResponse.model_validate(req.status)
return RequirementResponse(
id=req.id,
project_id=req.project_id,
@@ -814,6 +845,7 @@ def _build_requirement_response(req) -> RequirementResponse:
tag=TagResponse.model_validate(req.tag),
priority=req.priority if req.priority else None,
groups=[GroupResponse.model_validate(g) for g in req.groups],
status=status_response,
validation_status=validation_status,
validated_by=validated_by,
validated_at=validated_at,
@@ -966,6 +998,7 @@ async def create_requirement(
req_desc=req_data.req_desc,
priority_id=req_data.priority_id,
group_ids=req_data.group_ids,
status_id=req_data.status_id,
)
await db.commit()
@@ -1017,6 +1050,7 @@ async def update_requirement(
tag_id=req_data.tag_id,
priority_id=req_data.priority_id,
group_ids=req_data.group_ids,
status_id=req_data.status_id,
)
await db.commit()

View File

@@ -151,6 +151,18 @@ class PriorityResponse(BaseModel):
from_attributes = True
# Requirement Status schemas
class RequirementStatusResponse(BaseModel):
"""Response schema for a requirement lifecycle status."""
id: int
status_code: str
status_name: str
description: Optional[str] = None
class Config:
from_attributes = True
# Validation schemas
class ValidationStatusResponse(BaseModel):
"""Response schema for a validation status."""
@@ -207,6 +219,9 @@ class RequirementResponse(BaseModel):
tag: TagResponse
priority: Optional[PriorityResponse] = None
groups: List[GroupResponse] = []
# Requirement lifecycle status (Draft, Regular)
status: Optional[RequirementStatusResponse] = None
# Validation status (Approved, Denied, etc.)
validation_status: Optional[str] = None # Computed from latest validation
validated_by: Optional[str] = None # Username of the validator
validated_at: Optional[datetime] = None # When the latest validation was made
@@ -231,6 +246,7 @@ class RequirementCreateRequest(BaseModel):
req_desc: Optional[str] = None
priority_id: Optional[int] = None
group_ids: Optional[List[int]] = None
status_id: Optional[int] = None # Defaults to Draft (1) if not provided
class RequirementUpdateRequest(BaseModel):
@@ -240,6 +256,7 @@ class RequirementUpdateRequest(BaseModel):
tag_id: Optional[int] = None
priority_id: Optional[int] = None
group_ids: Optional[List[int]] = None
status_id: Optional[int] = None
class RequirementHistoryResponse(BaseModel):

View File

@@ -12,6 +12,7 @@ from src.repositories.validation_repository import ValidationRepository
from src.repositories.relationship_type_repository import RelationshipTypeRepository
from src.repositories.requirement_link_repository import RequirementLinkRepository
from src.repositories.comment_repository import CommentRepository, ReplyRepository
from src.repositories.requirement_status_repository import RequirementStatusRepository
__all__ = [
"UserRepository",
@@ -27,4 +28,5 @@ __all__ = [
"RequirementLinkRepository",
"CommentRepository",
"ReplyRepository",
"RequirementStatusRepository",
]

View File

@@ -5,7 +5,7 @@ from typing import List, Optional, Dict, Any
from sqlalchemy import select, text
from sqlalchemy.orm import selectinload
from sqlalchemy.ext.asyncio import AsyncSession
from src.db_models import Requirement, Group, Tag, Priority, Validation, RequirementHistory
from src.db_models import Requirement, Group, Tag, Priority, Validation, RequirementHistory, RequirementStatus
import logging
logger = logging.getLogger(__name__)
@@ -19,7 +19,7 @@ class RequirementRepository:
async def get_all(self) -> List[Requirement]:
"""
Get all requirements with their related data (tag, priority, groups, validations).
Get all requirements with their related data (tag, priority, groups, validations, status).
Returns:
List of all requirements with eager-loaded relationships
@@ -30,6 +30,7 @@ class RequirementRepository:
selectinload(Requirement.tag),
selectinload(Requirement.priority),
selectinload(Requirement.groups),
selectinload(Requirement.status),
selectinload(Requirement.validations).selectinload(Validation.status),
selectinload(Requirement.validations).selectinload(Validation.user),
selectinload(Requirement.user),
@@ -55,6 +56,7 @@ class RequirementRepository:
selectinload(Requirement.tag),
selectinload(Requirement.priority),
selectinload(Requirement.groups),
selectinload(Requirement.status),
selectinload(Requirement.validations).selectinload(Validation.status),
selectinload(Requirement.validations).selectinload(Validation.user),
selectinload(Requirement.user),
@@ -81,6 +83,7 @@ class RequirementRepository:
selectinload(Requirement.tag),
selectinload(Requirement.priority),
selectinload(Requirement.groups),
selectinload(Requirement.status),
selectinload(Requirement.validations).selectinload(Validation.status),
selectinload(Requirement.validations).selectinload(Validation.user),
selectinload(Requirement.user),
@@ -107,6 +110,7 @@ class RequirementRepository:
selectinload(Requirement.tag),
selectinload(Requirement.priority),
selectinload(Requirement.groups),
selectinload(Requirement.status),
selectinload(Requirement.validations).selectinload(Validation.status),
selectinload(Requirement.validations).selectinload(Validation.user),
selectinload(Requirement.user),
@@ -134,6 +138,7 @@ class RequirementRepository:
selectinload(Requirement.tag),
selectinload(Requirement.priority),
selectinload(Requirement.groups),
selectinload(Requirement.status),
selectinload(Requirement.validations).selectinload(Validation.status),
selectinload(Requirement.validations).selectinload(Validation.user),
selectinload(Requirement.user),
@@ -171,6 +176,7 @@ class RequirementRepository:
selectinload(Requirement.tag),
selectinload(Requirement.priority),
selectinload(Requirement.groups),
selectinload(Requirement.status),
selectinload(Requirement.validations).selectinload(Validation.status),
selectinload(Requirement.validations).selectinload(Validation.user),
selectinload(Requirement.user),
@@ -198,6 +204,7 @@ class RequirementRepository:
req_desc: Optional[str] = None,
priority_id: Optional[int] = None,
group_ids: Optional[List[int]] = None,
status_id: Optional[int] = None,
) -> Requirement:
"""
Create a new requirement.
@@ -210,6 +217,7 @@ class RequirementRepository:
req_desc: The requirement description (optional)
priority_id: The priority ID (optional)
group_ids: List of group IDs to associate (optional)
status_id: The requirement status ID (optional, defaults to 1=Draft)
Returns:
The created Requirement
@@ -221,6 +229,7 @@ class RequirementRepository:
req_name=req_name,
req_desc=req_desc,
priority_id=priority_id,
status_id=status_id if status_id else 1, # Default to Draft
)
# Add groups if provided
@@ -247,6 +256,7 @@ class RequirementRepository:
tag_id: Optional[int] = None,
priority_id: Optional[int] = None,
group_ids: Optional[List[int]] = None,
status_id: Optional[int] = None,
) -> Optional[Requirement]:
"""
Update an existing requirement.
@@ -259,6 +269,7 @@ class RequirementRepository:
tag_id: New tag ID (optional)
priority_id: New priority ID (optional)
group_ids: New list of group IDs (optional)
status_id: New status ID (optional)
Returns:
The updated Requirement, or None if not found
@@ -276,6 +287,8 @@ class RequirementRepository:
requirement.tag_id = tag_id
if priority_id is not None:
requirement.priority_id = priority_id
if status_id is not None:
requirement.status_id = status_id
# Set the last editor
requirement.last_editor_id = editor_id

View File

@@ -0,0 +1,102 @@
"""
Repository layer for RequirementStatus database operations.
"""
from typing import List, Optional
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.db_models import RequirementStatus
import logging
logger = logging.getLogger(__name__)
class RequirementStatusRepository:
"""Repository for RequirementStatus-related database operations."""
def __init__(self, session: AsyncSession):
self.session = session
async def get_all(self) -> List[RequirementStatus]:
"""
Get all requirement statuses.
Returns:
List of all requirement statuses
"""
result = await self.session.execute(
select(RequirementStatus).order_by(RequirementStatus.id)
)
return list(result.scalars().all())
async def get_by_id(self, status_id: int) -> Optional[RequirementStatus]:
"""
Get a requirement status by ID.
Args:
status_id: The status ID
Returns:
RequirementStatus if found, None otherwise
"""
result = await self.session.execute(
select(RequirementStatus).where(RequirementStatus.id == status_id)
)
return result.scalar_one_or_none()
async def get_by_code(self, status_code: str) -> Optional[RequirementStatus]:
"""
Get a requirement status by code.
Args:
status_code: The status code (e.g., 'DRAFT', 'REGULAR')
Returns:
RequirementStatus if found, None otherwise
"""
result = await self.session.execute(
select(RequirementStatus).where(RequirementStatus.status_code == status_code)
)
return result.scalar_one_or_none()
async def create(
self,
status_code: str,
status_name: str,
description: Optional[str] = None
) -> RequirementStatus:
"""
Create a new requirement status.
Args:
status_code: The unique status code
status_name: The display name
description: Optional description
Returns:
The created RequirementStatus
"""
status = RequirementStatus(
status_code=status_code,
status_name=status_name,
description=description
)
self.session.add(status)
await self.session.flush()
await self.session.refresh(status)
return status
async def ensure_default_statuses_exist(self) -> None:
"""
Ensure default requirement statuses exist in the database.
Called during application startup.
"""
default_statuses = [
("DRAFT", "Draft", "Initial version, not ready for review"),
("REGULAR", "Regular", "Active requirement"),
]
for status_code, status_name, description in default_statuses:
existing = await self.get_by_code(status_code)
if existing is None:
logger.info(f"Creating default requirement status: {status_code}")
await self.create(status_code, status_name, description)

View File

@@ -1,13 +1,13 @@
import { useState, useEffect } from 'react'
import { useAuth, useProject } from '@/hooks'
import { useParams, Link } from 'react-router-dom'
import { requirementService, validationService, relationshipService, commentService, tagService, priorityService, groupService } from '@/services'
import { requirementService, validationService, relationshipService, commentService, tagService, priorityService, groupService, requirementStatusService } from '@/services'
import type { Requirement } from '@/services/requirementService'
import type { RelationshipType, RequirementLink, RequirementSearchResult } from '@/services/relationshipService'
import type { Tag } from '@/services/tagService'
import type { Priority } from '@/services/priorityService'
import type { Group } from '@/services/groupService'
import type { ValidationStatus, ValidationHistory, Comment, RequirementHistory } from '@/types'
import type { ValidationStatus, ValidationHistory, Comment, RequirementHistory, RequirementStatus } from '@/types'
// Tab types
type TabType = 'description' | 'relationships' | 'acceptance-criteria' | 'shared-comments' | 'validate' | 'history'
@@ -64,9 +64,11 @@ export default function RequirementDetailPage() {
const [editTagId, setEditTagId] = useState<number | ''>('')
const [editPriorityId, setEditPriorityId] = useState<number | ''>('')
const [editGroupIds, setEditGroupIds] = useState<number[]>([])
const [editStatusId, setEditStatusId] = useState<number | ''>(1)
const [availableTags, setAvailableTags] = useState<Tag[]>([])
const [availablePriorities, setAvailablePriorities] = useState<Priority[]>([])
const [availableGroups, setAvailableGroups] = useState<Group[]>([])
const [availableStatuses, setAvailableStatuses] = useState<RequirementStatus[]>([])
const [editOptionsLoading, setEditOptionsLoading] = useState(false)
// Requirement history state
@@ -429,21 +431,24 @@ export default function RequirementDetailPage() {
setEditTagId(requirement.tag.id)
setEditPriorityId(requirement.priority?.id || '')
setEditGroupIds(requirement.groups.map(g => g.id))
setEditStatusId(requirement.status?.id || 1)
setEditError(null)
setShowEditModal(true)
// Fetch options if not already loaded
if (availableTags.length === 0 || availablePriorities.length === 0 || availableGroups.length === 0) {
if (availableTags.length === 0 || availablePriorities.length === 0 || availableGroups.length === 0 || availableStatuses.length === 0) {
try {
setEditOptionsLoading(true)
const [tags, priorities, groups] = await Promise.all([
const [tags, priorities, groups, statuses] = await Promise.all([
tagService.getTags(),
priorityService.getPriorities(),
groupService.getGroups()
groupService.getGroups(),
requirementStatusService.getStatuses()
])
setAvailableTags(tags)
setAvailablePriorities(priorities)
setAvailableGroups(groups)
setAvailableStatuses(statuses)
} catch (err) {
console.error('Failed to fetch edit options:', err)
setEditError('Failed to load options. Please try again.')
@@ -475,7 +480,8 @@ export default function RequirementDetailPage() {
req_desc: editReqDesc.trim() || undefined,
tag_id: editTagId as number,
priority_id: editPriorityId ? editPriorityId as number : undefined,
group_ids: editGroupIds
group_ids: editGroupIds,
status_id: editStatusId ? editStatusId as number : undefined,
})
// Refetch to ensure consistency (trigger updates version)
@@ -515,6 +521,9 @@ export default function RequirementDetailPage() {
}
}
// Check if requirement is in draft status
const isDraftStatus = requirement?.status?.status_code === 'DRAFT'
if (loading) {
return (
<div className="min-h-screen bg-white flex items-center justify-center">
@@ -561,9 +570,33 @@ export default function RequirementDetailPage() {
return (
<div>
<h3 className="text-xl font-bold text-gray-800 mb-2">Description</h3>
{/* Draft indicator banner */}
{isDraftStatus && (
<div className="mb-4 p-3 bg-amber-50 border border-amber-300 border-dashed rounded-lg">
<div className="flex items-center gap-2">
<span className="text-amber-600 text-lg">📝</span>
<div>
<p className="font-semibold text-amber-800">Draft Requirement</p>
<p className="text-sm text-amber-700">This requirement is still in draft status and is not finalized. It may be subject to changes.</p>
</div>
</div>
</div>
)}
<p className="text-sm text-gray-700 mb-2">
<span className="font-semibold">Version:</span> {requirement.version}
</p>
<p className="text-sm text-gray-700 mb-2">
<span className="font-semibold">Status:</span>{' '}
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
isDraftStatus
? 'bg-amber-100 text-amber-800 border border-amber-300'
: 'bg-blue-100 text-blue-800'
}`}>
{requirement.status?.status_name || 'Unknown'}
</span>
</p>
<p className="text-sm text-gray-700 mb-2">
<span className="font-semibold">Validation Status:</span>{' '}
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getValidationStatusStyle(validationStatus)}`}>
@@ -1220,6 +1253,14 @@ export default function RequirementDetailPage() {
<div className="max-w-5xl mx-auto px-8 py-8">
{/* Requirement Header */}
<div className="text-center mb-8">
{/* Draft Status Indicator */}
{isDraftStatus && (
<div className="mb-4 inline-flex items-center gap-2 px-4 py-2 bg-amber-100 border-2 border-dashed border-amber-400 rounded-lg">
<span className="text-amber-600 text-lg">📝</span>
<span className="text-amber-800 font-medium">Draft - Not Finalized</span>
</div>
)}
{/* Tag */}
<h2 className="text-2xl font-bold text-gray-800 mb-2">{tagCode}</h2>
@@ -1491,6 +1532,28 @@ export default function RequirementDetailPage() {
</select>
</div>
{/* Status */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Status
</label>
<select
value={editStatusId}
onChange={(e) => setEditStatusId(e.target.value ? Number(e.target.value) : '')}
className="w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent"
disabled={editLoading}
>
{availableStatuses.map((status) => (
<option key={status.id} value={status.id}>
{status.status_name} {status.description ? `- ${status.description}` : ''}
</option>
))}
</select>
<p className="mt-1 text-xs text-gray-500">
Draft requirements are not finalized and marked with a visual indicator.
</p>
</div>
{/* Description */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">

View File

@@ -22,6 +22,11 @@ const getValidationStatusStyle = (status: string): { bgColor: string; textColor:
}
}
// Check if requirement is in draft status
const isDraftStatus = (statusCode: string | undefined): boolean => {
return statusCode === 'DRAFT'
}
export default function RequirementsPage() {
const { user, logout, isAuditor } = useAuth()
const { currentProject, isLoading: projectLoading } = useProject()
@@ -426,18 +431,33 @@ export default function RequirementsPage() {
const validationStatus = req.validation_status || 'Not Validated'
const validationStyle = getValidationStatusStyle(validationStatus)
const isStale = req.validation_version !== null && req.validation_version !== req.version
const isDraft = isDraftStatus(req.status?.status_code)
return (
<div
key={req.id}
className="flex items-center rounded overflow-hidden border border-gray-300 bg-white"
className={`flex items-center rounded overflow-hidden border bg-white ${
isDraft
? 'border-amber-400 border-dashed border-2'
: 'border-gray-300'
}`}
>
{/* Tag and name section */}
<div className="px-4 py-4 min-w-[280px]">
<div className="flex items-center gap-2">
{isDraft && (
<span
className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-800 border border-amber-300"
title="This requirement is still in draft and not finalized"
>
📝 Draft
</span>
)}
<span className="font-bold text-gray-800">
{tagLabel} - {req.req_name}
</span>
</div>
</div>
{/* Group chips */}
<div className="flex-1 px-4 py-4">

View File

@@ -7,6 +7,7 @@ export { priorityService } from './priorityService'
export type { Priority } from './priorityService'
export { requirementService } from './requirementService'
export type { Requirement, RequirementCreateRequest, RequirementUpdateRequest } from './requirementService'
export { requirementStatusService } from './requirementStatusService'
export { projectService } from './projectService'
export type { Project, ProjectCreateRequest, ProjectUpdateRequest } from './projectService'
export { validationService } from './validationService'

View File

@@ -1,7 +1,7 @@
import { Group } from './groupService'
import { Tag } from './tagService'
import { Priority } from './priorityService'
import type { RequirementHistory } from '@/types'
import type { RequirementHistory, RequirementStatus } from '@/types'
const API_BASE_URL = '/api'
@@ -16,6 +16,9 @@ export interface Requirement {
tag: Tag
priority: Priority | null
groups: Group[]
// Requirement lifecycle status (Draft, Regular)
status: RequirementStatus | null
// Validation status (Approved, Denied, etc.)
validation_status: string | null
validated_by: string | null
validated_at: string | null
@@ -31,6 +34,7 @@ export interface RequirementCreateRequest {
req_desc?: string
priority_id?: number
group_ids?: number[]
status_id?: number
}
export interface RequirementUpdateRequest {
@@ -39,6 +43,7 @@ export interface RequirementUpdateRequest {
tag_id?: number
priority_id?: number
group_ids?: number[]
status_id?: number
}
class RequirementService {

View File

@@ -0,0 +1,39 @@
import type { RequirementStatus } from '@/types'
const API_BASE_URL = '/api'
class RequirementStatusService {
/**
* Get all requirement lifecycle statuses.
*/
async getStatuses(): Promise<RequirementStatus[]> {
try {
const response = await fetch(`${API_BASE_URL}/requirement-statuses`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const statuses: RequirementStatus[] = await response.json()
return statuses
} catch (error) {
console.error('Failed to fetch requirement statuses:', error)
throw error
}
}
/**
* Check if a requirement is in draft status.
*/
isDraft(status: RequirementStatus | null): boolean {
return status?.status_code === 'DRAFT'
}
}
export const requirementStatusService = new RequirementStatusService()

View File

@@ -1,5 +1,13 @@
export * from './auth'
// Requirement Status types (lifecycle status)
export interface RequirementStatus {
id: number
status_code: string
status_name: string
description: string | null
}
// Validation types
export interface ValidationStatus {
id: number