Phase F of permissions overhaul.
Adds res.users.x_fc_plating_role Selection field (8 options matching
the role hierarchy). Compute reads highest plating group from
groups_id (precedence: owner > QM > manager > sales_manager >
shop_manager > sales_rep > technician). Inverse uses sudo().write()
to clear all plating-role groups (additive-by-default m2m (3, id))
then adds the chosen one, and posts a Markup-wrapped chatter audit
naming the actor.
New Owner-only menu: Plating > Configuration > Team. Standard
res.users kanban grouped by x_fc_plating_role with records_draggable
for drag-and-drop role changes. Domain hides shared/portal users
and archived users.
res.company gains two Designated Official fields:
- x_fc_cgp_designated_official_id (CGP DO per Defence Production Act §22)
- x_fc_nadcap_authority_user_id (Nadcap signer)
Both tracking=True for audit. View-level domain restricts picker to
Owner or Quality Manager users via [(ref('...'), ref('...'))] xmlid
domains. New 'Plating Designated Officials' page on res.company form,
Owner-only visibility.
Tests in test_team_page.py cover compute/inverse/chatter/menu.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
213 lines
8.7 KiB
Python
213 lines
8.7 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.
|
|
|
|
from odoo import api, fields, models
|
|
|
|
|
|
def _fp_tz_get(self):
|
|
"""Same selection list Odoo uses on res.partner.tz."""
|
|
import pytz
|
|
return [(tz, tz) for tz in pytz.all_timezones]
|
|
|
|
|
|
class ResCompany(models.Model):
|
|
_inherit = 'res.company'
|
|
|
|
# ----- Fusion Plating default timezone --------------------------------
|
|
# Used as the fallback whenever a user's res.users.tz is empty (cron
|
|
# jobs, batch emails, headless contexts). Auto-populated by the
|
|
# post_init_hook in fusion_plating/__init__.py.
|
|
x_fc_default_tz = fields.Selection(
|
|
_fp_tz_get,
|
|
string='Fusion Plating Timezone',
|
|
default=lambda self: self.env.user.tz or 'America/Toronto',
|
|
help='Timezone used for plating dashboards, reports, and emails when '
|
|
'a user has no personal timezone set. Detected automatically on '
|
|
'install; the admin can change it any time from '
|
|
'Settings > Fusion Plating.',
|
|
)
|
|
|
|
# ----- Worker auto-promotion default -----------------------------------
|
|
# Default number of successful WO completions a worker needs on a role
|
|
# before it's auto-added to their Shop Roles. Each role can override
|
|
# via fp.work.role.mastery_required.
|
|
x_fc_default_mastery_threshold = fields.Integer(
|
|
string='Default Mastery Threshold',
|
|
default=3,
|
|
help='How many successful WO completions an operator needs on a '
|
|
"task before it's added to their Shop Roles automatically. "
|
|
'New roles inherit this number; managers can override per '
|
|
'role on the role form. 1 = promote on first success; 3 = '
|
|
'solid baseline; 5+ for tasks that need real practice.',
|
|
)
|
|
|
|
# ----- Facility footprint for this legal entity ----------------------
|
|
x_fc_facility_ids = fields.One2many(
|
|
'fusion.plating.facility',
|
|
'company_id',
|
|
string='Plating Facilities',
|
|
)
|
|
x_fc_facility_count = fields.Integer(
|
|
string='# Facilities',
|
|
compute='_compute_x_fc_facility_count',
|
|
)
|
|
x_fc_default_facility_id = fields.Many2one(
|
|
'fusion.plating.facility',
|
|
string='Default Facility',
|
|
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)
|
|
|
|
# =====================================================================
|
|
# CoC / Certificate report settings
|
|
# =====================================================================
|
|
x_fc_owner_user_id = fields.Many2one(
|
|
'res.users',
|
|
string='Certificate Owner (Default Signer)',
|
|
help='Quality manager / owner whose signature appears on Certificates '
|
|
'of Conformance by default. Signature is pulled from their linked '
|
|
'HR Employee record.',
|
|
)
|
|
x_fc_coc_signature_override = fields.Binary(
|
|
string='Signature Override Image',
|
|
help='Optional. Upload a pre-scanned signature image to use on '
|
|
'Certificates of Conformance. Overrides the Owner user\'s '
|
|
'employee signature when set. Useful if the owner doesn\'t have '
|
|
'an HR record or wants a different signature for plating certs.',
|
|
)
|
|
|
|
# --- Accreditation logos shown in CoC header ---
|
|
x_fc_nadcap_logo = fields.Binary(string='Nadcap Logo')
|
|
x_fc_nadcap_active = fields.Boolean(
|
|
string='Nadcap Accredited',
|
|
help='Show the Nadcap logo on certificates.',
|
|
)
|
|
x_fc_as9100_logo = fields.Binary(string='AS9100 / ISO 9001 Logo')
|
|
x_fc_as9100_active = fields.Boolean(
|
|
string='AS9100 / ISO 9001 Certified',
|
|
help='Show the AS9100 / ISO 9001 logo on certificates.',
|
|
)
|
|
x_fc_cgp_logo = fields.Binary(string='Controlled Goods Program Logo')
|
|
x_fc_cgp_active = fields.Boolean(
|
|
string='CGP Registered',
|
|
help='Show the Controlled Goods Program logo on certificates.',
|
|
)
|
|
|
|
# =====================================================================
|
|
# Sub 12a — Default recipe editor
|
|
# =====================================================================
|
|
x_fc_default_recipe_editor = fields.Selection(
|
|
[('tree', 'Tree Editor'), ('simple', 'Simple Editor')],
|
|
string='Default Recipe Editor',
|
|
default='tree',
|
|
help='Which editor opens when a new recipe is created OR when a '
|
|
'recipe with preferred_editor=auto is selected. Per-recipe '
|
|
'preferred_editor (tree/simple) overrides this.',
|
|
)
|
|
|
|
# =====================================================================
|
|
# Sub 12c+ — Default Certification Statement
|
|
# =====================================================================
|
|
x_fc_default_cert_statement = fields.Text(
|
|
string='Default Cert Statement',
|
|
help='Boilerplate text printed in the Certificate of Conformance '
|
|
'"Certification Statement" block. Per-customer override on '
|
|
'res.partner.x_fc_cert_statement takes precedence when set. '
|
|
'When BOTH are blank the report falls back to a hardcoded '
|
|
'AS9100/ISO 9001 statement.',
|
|
)
|
|
|
|
# =====================================================================
|
|
# Phase F — Plating Designated Officials
|
|
# =====================================================================
|
|
# These are SPECIFIC NAMED PEOPLE registered with regulatory bodies.
|
|
# Stored as Many2one to res.users so the link survives renames.
|
|
# View-level domain restricts the picker to Owner or Quality Manager
|
|
# group members (a Python-side domain would resolve groups by id at
|
|
# recordset load and is fragile across DB migrations).
|
|
x_fc_cgp_designated_official_id = fields.Many2one(
|
|
'res.users',
|
|
string='CGP Designated Official',
|
|
tracking=True,
|
|
help='Specific person registered with PSPC as Designated Official '
|
|
'under Defence Production Act §22. Must be Owner or Quality '
|
|
'Manager.',
|
|
)
|
|
|
|
x_fc_nadcap_authority_user_id = fields.Many2one(
|
|
'res.users',
|
|
string='Nadcap Authority',
|
|
tracking=True,
|
|
help='Specific person who signs Nadcap-specific certificates and '
|
|
'audits. Must be Owner or Quality Manager.',
|
|
)
|