summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsvs-odoo <svs@odoo.com>2020-07-15 08:31:52 +0000
committersvs-odoo <svs@odoo.com>2020-07-31 12:36:54 +0000
commit61300f6817cabf1f3f57f96c4aa30b049eeeffe4 (patch)
tree6be2e5a9669e2c2b939f2c76c7168797937f4ba1
parent38018469a7c2604bfa813cfcc6cb7bb824dacf7e (diff)
[IMP] sale_stock: extend replenishment report
Adds Sale Order as consuming document for the report. Also, make the SO line `warehouse_id` field related to SO warehouse. task-2058495
-rw-r--r--addons/sale_stock/__manifest__.py1
-rw-r--r--addons/sale_stock/models/sale_order.py6
-rw-r--r--addons/sale_stock/models/stock.py4
-rw-r--r--addons/sale_stock/report/__init__.py1
-rw-r--r--addons/sale_stock/report/report_stock_forecasted.py32
-rw-r--r--addons/sale_stock/report/report_stock_forecasted.xml12
-rw-r--r--addons/sale_stock/tests/__init__.py1
-rw-r--r--addons/sale_stock/tests/test_sale_stock_report.py57
8 files changed, 110 insertions, 4 deletions
diff --git a/addons/sale_stock/__manifest__.py b/addons/sale_stock/__manifest__.py
index 0992ac081e0..638753a08d0 100644
--- a/addons/sale_stock/__manifest__.py
+++ b/addons/sale_stock/__manifest__.py
@@ -30,6 +30,7 @@ Preferences
'views/stock_production_lot_views.xml',
'views/report_invoice.xml',
'views/res_users_views.xml',
+ 'report/report_stock_forecasted.xml',
'report/sale_order_report_templates.xml',
'report/stock_report_deliveryslip.xml',
'data/sale_stock_data.xml',
diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py
index fde99c982c2..aa4a7dc2879 100644
--- a/addons/sale_stock/models/sale_order.py
+++ b/addons/sale_stock/models/sale_order.py
@@ -263,7 +263,7 @@ class SaleOrderLine(models.Model):
scheduled_date = fields.Datetime(compute='_compute_qty_at_date')
free_qty_today = fields.Float(compute='_compute_qty_at_date')
qty_available_today = fields.Float(compute='_compute_qty_at_date')
- warehouse_id = fields.Many2one('stock.warehouse', compute='_compute_qty_at_date')
+ warehouse_id = fields.Many2one(related='order_id.warehouse_id')
qty_to_deliver = fields.Float(compute='_compute_qty_to_deliver')
is_mto = fields.Boolean(compute='_compute_is_mto')
display_qty_widget = fields.Boolean(compute='_compute_qty_to_deliver')
@@ -278,7 +278,7 @@ class SaleOrderLine(models.Model):
else:
line.display_qty_widget = False
- @api.depends('product_id', 'customer_lead', 'product_uom_qty', 'product_uom', 'order_id.warehouse_id', 'order_id.commitment_date')
+ @api.depends('product_id', 'customer_lead', 'product_uom_qty', 'product_uom', 'order_id.commitment_date')
def _compute_qty_at_date(self):
""" Compute the quantity forecasted of product at delivery date. There are
two cases:
@@ -292,7 +292,6 @@ class SaleOrderLine(models.Model):
for line in self:
if not line.display_qty_widget:
continue
- line.warehouse_id = line.order_id.warehouse_id
if line.order_id.commitment_date:
date = line.order_id.commitment_date
else:
@@ -327,7 +326,6 @@ class SaleOrderLine(models.Model):
remaining.scheduled_date = False
remaining.free_qty_today = False
remaining.qty_available_today = False
- remaining.warehouse_id = False
@api.depends('product_id', 'route_id', 'order_id.warehouse_id', 'product_id.route_ids')
def _compute_is_mto(self):
diff --git a/addons/sale_stock/models/stock.py b/addons/sale_stock/models/stock.py
index 9b679944502..e1d36c38f7d 100644
--- a/addons/sale_stock/models/stock.py
+++ b/addons/sale_stock/models/stock.py
@@ -36,6 +36,10 @@ class StockMove(models.Model):
#rslt += invoices.mapped('reverse_entry_ids')
return rslt
+ def _get_source_document(self):
+ res = super()._get_source_document()
+ return self.sale_line_id.order_id or res
+
def _assign_picking_post_process(self, new=False):
super(StockMove, self)._assign_picking_post_process(new=new)
if new:
diff --git a/addons/sale_stock/report/__init__.py b/addons/sale_stock/report/__init__.py
index 660a07d84e2..4109cd49093 100644
--- a/addons/sale_stock/report/__init__.py
+++ b/addons/sale_stock/report/__init__.py
@@ -2,4 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import sale_report
+from . import report_stock_forecasted
from . import report_stock_rule
diff --git a/addons/sale_stock/report/report_stock_forecasted.py b/addons/sale_stock/report/report_stock_forecasted.py
new file mode 100644
index 00000000000..be66eed9e3a
--- /dev/null
+++ b/addons/sale_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 = self._product_sale_domain(product_template_ids, product_variant_ids)
+ so_lines = self.env['sale.order.line'].search(domain)
+ out_sum = 0
+ if so_lines:
+ product_uom = so_lines[0].product_id.uom_id
+ quantities = so_lines.mapped(lambda line: line.product_uom._compute_quantity(line.product_uom_qty, product_uom))
+ out_sum = sum(quantities)
+ res['draft_sale_qty'] = out_sum
+ res['qty']['out'] += out_sum
+ return res
+
+ def _product_sale_domain(self, product_template_ids, product_variant_ids):
+ domain = [('state', 'in', ['draft', 'sent'])]
+ if product_template_ids:
+ domain += [('product_template_id', 'in', product_template_ids)]
+ elif product_variant_ids:
+ domain += [('product_id', 'in', product_variant_ids)]
+ warehouse_id = self.env.context.get('warehouse', False)
+ if warehouse_id:
+ domain += [('warehouse_id', '=', warehouse_id)]
+ return domain
diff --git a/addons/sale_stock/report/report_stock_forecasted.xml b/addons/sale_stock/report/report_stock_forecasted.xml
new file mode 100644
index 00000000000..9f43b615fc0
--- /dev/null
+++ b/addons/sale_stock/report/report_stock_forecasted.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="sale_report_product_product_replenishment" inherit_id="stock.report_product_product_replenishment">
+ <xpath expr="//tr[@name='draft_picking_out']" position="after">
+ <tr t-if="docs['draft_sale_qty']" name="draft_so_out">
+ <td colspan="2">Draft SO</td>
+ <td t-esc="docs['draft_sale_qty']" class="text-right"/>
+ <td t-esc="docs['uom']" groups="uom.group_uom"/>
+ </tr>
+ </xpath>
+ </template>
+</odoo>
diff --git a/addons/sale_stock/tests/__init__.py b/addons/sale_stock/tests/__init__.py
index c9c90165757..f4f97c3a04a 100644
--- a/addons/sale_stock/tests/__init__.py
+++ b/addons/sale_stock/tests/__init__.py
@@ -5,5 +5,6 @@ from . import test_anglo_saxon_valuation
from . import test_anglo_saxon_valuation_reconciliation
from . import test_sale_stock
from . import test_sale_stock_lead_time
+from . import test_sale_stock_report
from . import test_sale_order_dates
from . import test_sale_stock_multicompany
diff --git a/addons/sale_stock/tests/test_sale_stock_report.py b/addons/sale_stock/tests/test_sale_stock_report.py
new file mode 100644
index 00000000000..a132a844dcd
--- /dev/null
+++ b/addons/sale_stock/tests/test_sale_stock_report.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import datetime, timedelta
+
+from odoo.tests.common import Form
+from odoo.addons.stock.tests.test_report import TestReportsCommon
+
+
+class TestSaleStockReports(TestReportsCommon):
+ def test_report_forecast_1_sale_order_replenishment(self):
+ """ Create and confirm two sale orders: one for the next week and one
+ for tomorrow. Then check in the report it's the most urgent who is
+ linked to the qty. on stock.
+ """
+ today = datetime.today()
+ # Put some quantity in stock.
+ quant_vals = {
+ 'product_id': self.product.id,
+ 'product_uom_id': self.product.uom_id.id,
+ 'location_id': self.stock_location.id,
+ 'quantity': 5,
+ 'reserved_quantity': 0,
+ }
+ self.env['stock.quant'].create(quant_vals)
+ # Create a first SO for the next week.
+ so_form = Form(self.env['sale.order'])
+ so_form.partner_id = self.partner
+ # so_form.validity_date = today + timedelta(days=7)
+ with so_form.order_line.new() as so_line:
+ so_line.product_id = self.product
+ so_line.product_uom_qty = 5
+ so_1 = so_form.save()
+ so_1.action_confirm()
+ so_1.picking_ids.scheduled_date = today + timedelta(days=7)
+
+ # Create a second SO for tomorrow.
+ so_form = Form(self.env['sale.order'])
+ so_form.partner_id = self.partner
+ # so_form.validity_date = today + timedelta(days=1)
+ with so_form.order_line.new() as so_line:
+ so_line.product_id = self.product
+ so_line.product_uom_qty = 5
+ so_2 = so_form.save()
+ so_2.action_confirm()
+ so_2.picking_ids.scheduled_date = today + timedelta(days=1)
+
+ report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids)
+ self.assertEqual(len(lines), 2)
+ line_1 = lines[0]
+ line_2 = lines[1]
+ self.assertEqual(line_1['quantity'], 5)
+ self.assertTrue(line_1['replenishment_filled'])
+ self.assertEqual(line_1['document_out'].id, so_2.id)
+ self.assertEqual(line_2['quantity'], 5)
+ self.assertEqual(line_2['replenishment_filled'], False)
+ self.assertEqual(line_2['document_out'].id, so_1.id)