changes
This commit is contained in:
222
fusion_inventory/models/stock_picking.py
Normal file
222
fusion_inventory/models/stock_picking.py
Normal file
@@ -0,0 +1,222 @@
|
||||
# -*- 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',
|
||||
}
|
||||
Reference in New Issue
Block a user