feat: add inventory transaction audit trail (Phase 2)
Adds complete audit trail for all stock movements: - InventoryTransaction model with transaction types (reserve, fulfill, release, adjust, set, import, return) - Alembic migration for inventory_transactions table - Transaction logging in order_inventory_service for all order operations - Captures quantity snapshots, order references, and timestamps Each inventory operation now creates a transaction record for accountability and debugging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"""Add inventory_transactions table
|
||||
|
||||
Revision ID: o3c4d5e6f7a8
|
||||
Revises: n2c3d4e5f6a7
|
||||
Create Date: 2026-01-01
|
||||
|
||||
Adds an audit trail for inventory movements:
|
||||
- Track all stock changes (reserve, fulfill, release, adjust, set)
|
||||
- Link transactions to orders for traceability
|
||||
- Store quantity snapshots for historical analysis
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "o3c4d5e6f7a8"
|
||||
down_revision = "n2c3d4e5f6a7"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Create transaction type enum
|
||||
transaction_type_enum = sa.Enum(
|
||||
"reserve",
|
||||
"fulfill",
|
||||
"release",
|
||||
"adjust",
|
||||
"set",
|
||||
"import",
|
||||
"return",
|
||||
name="transactiontype",
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"inventory_transactions",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("vendor_id", sa.Integer(), nullable=False),
|
||||
sa.Column("product_id", sa.Integer(), nullable=False),
|
||||
sa.Column("inventory_id", sa.Integer(), nullable=True),
|
||||
sa.Column("transaction_type", transaction_type_enum, nullable=False),
|
||||
sa.Column("quantity_change", sa.Integer(), nullable=False),
|
||||
sa.Column("quantity_after", sa.Integer(), nullable=False),
|
||||
sa.Column("reserved_after", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("location", sa.String(), nullable=True),
|
||||
sa.Column("warehouse", sa.String(), nullable=True),
|
||||
sa.Column("order_id", sa.Integer(), nullable=True),
|
||||
sa.Column("order_number", sa.String(), nullable=True),
|
||||
sa.Column("reason", sa.Text(), nullable=True),
|
||||
sa.Column("created_by", sa.String(), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
sa.ForeignKeyConstraint(["vendor_id"], ["vendors.id"]),
|
||||
sa.ForeignKeyConstraint(["product_id"], ["products.id"]),
|
||||
sa.ForeignKeyConstraint(["inventory_id"], ["inventory.id"]),
|
||||
sa.ForeignKeyConstraint(["order_id"], ["orders.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
# Create indexes
|
||||
op.create_index(
|
||||
"ix_inventory_transactions_id",
|
||||
"inventory_transactions",
|
||||
["id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_inventory_transactions_vendor_id",
|
||||
"inventory_transactions",
|
||||
["vendor_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_inventory_transactions_product_id",
|
||||
"inventory_transactions",
|
||||
["product_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_inventory_transactions_inventory_id",
|
||||
"inventory_transactions",
|
||||
["inventory_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_inventory_transactions_transaction_type",
|
||||
"inventory_transactions",
|
||||
["transaction_type"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_inventory_transactions_order_id",
|
||||
"inventory_transactions",
|
||||
["order_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_inventory_transactions_created_at",
|
||||
"inventory_transactions",
|
||||
["created_at"],
|
||||
)
|
||||
op.create_index(
|
||||
"idx_inv_tx_vendor_product",
|
||||
"inventory_transactions",
|
||||
["vendor_id", "product_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"idx_inv_tx_vendor_created",
|
||||
"inventory_transactions",
|
||||
["vendor_id", "created_at"],
|
||||
)
|
||||
op.create_index(
|
||||
"idx_inv_tx_type_created",
|
||||
"inventory_transactions",
|
||||
["transaction_type", "created_at"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("idx_inv_tx_type_created", table_name="inventory_transactions")
|
||||
op.drop_index("idx_inv_tx_vendor_created", table_name="inventory_transactions")
|
||||
op.drop_index("idx_inv_tx_vendor_product", table_name="inventory_transactions")
|
||||
op.drop_index(
|
||||
"ix_inventory_transactions_created_at", table_name="inventory_transactions"
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_inventory_transactions_order_id", table_name="inventory_transactions"
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_inventory_transactions_transaction_type", table_name="inventory_transactions"
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_inventory_transactions_inventory_id", table_name="inventory_transactions"
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_inventory_transactions_product_id", table_name="inventory_transactions"
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_inventory_transactions_vendor_id", table_name="inventory_transactions"
|
||||
)
|
||||
op.drop_index("ix_inventory_transactions_id", table_name="inventory_transactions")
|
||||
op.drop_table("inventory_transactions")
|
||||
|
||||
# Drop enum
|
||||
sa.Enum(name="transactiontype").drop(op.get_bind(), checkfirst=True)
|
||||
Reference in New Issue
Block a user