Skip to content
Snippets Groups Projects
Commit d83334ef authored by yhu-odoo's avatar yhu-odoo
Browse files

[IMP] purchase_stock: add vendor on-time delivery rate

Add on-time delivery rate to vendor. When editing a PO, user can see
the overall on-time delivery rate during the last 365 days next to the
vendor. When click it, user will be redirect to a graph view showing
the detail of the on-time delivery rate of this vendor. Also add a stat
button to show the on-time delivery rate on partner form.

Task: 2230811
PR: #49921
parent eb6ddb6e
Branches
Tags
No related merge requests found
......@@ -14,10 +14,12 @@
'data/purchase_stock_data.xml',
'data/mail_data.xml',
'views/assets.xml',
'report/vendor_delay_report.xml',
'views/purchase_views.xml',
'views/stock_views.xml',
'views/stock_rule_views.xml',
'views/res_config_settings_views.xml',
'views/res_partner_views.xml',
'views/stock_production_lot_views.xml',
'report/purchase_report_views.xml',
'report/purchase_report_templates.xml',
......
......@@ -6,5 +6,6 @@ from . import product
from . import purchase
from . import res_company
from . import res_config_settings
from . import res_partner
from . import stock
from . import stock_rule
......@@ -29,6 +29,7 @@ class PurchaseOrder(models.Model):
is_shipped = fields.Boolean(compute="_compute_is_shipped")
effective_date = fields.Datetime("Effective Date", compute='_compute_effective_date', store=True, copy=False,
help="Completion date of the first receipt order.")
on_time_rate = fields.Float(related='partner_id.on_time_rate')
@api.depends('order_line.move_ids.returned_move_ids',
'order_line.move_ids.state',
......
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from odoo import api, fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
purchase_line_ids = fields.One2many('purchase.order.line', 'partner_id', string="Purchase Lines")
on_time_rate = fields.Float(
"On-Time Delivery Rate", compute='_compute_on_time_rate',
help="Over the past 12 months, number of products received on time / total number of ordered products.")
@api.depends('purchase_line_ids')
def _compute_on_time_rate(self):
order_lines = self.env['purchase.order.line'].search([
('partner_id', 'in', self.ids),
('date_order', '>', fields.Date.today() - timedelta(365)),
('qty_received', '!=', 0),
]).filtered(lambda l: l.product_id.product_tmpl_id.type != 'service' and l.order_id.state in ['done', 'purchase'])
partner_dict = {}
for line in order_lines:
on_time, ordered = partner_dict.get(line.partner_id, (0, 0))
ordered += line.product_uom_qty
on_time += sum(line.mapped('move_ids').filtered(lambda m: m.state == 'done' and m.date <= m.purchase_line_id.date_planned).mapped('quantity_done'))
partner_dict[line.partner_id] = (on_time, ordered)
seen_partner = self.env['res.partner']
for partner, numbers in partner_dict.items():
seen_partner |= partner
on_time, ordered = numbers
partner.on_time_rate = on_time / ordered * 100 if ordered else 100
(self - seen_partner).on_time_rate = 100
......@@ -3,3 +3,4 @@
from . import purchase_report
from . import report_stock_rule
from . import vendor_delay_report
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools
from odoo.exceptions import UserError
from odoo.osv.expression import expression
class VendorDelayReport(models.Model):
_name = "vendor.delay.report"
_description = "Vendor Delay Report"
_auto = False
partner_id = fields.Many2one('res.partner', 'Vendor', readonly=True)
product_id = fields.Many2one('product.product', 'Product', readonly=True)
category_id = fields.Many2one('product.category', 'Product Category', readonly=True)
date = fields.Datetime('Effective Date', readonly=True)
qty_total = fields.Float('Total Quantity', readonly=True)
qty_on_time = fields.Float('On-Time Quantity', readonly=True)
on_time_rate = fields.Float('On-Time Delivery Rate', readonly=True)
def init(self):
tools.drop_view_if_exists(self.env.cr, 'vendor_delay_report')
self.env.cr.execute("""
CREATE OR replace VIEW vendor_delay_report AS(
SELECT m.id AS id,
m.date AS date,
m.purchase_line_id AS purchase_line_id,
m.product_id AS product_id,
Min(pc.id) AS category_id,
Min(po.partner_id) AS partner_id,
Sum(pol.product_uom_qty) AS qty_total,
Sum(CASE
WHEN pol.date_planned >= m.date THEN ml.qty_done
ELSE 0
END) AS qty_on_time
FROM stock_move m
JOIN stock_move_line ml
ON m.id = ml.move_id
JOIN purchase_order_line pol
ON pol.id = m.purchase_line_id
JOIN purchase_order po
ON po.id = pol.order_id
JOIN product_product p
ON p.id = m.product_id
JOIN product_template pt
ON pt.id = p.product_tmpl_id
JOIN product_category pc
ON pc.id = pt.categ_id
WHERE m.state = 'done'
GROUP BY m.id
)""")
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
if 'on_time_rate' not in fields:
res = super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
return res
fields.remove('on_time_rate')
if 'qty_total' not in fields:
fields.append('qty_total')
if 'qty_on_time' not in fields:
fields.append('qty_on_time')
res = super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
for group in res:
if group['qty_total'] == 0:
on_time_rate = 100
else:
on_time_rate = group['qty_on_time'] / group['qty_total'] * 100
group.update({'on_time_rate': on_time_rate})
return res
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="vendor_delay_report_filter" model="ir.ui.view">
<field name="name">vendor.delay.report.search</field>
<field name="model">vendor.delay.report</field>
<field name="arch" type="xml">
<search string="On-time Delivery">
<field name="partner_id"/>
<field name="product_id"/>
<filter string="Effective Date Last Year" name="later_than_a_year_ago" domain="[('date', '&gt;=', ((context_today()-relativedelta(years=1)).strftime('%Y-%m-%d')))]"/>
</search>
</field>
</record>
<record id="vendor_delay_report_view_graph" model="ir.ui.view">
<field name="name">vendor.delay.report.view.graph</field>
<field name="model">vendor.delay.report</field>
<field name="arch" type="xml">
<graph string="On-Time Delivery" type="bar">
<field name="product_id" type="row"/>
<field name="on_time_rate" type="measure"/>
</graph>
</field>
</record>
<record id="action_purchase_vendor_delay_report" model="ir.actions.act_window">
<field name="name">On-time Delivery</field>
<field name="res_model">vendor.delay.report</field>
<field name="view_mode">graph</field>
<field name="search_view_id" ref="vendor_delay_report_filter"/>
<field name="help">Vendor On-time Delivery analysis</field>
<field name="target">current</field>
<field name="context">{'search_default_later_than_a_year_ago':1}</field>
</record>
<menuitem id="delay_report" name="Vendor Delay" parent="purchase.menu_purchase_root" sequence="99" groups="purchase.group_purchase_manager" action="action_purchase_vendor_delay_report"/>
</odoo>
......@@ -11,3 +11,5 @@ access_stock_picking_purchase_user_manager,stock.picking,stock.model_stock_picki
access_stock_move_purchase_user_manager,stock.move,stock.model_stock_move,purchase.group_purchase_manager,1,1,1,1
access_stock_warehouse_orderpoint_manager,stock.warehouse.orderpoint,stock.model_stock_warehouse_orderpoint,purchase.group_purchase_manager,1,0,0,0
access_stock_warehouse_orderpoint_user,stock.warehouse.orderpoint,stock.model_stock_warehouse_orderpoint,purchase.group_purchase_user,1,0,0,0
access_report_purchase_order,vendor.delay.report,model_vendor_delay_report,purchase.group_purchase_manager,1,0,0,0
access_report_purchase_order_user,vendor.delay.report user,model_vendor_delay_report,purchase.group_purchase_user,1,0,0,0
......@@ -18,6 +18,19 @@
<field name="picking_ids" invisible="1"/>
</button>
</xpath>
<xpath expr="//field[@name='partner_id']" position="replace">
<label for="partner_id"/>
<div class="o_row">
<field name="partner_id" widget="res_partner_many2one" context="{'res_partner_search_mode': 'supplier', 'show_vat': True}"
placeholder="Name, TIN, Email, or Reference"
/>
<div class="oe_edit_only">
<button name="%(action_purchase_vendor_delay_report)d" type="action" class="oe_stat_button" context="{'search_default_partner_id': partner_id}">
<field name="on_time_rate" class="text-muted" widget='percentpie'/>
</button>
</div>
</div>
</xpath>
<xpath expr="//field[@name='currency_id']" position="after">
<field name="is_shipped" invisible="1"/>
</xpath>
......
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_partner_view_purchase_buttons_inherit" model="ir.ui.view">
<field name="name">res.partner.purchase.stock.form.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="purchase.res_partner_view_purchase_buttons"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='purchase_order_count']/.." position="after">
<button class="oe_stat_button" name="%(action_purchase_vendor_delay_report)d" type="action"
groups="purchase.group_purchase_user"
icon="fa-truck"
context="{'search_default_partner_id': id}">
<div class="o_form_field o_stat_info">
<div class="o_row">
<span class="o_stat_value">
<field string="On-time Rate" name="on_time_rate" widget="integer"/>
</span>
<span class="o_stat_value">%</span>
</div>
<span class="o_stat_text">On-time Rate</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment