Mar 21 2026: code refactory 3
This commit is contained in:
@@ -21,6 +21,8 @@ import time as _time
|
|||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
from extensions import db, init_logger
|
from extensions import db, init_logger
|
||||||
|
from config import get_config
|
||||||
|
from utils.template_helpers import register_template_helpers
|
||||||
from logger_handler import log_database_operations
|
from logger_handler import log_database_operations
|
||||||
from models import set_db
|
from models import set_db
|
||||||
from turnstile_utils import turnstile_utils
|
from turnstile_utils import turnstile_utils
|
||||||
@@ -43,21 +45,9 @@ def create_app() -> Flask:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Configuration
|
# Configuration
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
|
# Load configuration from config.py (single source of truth for env vars)
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL')
|
cfg = get_config()
|
||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = os.environ.get('SQLALCHEMY_TRACK_MODIFICATIONS')
|
app.config.from_object(cfg)
|
||||||
app.config['TEMPLATES_AUTO_RELOAD'] = os.environ.get('TEMPLATES_AUTO_RELOAD')
|
|
||||||
|
|
||||||
# Session / cookie configuration
|
|
||||||
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=30)
|
|
||||||
app.config['SESSION_COOKIE_SECURE'] = os.environ.get('SESSION_COOKIE_SECURE', 'false').lower() == 'true'
|
|
||||||
app.config['SESSION_COOKIE_HTTPONLY'] = os.environ.get('SESSION_COOKIE_HTTPONLY', 'true').lower() == 'true'
|
|
||||||
app.config['SESSION_COOKIE_SAMESITE'] = os.environ.get('SESSION_COOKIE_SAMESITE')
|
|
||||||
|
|
||||||
# Photo verification
|
|
||||||
app.config['PHOTO_VERIFICATION_ENABLED'] = os.environ.get('ENABLE_PHOTO_VERIFICATION', 'true').lower() == 'true'
|
|
||||||
app.config['DISTANCE_THRESHOLD_FOR_VERIFICATION'] = float(os.environ.get('PHOTO_VERIFICATION_DISTANCE_THRESHOLD', '0.3'))
|
|
||||||
app.config['VERIFICATION_PHOTO_MAX_SIZE'] = int(os.environ.get('VERIFICATION_PHOTO_MAX_SIZE', str(5 * 1024 * 1024)))
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Database initialization
|
# Database initialization
|
||||||
@@ -129,51 +119,8 @@ def create_app() -> Flask:
|
|||||||
'turnstile_site_key': turnstile_utils.get_site_key()
|
'turnstile_site_key': turnstile_utils.get_site_key()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Helper functions for context processors
|
# Register template helper context processors (from utils/template_helpers.py)
|
||||||
def get_employee_name(employee_id):
|
register_template_helpers(app)
|
||||||
"""Helper function to get employee full name by ID"""
|
|
||||||
from sqlalchemy import text as sa_text
|
|
||||||
try:
|
|
||||||
result = db.session.execute(sa_text("""
|
|
||||||
SELECT CONCAT(firstName, ' ', lastName) as full_name
|
|
||||||
FROM employee
|
|
||||||
WHERE id = :employee_id
|
|
||||||
"""), {'employee_id': employee_id})
|
|
||||||
row = result.fetchone()
|
|
||||||
return row[0] if row else f"Employee {employee_id}"
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠️ Error getting employee name for ID {employee_id}: {e}")
|
|
||||||
return f"Employee {employee_id}"
|
|
||||||
|
|
||||||
def get_qr_code_checkin_count(qr_code_id):
|
|
||||||
"""Helper function to get total check-ins count for a QR code"""
|
|
||||||
from models.attendance import AttendanceData
|
|
||||||
try:
|
|
||||||
count = AttendanceData.query.filter_by(qr_code_id=qr_code_id).count()
|
|
||||||
return count
|
|
||||||
except Exception as e:
|
|
||||||
from extensions import logger_handler as _lh
|
|
||||||
_lh.logger.error(f"Error getting check-ins count for QR {qr_code_id}: {e}")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
@app.context_processor
|
|
||||||
def inject_payroll_utils():
|
|
||||||
"""Inject payroll utility functions into templates"""
|
|
||||||
from working_hours_calculator import convert_minutes_to_base100, round_base100_hours
|
|
||||||
return {
|
|
||||||
'convert_minutes_to_base100': convert_minutes_to_base100,
|
|
||||||
'round_base100_hours': round_base100_hours,
|
|
||||||
'get_employee_name': get_employee_name,
|
|
||||||
'format_hours': lambda hours: f"{hours:.2f}" if hours else "0.00"
|
|
||||||
}
|
|
||||||
|
|
||||||
@app.context_processor
|
|
||||||
def inject_dashboard_utils():
|
|
||||||
"""Inject dashboard utility functions into templates"""
|
|
||||||
return {
|
|
||||||
'now': datetime.utcnow,
|
|
||||||
'get_qr_code_checkin_count': get_qr_code_checkin_count
|
|
||||||
}
|
|
||||||
|
|
||||||
@app.template_filter('strftime')
|
@app.template_filter('strftime')
|
||||||
def strftime_filter(value, format='%m/%d/%Y'):
|
def strftime_filter(value, format='%m/%d/%Y'):
|
||||||
@@ -319,7 +266,8 @@ def create_tables():
|
|||||||
from models.user import User
|
from models.user import User
|
||||||
admin = User.query.filter_by(username='admin').first()
|
admin = User.query.filter_by(username='admin').first()
|
||||||
if not admin:
|
if not admin:
|
||||||
default_password = os.environ.get('DEFAULT_ADMIN_PASSWORD', 'admin123')
|
from config import Config as _Cfg
|
||||||
|
default_password = _Cfg.DEFAULT_ADMIN_PASSWORD
|
||||||
admin = User(
|
admin = User(
|
||||||
full_name='System Administrator',
|
full_name='System Administrator',
|
||||||
email='admin@example.com',
|
email='admin@example.com',
|
||||||
@@ -436,9 +384,10 @@ if __name__ == '__main__':
|
|||||||
print(f"❌ Application startup failed: {e}")
|
print(f"❌ Application startup failed: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
from config import Config as _Cfg
|
||||||
app.run(
|
app.run(
|
||||||
debug=os.environ.get('DEBUG'),
|
debug=_Cfg.DEBUG,
|
||||||
host=os.environ.get('FLASK_HOST'),
|
host=_Cfg.FLASK_HOST,
|
||||||
port=os.environ.get('FLASK_PORT'),
|
port=_Cfg.FLASK_PORT,
|
||||||
threaded=os.environ.get('THREADED')
|
threaded=_Cfg.THREADED
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"""
|
||||||
|
config.py
|
||||||
|
=========
|
||||||
|
Centralised application configuration.
|
||||||
|
|
||||||
|
All environment variable reads happen here — once, at startup.
|
||||||
|
Blueprints and helpers that need a config value use:
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
value = current_app.config['KEY']
|
||||||
|
|
||||||
|
Or for values needed at module import time (before app context):
|
||||||
|
|
||||||
|
from config import Config
|
||||||
|
value = Config.COMPANY_NAME
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Core Flask
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
SECRET_KEY = os.environ.get('SECRET_KEY', 'change-me-in-production')
|
||||||
|
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', '')
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = (
|
||||||
|
os.environ.get('SQLALCHEMY_TRACK_MODIFICATIONS', 'False').lower() == 'true'
|
||||||
|
)
|
||||||
|
TEMPLATES_AUTO_RELOAD = (
|
||||||
|
os.environ.get('TEMPLATES_AUTO_RELOAD', 'True').lower() == 'true'
|
||||||
|
)
|
||||||
|
DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Session / cookies
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
PERMANENT_SESSION_LIFETIME = timedelta(days=30)
|
||||||
|
SESSION_COOKIE_SECURE = (
|
||||||
|
os.environ.get('SESSION_COOKIE_SECURE', 'false').lower() == 'true'
|
||||||
|
)
|
||||||
|
SESSION_COOKIE_HTTPONLY = (
|
||||||
|
os.environ.get('SESSION_COOKIE_HTTPONLY', 'true').lower() == 'true'
|
||||||
|
)
|
||||||
|
SESSION_COOKIE_SAMESITE = os.environ.get('SESSION_COOKIE_SAMESITE', 'Lax')
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Application identity
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
COMPANY_NAME = os.environ.get('COMPANY_NAME', 'QR Code Management System')
|
||||||
|
CONTRACT_NAME = os.environ.get('CONTRACT_NAME', 'Default Contract')
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# File uploads
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER', '/tmp')
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Photo verification
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
PHOTO_VERIFICATION_ENABLED = (
|
||||||
|
os.environ.get('ENABLE_PHOTO_VERIFICATION', 'true').lower() == 'true'
|
||||||
|
)
|
||||||
|
DISTANCE_THRESHOLD_FOR_VERIFICATION = float(
|
||||||
|
os.environ.get('PHOTO_VERIFICATION_DISTANCE_THRESHOLD', '0.3')
|
||||||
|
)
|
||||||
|
VERIFICATION_PHOTO_MAX_SIZE = int(
|
||||||
|
os.environ.get('VERIFICATION_PHOTO_MAX_SIZE', str(5 * 1024 * 1024))
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Check-in interval
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
TIME_INTERVAL = int(os.environ.get('TIME_INTERVAL', '30'))
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Server
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
FLASK_HOST = os.environ.get('FLASK_HOST', '0.0.0.0')
|
||||||
|
FLASK_PORT = int(os.environ.get('FLASK_PORT', '5000'))
|
||||||
|
THREADED = os.environ.get('THREADED', 'True').lower() == 'true'
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Default admin (used only on first boot)
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
DEFAULT_ADMIN_PASSWORD = os.environ.get('DEFAULT_ADMIN_PASSWORD', 'admin123')
|
||||||
|
|
||||||
|
|
||||||
|
class DevelopmentConfig(Config):
|
||||||
|
DEBUG = True
|
||||||
|
SESSION_COOKIE_SECURE = False
|
||||||
|
|
||||||
|
|
||||||
|
class ProductionConfig(Config):
|
||||||
|
DEBUG = False
|
||||||
|
TEMPLATES_AUTO_RELOAD = False
|
||||||
|
|
||||||
|
|
||||||
|
# Active config selected by environment variable
|
||||||
|
_config_map = {
|
||||||
|
'development': DevelopmentConfig,
|
||||||
|
'production': ProductionConfig,
|
||||||
|
'default': Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
"""Return the active Config class based on FLASK_ENV."""
|
||||||
|
env = os.environ.get('FLASK_ENV', 'default').lower()
|
||||||
|
return _config_map.get(env, Config)
|
||||||
+13
-13
@@ -6,7 +6,7 @@ Payroll dashboard and Excel export routes.
|
|||||||
Routes: /payroll, /payroll/export-excel, /api/working-hours/calculate,
|
Routes: /payroll, /payroll/export-excel, /api/working-hours/calculate,
|
||||||
/api/employee/<id>/miss-punch-details
|
/api/employee/<id>/miss-punch-details
|
||||||
"""
|
"""
|
||||||
from flask import Blueprint, render_template, request, redirect, flash, session, jsonify, send_file, url_for
|
from flask import Blueprint, render_template, request, redirect, flash, session, jsonify, send_file, url_for, current_app
|
||||||
from datetime import datetime, date, timedelta, time
|
from datetime import datetime, date, timedelta, time
|
||||||
import io, json, traceback, os
|
import io, json, traceback, os
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ def export_payroll_excel():
|
|||||||
print("📊 Creating enhanced payroll report with SP/PW support")
|
print("📊 Creating enhanced payroll report with SP/PW support")
|
||||||
try:
|
try:
|
||||||
from enhanced_payroll_excel_exporter import EnhancedPayrollExcelExporter
|
from enhanced_payroll_excel_exporter import EnhancedPayrollExcelExporter
|
||||||
exporter = EnhancedPayrollExcelExporter(company_name=os.environ.get('COMPANY_NAME', 'Your Company'))
|
exporter = EnhancedPayrollExcelExporter(company_name=current_app.config.get('COMPANY_NAME', 'QR Code Management System'))
|
||||||
excel_file = exporter.create_enhanced_payroll_report(
|
excel_file = exporter.create_enhanced_payroll_report(
|
||||||
start_date, end_date, attendance_records, employee_names, project_name
|
start_date, end_date, attendance_records, employee_names, project_name
|
||||||
)
|
)
|
||||||
@@ -302,8 +302,8 @@ def export_payroll_excel():
|
|||||||
print("⚠️ Enhanced exporter not available, falling back to standard exporter")
|
print("⚠️ Enhanced exporter not available, falling back to standard exporter")
|
||||||
# Fall back to standard exporter
|
# Fall back to standard exporter
|
||||||
exporter = PayrollExcelExporter(
|
exporter = PayrollExcelExporter(
|
||||||
company_name=os.environ.get('COMPANY_NAME', 'Your Company'),
|
company_name=current_app.config.get('COMPANY_NAME', 'QR Code Management System'),
|
||||||
contract_name=os.environ.get('CONTRACT_NAME', 'Default Contract')
|
contract_name=current_app.config.get('CONTRACT_NAME', 'Default Contract')
|
||||||
)
|
)
|
||||||
excel_file = exporter.create_payroll_report(
|
excel_file = exporter.create_payroll_report(
|
||||||
start_date, end_date, attendance_records, employee_names
|
start_date, end_date, attendance_records, employee_names
|
||||||
@@ -313,8 +313,8 @@ def export_payroll_excel():
|
|||||||
print(f"⚠️ Error with enhanced exporter: {e}, falling back to standard exporter")
|
print(f"⚠️ Error with enhanced exporter: {e}, falling back to standard exporter")
|
||||||
# Fall back to standard exporter
|
# Fall back to standard exporter
|
||||||
exporter = PayrollExcelExporter(
|
exporter = PayrollExcelExporter(
|
||||||
company_name=os.environ.get('COMPANY_NAME', 'Your Company'),
|
company_name=current_app.config.get('COMPANY_NAME', 'QR Code Management System'),
|
||||||
contract_name=os.environ.get('CONTRACT_NAME', 'Default Contract')
|
contract_name=current_app.config.get('CONTRACT_NAME', 'Default Contract')
|
||||||
)
|
)
|
||||||
excel_file = exporter.create_payroll_report(
|
excel_file = exporter.create_payroll_report(
|
||||||
start_date, end_date, attendance_records, employee_names
|
start_date, end_date, attendance_records, employee_names
|
||||||
@@ -326,7 +326,7 @@ def export_payroll_excel():
|
|||||||
print("📊 Creating detailed SP/PW daily breakdown report")
|
print("📊 Creating detailed SP/PW daily breakdown report")
|
||||||
try:
|
try:
|
||||||
from enhanced_payroll_excel_exporter import EnhancedPayrollExcelExporter
|
from enhanced_payroll_excel_exporter import EnhancedPayrollExcelExporter
|
||||||
exporter = EnhancedPayrollExcelExporter(company_name=os.environ.get('COMPANY_NAME', 'Your Company'))
|
exporter = EnhancedPayrollExcelExporter(company_name=current_app.config.get('COMPANY_NAME', 'QR Code Management System'))
|
||||||
excel_file = exporter.create_detailed_sp_pw_report(
|
excel_file = exporter.create_detailed_sp_pw_report(
|
||||||
start_date, end_date, attendance_records, employee_names
|
start_date, end_date, attendance_records, employee_names
|
||||||
)
|
)
|
||||||
@@ -336,8 +336,8 @@ def export_payroll_excel():
|
|||||||
print("⚠️ Enhanced exporter not available, falling back to detailed hours report")
|
print("⚠️ Enhanced exporter not available, falling back to detailed hours report")
|
||||||
# Fall back to standard detailed report
|
# Fall back to standard detailed report
|
||||||
exporter = PayrollExcelExporter(
|
exporter = PayrollExcelExporter(
|
||||||
company_name=os.environ.get('COMPANY_NAME', 'Your Company'),
|
company_name=current_app.config.get('COMPANY_NAME', 'QR Code Management System'),
|
||||||
contract_name=os.environ.get('CONTRACT_NAME', 'Default Contract')
|
contract_name=current_app.config.get('CONTRACT_NAME', 'Default Contract')
|
||||||
)
|
)
|
||||||
excel_file = exporter.create_detailed_hours_report(
|
excel_file = exporter.create_detailed_hours_report(
|
||||||
start_date, end_date, attendance_records, employee_names
|
start_date, end_date, attendance_records, employee_names
|
||||||
@@ -347,8 +347,8 @@ def export_payroll_excel():
|
|||||||
print(f"⚠️ Error with enhanced exporter: {e}, falling back to detailed hours report")
|
print(f"⚠️ Error with enhanced exporter: {e}, falling back to detailed hours report")
|
||||||
# Fall back to standard detailed report
|
# Fall back to standard detailed report
|
||||||
exporter = PayrollExcelExporter(
|
exporter = PayrollExcelExporter(
|
||||||
company_name=os.environ.get('COMPANY_NAME', 'Your Company'),
|
company_name=current_app.config.get('COMPANY_NAME', 'QR Code Management System'),
|
||||||
contract_name=os.environ.get('CONTRACT_NAME', 'Default Contract')
|
contract_name=current_app.config.get('CONTRACT_NAME', 'Default Contract')
|
||||||
)
|
)
|
||||||
excel_file = exporter.create_detailed_hours_report(
|
excel_file = exporter.create_detailed_hours_report(
|
||||||
start_date, end_date, attendance_records, employee_names
|
start_date, end_date, attendance_records, employee_names
|
||||||
@@ -358,8 +358,8 @@ def export_payroll_excel():
|
|||||||
else:
|
else:
|
||||||
# Use standard exporter for existing report types
|
# Use standard exporter for existing report types
|
||||||
exporter = PayrollExcelExporter(
|
exporter = PayrollExcelExporter(
|
||||||
company_name=os.environ.get('COMPANY_NAME', 'Your Company'),
|
company_name=current_app.config.get('COMPANY_NAME', 'QR Code Management System'),
|
||||||
contract_name=os.environ.get('CONTRACT_NAME', 'Default Contract')
|
contract_name=current_app.config.get('CONTRACT_NAME', 'Default Contract')
|
||||||
)
|
)
|
||||||
|
|
||||||
if report_type == 'detailed':
|
if report_type == 'detailed':
|
||||||
|
|||||||
+1
-1
@@ -633,7 +633,7 @@ def qr_checkin(qr_url):
|
|||||||
# Check for recent check-ins with 30-minute interval validation
|
# Check for recent check-ins with 30-minute interval validation
|
||||||
today = date.today()
|
today = date.today()
|
||||||
current_time = datetime.now()
|
current_time = datetime.now()
|
||||||
time_interval = int(os.environ.get('TIME_INTERVAL'))
|
time_interval = current_app.config.get('TIME_INTERVAL', 30)
|
||||||
the_last_checkin_time = current_time - timedelta(minutes=time_interval)
|
the_last_checkin_time = current_time - timedelta(minutes=time_interval)
|
||||||
|
|
||||||
# Find the most recent check-in for this employee at this location today
|
# Find the most recent check-in for this employee at this location today
|
||||||
|
|||||||
+7
-1979
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
|||||||
|
"""
|
||||||
|
utils/template_helpers.py
|
||||||
|
=========================
|
||||||
|
Template utility functions injected into Jinja2 via context processors.
|
||||||
|
|
||||||
|
Extracted from create_app() in app.py so they can be independently
|
||||||
|
imported, tested, and reused.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy import text as sa_text
|
||||||
|
|
||||||
|
from extensions import db, logger_handler
|
||||||
|
|
||||||
|
|
||||||
|
def get_employee_name(employee_id):
|
||||||
|
"""Return 'Lastname, Firstname' for a given employee ID.
|
||||||
|
Falls back to 'Employee <id>' if not found or on error.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = db.session.execute(sa_text("""
|
||||||
|
SELECT CONCAT(firstName, ' ', lastName) as full_name
|
||||||
|
FROM employee
|
||||||
|
WHERE id = :employee_id
|
||||||
|
"""), {'employee_id': employee_id})
|
||||||
|
row = result.fetchone()
|
||||||
|
return row[0] if row else f"Employee {employee_id}"
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Error getting employee name for ID {employee_id}: {e}")
|
||||||
|
return f"Employee {employee_id}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_qr_code_checkin_count(qr_code_id):
|
||||||
|
"""Return total number of check-ins for a given QR code ID."""
|
||||||
|
from models.attendance import AttendanceData
|
||||||
|
try:
|
||||||
|
return AttendanceData.query.filter_by(qr_code_id=qr_code_id).count()
|
||||||
|
except Exception as e:
|
||||||
|
logger_handler.logger.error(
|
||||||
|
f"Error getting check-ins count for QR {qr_code_id}: {e}"
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def format_hours(hours):
|
||||||
|
"""Format a decimal hours value to 2 decimal places."""
|
||||||
|
return f"{hours:.2f}" if hours else "0.00"
|
||||||
|
|
||||||
|
|
||||||
|
def register_template_helpers(app):
|
||||||
|
"""
|
||||||
|
Register all template helper context processors on the given Flask app.
|
||||||
|
Call this once inside create_app() after the app is configured.
|
||||||
|
"""
|
||||||
|
from working_hours_calculator import (
|
||||||
|
convert_minutes_to_base100, round_base100_hours
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_payroll_utils():
|
||||||
|
"""Inject payroll utility functions into all templates."""
|
||||||
|
return {
|
||||||
|
'convert_minutes_to_base100': convert_minutes_to_base100,
|
||||||
|
'round_base100_hours': round_base100_hours,
|
||||||
|
'get_employee_name': get_employee_name,
|
||||||
|
'format_hours': format_hours,
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_dashboard_utils():
|
||||||
|
"""Inject dashboard utility functions into all templates."""
|
||||||
|
return {
|
||||||
|
'now': datetime.utcnow,
|
||||||
|
'get_qr_code_checkin_count': get_qr_code_checkin_count,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user