From 41336b179fc6013b80ad567686fd3b4ddefea783 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 19 Apr 2026 12:01:44 -0400 Subject: [PATCH] =?UTF-8?q?feat(plating):=20company-level=20UoM=20defaults?= =?UTF-8?q?=20=E2=80=94=20F/C,=20mils/microns,=20etc.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Different facilities use different measurement systems. North-American aerospace shops live in °F + mils + gallons + lb; ROW + most metric shops use °C + microns + litres + kg. Add company-level defaults so each shop picks its units once; new records inherit them automatically. **Settings on res.company** (7 Selection fields): • x_fc_default_temp_uom — °F / °C • x_fc_default_thickness_uom — mils / microns / inches / mm • x_fc_default_volume_uom — US gal / litres / Imp gal • x_fc_default_mass_uom — lb / kg / oz / g • x_fc_default_pressure_uom — psi / bar / kPa • x_fc_default_current_density_uom — A/ft² (ASF) / A/dm² (ASD) • x_fc_default_area_uom — sq in / sq ft / cm² / m² All default to North-American aerospace conventions (F, mils, gal, lb, psi, asf, sq_in) — admins flip them once during onboarding via Settings → Fusion Plating → Units of Measure. **Per-record use** (this round) • mrp.workorder.x_fc_bake_temp_uom (°F / °C) — defaults from company, operator can override per WO if a specific bake needs a different unit (rare but allowed). • Bake-finish gate error message now reports the actual unit: "Bake Temp (°F)" or "Bake Temp (°C)" instead of hard-coded F. • Form: Bake Temp + Temp Unit picker side-by-side in the bake group. **Settings UI** — new "Units of Measure" block on Settings → Fusion Plating page with help text per unit explaining where each is used. **Verified end-to-end** (scripts/fp_uom_smoke.py): • All 7 defaults populate with NA-aerospace defaults • Switching company default to °C makes a NEW WO inherit °C • Existing WOs keep their stored °F (no surprise mutation) **Roadmap (deferred to next round)** — wire the same default-from-company inheritance to: • fp.bake.oven.target_temp (currently no UoM) • fp.bake.window.bake_temp (currently no UoM) • fp.coating.config.bake_temperature (currently no UoM) • fp.tank.volume already has volume_uom; default from company • fp.bath.log chemistry readings already use parameter.uom; align with company default for new params The settings + framework are now in place — adding more per-record uom fields is mechanical from here. Co-Authored-By: Claude Opus 4.7 (1M context) --- fusion_plating/fusion_plating/__manifest__.py | 2 +- .../fusion_plating/models/res_company.py | 64 +++++++++++++++++++ .../models/res_config_settings.py | 30 +++++++++ .../views/res_config_settings_views.xml | 40 ++++++++++++ .../fusion_plating_bridge_mrp/__manifest__.py | 2 +- .../models/mrp_workorder.py | 14 +++- .../views/mrp_workorder_views.xml | 7 +- fusion_plating/scripts/fp_uom_smoke.py | 35 ++++++++++ 8 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 fusion_plating/scripts/fp_uom_smoke.py diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index 47f58352..aaa933b4 100644 --- a/fusion_plating/fusion_plating/__manifest__.py +++ b/fusion_plating/fusion_plating/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating', - 'version': '19.0.5.1.0', + 'version': '19.0.5.2.0', 'category': 'Manufacturing/Plating', 'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.', 'description': """ diff --git a/fusion_plating/fusion_plating/models/res_company.py b/fusion_plating/fusion_plating/models/res_company.py index e61495f0..aa1733c6 100644 --- a/fusion_plating/fusion_plating/models/res_company.py +++ b/fusion_plating/fusion_plating/models/res_company.py @@ -59,6 +59,70 @@ class ResCompany(models.Model): help='Facility used when the context does not specify one (single-site shops).', ) + # ----- Unit-of-measure defaults -------------------------------------- + # Plating shops in different jurisdictions / industries use different + # measurement systems. North-American aerospace shops live in °F + mils; + # ROW + most industrial shops use °C + microns. Each company picks its + # defaults once here; every WO / oven / bath log / thickness reading + # inherits the choice. Per-record overrides remain possible. + x_fc_default_temp_uom = fields.Selection( + [('F', '°F (Fahrenheit)'), + ('C', '°C (Celsius)')], + string='Temperature Unit', + default='F', + help='Used for bake temps, oven setpoints, bath temperatures.', + ) + x_fc_default_thickness_uom = fields.Selection( + [('mils', 'mils'), + ('microns', 'µm (microns)'), + ('inches', 'inches'), + ('mm', 'mm')], + string='Thickness Unit', + default='mils', + help='Used for coating spec targets and Fischerscope readings.', + ) + x_fc_default_volume_uom = fields.Selection( + [('gal', 'US Gallons'), + ('L', 'Litres'), + ('imp_gal', 'Imperial Gallons')], + string='Volume Unit', + default='gal', + help='Used for bath volumes and chemical addition logs.', + ) + x_fc_default_mass_uom = fields.Selection( + [('lb', 'Pounds (lb)'), + ('kg', 'Kilograms (kg)'), + ('oz', 'Ounces (oz)'), + ('g', 'Grams (g)')], + string='Mass Unit', + default='lb', + help='Used for chemical doses, parts weight, waste manifests.', + ) + x_fc_default_pressure_uom = fields.Selection( + [('psi', 'PSI'), + ('bar', 'bar'), + ('kPa', 'kPa')], + string='Pressure Unit', + default='psi', + help='Used for compressed-air pressure, agitation, etc.', + ) + x_fc_default_current_density_uom = fields.Selection( + [('asf', 'A/ft² (ASF)'), + ('asd', 'A/dm² (ASD)')], + string='Current Density Unit', + default='asf', + help='Used for electrolytic plating bath current density.', + ) + x_fc_default_area_uom = fields.Selection( + [('sq_in', 'sq in'), + ('sq_ft', 'sq ft'), + ('sq_cm', 'cm²'), + ('sq_m', 'm²')], + string='Area Unit', + default='sq_in', + help='Used for part surface area, masking area.', + ) + def _compute_x_fc_facility_count(self): for rec in self: rec.x_fc_facility_count = len(rec.x_fc_facility_ids) diff --git a/fusion_plating/fusion_plating/models/res_config_settings.py b/fusion_plating/fusion_plating/models/res_config_settings.py index c9a7b665..fcc62866 100644 --- a/fusion_plating/fusion_plating/models/res_config_settings.py +++ b/fusion_plating/fusion_plating/models/res_config_settings.py @@ -25,3 +25,33 @@ class ResConfigSettings(models.TransientModel): readonly=False, string='Default Mastery Threshold', ) + + # ----- Unit-of-measure defaults -------------------------------------- + x_fc_default_temp_uom = fields.Selection( + related='company_id.x_fc_default_temp_uom', + readonly=False, string='Temperature Unit', + ) + x_fc_default_thickness_uom = fields.Selection( + related='company_id.x_fc_default_thickness_uom', + readonly=False, string='Thickness Unit', + ) + x_fc_default_volume_uom = fields.Selection( + related='company_id.x_fc_default_volume_uom', + readonly=False, string='Volume Unit', + ) + x_fc_default_mass_uom = fields.Selection( + related='company_id.x_fc_default_mass_uom', + readonly=False, string='Mass Unit', + ) + x_fc_default_pressure_uom = fields.Selection( + related='company_id.x_fc_default_pressure_uom', + readonly=False, string='Pressure Unit', + ) + x_fc_default_current_density_uom = fields.Selection( + related='company_id.x_fc_default_current_density_uom', + readonly=False, string='Current Density Unit', + ) + x_fc_default_area_uom = fields.Selection( + related='company_id.x_fc_default_area_uom', + readonly=False, string='Area Unit', + ) diff --git a/fusion_plating/fusion_plating/views/res_config_settings_views.xml b/fusion_plating/fusion_plating/views/res_config_settings_views.xml index 0dc6df66..62bea3e5 100644 --- a/fusion_plating/fusion_plating/views/res_config_settings_views.xml +++ b/fusion_plating/fusion_plating/views/res_config_settings_views.xml @@ -37,6 +37,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py b/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py index ab820314..8fea443c 100644 --- a/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py +++ b/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py @@ -5,7 +5,7 @@ { "name": "Fusion Plating — MRP Bridge", - 'version': '19.0.6.7.0', + 'version': '19.0.6.8.0', 'category': 'Manufacturing/Plating', 'summary': 'Bridge Fusion Plating facilities, baths and tanks to Odoo MRP work orders.', 'description': """ diff --git a/fusion_plating/fusion_plating_bridge_mrp/models/mrp_workorder.py b/fusion_plating/fusion_plating_bridge_mrp/models/mrp_workorder.py index 752cb34d..4941848b 100644 --- a/fusion_plating/fusion_plating_bridge_mrp/models/mrp_workorder.py +++ b/fusion_plating/fusion_plating_bridge_mrp/models/mrp_workorder.py @@ -71,8 +71,15 @@ class MrpWorkorder(models.Model): 'which one for the chart-recorder trail.', ) x_fc_bake_temp = fields.Float( - string='Bake Temp (°F)', digits=(5, 1), - help='Setpoint temperature recorded for this bake WO.', + string='Bake Temp', digits=(5, 1), + help='Setpoint temperature recorded for this bake WO. Unit ' + 'follows the company default (Settings → Fusion Plating → ' + 'Units of Measure) — overrideable per WO via Temp Unit.', + ) + x_fc_bake_temp_uom = fields.Selection( + [('F', '°F'), ('C', '°C')], + string='Temp Unit', + default=lambda self: self.env.company.x_fc_default_temp_uom or 'F', ) x_fc_bake_duration_hours = fields.Float( string='Bake Duration (h)', digits=(5, 2), @@ -811,7 +818,8 @@ class MrpWorkorder(models.Model): continue missing = [] if not wo.x_fc_bake_temp: - missing.append(_('Bake Temp (°F)')) + unit = '°' + (wo.x_fc_bake_temp_uom or 'F') + missing.append(_('Bake Temp (%s)') % unit) if not wo.x_fc_bake_duration_hours: missing.append(_('Bake Duration (h)')) if wo.x_fc_oven_id and not wo.x_fc_oven_id.chart_recorder_ref: diff --git a/fusion_plating/fusion_plating_bridge_mrp/views/mrp_workorder_views.xml b/fusion_plating/fusion_plating_bridge_mrp/views/mrp_workorder_views.xml index 4a2971bd..53a891df 100644 --- a/fusion_plating/fusion_plating_bridge_mrp/views/mrp_workorder_views.xml +++ b/fusion_plating/fusion_plating_bridge_mrp/views/mrp_workorder_views.xml @@ -199,7 +199,12 @@ required="x_fc_requires_oven"/> - + diff --git a/fusion_plating/scripts/fp_uom_smoke.py b/fusion_plating/scripts/fp_uom_smoke.py new file mode 100644 index 00000000..6a2b7827 --- /dev/null +++ b/fusion_plating/scripts/fp_uom_smoke.py @@ -0,0 +1,35 @@ +env = env # noqa +co = env.company +print('Company unit defaults on', co.name) +for f in ('x_fc_default_temp_uom', 'x_fc_default_thickness_uom', + 'x_fc_default_volume_uom', 'x_fc_default_mass_uom', + 'x_fc_default_pressure_uom', 'x_fc_default_current_density_uom', + 'x_fc_default_area_uom'): + print(f' {f:<36} {getattr(co, f, "(missing)")}') + +# Switch company default to Celsius and verify a new WO inherits it +print('\\nSwitching company default to °C...') +co.sudo().x_fc_default_temp_uom = 'C' + +mo = env['mrp.production'].sudo().search([], order='id desc', limit=1) +if mo: + bake_wo = mo.workorder_ids.filtered( + lambda w: hasattr(w, '_fp_classify_kind') and w._fp_classify_kind() == 'bake' + )[:1] + if bake_wo: + # Existing WOs keep their stored value + print(f'existing bake WO {bake_wo.id}: x_fc_bake_temp_uom = {bake_wo.x_fc_bake_temp_uom}') + + # New WO should default from current company setting + new_wo_vals = { + 'production_id': mo.id, 'name': 'Test Bake WO', + 'workcenter_id': mo.workorder_ids[0].workcenter_id.id, + } + new_wo = env['mrp.workorder'].sudo().create(new_wo_vals) + print(f'NEW WO {new_wo.id}: x_fc_bake_temp_uom = {new_wo.x_fc_bake_temp_uom} (should be C)') + new_wo.sudo().unlink() + +# Restore F default +co.sudo().x_fc_default_temp_uom = 'F' +print('Restored to °F.') +env.cr.commit()