Skip to content

[IMP] last_ordered_products: improved sale and purchase by adding this module #654

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: 18.0
Choose a base branch
from
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_ordered_products/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
20 changes: 20 additions & 0 deletions last_ordered_products/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
'name': "Last Ordered Products",
'version': '1.0',
'depends': ['product'],
'author': "Parthav Chodvadiya (PPCH)",
'category': '',
'description': """
Show last ordered products for customers in sale order and for vendors in purchase order
""",
'data': [
'views/product_views.xml',
],
'assets': {
'web.assets_backend': [
'last_ordered_products/static/src/**/*.js',
'last_ordered_products/static/src/**/*.xml',
],
},
'license': 'LGPL-3',
}
2 changes: 2 additions & 0 deletions last_ordered_products/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import product_product
from . import product_template
96 changes: 96 additions & 0 deletions last_ordered_products/models/product_product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from odoo import api, fields, models


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

last_order_time = fields.Datetime(compute='_compute_last_order_time')
last_date_str = fields.Char(compute='_compute_last_order_time')

@api.depends_context('order_id')
def _compute_last_order_time(self):
'''Compute the last order time for each product based on the latest sale or purchase.'''

order_type = False
if self.env.context.get('active_model') == 'sale.order.line':
partner_id = self.env['sale.order'].browse(self.env.context.get('order_id')).partner_id.id
order_type = 'sale'
elif self.env.context.get('active_model') == 'purchase.order.line':
partner_id = self.env['purchase.order'].browse(self.env.context.get('order_id')).partner_id.id
order_type = 'purchase'
elif self.env.context.get('active_model') == 'account.journal':
active_id = self.env.context.get('active_id')
if active_id:
order_type = self.env['account.journal'].browse(active_id).type
partner_id = self.env.context.get('default_partner_id')

if not partner_id:
for record in self:
record.last_order_time = False
record.last_date_str = False
return

last_ordered_products = {}

if order_type == 'sale':
last_ordered_products = self._get_last_sold_products(partner_id)
elif order_type == 'purchase':
last_ordered_products = self._get_last_purchased_products(partner_id)

for record in self:
last_date = last_ordered_products.get(record.id)

record.last_order_time = last_date if last_date else False
record.last_date_str = self.env['product.template']._get_time_ago_string(last_date) if last_date else False

def _get_last_sold_products(self, partner_id):
'''Fetch products last sold to the given customer'''

sale_order_lines = self.env['sale.order.line'].search([
('order_id.partner_id', '=', partner_id)
])

if not sale_order_lines:
return {}

invoices = self.env['account.move'].search([
('partner_id', '=', partner_id),
('invoice_origin', 'in', sale_order_lines.order_id.mapped('name'))
])

last_sale_ordered_products = {}
invoice_dates = {inv.invoice_origin: inv.create_date for inv in invoices}
for sol in sale_order_lines:
last_date = invoice_dates.get(sol.order_id.name)
if last_date:
product_id = sol.product_id.id
if product_id not in last_sale_ordered_products or last_date > last_sale_ordered_products[product_id]:
last_sale_ordered_products[product_id] = last_date

return last_sale_ordered_products

def _get_last_purchased_products(self, partner_id):
'''Fetch products last purchased to the given vendor'''

purchase_order_line = self.env['purchase.order.line'].search([
('order_id.partner_id', '=', partner_id)
])

if not purchase_order_line:
return {}

invoices = self.env['account.move'].search([
('partner_id', '=', partner_id),
('invoice_origin', 'in', purchase_order_line.order_id.mapped('name'))
])

last_purchased_order_products = {}
invoice_dates = {inv.invoice_origin: inv.create_date for inv in invoices}
for sol in purchase_order_line:
last_date = invoice_dates.get(sol.order_id.name)
if last_date:
product_id = sol.product_id.id
if product_id not in last_purchased_order_products or last_date > last_purchased_order_products[product_id]:
last_purchased_order_products[product_id] = last_date

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


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

last_order_time = fields.Datetime(compute='_compute_last_order_time')
last_date_str = fields.Char(compute='_compute_last_order_time')

@api.depends_context('order_id')
def _compute_last_order_time(self):
'''Compute the last order time for each product based on the latest sale or purchase.'''

partner_id = self.env.context.get('partner_id')
order_type = self.env.context.get('order_type')

if not partner_id:
for record in self:
record.last_order_time = False
record.last_date_str = False
return

last_ordered_products = {}

if order_type == 'sale':
last_ordered_products = self._get_last_sold_products(partner_id)

for record in self:
last_date = last_ordered_products.get(record.id)

record.last_order_time = last_date if last_date else False
record.last_date_str = self._get_time_ago_string(last_date) if last_date else False

def _get_last_sold_products(self, partner_id):
'''Fetch products last sold to the given customer'''

sale_order_lines = self.env['sale.order.line'].search([
('order_id.partner_id', '=', partner_id)
])

