Files
odoo_source/addons/stock/report/report_stock_quantity.py
T
Nicolas Galler 0d2aa75977 [FIX] stock: correct product_qty filter in forecast inventory report
Behavior prior to the fix:

- when adding a filter on "Quantity < 0" (or > 0) on the forecast
inventory report the quantity reported becomes incorrect: for example if
there is a restock on Tuesday and a shipment on Friday that drives the
forecasted quantity into a negative, the forecasted inventory report
instead reports a negative quantity on Monday.
- the problem occurs because the view the report is based on is built
using positive and negative entries that are supposed to cancel each
other when accounting for stock items (quant).  This way the quantity is
reported as 0 before the quant is moved in the inventory (there is a
positive entry for the quant, and a negative entry for the move up to
the move effective date).  But when a filter on quantity is placed the
"cancelling" entries are not loaded and the report ends up with just the
negative value

Behavior after the fix:

- the quantities are reported as expected: with a negative on Friday,
and 0 on the days before
- to achieve this the view was modified to perform the grouping of
forecast entries inside of the view.  This way we have one row per
product / day / state and we don't risk selecting only part of the
report.
- as the view is only read in aggregate there will not be any visible
changes to its consumers

opw-2379098

closes odoo/odoo#62574

X-original-commit: c16d4b5e7b9181c2c792f595a117de10510d45be
Signed-off-by: Nicolas Martinelli (nim) <nim@odoo.com>
Signed-off-by: Nicolas Galler <ngaller@users.noreply.github.com>
2020-11-30 07:28:47 +00:00

132 lines
5.3 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, tools
class ReportStockQuantity(models.Model):
_name = 'report.stock.quantity'
_auto = False
_description = 'Stock Quantity Report'
date = fields.Date(string='Date', readonly=True)
product_tmpl_id = fields.Many2one('product.template', related='product_id.product_tmpl_id')
product_id = fields.Many2one('product.product', string='Product', readonly=True)
state = fields.Selection([
('forecast', 'Forecasted Stock'),
('in', 'Forecasted Receipts'),
('out', 'Forecasted Deliveries'),
], string='State', readonly=True)
product_qty = fields.Float(string='Quantity', readonly=True)
move_ids = fields.One2many('stock.move', readonly=True)
company_id = fields.Many2one('res.company', readonly=True)
warehouse_id = fields.Many2one('stock.warehouse', readonly=True)
def init(self):
tools.drop_view_if_exists(self._cr, 'report_stock_quantity')
query = """
CREATE or REPLACE VIEW report_stock_quantity AS (
WITH forecast_qty AS (
SELECT
m.id,
m.product_id,
CASE
WHEN (whs.id IS NOT NULL AND whd.id IS NULL) OR ls.usage = 'transit' THEN 'out'
WHEN (whs.id IS NULL AND whd.id IS NOT NULL) OR ld.usage = 'transit' THEN 'in'
END AS state,
m.date::date AS date,
CASE
WHEN (whs.id IS NOT NULL AND whd.id IS NULL) OR ls.usage = 'transit' THEN -product_qty
WHEN (whs.id IS NULL AND whd.id IS NOT NULL) OR ld.usage = 'transit' THEN product_qty
END AS product_qty,
m.company_id,
CASE
WHEN (whs.id IS NOT NULL AND whd.id IS NULL) OR ls.usage = 'transit' THEN whs.id
WHEN (whs.id IS NULL AND whd.id IS NOT NULL) OR ld.usage = 'transit' THEN whd.id
END AS warehouse_id
FROM
stock_move m
LEFT JOIN stock_location ls on (ls.id=m.location_id)
LEFT JOIN stock_location ld on (ld.id=m.location_dest_id)
LEFT JOIN stock_warehouse whs ON ls.parent_path like concat('%/', whs.view_location_id, '/%')
LEFT JOIN stock_warehouse whd ON ld.parent_path like concat('%/', whd.view_location_id, '/%')
LEFT JOIN product_product pp on pp.id=m.product_id
LEFT JOIN product_template pt on pt.id=pp.product_tmpl_id
WHERE
pt.type = 'product' AND
product_qty != 0 AND
(whs.id IS NOT NULL OR whd.id IS NOT NULL) AND
(whs.id IS NULL OR whd.id IS NULL OR whs.id != whd.id) AND
m.state NOT IN ('cancel', 'draft', 'done')
UNION
SELECT
-q.id as id,
q.product_id,
'forecast' as state,
date.*::date,
q.quantity as product_qty,
q.company_id,
wh.id as warehouse_id
FROM
GENERATE_SERIES((now() at time zone 'utc')::date - interval '3month',
(now() at time zone 'utc')::date + interval '3 month', '1 day'::interval) date,
stock_quant q
LEFT JOIN stock_location l on (l.id=q.location_id)
LEFT JOIN stock_warehouse wh ON l.parent_path like concat('%/', wh.view_location_id, '/%')
WHERE
(l.usage = 'internal' AND wh.id IS NOT NULL) OR
l.usage = 'transit'
UNION
SELECT
m.id,
m.product_id,
'forecast' as state,
GENERATE_SERIES(
CASE
WHEN m.state = 'done' THEN (now() at time zone 'utc')::date - interval '3month'
ELSE m.date::date
END,
CASE
WHEN m.state != 'done' THEN (now() at time zone 'utc')::date + interval '3 month'
ELSE m.date::date - interval '1 day'
END, '1 day'::interval)::date date,
CASE
WHEN ((whs.id IS NOT NULL AND whd.id IS NULL) OR ls.usage = 'transit') AND m.state = 'done' THEN product_qty
WHEN ((whs.id IS NULL AND whd.id IS NOT NULL) OR ld.usage = 'transit') AND m.state = 'done' THEN -product_qty
WHEN (whs.id IS NOT NULL AND whd.id IS NULL) OR ls.usage = 'transit' THEN -product_qty
WHEN (whs.id IS NULL AND whd.id IS NOT NULL) OR ld.usage = 'transit' THEN product_qty
END AS product_qty,
m.company_id,
CASE
WHEN (whs.id IS NOT NULL AND whd.id IS NULL) OR ls.usage = 'transit' THEN whs.id
WHEN (whs.id IS NULL AND whd.id IS NOT NULL) OR ld.usage = 'transit' THEN whd.id
END AS warehouse_id
FROM
stock_move m
LEFT JOIN stock_location ls on (ls.id=m.location_id)
LEFT JOIN stock_location ld on (ld.id=m.location_dest_id)
LEFT JOIN stock_warehouse whs ON ls.parent_path like concat('%/', whs.view_location_id, '/%')
LEFT JOIN stock_warehouse whd ON ld.parent_path like concat('%/', whd.view_location_id, '/%')
LEFT JOIN product_product pp on pp.id=m.product_id
LEFT JOIN product_template pt on pt.id=pp.product_tmpl_id
WHERE
pt.type = 'product' AND
product_qty != 0 AND
(whs.id IS NOT NULL OR whd.id IS NOT NULL) AND
(whs.id IS NULL or whd.id IS NULL OR whs.id != whd.id) AND
m.state NOT IN ('cancel', 'draft')
) -- /forecast_qty
SELECT
MIN(id) as id,
product_id,
state,
date,
sum(product_qty) as product_qty,
company_id,
warehouse_id
FROM forecast_qty
GROUP BY product_id, state, date, company_id, warehouse_id
);
"""
self.env.cr.execute(query)