feat: implement company-based ownership architecture

- Add database migration to make vendor.owner_user_id nullable
- Update Vendor model to support company-based ownership (DEPRECATED vendor.owner_user_id)
- Implement company_service with singleton pattern (consistent with vendor_service)
- Create Company model with proper relationships to vendors and users
- Add company exception classes for proper error handling
- Refactor companies API to use singleton service pattern

Architecture Change:
- OLD: Each vendor has its own owner (vendor.owner_user_id)
- NEW: Vendors belong to a company, company has one owner (company.owner_user_id)
- This allows one company owner to manage multiple vendor brands

Technical Details:
- Company service uses singleton pattern (not factory)
- Company service accepts db: Session as parameter (follows SVC-003)
- Uses AuthManager for password hashing (consistent with admin_service)
- Added _generate_temp_password() helper method
This commit is contained in:
2025-12-01 21:50:09 +01:00
parent 281181d7ea
commit 4ca738dc7f
7 changed files with 1018 additions and 19 deletions

View File

@@ -0,0 +1,48 @@
"""make_vendor_owner_user_id_nullable_for_company_ownership
Revision ID: 5818330181a5
Revises: d0325d7c0f25
Create Date: 2025-12-01 20:30:06.158027
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '5818330181a5'
down_revision: Union[str, None] = 'd0325d7c0f25'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""
Make vendor.owner_user_id nullable to support company-level ownership.
Architecture Change:
- OLD: Each vendor has its own owner (vendor.owner_user_id)
- NEW: Vendors belong to a company, company has one owner (company.owner_user_id)
This allows one company owner to manage multiple vendor brands.
"""
# Use batch operations for SQLite compatibility
with op.batch_alter_table('vendors', schema=None) as batch_op:
batch_op.alter_column('owner_user_id',
existing_type=sa.INTEGER(),
nullable=True)
def downgrade() -> None:
"""
Revert vendor.owner_user_id to non-nullable.
WARNING: This will fail if there are vendors without owner_user_id!
"""
# Use batch operations for SQLite compatibility
with op.batch_alter_table('vendors', schema=None) as batch_op:
batch_op.alter_column('owner_user_id',
existing_type=sa.INTEGER(),
nullable=False)