mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-06 03:30:25 -06:00
Major Features: - Add project costs feature with full CRUD operations - Implement toast notification system for better user feedback - Enhance analytics dashboard with improved visualizations - Add OIDC authentication improvements and debug tools Improvements: - Enhance reports with new filtering and export capabilities - Update command palette with additional shortcuts - Improve mobile responsiveness across all pages - Refactor UI components for consistency Removals: - Remove license server integration and related dependencies - Clean up unused license-related templates and utilities Technical Changes: - Add new migration 018 for project_costs table - Update models: Project, Settings, User with new relationships - Refactor routes: admin, analytics, auth, invoices, projects, reports - Update static assets: CSS improvements, new JS modules - Enhance templates: analytics, admin, projects, reports Documentation: - Add comprehensive documentation for project costs feature - Document toast notification system with visual guides - Update README with new feature descriptions - Add migration instructions and quick start guides - Document OIDC improvements and Kanban enhancements Files Changed: - Modified: 56 files (core app, models, routes, templates, static assets) - Deleted: 6 files (license server integration) - Added: 28 files (new features, documentation, migrations)
167 lines
8.1 KiB
Python
167 lines
8.1 KiB
Python
from datetime import datetime
|
|
from app import db
|
|
from app.config import Config
|
|
import os
|
|
|
|
class Settings(db.Model):
|
|
"""Settings model for system configuration"""
|
|
|
|
__tablename__ = 'settings'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
timezone = db.Column(db.String(50), default='Europe/Rome', nullable=False)
|
|
currency = db.Column(db.String(3), default='EUR', nullable=False)
|
|
rounding_minutes = db.Column(db.Integer, default=1, nullable=False)
|
|
single_active_timer = db.Column(db.Boolean, default=True, nullable=False)
|
|
allow_self_register = db.Column(db.Boolean, default=True, nullable=False)
|
|
idle_timeout_minutes = db.Column(db.Integer, default=30, nullable=False)
|
|
backup_retention_days = db.Column(db.Integer, default=30, nullable=False)
|
|
backup_time = db.Column(db.String(5), default='02:00', nullable=False) # HH:MM format
|
|
export_delimiter = db.Column(db.String(1), default=',', nullable=False)
|
|
|
|
# Company branding for invoices
|
|
company_name = db.Column(db.String(200), default='Your Company Name', nullable=False)
|
|
company_address = db.Column(db.Text, default='Your Company Address', nullable=False)
|
|
company_email = db.Column(db.String(200), default='info@yourcompany.com', nullable=False)
|
|
company_phone = db.Column(db.String(50), default='+1 (555) 123-4567', nullable=False)
|
|
company_website = db.Column(db.String(200), default='www.yourcompany.com', nullable=False)
|
|
company_logo_filename = db.Column(db.String(255), default='', nullable=True) # Changed from company_logo_path
|
|
company_tax_id = db.Column(db.String(100), default='', nullable=True)
|
|
company_bank_info = db.Column(db.Text, default='', nullable=True)
|
|
|
|
# PDF template customization
|
|
invoice_pdf_template_html = db.Column(db.Text, default='', nullable=True)
|
|
invoice_pdf_template_css = db.Column(db.Text, default='', nullable=True)
|
|
|
|
# Invoice defaults
|
|
invoice_prefix = db.Column(db.String(10), default='INV', nullable=False)
|
|
invoice_start_number = db.Column(db.Integer, default=1000, nullable=False)
|
|
invoice_terms = db.Column(db.Text, default='Payment is due within 30 days of invoice date.', nullable=False)
|
|
invoice_notes = db.Column(db.Text, default='Thank you for your business!', nullable=False)
|
|
|
|
# Privacy and analytics settings
|
|
allow_analytics = db.Column(db.Boolean, default=True, nullable=False) # Controls system info sharing for analytics
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
|
|
|
def __init__(self, **kwargs):
|
|
# Set defaults from config
|
|
self.timezone = kwargs.get('timezone', Config.TZ)
|
|
self.currency = kwargs.get('currency', Config.CURRENCY)
|
|
self.rounding_minutes = kwargs.get('rounding_minutes', Config.ROUNDING_MINUTES)
|
|
self.single_active_timer = kwargs.get('single_active_timer', Config.SINGLE_ACTIVE_TIMER)
|
|
self.allow_self_register = kwargs.get('allow_self_register', Config.ALLOW_SELF_REGISTER)
|
|
self.idle_timeout_minutes = kwargs.get('idle_timeout_minutes', Config.IDLE_TIMEOUT_MINUTES)
|
|
self.backup_retention_days = kwargs.get('backup_retention_days', Config.BACKUP_RETENTION_DAYS)
|
|
self.backup_time = kwargs.get('backup_time', Config.BACKUP_TIME)
|
|
self.export_delimiter = kwargs.get('export_delimiter', ',')
|
|
|
|
# Set company branding defaults
|
|
self.company_name = kwargs.get('company_name', 'Your Company Name')
|
|
self.company_address = kwargs.get('company_address', 'Your Company Address')
|
|
self.company_email = kwargs.get('company_email', 'info@yourcompany.com')
|
|
self.company_phone = kwargs.get('company_phone', '+1 (555) 123-4567')
|
|
self.company_website = kwargs.get('company_website', 'www.yourcompany.com')
|
|
self.company_logo_filename = kwargs.get('company_logo_filename', '')
|
|
self.company_tax_id = kwargs.get('company_tax_id', '')
|
|
self.company_bank_info = kwargs.get('company_bank_info', '')
|
|
|
|
# PDF template customization
|
|
self.invoice_pdf_template_html = kwargs.get('invoice_pdf_template_html', '')
|
|
self.invoice_pdf_template_css = kwargs.get('invoice_pdf_template_css', '')
|
|
|
|
# Set invoice defaults
|
|
self.invoice_prefix = kwargs.get('invoice_prefix', 'INV')
|
|
self.invoice_start_number = kwargs.get('invoice_start_number', 1000)
|
|
self.invoice_terms = kwargs.get('invoice_terms', 'Payment is due within 30 days of invoice date.')
|
|
self.invoice_notes = kwargs.get('invoice_notes', 'Thank you for your business!')
|
|
|
|
def __repr__(self):
|
|
return f'<Settings {self.id}>'
|
|
|
|
def get_logo_url(self):
|
|
"""Get the full URL for the company logo"""
|
|
if self.company_logo_filename:
|
|
return f'/uploads/logos/{self.company_logo_filename}'
|
|
return None
|
|
|
|
def get_logo_path(self):
|
|
"""Get the full file system path for the company logo"""
|
|
if not self.company_logo_filename:
|
|
return None
|
|
|
|
try:
|
|
from flask import current_app
|
|
upload_folder = os.path.join(current_app.root_path, 'static', 'uploads', 'logos')
|
|
return os.path.join(upload_folder, self.company_logo_filename)
|
|
except RuntimeError:
|
|
# current_app not available (e.g., during testing or initialization)
|
|
# Fallback to a relative path
|
|
return os.path.join('app', 'static', 'uploads', 'logos', self.company_logo_filename)
|
|
|
|
def has_logo(self):
|
|
"""Check if company has a logo uploaded"""
|
|
if not self.company_logo_filename:
|
|
return False
|
|
|
|
logo_path = self.get_logo_path()
|
|
return logo_path and os.path.exists(logo_path)
|
|
|
|
def to_dict(self):
|
|
"""Convert settings to dictionary for API responses"""
|
|
return {
|
|
'id': self.id,
|
|
'timezone': self.timezone,
|
|
'currency': self.currency,
|
|
'rounding_minutes': self.rounding_minutes,
|
|
'single_active_timer': self.single_active_timer,
|
|
'allow_self_register': self.allow_self_register,
|
|
'idle_timeout_minutes': self.idle_timeout_minutes,
|
|
'backup_retention_days': self.backup_retention_days,
|
|
'backup_time': self.backup_time,
|
|
'export_delimiter': self.export_delimiter,
|
|
'company_name': self.company_name,
|
|
'company_address': self.company_address,
|
|
'company_email': self.company_email,
|
|
'company_phone': self.company_phone,
|
|
'company_website': self.company_website,
|
|
'company_logo_filename': self.company_logo_filename,
|
|
'company_logo_url': self.get_logo_url(),
|
|
'has_logo': self.has_logo(),
|
|
'company_tax_id': self.company_tax_id,
|
|
'company_bank_info': self.company_bank_info,
|
|
'invoice_prefix': self.invoice_prefix,
|
|
'invoice_start_number': self.invoice_start_number,
|
|
'invoice_terms': self.invoice_terms,
|
|
'invoice_notes': self.invoice_notes,
|
|
'invoice_pdf_template_html': self.invoice_pdf_template_html,
|
|
'invoice_pdf_template_css': self.invoice_pdf_template_css,
|
|
'allow_analytics': self.allow_analytics,
|
|
'created_at': self.created_at.isoformat() if self.created_at else None,
|
|
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
|
}
|
|
|
|
@classmethod
|
|
def get_settings(cls):
|
|
"""Get the singleton settings instance, creating it if it doesn't exist"""
|
|
settings = cls.query.first()
|
|
if not settings:
|
|
settings = cls()
|
|
db.session.add(settings)
|
|
db.session.commit()
|
|
return settings
|
|
|
|
@classmethod
|
|
def update_settings(cls, **kwargs):
|
|
"""Update settings with new values"""
|
|
settings = cls.get_settings()
|
|
|
|
for key, value in kwargs.items():
|
|
if hasattr(settings, key):
|
|
setattr(settings, key, value)
|
|
|
|
settings.updated_at = datetime.utcnow()
|
|
db.session.commit()
|
|
return settings
|