diff --git a/fusion_clock/models/clock_activity_log.py b/fusion_clock/models/clock_activity_log.py index 6cbb8afa..7e6c9e56 100644 --- a/fusion_clock/models/clock_activity_log.py +++ b/fusion_clock/models/clock_activity_log.py @@ -34,6 +34,8 @@ class FusionClockActivityLog(models.Model): ('correction_request', 'Correction Request'), ('ip_fallback', 'IP Fallback Used'), ('streak_milestone', 'Streak Milestone'), + ('card_enrollment', 'Card Enrollment'), + ('unknown_card_tap', 'Unknown Card Tap'), ], string='Log Type', required=True, @@ -72,6 +74,7 @@ class FusionClockActivityLog(models.Model): ('backend_fab', 'Backend FAB'), ('kiosk', 'Kiosk'), ('system', 'System (Cron)'), + ('nfc_kiosk', 'NFC Kiosk'), ], string='Source', ) diff --git a/fusion_clock/models/hr_attendance.py b/fusion_clock/models/hr_attendance.py index 242e46f2..6883fc83 100644 --- a/fusion_clock/models/hr_attendance.py +++ b/fusion_clock/models/hr_attendance.py @@ -130,6 +130,7 @@ class HrAttendance(models.Model): ('systray', 'Systray'), ('backend_fab', 'Backend FAB'), ('kiosk', 'Kiosk'), + ('nfc_kiosk', 'NFC Kiosk'), ('manual', 'Manual'), ('auto', 'Auto Clock-Out'), ], @@ -147,6 +148,16 @@ class HrAttendance(models.Model): digits=(10, 2), help="Distance from location center at clock-out, in meters.", ) + x_fclk_check_in_photo = fields.Binary( + string='Check-In Photo', + attachment=True, + help="Front-camera photo captured at NFC kiosk clock-in.", + ) + x_fclk_check_out_photo = fields.Binary( + string='Check-Out Photo', + attachment=True, + help="Front-camera photo captured at NFC kiosk clock-out.", + ) x_fclk_break_minutes = fields.Float( string='Break (min)', default=0.0, diff --git a/fusion_clock/tests/test_nfc_models.py b/fusion_clock/tests/test_nfc_models.py index 8afe4bc8..fcbd31a1 100644 --- a/fusion_clock/tests/test_nfc_models.py +++ b/fusion_clock/tests/test_nfc_models.py @@ -31,3 +31,54 @@ class TestNfcModels(TransactionCase): self.bob.x_fclk_nfc_card_uid = False self.assertFalse(self.alice.x_fclk_nfc_card_uid) self.assertFalse(self.bob.x_fclk_nfc_card_uid) + + +@tagged('-at_install', 'post_install', 'fusion_clock') +class TestNfcAttendanceFields(TransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.employee = cls.env['hr.employee'].create({ + 'name': 'NFC Test Employee', + 'x_fclk_enable_clock': True, + }) + + def test_clock_source_includes_nfc_kiosk(self): + attendance = self.env['hr.attendance'].create({ + 'employee_id': self.employee.id, + 'check_in': '2026-05-13 08:00:00', + 'x_fclk_clock_source': 'nfc_kiosk', + }) + self.assertEqual(attendance.x_fclk_clock_source, 'nfc_kiosk') + + def test_photo_fields_accept_binary(self): + # 1x1 transparent PNG as base64 + png_b64 = ( + b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAA' + b'C0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' + ) + attendance = self.env['hr.attendance'].create({ + 'employee_id': self.employee.id, + 'check_in': '2026-05-13 08:00:00', + 'x_fclk_check_in_photo': png_b64, + }) + self.assertTrue(attendance.x_fclk_check_in_photo) + + def test_activity_log_accepts_new_selections(self): + log = self.env['fusion.clock.activity.log'].create({ + 'employee_id': self.employee.id, + 'log_type': 'card_enrollment', + 'source': 'nfc_kiosk', + 'description': 'Test enrollment log', + }) + self.assertEqual(log.log_type, 'card_enrollment') + self.assertEqual(log.source, 'nfc_kiosk') + + log2 = self.env['fusion.clock.activity.log'].create({ + 'employee_id': self.employee.id, + 'log_type': 'unknown_card_tap', + 'source': 'nfc_kiosk', + 'description': 'Test unknown card log', + }) + self.assertEqual(log2.log_type, 'unknown_card_tap')