Replace em-dashes and en-dashes with hyphens across 789 shipped source files (py/xml/js/scss) so the delivered module reads as human-written; em-dashes had become a recognizable AI-generated tell. Internal .md dev notes are excluded. The WO-sticker mojibake strippers keep their dash search targets (now written — / –). No logic changes: comments and display strings only; validated with py_compile + lxml parse. Rewrite the 7 customer notification emails to be intake-neutral (ship-in / drop-off / pickup) and repair-aware, and fix the Shipped email documents line (packing slip vs bill of lading; certificate only when issued). Subjects use a hyphen separator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
172 lines
6.3 KiB
Python
172 lines
6.3 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.
|
|
#
|
|
# Phase 1 (Sub 11) - relocated from fusion_plating_bridge_mrp.
|
|
# This model never had MRP fields; the bridge module was just its
|
|
# initial home. Now lives under fusion_plating_jobs.
|
|
"""QC Checklist Template - admin config for per-customer QC requirements.
|
|
|
|
Customers differ wildly in what they expect from quality control:
|
|
* commercial job-shop accounts often just want "did it plate?" - one
|
|
visual check
|
|
* aerospace / Nadcap customers expect visual, dimensional,
|
|
adhesion, and Fischerscope thickness readings - every part, every
|
|
lot, signed off
|
|
* internal rework jobs may have no QC requirement at all
|
|
|
|
Rather than coding that policy into the shop, each customer gets their
|
|
own checklist template. On job confirm, the active template is cloned
|
|
into a fresh `fusion.plating.quality.check` - the instance operators
|
|
actually fill in.
|
|
"""
|
|
from odoo import api, fields, models, _
|
|
|
|
|
|
class FpQcChecklistTemplate(models.Model):
|
|
_name = 'fp.qc.checklist.template'
|
|
_description = 'Fusion Plating - QC Checklist Template'
|
|
_inherit = ['mail.thread']
|
|
_order = 'partner_id, sequence, name'
|
|
|
|
name = fields.Char(
|
|
string='Template Name', required=True, tracking=True,
|
|
help='e.g. "Standard Aerospace CoC + Thickness" or '
|
|
'"Commercial - Visual Only".',
|
|
)
|
|
sequence = fields.Integer(default=10)
|
|
active = fields.Boolean(default=True)
|
|
partner_id = fields.Many2one(
|
|
'res.partner', string='Customer',
|
|
domain="[('customer_rank', '>', 0)]",
|
|
help='Leave blank for the global default template. A customer-'
|
|
'specific template wins over the default when both exist.',
|
|
tracking=True,
|
|
)
|
|
notes = fields.Html(
|
|
string='Notes',
|
|
help='Context for QC inspectors - what this customer cares '
|
|
'about, common reject reasons, spec docs to reference.',
|
|
)
|
|
|
|
line_ids = fields.One2many(
|
|
'fp.qc.checklist.template.line', 'template_id',
|
|
string='Checklist Items', copy=True,
|
|
)
|
|
|
|
require_thickness_readings = fields.Boolean(
|
|
string='Require Thickness Readings', default=False, tracking=True,
|
|
help='Job cannot be marked done unless at least one '
|
|
'fp.thickness.reading is logged against it. Use for '
|
|
'aerospace / Nadcap accounts.',
|
|
)
|
|
require_thickness_report_pdf = fields.Boolean(
|
|
string='Require Thickness Report PDF', default=False, tracking=True,
|
|
help='Job cannot be marked done unless the operator has '
|
|
'uploaded the Fischerscope / XDAL 600 PDF report to the '
|
|
'quality check.',
|
|
)
|
|
require_inspector_signoff = fields.Boolean(
|
|
string='Require Inspector Sign-off', default=True, tracking=True,
|
|
help='The quality check itself must be in the "passed" state '
|
|
'(not just draft or in-progress).',
|
|
)
|
|
|
|
check_count = fields.Integer(
|
|
string='# QC Checks Created', compute='_compute_check_count',
|
|
)
|
|
|
|
def _compute_check_count(self):
|
|
Check = self.env['fusion.plating.quality.check']
|
|
for rec in self:
|
|
rec.check_count = Check.search_count([
|
|
('template_id', '=', rec.id),
|
|
])
|
|
|
|
@api.model
|
|
def resolve_for_partner(self, partner):
|
|
"""Return the best-matching template for a customer.
|
|
|
|
Order: active customer-specific template > active default template >
|
|
None (no QC required).
|
|
"""
|
|
if partner:
|
|
specific = self.search([
|
|
('partner_id', '=', partner.id),
|
|
('active', '=', True),
|
|
], limit=1)
|
|
if specific:
|
|
return specific
|
|
return self.search([
|
|
('partner_id', '=', False),
|
|
('active', '=', True),
|
|
], limit=1)
|
|
|
|
def action_view_checks(self):
|
|
self.ensure_one()
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': _('QC Checks - %s') % self.name,
|
|
'res_model': 'fusion.plating.quality.check',
|
|
'view_mode': 'list,form',
|
|
'domain': [('template_id', '=', self.id)],
|
|
'target': 'current',
|
|
}
|
|
|
|
|
|
class FpQcChecklistTemplateLine(models.Model):
|
|
_name = 'fp.qc.checklist.template.line'
|
|
_description = 'Fusion Plating - QC Checklist Template Line'
|
|
_order = 'sequence, id'
|
|
|
|
template_id = fields.Many2one(
|
|
'fp.qc.checklist.template', string='Template',
|
|
required=True, ondelete='cascade',
|
|
)
|
|
sequence = fields.Integer(default=10)
|
|
name = fields.Char(
|
|
string='Check Item', required=True, translate=True,
|
|
help='The operator-facing question, e.g. "No visible pits or '
|
|
'blemishes on surface", "Thickness within 0.0005-0.0010".',
|
|
)
|
|
description = fields.Text(
|
|
string='Inspection Guidance',
|
|
help='Extra detail shown on the tablet when the operator taps '
|
|
'the item. Use for photos-to-compare-against, acceptable-'
|
|
'colour ranges, how to position the part, etc.',
|
|
)
|
|
check_type = fields.Selection(
|
|
[
|
|
('visual', 'Visual Inspection'),
|
|
('dimensional', 'Dimensional'),
|
|
('thickness', 'Thickness'),
|
|
('adhesion', 'Adhesion'),
|
|
('hardness', 'Hardness'),
|
|
('salt_spray', 'Salt Spray'),
|
|
('functional', 'Functional'),
|
|
('other', 'Other'),
|
|
],
|
|
string='Check Type', default='visual', required=True,
|
|
)
|
|
required = fields.Boolean(
|
|
string='Required', default=True,
|
|
help='If off, the inspector can skip this item without blocking '
|
|
'the QC from passing.',
|
|
)
|
|
requires_value = fields.Boolean(
|
|
string='Requires Numeric Value', default=False,
|
|
help='Inspector must enter a measurement. If min/max are set, '
|
|
'the reading must fall inside to count as pass.',
|
|
)
|
|
value_min = fields.Float(string='Min Value', digits=(12, 4))
|
|
value_max = fields.Float(string='Max Value', digits=(12, 4))
|
|
value_uom = fields.Char(
|
|
string='Unit',
|
|
help='Free text. e.g. "mils", "microns", "HV", "µm".',
|
|
)
|
|
requires_photo = fields.Boolean(
|
|
string='Requires Photo', default=False,
|
|
help='Inspector must attach a photo of the part.',
|
|
)
|