changes
This commit is contained in:
156
fusion_plating/fusion_plating_quality/models/fp_rma_links.py
Normal file
156
fusion_plating/fusion_plating_quality/models/fp_rma_links.py
Normal file
@@ -0,0 +1,156 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase A. Inverse Many2one fields on NCR, Hold and fp.receiving so
|
||||
# RMA can hang One2many counterparts off them. Plus a tiny override on
|
||||
# fp.receiving.create to flip a linked RMA into the `received` state and
|
||||
# trigger the auto-spawn rules.
|
||||
|
||||
import logging
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FpNcrRmaLink(models.Model):
|
||||
_inherit = 'fusion.plating.ncr'
|
||||
|
||||
rma_id = fields.Many2one(
|
||||
'fusion.plating.rma', string='RMA',
|
||||
ondelete='set null', index=True,
|
||||
help='Return that triggered this NCR (auto-set by RMA receive).',
|
||||
)
|
||||
|
||||
|
||||
class FpQualityHoldRmaLink(models.Model):
|
||||
_inherit = 'fusion.plating.quality.hold'
|
||||
|
||||
rma_id = fields.Many2one(
|
||||
'fusion.plating.rma', string='RMA',
|
||||
ondelete='set null', index=True,
|
||||
help='Return that placed these parts on hold.',
|
||||
)
|
||||
|
||||
|
||||
class FpReceivingRmaLink(models.Model):
|
||||
_inherit = 'fp.receiving'
|
||||
|
||||
rma_id = fields.Many2one(
|
||||
'fusion.plating.rma', string='Linked RMA',
|
||||
ondelete='set null', index=True,
|
||||
help='If set, this receiving is the inbound for a customer return. '
|
||||
'When created, it transitions the RMA to `received` and may '
|
||||
'auto-spawn an NCR + Hold per the RMA toggles.',
|
||||
)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
records = super().create(vals_list)
|
||||
# Walk new records, mirror back to RMA, walk the receiving's own
|
||||
# state machine (draft → counted → staged → closed) so the linked
|
||||
# SO's x_fc_receiving_status updates, then fire the RMA receive
|
||||
# hook. Without this the receiving sat at draft and the SO read
|
||||
# 'not_received' even though the parts were physically at the shop.
|
||||
for rec in records:
|
||||
if not rec.rma_id:
|
||||
continue
|
||||
rma = rec.rma_id.sudo()
|
||||
# Mirror inbound link both ways.
|
||||
if not rma.inbound_receiving_id:
|
||||
rma.inbound_receiving_id = rec.id
|
||||
if rma.state in ('authorised', 'shipped_to_us'):
|
||||
# Use received_qty as qty_received fallback if not set.
|
||||
if not rma.qty_received and rec.received_qty:
|
||||
rma.qty_received = rec.received_qty
|
||||
# Walk the receiving's lifecycle to closed so SO status
|
||||
# updates. RMA receipts don't have a multi-day racking
|
||||
# delay (parts are already plated and being inspected for
|
||||
# the complaint, not racked for fresh plating), so we
|
||||
# fast-forward all three transitions in one shot.
|
||||
rec.sudo()._fp_rma_fast_close()
|
||||
rma._enter_received_state(receiving=rec)
|
||||
else:
|
||||
_logger.info(
|
||||
'RMA %s linked to fp.receiving %s but state %s does '
|
||||
'not trigger auto-receive hook.',
|
||||
rma.name, rec.name, rma.state,
|
||||
)
|
||||
return records
|
||||
|
||||
def _fp_rma_fast_close(self):
|
||||
"""Walk an RMA-bound receiving from draft to closed in one call.
|
||||
|
||||
For RMA returns, the receiving's box-count → racking → close walk
|
||||
is purely administrative — the parts are already plated and the
|
||||
operator opens them on triage, not on intake. Fast-forwarding
|
||||
here keeps the SO's x_fc_receiving_status accurate without
|
||||
forcing the receiver to click three buttons in sequence.
|
||||
"""
|
||||
for rec in self:
|
||||
if not rec.box_count_in:
|
||||
# Best-effort default: 1 box if unknown. Real qty lives on
|
||||
# the RMA's qty_returned / qty_received.
|
||||
rec.box_count_in = 1
|
||||
if rec.state == 'draft':
|
||||
rec.action_mark_counted()
|
||||
if rec.state == 'counted':
|
||||
rec.action_mark_staged()
|
||||
if rec.state == 'staged':
|
||||
rec.action_close()
|
||||
|
||||
|
||||
class AccountMoveRmaLink(models.Model):
|
||||
"""Auto-link a credit note back to its RMA when the accountant
|
||||
confirms the reversal wizard. Looks up by invoice_origin matching
|
||||
an RMA's sale_order_id.name, scoped to RMAs in `resolving` state
|
||||
with resolution_type='refund' and no refund_invoice_id yet.
|
||||
|
||||
Also flips the RMA from `resolving` to `resolved` once the credit
|
||||
note is linked — mirrors the auto-progression for replace/rework
|
||||
paths so the RMA doesn't get stuck after a refund.
|
||||
"""
|
||||
_inherit = 'account.move'
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
moves = super().create(vals_list)
|
||||
moves._fp_link_to_rma()
|
||||
return moves
|
||||
|
||||
def write(self, vals):
|
||||
result = super().write(vals)
|
||||
if 'state' in vals and vals.get('state') == 'posted':
|
||||
self._fp_link_to_rma()
|
||||
return result
|
||||
|
||||
def _fp_link_to_rma(self):
|
||||
Rma = self.env['fusion.plating.rma'].sudo()
|
||||
for move in self:
|
||||
if move.move_type != 'out_refund':
|
||||
continue
|
||||
if not move.invoice_origin:
|
||||
continue
|
||||
candidate = Rma.search([
|
||||
('sale_order_id.name', '=', move.invoice_origin),
|
||||
('resolution_type', '=', 'refund'),
|
||||
('refund_invoice_id', '=', False),
|
||||
('state', 'in', ('resolving', 'triaged')),
|
||||
], limit=1)
|
||||
if not candidate:
|
||||
continue
|
||||
candidate.refund_invoice_id = move.id
|
||||
candidate.state = 'resolved'
|
||||
candidate.message_post(
|
||||
body=Markup(
|
||||
'Refund credit note <b>%s</b> linked back to this RMA. '
|
||||
'Marked Resolved.'
|
||||
) % move.name,
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_note',
|
||||
)
|
||||
candidate._fire_rma_notification('rma_resolved')
|
||||
Reference in New Issue
Block a user