Files
Odoo-Modules/fusion_plating/fusion_plating/models/_fp_uom_selection.py
gsinghpal 13e300d90e changes
2026-04-28 19:39:37 -04:00

419 lines
10 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
"""Shared Unit-of-Measure selection list for plating chemistry, physical
quantities, and process inputs.
Free-text unit fields invite typos ("kgs", "Kg", "kilo", "KG") that
break filters, reports, and trend graphs. Every UoM in the plating
domain — chemistry, mass, volume, length, area, electrical, time,
pressure, dimensionless — lives here as a curated selection so users
pick from a known list instead of typing.
Re-use:
from .._fp_uom_selection import FP_UOM_SELECTION, FP_UOM_LEGACY_MAP
uom = fields.Selection(FP_UOM_SELECTION, string='Unit')
Migration:
Use FP_UOM_LEGACY_MAP to translate pre-existing free-text values
into selection keys during post_init / migration. Anything not in
the map gets cleared (NULL) so the user is forced to pick.
"""
# Single source of truth — keep alphabetised within each section.
FP_UOM_SELECTION = [
# --- Concentration / chemistry ---------------------------------------
('g_l', 'g/L'),
('mg_l', 'mg/L'),
('ug_l', 'µg/L'),
('kg_l', 'kg/L'),
('oz_gal', 'oz/gal (US)'),
('oz_gal_imp', 'oz/Imp gal'),
('ml_l', 'mL/L'),
('mol_l', 'mol/L'),
('n', 'N (Normality)'),
('ppm', 'ppm'),
('ppb', 'ppb'),
('pct', '%'),
('pct_w', '% (w/w)'),
('pct_v', '% (v/v)'),
('pct_vw', '% (v/w)'),
# --- Temperature -----------------------------------------------------
('c', '°C'),
('f', '°F'),
('k', 'K'),
# --- Dimensionless / pH / specific units -----------------------------
('ph', 'pH'),
('su', 'SU (Standard Units)'),
('ratio', 'Ratio (e.g. 5:1)'),
('none', '— (none)'),
# --- Conductivity / turbidity ----------------------------------------
('us_cm', 'µS/cm'),
('ms_cm', 'mS/cm'),
('ntu', 'NTU'),
# --- Time ------------------------------------------------------------
('s', 's (seconds)'),
('min', 'min'),
('h', 'h'),
('day', 'day'),
# --- Mass ------------------------------------------------------------
('mg', 'mg'),
('g', 'g'),
('kg', 'kg'),
('t', 't (tonne)'),
('oz', 'oz'),
('lb', 'lb'),
# --- Volume ----------------------------------------------------------
('ml', 'mL'),
('l', 'L'),
('m3', ''),
('gal_us', 'US gal'),
('gal_imp', 'Imp gal'),
('ft3', 'ft³'),
# --- Length / thickness ----------------------------------------------
('nm', 'nm'),
('um', 'µm'),
('mm', 'mm'),
('cm', 'cm'),
('m', 'm'),
('mil', 'mil (0.001 in)'),
('in', 'in'),
('ft', 'ft'),
# --- Area ------------------------------------------------------------
('cm2', 'cm²'),
('m2', ''),
('in2', 'in²'),
('ft2', 'ft²'),
('dm2', 'dm²'),
# --- Electrical / current density ------------------------------------
('a', 'A'),
('ma', 'mA'),
('v', 'V'),
('asd_a_dm2', 'A/dm² (ASD)'),
('asd_a_ft2', 'A/ft² (ASF)'),
('dm2_l', 'dm²/L (load)'),
# --- Pressure --------------------------------------------------------
('pa', 'Pa'),
('kpa', 'kPa'),
('bar', 'bar'),
('psi', 'psi'),
('mmhg', 'mmHg'),
# --- Rate / flow / generation ----------------------------------------
('kg_day', 'kg/day'),
('l_day', 'L/day'),
('kg_month', 'kg/month'),
('l_min', 'L/min'),
('gpm', 'gpm'),
('cfm', 'cfm'),
# --- Exposure / occupational hygiene ---------------------------------
('mg_m3', 'mg/m³'),
('ug_m3', 'µg/m³'),
('dba', 'dBA'),
('lux', 'lux'),
# --- Plating-specific counts -----------------------------------------
('mto', 'MTO (metal turnover)'),
('cycles', 'cycles'),
('count', 'count'),
('each', 'each'),
('rpm', 'rpm'),
]
# Map free-text values produced before this list existed → selection keys.
# Keep keys lower-cased + stripped during lookup.
FP_UOM_LEGACY_MAP = {
# Concentration
'g/l': 'g_l',
'gpl': 'g_l',
'grams/l': 'g_l',
'g per l': 'g_l',
'mg/l': 'mg_l',
'ug/l': 'ug_l',
'µg/l': 'ug_l',
'kg/l': 'kg_l',
'oz/gal': 'oz_gal',
'oz/g': 'oz_gal',
'oz/gallon': 'oz_gal',
'oz/imp gal': 'oz_gal_imp',
'ml/l': 'ml_l',
'mol/l': 'mol_l',
'molar': 'mol_l',
'm': 'mol_l',
'n': 'n',
'normal': 'n',
'normality': 'n',
'ppm': 'ppm',
'ppb': 'ppb',
'%': 'pct',
'percent': 'pct',
'pct': 'pct',
'% w/w': 'pct_w',
'%(w/w)': 'pct_w',
'%w/w': 'pct_w',
'% v/v': 'pct_v',
'%v/v': 'pct_v',
'% v/w': 'pct_vw',
# Temperature
'c': 'c',
'°c': 'c',
'celsius': 'c',
'deg c': 'c',
'degc': 'c',
'f': 'f',
'°f': 'f',
'fahrenheit': 'f',
'deg f': 'f',
'degf': 'f',
'k': 'k',
'kelvin': 'k',
# Dimensionless
'ph': 'ph',
'su': 'su',
'standard units': 'su',
'ratio': 'ratio',
'-': 'none',
'none': 'none',
# Conductivity / turbidity
'us/cm': 'us_cm',
'µs/cm': 'us_cm',
'ms/cm': 'ms_cm',
'ntu': 'ntu',
# Time
'second': 's',
'seconds': 's',
'sec': 's',
'secs': 's',
's': 's',
'minute': 'min',
'minutes': 'min',
'min': 'min',
'mins': 'min',
'hour': 'h',
'hours': 'h',
'hr': 'h',
'hrs': 'h',
'h': 'h',
'day': 'day',
'days': 'day',
'd': 'day',
# Mass
'mg': 'mg',
'g': 'g',
'gr': 'g',
'gram': 'g',
'grams': 'g',
'kg': 'kg',
'kgs': 'kg',
'kilogram': 'kg',
'kilograms': 'kg',
't': 't',
'tonne': 't',
'tonnes': 't',
'metric ton': 't',
'oz': 'oz',
'ounce': 'oz',
'ounces': 'oz',
'lb': 'lb',
'lbs': 'lb',
'pound': 'lb',
'pounds': 'lb',
# Volume
'ml': 'ml',
'l': 'l',
'liter': 'l',
'liters': 'l',
'litre': 'l',
'litres': 'l',
'm3': 'm3',
'': 'm3',
'cubic meter': 'm3',
'gal': 'gal_us',
'gal_us': 'gal_us',
'us gal': 'gal_us',
'gallon': 'gal_us',
'gallons': 'gal_us',
'imp gal': 'gal_imp',
'imperial gallon': 'gal_imp',
'ft3': 'ft3',
'ft³': 'ft3',
'cubic feet': 'ft3',
'cu ft': 'ft3',
# Length
'nm': 'nm',
'um': 'um',
'µm': 'um',
'micron': 'um',
'mm': 'mm',
'cm': 'cm',
'mil': 'mil',
'in': 'in',
'inch': 'in',
'inches': 'in',
'"': 'in',
'ft': 'ft',
'feet': 'ft',
'foot': 'ft',
# Area
'cm2': 'cm2',
'cm²': 'cm2',
'm2': 'm2',
'': 'm2',
'in2': 'in2',
'in²': 'in2',
'sq in': 'in2',
'ft2': 'ft2',
'ft²': 'ft2',
'sq ft': 'ft2',
'dm2': 'dm2',
'dm²': 'dm2',
# Electrical
'a': 'a',
'amp': 'a',
'amps': 'a',
'ampere': 'a',
'amperes': 'a',
'ma': 'ma',
'milliamp': 'ma',
'milliamps': 'ma',
'v': 'v',
'volt': 'v',
'volts': 'v',
'a/dm2': 'asd_a_dm2',
'a/dm²': 'asd_a_dm2',
'asd': 'asd_a_dm2',
'a/ft2': 'asd_a_ft2',
'a/ft²': 'asd_a_ft2',
'asf': 'asd_a_ft2',
'dm2/l': 'dm2_l',
'dm²/l': 'dm2_l',
# Pressure
'pa': 'pa',
'kpa': 'kpa',
'bar': 'bar',
'psi': 'psi',
'mmhg': 'mmhg',
# Rate
'kg/day': 'kg_day',
'l/day': 'l_day',
'kg/month': 'kg_month',
'l/min': 'l_min',
'lpm': 'l_min',
'gpm': 'gpm',
'cfm': 'cfm',
# Exposure
'mg/m3': 'mg_m3',
'mg/m³': 'mg_m3',
'ug/m3': 'ug_m3',
'µg/m³': 'ug_m3',
'dba': 'dba',
'db': 'dba',
'lux': 'lux',
# Plating counts
'mto': 'mto',
'cycle': 'cycles',
'cycles': 'cycles',
'count': 'count',
'each': 'each',
'ea': 'each',
'pcs': 'each',
'pieces': 'each',
'rpm': 'rpm',
}
def fp_normalize_legacy_uom(raw_value):
"""Translate a legacy free-text UoM string to a selection key.
Returns the selection key, or None if no match (caller decides whether
to NULL the column or leave it).
"""
if raw_value is None:
return None
key = (raw_value or '').strip().lower()
if not key:
return None
return FP_UOM_LEGACY_MAP.get(key)
def fp_migrate_uom_column(env, table, column, label_for_log=None):
"""Walk a table's free-text uom column and rewrite values into the
selection keys. Unmapped values are set to NULL so the user is forced
to pick a valid one.
Idempotent — running on a column that's already converted is a no-op
because all values will already be selection keys (which are a subset
of FP_UOM_LEGACY_MAP via identity mappings like 'g_l''g_l').
Args:
env: Odoo environment.
table: SQL table name (e.g. 'fusion_plating_bath_parameter').
column: SQL column name (e.g. 'uom').
label_for_log: human-readable name for the migration log line.
"""
cr = env.cr
cr.execute(
"SELECT 1 FROM information_schema.columns "
"WHERE table_name = %s AND column_name = %s",
(table, column),
)
if not cr.fetchone():
return 0, 0 # table/column not present (module not installed)
cr.execute(f'SELECT id, "{column}" FROM "{table}" WHERE "{column}" IS NOT NULL')
rows = cr.fetchall()
valid_keys = {k for k, _ in FP_UOM_SELECTION}
cleared = 0
rewritten = 0
for row_id, raw in rows:
if raw in valid_keys:
continue # already a selection key
new_key = fp_normalize_legacy_uom(raw)
if new_key:
cr.execute(
f'UPDATE "{table}" SET "{column}" = %s WHERE id = %s',
(new_key, row_id),
)
rewritten += 1
else:
cr.execute(
f'UPDATE "{table}" SET "{column}" = NULL WHERE id = %s',
(row_id,),
)
cleared += 1
import logging
_logger = logging.getLogger(__name__)
_logger.info(
'Fusion Plating UoM migration — %s.%s%s: %s rewritten, %s cleared',
table, column, f' ({label_for_log})' if label_for_log else '',
rewritten, cleared,
)
return rewritten, cleared