Implement comprehensive project time logging feature
Add complete project management system with role-based access control: **Core Features:** - Project creation and management for Admins/Supervisors - Time tracking with optional project selection and notes - Project-based filtering and reporting in history - Enhanced export functionality with project data - Team-specific project assignments **Database Changes:** - New Project model with full relationships - Enhanced TimeEntry model with project_id and notes - Updated migration scripts with rollback support - Sample project creation for testing **User Interface:** - Project management templates (create, edit, list) - Enhanced time tracking with project dropdown - Project filtering in history page - Updated navigation for role-based access - Modern styling with hover effects and responsive design **API Enhancements:** - Project validation and access control - Updated arrive endpoint with project support - Enhanced export functions with project data - Role-based route protection **Migration Support:** - Comprehensive migration scripts (migrate_projects.py) - Updated main migration script (migrate_db.py) - Detailed migration documentation - Rollback functionality for safe deployment **Role-Based Access:** - Admins: Full project CRUD operations - Supervisors: Project creation and management - Team Leaders: View team hours with projects - Team Members: Select projects when tracking time 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
184
migrate_projects.py
Normal file
184
migrate_projects.py
Normal file
@@ -0,0 +1,184 @@
|
||||
from app import app, db
|
||||
from models import User, TimeEntry, Project, Team, Role
|
||||
from sqlalchemy import text
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def migrate_projects():
|
||||
"""Migration script to add project time logging functionality"""
|
||||
with app.app_context():
|
||||
logger.info("Starting migration for project time logging...")
|
||||
|
||||
# Check if the project table exists
|
||||
try:
|
||||
# Create the project table if it doesn't exist
|
||||
db.engine.execute(text("""
|
||||
CREATE TABLE IF NOT EXISTS project (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
code VARCHAR(20) NOT NULL UNIQUE,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by_id INTEGER NOT NULL,
|
||||
team_id INTEGER,
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
FOREIGN KEY (created_by_id) REFERENCES user (id),
|
||||
FOREIGN KEY (team_id) REFERENCES team (id)
|
||||
)
|
||||
"""))
|
||||
logger.info("Project table created or already exists")
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating project table: {e}")
|
||||
return
|
||||
|
||||
# Check if the time_entry table has the project-related columns
|
||||
try:
|
||||
# Check if project_id and notes columns exist in time_entry
|
||||
result = db.engine.execute(text("PRAGMA table_info(time_entry)"))
|
||||
columns = [row[1] for row in result]
|
||||
|
||||
if 'project_id' not in columns:
|
||||
db.engine.execute(text("ALTER TABLE time_entry ADD COLUMN project_id INTEGER REFERENCES project(id)"))
|
||||
logger.info("Added project_id column to time_entry table")
|
||||
|
||||
if 'notes' not in columns:
|
||||
db.engine.execute(text("ALTER TABLE time_entry ADD COLUMN notes TEXT"))
|
||||
logger.info("Added notes column to time_entry table")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating time_entry table: {e}")
|
||||
return
|
||||
|
||||
# Create some default projects for demonstration
|
||||
try:
|
||||
# Check if any projects exist
|
||||
existing_projects = Project.query.count()
|
||||
if existing_projects == 0:
|
||||
# Find an admin or supervisor user to be the creator
|
||||
admin_user = User.query.filter_by(is_admin=True).first()
|
||||
if not admin_user:
|
||||
admin_user = User.query.filter(User.role.in_([Role.ADMIN, Role.SUPERVISOR])).first()
|
||||
|
||||
if admin_user:
|
||||
# Create some sample projects
|
||||
sample_projects = [
|
||||
{
|
||||
'name': 'General Administration',
|
||||
'code': 'ADMIN001',
|
||||
'description': 'General administrative tasks and meetings',
|
||||
'team_id': None, # Available to all teams
|
||||
},
|
||||
{
|
||||
'name': 'Development Project',
|
||||
'code': 'DEV001',
|
||||
'description': 'Software development and maintenance tasks',
|
||||
'team_id': None, # Available to all teams
|
||||
},
|
||||
{
|
||||
'name': 'Customer Support',
|
||||
'code': 'SUPPORT001',
|
||||
'description': 'Customer service and technical support activities',
|
||||
'team_id': None, # Available to all teams
|
||||
}
|
||||
]
|
||||
|
||||
for proj_data in sample_projects:
|
||||
project = Project(
|
||||
name=proj_data['name'],
|
||||
code=proj_data['code'],
|
||||
description=proj_data['description'],
|
||||
team_id=proj_data['team_id'],
|
||||
created_by_id=admin_user.id,
|
||||
is_active=True
|
||||
)
|
||||
db.session.add(project)
|
||||
|
||||
db.session.commit()
|
||||
logger.info(f"Created {len(sample_projects)} sample projects")
|
||||
else:
|
||||
logger.warning("No admin or supervisor user found to create sample projects")
|
||||
else:
|
||||
logger.info(f"Found {existing_projects} existing projects, skipping sample creation")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating sample projects: {e}")
|
||||
db.session.rollback()
|
||||
|
||||
# Update database schema to match the current models
|
||||
try:
|
||||
db.create_all()
|
||||
logger.info("Database schema updated successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating database schema: {e}")
|
||||
return
|
||||
|
||||
# Verify the migration
|
||||
try:
|
||||
# Check if we can query the new tables and columns
|
||||
project_count = Project.query.count()
|
||||
logger.info(f"Project table accessible with {project_count} projects")
|
||||
|
||||
# Check if time_entry has the new columns
|
||||
result = db.engine.execute(text("PRAGMA table_info(time_entry)"))
|
||||
columns = [row[1] for row in result]
|
||||
|
||||
required_columns = ['project_id', 'notes']
|
||||
missing_columns = [col for col in required_columns if col not in columns]
|
||||
|
||||
if missing_columns:
|
||||
logger.error(f"Missing columns in time_entry: {missing_columns}")
|
||||
return
|
||||
else:
|
||||
logger.info("All required columns present in time_entry table")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error verifying migration: {e}")
|
||||
return
|
||||
|
||||
logger.info("Project time logging migration completed successfully!")
|
||||
print("\n" + "="*60)
|
||||
print("PROJECT TIME LOGGING FEATURE ENABLED")
|
||||
print("="*60)
|
||||
print("✅ Project management interface available for Admins/Supervisors")
|
||||
print("✅ Time tracking with optional project selection")
|
||||
print("✅ Project-based reporting and filtering")
|
||||
print("✅ Enhanced export functionality with project data")
|
||||
print("\nAccess project management via:")
|
||||
print("- Admin dropdown → Manage Projects")
|
||||
print("- Supervisor dropdown → Manage Projects")
|
||||
print("="*60)
|
||||
|
||||
def rollback_projects():
|
||||
"""Rollback migration (removes project functionality)"""
|
||||
with app.app_context():
|
||||
logger.warning("Rolling back project time logging migration...")
|
||||
|
||||
try:
|
||||
# Drop the project table
|
||||
db.engine.execute(text("DROP TABLE IF EXISTS project"))
|
||||
logger.info("Dropped project table")
|
||||
|
||||
# Note: SQLite doesn't support dropping columns, so we can't remove
|
||||
# project_id and notes columns from time_entry table
|
||||
logger.warning("Note: project_id and notes columns in time_entry table cannot be removed due to SQLite limitations")
|
||||
logger.warning("These columns will remain but will not be used")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during rollback: {e}")
|
||||
return
|
||||
|
||||
logger.info("Project time logging rollback completed")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "rollback":
|
||||
rollback_projects()
|
||||
else:
|
||||
migrate_projects()
|
||||
Reference in New Issue
Block a user