# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) from odoo import models, fields, api, _ from odoo.exceptions import ValidationError from ..controllers.clock_nfc_kiosk import FusionClockNfcKiosk class FusionClockNfcEnrollmentWizard(models.TransientModel): """Tap-driven NFC card enrollment for the fusion_clock kiosk. Flow: 1. Manager opens this wizard. 2. Card UID field is auto-focused. 3. Manager taps an NFC card on the USB HID reader; the reader types the UID into the focused field and hits Enter, which advances focus to the Employee picker. 4. Manager selects the employee. 5. Manager clicks "Enroll Card" (closes wizard) or "Enroll & Next" (resets wizard for the next card). The wizard reuses ``FusionClockNfcKiosk._normalize_uid`` so the stored format matches whatever the kiosk's ``/tap`` endpoint will look up later. """ _name = 'fusion.clock.nfc.enrollment.wizard' _description = 'NFC Card Enrollment Wizard' card_uid = fields.Char( string='Card UID', required=True, help='Tap an NFC card on the USB reader. Most HID readers type the ' 'UID followed by Enter, which advances focus to the Employee ' 'field below. You can also paste a UID manually.', ) normalized_uid = fields.Char( string='Normalized UID', compute='_compute_normalized_uid', store=False, help='UID after format normalization (uppercase, colon-separated hex). ' 'This is what gets stored on the employee record.', ) employee_id = fields.Many2one( 'hr.employee', string='Employee', domain=[('x_fclk_enable_clock', '=', True)], ) existing_employee_id = fields.Many2one( 'hr.employee', string='Currently Assigned To', compute='_compute_existing_employee', store=False, ) warning_message = fields.Char( compute='_compute_existing_employee', store=False, ) @api.depends('card_uid') def _compute_normalized_uid(self): for wiz in self: wiz.normalized_uid = FusionClockNfcKiosk._normalize_uid(wiz.card_uid) or '' @api.depends('normalized_uid', 'employee_id') def _compute_existing_employee(self): for wiz in self: if not wiz.normalized_uid: wiz.existing_employee_id = False wiz.warning_message = '' continue existing = self.env['hr.employee'].sudo().search([ ('x_fclk_nfc_card_uid', '=', wiz.normalized_uid), ], limit=1) if existing and existing != wiz.employee_id: wiz.existing_employee_id = existing wiz.warning_message = _( "⚠ This card is currently assigned to %(name)s. " "Enrolling will reassign it.", name=existing.name, ) else: wiz.existing_employee_id = False wiz.warning_message = '' def action_enroll(self): """Enroll the card to the selected employee and close the wizard.""" self.ensure_one() self._do_enroll() return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('Card Enrolled'), 'message': _("%(uid)s assigned to %(name)s.", uid=self.normalized_uid, name=self.employee_id.name), 'type': 'success', 'sticky': False, 'next': {'type': 'ir.actions.act_window_close'}, }, } def action_enroll_and_next(self): """Enroll the card, then reopen the wizard cleared for the next card.""" self.ensure_one() self._do_enroll() return { 'type': 'ir.actions.act_window', 'name': _('Enroll NFC Card'), 'res_model': self._name, 'view_mode': 'form', 'target': 'new', 'context': {}, # explicitly empty so no defaults persist } def _do_enroll(self): """Validate, then write the normalized UID to the employee record. Reassigns the card from any existing holder. Logs the event in the activity log for audit. """ if not self.normalized_uid: raise ValidationError(_( "Card UID is empty or not valid hex. Tap the card again on " "the reader." )) if not self.employee_id: raise ValidationError(_("Please select an employee.")) # Reassignment: clear the UID from whoever currently holds it if self.existing_employee_id and self.existing_employee_id != self.employee_id: self.existing_employee_id.sudo().x_fclk_nfc_card_uid = False self.env['fusion.clock.activity.log'].sudo().create({ 'employee_id': self.existing_employee_id.id, 'log_type': 'card_enrollment', 'description': _( "NFC card %(uid)s unassigned by %(user)s (reassigning to %(new)s)", uid=self.normalized_uid, user=self.env.user.name, new=self.employee_id.name, ), 'source': 'nfc_kiosk', }) self.employee_id.sudo().x_fclk_nfc_card_uid = self.normalized_uid self.env['fusion.clock.activity.log'].sudo().create({ 'employee_id': self.employee_id.id, 'log_type': 'card_enrollment', 'description': _( "NFC card %(uid)s enrolled by %(user)s", uid=self.normalized_uid, user=self.env.user.name, ), 'source': 'nfc_kiosk', })