From 5a4a65c65b54285a09c5015c79f889afebeefa44 Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Mon, 14 Jul 2025 22:38:18 -0600 Subject: [PATCH] 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 --- check_stock_lot_fields.py | 11 ++ .../__pycache__/stock_lot.cpython-312.pyc | Bin 5021 -> 8831 bytes lims_management/models/stock_lot.py | 103 ++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 check_stock_lot_fields.py diff --git a/check_stock_lot_fields.py b/check_stock_lot_fields.py new file mode 100644 index 0000000..c510ee9 --- /dev/null +++ b/check_stock_lot_fields.py @@ -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}") \ No newline at end of file diff --git a/lims_management/models/__pycache__/stock_lot.cpython-312.pyc b/lims_management/models/__pycache__/stock_lot.cpython-312.pyc index 484c66eb99fd32157c65f35f1894338ae9db2ed7..ad21e4d999344c03075c8d91eb60ba39131dae8e 100644 GIT binary patch literal 8831 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 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