Sep 11 - Reupload the code
This commit is contained in:
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
@@ -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}>'
|
||||
@@ -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()
|
||||
@@ -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})>'
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user