[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
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user