refactor: replace custom employee_pin field with Odoo's standard pin field and update UI for inline regeneration

This commit is contained in:
Suherdy Yacob 2026-05-31 22:24:55 +07:00
parent ea9bf82a6c
commit 406f353b98
4 changed files with 29 additions and 48 deletions

View File

@ -1,52 +1,41 @@
Employee PIN Generator
======================
.. contents::
:local:
:depth: 1
Overview
--------
This Odoo 19 module automatically assigns a unique, randomly generated 6-digit PIN to every employee record. The PIN is enforced to be globally unique across **all companies** in the same Odoo database via a SQL UNIQUE constraint.
This Odoo 19 module automatically assigns a unique, randomly generated 6-digit PIN to every employee record using Odoo's standard PIN Code field. The PIN is enforced to be globally unique across all companies in the database via a SQL UNIQUE constraint.
Features
--------
- Auto-generates a 6-digit numeric PIN when a new employee is created.
- PIN uniqueness is enforced at the database level (UNIQUE constraint on ``employee_pin``).
- No two employees — even in different companies — can share the same PIN.
- A **Regenerate PIN** button is available on the employee form (Private Information tab) for HR managers and users.
- A confirmation wizard is shown before replacing the current PIN, displaying the existing value.
- Auto-generates a unique 6-digit numeric PIN when a new employee is created.
- Reuses Odoo's standard PIN Code field (used in Point of Sale and HR Attendance).
- PIN uniqueness is enforced at the database level across all companies.
- A Regenerate PIN button is available inline with the PIN Code field on the employee form.
- A confirmation wizard is shown before replacing the current PIN.
Technical Details
-----------------
- Model extended: ``hr.employee``
- New field: ``employee_pin`` (Char, size 6, unique)
- New transient model: ``hr.employee.regenerate.pin.wizard``
- PIN generation retries up to 1,000 times to avoid collision in a crowded pool.
- Model extended: hr.employee
- Field modified: pin (Odoo's standard PIN Code)
- New transient model: hr.employee.regenerate.pin.wizard
- PIN generation retries up to 1,000 times to avoid collision.
Installation
------------
1. Copy ``hr_employee_pin`` into your custom addons path.
2. Update the addons list in Odoo (Settings → Activate Developer Mode → Update App List).
3. Install **Employee PIN Generator** from the Apps menu.
4. Existing employees will NOT receive a PIN automatically — use the **Regenerate PIN** button on each record, or run a one-time migration script.
1. Copy hr_employee_pin into your custom addons path.
2. Update the addons list in Odoo.
3. Install Employee PIN Generator.
Usage
-----
When creating a new employee the PIN is generated automatically and shown in the **Private Information** tab under the *Employee PIN* section.
To regenerate the PIN:
1. Open an employee record.
2. Go to the **Private Information** tab.
3. Find the *Employee PIN* section and click **Regenerate PIN**.
2. Go to the Settings tab under the Attendance/Point of Sale section.
3. Click the Regenerate PIN button inline with the PIN Code field.
4. Confirm in the dialog.
5. A success notification will show the new PIN.
Author
------

View File

@ -10,19 +10,11 @@ _logger = logging.getLogger(__name__)
class HrEmployee(models.Model):
_inherit = 'hr.employee'
employee_pin = fields.Char(
string='Employee PIN',
size=6,
copy=False,
help='Unique 6-digit PIN for this employee. '
'Globally unique across all companies.',
)
_sql_constraints = [
(
'employee_pin_unique',
'UNIQUE(employee_pin)',
'The Employee PIN must be unique across all employees and companies.',
'pin_unique',
'UNIQUE(pin)',
'The Employee PIN Code must be unique across all employees and companies.',
)
]
@ -34,7 +26,7 @@ class HrEmployee(models.Model):
def _generate_unique_pin(self):
"""Return a random 6-digit string that is not yet used by any employee."""
existing = set(
self.sudo().search([('employee_pin', '!=', False)]).mapped('employee_pin')
self.sudo().search([('pin', '!=', False)]).mapped('pin')
)
for _attempt in range(1000):
pin = '{:06d}'.format(random.randint(0, 999999))
@ -52,8 +44,8 @@ class HrEmployee(models.Model):
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if not vals.get('employee_pin'):
vals['employee_pin'] = self._generate_unique_pin()
if not vals.get('pin'):
vals['pin'] = self._generate_unique_pin()
return super().create(vals_list)
# ------------------------------------------------------------------
@ -64,7 +56,7 @@ class HrEmployee(models.Model):
"""Regenerate a new unique PIN for this employee (called from button)."""
self.ensure_one()
new_pin = self._generate_unique_pin()
self.employee_pin = new_pin
self.pin = new_pin
return {
'type': 'ir.actions.client',
'tag': 'display_notification',

View File

@ -8,16 +8,16 @@
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<!-- Insert employee PIN after the existing 'PIN Code' field
inside the Attendance/Point of Sale group (Settings tab). -->
<field name="pin" position="after">
<field name="employee_pin" string="Employee PIN (6-digit)" readonly="1"/>
<div>
<!-- Replace the standard PIN field to make it readonly and add a Regenerate button inline -->
<field name="pin" position="replace">
<label for="pin" string="PIN Code"/>
<div class="o_row">
<field name="pin" readonly="1" class="oe_inline"/>
<button name="%(hr_employee_pin.action_regenerate_pin_wizard)d"
string="Regenerate PIN"
type="action"
icon="fa-refresh"
class="btn-secondary"
class="btn btn-link"
context="{'default_employee_id': id}"/>
</div>
</field>

View File

@ -15,7 +15,7 @@ class RegeneratePinWizard(models.TransientModel):
)
current_pin = fields.Char(
string='Current PIN',
related='employee_id.employee_pin',
related='employee_id.pin',
readonly=True,
)