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 last_purchased_products/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
29 changes: 29 additions & 0 deletions last_purchased_products/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "Last Purchased Products",
"description": """
add last order time next to product display name,
order product list as recently to old invoice creation time,
add same functionality to customer invoice,
add Unit of Measure next to price in catalog kanban view,
add recent invoice time after price in catalog kanban view,
add customer name next to invoice time.
""",
"version": "1.0",
"depends": ["sale_management", "purchase", "stock"],
"author": "danal",
"category": "Category",
"license": "LGPL-3",
"data": [
"views/sale_order_views.xml",
"views/account_move_views.xml",
"views/purchase_order_views.xml",
"views/product_views.xml",
],
"assets": {
"web.assets_backend": [
"last_purchased_products/static/src/product_catalog/**/*.xml",
]
},
"installable": True,
"application": False,
}
4 changes: 4 additions & 0 deletions last_purchased_products/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import product_template
from . import product
150 changes: 150 additions & 0 deletions last_purchased_products/models/product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import datetime
from dateutil.relativedelta import relativedelta

from odoo import api, fields, models


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

last_order = fields.Datetime(compute="_compute_last_order")
last_invoice_date = fields.Datetime(compute="_compute_last_invoice_date")
last_invoice_time = fields.Char(compute="_compute_invoice_time")
invoice_partner_name = fields.Char(compute="_compute_last_invoice_date")

@api.depends_context("customer", "formatted_display_name")
def _compute_display_name(self):
res = super()._compute_display_name()
if not self.env.context.get("customer") or not self.env.context.get(
"formatted_display_name"
):
return res
compute_agotime_ref = self.compute_agotime
for product in self:
if not product.last_order:
continue
ago = compute_agotime_ref(product.last_order)
current_product_name = product.display_name or ""
if self.env.context.get("formatted_display_name"):
if ago:
time_postfix = f"\t--{ago}--"
else:
time_postfix = ""
product.display_name = f"{current_product_name}{time_postfix}"
else:
product.display_name = f"{current_product_name}"

@api.depends_context("customer", "vendor")
def _compute_last_invoice_date(self):
customer_id = self.env.context.get("customer")
vendor_id = self.env.context.get("vendor")
domain = [
("product_id", "in", self.ids),
("parent_state", "=", "posted"),
]
if customer_id:
domain.append(("move_id.move_type", "=", "out_invoice"))
domain.append(("partner_id", "=", customer_id))
elif vendor_id:
domain.append(("move_id.move_type", "=", "in_invoice"))
domain.append(("partner_id", "=", vendor_id))
else:
domain.append(("move_id.move_type", "=", "out_invoice"))

last_invoice_dates = self.env["account.move.line"].search(
domain, order="create_date desc"
)
invoice_dates = {}
invoice_partners = {}
for invoice in last_invoice_dates:
if invoice.product_id.id not in invoice_dates:
invoice_dates[invoice.product_id.id] = invoice.create_date
if invoice.product_id.id not in invoice_partners:
invoice_partners[invoice.product_id.id] = invoice.partner_id.name
for product in self:
product.last_invoice_date = invoice_dates.get(product.id, False)
product.invoice_partner_name = invoice_partners.get(product.id, False)

@api.depends_context("customer")
def _compute_last_order(self):
last_orders = self.env["sale.order.line"].search(
[
("order_id.partner_id", "=", self.env.context.get("customer")),
("state", "=", "sale"),
],
order="order_id",
)
order_dates = {}
for order in last_orders:
p_id = order.product_id.id
if p_id not in order_dates:
order_dates[p_id] = order.order_id.date_order
for product in self:
product.last_order = order_dates.get(product.id, False)

@api.depends("last_invoice_date")
@api.depends_context("isPurchase")
def _compute_invoice_time(self):
if self.env.context.get("isPurchase"):
self.write({"last_invoice_time": False})
return
compute_agotime_ref = self.compute_agotime
for product in self:
if product.last_invoice_date:
ago = compute_agotime_ref(product.last_invoice_date)
if not ago or ("s" in ago):
ago = "Just Now"
product.last_invoice_time = ago
else:
product.last_invoice_time = False

def action_open_last_invoice(self):
self.ensure_one()
domain = [
("move_id.move_type", "=", "out_invoice"),
("product_id", "=", self.id),
("parent_state", "=", "posted"),
]
last_line = self.env["account.move.line"].search(
domain, order="create_date desc", limit=1
)
return {
"type": "ir.actions.act_window",
"res_model": "account.move",
"res_id": last_line.move_id.id,
"view_mode": "form",
"target": "current",
}

@api.model
def name_search(self, name="", args=None, operator="ilike", limit=100):
if not self.env.context.get("customer") and not self.env.context.get("vendor"):
return super().name_search(name, args, operator, limit)
res = super().name_search(name, args, operator, limit=100)
ids = [r[0] for r in res]
records = self.browse(ids)
records.mapped("last_invoice_date")
sorted_records = records.sorted(
key=(lambda r: r.last_invoice_date or datetime.datetime.min), reverse=True
)
return [(r.id, r.display_name) for r in sorted_records][:limit]

def compute_agotime(self, datetime_field):
now = fields.Datetime.now()
rd = relativedelta(now, datetime_field)
if rd.years:
ago = f"{rd.years}y"
elif rd.months:
ago = f"{rd.months}mo"
elif rd.days:
ago = f"{rd.days}d"
elif rd.hours:
ago = f"{rd.hours}h"
elif rd.minutes:
ago = f"{rd.minutes}m"
elif rd.seconds:
ago = f"{rd.seconds}s"
else:
ago = ""

return ago
27 changes: 27 additions & 0 deletions last_purchased_products/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from odoo import api, fields, models


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

product_variant_id = fields.Many2one(
"product.product", "Product", compute="_compute_product_variant_id", store=True
)
display_name = fields.Char(related="product_variant_id.display_name")

@api.depends("product_variant_ids")
def _compute_product_variant_id(self):
for p in self:
p.product_variant_id = p.product_variant_ids[:1].id

@api.model
def name_search(self, name="", args=None, operator="ilike", limit=100):
variant_results = self.env["product.product"].name_search(
name, args, operator, limit=limit
)
if not variant_results:
return []
variant_ids = [res[0] for res in variant_results]
variants = self.env["product.product"].browse(variant_ids)
templates = variants.mapped("product_tmpl_id")
return [(t.id, t.display_name) for t in templates][:limit]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-inherit="product.ProductCatalogOrderLine" t-inherit-mode="extension">
<xpath expr="//span[@t-out=&quot;price&quot;]" position="attributes">
<attribute name="class">o_product_catalog_price fw-bold</attribute>
</xpath>
<xpath expr="//span[@t-out=&quot;price&quot;]" position="after">
<t t-if="this.env.displayUoM">
/ <span class="text-muted text-truncate w-50 me-4" t-out="props.uomDisplayName"/>
</t>
<t t-else="">
<span class="me-4"/>
</t>
</xpath>
</t>
</templates>
1 change: 1 addition & 0 deletions last_purchased_products/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_last_order_product
Loading