Spaces:
Runtime error
Runtime error
File size: 7,270 Bytes
701628b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | """
CONSTABLE – SQLite database helpers.
Tables:
employees – id (TEXT PK), name (TEXT), user_type (TEXT), aadhaar (TEXT), mobile (TEXT), registered_at (TEXT)
attendance – id (INTEGER PK), employee_id (TEXT FK), timestamp (TEXT), date (TEXT), punch_type (TEXT 'in'|'out')
"""
import sqlite3
import os
from datetime import datetime, date
DB_DIR = os.path.join(os.path.dirname(__file__))
DB_PATH = os.path.join(DB_DIR, "constable.db")
def get_connection():
conn = sqlite3.connect(DB_PATH, check_same_thread=False)
conn.row_factory = sqlite3.Row
return conn
def init_db():
"""Create tables if they don't exist; add new columns to existing tables."""
os.makedirs(DB_DIR, exist_ok=True)
conn = get_connection()
cur = conn.cursor()
cur.executescript("""
CREATE TABLE IF NOT EXISTS employees (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
registered_at TEXT NOT NULL,
user_type TEXT DEFAULT 'employee',
aadhaar TEXT DEFAULT '',
mobile TEXT DEFAULT ''
);
CREATE TABLE IF NOT EXISTS attendance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
employee_id TEXT NOT NULL,
timestamp TEXT NOT NULL,
date TEXT NOT NULL,
punch_type TEXT NOT NULL DEFAULT 'in',
at_iso TEXT,
FOREIGN KEY (employee_id) REFERENCES employees(id)
);
""")
# Migrate: add punch_type to attendance if missing
try:
cur.execute("SELECT punch_type FROM attendance LIMIT 1")
except sqlite3.OperationalError:
cur.execute("ALTER TABLE attendance ADD COLUMN punch_type TEXT DEFAULT 'in'")
cur.execute("UPDATE attendance SET punch_type = 'in' WHERE punch_type IS NULL OR punch_type = ''")
# Migrate: add at_iso for cooldown if missing
cur.execute("PRAGMA table_info(attendance)")
cols = [r[1] for r in cur.fetchall()]
if "at_iso" not in cols:
cur.execute("ALTER TABLE attendance ADD COLUMN at_iso TEXT")
# Migrate employees: add user_type, aadhaar, mobile if missing
for col, default in [("user_type", "employee"), ("aadhaar", ""), ("mobile", "")]:
try:
cur.execute("SELECT " + col + " FROM employees LIMIT 1")
except sqlite3.OperationalError:
cur.execute("ALTER TABLE employees ADD COLUMN " + col + " TEXT DEFAULT '" + default.replace("'", "''") + "'")
cur.execute("UPDATE employees SET " + col + " = ? WHERE " + col + " IS NULL", (default,))
conn.commit()
conn.close()
# ---------------------------------------------------------------------------
# Employee helpers
# ---------------------------------------------------------------------------
def add_employee(employee_id: str, name: str, user_type: str = "employee", aadhaar: str = "", mobile: str = "") -> bool:
"""Insert or replace an employee record. Returns True on success."""
conn = get_connection()
try:
conn.execute(
"""INSERT OR REPLACE INTO employees (id, name, registered_at, user_type, aadhaar, mobile)
VALUES (?, ?, ?, ?, ?, ?)""",
(employee_id, name, datetime.now().isoformat(timespec="seconds"), user_type, aadhaar or "", mobile or ""),
)
conn.commit()
return True
except Exception as e:
print(f"[DB] add_employee error: {e}")
return False
finally:
conn.close()
def get_employee(employee_id: str):
"""Return employee row or None."""
conn = get_connection()
try:
row = conn.execute(
"SELECT * FROM employees WHERE id = ?", (employee_id,)
).fetchone()
return dict(row) if row else None
finally:
conn.close()
def get_all_employees():
conn = get_connection()
try:
rows = conn.execute("SELECT * FROM employees ORDER BY registered_at DESC").fetchall()
return [dict(r) for r in rows]
finally:
conn.close()
def delete_employee(employee_id: str) -> bool:
"""Delete an employee and their attendance records. Returns True on success."""
conn = get_connection()
try:
conn.execute("DELETE FROM attendance WHERE employee_id = ?", (employee_id,))
conn.execute("DELETE FROM employees WHERE id = ?", (employee_id,))
conn.commit()
return True
except Exception as e:
print(f"[DB] delete_employee error: {e}")
return False
finally:
conn.close()
# ---------------------------------------------------------------------------
# Attendance: one punch in per day, then only punch out; 1 min cooldown; last punch out only
# ---------------------------------------------------------------------------
COOLDOWN_SECONDS = 60
def mark_attendance(employee_id: str) -> dict:
"""
First time today = punch in only. Every other time = punch out only.
1 min cooldown for same user. Only last punch out time is used for display.
Returns {'status': 'success'|'cooldown', 'punch_type': 'in'|'out', 'timestamp': ...}
"""
today = date.today().isoformat()
now = datetime.now()
now_iso = now.isoformat()
ts = now.strftime("%I:%M %p")
conn = get_connection()
try:
last_row = conn.execute(
"SELECT at_iso FROM attendance WHERE employee_id = ? AND date = ? ORDER BY id DESC LIMIT 1",
(employee_id, today),
).fetchone()
if last_row and last_row["at_iso"]:
try:
last_dt = datetime.fromisoformat(last_row["at_iso"])
if (now - last_dt).total_seconds() < COOLDOWN_SECONDS:
return {"status": "cooldown", "punch_type": None, "timestamp": None}
except (ValueError, TypeError):
pass
has_any_today = conn.execute(
"SELECT 1 FROM attendance WHERE employee_id = ? AND date = ? LIMIT 1",
(employee_id, today),
).fetchone()
next_punch = "out" if has_any_today else "in"
conn.execute(
"INSERT INTO attendance (employee_id, timestamp, date, punch_type, at_iso) VALUES (?, ?, ?, ?, ?)",
(employee_id, ts, today, next_punch, now_iso),
)
conn.commit()
return {"status": "success", "punch_type": next_punch, "timestamp": ts}
finally:
conn.close()
def get_today_attendance():
"""Return today's attendance: one row per employee with first punch_in and last punch_out."""
today = date.today().isoformat()
conn = get_connection()
try:
rows = conn.execute(
"""SELECT e.id, e.name,
(SELECT MIN(a.timestamp) FROM attendance a WHERE a.employee_id = e.id AND a.date = ? AND a.punch_type = 'in') AS punch_in,
(SELECT MAX(a.timestamp) FROM attendance a WHERE a.employee_id = e.id AND a.date = ? AND a.punch_type = 'out') AS punch_out
FROM attendance a
JOIN employees e ON a.employee_id = e.id
WHERE a.date = ?
GROUP BY e.id, e.name""",
(today, today, today),
).fetchall()
return [dict(r) for r in rows]
finally:
conn.close()
|