תיעוד טכני

מדריך מפורט למפתחים: API, מסד נתונים, ארכיטקטורה וטכנולוגיות

ארכיטקטורה

מערכת הנוכחות בנויה על ארכיטקטורה מודולרית תלת-שכבתית (Three-Tier Architecture).

שכבת התצוגה (Presentation Layer)

טכנולוגיות: HTML5, CSS3, JavaScript ES6+, Bootstrap 5 RTL

אחראית על הממשק עם המשתמש, תצוגה, וחוויית משתמש.

שכבת הלוגיקה (Business Logic Layer)

טכנולוגיות: Python 3.11, Flask 3.0, Flask-Login, Flask-SQLAlchemy

מכילה את כל הלוגיקה העסקית, API endpoints, אימות והרשאות.

שכבת הנתונים (Data Layer)

טכנולוגיות: PostgreSQL 15, SQLAlchemy ORM

ניהול ואחסון הנתונים עם תמיכה בטרנזקציות ושלמות מידע.

מבנה הקבצים

app/
├── models_attendance.py      # מודלים של מסד הנתונים
├── api_attendance.py          # API endpoints
├── routes_attendance.py       # Flask routes (HTML pages)
├── templates/
│   └── attendance/            # 27 HTML templates
│       ├── employee-dashboard.html
│       ├── admin-dashboard.html
│       ├── check-in-out.html
│       └── ...
├── static/
│   ├── css/
│   │   └── modules/
│   │       └── attendance.css
│   ├── js/
│   │   └── modules/
│   │       └── attendance-manager.js
│   └── docs/
│       └── attendance/        # Documentation
└── migrations/                # Database migrations

מסד נתונים

המערכת משתמשת ב-9 טבלאות עיקריות, כולן מחוברות דרך Foreign Keys.

1. טבלת AttendanceRecord

תיאור: רשומות כניסה ויציאה של עובדים

שדה טיפוס אילוצים תיאור
id Integer Primary Key מזהה ייחודי
user_id Integer Foreign Key → users.id מזהה עובד
check_in_time DateTime Not Null זמן כניסה
check_out_time DateTime Nullable זמן יציאה
location_checkin String Nullable מיקום GPS בכניסה
location_checkout String Nullable מיקום GPS ביציאה
status String(20) Default: 'present' סטטוס: present/absent/leave
notes Text Nullable הערות
created_at DateTime Default: now() תאריך יצירה
# Example query
from app.models_attendance import AttendanceRecord

# Get today's attendance for a user
today_attendance = AttendanceRecord.query.filter(
    AttendanceRecord.user_id == user_id,
    func.date(AttendanceRecord.check_in_time) == date.today()
).first()

2. טבלת LeaveRequest

תיאור: בקשות חופשה של עובדים

שדה טיפוס אילוצים תיאור
id Integer Primary Key מזהה ייחודי
user_id Integer Foreign Key → users.id מזהה עובד
leave_type String(50) Not Null סוג: vacation/sick/unpaid
start_date Date Not Null תאריך התחלה
end_date Date Not Null תאריך סיום
status String(20) Default: 'pending' סטטוס: pending/approved/rejected
reason Text Nullable סיבה לבקשה
approved_by Integer Foreign Key → users.id מזהה מאשר
approved_at DateTime Nullable מתי אושר

3. טבלת LeaveBalance

תיאור: יתרות חופשה לכל עובד

שדה טיפוס תיאור
id Integer מזהה ייחודי
user_id Integer Foreign Key → users.id
year Integer שנה
total_days Integer סה"כ ימים
used_days Integer ימים שנוצלו
remaining_days Integer ימים נותרים (מחושב)

טבלאות נוספות

קשרים: כל הטבלאות מקושרות לטבלת users דרך Foreign Keys עם CASCADE על מחיקה.

API Endpoints

המערכת מספקת 20+ API endpoints מאובטחים עם אימות וניהול הרשאות.

אימות והרשאות

# All API endpoints require authentication
from flask_login import login_required, current_user

# Permission decorators
@require_role('admin', 'manager')  # Only admin or manager
@require_admin                      # Only admin
@can_view_user_data(user_id)      # Can view specific user data

Check-In / Check-Out

GET /api/attendance/status

תיאור: קבלת סטטוס נוכחות נוכחי של העובד

הרשאות: משתמש מחובר

תגובה:

{
  "success": true,
  "data": {
    "checked_in": false,
    "check_in_time": null,
    "check_out_time": null
  }
}

POST /api/attendance/check-in

תיאור: ביצוע כניסה (check-in)

הרשאות: משתמש מחובר

