summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsvs-odoo <svs@odoo.com>2020-07-15 08:18:25 +0000
committersvs-odoo <svs@odoo.com>2020-07-31 12:36:54 +0000
commit38018469a7c2604bfa813cfcc6cb7bb824dacf7e (patch)
treea0442eead4a357199c23179066494845f121c186
parent832af921156adf9951e533397e8a1b6e384c711b (diff)
[IMP] purchase_stock: extend replenishment report
Adds Purchase Orders as replenishment/consuming document for the replenishment report. task-2058495
-rw-r--r--addons/purchase_stock/__manifest__.py1
-rw-r--r--addons/purchase_stock/models/stock.py4
-rw-r--r--addons/purchase_stock/report/__init__.py1
-rw-r--r--addons/purchase_stock/report/report_stock_forecasted.py32
-rw-r--r--addons/purchase_stock/report/report_stock_forecasted.xml12
-rw-r--r--addons/purchase_stock/tests/__init__.py1
-rw-r--r--addons/purchase_stock/tests/test_purchase_stock_report.py147
7 files changed, 198 insertions, 0 deletions
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="purchase_report_product_product_replenishment" inherit_id="stock.report_product_product_replenishment">
+ <xpath expr="//tr[@name='draft_picking_in']" position="after">
+ <tr t-if="docs['draft_purchase_qty']" name="draft_po_in">
+ <td colspan="2">Draft PO</td>
+ <td t-esc="docs['draft_purchase_qty']" class="text-right"/>
+ <td t-esc="docs['uom']" groups="uom.group_uom"/>
+ </tr>
+ </xpath>
+ </template>
+</odoo>
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)