76 lines
3.4 KiB
Python
76 lines
3.4 KiB
Python
"""Fixtures for historical QR pairing; no live PocketBase access."""
|
|
import importlib.util
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
|
|
SCRIPT = Path(__file__).with_name("2026-09-25-backfill-clock-shifts.py")
|
|
spec = importlib.util.spec_from_file_location("clock_shift_backfill", SCRIPT)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
|
|
|
|
def event(id_, employee, direction, time, site="site1", entry=""):
|
|
return {"id": id_, "employee": employee, "direction": direction,
|
|
"scanned_at": time, "site": site, "timesheet_entry": entry}
|
|
|
|
|
|
class PairingTests(unittest.TestCase):
|
|
def test_normal_and_split_sessions_copy_only_real_out_link(self):
|
|
rows = [
|
|
event("i1", "e1", "in", "2026-09-23T05:00:00Z"),
|
|
event("o1", "e1", "out", "2026-09-23T09:00:00Z", entry="t1"),
|
|
event("i2", "e1", "in", "2026-09-23T10:00:00Z"),
|
|
event("o2", "e1", "out", "2026-09-23T13:00:00Z", entry="t1"),
|
|
]
|
|
shifts, anomalies = module.pair_events(rows)
|
|
self.assertEqual(anomalies, [])
|
|
self.assertEqual(len(shifts), 2)
|
|
self.assertEqual(shifts[0]["timesheet_entry"], "t1")
|
|
self.assertEqual(shifts[1]["payroll_status"], "synced")
|
|
self.assertEqual(shifts[0]["local_work_date"], "2026-09-23")
|
|
|
|
def test_overnight_historical_pair_is_attributed_to_start_date(self):
|
|
shifts, anomalies = module.pair_events([
|
|
event("i1", "e1", "in", "2026-09-23T20:30:00Z"),
|
|
event("o1", "e1", "out", "2026-09-23T23:30:00Z"),
|
|
])
|
|
self.assertEqual(len(shifts), 1)
|
|
self.assertEqual(shifts[0]["local_work_date"], "2026-09-23")
|
|
self.assertEqual(shifts[0]["clock_out_event"], "o1")
|
|
self.assertTrue(any(a["kind"] == "utc_local_date_disagreement" for a in anomalies))
|
|
|
|
def test_consecutive_in_and_orphan_out_are_not_guessed(self):
|
|
shifts, anomalies = module.pair_events([
|
|
event("i1", "e1", "in", "2026-09-23T05:00:00Z"),
|
|
event("i2", "e1", "in", "2026-09-23T06:00:00Z"),
|
|
event("o2", "e1", "out", "2026-09-23T14:00:00Z"),
|
|
event("orphan", "e2", "out", "2026-09-23T14:00:00Z"),
|
|
])
|
|
self.assertEqual(shifts, [])
|
|
self.assertEqual({a["kind"] for a in anomalies}, {"consecutive_in", "orphan_out"})
|
|
|
|
def test_seven_latest_ins_remain_open_without_synthetic_hours(self):
|
|
rows = [event(f"in{n}", f"e{n}", "in", "2026-09-23T05:00:00Z")
|
|
for n in range(7)]
|
|
shifts, anomalies = module.pair_events(rows)
|
|
self.assertEqual(anomalies, [])
|
|
self.assertEqual(len(shifts), 7)
|
|
self.assertTrue(all(s["payroll_status"] == "open" for s in shifts))
|
|
self.assertTrue(all("clock_out_at" not in s for s in shifts))
|
|
self.assertTrue(all("timesheet_entry" not in s for s in shifts))
|
|
|
|
def test_duplicate_direct_entries_flagged_without_changing_totals(self):
|
|
shifts, _ = module.pair_events([
|
|
event("i1", "e1", "in", "2026-09-23T05:00:00Z"),
|
|
event("o1", "e1", "out", "2026-09-23T14:00:00Z"),
|
|
])
|
|
anomalies = module.audit_timesheets(shifts, [
|
|
{"id": "t1", "employee": "e1", "date_logged": "2026-09-23T00:00:00Z", "timesheet": ""},
|
|
{"id": "t2", "employee": "e1", "date_logged": "2026-09-23T00:00:00Z", "timesheet": ""},
|
|
])
|
|
self.assertTrue(any(a["kind"] == "duplicate_direct_entries" for a in anomalies))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|