folder rename
This commit is contained in:
184
fusion_plating/fusion_plating_quality/models/fp_quality_hold.py
Normal file
184
fusion_plating/fusion_plating_quality/models/fp_quality_hold.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# -*- 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 FpQualityHold(models.Model):
|
||||
"""Quality Hold — parts pulled from production for quality review.
|
||||
|
||||
Enables the Steelhead-style "Move Parts Into Quality Management"
|
||||
workflow. An operator can split a partial quantity off a job and
|
||||
place it on hold for inspection, rework, or scrap.
|
||||
"""
|
||||
_name = 'fusion.plating.quality.hold'
|
||||
_description = 'Fusion Plating — Quality Hold'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'create_date desc'
|
||||
|
||||
name = fields.Char(
|
||||
string='Reference',
|
||||
required=True,
|
||||
copy=False,
|
||||
readonly=True,
|
||||
default=lambda self: self._default_name(),
|
||||
tracking=True,
|
||||
)
|
||||
|
||||
# ----- What's on hold -----
|
||||
# NOTE: workorder_id, production_id, and portal_job_id live in
|
||||
# fusion_plating_bridge_mrp (which depends on mrp and
|
||||
# fusion_plating_portal). Keeping them here would force hard
|
||||
# dependencies and break minimal CE-only installs.
|
||||
part_ref = fields.Char(string='Part Number')
|
||||
|
||||
# ----- Hold details -----
|
||||
qty_on_hold = fields.Integer(string='Qty on Hold', required=True)
|
||||
qty_original = fields.Integer(string='Original Qty')
|
||||
mark_for_scrap = fields.Boolean(string='Mark for Scrap', default=False)
|
||||
hold_reason = fields.Selection(
|
||||
[
|
||||
('damaged', 'Parts Damaged'),
|
||||
('out_of_spec', 'Out of Specification'),
|
||||
('contamination', 'Contamination'),
|
||||
('customer_complaint', 'Customer Complaint'),
|
||||
('process_deviation', 'Process Deviation'),
|
||||
('other', 'Other'),
|
||||
],
|
||||
string='Hold Reason',
|
||||
default='other',
|
||||
tracking=True,
|
||||
)
|
||||
description = fields.Text(string='Description')
|
||||
attachment_ids = fields.Many2many(
|
||||
'ir.attachment',
|
||||
string='Attachments',
|
||||
)
|
||||
|
||||
# ----- Location / station context -----
|
||||
facility_id = fields.Many2one(
|
||||
'fusion.plating.facility',
|
||||
string='Facility',
|
||||
)
|
||||
work_center_id = fields.Many2one(
|
||||
'fusion.plating.work.center',
|
||||
string='Station',
|
||||
)
|
||||
current_process_node = fields.Char(string='Current Process Node')
|
||||
|
||||
# ----- Status -----
|
||||
state = fields.Selection(
|
||||
[
|
||||
('on_hold', 'On Hold'),
|
||||
('under_review', 'Under Review'),
|
||||
('released', 'Released to Production'),
|
||||
('scrapped', 'Scrapped'),
|
||||
('reworked', 'Sent to Rework'),
|
||||
],
|
||||
string='Status',
|
||||
default='on_hold',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
|
||||
# ----- Resolution -----
|
||||
ncr_id = fields.Many2one('fusion.plating.ncr', string='Linked NCR')
|
||||
resolved_by_id = fields.Many2one('res.users', string='Resolved By')
|
||||
resolution_date = fields.Datetime(string='Resolution Date')
|
||||
resolution_notes = fields.Text(string='Resolution Notes')
|
||||
|
||||
# ----- Housekeeping -----
|
||||
operator_id = fields.Many2one(
|
||||
'res.users',
|
||||
string='Held By',
|
||||
default=lambda self: self.env.user,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Defaults / create
|
||||
# ------------------------------------------------------------------
|
||||
@api.model
|
||||
def _default_name(self):
|
||||
seq = self.env['ir.sequence'].next_by_code(
|
||||
'fusion.plating.quality.hold',
|
||||
)
|
||||
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)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Actions
|
||||
# ------------------------------------------------------------------
|
||||
def action_start_review(self):
|
||||
self.write({'state': 'under_review'})
|
||||
self._post_state_message('Under Review')
|
||||
|
||||
def action_release(self):
|
||||
self.write({
|
||||
'state': 'released',
|
||||
'resolved_by_id': self.env.user.id,
|
||||
'resolution_date': fields.Datetime.now(),
|
||||
})
|
||||
self._post_state_message('Released to Production')
|
||||
|
||||
def action_scrap(self):
|
||||
self.write({
|
||||
'state': 'scrapped',
|
||||
'mark_for_scrap': True,
|
||||
'resolved_by_id': self.env.user.id,
|
||||
'resolution_date': fields.Datetime.now(),
|
||||
})
|
||||
self._post_state_message('Scrapped')
|
||||
|
||||
def action_send_to_rework(self):
|
||||
self.write({
|
||||
'state': 'reworked',
|
||||
'resolved_by_id': self.env.user.id,
|
||||
'resolution_date': fields.Datetime.now(),
|
||||
})
|
||||
self._post_state_message('Sent to Rework')
|
||||
|
||||
def action_create_ncr(self):
|
||||
"""Create a linked NCR from this hold record."""
|
||||
self.ensure_one()
|
||||
ncr = self.env['fusion.plating.ncr'].create({
|
||||
'facility_id': self.facility_id.id,
|
||||
'source': 'shop_floor',
|
||||
'severity': 'medium',
|
||||
'part_ref': self.part_ref,
|
||||
'quantity_affected': self.qty_on_hold,
|
||||
'description': self.description or '',
|
||||
})
|
||||
self.write({'ncr_id': ncr.id})
|
||||
self._post_state_message(f'NCR {ncr.name} created')
|
||||
return {
|
||||
'name': 'NCR',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'fusion.plating.ncr',
|
||||
'res_id': ncr.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------
|
||||
def _post_state_message(self, label):
|
||||
for rec in self:
|
||||
rec.message_post(
|
||||
body=f"Hold status changed to <b>{label}</b>.",
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
Reference in New Issue
Block a user