172 lines
6.4 KiB
Python
172 lines
6.4 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.',
|
||
)
|