Restructurign code

This commit is contained in:
Nguyen Ngo
2025-08-19 13:15:07 -04:00
parent 8c1a3f8a38
commit 4248917d17
7 changed files with 279 additions and 227 deletions
+4 -227
View File
@@ -32,236 +32,13 @@ VALID_ROLES = ['admin', 'staff', 'payroll', 'project_manager']
# Roles that have staff-level permissions (non-admin roles)
STAFF_LEVEL_ROLES = ['staff', 'payroll', 'project_manager']
# Import and initialize models
from models import set_db
User, QRCode, QRCodeStyle, Project, AttendanceData = set_db(db)
# Initialize the logging system
logger_handler = AppLogger(app, db)
# User Model
class User(db.Model):
"""
User model to manage system users with role-based access control
"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
full_name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
role = db.Column(db.String(20), nullable=False, default='staff') # admin or staff
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
created_date = db.Column(db.DateTime, default=datetime.utcnow)
active_status = db.Column(db.Boolean, default=True)
last_login_date = db.Column(db.DateTime, nullable=True)
# Relationships
created_users = db.relationship('User', backref=db.backref('creator', remote_side=[id]))
created_qr_codes = db.relationship('QRCode', backref='creator', lazy='dynamic')
def set_password(self, password):
"""Hash and set user password"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""Verify user password"""
return check_password_hash(self.password_hash, password)
def is_admin(self):
"""Check if user has admin privileges"""
return self.role == 'admin'
def has_staff_permissions(self):
"""Check if user has staff-level permissions (includes new roles)"""
return self.role in STAFF_LEVEL_ROLES
def has_export_permissions(user_role):
"""Check if user role has export permissions"""
return user_role in ['admin', 'payroll']
def get_role_display_name(self):
"""Get user-friendly role name"""
role_names = {
'admin': 'Administrator',
'staff': 'Staff User',
'payroll': 'Payroll Specialist',
'project_manager': 'Project Manager'
}
return role_names.get(self.role, self.role.title())
# QR Code Model
class QRCode(db.Model):
"""
Enhanced QR Code model to manage QR code records and metadata with address coordinates
"""
__tablename__ = 'qr_codes'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
location = db.Column(db.String(100), nullable=False)
location_address = db.Column(db.Text, nullable=False)
location_event = db.Column(db.String(200), nullable=False)
qr_code_image = db.Column(db.Text, nullable=False) # Base64 encoded image
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
created_date = db.Column(db.DateTime, default=datetime.utcnow)
active_status = db.Column(db.Boolean, default=True)
qr_url = db.Column(db.String(255), unique=True, nullable=True)
# Address Coordinates Fields
address_latitude = db.Column(db.Float, nullable=True)
address_longitude = db.Column(db.Float, nullable=True)
coordinate_accuracy = db.Column(db.String(50), nullable=True, default='geocoded')
coordinates_updated_date = db.Column(db.DateTime, nullable=True)
project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=True)
# The project_id field:
project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=True)
# QR Code Customization fields
fill_color = db.Column(db.String(7), default="#000000") # Hex color
back_color = db.Column(db.String(7), default="#FFFFFF") # Background color
box_size = db.Column(db.Integer, default=10)
border = db.Column(db.Integer, default=4)
error_correction = db.Column(db.String(1), default='L')
style_id = db.Column(db.Integer, db.ForeignKey('qr_code_styles.id'), nullable=True)
# Relationship to style
style = db.relationship('QRCodeStyle', backref='qr_codes')
@property
def has_coordinates(self):
"""Check if this QR code has address coordinates"""
return self.address_latitude is not None and self.address_longitude is not None
@property
def coordinates_display(self):
"""Get formatted coordinates for display"""
if self.has_coordinates:
return f"{self.address_latitude:.10f}, {self.address_longitude:.10f}"
return "Coordinates not available"
def update_coordinates(self, latitude, longitude, accuracy='geocoded'):
"""Update the address coordinates for this QR code"""
self.address_latitude = latitude
self.address_longitude = longitude
self.coordinate_accuracy = accuracy
self.coordinates_updated_date = datetime.utcnow()
class QRCodeStyle(db.Model):
"""QR Code customization styles"""
__tablename__ = 'qr_code_styles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False) # Style name
fill_color = db.Column(db.String(7), default="#000000") # Hex color for QR modules
back_color = db.Column(db.String(7), default="#FFFFFF") # Hex color for background
box_size = db.Column(db.Integer, default=10) # Size of each QR module
border = db.Column(db.Integer, default=4) # Border size
error_correction = db.Column(db.String(1), default='L') # L, M, Q, H
is_default = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
created_by = db.Column(db.Integer, db.ForeignKey('users.id'))
def __repr__(self):
return f'<QRCodeStyle {self.name}>'
class Project(db.Model):
"""
Project model to organize QR codes by projects
"""
__tablename__ = 'projects'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=True)
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
created_date = db.Column(db.DateTime, default=datetime.utcnow)
active_status = db.Column(db.Boolean, default=True)
# Relationships
qr_codes = db.relationship('QRCode', backref='project', lazy='dynamic')
creator = db.relationship('User', backref='created_projects')
def __repr__(self):
return f'<Project {self.name}>'
@property
def qr_count(self):
"""Get count of QR codes in this project"""
return self.qr_codes.filter_by(active_status=True).count()
@property
def total_qr_count(self):
"""Get total count of QR codes (including inactive) in this project"""
return self.qr_codes.count()
# Attendance Data Model
class AttendanceData(db.Model):
"""Enhanced attendance tracking model with location support"""
__tablename__ = 'attendance_data'
# Existing fields
id = db.Column(db.Integer, primary_key=True)
qr_code_id = db.Column(db.Integer, db.ForeignKey('qr_codes.id', ondelete='CASCADE'), nullable=False)
employee_id = db.Column(db.String(50), nullable=False)
check_in_date = db.Column(db.Date, nullable=False, default=datetime.today)
check_in_time = db.Column(db.Time, nullable=False, default=datetime.now().time)
device_info = db.Column(db.String(200))
user_agent = db.Column(db.Text)
ip_address = db.Column(db.String(45))
location_name = db.Column(db.String(100), nullable=False)
status = db.Column(db.String(20), default='present')
created_timestamp = db.Column(db.DateTime, default=datetime.utcnow)
updated_timestamp = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
latitude = db.Column(db.Float, nullable=True)
longitude = db.Column(db.Float, nullable=True)
accuracy = db.Column(db.Float, nullable=True)
location_accuracy = db.Column(db.Float, nullable=True)
altitude = db.Column(db.Float, nullable=True)
location_source = db.Column(db.String(50), default='manual')
address = db.Column(db.String(500), nullable=True)
# Relationships
qr_code = db.relationship('QRCode', backref=db.backref('attendance_records', lazy='dynamic'))
def __repr__(self):
return f'<AttendanceData {self.employee_id} at {self.location_name} on {self.check_in_date}>'
@property
def has_location_data(self):
"""Check if this record has GPS coordinates"""
return self.latitude is not None and self.longitude is not None
@property
def location_accuracy_level(self):
"""Get human-readable accuracy level"""
if not self.accuracy:
return 'unknown'
elif self.accuracy <= 50:
return 'high'
elif self.accuracy <= 100:
return 'medium'
else:
return 'low'
@property
def coordinates_display(self):
"""Get formatted coordinates for display"""
if self.has_location_data:
return f"{self.latitude:.10f}, {self.longitude:.10f}"
return "No GPS data"
def to_dict(self):
"""Convert to dictionary for JSON responses"""
return {
'id': self.id,
'employee_id': self.employee_id,
'check_in_date': self.check_in_date.isoformat(),
'check_in_time': self.check_in_time.isoformat(),
'location_name': self.location_name,
'status': self.status,
'has_location': self.has_location_data,
'coordinates': self.coordinates_display,
'accuracy': self.accuracy,
'address': self.address,
'location_source': self.location_source
}
# Utility functions
def is_valid_role(role):
"""Check if role is valid"""
+21
View File
@@ -0,0 +1,21 @@
"""
Models package for QR Attendance Management System
==================================================
This package contains all SQLAlchemy models split from app.py for better organization.
All models maintain backward compatibility and existing functionality.
"""
from . import base
def set_db(database):
"""Set the database instance for all models"""
base.db = database
# Now import all models (they will use base.db)
from .user import User
from .qrcode import QRCode, QRCodeStyle
from .project import Project
from .attendance import AttendanceData
return User, QRCode, QRCodeStyle, Project, AttendanceData
+58
View File
@@ -0,0 +1,58 @@
"""
Attendance Model for QR Attendance Management System
===================================================
AttendanceData model for tracking attendance records with location support.
Extracted from app.py for better code organization.
"""
from datetime import datetime
from . import base
class AttendanceData(base.db.Model):
"""Enhanced attendance tracking model with location support"""
__tablename__ = 'attendance_data'
# Existing fields
id = base.db.Column(base.db.Integer, primary_key=True)
qr_code_id = base.db.Column(base.db.Integer, base.db.ForeignKey('qr_codes.id', ondelete='CASCADE'), nullable=False)
employee_id = base.db.Column(base.db.String(50), nullable=False)
check_in_date = base.db.Column(base.db.Date, nullable=False, default=datetime.today)
check_in_time = base.db.Column(base.db.Time, nullable=False, default=datetime.now().time)
device_info = base.db.Column(base.db.String(200))
user_agent = base.db.Column(base.db.Text)
ip_address = base.db.Column(base.db.String(45))
location_name = base.db.Column(base.db.String(100), nullable=False)
status = base.db.Column(base.db.String(20), default='present')
created_timestamp = base.db.Column(base.db.DateTime, default=datetime.utcnow)
updated_timestamp = base.db.Column(base.db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
latitude = base.db.Column(base.db.Float, nullable=True)
longitude = base.db.Column(base.db.Float, nullable=True)
accuracy = base.db.Column(base.db.Float, nullable=True)
location_accuracy = base.db.Column(base.db.Float, nullable=True)
altitude = base.db.Column(base.db.Float, nullable=True)
location_source = base.db.Column(base.db.String(50), default='manual')
address = base.db.Column(base.db.String(500), nullable=True)
# Relationships
qr_code = base.db.relationship('QRCode', backref=base.db.backref('attendance_records', lazy='dynamic'))
def __repr__(self):
return f'<AttendanceData {self.employee_id} at {self.location_name} on {self.check_in_date}>'
@property
def has_location_data(self):
"""Check if this record has GPS coordinates"""
return self.latitude is not None and self.longitude is not None
@property
def location_accuracy_level(self):
"""Get human-readable accuracy level"""
if not self.accuracy:
return 'unknown'
elif self.accuracy <= 5:
return 'high'
elif self.accuracy <= 20:
return 'medium'
else:
return 'low'
+7
View File
@@ -0,0 +1,7 @@
# models/base.py
"""
Base module to hold the database instance for all models
"""
# This will be set by app.py
db = None
+40
View File
@@ -0,0 +1,40 @@
"""
Project Model for QR Attendance Management System
================================================
Project model to organize QR codes by projects.
Extracted from app.py for better code organization.
"""
from datetime import datetime
from . import base
class Project(base.db.Model):
"""
Project model to organize QR codes by projects
"""
__tablename__ = 'projects'
id = base.db.Column(base.db.Integer, primary_key=True)
name = base.db.Column(base.db.String(100), nullable=False)
description = base.db.Column(base.db.Text, nullable=True)
created_by = base.db.Column(base.db.Integer, base.db.ForeignKey('users.id'), nullable=True)
created_date = base.db.Column(base.db.DateTime, default=datetime.utcnow)
active_status = base.db.Column(base.db.Boolean, default=True)
# Relationships
qr_codes = base.db.relationship('QRCode', backref='project', lazy='dynamic')
creator = base.db.relationship('User', backref='created_projects')
def __repr__(self):
return f'<Project {self.name}>'
@property
def qr_count(self):
"""Get count of QR codes in this project"""
return self.qr_codes.filter_by(active_status=True).count()
@property
def total_qr_count(self):
"""Get total count of QR codes (including inactive) in this project"""
return self.qr_codes.count()
+81
View File
@@ -0,0 +1,81 @@
"""
QRCode and QRCodeStyle Models for QR Attendance Management System
================================================================
QRCode models to manage QR code records and metadata with customization options.
Extracted from app.py for better code organization.
"""
from datetime import datetime
from . import base
class QRCode(base.db.Model):
"""
Enhanced QR Code model to manage QR code records and metadata with address coordinates
"""
__tablename__ = 'qr_codes'
id = base.db.Column(base.db.Integer, primary_key=True)
name = base.db.Column(base.db.String(100), nullable=False)
location = base.db.Column(base.db.String(100), nullable=False)
location_address = base.db.Column(base.db.Text, nullable=False)
location_event = base.db.Column(base.db.String(200), nullable=False)
qr_code_image = base.db.Column(base.db.Text, nullable=False) # Base64 encoded image
created_by = base.db.Column(base.db.Integer, base.db.ForeignKey('users.id'), nullable=True)
created_date = base.db.Column(base.db.DateTime, default=datetime.utcnow)
active_status = base.db.Column(base.db.Boolean, default=True)
qr_url = base.db.Column(base.db.String(255), unique=True, nullable=True)
# Address Coordinates Fields
address_latitude = base.db.Column(base.db.Float, nullable=True)
address_longitude = base.db.Column(base.db.Float, nullable=True)
coordinate_accuracy = base.db.Column(base.db.String(50), nullable=True, default='geocoded')
coordinates_updated_date = base.db.Column(base.db.DateTime, nullable=True)
project_id = base.db.Column(base.db.Integer, base.db.ForeignKey('projects.id'), nullable=True)
# QR Code Customization fields
fill_color = base.db.Column(base.db.String(7), default="#000000") # Hex color
back_color = base.db.Column(base.db.String(7), default="#FFFFFF") # Background color
box_size = base.db.Column(base.db.Integer, default=10)
border = base.db.Column(base.db.Integer, default=4)
error_correction = base.db.Column(base.db.String(1), default='L')
style_id = base.db.Column(base.db.Integer, base.db.ForeignKey('qr_code_styles.id'), nullable=True)
# Relationship to style
style = base.db.relationship('QRCodeStyle', backref='qr_codes')
@property
def has_coordinates(self):
"""Check if this QR code has address coordinates"""
return self.address_latitude is not None and self.address_longitude is not None
@property
def coordinates_display(self):
"""Get formatted coordinates for display"""
if self.has_coordinates:
return f"{self.address_latitude:.10f}, {self.address_longitude:.10f}"
return "Coordinates not available"
def update_coordinates(self, latitude, longitude, accuracy='geocoded'):
"""Update the address coordinates for this QR code"""
self.address_latitude = latitude
self.address_longitude = longitude
self.coordinate_accuracy = accuracy
self.coordinates_updated_date = datetime.utcnow()
class QRCodeStyle(base.db.Model):
"""QR Code customization styles"""
__tablename__ = 'qr_code_styles'
id = base.db.Column(base.db.Integer, primary_key=True)
name = base.db.Column(base.db.String(100), nullable=False) # Style name
fill_color = base.db.Column(base.db.String(7), default="#000000") # Hex color for QR modules
back_color = base.db.Column(base.db.String(7), default="#FFFFFF") # Hex color for background
box_size = base.db.Column(base.db.Integer, default=10) # Size of each QR module
border = base.db.Column(base.db.Integer, default=4) # Border size
error_correction = base.db.Column(base.db.String(1), default='L') # L, M, Q, H
is_default = base.db.Column(base.db.Boolean, default=False)
created_at = base.db.Column(base.db.DateTime, default=datetime.utcnow)
created_by = base.db.Column(base.db.Integer, base.db.ForeignKey('users.id'))
def __repr__(self):
return f'<QRCodeStyle {self.name}>'
+68
View File
@@ -0,0 +1,68 @@
"""
User Model for QR Attendance Management System
==============================================
User model to manage system users with role-based access control.
Extracted from app.py for better code organization.
"""
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
# Valid user roles (kept in sync with app.py)
STAFF_LEVEL_ROLES = ['staff', 'payroll', 'project_manager']
# Import db from app - this works because app.py imports this file after db is created
import sys
from . import base
class User(base.db.Model):
"""
User model to manage system users with role-based access control
"""
__tablename__ = 'users'
id = base.db.Column(base.db.Integer, primary_key=True)
full_name = base.db.Column(base.db.String(100), nullable=False)
email = base.db.Column(base.db.String(120), unique=True, nullable=False)
username = base.db.Column(base.db.String(80), unique=True, nullable=False)
password_hash = base.db.Column(base.db.String(255), nullable=False)
role = base.db.Column(base.db.String(20), nullable=False, default='staff') # admin or staff
created_by = base.db.Column(base.db.Integer, base.db.ForeignKey('users.id'), nullable=True)
created_date = base.db.Column(base.db.DateTime, default=datetime.utcnow)
active_status = base.db.Column(base.db.Boolean, default=True)
last_login_date = base.db.Column(base.db.DateTime, nullable=True)
# Relationships
created_users = base.db.relationship('User', backref=base.db.backref('creator', remote_side=[id]))
created_qr_codes = base.db.relationship('QRCode', backref='creator', lazy='dynamic')
def set_password(self, password):
"""Hash and set user password"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""Verify user password"""
return check_password_hash(self.password_hash, password)
def is_admin(self):
"""Check if user has admin privileges"""
return self.role == 'admin'
def has_staff_permissions(self):
"""Check if user has staff-level permissions (includes new roles)"""
return self.role in STAFF_LEVEL_ROLES
def has_export_permissions(user_role):
"""Check if user role has export permissions"""
return user_role in ['admin', 'payroll']
def get_role_display_name(self):
"""Get user-friendly role name"""
role_names = {
'admin': 'Administrator',
'staff': 'Staff User',
'payroll': 'Payroll Specialist',
'project_manager': 'Project Manager'
}
return role_names.get(self.role, self.role.title())