Files
Odoo-Modules/fusion_inventory/models/stock_picking.py
gsinghpal e9cf75ee48 changes
2026-03-14 12:04:20 -04:00

223 lines
7.7 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
import logging
from odoo import models, fields, api
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class StockPicking(models.Model):
_inherit = 'stock.picking'
# ── Sale Order link ──
x_fi_sale_order_id = fields.Many2one(
'sale.order', string='Sale Order',
compute='_compute_fi_sale_invoice', store=True)
x_fi_sale_order_state = fields.Selection(
related='x_fi_sale_order_id.state', string='SO Status',
store=True, tracking=False)
# ── Invoice tracking (customer) ──
x_fi_invoice_ids = fields.Many2many(
'account.move', 'stock_picking_invoice_rel',
'picking_id', 'move_id',
string='Invoices',
compute='_compute_fi_sale_invoice', store=True)
x_fi_invoice_count = fields.Integer(
compute='_compute_fi_sale_invoice', store=True)
x_fi_invoice_status = fields.Selection([
('no', 'No Invoice'),
('invoiced', 'Invoiced'),
('paid', 'Paid'),
], string='Invoice Status', compute='_compute_fi_sale_invoice', store=True)
# ── Purchase Order link ──
x_fi_purchase_order_id = fields.Many2one(
'purchase.order', string='Purchase Order',
compute='_compute_fi_purchase_bill', store=True)
x_fi_purchase_order_state = fields.Selection(
related='x_fi_purchase_order_id.state', string='PO Status',
store=True, tracking=False)
# ── Bill tracking (vendor) ──
x_fi_bill_ids = fields.Many2many(
'account.move', 'stock_picking_bill_rel',
'picking_id', 'move_id',
string='Vendor Bills',
compute='_compute_fi_purchase_bill', store=True)
x_fi_bill_count = fields.Integer(
compute='_compute_fi_purchase_bill', store=True)
x_fi_bill_status = fields.Selection([
('no', 'No Bill'),
('billed', 'Billed'),
('paid', 'Paid'),
], string='Bill Status', compute='_compute_fi_purchase_bill', store=True)
@api.depends('sale_id', 'sale_id.invoice_ids', 'sale_id.invoice_ids.payment_state',
'origin')
def _compute_fi_sale_invoice(self):
for pick in self:
so = pick.sale_id
if not so and pick.origin:
so = self.env['sale.order'].search(
[('name', '=', pick.origin)], limit=1)
pick.x_fi_sale_order_id = so.id if so else False
if so:
invoices = so.invoice_ids.filtered(
lambda m: m.state == 'posted' and m.move_type == 'out_invoice')
pick.x_fi_invoice_ids = invoices
pick.x_fi_invoice_count = len(invoices)
if not invoices:
pick.x_fi_invoice_status = 'no'
elif all(inv.payment_state in ('paid', 'in_payment', 'reversed')
for inv in invoices):
pick.x_fi_invoice_status = 'paid'
else:
pick.x_fi_invoice_status = 'invoiced'
else:
pick.x_fi_invoice_ids = self.env['account.move']
pick.x_fi_invoice_count = 0
pick.x_fi_invoice_status = 'no'
@api.depends('purchase_id', 'purchase_id.invoice_ids',
'purchase_id.invoice_ids.payment_state', 'origin')
def _compute_fi_purchase_bill(self):
PO = self.env['purchase.order']
for pick in self:
po = pick.purchase_id
if not po and pick.origin:
po = PO.search([('name', '=', pick.origin)], limit=1)
pick.x_fi_purchase_order_id = po.id if po else False
if po:
bills = po.invoice_ids.filtered(
lambda m: m.state == 'posted' and m.move_type == 'in_invoice')
pick.x_fi_bill_ids = bills
pick.x_fi_bill_count = len(bills)
if not bills:
pick.x_fi_bill_status = 'no'
elif all(b.payment_state in ('paid', 'in_payment', 'reversed')
for b in bills):
pick.x_fi_bill_status = 'paid'
else:
pick.x_fi_bill_status = 'billed'
else:
pick.x_fi_bill_ids = self.env['account.move']
pick.x_fi_bill_count = 0
pick.x_fi_bill_status = 'no'
# ── Smart button actions ──
def action_view_sale_order(self):
self.ensure_one()
if not self.x_fi_sale_order_id:
return
return {
'type': 'ir.actions.act_window',
'name': 'Sale Order',
'res_model': 'sale.order',
'view_mode': 'form',
'res_id': self.x_fi_sale_order_id.id,
}
def action_view_invoices(self):
self.ensure_one()
invoices = self.x_fi_invoice_ids
if not invoices:
return
if len(invoices) == 1:
return {
'type': 'ir.actions.act_window',
'name': 'Invoice',
'res_model': 'account.move',
'view_mode': 'form',
'res_id': invoices.id,
}
return {
'type': 'ir.actions.act_window',
'name': 'Invoices',
'res_model': 'account.move',
'view_mode': 'list,form',
'domain': [('id', 'in', invoices.ids)],
}
def action_view_purchase_order(self):
self.ensure_one()
if not self.x_fi_purchase_order_id:
return
return {
'type': 'ir.actions.act_window',
'name': 'Purchase Order',
'res_model': 'purchase.order',
'view_mode': 'form',
'res_id': self.x_fi_purchase_order_id.id,
}
def action_view_bills(self):
self.ensure_one()
bills = self.x_fi_bill_ids
if not bills:
return
if len(bills) == 1:
return {
'type': 'ir.actions.act_window',
'name': 'Vendor Bill',
'res_model': 'account.move',
'view_mode': 'form',
'res_id': bills.id,
}
return {
'type': 'ir.actions.act_window',
'name': 'Vendor Bills',
'res_model': 'account.move',
'view_mode': 'list,form',
'domain': [('id', 'in', bills.ids)],
}
# ── Booking warning on confirm ──
def button_validate(self):
Booking = self.env['fusion.inventory.booking']
for pick in self.filtered(lambda p: p.picking_type_code == 'outgoing'):
for move in pick.move_ids:
active_bookings = Booking.search([
('product_id', '=', move.product_id.id),
('state', '=', 'active'),
('user_id', '!=', self.env.uid),
])
if active_bookings:
bookers = ', '.join(active_bookings.mapped('user_id.name'))
_logger.warning(
'Product %s is booked by %s but being delivered in %s',
move.product_id.name, bookers, pick.name)
return super().button_validate()
# ── Serial Number Scan ──
def action_scan_serial_numbers(self):
self.ensure_one()
wizard = self.env['fusion.serial.scan.wizard'].create({
'picking_id': self.id,
})
wizard._scan()
return {
'type': 'ir.actions.act_window',
'name': 'Serial Number Scan Results',
'res_model': 'fusion.serial.scan.wizard',
'view_mode': 'form',
'res_id': wizard.id,
'target': 'new',
}