diff --git a/products_orderby_invoice/__init__.py b/products_orderby_invoice/__init__.py new file mode 100644 index 00000000000..d6210b1285d --- /dev/null +++ b/products_orderby_invoice/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/products_orderby_invoice/__manifest__.py b/products_orderby_invoice/__manifest__.py new file mode 100644 index 00000000000..91ab0968878 --- /dev/null +++ b/products_orderby_invoice/__manifest__.py @@ -0,0 +1,12 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Products Orderby Invoice', + 'version': '1.0', + 'depends': ['sale_management', 'account', 'stock', 'purchase'], + 'data': [ + 'views/product_views.xml', + ], + 'installable': True, + 'license': 'LGPL-3', +} diff --git a/products_orderby_invoice/models/__init__.py b/products_orderby_invoice/models/__init__.py new file mode 100644 index 00000000000..080a714ff17 --- /dev/null +++ b/products_orderby_invoice/models/__init__.py @@ -0,0 +1,4 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import product_template +from . import product_product diff --git a/products_orderby_invoice/models/product_product.py b/products_orderby_invoice/models/product_product.py new file mode 100644 index 00000000000..6cf77900596 --- /dev/null +++ b/products_orderby_invoice/models/product_product.py @@ -0,0 +1,63 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class ProductProduct(models.Model): + _inherit = 'product.product' + + surplus_qty = fields.Float(string='Surplus Quantity', compute='_compute_surplus_qty') + invoice_date = fields.Datetime(string='Last Invoice Date', compute='_compute_invoice_date') + + @api.depends('invoice_date') + def _compute_invoice_date(self): + for rec in self: + lines = self.env['account.move.line'].search([ + ('product_id', '=', rec.id), + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.state', '=', 'posted'), + ], limit=1, order='date desc') + rec.invoice_date = lines.move_id.date if lines else False + + @api.depends('virtual_available', 'qty_available') + def _compute_surplus_qty(self): + for rec in self: + rec.surplus_qty = rec.qty_available - rec.virtual_available + + @api.model + def name_search(self, name, args=None, operator='ilike', limit=100): + args = list(args) if args else [] + partner_id = self.env.context.get('partner_id') + results = [] + matched_ids = set() + + if partner_id: + lines = self.env['account.move.line'].search([ + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.partner_id', '=', partner_id), + ('move_id.state', '=', 'posted'), + ('product_id', '!=', False), + ]) + + lines = sorted(lines, key=lambda l: l.move_id.invoice_date or l.create_date, reverse=True) + + for line in lines: + product = line.product_id + if product.id in matched_ids: + continue + results.append((product.id, product.display_name)) + matched_ids.add(product.id) + if len(results) >= limit: + break + + remaining = limit - len(results) + if remaining > 0: + domain = args[:] + if name: + domain.append(('name', operator, name)) + if matched_ids: + domain.append(('id', 'not in', list(matched_ids))) + others = super().name_search(name, domain, operator=operator, limit=remaining) + results.extend(others) + + return results diff --git a/products_orderby_invoice/models/product_template.py b/products_orderby_invoice/models/product_template.py new file mode 100644 index 00000000000..9edb9ec3c75 --- /dev/null +++ b/products_orderby_invoice/models/product_template.py @@ -0,0 +1,46 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields, api + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + @api.model + def name_search(self, name, args=None, operator="ilike", limit=100): + args = list(args) if args else [] + partner_id = self.env.context.get("partner_id") + results = [] + seen_ids = set() + + if partner_id: + lines = self.env["account.move.line"].search([ + ("move_id.move_type", "=", "out_invoice"), + ("move_id.partner_id", "=", partner_id), + ("move_id.state", "=", "posted"), + ("product_id.product_tmpl_id", "!=", False), + ]) + lines = sorted(lines, key=lambda l: l.move_id.invoice_date or fields.Date.today(), reverse=True) + + for line in lines: + tmpl = line.product_id.product_tmpl_id + if tmpl.id in seen_ids: + continue + days = line.product_id.product_tmpl_id.sale_delay + display = f"{tmpl.display_name} (Order Lead time {days} days)" + results.append((tmpl.id, display)) + seen_ids.add(tmpl.id) + if len(results) >= limit: + break + + remaining = limit - len(results) + if remaining > 0: + domain = args[:] + if name: + domain.append(("name", operator, name)) + if seen_ids: + domain.append(("id", "not in", list(seen_ids))) + others = super().name_search(name, args=domain, operator=operator, limit=remaining) + results.extend(others) + + return results diff --git a/products_orderby_invoice/views/product_views.xml b/products_orderby_invoice/views/product_views.xml new file mode 100644 index 00000000000..89d858b17da --- /dev/null +++ b/products_orderby_invoice/views/product_views.xml @@ -0,0 +1,29 @@ + + + + product.view.kanban.catalog.inherit.surplus.qty + product.product + + + + ( + + + + + + + + + + + 0.00 + + ) +
+ Last invoice date : +
+
+
+
+