folder rename
This commit is contained in:
14
fusion_plating/fusion_plating_nuclear/models/__init__.py
Normal file
14
fusion_plating/fusion_plating_nuclear/models/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
|
||||
from . import fp_n299_level
|
||||
from . import fp_nuclear_program
|
||||
from . import fp_nuclear_itp
|
||||
from . import fp_10cfr21_report
|
||||
from . import fp_nuclear_pedigree
|
||||
from . import fp_cnsc_licence
|
||||
from . import fp_customer_spec
|
||||
from . import fp_ncr
|
||||
from . import res_company
|
||||
@@ -0,0 +1,166 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
class Fp10CFR21Report(models.Model):
|
||||
"""10 CFR Part 21 — Reporting of Defects and Noncompliance.
|
||||
|
||||
10 CFR Part 21 is the US Nuclear Regulatory Commission regulation
|
||||
that requires suppliers of basic components to nuclear facilities
|
||||
to evaluate potential defects and noncompliances, and to report
|
||||
reportable ones to the NRC within 60 days of discovery.
|
||||
|
||||
The typical workflow is:
|
||||
|
||||
1. discovery — something is observed that might be a defect
|
||||
2. evaluation — determine whether it rises to the level of a
|
||||
"substantial safety hazard" (reportable)
|
||||
3. reportable — yes, it's reportable → file with NRC
|
||||
or
|
||||
not_reportable — evaluation closes with a documented basis
|
||||
4. reported — notice filed with NRC and affected customers
|
||||
5. closed — corrective action verified, record sealed
|
||||
|
||||
The record has a due date that is 60 days after discovery, driven by
|
||||
the `days_since_discovery` computed field.
|
||||
"""
|
||||
_name = 'fusion.plating.10cfr21.report'
|
||||
_description = 'Fusion Plating — 10 CFR Part 21 Defect Report'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'discovery_date desc, id desc'
|
||||
|
||||
name = fields.Char(
|
||||
string='Reference',
|
||||
required=True,
|
||||
copy=False,
|
||||
readonly=True,
|
||||
default=lambda self: self._default_name(),
|
||||
tracking=True,
|
||||
)
|
||||
discovery_date = fields.Date(
|
||||
string='Discovery Date',
|
||||
required=True,
|
||||
default=lambda self: fields.Date.context_today(self),
|
||||
tracking=True,
|
||||
)
|
||||
report_date = fields.Date(
|
||||
string='Report Date',
|
||||
tracking=True,
|
||||
help='Date the notification was filed. 10 CFR 21 requires reporting '
|
||||
'within 60 days of discovery for reportable defects.',
|
||||
)
|
||||
days_since_discovery = fields.Integer(
|
||||
string='Days Since Discovery',
|
||||
compute='_compute_days_since_discovery',
|
||||
search='_search_days_since_discovery',
|
||||
)
|
||||
part_description = fields.Html(
|
||||
string='Part / Component',
|
||||
)
|
||||
defect_description = fields.Html(
|
||||
string='Defect Description',
|
||||
)
|
||||
customer_ids = fields.Many2many(
|
||||
'res.partner',
|
||||
'fp_10cfr21_customer_rel',
|
||||
'report_id',
|
||||
'partner_id',
|
||||
string='Affected Customers',
|
||||
)
|
||||
ncr_id = fields.Many2one(
|
||||
'fusion.plating.ncr',
|
||||
string='Source NCR',
|
||||
help='The non-conformance report that triggered the 10 CFR 21 '
|
||||
'evaluation.',
|
||||
)
|
||||
reportable = fields.Boolean(
|
||||
string='Reportable',
|
||||
tracking=True,
|
||||
help='Set after evaluation. True means the defect rises to the '
|
||||
'level of a substantial safety hazard and must be reported.',
|
||||
)
|
||||
corrective_action = fields.Html(
|
||||
string='Corrective Action',
|
||||
)
|
||||
state = fields.Selection(
|
||||
[
|
||||
('discovery', 'Discovery'),
|
||||
('evaluation', 'Evaluation'),
|
||||
('reportable', 'Reportable'),
|
||||
('reported', 'Reported'),
|
||||
('not_reportable', 'Not Reportable'),
|
||||
('closed', 'Closed'),
|
||||
],
|
||||
string='Status',
|
||||
default='discovery',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
@api.model
|
||||
def _default_name(self):
|
||||
seq = self.env['ir.sequence'].next_by_code(
|
||||
'fusion.plating.10cfr21.report'
|
||||
)
|
||||
return seq or '/'
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if not vals.get('name') or vals.get('name') == '/':
|
||||
vals['name'] = self._default_name()
|
||||
return super().create(vals_list)
|
||||
|
||||
@api.depends('discovery_date')
|
||||
def _compute_days_since_discovery(self):
|
||||
today = fields.Date.context_today(self)
|
||||
for rec in self:
|
||||
if rec.discovery_date:
|
||||
rec.days_since_discovery = (today - rec.discovery_date).days
|
||||
else:
|
||||
rec.days_since_discovery = 0
|
||||
|
||||
def _search_days_since_discovery(self, operator, value):
|
||||
today = fields.Date.context_today(self)
|
||||
from datetime import timedelta
|
||||
target = today - timedelta(days=value)
|
||||
if operator in ('>=', '>'):
|
||||
date_op = '<=' if operator == '>=' else '<'
|
||||
return [('discovery_date', date_op, target)]
|
||||
if operator in ('<=', '<'):
|
||||
date_op = '>=' if operator == '<=' else '>'
|
||||
return [('discovery_date', date_op, target)]
|
||||
if operator == '=':
|
||||
return [('discovery_date', '=', target)]
|
||||
return [('discovery_date', '!=', target)]
|
||||
|
||||
def action_start_evaluation(self):
|
||||
self.write({'state': 'evaluation'})
|
||||
|
||||
def action_mark_reportable(self):
|
||||
self.write({'state': 'reportable', 'reportable': True})
|
||||
|
||||
def action_mark_not_reportable(self):
|
||||
self.write({'state': 'not_reportable', 'reportable': False})
|
||||
|
||||
def action_file_report(self):
|
||||
self.write({
|
||||
'state': 'reported',
|
||||
'report_date': fields.Date.context_today(self),
|
||||
})
|
||||
|
||||
def action_close(self):
|
||||
self.write({'state': 'closed'})
|
||||
|
||||
def action_reset_to_discovery(self):
|
||||
self.write({'state': 'discovery'})
|
||||
@@ -0,0 +1,99 @@
|
||||
# -*- 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 fields, models
|
||||
|
||||
|
||||
class FpCnscLicence(models.Model):
|
||||
"""CNSC Licence record.
|
||||
|
||||
The Canadian Nuclear Safety Commission (CNSC) regulates all nuclear
|
||||
activities in Canada. A plating / finishing supplier that works on
|
||||
nuclear items may hold — or be listed under — one or more CNSC
|
||||
licences:
|
||||
|
||||
* Class II nuclear facility licence (on-site sources,
|
||||
irradiators, handling of nuclear substances)
|
||||
* Transport licence for nuclear substances
|
||||
* Export licence for controlled nuclear items
|
||||
|
||||
This record tracks the licence number, issue and expiry dates, and
|
||||
any key conditions so the facility can manage renewals and
|
||||
compliance awareness.
|
||||
"""
|
||||
_name = 'fusion.plating.cnsc.licence'
|
||||
_description = 'Fusion Plating — CNSC Licence'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'expiry_date, name'
|
||||
|
||||
name = fields.Char(
|
||||
string='Title',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
licence_number = fields.Char(
|
||||
string='Licence Number',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
facility_id = fields.Many2one(
|
||||
'fusion.plating.facility',
|
||||
string='Facility',
|
||||
tracking=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
related='facility_id.company_id',
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
licence_type = fields.Selection(
|
||||
[
|
||||
('class_ii_nuclear_facility', 'Class II Nuclear Facility'),
|
||||
('transport', 'Transport of Nuclear Substances'),
|
||||
('export', 'Export of Controlled Nuclear Items'),
|
||||
('other', 'Other'),
|
||||
],
|
||||
string='Licence Type',
|
||||
default='class_ii_nuclear_facility',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
issue_date = fields.Date(
|
||||
string='Issue Date',
|
||||
tracking=True,
|
||||
)
|
||||
expiry_date = fields.Date(
|
||||
string='Expiry Date',
|
||||
tracking=True,
|
||||
)
|
||||
conditions = fields.Html(
|
||||
string='Licence Conditions',
|
||||
)
|
||||
state = fields.Selection(
|
||||
[
|
||||
('active', 'Active'),
|
||||
('expiring', 'Expiring Soon'),
|
||||
('expired', 'Expired'),
|
||||
('revoked', 'Revoked'),
|
||||
],
|
||||
string='Status',
|
||||
default='active',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
attachment_id = fields.Many2one(
|
||||
'ir.attachment',
|
||||
string='Licence Document',
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
'fp_cnsc_licence_number_uniq',
|
||||
'unique(licence_number, company_id)',
|
||||
'CNSC licence number must be unique per company.',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,52 @@
|
||||
# -*- 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 fields, models
|
||||
|
||||
|
||||
class FpCustomerSpec(models.Model):
|
||||
"""Extend the quality customer-spec library with nuclear flags.
|
||||
|
||||
When a customer spec is flagged `x_fc_is_nuclear`, the system will
|
||||
surface the N299 level, NQA-1 applicability and nuclear customer
|
||||
type so that jobs referencing the spec can inherit the correct
|
||||
retention and traceability rules.
|
||||
"""
|
||||
_inherit = 'fusion.plating.customer.spec'
|
||||
|
||||
x_fc_is_nuclear = fields.Boolean(
|
||||
string='Nuclear Spec',
|
||||
tracking=True,
|
||||
help='Tick when this specification applies to nuclear work.',
|
||||
)
|
||||
x_fc_n299_level_id = fields.Many2one(
|
||||
'fusion.plating.n299.level',
|
||||
string='CSA N299 Level',
|
||||
tracking=True,
|
||||
)
|
||||
x_fc_nqa1_applicable = fields.Boolean(
|
||||
string='NQA-1 Applicable',
|
||||
tracking=True,
|
||||
help='Tick when this specification covers work subject to ASME NQA-1.',
|
||||
)
|
||||
x_fc_extended_retention_years = fields.Integer(
|
||||
string='Extended Retention (Years)',
|
||||
help='Override the default document-retention period for records '
|
||||
'tied to this specification. Leave 0 to inherit the N299 level '
|
||||
'default or the company default.',
|
||||
)
|
||||
x_fc_nuclear_customer_type = fields.Selection(
|
||||
[
|
||||
('opg', 'OPG — Ontario Power Generation'),
|
||||
('bruce_power', 'Bruce Power'),
|
||||
('aecl', 'AECL / CNL'),
|
||||
('cameco', 'Cameco'),
|
||||
('candu_energy', 'Candu Energy'),
|
||||
('us_utility', 'US Nuclear Utility'),
|
||||
('other', 'Other'),
|
||||
],
|
||||
string='Nuclear Customer Type',
|
||||
tracking=True,
|
||||
)
|
||||
@@ -0,0 +1,81 @@
|
||||
# -*- 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 fields, models
|
||||
|
||||
|
||||
class FpN299Level(models.Model):
|
||||
"""CSA N299 Quality Assurance Level.
|
||||
|
||||
CSA N299 is the Canadian Standards Association standard covering
|
||||
quality assurance program requirements for items and services
|
||||
supplied to nuclear power plants. It defines four levels of quality
|
||||
programs keyed to the safety classification of the supplied item:
|
||||
|
||||
Level 1 — Safety-critical items (highest)
|
||||
Level 2 — Safety-related items
|
||||
Level 3 — Items important to safety
|
||||
Level 4 — Commercial grade (lowest)
|
||||
|
||||
The level drives how long nuclear quality records must be retained
|
||||
(life-of-plant on the highest levels, commercial norms on the lowest)
|
||||
and how rigorous the supplier's quality program must be.
|
||||
"""
|
||||
_name = 'fusion.plating.n299.level'
|
||||
_description = 'Fusion Plating — CSA N299 Quality Level'
|
||||
_order = 'level_number, code'
|
||||
|
||||
name = fields.Char(
|
||||
string='Level',
|
||||
required=True,
|
||||
translate=True,
|
||||
)
|
||||
code = fields.Char(
|
||||
string='Code',
|
||||
required=True,
|
||||
help='Short identifier, e.g. N299_L1.',
|
||||
)
|
||||
level_number = fields.Integer(
|
||||
string='Level Number',
|
||||
required=True,
|
||||
help='1 (highest, safety-critical) to 4 (commercial grade).',
|
||||
)
|
||||
description = fields.Text(
|
||||
string='Description',
|
||||
translate=True,
|
||||
)
|
||||
retention_years = fields.Integer(
|
||||
string='Retention (Years)',
|
||||
required=True,
|
||||
default=40,
|
||||
help='Default records-retention period for this level. Canadian nuclear '
|
||||
'quality records are typically retained for 40 years or longer at '
|
||||
'the highest levels — often "life of plant".',
|
||||
)
|
||||
safety_classification = fields.Selection(
|
||||
[
|
||||
('safety_critical', 'Safety-critical'),
|
||||
('safety_related', 'Safety-related'),
|
||||
('important_to_safety', 'Important to safety'),
|
||||
('commercial_grade', 'Commercial grade'),
|
||||
],
|
||||
string='Safety Classification',
|
||||
required=True,
|
||||
default='safety_critical',
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
'fp_n299_level_code_uniq',
|
||||
'unique(code)',
|
||||
'N299 level code must be unique.',
|
||||
),
|
||||
(
|
||||
'fp_n299_level_number_uniq',
|
||||
'unique(level_number)',
|
||||
'N299 level number must be unique.',
|
||||
),
|
||||
]
|
||||
61
fusion_plating/fusion_plating_nuclear/models/fp_ncr.py
Normal file
61
fusion_plating/fusion_plating_nuclear/models/fp_ncr.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# -*- 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 fields, models
|
||||
|
||||
|
||||
class FpNcr(models.Model):
|
||||
"""Extend the quality NCR with nuclear flags and 10 CFR 21 hook.
|
||||
|
||||
When an NCR is opened against a nuclear job, quality needs to
|
||||
decide within the 10 CFR Part 21 evaluation clock whether the
|
||||
non-conformance rises to the level of a substantial safety hazard.
|
||||
The `x_fc_cfr21_report_id` link lets an NCR spawn a 10 CFR 21
|
||||
record and track the outcome without duplicating the narrative.
|
||||
"""
|
||||
_inherit = 'fusion.plating.ncr'
|
||||
|
||||
x_fc_is_nuclear = fields.Boolean(
|
||||
string='Nuclear Job',
|
||||
tracking=True,
|
||||
help='Tick when the non-conformance occurred on a nuclear job. '
|
||||
'This flags the NCR for 10 CFR Part 21 evaluation.',
|
||||
)
|
||||
x_fc_n299_level_id = fields.Many2one(
|
||||
'fusion.plating.n299.level',
|
||||
string='CSA N299 Level',
|
||||
tracking=True,
|
||||
)
|
||||
x_fc_cfr21_evaluated = fields.Boolean(
|
||||
string='10 CFR 21 Evaluated',
|
||||
tracking=True,
|
||||
)
|
||||
x_fc_cfr21_report_id = fields.Many2one(
|
||||
'fusion.plating.10cfr21.report',
|
||||
string='10 CFR 21 Report',
|
||||
tracking=True,
|
||||
)
|
||||
|
||||
def action_open_cfr21_report(self):
|
||||
"""Create or open the linked 10 CFR Part 21 evaluation record."""
|
||||
self.ensure_one()
|
||||
if not self.x_fc_cfr21_report_id:
|
||||
report = self.env['fusion.plating.10cfr21.report'].create({
|
||||
'discovery_date': fields.Date.context_today(self),
|
||||
'part_description': self.part_ref or '',
|
||||
'defect_description': self.description or '',
|
||||
'ncr_id': self.id,
|
||||
})
|
||||
self.write({
|
||||
'x_fc_cfr21_report_id': report.id,
|
||||
'x_fc_cfr21_evaluated': True,
|
||||
})
|
||||
return {
|
||||
'name': '10 CFR 21 Report',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'fusion.plating.10cfr21.report',
|
||||
'view_mode': 'form',
|
||||
'res_id': self.x_fc_cfr21_report_id.id,
|
||||
}
|
||||
126
fusion_plating/fusion_plating_nuclear/models/fp_nuclear_itp.py
Normal file
126
fusion_plating/fusion_plating_nuclear/models/fp_nuclear_itp.py
Normal file
@@ -0,0 +1,126 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
class FpNuclearItp(models.Model):
|
||||
"""Inspection and Test Plan (ITP).
|
||||
|
||||
For nuclear work, every critical part typically has an approved
|
||||
Inspection and Test Plan that lists:
|
||||
|
||||
* Hold points (customer has to be present before work continues)
|
||||
* Witness points (customer may be present)
|
||||
* Review points (customer reviews records after the fact)
|
||||
* Test methods and acceptance criteria
|
||||
|
||||
The ITP is usually prepared by the supplier, reviewed by the
|
||||
customer, and formally approved before work can start. Once
|
||||
approved, any deviation needs a change record.
|
||||
"""
|
||||
_name = 'fusion.plating.nuclear.itp'
|
||||
_description = 'Fusion Plating — Nuclear Inspection and Test Plan'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'name desc'
|
||||
|
||||
name = fields.Char(
|
||||
string='Reference',
|
||||
required=True,
|
||||
copy=False,
|
||||
readonly=True,
|
||||
default=lambda self: self._default_name(),
|
||||
tracking=True,
|
||||
)
|
||||
customer_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Customer',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
part_number = fields.Char(
|
||||
string='Part Number',
|
||||
tracking=True,
|
||||
)
|
||||
drawing_ref = fields.Char(
|
||||
string='Drawing Ref',
|
||||
tracking=True,
|
||||
)
|
||||
n299_level_id = fields.Many2one(
|
||||
'fusion.plating.n299.level',
|
||||
string='CSA N299 Level',
|
||||
tracking=True,
|
||||
)
|
||||
hold_points = fields.Html(
|
||||
string='Hold / Witness / Review Points',
|
||||
help='Document the hold, witness and review points the customer has '
|
||||
'specified for this part.',
|
||||
)
|
||||
test_methods = fields.Html(
|
||||
string='Test Methods',
|
||||
)
|
||||
acceptance_criteria = fields.Html(
|
||||
string='Acceptance Criteria',
|
||||
)
|
||||
document_ids = fields.Many2many(
|
||||
'ir.attachment',
|
||||
'fp_nuclear_itp_attachment_rel',
|
||||
'itp_id',
|
||||
'attachment_id',
|
||||
string='Attached Documents',
|
||||
)
|
||||
state = fields.Selection(
|
||||
[
|
||||
('draft', 'Draft'),
|
||||
('customer_review', 'Customer Review'),
|
||||
('approved', 'Approved'),
|
||||
('in_use', 'In Use'),
|
||||
('superseded', 'Superseded'),
|
||||
],
|
||||
string='Status',
|
||||
default='draft',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
customer_approval_date = fields.Date(
|
||||
string='Customer Approval',
|
||||
tracking=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
@api.model
|
||||
def _default_name(self):
|
||||
seq = self.env['ir.sequence'].next_by_code('fusion.plating.nuclear.itp')
|
||||
return seq or '/'
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if not vals.get('name') or vals.get('name') == '/':
|
||||
vals['name'] = self._default_name()
|
||||
return super().create(vals_list)
|
||||
|
||||
def action_submit_for_review(self):
|
||||
self.write({'state': 'customer_review'})
|
||||
|
||||
def action_approve(self):
|
||||
self.write({
|
||||
'state': 'approved',
|
||||
'customer_approval_date': fields.Date.context_today(self),
|
||||
})
|
||||
|
||||
def action_put_in_use(self):
|
||||
self.write({'state': 'in_use'})
|
||||
|
||||
def action_supersede(self):
|
||||
self.write({'state': 'superseded'})
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
@@ -0,0 +1,122 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class FpNuclearPedigree(models.Model):
|
||||
"""Nuclear Pedigree Traceability Record.
|
||||
|
||||
Nuclear pedigree is the unbroken chain of traceability from the raw
|
||||
material a plated part was made from, through every processing
|
||||
step, to the finished and shipped component. Nuclear operators use
|
||||
pedigree records decades after shipment — long after the original
|
||||
work order has aged out of normal systems — which is why the
|
||||
retention clock is driven by the N299 level rather than a generic
|
||||
document-retention rule.
|
||||
|
||||
Once the record is locked (typically at ship), the fields above the
|
||||
chatter are read-only and all downstream edits should go through
|
||||
the normal change-control process.
|
||||
"""
|
||||
_name = 'fusion.plating.nuclear.pedigree'
|
||||
_description = 'Fusion Plating — Nuclear Pedigree Record'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'ship_date desc, id desc'
|
||||
|
||||
name = fields.Char(
|
||||
string='Job / Lot Ref',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
part_number = fields.Char(
|
||||
string='Part Number',
|
||||
tracking=True,
|
||||
)
|
||||
lot_serial = fields.Char(
|
||||
string='Lot / Serial',
|
||||
tracking=True,
|
||||
)
|
||||
customer_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Customer',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
n299_level_id = fields.Many2one(
|
||||
'fusion.plating.n299.level',
|
||||
string='CSA N299 Level',
|
||||
tracking=True,
|
||||
)
|
||||
raw_material_cert_refs = fields.Char(
|
||||
string='Raw Material Cert Refs',
|
||||
help='Mill test report, CMTR, or material certificate reference(s).',
|
||||
)
|
||||
raw_material_heat_no = fields.Char(
|
||||
string='Raw Material Heat No',
|
||||
)
|
||||
process_steps_json = fields.Text(
|
||||
string='Process Steps',
|
||||
help='Structured trail of each processing step the part went '
|
||||
'through (station, operator, timestamp, parameters).',
|
||||
)
|
||||
operators_log = fields.Text(
|
||||
string='Operators Log',
|
||||
)
|
||||
chemistry_cert_refs = fields.Char(
|
||||
string='Chemistry Cert Refs',
|
||||
)
|
||||
test_results = fields.Html(
|
||||
string='Test Results',
|
||||
)
|
||||
ship_date = fields.Date(
|
||||
string='Ship Date',
|
||||
tracking=True,
|
||||
)
|
||||
retention_until_date = fields.Date(
|
||||
string='Retain Until',
|
||||
compute='_compute_retention_until_date',
|
||||
store=True,
|
||||
help='Ship date plus the retention period of the assigned N299 level.',
|
||||
)
|
||||
attachment_ids = fields.Many2many(
|
||||
'ir.attachment',
|
||||
'fp_nuclear_pedigree_attachment_rel',
|
||||
'pedigree_id',
|
||||
'attachment_id',
|
||||
string='Supporting Documents',
|
||||
)
|
||||
locked = fields.Boolean(
|
||||
string='Locked',
|
||||
default=False,
|
||||
tracking=True,
|
||||
help='Once the part has shipped, lock the pedigree to prevent '
|
||||
'modification. Further changes must go through formal '
|
||||
'change control.',
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
@api.depends('ship_date', 'n299_level_id.retention_years')
|
||||
def _compute_retention_until_date(self):
|
||||
for rec in self:
|
||||
if rec.ship_date and rec.n299_level_id and rec.n299_level_id.retention_years:
|
||||
rec.retention_until_date = rec.ship_date + relativedelta(
|
||||
years=rec.n299_level_id.retention_years
|
||||
)
|
||||
else:
|
||||
rec.retention_until_date = False
|
||||
|
||||
def action_lock(self):
|
||||
self.write({'locked': True})
|
||||
|
||||
def action_unlock(self):
|
||||
self.write({'locked': False})
|
||||
@@ -0,0 +1,100 @@
|
||||
# -*- 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 fields, models
|
||||
|
||||
|
||||
class FpNuclearProgram(models.Model):
|
||||
"""Nuclear Quality Program per facility.
|
||||
|
||||
A facility that supplies the nuclear industry typically runs a
|
||||
documented quality program aligned to CSA N299 (Canada) and/or
|
||||
NQA-1 (US), and is listed on one or more nuclear operators'
|
||||
approved supplier lists. This record tracks the program manual
|
||||
revision, the target N299 level, supplier certification status, and
|
||||
the audit cadence (internal, customer, CNSC awareness).
|
||||
|
||||
A facility can have more than one nuclear program — e.g. a Level 3
|
||||
program for OPG work and a separate NQA-1 program for a US utility.
|
||||
"""
|
||||
_name = 'fusion.plating.nuclear.program'
|
||||
_description = 'Fusion Plating — Nuclear Quality Program'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'facility_id, name'
|
||||
|
||||
name = fields.Char(
|
||||
string='Program',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
facility_id = fields.Many2one(
|
||||
'fusion.plating.facility',
|
||||
string='Facility',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
related='facility_id.company_id',
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
n299_level_id = fields.Many2one(
|
||||
'fusion.plating.n299.level',
|
||||
string='CSA N299 Level',
|
||||
tracking=True,
|
||||
)
|
||||
nqa1_applicable = fields.Boolean(
|
||||
string='NQA-1 Applicable',
|
||||
tracking=True,
|
||||
help='Tick when this program covers work for US nuclear facilities '
|
||||
'subject to ASME NQA-1.',
|
||||
)
|
||||
cnsc_licensed_supplier = fields.Boolean(
|
||||
string='CNSC Licensed Supplier',
|
||||
tracking=True,
|
||||
help='Tick when the facility holds or is listed under a CNSC '
|
||||
'licence or a CNSC licensee\'s approved supplier programme.',
|
||||
)
|
||||
program_manual_rev = fields.Char(
|
||||
string='Program Manual Rev',
|
||||
tracking=True,
|
||||
)
|
||||
last_audit_date = fields.Date(
|
||||
string='Last Audit',
|
||||
tracking=True,
|
||||
)
|
||||
next_audit_date = fields.Date(
|
||||
string='Next Audit',
|
||||
tracking=True,
|
||||
)
|
||||
state = fields.Selection(
|
||||
[
|
||||
('draft', 'Draft'),
|
||||
('active', 'Active'),
|
||||
('suspended', 'Suspended'),
|
||||
('withdrawn', 'Withdrawn'),
|
||||
],
|
||||
string='Status',
|
||||
default='draft',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
notes = fields.Html(
|
||||
string='Notes',
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
def action_activate(self):
|
||||
self.write({'state': 'active'})
|
||||
|
||||
def action_suspend(self):
|
||||
self.write({'state': 'suspended'})
|
||||
|
||||
def action_withdraw(self):
|
||||
self.write({'state': 'withdrawn'})
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
26
fusion_plating/fusion_plating_nuclear/models/res_company.py
Normal file
26
fusion_plating/fusion_plating_nuclear/models/res_company.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- 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 fields, models
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
"""Extend company with a default nuclear records retention policy.
|
||||
|
||||
Nuclear quality records typically have to be retained for 40 years
|
||||
or longer — far beyond the standard document-retention rule of a
|
||||
commercial shop. This field sets the company-wide default used
|
||||
when a pedigree record has no N299 level linked or when a higher
|
||||
authority needs to be applied.
|
||||
"""
|
||||
_inherit = 'res.company'
|
||||
|
||||
x_fc_nuclear_retention_years = fields.Integer(
|
||||
string='Nuclear Retention (Years)',
|
||||
default=40,
|
||||
help='Default records-retention period for nuclear quality records, '
|
||||
'applied when no N299 level or spec override is set. Typical '
|
||||
'Canadian nuclear practice is 40 years or life-of-plant.',
|
||||
)
|
||||
Reference in New Issue
Block a user