From 3751379f1e9a4c215fb6eb898b4ccc67659b9ace Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Tue, 10 May 2022 21:51:50 +0700 Subject: initial commit 2 --- addons/purchase_stock/report/__init__.py | 7 ++ addons/purchase_stock/report/purchase_report.py | 70 +++++++++++++++++ .../report/purchase_report_templates.xml | 46 ++++++++++++ .../report/purchase_report_views.xml | 15 ++++ .../report/report_stock_forecasted.py | 32 ++++++++ .../report/report_stock_forecasted.xml | 12 +++ addons/purchase_stock/report/report_stock_rule.py | 17 +++++ addons/purchase_stock/report/report_stock_rule.xml | 32 ++++++++ .../purchase_stock/report/vendor_delay_report.py | 87 ++++++++++++++++++++++ .../purchase_stock/report/vendor_delay_report.xml | 35 +++++++++ 10 files changed, 353 insertions(+) create mode 100644 addons/purchase_stock/report/__init__.py create mode 100644 addons/purchase_stock/report/purchase_report.py create mode 100644 addons/purchase_stock/report/purchase_report_templates.xml create mode 100644 addons/purchase_stock/report/purchase_report_views.xml create mode 100644 addons/purchase_stock/report/report_stock_forecasted.py create mode 100644 addons/purchase_stock/report/report_stock_forecasted.xml create mode 100644 addons/purchase_stock/report/report_stock_rule.py create mode 100644 addons/purchase_stock/report/report_stock_rule.xml create mode 100644 addons/purchase_stock/report/vendor_delay_report.py create mode 100644 addons/purchase_stock/report/vendor_delay_report.xml (limited to 'addons/purchase_stock/report') diff --git a/addons/purchase_stock/report/__init__.py b/addons/purchase_stock/report/__init__.py new file mode 100644 index 00000000..68360661 --- /dev/null +++ b/addons/purchase_stock/report/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import purchase_report +from . import report_stock_forecasted +from . import report_stock_rule +from . import vendor_delay_report diff --git a/addons/purchase_stock/report/purchase_report.py b/addons/purchase_stock/report/purchase_report.py new file mode 100644 index 00000000..67e63816 --- /dev/null +++ b/addons/purchase_stock/report/purchase_report.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import re + +from odoo import api, fields, models +from odoo.exceptions import UserError +from odoo.osv.expression import expression + + +class PurchaseReport(models.Model): + _inherit = "purchase.report" + + picking_type_id = fields.Many2one('stock.warehouse', 'Warehouse', readonly=True) + avg_receipt_delay = fields.Float( + 'Average Receipt Delay', digits=(16, 2), readonly=True, store=False, # needs store=False to prevent showing up as a 'measure' option + help="Amount of time between expected and effective receipt date. Due to a hack needed to calculate this, \ + every record will show the same average value, therefore only use this as an aggregated value with group_operator=avg") + effective_date = fields.Datetime(string="Effective Date") + + def _select(self): + return super(PurchaseReport, self)._select() + ", spt.warehouse_id as picking_type_id, po.effective_date as effective_date" + + def _from(self): + return super(PurchaseReport, self)._from() + " left join stock_picking_type spt on (spt.id=po.picking_type_id)" + + def _group_by(self): + return super(PurchaseReport, self)._group_by() + ", spt.warehouse_id, effective_date" + + @api.model + def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): + """ This is a hack to allow us to correctly calculate the average of PO specific date values since + the normal report query result will duplicate PO values across its PO lines during joins and + lead to incorrect aggregation values. + + Only the AVG operator is supported for avg_receipt_delay. + """ + avg_receipt_delay = next((field for field in fields if re.search(r'\bavg_receipt_delay\b', field)), False) + + if avg_receipt_delay: + fields.remove(avg_receipt_delay) + if any(field.split(':')[1].split('(')[0] != 'avg' for field in [avg_receipt_delay] if field): + raise UserError("Value: 'avg_receipt_delay' should only be used to show an average. If you are seeing this message then it is being accessed incorrectly.") + + res = [] + if fields: + res = super(PurchaseReport, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy) + + if not res and avg_receipt_delay: + res = [{}] + + if avg_receipt_delay: + query = """ SELECT AVG(receipt_delay.po_receipt_delay)::decimal(16,2) AS avg_receipt_delay + FROM ( + SELECT extract(epoch from age(po.effective_date, po.date_planned))/(24*60*60) AS po_receipt_delay + FROM purchase_order po + WHERE po.id IN ( + SELECT "purchase_report"."order_id" FROM %s WHERE %s) + ) AS receipt_delay + """ + + subdomain = domain + [('company_id', '=', self.env.company.id), ('effective_date', '!=', False)] + subtables, subwhere, subparams = expression(subdomain, self).query.get_sql() + + self.env.cr.execute(query % (subtables, subwhere), subparams) + res[0].update({ + '__count': 1, + avg_receipt_delay.split(':')[0]: self.env.cr.fetchall()[0][0], + }) + return res diff --git a/addons/purchase_stock/report/purchase_report_templates.xml b/addons/purchase_stock/report/purchase_report_templates.xml new file mode 100644 index 00000000..89acd21a --- /dev/null +++ b/addons/purchase_stock/report/purchase_report_templates.xml @@ -0,0 +1,46 @@ + + + + + + + + diff --git a/addons/purchase_stock/report/purchase_report_views.xml b/addons/purchase_stock/report/purchase_report_views.xml new file mode 100644 index 00000000..ad272a7b --- /dev/null +++ b/addons/purchase_stock/report/purchase_report_views.xml @@ -0,0 +1,15 @@ + + + + + purchase.report.search.stock + purchase.report + + + + + + + + + diff --git a/addons/purchase_stock/report/report_stock_forecasted.py b/addons/purchase_stock/report/report_stock_forecasted.py new file mode 100644 index 00000000..6ae5459a --- /dev/null +++ b/addons/purchase_stock/report/report_stock_forecasted.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class ReplenishmentReport(models.AbstractModel): + _inherit = 'report.stock.report_product_product_replenishment' + + def _compute_draft_quantity_count(self, product_template_ids, product_variant_ids, wh_location_ids): + res = super()._compute_draft_quantity_count(product_template_ids, product_variant_ids, wh_location_ids) + domain = [('state', 'in', ['draft', 'sent'])] + domain += self._product_purchase_domain(product_template_ids, product_variant_ids) + warehouse_id = self.env.context.get('warehouse', False) + if warehouse_id: + domain += [('order_id.picking_type_id.warehouse_id', '=', warehouse_id)] + po_lines = self.env['purchase.order.line'].read_group(domain, ['product_uom_qty'], 'product_id') + in_sum = sum(line['product_uom_qty'] for line in po_lines) + + res['draft_purchase_qty'] = in_sum + res['qty']['in'] += in_sum + return res + + def _product_purchase_domain(self, product_template_ids, product_variant_ids): + if product_variant_ids: + return [('product_id', 'in', product_variant_ids)] + elif product_template_ids: + products = self.env['product.product'].search_read( + [('product_tmpl_id', 'in', product_template_ids)], ['id'] + ) + product_ids = [product['id'] for product in products] + return [('product_id', 'in', product_ids)] diff --git a/addons/purchase_stock/report/report_stock_forecasted.xml b/addons/purchase_stock/report/report_stock_forecasted.xml new file mode 100644 index 00000000..65538563 --- /dev/null +++ b/addons/purchase_stock/report/report_stock_forecasted.xml @@ -0,0 +1,12 @@ + + + + diff --git a/addons/purchase_stock/report/report_stock_rule.py b/addons/purchase_stock/report/report_stock_rule.py new file mode 100644 index 00000000..cc92df7c --- /dev/null +++ b/addons/purchase_stock/report/report_stock_rule.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, models + + +class ReportStockRule(models.AbstractModel): + _inherit = 'report.stock.report_stock_rule' + + @api.model + def _get_rule_loc(self, rule, product_id): + """ We override this method to handle buy rules which do not have a location_src_id. + """ + res = super(ReportStockRule, self)._get_rule_loc(rule, product_id) + if rule.action == 'buy': + res['source'] = self.env.ref('stock.stock_location_suppliers') + return res diff --git a/addons/purchase_stock/report/report_stock_rule.xml b/addons/purchase_stock/report/report_stock_rule.xml new file mode 100644 index 00000000..7d524099 --- /dev/null +++ b/addons/purchase_stock/report/report_stock_rule.xml @@ -0,0 +1,32 @@ + + + + \ No newline at end of file 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 00000000..ef55b2ca --- /dev/null +++ b/addons/purchase_stock/report/vendor_delay_report.py @@ -0,0 +1,87 @@ +# -*- 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::date >= m.date::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 all('on_time_rate' not in field for field in fields): + res = super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy) + return res + + for field in fields: + if 'on_time_rate' not in field: + continue + + fields.remove(field) + + agg = field.split(':')[1:] + if agg and agg[0] != 'sum': + raise NotImplementedError('Aggregate functions other than \':sum\' are not allowed.') + + qty_total = field.replace('on_time_rate', 'qty_total') + if qty_total not in fields: + fields.append(qty_total) + qty_on_time = field.replace('on_time_rate', 'qty_on_time') + if qty_on_time not in fields: + fields.append(qty_on_time) + break + + 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 00000000..cd4601cc --- /dev/null +++ b/addons/purchase_stock/report/vendor_delay_report.xml @@ -0,0 +1,35 @@ + + + + vendor.delay.report.search + vendor.delay.report + + + + + + + + + + + vendor.delay.report.view.graph + vendor.delay.report + + + + + + + + + + On-time Delivery + vendor.delay.report + graph + + Vendor On-time Delivery analysis + current + {'search_default_later_than_a_year_ago':1} + + -- cgit v1.2.3