changes
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
{
|
||||
'name': 'Fusion Plating — Receiving & Inspection',
|
||||
'version': '19.0.3.3.0',
|
||||
'version': '19.0.3.5.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Parts receiving, inspection, damage logging, and manufacturing gate.',
|
||||
'description': """
|
||||
|
||||
@@ -7,6 +7,5 @@ from . import fp_receiving_damage
|
||||
from . import fp_receiving_line
|
||||
from . import fp_receiving
|
||||
from . import fp_racking_inspection
|
||||
from . import fp_receiving_racking_link
|
||||
from . import sale_order
|
||||
# Phase 6 (Sub 11) — mrp_production hook retired.
|
||||
# from . import mrp_production
|
||||
|
||||
@@ -125,6 +125,7 @@ class FpReceiving(models.Model):
|
||||
rec.state = 'counted'
|
||||
rec.received_by_id = self.env.user
|
||||
rec.received_date = fields.Datetime.now()
|
||||
rec._update_so_receiving_status()
|
||||
rec.message_post(body=_(
|
||||
'%(user)s counted %(n)d box(es) at receiving.'
|
||||
) % {'user': self.env.user.name, 'n': rec.box_count_in})
|
||||
@@ -219,12 +220,28 @@ class FpReceiving(models.Model):
|
||||
rec.message_post(body=_('Discrepancy resolved.'))
|
||||
|
||||
def _update_so_receiving_status(self):
|
||||
"""Update the linked sale order's receiving status."""
|
||||
"""Update the linked sale order's receiving status.
|
||||
|
||||
Sub 8 maps the new box-count-only states (`counted`, `staged`,
|
||||
`closed`) onto the SO's `x_fc_receiving_status`:
|
||||
- draft -> not_received (no rows or just-created)
|
||||
- counted / staged -> partial (boxes on dock, parts not yet
|
||||
racked / inspected)
|
||||
- closed -> received (all boxes opened, racking done)
|
||||
Legacy states (inspecting / accepted / discrepancy / resolved) keep
|
||||
their original mapping for back-compat with pre-Sub-8 records.
|
||||
"""
|
||||
for rec in self:
|
||||
if rec.sale_order_id:
|
||||
if rec.state in ('accepted', 'resolved'):
|
||||
rec.sale_order_id.x_fc_receiving_status = 'received'
|
||||
elif rec.state == 'discrepancy':
|
||||
rec.sale_order_id.x_fc_receiving_status = 'partial'
|
||||
elif rec.state == 'inspecting':
|
||||
rec.sale_order_id.x_fc_receiving_status = 'partial'
|
||||
if not rec.sale_order_id:
|
||||
continue
|
||||
if rec.state == 'closed':
|
||||
rec.sale_order_id.x_fc_receiving_status = 'received'
|
||||
elif rec.state in ('counted', 'staged'):
|
||||
rec.sale_order_id.x_fc_receiving_status = 'partial'
|
||||
# Legacy states preserved.
|
||||
elif rec.state in ('accepted', 'resolved'):
|
||||
rec.sale_order_id.x_fc_receiving_status = 'received'
|
||||
elif rec.state in ('discrepancy', 'inspecting'):
|
||||
rec.sale_order_id.x_fc_receiving_status = 'partial'
|
||||
elif rec.state == 'draft':
|
||||
rec.sale_order_id.x_fc_receiving_status = 'not_received'
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
#
|
||||
# Sub 12 audit fix — discoverable handoff from fp.receiving (boxes
|
||||
# counted) to fp.racking.inspection (parts inspected by the racking
|
||||
# crew). The racking inspection is auto-created on fp.job.action_confirm
|
||||
# but until now there was no smart-button on the receiving form to find
|
||||
# it — racking crew had to navigate via a separate menu.
|
||||
|
||||
from odoo import _, fields, models
|
||||
|
||||
|
||||
class FpReceivingRackingLink(models.Model):
|
||||
_inherit = 'fp.receiving'
|
||||
|
||||
racking_inspection_count = fields.Integer(
|
||||
string='Racking Inspections', compute='_compute_racking_inspection_count',
|
||||
)
|
||||
|
||||
def _compute_racking_inspection_count(self):
|
||||
Inspection = self.env['fp.racking.inspection'] \
|
||||
if 'fp.racking.inspection' in self.env else None
|
||||
for rec in self:
|
||||
if Inspection is None or not rec.sale_order_id:
|
||||
rec.racking_inspection_count = 0
|
||||
continue
|
||||
rec.racking_inspection_count = Inspection.search_count([
|
||||
('sale_order_id', '=', rec.sale_order_id.id),
|
||||
])
|
||||
|
||||
def action_view_racking_inspections(self):
|
||||
"""Open the racking inspection(s) for this receiving's SO. If
|
||||
none exists yet, default-create context lets the user spawn one
|
||||
with the SO context pre-filled.
|
||||
"""
|
||||
self.ensure_one()
|
||||
Inspection = self.env['fp.racking.inspection']
|
||||
domain = [('sale_order_id', '=', self.sale_order_id.id)] \
|
||||
if self.sale_order_id else []
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Racking Inspections'),
|
||||
'res_model': 'fp.racking.inspection',
|
||||
'view_mode': 'list,form',
|
||||
'domain': domain,
|
||||
'context': {
|
||||
'default_sale_order_id': self.sale_order_id.id
|
||||
if self.sale_order_id else False,
|
||||
},
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import models, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MrpProduction(models.Model):
|
||||
_inherit = 'mrp.production'
|
||||
|
||||
def action_confirm(self):
|
||||
"""Soft gate: warn if parts haven't been received yet.
|
||||
|
||||
Checks the linked sale order's receiving status. If parts
|
||||
are not yet received, logs a warning. This is informational
|
||||
only -- it does not block confirmation. The gate is soft
|
||||
because handshake deals and urgent jobs need flexibility.
|
||||
"""
|
||||
for production in self:
|
||||
so = production._get_source_sale_order()
|
||||
if so and so.x_fc_receiving_status in ('not_received', False):
|
||||
_logger.warning(
|
||||
'MO %s: parts not yet received for SO %s (receiving status: %s). '
|
||||
'Proceeding with confirmation.',
|
||||
production.name, so.name, so.x_fc_receiving_status,
|
||||
)
|
||||
production.message_post(
|
||||
body=_(
|
||||
'Warning: Parts not yet received for sale order '
|
||||
'<a href="/odoo/sale-order/%s">%s</a>. '
|
||||
'Manufacturing confirmed without receiving verification.'
|
||||
) % (so.id, so.name),
|
||||
)
|
||||
return super().action_confirm()
|
||||
|
||||
def _get_source_sale_order(self):
|
||||
"""Find the sale order linked to this MO via origin field."""
|
||||
self.ensure_one()
|
||||
if not self.origin:
|
||||
return False
|
||||
# origin may contain SO name like "S00001" or configurator ref "CFG-00001"
|
||||
so = self.env['sale.order'].search(
|
||||
[('name', '=', self.origin)], limit=1,
|
||||
)
|
||||
if not so:
|
||||
# Try matching by origin containing the SO name
|
||||
so = self.env['sale.order'].search(
|
||||
[('name', 'ilike', self.origin)], limit=1,
|
||||
)
|
||||
return so or False
|
||||
@@ -76,13 +76,24 @@
|
||||
statusbar_visible="draft,counted,staged,closed"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_view_racking_inspections"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-search-plus">
|
||||
<field name="racking_inspection_count"
|
||||
widget="statinfo"
|
||||
string="Racking Inspections"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<i class="fa fa-info-circle me-2"/>
|
||||
<strong>Receiving = box count only.</strong>
|
||||
Count the boxes the truck dropped off, set the number
|
||||
below, and stage them for racking. The racking crew
|
||||
opens the boxes and inspects each part — see
|
||||
<em>Plating → Operations → Racking Inspection</em>.
|
||||
opens the boxes and inspects each part — click
|
||||
<strong>Racking Inspections</strong> above to jump
|
||||
straight to the open inspection for this SO.
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
|
||||
Reference in New Issue
Block a user