Sep 11 - Reupload the code

This commit is contained in:
2026-09-11 22:42:45 -04:00
commit d92cff81e5
130 changed files with 73508 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
"""
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, QRCodeLocation # ADDED: QRCodeLocation
from .project import Project
from .attendance import AttendanceData
from .employee import Employee
from .time_attendance import TimeAttendance
from .permissions import UserProjectPermission, UserLocationPermission
return User, QRCode, QRCodeStyle, QRCodeLocation, Project, AttendanceData, Employee, TimeAttendance, UserProjectPermission, UserLocationPermission
+81
View File
@@ -0,0 +1,81 @@
"""
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=lambda: 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)
# Stores the QR-side address for dynamic QR check-ins (overrides qr_codes.location_address join)
qr_address = base.db.Column(base.db.Text, nullable=True)
# True when this record was created via a Dynamic QR code scan
is_dynamic_qr = base.db.Column(base.db.Boolean, default=False, nullable=False)
verification_photo = base.db.Column(base.db.Text, nullable=True) # Base64 encoded image
verification_required = base.db.Column(base.db.Boolean, default=False)
verification_status = base.db.Column(base.db.String(20), nullable=True) # 'pending', 'approved', 'rejected'
verification_timestamp = base.db.Column(base.db.DateTime, nullable=True)
edit_note = base.db.Column(base.db.Text, 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'
@property
def needs_photo_verification(self):
"""Check if this check-in requires photo verification"""
return self.verification_required == True
@property
def is_verification_pending(self):
"""Check if photo verification is pending approval"""
return self.verification_status == 'pending'
@property
def is_verification_approved(self):
"""Check if photo verification was approved"""
return self.verification_status == 'approved'
+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
+82
View File
@@ -0,0 +1,82 @@
"""
Employee Model for QR Attendance Management System
=================================================
Employee model to manage employee data from the external employee table.
This model interfaces with the existing employee table structure.
"""
from datetime import datetime
from sqlalchemy import cast, String
from . import base
class Employee(base.db.Model):
"""
Employee model to manage employee records
Maps to existing employee table structure
"""
__tablename__ = 'employee'
# Map to existing table structure from employee.sql
index = base.db.Column('index', base.db.BigInteger, primary_key=True, autoincrement=True)
id = base.db.Column('id', base.db.BigInteger, nullable=False, unique=True)
firstName = base.db.Column('firstName', base.db.String(50), nullable=False)
lastName = base.db.Column('lastName', base.db.String(50), nullable=False)
title = base.db.Column('title', base.db.String(20), nullable=True)
contractId = base.db.Column('contractId', base.db.BigInteger, nullable=False, default=1)
def __repr__(self):
return f'<Employee {self.firstName} {self.lastName} (ID: {self.id})>'
@property
def full_name(self):
"""Get employee's full name"""
return f"{self.firstName} {self.lastName}"
@property
def display_title(self):
"""Get formatted title for display"""
return self.title if self.title else "No Title"
@classmethod
def get_by_employee_id(cls, employee_id):
"""Get employee by their ID (not primary key index)"""
return cls.query.filter_by(id=employee_id).first()
@classmethod
def search_employees(cls, search_term):
"""Search employees by name, ID, or title"""
if not search_term:
return cls.query.all()
search_pattern = f"%{search_term}%"
return cls.query.filter(
base.db.or_(
cls.firstName.like(search_pattern),
cls.lastName.like(search_pattern),
cls.title.like(search_pattern),
cast(cls.id, String).like(search_pattern)
)
).all()
def to_dict(self):
"""Convert employee to dictionary for JSON serialization"""
return {
'index': self.index,
'id': self.id,
'firstName': self.firstName,
'lastName': self.lastName,
'full_name': self.full_name,
'title': self.title,
'display_title': self.display_title,
'contractId': self.contractId
}
project = base.db.relationship('Project', foreign_keys=[contractId],
primaryjoin="Employee.contractId == Project.id",
backref='employees')
@property
def contract_name(self):
"""Get project name from contractId"""
return self.project.name if self.project else f"Contract {self.contractId}"
+48
View File
@@ -0,0 +1,48 @@
"""
Permission Models for QR Attendance Management System
====================================================
Permission models to manage Project Manager access control.
These models define which projects and locations a Project Manager can access.
"""
from datetime import datetime
from . import base
class UserProjectPermission(base.db.Model):
"""
UserProjectPermission model to manage project access for Project Managers
Links users to specific projects they are allowed to view
"""
__tablename__ = 'user_project_permissions'
id = base.db.Column(base.db.Integer, primary_key=True)
user_id = base.db.Column(base.db.Integer, base.db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
project_id = base.db.Column(base.db.Integer, base.db.ForeignKey('projects.id', ondelete='CASCADE'), nullable=False)
created_date = base.db.Column(base.db.DateTime, default=datetime.utcnow)
# Relationships
user = base.db.relationship('User', backref=base.db.backref('project_permissions', lazy='dynamic', cascade='all, delete-orphan'))
project = base.db.relationship('Project', backref=base.db.backref('user_permissions', lazy='dynamic'))
def __repr__(self):
return f'<UserProjectPermission user_id={self.user_id} project_id={self.project_id}>'
class UserLocationPermission(base.db.Model):
"""
UserLocationPermission model to manage location access for Project Managers
Links users to specific locations they are allowed to view
"""
__tablename__ = 'user_location_permissions'
id = base.db.Column(base.db.Integer, primary_key=True)
user_id = base.db.Column(base.db.Integer, base.db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
location_name = base.db.Column(base.db.String(200), nullable=False)
created_date = base.db.Column(base.db.DateTime, default=datetime.utcnow)
# Relationships
user = base.db.relationship('User', backref=base.db.backref('location_permissions', lazy='dynamic', cascade='all, delete-orphan'))
def __repr__(self):
return f'<UserLocationPermission user_id={self.user_id} location={self.location_name}>'
+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()
+118
View File
@@ -0,0 +1,118 @@
"""
QRCode, QRCodeStyle, and QRCodeLocation 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)
# nullable=True for dynamic QR codes which have no single fixed location/address
location = base.db.Column(base.db.String(100), nullable=True)
location_address = base.db.Column(base.db.Text, nullable=True)
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)
# --- ADDED: QR Code Type ---
# 'standard' = fixed single location (existing behavior, default)
# 'dynamic' = employee selects location from a list at scan time
qr_type = base.db.Column(base.db.String(20), nullable=False, default='standard')
# Per-QR photo verification toggle (default: enabled)
photo_verification_enabled = base.db.Column(base.db.Boolean, nullable=False, default=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}>'
# --- ADDED: QRCodeLocation model ---
class QRCodeLocation(base.db.Model):
"""
Selectable locations for dynamic QR codes.
Each record represents one location option displayed to the employee at scan time.
Only relevant when the parent QRCode.qr_type == 'dynamic'.
"""
__tablename__ = 'qr_code_locations'
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
)
location_name = base.db.Column(base.db.String(100), nullable=False)
location_address = base.db.Column(base.db.Text, nullable=True)
address_latitude = base.db.Column(base.db.Float, nullable=True)
address_longitude = base.db.Column(base.db.Float, nullable=True)
sort_order = base.db.Column(base.db.Integer, default=0, nullable=False)
active_status = base.db.Column(base.db.Boolean, default=True, nullable=False)
created_date = base.db.Column(base.db.DateTime, default=datetime.utcnow)
# Back-reference: qr_code_instance.locations → list of QRCodeLocation rows
qr_code = base.db.relationship('QRCode', backref='locations')
def __repr__(self):
return f'<QRCodeLocation "{self.location_name}" (QR #{self.qr_code_id})>'
+132
View File
@@ -0,0 +1,132 @@
"""
Time Attendance Model for QR Attendance Management System
=========================================================
TimeAttendance model to manage imported time attendance data from Excel files.
This model is designed to store attendance data imported from external sources.
"""
from datetime import datetime
from . import base
class TimeAttendance(base.db.Model):
"""
Time Attendance model to manage imported attendance records from Excel files
"""
__tablename__ = 'time_attendance'
# Primary key
id = base.db.Column(base.db.Integer, primary_key=True, autoincrement=True)
# Employee identification
employee_id = base.db.Column(base.db.String(50), nullable=False, index=True)
employee_name = base.db.Column(base.db.String(200), nullable=False)
# Platform and device information
platform = base.db.Column(base.db.String(200), nullable=True)
# Date and time information
attendance_date = base.db.Column(base.db.Date, nullable=False, index=True)
attendance_time = base.db.Column(base.db.Time, nullable=False)
# Location information
location_name = base.db.Column(base.db.String(200), nullable=False)
# Action and event details
action_description = base.db.Column(base.db.String(100), nullable=False)
event_description = base.db.Column(base.db.Text, nullable=True)
recorded_address = base.db.Column(base.db.Text, nullable=True)
# Distance/Location accuracy field (in miles)
distance = base.db.Column(base.db.Float, nullable=True)
# Import tracking
import_batch_id = base.db.Column(base.db.String(36), nullable=True, index=True)
import_date = base.db.Column(base.db.DateTime, default=datetime.utcnow)
import_source = base.db.Column(base.db.String(100), nullable=True)
project_id = base.db.Column(base.db.Integer, base.db.ForeignKey('projects.id'), nullable=True, index=True)
# Audit fields
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)
updated_date = base.db.Column(base.db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationship
project = base.db.relationship('Project', backref='time_attendance_records')
def __repr__(self):
return f'<TimeAttendance {self.employee_id} - {self.employee_name} at {self.location_name} on {self.attendance_date}>'
@property
def full_datetime(self):
"""Get combined datetime from date and time"""
return datetime.combine(self.attendance_date, self.attendance_time)
@property
def formatted_datetime(self):
"""Get formatted datetime string for display"""
return self.full_datetime.strftime('%Y-%m-%d %H:%M:%S')
@classmethod
def get_by_employee_id(cls, employee_id, start_date=None, end_date=None):
"""Get attendance records by employee ID with optional date range"""
query = cls.query.filter_by(employee_id=employee_id)
if start_date:
query = query.filter(cls.attendance_date >= start_date)
if end_date:
query = query.filter(cls.attendance_date <= end_date)
return query.order_by(cls.attendance_date.desc(), cls.attendance_time.desc()).all()
@classmethod
def get_by_location(cls, location_name, start_date=None, end_date=None):
"""Get attendance records by location with optional date range"""
query = cls.query.filter_by(location_name=location_name)
if start_date:
query = query.filter(cls.attendance_date >= start_date)
if end_date:
query = query.filter(cls.attendance_date <= end_date)
return query.order_by(cls.attendance_date.desc(), cls.attendance_time.desc()).all()
@classmethod
def get_by_import_batch(cls, batch_id):
"""Get all records from a specific import batch"""
return cls.query.filter_by(import_batch_id=batch_id).order_by(
cls.attendance_date.desc(), cls.attendance_time.desc()
).all()
@classmethod
def get_unique_employees(cls):
"""Get list of unique employees from time attendance records"""
return base.db.session.query(
cls.employee_id,
cls.employee_name
).distinct().order_by(cls.employee_name).all()
@classmethod
def get_unique_locations(cls):
"""Get list of unique locations from time attendance records"""
return base.db.session.query(cls.location_name).distinct().order_by(cls.location_name).all()
def to_dict(self):
"""Convert record to dictionary for JSON serialization"""
return {
'id': self.id,
'employee_id': self.employee_id,
'employee_name': self.employee_name,
'platform': self.platform,
'attendance_date': self.attendance_date.isoformat() if self.attendance_date else None,
'attendance_time': self.attendance_time.isoformat() if self.attendance_time else None,
'formatted_datetime': self.formatted_datetime,
'location_name': self.location_name,
'action_description': self.action_description,
'event_description': self.event_description,
'recorded_address': self.recorded_address,
'import_batch_id': self.import_batch_id,
'import_date': self.import_date.isoformat() if self.import_date else None,
'import_source': self.import_source,
'created_date': self.created_date.isoformat() if self.created_date else None,
'updated_date': self.updated_date.isoformat() if self.updated_date else None
}
+70
View File
@@ -0,0 +1,70 @@
"""
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', 'accounting']
# 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
@staticmethod
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',
'accounting': 'Accounting Specialist'
}
return role_names.get(self.role, self.role.title())