Body:

{
  "location": "32.0853,34.7818"  // Optional GPS coordinates
}

תגובה מוצלחת:

{
  "success": true,
  "message": "נרשמת בהצלחה",
  "data": {
    "id": 123,
    "check_in_time": "2025-11-20T08:30:00",
    "location": "32.0853,34.7818"
  }
}

POST /api/attendance/check-out

תיאור: ביצוע יציאה (check-out)

הרשאות: משתמש מחובר

Body:

{
  "location": "32.0853,34.7818"  // Optional
}

תגובה:

{
  "success": true,
  "message": "יציאה נרשמה בהצלחה",
  "data": {
    "id": 123,
    "check_in_time": "2025-11-20T08:30:00",
    "check_out_time": "2025-11-20T17:00:00",
    "hours_worked": 8.5
  }
}

Leave Requests

GET /api/attendance/leave-requests

תיאור: קבלת רשימת בקשות חופשה

הרשאות: משתמש רואה רק את הבקשות שלו, מנהל רואה הכל

Query Parameters:

  • status - סנן לפי סטטוס (pending/approved/rejected)
  • user_id - סנן לפי עובד (מנהלים בלבד)

POST /api/attendance/leave-requests

תיאור: יצירת בקשת חופשה חדשה

Body:

{
  "leave_type": "vacation",
  "start_date": "2025-12-01",
  "end_date": "2025-12-05",
  "reason": "חופשה משפחתית"
}

Validations:

  • end_date >= start_date
  • start_date >= today
  • leave_type חייב להיות אחד מ: vacation, sick, unpaid
  • בדיקת יתרה (אזהרה אם אין מספיק ימים)

PUT /api/attendance/leave-requests/{id}/approve

תיאור: אישור בקשת חופשה

הרשאות: מנהל או אדמין בלבד

Body:

{
  "notes": "מאושר"  // Optional
}

PUT /api/attendance/leave-requests/{id}/reject

תיאור: דחיית בקשת חופשה

הרשאות: מנהל או אדמין בלבד

Body:

{
  "reason": "אין אפשרות בתאריכים אלו"  // Required
}

Admin Endpoints

GET /api/attendance/admin/statistics

תיאור: סטטיסטיקות כלליות למנהלים

הרשאות: @require_role('admin', 'manager')

תגובה:

{
  "success": true,
  "data": {
    "total_employees": 150,
    "present_today": 142,
    "on_leave": 8,
    "late_today": 5,
    "pending_requests": 12
  }
}

GET /api/attendance/admin/today

תיאור: רשימת כל העובדים ונוכחות היום

הרשאות: @require_role('admin', 'manager')

GET /api/attendance/records

תיאור: קבלת רשומות נוכחות

Query Parameters:

  • user_id - סנן לפי עובד
  • start_date - מתאריך
  • end_date - עד תאריך
  • limit - מספר רשומות (ברירת מחדל: 100)

Error Handling

כל ה-API endpoints מחזירים שגיאות בפורמט אחיד:

{
  "success": false,
  "error": "נדרשת הזדהות",
  "code": 401
}

קודי שגיאה נפוצים:

ארכיטקטורת Frontend

AttendanceManager Class

מחלקת JavaScript מרכזית המנהלת את כל הפונקציונליות של הצד הלקוח.

// File: attendance-manager.js (583 lines)

class AttendanceManager {
    constructor() {
        this.attendanceStatus = null;
        this.currentLocation = null;
        this.init();
    }

    async init() {
        // Initialize the manager
        await this.loadAttendanceStatus();
        this.setupEventListeners();
        this.requestLocation();
    }

    // Main methods:
    async loadAttendanceStatus()      // GET /api/attendance/status
    async performCheckIn()             // POST /api/attendance/check-in
    async performCheckOut()            // POST /api/attendance/check-out
    async loadLeaveRequests()          // GET /api/attendance/leave-requests
    async submitLeaveRequest()         // POST /api/attendance/leave-requests
    renderAttendanceStatus()           // Update UI
    // ... +30 more methods
}

// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
    window.attendanceManager = new AttendanceManager();
});

תהליך Check-In (דיאגרמת זרימה)

1. User clicks "כניסה" button
   ↓
2. AttendanceManager.performCheckIn()
   ↓
3. Show loading state (button text: "מבצע כניסה...")
   ↓
4. Get GPS location (if permitted)
   ↓
5. POST /api/attendance/check-in
   ↓
6. API validates: not already checked in
   ↓
7. Create AttendanceRecord in DB
   ↓
8. Return success response
   ↓
9. Show SweetAlert success message
   ↓
10. Update UI: button changes to "יציאה" (red)
    ↓
11. Display check-in time in card
    ↓
12. Reload status from server (verify)

Response Handling

// Extract data from API response
const response = await fetch('/api/attendance/status');
const json = await response.json();

// Handle both formats:
// New: { success: true, data: { checked_in: false } }
// Old: { checked_in: false }
const data = json.data || json;

this.attendanceStatus = data;

Three-State Button Logic

renderAttendanceStatus() {
    const status = this.attendanceStatus;

    // Determine state
    const isCheckedIn = status.checked_in;
    const isCheckedOut = status.check_out_time != null;

    if (isCheckedOut) {
        // State 3: Completed for today
        statusClass = 'complete';
        buttonText = 'הושלם ליום זה';
        buttonDisabled = true;
        buttonClass = 'btn-secondary';
    } else if (isCheckedIn) {
        // State 2: Checked in, can check out
        statusClass = 'active';
        buttonText = 'יציאה';
        buttonDisabled = false;
        buttonClass = 'btn-danger';
    } else {
        // State 1: Not checked in yet
        statusClass = 'inactive';
        buttonText = 'כניסה';
        buttonDisabled = false;
        buttonClass = 'btn-success';
    }

    // Update DOM
    document.querySelector('button').textContent = buttonText;
}

מחסנית טכנולוגיות

Backend

Python 3.11 Flask 3.0 SQLAlchemy Flask-Login Flask-Migrate PostgreSQL 15

Frontend

HTML5 CSS3 JavaScript ES6+ Bootstrap 5 RTL SweetAlert2 Chart.js

כלים ו-DevOps

Docker Docker Compose Git Alembic (Migrations)

ספריות נוספות

התקנה ופריסה

התקנה מקומית

# 1. Clone the repository
git clone https://github.com/your-org/attendance-system.git
cd attendance-system

# 2. Create virtual environment
python3 -m venv venv
source venv/bin/activate

# 3. Install dependencies
pip install -r requirements.txt

# 4. Setup database
export DATABASE_URL="postgresql://user:pass@localhost/attendance"
flask db upgrade

# 5. Run the application
flask run

פריסה עם Docker

# Using docker-compose
docker-compose up -d

# Check logs
docker-compose logs -f web

# Run migrations
docker-compose exec web flask db upgrade

# Create demo data
docker-compose exec web python create_attendance_demo_data.py

משתני סביבה

# .env file
DATABASE_URL=postgresql://user:pass@db:5432/techlabs
SECRET_KEY=your-secret-key-here
FLASK_ENV=production
FLASK_DEBUG=0

# Optional
ENABLE_GPS_TRACKING=true
MAX_DISTANCE_METERS=500
DEFAULT_WORK_HOURS=8

אבטחה

מנגנוני הגנה

Audit Log

כל פעולה רגישה נרשמת ב-AttendanceAuditLog:

from app.models_attendance import AttendanceAuditLog

# Log example
audit = AttendanceAuditLog(
    user_id=admin_id,
    action='EDIT_ATTENDANCE',
    target_user_id=employee_id,
    old_value='08:30',
    new_value='08:00',
    reason='תיקון לבקשת עובד',
    ip_address=request.remote_addr
)
db.session.add(audit)
db.session.commit()

ביצועים ואופטימיזציה

אופטימיזציות בצד השרת

אופטימיזציות בצד הלקוח

# Example: Efficient query with JOIN
records = db.session.query(
    AttendanceRecord, User
).join(
    User, AttendanceRecord.user_id == User.id
).filter(
    func.date(AttendanceRecord.check_in_time) == date.today()
).all()

# Instead of:
# for record in records:
#     user = User.query.get(record.user_id)  # N+1 problem!

בדיקות (Testing)

הרצת בדיקות

# Run all tests
pytest

# Run specific test file
pytest tests/test_attendance_api.py

# Run with coverage
pytest --cov=app/api_attendance --cov-report=html

# Run UX tests (Playwright)
node test_attendance_ux.js

דוגמת בדיקה

def test_check_in(client, auth):
    # Login as employee
    auth.login('david.cohen@techlabs.com', '123456')

    # Perform check-in
    response = client.post('/api/attendance/check-in', json={
        'location': '32.0853,34.7818'
    })

    assert response.status_code == 200
    data = response.get_json()
    assert data['success'] == True
    assert 'check_in_time' in data['data']

    # Verify in database
    record = AttendanceRecord.query.filter_by(
        user_id=auth.user_id,
        check_in_time__isnot=None
    ).first()
    assert record is not None
חזרה לעמוד הראשי