From 38018469a7c2604bfa813cfcc6cb7bb824dacf7e Mon Sep 17 00:00:00 2001 From: svs-odoo Date: Wed, 15 Jul 2020 08:18:25 +0000 Subject: [PATCH] [IMP] purchase_stock: extend replenishment report Adds Purchase Orders as replenishment/consuming document for the replenishment report. task-2058495 --- addons/purchase_stock/__manifest__.py | 1 + addons/purchase_stock/models/stock.py | 4 + addons/purchase_stock/report/__init__.py | 1 + .../report/report_stock_forecasted.py | 32 ++++ .../report/report_stock_forecasted.xml | 12 ++ addons/purchase_stock/tests/__init__.py | 1 + .../tests/test_purchase_stock_report.py | 147 ++++++++++++++++++ 7 files changed, 198 insertions(+) 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/tests/test_purchase_stock_report.py diff --git a/addons/purchase_stock/__manifest__.py b/addons/purchase_stock/__manifest__.py index 70745809351..cbffb7a15e9 100644 --- a/addons/purchase_stock/__manifest__.py +++ b/addons/purchase_stock/__manifest__.py @@ -24,6 +24,7 @@ 'views/product_category_views.xml', 'report/purchase_report_views.xml', 'report/purchase_report_templates.xml', + 'report/report_stock_forecasted.xml', 'report/report_stock_rule.xml', ], 'demo': [ diff --git a/addons/purchase_stock/models/stock.py b/addons/purchase_stock/models/stock.py index fa8cece163a..1b70a05954c 100644 --- a/addons/purchase_stock/models/stock.py +++ b/addons/purchase_stock/models/stock.py @@ -105,6 +105,10 @@ class StockMove(models.Model): rslt += self.mapped('picking_id.purchase_id.invoice_ids').filtered(lambda x: x.state == 'posted') return rslt + def _get_source_document(self): + res = super()._get_source_document() + return self.purchase_line_id.order_id or res + class StockWarehouse(models.Model): _inherit = 'stock.warehouse' diff --git a/addons/purchase_stock/report/__init__.py b/addons/purchase_stock/report/__init__.py index dfdf3818fdb..68360661a12 100644 --- a/addons/purchase_stock/report/__init__.py +++ b/addons/purchase_stock/report/__init__.py @@ -2,5 +2,6 @@ # 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/report_stock_forecasted.py b/addons/purchase_stock/report/report_stock_forecasted.py new file mode 100644 index 00000000000..6ae5459a81b --- /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 00000000000..65538563f8e --- /dev/null +++ b/addons/purchase_stock/report/report_stock_forecasted.xml @@ -0,0 +1,12 @@ + + + + diff --git a/addons/purchase_stock/tests/__init__.py b/addons/purchase_stock/tests/__init__.py index ca01168d5ad..aa9e156fd88 100644 --- a/addons/purchase_stock/tests/__init__.py +++ b/addons/purchase_stock/tests/__init__.py @@ -11,6 +11,7 @@ from . import test_purchase_delete_order from . import test_purchase_lead_time from . import test_purchase_order from . import test_purchase_order_process +from . import test_purchase_stock_report from . import test_stockvaluation from . import test_replenish_wizard from . import test_reordering_rule diff --git a/addons/purchase_stock/tests/test_purchase_stock_report.py b/addons/purchase_stock/tests/test_purchase_stock_report.py new file mode 100644 index 00000000000..824a11e7ffc --- /dev/null +++ b/addons/purchase_stock/tests/test_purchase_stock_report.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests.common import Form +from odoo.addons.stock.tests.test_report import TestReportsCommon + + +class TestPurchaseStockReports(TestReportsCommon): + def test_report_forecast_1_purchase_order_multi_receipt(self): + """ Create a PO for 5 product, receive them then increase the quantity to 10. + """ + po_form = Form(self.env['purchase.order']) + po_form.partner_id = self.partner + with po_form.order_line.new() as line: + line.product_id = self.product + line.product_qty = 5 + po = po_form.save() + + # Checks the report. + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty_in = docs['draft_picking_qty']['in'] + draft_purchase_qty = docs['draft_purchase_qty'] + pending_qty_in = docs['qty']['in'] + self.assertEqual(len(lines), 0, "Must have 0 line for now.") + self.assertEqual(draft_picking_qty_in, 0) + self.assertEqual(draft_purchase_qty, 5) + self.assertEqual(pending_qty_in, 5) + + # Confirms the PO and checks the report again. + po.button_confirm() + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty_in = docs['draft_picking_qty']['in'] + draft_purchase_qty = docs['draft_purchase_qty'] + pending_qty_in = docs['qty']['in'] + self.assertEqual(len(lines), 1) + self.assertEqual(lines[0]['document_in'].id, po.id) + self.assertEqual(lines[0]['quantity'], 5) + self.assertEqual(lines[0]['document_out'], False) + self.assertEqual(draft_picking_qty_in, 0) + self.assertEqual(draft_purchase_qty, 0) + self.assertEqual(pending_qty_in, 0) + + # Receives 5 products. + receipt = po.picking_ids + res_dict = receipt.button_validate() + wizard = Form(self.env[res_dict['res_model']].with_context(res_dict['context'])).save() + wizard.process() + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty_in = docs['draft_picking_qty']['in'] + draft_purchase_qty = docs['draft_purchase_qty'] + pending_qty_in = docs['qty']['in'] + self.assertEqual(len(lines), 0) + self.assertEqual(draft_picking_qty_in, 0) + self.assertEqual(draft_purchase_qty, 0) + self.assertEqual(pending_qty_in, 0) + + # Increase the PO quantity to 10, so must create a second receipt. + po_form = Form(po) + with po_form.order_line.edit(0) as line: + line.product_qty = 10 + po = po_form.save() + # Checks the report. + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty_in = docs['draft_picking_qty']['in'] + draft_purchase_qty = docs['draft_purchase_qty'] + pending_qty_in = docs['qty']['in'] + self.assertEqual(len(lines), 1, "Must have 1 line for now.") + self.assertEqual(lines[0]['document_in'].id, po.id) + self.assertEqual(lines[0]['quantity'], 5) + self.assertEqual(draft_picking_qty_in, 0) + self.assertEqual(draft_purchase_qty, 0) + self.assertEqual(pending_qty_in, 0) + + def test_report_forecast_2_purchase_order_three_step_receipt(self): + """ Create a PO for 4 product, receive them then increase the quantity + to 10, but use three steps receipt. + """ + grp_multi_loc = self.env.ref('stock.group_stock_multi_locations') + grp_multi_routes = self.env.ref('stock.group_adv_location') + self.env.user.write({'groups_id': [(4, grp_multi_loc.id)]}) + self.env.user.write({'groups_id': [(4, grp_multi_routes.id)]}) + # Configure warehouse. + warehouse = self.env.ref('stock.warehouse0') + warehouse.reception_steps = 'three_steps' + + po_form = Form(self.env['purchase.order']) + po_form.partner_id = self.partner + with po_form.order_line.new() as line: + line.product_id = self.product + line.product_qty = 4 + po = po_form.save() + + # Checks the report -> Must be empty for now, just display some pending qty. + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty_in = docs['draft_picking_qty']['in'] + draft_purchase_qty = docs['draft_purchase_qty'] + pending_qty_in = docs['qty']['in'] + self.assertEqual(len(lines), 0, "Must have 0 line for now.") + self.assertEqual(draft_picking_qty_in, 0) + self.assertEqual(draft_purchase_qty, 4) + self.assertEqual(pending_qty_in, 4) + + # Confirms the PO and checks the report again. + po.button_confirm() + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty_in = docs['draft_picking_qty']['in'] + draft_purchase_qty = docs['draft_purchase_qty'] + pending_qty_in = docs['qty']['in'] + self.assertEqual(len(lines), 1) + self.assertEqual(lines[0]['document_in'].id, po.id) + self.assertEqual(lines[0]['quantity'], 4) + self.assertEqual(lines[0]['document_out'], False) + self.assertEqual(draft_picking_qty_in, 0) + self.assertEqual(draft_purchase_qty, 0) + self.assertEqual(pending_qty_in, 0) + # Get back the different transfers. + receipt = po.picking_ids + + # Receives 4 products. + res_dict = receipt.button_validate() + wizard = Form(self.env[res_dict['res_model']].with_context(res_dict['context'])).save() + wizard.process() + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty_in = docs['draft_picking_qty']['in'] + draft_purchase_qty = docs['draft_purchase_qty'] + pending_qty_in = docs['qty']['in'] + self.assertEqual(len(lines), 0) + self.assertEqual(draft_picking_qty_in, 0) + self.assertEqual(draft_purchase_qty, 0) + self.assertEqual(pending_qty_in, 0) + + # Increase the PO quantity to 10, so must create a second receipt. + po_form = Form(po) + with po_form.order_line.edit(0) as line: + line.product_qty = 10 + po = po_form.save() + # Checks the report. + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty_in = docs['draft_picking_qty']['in'] + draft_purchase_qty = docs['draft_purchase_qty'] + pending_qty_in = docs['qty']['in'] + self.assertEqual(len(lines), 1) + self.assertEqual(lines[0]['document_in'].id, po.id) + self.assertEqual(lines[0]['quantity'], 6) + self.assertEqual(draft_picking_qty_in, 0) + self.assertEqual(draft_purchase_qty, 0) + self.assertEqual(pending_qty_in, 0)