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>
132 lines
5.3 KiB
Python
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)
|