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>
112 lines
3.5 KiB
Python
112 lines
3.5 KiB
Python
"""add_inspection_sessions
|
|
|
|
Revision ID: b4c5d6e7f8a9
|
|
Revises: 9566bf2a256b
|
|
Create Date: 2026-02-10 13:20:00.000000
|
|
|
|
"""
|
|
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.dialects import postgresql
|
|
|
|
revision: str = "b4c5d6e7f8a9"
|
|
down_revision: Union[str, None] = "9566bf2a256b"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"inspection_sessions",
|
|
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
|
sa.Column(
|
|
"tenant_id", sa.String(50), sa.ForeignKey("tenants.id"), nullable=False
|
|
),
|
|
sa.Column(
|
|
"template_id",
|
|
postgresql.UUID(as_uuid=True),
|
|
sa.ForeignKey("inspection_templates.id"),
|
|
nullable=False,
|
|
),
|
|
sa.Column(
|
|
"inspector_id",
|
|
postgresql.UUID(as_uuid=True),
|
|
sa.ForeignKey("users.id"),
|
|
nullable=False,
|
|
),
|
|
sa.Column("status", sa.String(20), nullable=False, server_default="draft"),
|
|
sa.Column("started_at", postgresql.TIMESTAMP(timezone=True), nullable=True),
|
|
sa.Column("completed_at", postgresql.TIMESTAMP(timezone=True), nullable=True),
|
|
sa.Column("notes", sa.Text, nullable=True),
|
|
sa.Column(
|
|
"created_at",
|
|
postgresql.TIMESTAMP(timezone=True),
|
|
server_default=sa.text("now()"),
|
|
),
|
|
sa.Column(
|
|
"updated_at",
|
|
postgresql.TIMESTAMP(timezone=True),
|
|
server_default=sa.text("now()"),
|
|
),
|
|
)
|
|
op.create_index(
|
|
"ix_sessions_tenant_status", "inspection_sessions", ["tenant_id", "status"]
|
|
)
|
|
op.create_index(
|
|
"ix_sessions_tenant_template",
|
|
"inspection_sessions",
|
|
["tenant_id", "template_id"],
|
|
)
|
|
op.create_index(
|
|
"ix_sessions_tenant_inspector",
|
|
"inspection_sessions",
|
|
["tenant_id", "inspector_id"],
|
|
)
|
|
|
|
op.create_table(
|
|
"inspection_records",
|
|
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
|
sa.Column(
|
|
"session_id",
|
|
postgresql.UUID(as_uuid=True),
|
|
sa.ForeignKey("inspection_sessions.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
),
|
|
sa.Column(
|
|
"template_item_id",
|
|
postgresql.UUID(as_uuid=True),
|
|
sa.ForeignKey("inspection_template_items.id"),
|
|
nullable=False,
|
|
),
|
|
sa.Column("value_numeric", sa.Float, nullable=True),
|
|
sa.Column("value_boolean", sa.Boolean, nullable=True),
|
|
sa.Column("value_text", sa.Text, nullable=True),
|
|
sa.Column("value_select", sa.String(200), nullable=True),
|
|
sa.Column("is_pass", sa.Boolean, nullable=True),
|
|
sa.Column("recorded_at", postgresql.TIMESTAMP(timezone=True), nullable=True),
|
|
sa.Column(
|
|
"created_at",
|
|
postgresql.TIMESTAMP(timezone=True),
|
|
server_default=sa.text("now()"),
|
|
),
|
|
sa.Column(
|
|
"updated_at",
|
|
postgresql.TIMESTAMP(timezone=True),
|
|
server_default=sa.text("now()"),
|
|
),
|
|
)
|
|
op.create_index("ix_records_session", "inspection_records", ["session_id"])
|
|
op.create_unique_constraint(
|
|
"uq_record_session_item",
|
|
"inspection_records",
|
|
["session_id", "template_item_id"],
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_table("inspection_records")
|
|
op.drop_table("inspection_sessions")
|