Files
TimeTrack/routes/company.py

341 lines
16 KiB
Python

"""
Company management routes
"""
from flask import Blueprint, render_template, request, redirect, url_for, flash, g, session
from models import db, Company, User, Role, Team, Project, SystemSettings, CompanyWorkConfig, WorkRegion
from routes.auth import admin_required, company_required, login_required
import logging
import re
logger = logging.getLogger(__name__)
companies_bp = Blueprint('companies', __name__, url_prefix='/admin/company')
@companies_bp.route('', methods=['GET', 'POST'])
@admin_required
@company_required
def admin_company():
"""View and manage company settings"""
company = g.company
# Handle form submissions
if request.method == 'POST':
action = request.form.get('action')
if action == 'update_company_details':
# Handle company details update
name = request.form.get('name')
description = request.form.get('description', '')
max_users = request.form.get('max_users')
is_active = 'is_active' in request.form
# Validate input
error = None
if not name:
error = 'Company name is required'
elif name != company.name and Company.query.filter_by(name=name).first():
error = 'Company name already exists'
if max_users:
try:
max_users = int(max_users)
if max_users < 1:
error = 'Maximum users must be at least 1'
except ValueError:
error = 'Maximum users must be a valid number'
else:
max_users = None
if error is None:
company.name = name
company.description = description
company.max_users = max_users
company.is_active = is_active
db.session.commit()
flash('Company details updated successfully!', 'success')
else:
flash(error, 'error')
return redirect(url_for('companies.admin_company'))
elif action == 'update_system_settings':
# Update registration setting
registration_enabled = 'registration_enabled' in request.form
reg_setting = SystemSettings.query.filter_by(key='registration_enabled').first()
if reg_setting:
reg_setting.value = 'true' if registration_enabled else 'false'
# Update email verification setting
email_verification_required = 'email_verification_required' in request.form
email_setting = SystemSettings.query.filter_by(key='email_verification_required').first()
if email_setting:
email_setting.value = 'true' if email_verification_required else 'false'
db.session.commit()
flash('System settings updated successfully!', 'success')
return redirect(url_for('companies.admin_company'))
elif action == 'update_work_policies':
# Get or create company work config
work_config = CompanyWorkConfig.query.filter_by(company_id=g.user.company_id).first()
if not work_config:
# Create default config for the company
preset = CompanyWorkConfig.get_regional_preset(WorkRegion.GERMANY)
work_config = CompanyWorkConfig(
company_id=g.user.company_id,
standard_hours_per_day=preset['standard_hours_per_day'],
standard_hours_per_week=preset['standard_hours_per_week'],
work_region=WorkRegion.GERMANY,
overtime_enabled=preset['overtime_enabled'],
overtime_rate=preset['overtime_rate'],
double_time_enabled=preset['double_time_enabled'],
double_time_threshold=preset['double_time_threshold'],
double_time_rate=preset['double_time_rate'],
require_breaks=preset['require_breaks'],
break_duration_minutes=preset['break_duration_minutes'],
break_after_hours=preset['break_after_hours'],
weekly_overtime_threshold=preset['weekly_overtime_threshold'],
weekly_overtime_rate=preset['weekly_overtime_rate']
)
db.session.add(work_config)
db.session.flush()
try:
# Handle regional preset selection
if request.form.get('apply_preset'):
region_code = request.form.get('region_preset')
if region_code:
region = WorkRegion(region_code)
preset = CompanyWorkConfig.get_regional_preset(region)
work_config.standard_hours_per_day = preset['standard_hours_per_day']
work_config.standard_hours_per_week = preset['standard_hours_per_week']
work_config.work_region = region
work_config.overtime_enabled = preset['overtime_enabled']
work_config.overtime_rate = preset['overtime_rate']
work_config.double_time_enabled = preset['double_time_enabled']
work_config.double_time_threshold = preset['double_time_threshold']
work_config.double_time_rate = preset['double_time_rate']
work_config.require_breaks = preset['require_breaks']
work_config.break_duration_minutes = preset['break_duration_minutes']
work_config.break_after_hours = preset['break_after_hours']
work_config.weekly_overtime_threshold = preset['weekly_overtime_threshold']
work_config.weekly_overtime_rate = preset['weekly_overtime_rate']
db.session.commit()
flash(f'Applied {preset["region_name"]} work policy preset', 'success')
else:
# Handle manual configuration update
work_config.standard_hours_per_day = float(request.form.get('standard_hours_per_day', 8.0))
work_config.standard_hours_per_week = float(request.form.get('standard_hours_per_week', 40.0))
work_config.overtime_enabled = request.form.get('overtime_enabled') == 'on'
work_config.overtime_rate = float(request.form.get('overtime_rate', 1.5))
work_config.double_time_enabled = request.form.get('double_time_enabled') == 'on'
work_config.double_time_threshold = float(request.form.get('double_time_threshold', 12.0))
work_config.double_time_rate = float(request.form.get('double_time_rate', 2.0))
work_config.require_breaks = request.form.get('require_breaks') == 'on'
work_config.break_duration_minutes = int(request.form.get('break_duration_minutes', 30))
work_config.break_after_hours = float(request.form.get('break_after_hours', 6.0))
work_config.weekly_overtime_threshold = float(request.form.get('weekly_overtime_threshold', 40.0))
work_config.weekly_overtime_rate = float(request.form.get('weekly_overtime_rate', 1.5))
work_config.work_region = WorkRegion.OTHER
# region_name removed - using work_region enum value instead
db.session.commit()
flash('Work policies updated successfully!', 'success')
except ValueError:
flash('Please enter valid numbers for all fields', 'error')
return redirect(url_for('companies.admin_company'))
# Get company statistics
stats = {
'total_users': User.query.filter_by(company_id=company.id).count(),
'total_teams': Team.query.filter_by(company_id=company.id).count(),
'total_projects': Project.query.filter_by(company_id=company.id).count(),
'active_projects': Project.query.filter_by(company_id=company.id, is_active=True).count(),
}
# Get current system settings
settings = {}
for setting in SystemSettings.query.all():
if setting.key == 'registration_enabled':
settings['registration_enabled'] = setting.value == 'true'
elif setting.key == 'email_verification_required':
settings['email_verification_required'] = setting.value == 'true'
# Get or create company work config
work_config = CompanyWorkConfig.query.filter_by(company_id=g.user.company_id).first()
if not work_config:
# Create default config for the company
preset = CompanyWorkConfig.get_regional_preset(WorkRegion.GERMANY)
work_config = CompanyWorkConfig(
company_id=g.user.company_id,
standard_hours_per_day=preset['standard_hours_per_day'],
standard_hours_per_week=preset['standard_hours_per_week'],
work_region=WorkRegion.GERMANY,
overtime_enabled=preset['overtime_enabled'],
overtime_rate=preset['overtime_rate'],
double_time_enabled=preset['double_time_enabled'],
double_time_threshold=preset['double_time_threshold'],
double_time_rate=preset['double_time_rate'],
require_breaks=preset['require_breaks'],
break_duration_minutes=preset['break_duration_minutes'],
break_after_hours=preset['break_after_hours'],
weekly_overtime_threshold=preset['weekly_overtime_threshold'],
weekly_overtime_rate=preset['weekly_overtime_rate']
)
db.session.add(work_config)
db.session.commit()
# Get available regional presets
regional_presets = []
for region in WorkRegion:
preset = CompanyWorkConfig.get_regional_preset(region)
regional_presets.append({
'code': region.value,
'name': preset['region_name'],
'description': f"{preset['standard_hours_per_day']}h/day, {preset['break_duration_minutes']}min break after {preset['break_after_hours']}h"
})
return render_template('admin_company.html',
title='Company Management',
company=company,
stats=stats,
settings=settings,
work_config=work_config,
regional_presets=regional_presets,
WorkRegion=WorkRegion)
@companies_bp.route('/users')
@admin_required
@company_required
def company_users():
"""Redirect to the unified organization management page"""
return redirect(url_for('organization.admin_organization'))
# Setup company route (separate from company blueprint due to different URL)
def setup_company():
"""Company setup route for creating new companies with admin users"""
existing_companies = Company.query.count()
# Determine access level
is_initial_setup = existing_companies == 0
is_system_admin = g.user and g.user.role == Role.SYSTEM_ADMIN
is_authorized = is_initial_setup or is_system_admin
# Check authorization for non-initial setups
if not is_initial_setup and not is_system_admin:
flash('You do not have permission to create new companies.', 'error')
return redirect(url_for('home') if g.user else url_for('login'))
if request.method == 'POST':
company_name = request.form.get('company_name')
company_description = request.form.get('company_description', '')
admin_username = request.form.get('admin_username')
admin_email = request.form.get('admin_email')
admin_password = request.form.get('admin_password')
confirm_password = request.form.get('confirm_password')
# Validate input
error = None
if not company_name:
error = 'Company name is required'
elif not admin_username:
error = 'Admin username is required'
elif not admin_email:
error = 'Admin email is required'
elif not admin_password:
error = 'Admin password is required'
elif admin_password != confirm_password:
error = 'Passwords do not match'
elif len(admin_password) < 6:
error = 'Password must be at least 6 characters long'
if error is None:
try:
# Generate company slug
slug = re.sub(r'[^\w\s-]', '', company_name.lower())
slug = re.sub(r'[-\s]+', '-', slug).strip('-')
# Ensure slug uniqueness
base_slug = slug
counter = 1
while Company.query.filter_by(slug=slug).first():
slug = f"{base_slug}-{counter}"
counter += 1
# Create company
company = Company(
name=company_name,
slug=slug,
description=company_description,
is_active=True
)
db.session.add(company)
db.session.flush() # Get company.id without committing
# Check if username/email already exists in this company context
existing_user_by_username = User.query.filter_by(
username=admin_username,
company_id=company.id
).first()
existing_user_by_email = User.query.filter_by(
email=admin_email,
company_id=company.id
).first()
if existing_user_by_username:
error = 'Username already exists in this company'
elif existing_user_by_email:
error = 'Email already registered in this company'
if error is None:
# Create admin user
admin_user = User(
username=admin_username,
email=admin_email,
company_id=company.id,
role=Role.ADMIN,
is_verified=True # Auto-verify company admin
)
admin_user.set_password(admin_password)
db.session.add(admin_user)
db.session.commit()
if is_initial_setup:
# Auto-login the admin user for initial setup
session['user_id'] = admin_user.id
session['username'] = admin_user.username
session['role'] = admin_user.role.value
flash(f'Company "{company_name}" created successfully! You are now logged in as the administrator.', 'success')
return redirect(url_for('home'))
else:
# For super admin creating additional companies, don't auto-login
flash(f'Company "{company_name}" created successfully! Admin user "{admin_username}" has been created with the company code "{slug}".', 'success')
return redirect(url_for('companies.admin_company') if g.user else url_for('login'))
else:
db.session.rollback()
except Exception as e:
db.session.rollback()
logger.error(f"Error during company setup: {str(e)}")
error = f"An error occurred during setup: {str(e)}"
if error:
flash(error, 'error')
return render_template('setup_company.html',
title='Company Setup',
existing_companies=existing_companies,
is_initial_setup=is_initial_setup,
is_super_admin=is_system_admin)