feat(#32): Enhanced barcode generation with uniqueness - Task 3 completed
- Added barcode field to stock.lot with automatic generation - Implemented unique barcode generation in format YYMMDDNNNNNNC - Added Luhn check digit for barcode validation - Handles high volume scenarios with sample type prefixes - Collision detection and retry mechanism for uniqueness - Successful test with ephemeral instance restart
This commit is contained in:
parent
57e87b4692
commit
5a4a65c65b
11
check_stock_lot_fields.py
Normal file
11
check_stock_lot_fields.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
import odoo
|
||||
|
||||
db_name = 'lims_demo'
|
||||
registry = odoo.registry(db_name)
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, 1, {})
|
||||
fields = env['stock.lot']._fields.keys()
|
||||
print("Stock.lot fields containing 'name' or 'barcode':")
|
||||
for f in fields:
|
||||
if 'name' in f or 'barcode' in f:
|
||||
print(f" - {f}")
|
Binary file not shown.
|
@ -1,11 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
from datetime import datetime
|
||||
import random
|
||||
|
||||
class StockLot(models.Model):
|
||||
_inherit = 'stock.lot'
|
||||
|
||||
is_lab_sample = fields.Boolean(string='Is a Laboratory Sample')
|
||||
|
||||
barcode = fields.Char(
|
||||
string='Código de Barras',
|
||||
compute='_compute_barcode',
|
||||
store=True,
|
||||
readonly=True,
|
||||
help="Código de barras único para la muestra en formato YYMMDDNNNNNNC"
|
||||
)
|
||||
|
||||
patient_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Patient',
|
||||
|
@ -112,3 +122,96 @@ class StockLot(models.Model):
|
|||
elif self.container_type:
|
||||
return dict(self._fields['container_type'].selection).get(self.container_type)
|
||||
return 'Unknown'
|
||||
|
||||
@api.depends('is_lab_sample', 'create_date')
|
||||
def _compute_barcode(self):
|
||||
"""Generate unique barcode for laboratory samples"""
|
||||
for record in self:
|
||||
if record.is_lab_sample and not record.barcode:
|
||||
record.barcode = record._generate_unique_barcode()
|
||||
elif not record.is_lab_sample:
|
||||
record.barcode = False
|
||||
|
||||
def _generate_unique_barcode(self):
|
||||
"""Generate a unique barcode in format YYMMDDNNNNNNC
|
||||
YY: Year (2 digits)
|
||||
MM: Month (2 digits)
|
||||
DD: Day (2 digits)
|
||||
NNNNNN: Sequential number (6 digits)
|
||||
C: Check digit
|
||||
"""
|
||||
self.ensure_one()
|
||||
now = datetime.now()
|
||||
date_prefix = now.strftime('%y%m%d')
|
||||
|
||||
# Get the highest sequence number for today
|
||||
domain = [
|
||||
('is_lab_sample', '=', True),
|
||||
('barcode', 'like', date_prefix + '%'),
|
||||
('id', '!=', self.id)
|
||||
]
|
||||
|
||||
max_barcode = self.search(domain, order='barcode desc', limit=1)
|
||||
|
||||
if max_barcode and max_barcode.barcode:
|
||||
# Extract sequence number from existing barcode
|
||||
try:
|
||||
sequence = int(max_barcode.barcode[6:12]) + 1
|
||||
except:
|
||||
sequence = 1
|
||||
else:
|
||||
sequence = 1
|
||||
|
||||
# Ensure we don't exceed 6 digits
|
||||
if sequence > 999999:
|
||||
# Add prefix based on sample type to allow more barcodes
|
||||
prefix_map = {
|
||||
'suero': '1',
|
||||
'edta': '2',
|
||||
'orina': '3',
|
||||
'hisopo': '4',
|
||||
'other': '9'
|
||||
}
|
||||
|
||||
type_prefix = '9' # default
|
||||
if self.sample_type_product_id:
|
||||
name_lower = self.sample_type_product_id.name.lower()
|
||||
for key, val in prefix_map.items():
|
||||
if key in name_lower:
|
||||
type_prefix = val
|
||||
break
|
||||
|
||||
sequence = int(type_prefix + str(sequence % 100000).zfill(5))
|
||||
|
||||
# Format sequence with leading zeros
|
||||
sequence_str = str(sequence).zfill(6)
|
||||
|
||||
# Calculate check digit using Luhn algorithm
|
||||
barcode_without_check = date_prefix + sequence_str
|
||||
check_digit = self._calculate_luhn_check_digit(barcode_without_check)
|
||||
|
||||
final_barcode = barcode_without_check + str(check_digit)
|
||||
|
||||
# Verify uniqueness
|
||||
existing = self.search([
|
||||
('barcode', '=', final_barcode),
|
||||
('id', '!=', self.id)
|
||||
], limit=1)
|
||||
|
||||
if existing:
|
||||
# If collision, add random component and retry
|
||||
sequence = sequence * 10 + random.randint(0, 9)
|
||||
sequence_str = str(sequence % 1000000).zfill(6)
|
||||
barcode_without_check = date_prefix + sequence_str
|
||||
check_digit = self._calculate_luhn_check_digit(barcode_without_check)
|
||||
final_barcode = barcode_without_check + str(check_digit)
|
||||
|
||||
return final_barcode
|
||||
|
||||
def _calculate_luhn_check_digit(self, number_str):
|
||||
"""Calculate Luhn check digit for barcode validation"""
|
||||
digits = [int(d) for d in number_str]
|
||||
odd_sum = sum(digits[-1::-2])
|
||||
even_sum = sum([sum(divmod(2 * d, 10)) for d in digits[-2::-2]])
|
||||
total = odd_sum + even_sum
|
||||
return (10 - (total % 10)) % 10
|
||||
|
|
Loading…
Reference in New Issue
Block a user