Skip to content

Commit d7feb26

Browse files
committed
[IMP] products_orderby_invoice: Imp search and catalog based on invoice history
Purpose: Make it easier to find and show products based on past invoices or bills. Approach: Changed name_search to list first the products already invoiced to the customer, sorted by latest date. Updated product catalog view to show last invoice date, moved info around, and added forecasted qty with color. Impact: Improves product search and helps users quickly spot stock levels and recent purchases.
1 parent db900fb commit d7feb26

File tree

4 files changed

+61
-49
lines changed

4 files changed

+61
-49
lines changed

products_orderby_invoice/__manifest__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88
'views/product_views.xml',
99
],
1010
'installable': True,
11+
'license': 'LGPL-3',
1112
}

products_orderby_invoice/models/product_product.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
13
from odoo import api, fields, models
24

5+
36
class ProductProduct(models.Model):
47
_inherit = 'product.product'
58

6-
79
surplus_qty = fields.Float(string='Surplus Quantity', compute='_compute_surplus_qty')
10+
invoice_date = fields.Datetime(string='Last Invoice Date', compute='_compute_invoice_date')
11+
12+
@api.depends('invoice_date')
13+
def _compute_invoice_date(self):
14+
for rec in self:
15+
lines = self.env['account.move.line'].search([
16+
('product_id', '=', rec.id),
17+
('move_id.move_type', '=', 'out_invoice'),
18+
('move_id.state', '=', 'posted'),
19+
], limit=1, order='date desc')
20+
rec.invoice_date = lines.move_id.date if lines else False
821

922
@api.depends('virtual_available', 'qty_available')
1023
def _compute_surplus_qty(self):
@@ -13,7 +26,6 @@ def _compute_surplus_qty(self):
1326

1427
@api.model
1528
def name_search(self, name, args=None, operator='ilike', limit=100):
16-
print("===============This is triggered====================")
1729
args = list(args) if args else []
1830
partner_id = self.env.context.get('partner_id')
1931
results = []
@@ -29,15 +41,12 @@ def name_search(self, name, args=None, operator='ilike', limit=100):
2941

3042
lines = sorted(lines, key=lambda l: l.move_id.invoice_date or l.create_date, reverse=True)
3143

32-
name_lower = name.lower() if name else ''
33-
3444
for line in lines:
3545
product = line.product_id
3646
if product.id in matched_ids:
3747
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)
48+
results.append((product.id, product.display_name))
49+
matched_ids.add(product.id)
4150
if len(results) >= limit:
4251
break
4352

@@ -47,7 +56,7 @@ def name_search(self, name, args=None, operator='ilike', limit=100):
4756
if name:
4857
domain.append(('name', operator, name))
4958
if matched_ids:
50-
domain.append(('id', 'not in', list(matched_ids))) # needs to be a list here
59+
domain.append(('id', 'not in', list(matched_ids)))
5160
others = super().name_search(name, domain, operator=operator, limit=remaining)
5261
results.extend(others)
5362

products_orderby_invoice/models/product_template.py

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
13
from odoo import models, fields, api
2-
from datetime import date
34

45

56
class ProductTemplate(models.Model):
@@ -10,48 +11,35 @@ def name_search(self, name, args=None, operator="ilike", limit=100):
1011
args = list(args) if args else []
1112
partner_id = self.env.context.get("partner_id")
1213
results = []
13-
matched_ids = []
14+
seen_ids = set()
1415

15-
lines = self.env["account.move.line"].search([
16+
if partner_id:
17+
lines = self.env["account.move.line"].search([
1618
("move_id.move_type", "=", "out_invoice"),
1719
("move_id.partner_id", "=", partner_id),
1820
("move_id.state", "=", "posted"),
1921
("product_id.product_tmpl_id", "!=", False),
2022
])
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)"
23+
lines = sorted(lines, key=lambda l: l.move_id.invoice_date or fields.Date.today(), reverse=True)
24+
25+
for line in lines:
26+
tmpl = line.product_id.product_tmpl_id
27+
if tmpl.id in seen_ids:
28+
continue
29+
days = line.product_id.product_tmpl_id.sale_delay
30+
display = f"{tmpl.display_name} (Order Lead time {days} days)"
4331
results.append((tmpl.id, display))
44-
matched_ids.append(tmpl.id)
45-
if len(results) >= limit:
46-
break
32+
seen_ids.add(tmpl.id)
33+
if len(results) >= limit:
34+
break
4735

4836
remaining = limit - len(results)
4937
if remaining > 0:
5038
domain = args[:]
5139
if name:
5240
domain.append(("name", operator, name))
53-
if matched_ids:
54-
domain.append(("id", "not in", matched_ids))
41+
if seen_ids:
42+
domain.append(("id", "not in", list(seen_ids)))
5543
others = super().name_search(name, args=domain, operator=operator, limit=remaining)
5644
results.extend(others)
5745

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<odoo>
33
<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>
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']"
9+
position="after">
10+
<span> (</span>
11+
<t t-if="record.surplus_qty.raw_value &gt; 0">
12+
<span class="text-success">+<field name="surplus_qty" /></span>
13+
</t>
14+
<t t-elif="record.surplus_qty.raw_value &lt; 0">
15+
<span class="text-danger">
16+
<field name="surplus_qty" />
17+
</span>
18+
</t>
19+
<t t-else="">
20+
<span class="text-muted">0.00</span>
21+
</t>
22+
<span>)</span>
23+
<div>
24+
Last invoice date :<field name="invoice_date" widget="remaining_days"/>
25+
</div>
26+
</xpath>
27+
</field>
28+
</record>
1529
</odoo>

0 commit comments

Comments
 (0)