feat: Phase 4 — inspection sessions (create, execute, complete)
All checks were successful
Deploy to Production / deploy (push) Successful in 1m5s

Backend:
- InspectionSession + InspectionRecord models with alembic migration
- 6 API endpoints: create, list, get detail, save records, complete, delete
- Auto pass/fail judgment for numeric (spec range) and boolean items
- Completed inspections are immutable, required items enforced on complete
- 14 new tests (total 53/53 passed)

Frontend:
- Inspection list page with in_progress/completed tabs
- Template select modal for starting new inspections
- Inspection execution page with data-type-specific inputs
- Auto-save with 1.5s debounce, manual save button
- Completion modal with notes and required item validation
- Read-only view for completed inspections
- Pass/fail badges and color-coded item cards

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
Johngreen
2026-02-10 13:46:23 +09:00
parent 180cc5b163
commit 581c845f54
10 changed files with 2192 additions and 1 deletions

View File

@@ -233,3 +233,75 @@ class InspectionTemplateItem(Base):
__table_args__ = (
Index("ix_template_items_template_order", "template_id", "sort_order"),
)
class InspectionSession(Base):
__tablename__ = "inspection_sessions"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(String(50), ForeignKey("tenants.id"), nullable=False)
template_id = Column(
UUID(as_uuid=True),
ForeignKey("inspection_templates.id"),
nullable=False,
)
inspector_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
status = Column(
String(20), nullable=False, default="draft"
) # draft | in_progress | completed
started_at = Column(TIMESTAMP(timezone=True), nullable=True)
completed_at = Column(TIMESTAMP(timezone=True), nullable=True)
notes = Column(Text, nullable=True)
created_at = Column(TIMESTAMP(timezone=True), default=utcnow)
updated_at = Column(TIMESTAMP(timezone=True), default=utcnow, onupdate=utcnow)
tenant = relationship("Tenant")
template = relationship("InspectionTemplate")
inspector = relationship("User")
records = relationship(
"InspectionRecord",
back_populates="session",
cascade="all, delete-orphan",
)
__table_args__ = (
Index("ix_sessions_tenant_status", "tenant_id", "status"),
Index("ix_sessions_tenant_template", "tenant_id", "template_id"),
Index("ix_sessions_tenant_inspector", "tenant_id", "inspector_id"),
)
class InspectionRecord(Base):
__tablename__ = "inspection_records"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
session_id = Column(
UUID(as_uuid=True),
ForeignKey("inspection_sessions.id", ondelete="CASCADE"),
nullable=False,
)
template_item_id = Column(
UUID(as_uuid=True),
ForeignKey("inspection_template_items.id"),
nullable=False,
)
value_numeric = Column(Float, nullable=True)
value_boolean = Column(Boolean, nullable=True)
value_text = Column(Text, nullable=True)
value_select = Column(String(200), nullable=True)
is_pass = Column(
Boolean, nullable=True
) # None = 미판정, True = 합격, False = 불합격
recorded_at = Column(TIMESTAMP(timezone=True), nullable=True)
created_at = Column(TIMESTAMP(timezone=True), default=utcnow)
updated_at = Column(TIMESTAMP(timezone=True), default=utcnow, onupdate=utcnow)
session = relationship("InspectionSession", back_populates="records")
template_item = relationship("InspectionTemplateItem")
__table_args__ = (
UniqueConstraint(
"session_id", "template_item_id", name="uq_record_session_item"
),
Index("ix_records_session", "session_id"),
)