Files
TimeTracker/migrations/versions/024_add_client_notes_table.py
T
Dries Peeters 935f30e4d6 feat: Add Client Notes feature for internal client tracking
Implement comprehensive client notes system allowing users to add
internal notes about clients that are never visible to clients
themselves. Notes support importance flagging, full CRUD operations,
and proper access controls.

Key Changes:
- Add ClientNote model with user/client relationships
- Create Alembic migration (025) for client_notes table
- Implement full REST API with 9 endpoints
- Add client_notes blueprint with CRUD routes
- Create UI templates (edit page + notes section on client view)
- Add importance toggle with AJAX functionality
- Implement permission system (users edit own, admins edit all)

Features:
- Internal-only notes with rich text support
- Mark notes as important for quick identification
- Author tracking with timestamps
- Cascade delete when client is removed
- Mobile-responsive design
- i18n support for all user-facing text

Testing:
- 24 comprehensive model tests
- 23 route/integration tests
- Full coverage of CRUD operations and permissions

Documentation:
- Complete feature guide in docs/CLIENT_NOTES_FEATURE.md
- API documentation with examples
- Troubleshooting section
- Updated main docs index

Database:
- Migration revision 025 (depends on 024)
- Fixed PostgreSQL boolean default value issue
- 4 indexes for query performance
- CASCADE delete constraint on client_id

This feature addresses the need for teams to track important
information about clients internally without exposing sensitive
notes to client-facing interfaces or documents.
2025-10-24 08:37:51 +02:00

72 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Add client notes table for internal notes about clients
Revision ID: 025
Revises: 024
Create Date: 2025-10-24 00:00:00
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '025'
down_revision = '024'
branch_labels = None
depends_on = None
def upgrade() -> None:
"""Create client_notes table"""
bind = op.get_bind()
inspector = sa.inspect(bind)
# Check if client_notes table already exists
if 'client_notes' not in inspector.get_table_names():
op.create_table('client_notes',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('client_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('is_important', sa.Boolean(), nullable=False, server_default=sa.text('false')),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['client_id'], ['clients.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
# Create indexes for better performance
op.create_index('ix_client_notes_client_id', 'client_notes', ['client_id'], unique=False)
op.create_index('ix_client_notes_user_id', 'client_notes', ['user_id'], unique=False)
op.create_index('ix_client_notes_created_at', 'client_notes', ['created_at'], unique=False)
op.create_index('ix_client_notes_is_important', 'client_notes', ['is_important'], unique=False)
print("✓ Created client_notes table")
else:
print(" client_notes table already exists")
def downgrade() -> None:
"""Drop client_notes table"""
bind = op.get_bind()
inspector = sa.inspect(bind)
# Check if client_notes table exists before trying to drop it
if 'client_notes' in inspector.get_table_names():
try:
# Drop indexes first
op.drop_index('ix_client_notes_is_important', table_name='client_notes')
op.drop_index('ix_client_notes_created_at', table_name='client_notes')
op.drop_index('ix_client_notes_user_id', table_name='client_notes')
op.drop_index('ix_client_notes_client_id', table_name='client_notes')
# Drop the table
op.drop_table('client_notes')
print("✓ Dropped client_notes table")
except Exception as e:
print(f"⚠ Warning dropping client_notes table: {e}")
else:
print(" client_notes table does not exist")