Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions custom_sale_purchase_display/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
14 changes: 14 additions & 0 deletions custom_sale_purchase_display/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
'name': 'Sale Order Product History',
'version': '1.0',
'category': 'Sales',
'depends': ['sale_management', 'stock', 'purchase'],
'data': [
'views/purchase_order_form.xml',
'views/sale_order_form_view.xml',
'views/product_template_kanban_catalog.xml',
],
'installable': True,
'application': False,
'license': 'LGPL-3'
}
2 changes: 2 additions & 0 deletions custom_sale_purchase_display/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import product_product
from . import product_template
79 changes: 79 additions & 0 deletions custom_sale_purchase_display/models/product_product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from odoo import models, fields, api


class ProductProduct(models.Model):
_inherit = 'product.product'

last_invoice_date = fields.Date(
string="Last Invoice Date",
compute="_compute_last_invoice_data",
store=False
)
last_invoice_time_diff = fields.Char(
string="Last Invoice Time Diff",
compute="_compute_last_invoice_data",
store=False
)

def _compute_last_invoice_data(self):
for product in self:
partner_id = product.env.context.get('sale_order_partner_id') \
or product.env.context.get('purchase_order_partner_id')

is_sale = bool(product.env.context.get('sale_order_partner_id'))
move_type = 'out_invoice' if is_sale else 'in_invoice'

domain = [
('state', '=', 'posted'),
('invoice_date', '!=', False),
('line_ids.product_id', '=', product.id),
('move_type', '=', move_type),
]
if partner_id:
domain.append(('partner_id', '=', partner_id))

move = product.env['account.move'].search(
domain, order='invoice_date desc', limit=1
)

product.last_invoice_date = move.invoice_date if move else False
product.last_invoice_time_diff = (
self._format_time_diff(move.invoice_date) if move else False
)

@api.model
def _get_recent_invoices(self, partner_id, is_sale=True):
if not partner_id:
return []

move_type = 'out_invoice' if is_sale else 'in_invoice'
moves = self.env['account.move'].search([
('partner_id', '=', partner_id),
('move_type', '=', move_type),
('state', '=', 'posted'),
('invoice_date', '!=', False)
], order='invoice_date desc')

recent, seen = [], set()
for mv in moves:
for line in mv.line_ids.filtered('product_id'):
pid = line.product_id.id
if pid not in seen:
recent.append({'pid': pid, 'date': mv.invoice_date})
seen.add(pid)
return recent

@api.model
def _format_time_diff(self, invoice_date):
if not invoice_date:
return ""
days = (fields.Date.today() - invoice_date).days
if days > 365:
return f"{days // 365}y ago"
if days > 30:
return f"{days // 30}mo ago"
if days > 7:
return f"{days // 7}w ago"
if days > 0:
return f"{days}d ago"
return "today"
68 changes: 68 additions & 0 deletions custom_sale_purchase_display/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from odoo import models, fields, api


class ProductTemplate(models.Model):
_inherit = 'product.template'

last_invoice_date = fields.Date(
string="Last Invoice Date",
related='product_variant_id.last_invoice_date',
store=False
)
last_invoice_time_diff = fields.Char(
string="Last Invoice Time Diff",
related='product_variant_id.last_invoice_time_diff',
store=False
)

product_variant_id = fields.Many2one(
'product.product',
compute='_compute_product_variant_id',
store=True,
index=True
)

@api.model
def name_search(self, name="", args=None, operator="ilike", limit=100):
args = args or []

partner_id = self.env.context.get('sale_order_partner_id') \
or self.env.context.get('purchase_order_partner_id')
if not partner_id:
return super().name_search(name, args, operator, limit)

is_sale = bool(self.env.context.get('sale_order_partner_id'))
recent_lines = self.env['product.product']._get_recent_invoices(
partner_id=partner_id,
is_sale=is_sale
)

if not recent_lines:
return super().name_search(name, args, operator, limit)

recent_template_ids = list(dict.fromkeys(
self.env['product.product'].browse(rl['pid']).product_tmpl_id.id
for rl in recent_lines
))

base_domain = [('name', operator, name)] + args

recent_templates = self.search(
[('id', 'in', recent_template_ids)] + base_domain,
limit=limit
)
other_templates = self.search(
[('id', 'not in', recent_template_ids)] + base_domain,
limit=max(0, limit - len(recent_templates))
)

results = []
for tmpl_id in recent_template_ids:
tmpl = recent_templates.filtered(lambda t: t.id == tmpl_id)
if tmpl:
td = tmpl.last_invoice_time_diff
label = f"{tmpl.display_name} ⏱ {td}" if td else tmpl.display_name
results.append((tmpl.id, label))

results.extend((t.id, t.display_name) for t in other_templates)
return results
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_product_template_kanban_inherit" model="ir.ui.view">
<field name="name">product.product.kanban.inherit.catalog</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_view_kanban_catalog"/>
<field name="arch" type="xml">
<xpath expr="//div[contains(@name, 'o_kanban_qty_available')]" position="inside">
<field name="virtual_available" invisible="1"/>
<field name="qty_available" invisible="1"/> <!-- Ensure loaded -->
<field name="last_invoice_date" invisible="1"/> <!-- Ensure loaded -->
<field name="last_invoice_time_diff" invisible="1"/> <!-- Ensure loaded -->
<t t-set="diff" t-value="record.virtual_available.raw_value - record.qty_available.raw_value"/>
<t t-if="diff &gt; 0">
<div style="color: green;">(+ <t t-esc="diff"/>)</div>
</t>
<t t-elif="diff &lt; 0">
<div style="color: red;">(<t t-esc="diff"/>)</div>
</t>
<t t-else="">
<div>(0)</div>
</t>
<div t-if="record.last_invoice_time_diff.value" style="font-size: 80%; color: #888;">
⏱ <t t-esc="record.last_invoice_time_diff.value"/>
</div>
<div t-if="record.last_invoice_date.value" style="font-size: 80%; color: #aaa;">
📅 <t t-esc="record.last_invoice_date.value"/>
</div>
</xpath>
</field>
</record>
</odoo>
13 changes: 13 additions & 0 deletions custom_sale_purchase_display/views/purchase_order_form.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_order_form_inherit_custom" model="ir.ui.view">
<field name="name">purchase.order.form.inherit.custom</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='order_line']/list/field[@name='product_id']" position="attributes">
<attribute name="context">{'purchase_order_partner_id': parent.partner_id}</attribute>
</xpath>
</field>
</record>
</odoo>
16 changes: 16 additions & 0 deletions custom_sale_purchase_display/views/sale_order_form_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_order_form_inherit_custom" model="ir.ui.view">
<field name="name">sale.order.form.inherit.custom</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='order_line']/list/field[@name='product_id']" position="attributes">
<attribute name="context">{'sale_order_partner_id': parent.partner_id}</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/list/field[@name='product_template_id']" position="attributes">
<attribute name="context">{'sale_order_partner_id': parent.partner_id}</attribute>
</xpath>
</field>
</record>
</odoo>