Files
factoryOps-v2/alembic/versions/247755b06e67_add_equipment_parts_counters_.py
Johngreen ab2a3e35b2 feat: Phase 0-2 complete — auth, machines, equipment parts with full CRUD
Multi-tenant factory inspection system (SpiFox, Enkid, Alpet):
- FastAPI backend with JWT auth, PostgreSQL (asyncpg)
- Next.js 16 frontend with App Router, SWR data fetching
- Machines CRUD with equipment parts management
- Part lifecycle tracking (hours/count/date) with counters
- Partial unique index for soft-delete support
- 24 pytest tests passing, E2E verified

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-02-10 12:05:22 +09:00

93 lines
4.7 KiB
Python

"""add_equipment_parts_counters_replacement_logs
Revision ID: 247755b06e67
Revises: f39c75bd9514
Create Date: 2026-02-10 11:29:35.848537
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = '247755b06e67'
down_revision: Union[str, Sequence[str], None] = 'f39c75bd9514'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('equipment_parts',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.String(length=50), nullable=False),
sa.Column('machine_id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(length=100), nullable=False),
sa.Column('part_number', sa.String(length=50), nullable=True),
sa.Column('category', sa.String(length=50), nullable=True),
sa.Column('lifecycle_type', sa.String(length=20), nullable=False),
sa.Column('lifecycle_limit', sa.Float(), nullable=False),
sa.Column('alarm_threshold', sa.Float(), nullable=True),
sa.Column('counter_source', sa.String(length=20), nullable=True),
sa.Column('installed_at', postgresql.TIMESTAMP(timezone=True), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['machine_id'], ['machines.id'], ),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'machine_id', 'name', name='uq_equipment_part_tenant_machine_name')
)
op.create_index('ix_equipment_parts_tenant_id', 'equipment_parts', ['tenant_id'], unique=False)
op.create_index('ix_equipment_parts_tenant_machine', 'equipment_parts', ['tenant_id', 'machine_id'], unique=False)
op.create_table('part_counters',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.String(length=50), nullable=False),
sa.Column('equipment_part_id', sa.UUID(), nullable=False),
sa.Column('current_value', sa.Float(), nullable=True),
sa.Column('lifecycle_pct', sa.Float(), nullable=True),
sa.Column('last_reset_at', postgresql.TIMESTAMP(timezone=True), nullable=False),
sa.Column('last_updated_at', postgresql.TIMESTAMP(timezone=True), nullable=False),
sa.Column('version', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['equipment_part_id'], ['equipment_parts.id'], ),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('equipment_part_id')
)
op.create_index('ix_part_counters_tenant_id', 'part_counters', ['tenant_id'], unique=False)
op.create_table('part_replacement_logs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.String(length=50), nullable=False),
sa.Column('equipment_part_id', sa.UUID(), nullable=False),
sa.Column('replaced_by', sa.UUID(), nullable=True),
sa.Column('replaced_at', postgresql.TIMESTAMP(timezone=True), nullable=False),
sa.Column('counter_at_replacement', sa.Float(), nullable=False),
sa.Column('lifecycle_pct_at_replacement', sa.Float(), nullable=False),
sa.Column('reason', sa.String(length=200), nullable=True),
sa.Column('notes', sa.Text(), nullable=True),
sa.ForeignKeyConstraint(['equipment_part_id'], ['equipment_parts.id'], ),
sa.ForeignKeyConstraint(['replaced_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index('ix_part_replacement_tenant_date', 'part_replacement_logs', ['tenant_id', 'replaced_at'], unique=False)
op.create_index('ix_part_replacement_tenant_part', 'part_replacement_logs', ['tenant_id', 'equipment_part_id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index('ix_part_replacement_tenant_part', table_name='part_replacement_logs')
op.drop_index('ix_part_replacement_tenant_date', table_name='part_replacement_logs')
op.drop_table('part_replacement_logs')
op.drop_index('ix_part_counters_tenant_id', table_name='part_counters')
op.drop_table('part_counters')
op.drop_index('ix_equipment_parts_tenant_machine', table_name='equipment_parts')
op.drop_index('ix_equipment_parts_tenant_id', table_name='equipment_parts')
op.drop_table('equipment_parts')
# ### end Alembic commands ###