Skip to content

Commit db900fb

Browse files
committed
[ADD] products_orderby_invoice: Imp dropdown in SO/RFQ Based on Invoice History
Modified the product dropdown logic in Sales Order (SO) and Request for Quotation (RFQ) forms to prioritize products based on the customer's invoice history. - Dropdown now shows products ordered by the customer in previous invoices - Ordering is based on most recent to oldest invoices - Aims to make product selection faster and more relevant for recurring customers
1 parent fbf9ee9 commit db900fb

File tree

6 files changed

+145
-0
lines changed

6 files changed

+145
-0
lines changed

products_orderby_invoice/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from . import models
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
{
4+
'name': 'Products Orderby Invoice',
5+
'version': '1.0',
6+
'depends': ['sale_management', 'account', 'stock', 'purchase'],
7+
'data': [
8+
'views/product_views.xml',
9+
],
10+
'installable': True,
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from . import product_template
4+
from . import product_product
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from odoo import api, fields, models
2+
3+
class ProductProduct(models.Model):
4+
_inherit = 'product.product'
5+
6+
7+
surplus_qty = fields.Float(string='Surplus Quantity', compute='_compute_surplus_qty')
8+
9+
@api.depends('virtual_available', 'qty_available')
10+
def _compute_surplus_qty(self):
11+
for rec in self:
12+
rec.surplus_qty = rec.qty_available - rec.virtual_available
13+
14+
@api.model
15+
def name_search(self, name, args=None, operator='ilike', limit=100):
16+
print("===============This is triggered====================")
17+
args = list(args) if args else []
18+
partner_id = self.env.context.get('partner_id')
19+
results = []
20+
matched_ids = set()
21+
22+
if partner_id:
23+
lines = self.env['account.move.line'].search([
24+
('move_id.move_type', '=', 'out_invoice'),
25+
('move_id.partner_id', '=', partner_id),
26+
('move_id.state', '=', 'posted'),
27+
('product_id', '!=', False),
28+
])
29+
30+
lines = sorted(lines, key=lambda l: l.move_id.invoice_date or l.create_date, reverse=True)
31+
32+
name_lower = name.lower() if name else ''
33+
34+
for line in lines:
35+
product = line.product_id
36+
if product.id in matched_ids:
37+
continue
38+
if not name or (operator == 'ilike' and name_lower in product.name.lower()):
39+
results.append((product.id, product.display_name))
40+
matched_ids.add(product.id)
41+
if len(results) >= limit:
42+
break
43+
44+
remaining = limit - len(results)
45+
if remaining > 0:
46+
domain = args[:]
47+
if name:
48+
domain.append(('name', operator, name))
49+
if matched_ids:
50+
domain.append(('id', 'not in', list(matched_ids))) # needs to be a list here
51+
others = super().name_search(name, domain, operator=operator, limit=remaining)
52+
results.extend(others)
53+
54+
return results
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from odoo import models, fields, api
2+
from datetime import date
3+
4+
5+
class ProductTemplate(models.Model):
6+
_inherit = "product.template"
7+
8+
@api.model
9+
def name_search(self, name, args=None, operator="ilike", limit=100):
10+
args = list(args) if args else []
11+
partner_id = self.env.context.get("partner_id")
12+
results = []
13+
matched_ids = []
14+
15+
lines = self.env["account.move.line"].search([
16+
("move_id.move_type", "=", "out_invoice"),
17+
("move_id.partner_id", "=", partner_id),
18+
("move_id.state", "=", "posted"),
19+
("product_id.product_tmpl_id", "!=", False),
20+
])
21+
22+
lines = sorted(
23+
lines, key=lambda l: l.move_id.invoice_date or fields.Date.today(),
24+
reverse=True)
25+
26+
tmpl_map = {}
27+
for line in lines:
28+
tmpl = line.product_id.product_tmpl_id
29+
if tmpl.id not in tmpl_map:
30+
tmpl_map[tmpl.id] = {"tmpl": tmpl, "date": line.move_id.invoice_date}
31+
if len(tmpl_map) >= limit:
32+
break
33+
34+
name_lower = name.lower() if name else ""
35+
today = date.today()
36+
37+
for info in tmpl_map.values():
38+
tmpl = info["tmpl"]
39+
invoice_date = info["date"]
40+
if not name or (operator == "ilike" and name_lower in tmpl.name.lower()):
41+
days = (today - invoice_date).days if invoice_date else "?"
42+
display = f"{tmpl.display_name} (Last ordered {days} days ago)"
43+
results.append((tmpl.id, display))
44+
matched_ids.append(tmpl.id)
45+
if len(results) >= limit:
46+
break
47+
48+
remaining = limit - len(results)
49+
if remaining > 0:
50+
domain = args[:]
51+
if name:
52+
domain.append(("name", operator, name))
53+
if matched_ids:
54+
domain.append(("id", "not in", matched_ids))
55+
others = super().name_search(name, args=domain, operator=operator, limit=remaining)
56+
results.extend(others)
57+
58+
return results
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<odoo>
3+
<record id="product_view_kanban_catalog_surplus_qty" model="ir.ui.view">
4+
<field name="name">product.view.kanban.catalog.inherit.surplus.qty</field>
5+
<field name="model">product.product</field>
6+
<field name="inherit_id" ref="product.product_view_kanban_catalog"/>
7+
<field name="arch" type="xml">
8+
<xpath expr="//div[@name='o_kanban_qty_available']/field[@name='uom_id']" position="after">
9+
<span> (</span>
10+
<field name="surplus_qty"></field>
11+
<span>)</span>
12+
</xpath>
13+
</field>
14+
</record>
15+
</odoo>

0 commit comments

Comments
 (0)