Added project dropdown in Time Attendance import

This commit is contained in:
2025-10-13 11:44:11 -04:00
parent 8269751b08
commit 08b814d294
4 changed files with 75 additions and 4 deletions
+10 -1
View File
@@ -6583,6 +6583,11 @@ def time_attendance_dashboard():
@log_database_operations('time_attendance_import') @log_database_operations('time_attendance_import')
def import_time_attendance(): def import_time_attendance():
"""Enhanced import with duplicate review""" """Enhanced import with duplicate review"""
if request.method == 'GET':
# Load active projects for dropdown
projects = Project.query.filter_by(active_status=True).order_by(Project.name).all()
return render_template('time_attendance_import.html', projects=projects)
if request.method == 'POST': if request.method == 'POST':
try: try:
# Check if this is coming from invalid review (file is already in session) # Check if this is coming from invalid review (file is already in session)
@@ -6717,12 +6722,16 @@ def import_time_attendance():
# Proceed with import # Proceed with import
print("🚀 Starting import process...") print("🚀 Starting import process...")
import_source = request.form.get('import_source', f"Manual Import - {filename}") import_source = request.form.get('import_source', f"Manual Import - {filename}")
project_id = request.form.get('project_id')
project_id = int(project_id) if project_id and project_id != '' else None
import_result = import_service.import_from_excel( import_result = import_service.import_from_excel(
temp_path, temp_path,
created_by=session['user_id'], created_by=session['user_id'],
import_source=import_source, import_source=import_source,
skip_duplicates=skip_duplicates, skip_duplicates=skip_duplicates,
force_import_hashes=force_import_hashes force_import_hashes=force_import_hashes,
project_id=project_id
) )
if import_result['success']: if import_result['success']:
+4
View File
@@ -41,12 +41,16 @@ class TimeAttendance(base.db.Model):
import_batch_id = base.db.Column(base.db.String(36), nullable=True, index=True) 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_date = base.db.Column(base.db.DateTime, default=datetime.utcnow)
import_source = base.db.Column(base.db.String(100), nullable=True) 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 # Audit fields
created_by = base.db.Column(base.db.Integer, base.db.ForeignKey('users.id'), 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) 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) 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): def __repr__(self):
return f'<TimeAttendance {self.employee_id} - {self.employee_name} at {self.location_name} on {self.attendance_date}>' return f'<TimeAttendance {self.employee_id} - {self.employee_name} at {self.location_name} on {self.attendance_date}>'
+57
View File
@@ -4,6 +4,16 @@
{% block extra_head %} {% block extra_head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/time_attendance.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/time_attendance.css') }}">
<style> <style>
.form-select:hover {
border-color: #8b5cf6;
}
.form-select:focus {
outline: none;
border-color: #8b5cf6;
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1);
}
.validation-preview { .validation-preview {
background: #f8fafc; background: #f8fafc;
border: 2px solid #e2e8f0; border: 2px solid #e2e8f0;
@@ -410,6 +420,36 @@
</div> </div>
</div> </div>
<!-- Project Selection - REQUIRED -->
<div class="form-group" style="margin-bottom: 1.5rem;">
<h3 style="margin-bottom: 0.75rem;">
<i class="fas fa-project-diagram"></i>
Select Project <span style="color: #ef4444;">*</span>
</h3>
<select name="project_id" id="project_id" class="form-select" required style="
width: 100%;
padding: 0.75rem;
border: 2px solid #e2e8f0;
border-radius: 0.5rem;
font-size: 0.9375rem;
background-color: #ffffff;
transition: all 0.2s ease;
">
<option value="">-- Select Project (Required) --</option>
{% for project in projects %}
<option value="{{ project.id }}">{{ project.name }}</option>
{% endfor %}
</select>
<p style="
margin-top: 0.5rem;
font-size: 0.875rem;
color: #64748b;
">
<i class="fas fa-info-circle"></i>
<strong>Required:</strong> Select which project this import data belongs to for filtering and reporting
</p>
</div>
<!-- Import Source --> <!-- Import Source -->
<div class="form-grid"> <div class="form-grid">
<div class="form-group"> <div class="form-group">
@@ -707,12 +747,29 @@ importForm.addEventListener('submit', (e) => {
return; return;
} }
// Validate project selection
const projectSelect = document.getElementById('project_id');
if (!projectSelect.value || projectSelect.value === '') {
e.preventDefault();
alert('Please select a project. Project selection is required for attendance imports.');
projectSelect.focus();
projectSelect.style.borderColor = '#ef4444';
return;
}
// Show progress indicator // Show progress indicator
progressIndicator.classList.add('active'); progressIndicator.classList.add('active');
submitBtn.disabled = true; submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...'; submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
}); });
// Reset border color when project is selected
document.getElementById('project_id').addEventListener('change', function() {
if (this.value) {
this.style.borderColor = '#e2e8f0';
}
});
function resetForm() { function resetForm() {
importForm.reset(); importForm.reset();
fileInfo.style.display = 'none'; fileInfo.style.display = 'none';
+3 -2
View File
@@ -440,7 +440,7 @@ class TimeAttendanceImportService:
def import_from_excel(self, file_path: str, created_by: int = None, def import_from_excel(self, file_path: str, created_by: int = None,
import_source: str = None, skip_duplicates: bool = True, import_source: str = None, skip_duplicates: bool = True,
force_import_hashes: List[str] = None) -> Dict[str, Any]: force_import_hashes: List[str] = None, project_id: int = None) -> Dict[str, Any]:
""" """
Import time attendance data from Excel file with enhanced duplicate handling Import time attendance data from Excel file with enhanced duplicate handling
@@ -581,7 +581,8 @@ class TimeAttendanceImportService:
**record_data, **record_data,
import_batch_id=batch_id, import_batch_id=batch_id,
import_source=import_source or f"Excel Import - {file_path}", import_source=import_source or f"Excel Import - {file_path}",
created_by=created_by created_by=created_by,
project_id=project_id
) )
self.db.session.add(time_attendance_record) self.db.session.add(time_attendance_record)