summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/purchase_stock/__manifest__.py2
-rw-r--r--addons/purchase_stock/models/__init__.py1
-rw-r--r--addons/purchase_stock/models/purchase.py1
-rw-r--r--addons/purchase_stock/models/res_partner.py36
-rw-r--r--addons/purchase_stock/report/__init__.py1
-rw-r--r--addons/purchase_stock/report/vendor_delay_report.py73
-rw-r--r--addons/purchase_stock/report/vendor_delay_report.xml37
-rw-r--r--addons/purchase_stock/security/ir.model.access.csv2
-rw-r--r--addons/purchase_stock/views/purchase_views.xml13
-rw-r--r--addons/purchase_stock/views/res_partner_views.xml27
10 files changed, 193 insertions, 0 deletions
diff --git a/addons/purchase_stock/__manifest__.py b/addons/purchase_stock/__manifest__.py
index 471374708a5..ae43f2adab8 100644
--- a/addons/purchase_stock/__manifest__.py
+++ b/addons/purchase_stock/__manifest__.py
@@ -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',
diff --git a/addons/purchase_stock/models/__init__.py b/addons/purchase_stock/models/__init__.py
index d822fea458a..85cc204cb90 100644
--- a/addons/purchase_stock/models/__init__.py
+++ b/addons/purchase_stock/models/__init__.py
@@ -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
diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py
index bfff1567238..5ff7c7f67fd 100644
--- a/addons/purchase_stock/models/purchase.py
+++ b/addons/purchase_stock/models/purchase.py
@@ -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',
diff --git a/addons/purchase_stock/models/res_partner.py b/addons/purchase_stock/models/res_partner.py
new file mode 100644
index 00000000000..10289cde11c
--- /dev/null
+++ b/addons/purchase_stock/models/res_partner.py
@@ -0,0 +1,36 @@
+# -*- 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
+
diff --git a/addons/purchase_stock/report/__init__.py b/addons/purchase_stock/report/__init__.py
index 129d295b8be..dfdf3818fdb 100644
--- a/addons/purchase_stock/report/__init__.py
+++ b/addons/purchase_stock/report/__init__.py
@@ -3,3 +3,4 @@
from . import purchase_report
from . import report_stock_rule
+from . import vendor_delay_report
diff --git a/addons/purchase_stock/report/vendor_delay_report.py b/addons/purchase_stock/report/vendor_delay_report.py
new file mode 100644
index 00000000000..25cfaf6c25a
--- /dev/null
+++ b/addons/purchase_stock/report/vendor_delay_report.py
@@ -0,0 +1,73 @@
+# -*- 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
diff --git a/addons/purchase_stock/report/vendor_delay_report.xml b/addons/purchase_stock/report/vendor_delay_report.xml
new file mode 100644
index 00000000000..7391d5eade7
--- /dev/null
+++ b/addons/purchase_stock/report/vendor_delay_report.xml
@@ -0,0 +1,37 @@
+<?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>
diff --git a/addons/purchase_stock/security/ir.model.access.csv b/addons/purchase_stock/security/ir.model.access.csv
index 12c826e863b..faf2bb933ac 100644
--- a/addons/purchase_stock/security/ir.model.access.csv
+++ b/addons/purchase_stock/security/ir.model.access.csv
@@ -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
diff --git a/addons/purchase_stock/views/purchase_views.xml b/addons/purchase_stock/views/purchase_views.xml
index 9c239bc4ce7..981802114c5 100644
--- a/addons/purchase_stock/views/purchase_views.xml
+++ b/addons/purchase_stock/views/purchase_views.xml
@@ -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>
diff --git a/addons/purchase_stock/views/res_partner_views.xml b/addons/purchase_stock/views/res_partner_views.xml
new file mode 100644
index 00000000000..88bad643ea3
--- /dev/null
+++ b/addons/purchase_stock/views/res_partner_views.xml
@@ -0,0 +1,27 @@
+<?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>