#!/usr/bin/env python3 """ Minimal Time Clock Application Requirements: - Python 3 - Flask - sqlite3 - Linux compatible - No JavaScript - Simple HTML UI Features: - User selection - Clock in - Clock out - Report generation - CRUD API - Automatic database initialization """ import sqlite3 from datetime import datetime, timedelta from flask import Flask, request, redirect, url_for, render_template, jsonify # ----------------------------------------------------------------------------- # Flask Configuration # ----------------------------------------------------------------------------- app = Flask(__name__) DATABASE = "database/timeclock.db" # ----------------------------------------------------------------------------- # Database Helpers # ----------------------------------------------------------------------------- def get_db_connection(): """ Create and return a sqlite3 database connection. """ conn = sqlite3.connect(DATABASE) conn.row_factory = sqlite3.Row return conn def initialize_database(): """ Create database tables if they do not already exist. """ conn = get_db_connection() cursor = conn.cursor() # Users table cursor.execute(""" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL ) """) # Entries table # # user_id: # References users.id # # entrytype: # Must be either 'in' or 'out' # # ts: # Timestamp stored as ISO datetime string # cursor.execute(""" CREATE TABLE IF NOT EXISTS entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, entrytype TEXT NOT NULL CHECK(entrytype IN ('in', 'out')), ts DATETIME NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ) """) conn.commit() # Add sample users if table is empty cursor.execute("SELECT COUNT(*) AS count FROM users") result = cursor.fetchone() if result["count"] == 0: cursor.execute("INSERT INTO users (name) VALUES ('Alice')") cursor.execute("INSERT INTO users (name) VALUES ('Bob')") cursor.execute("INSERT INTO users (name) VALUES ('Charlie')") conn.commit() conn.close() # ----------------------------------------------------------------------------- # Utility Functions # ----------------------------------------------------------------------------- def create_entry(user_id, entry_type): """ Create a clock in/out entry for the specified user. """ conn = get_db_connection() cursor = conn.cursor() timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") cursor.execute(""" INSERT INTO entries (user_id, entrytype, ts) VALUES (?, ?, ?) """, (user_id, entry_type, timestamp)) conn.commit() conn.close() def generate_report(user_id, begin_date, end_date): """ Generate total worked hours for a user between two dates. Assumptions: - Entries alternate correctly between 'in' and 'out' - Missing pairs are ignored """ conn = get_db_connection() cursor = conn.cursor() # Inclusive date range begin_dt = datetime.strptime(begin_date, "%Y-%m-%dT%H:%M") end_dt = datetime.strptime(end_date, "%Y-%m-%dT%H:%M") # Include entire ending day end_dt = end_dt + timedelta(days=1) cursor.execute(""" SELECT entrytype, ts FROM entries WHERE user_id = ? AND ts >= ? AND ts < ? ORDER BY ts ASC """, ( user_id, begin_dt.strftime("%Y-%m-%d %H:%M"), end_dt.strftime("%Y-%m-%d %H:%M") )) rows = cursor.fetchall() conn.close() total_seconds = 0 clock_in_time = None for row in rows: entry_type = row["entrytype"] timestamp = datetime.strptime(row["ts"], "%Y-%m-%d %H:%M:%S") if entry_type == "in": clock_in_time = timestamp elif entry_type == "out" and clock_in_time is not None: delta = timestamp - clock_in_time total_seconds += delta.total_seconds() clock_in_time = None total_hours = total_seconds / 3600.0 return round(total_hours, 2) # ----------------------------------------------------------------------------- # Web UI Routes # ----------------------------------------------------------------------------- @app.route("/", methods=["GET", "POST"]) def index(): """ Main application page. """ conn = get_db_connection() users = conn.execute(""" SELECT id, name FROM users ORDER BY name """).fetchall() conn.close() report_hours = None if request.method == "POST": user_id = request.form.get("user_id") action = request.form.get("action") if action == "clock_in": create_entry(user_id, "in") elif action == "clock_out": create_entry(user_id, "out") elif action == "report": begin_date = request.form.get("begin_date") end_date = request.form.get("end_date") report_hours = generate_report( user_id, begin_date, end_date ) return render_template( "index.html", users=users, report_hours=report_hours ) # ----------------------------------------------------------------------------- # CRUD API # ----------------------------------------------------------------------------- @app.route("/api/users", methods=["GET"]) def api_get_users(): """ Return all users. """ conn = get_db_connection() users = conn.execute(""" SELECT id, name FROM users """).fetchall() conn.close() return jsonify([dict(user) for user in users]) @app.route("/api/entries", methods=["GET"]) def api_get_entries(): """ Return all entries. """ conn = get_db_connection() entries = conn.execute(""" SELECT id, user_id, entrytype, ts FROM entries ORDER BY ts DESC """).fetchall() conn.close() return jsonify([dict(entry) for entry in entries]) @app.route("/api/entries", methods=["POST"]) def api_create_entry(): """ Create a clock entry. """ data = request.get_json() user_id = data.get("user_id") entry_type = data.get("entrytype") if entry_type not in ("in", "out"): return jsonify({"error": "Invalid entrytype"}), 400 create_entry(user_id, entry_type) return jsonify({"status": "success"}), 201 # ----------------------------------------------------------------------------- # Application Entry Point # ----------------------------------------------------------------------------- if __name__ == "__main__": initialize_database() app.run( host="0.0.0.0", port=5000, debug=False )