Files
Odoo-Modules/fusion_plating/fusion_plating/models/res_company.py
gsinghpal 89a937fb32 feat(plating-team): Owner-only Team kanban + Designated Official fields
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>
2026-05-24 02:03:44 -04:00

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', '')],
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.',
)