Files
gsinghpal f08f328688 changes
2026-04-27 00:11:18 -04:00

157 lines
5.9 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.
#
# 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')