diff --git a/custom_sale_purchase_display/__init__.py b/custom_sale_purchase_display/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/custom_sale_purchase_display/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/custom_sale_purchase_display/__manifest__.py b/custom_sale_purchase_display/__manifest__.py new file mode 100644 index 0000000000..670fa6178c --- /dev/null +++ b/custom_sale_purchase_display/__manifest__.py @@ -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' +} diff --git a/custom_sale_purchase_display/models/__init__.py b/custom_sale_purchase_display/models/__init__.py new file mode 100644 index 0000000000..18b37e8532 --- /dev/null +++ b/custom_sale_purchase_display/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_product +from . import product_template diff --git a/custom_sale_purchase_display/models/product_product.py b/custom_sale_purchase_display/models/product_product.py new file mode 100644 index 0000000000..fbde660dcc --- /dev/null +++ b/custom_sale_purchase_display/models/product_product.py @@ -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" diff --git a/custom_sale_purchase_display/models/product_template.py b/custom_sale_purchase_display/models/product_template.py new file mode 100644 index 0000000000..1e76394b4a --- /dev/null +++ b/custom_sale_purchase_display/models/product_template.py @@ -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 diff --git a/custom_sale_purchase_display/views/product_template_kanban_catalog.xml b/custom_sale_purchase_display/views/product_template_kanban_catalog.xml new file mode 100644 index 0000000000..7224063bbd --- /dev/null +++ b/custom_sale_purchase_display/views/product_template_kanban_catalog.xml @@ -0,0 +1,32 @@ + + + + product.product.kanban.inherit.catalog + product.product + + + + + + + + + +
(+ )
+
+ +
()
+
+ +
(0)
+
+
+ ⏱ +
+
+ 📅 +
+
+
+
+
diff --git a/custom_sale_purchase_display/views/purchase_order_form.xml b/custom_sale_purchase_display/views/purchase_order_form.xml new file mode 100644 index 0000000000..8ea02c5693 --- /dev/null +++ b/custom_sale_purchase_display/views/purchase_order_form.xml @@ -0,0 +1,13 @@ + + + + purchase.order.form.inherit.custom + purchase.order + + + + {'purchase_order_partner_id': parent.partner_id} + + + + diff --git a/custom_sale_purchase_display/views/sale_order_form_view.xml b/custom_sale_purchase_display/views/sale_order_form_view.xml new file mode 100644 index 0000000000..ca78d8637e --- /dev/null +++ b/custom_sale_purchase_display/views/sale_order_form_view.xml @@ -0,0 +1,16 @@ + + + + sale.order.form.inherit.custom + sale.order + + + + {'sale_order_partner_id': parent.partner_id} + + + {'sale_order_partner_id': parent.partner_id} + + + +