if not sale_order_lines:
return {}

invoices = self.env['account.move'].search([
('partner_id', '=', partner_id),
('invoice_origin', 'in', sale_order_lines.order_id.mapped('name'))
])

last_sale_ordered_products = {}
invoice_dates = {inv.invoice_origin: inv.create_date for inv in invoices}
for sol in sale_order_lines:
last_date = invoice_dates.get(sol.order_id.name)
if last_date:
product_id = sol.product_id.product_tmpl_id.id
if product_id not in last_sale_ordered_products or last_date > last_sale_ordered_products[product_id]:
last_sale_ordered_products[product_id] = last_date

return last_sale_ordered_products

def _get_time_ago_string(self, last_date):
'''Convert datetime to human-readable time difference (e.g., "1d", "4h", "4mo")'''

if not last_date:
return ""

now = fields.Datetime.now()
diff = now - last_date

if diff.days > 365:
return f"{diff.days // 365}y"
elif diff.days > 30:
return f"{diff.days // 30}mo"
elif diff.days > 0:
return f"{diff.days}d"
elif diff.seconds >= 3600:
return f"{diff.seconds // 3600}h"
elif diff.seconds >= 60:
return f"{diff.seconds // 60}m"
else:
return f"{diff.seconds}s"
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="web.AutoComplete" t-inherit-mode="extension">
<xpath expr="//t[@t-esc='option.label']" position="replace">
<t t-if="option.time_str">
<div class="d-flex justify-content-between text-success">
<span><t t-esc="option.label" /></span>
<span class="px-2"><t t-esc="option.time_str" /></span>
</div>
</t>
<t t-else="">
<span t-esc="option.label"/>
</t>
</xpath>
</t>
</templates>
10 changes: 10 additions & 0 deletions last_ordered_products/static/src/product_catalog/kanban_model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ProductCatalogKanbanModel } from "@product/product_catalog/kanban_model";
import { patch } from "@web/core/utils/patch";

patch(ProductCatalogKanbanModel.prototype, {
async _loadData(params){
const result = await super._loadData(...arguments);
result.records.sort((a, b) => new Date(b.last_order_time) - new Date(a.last_order_time));
return result;
}
});
12 changes: 12 additions & 0 deletions last_ordered_products/static/src/product_catalog/kanban_record.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ProductCatalogKanbanRecord } from "@product/product_catalog/kanban_record";
import { ProductCatalogLastOrderOrderLine } from "./order_line/order_line";
import { patch } from "@web/core/utils/patch";

patch(ProductCatalogKanbanRecord.prototype, {
get orderLineComponent() {
if (this.env.orderResModel === "sale.order") {
return ProductCatalogLastOrderOrderLine;
}
return super.orderLineComponent;
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ProductCatalogOrderLine } from "@product/product_catalog/order_line/order_line";

export class ProductCatalogLastOrderOrderLine extends ProductCatalogOrderLine {
static template = "ProductCatalogLastOrderOrderLine";
static props = {
...ProductCatalogLastOrderOrderLine.props,
sale_uom: { type: Object, optional: true },
uom: Object,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="ProductCatalogLastOrderOrderLine"
t-inherit="product.ProductCatalogOrderLine"
t-inherit-mode="primary">
<xpath expr="//span[hasclass('o_product_catalog_price')]" position="attributes">
<attribute name="t-if">!this.env.displayUoM</attribute>
</xpath>
<xpath expr="//span[hasclass('o_product_catalog_price')]" position="after">
<span class="o_product_catalog_price" t-if="env.displayUoM">
<t t-out="price"/>
/ <span t-att-class="{'fw-bold text-primary': props.sale_uom}">
<t t-if="props.sale_uom" t-esc="props.sale_uom.display_name"/>
<t t-else="" t-esc="props.uom.display_name"/>
</span>
</span>
</xpath>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ProductCatalogSaleOrderLine } from "@sale_stock/product_catalog/sale_order_line/sale_order_line";
import { patch } from "@web/core/utils/patch";

patch(ProductCatalogSaleOrderLine, {
template: "ProductCatalogLastOrderOrderLine",
props: {
...ProductCatalogSaleOrderLine.props,
deliveredQty: Number,
sale_uom: { type: Object, optional: true },
uom: Object,
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ProductLabelSectionAndNoteFieldAutocomplete } from "@account/components/product_label_section_and_note_field/product_label_section_and_note_field";
import { patch } from "@web/core/utils/patch";

patch(ProductLabelSectionAndNoteFieldAutocomplete.prototype, {
mapRecordToOption(result) {
let res = super.mapRecordToOption(result)
let time_str = result[2] ? result[2] : ""
if (time_str === ""){
return res;
}
res['time_str'] = time_str
return res
},
});
1 change: 1 addition & 0 deletions last_ordered_products/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_last_ordered_products
Loading