feat: Phase 3 — inspection templates (backend + frontend)
All checks were successful
Deploy to Production / deploy (push) Successful in 1m7s

- Add InspectionTemplate and InspectionTemplateItem models
- Add 9 API endpoints for template CRUD and item management
- Add Alembic migration for inspection_templates tables
- Add 15 backend tests (39/39 total pass)
- Add TemplateEditor component with item management UI
- Add templates list, create, and edit pages
- Add tab bar, template grid, form row CSS classes
- Fix CORS middleware ordering in main.py
- Move CORS middleware before router registration

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
Johngreen
2026-02-10 13:24:30 +09:00
parent ee691be3ce
commit 7de011621d
14 changed files with 2177 additions and 10 deletions

View File

@@ -14,7 +14,7 @@ from sqlalchemy import (
text,
)
from sqlalchemy.dialects.postgresql import UUID, JSONB, TIMESTAMP
from sqlalchemy.orm import relationship
from sqlalchemy.orm import relationship, backref
from src.database.config import Base
@@ -164,3 +164,72 @@ class PartReplacementLog(Base):
Index("ix_part_replacement_tenant_part", "tenant_id", "equipment_part_id"),
Index("ix_part_replacement_tenant_date", "tenant_id", "replaced_at"),
)
class InspectionTemplate(Base):
__tablename__ = "inspection_templates"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(String(50), ForeignKey("tenants.id"), nullable=False)
name = Column(String(200), nullable=False)
subject_type = Column(String(20), nullable=False) # equipment | product
machine_id = Column(UUID(as_uuid=True), ForeignKey("machines.id"), nullable=True)
product_code = Column(String(50), nullable=True)
schedule_type = Column(
String(20), nullable=False
) # daily | weekly | monthly | yearly | ad_hoc
inspection_mode = Column(
String(20), default="measurement"
) # checklist | measurement | monitoring
version = Column(Integer, default=1)
is_active = Column(Boolean, default=True)
created_at = Column(TIMESTAMP(timezone=True), default=utcnow)
updated_at = Column(TIMESTAMP(timezone=True), default=utcnow, onupdate=utcnow)
tenant = relationship("Tenant")
machine = relationship("Machine")
items = relationship(
"InspectionTemplateItem",
back_populates="template",
cascade="all, delete-orphan",
order_by="InspectionTemplateItem.sort_order",
)
__table_args__ = (
Index("ix_templates_tenant_subject", "tenant_id", "subject_type"),
Index("ix_templates_tenant_machine", "tenant_id", "machine_id"),
)
class InspectionTemplateItem(Base):
__tablename__ = "inspection_template_items"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
template_id = Column(
UUID(as_uuid=True),
ForeignKey("inspection_templates.id", ondelete="CASCADE"),
nullable=False,
)
sort_order = Column(Integer, nullable=False)
name = Column(String(200), nullable=False)
category = Column(String(100), nullable=True)
data_type = Column(String(20), nullable=False) # numeric | boolean | text | select
unit = Column(String(20), nullable=True)
spec_min = Column(Float, nullable=True)
spec_max = Column(Float, nullable=True)
warning_min = Column(Float, nullable=True)
warning_max = Column(Float, nullable=True)
trend_window = Column(Integer, nullable=True)
select_options = Column(JSONB, nullable=True)
equipment_part_id = Column(
UUID(as_uuid=True), ForeignKey("equipment_parts.id"), nullable=True
)
is_required = Column(Boolean, default=True)
created_at = Column(TIMESTAMP(timezone=True), default=utcnow)
template = relationship("InspectionTemplate", back_populates="items")
equipment_part = relationship("EquipmentPart")
__table_args__ = (
Index("ix_template_items_template_order", "template_id", "sort_order"),
)