diff --git a/addons/stock/report/report_stock_forecast.py b/addons/stock/report/report_stock_forecast.py index b7f7712024f..1fbfe364d61 100644 --- a/addons/stock/report/report_stock_forecast.py +++ b/addons/stock/report/report_stock_forecast.py @@ -14,102 +14,176 @@ class ReportStockForecat(models.Model): cumulative_quantity = fields.Float(string='Cumulative Quantity', readonly=True) quantity = fields.Float(readonly=True) company_id = fields.Many2one('res.company', string='Company', readonly=True) + reference = fields.Char('Reference') @api.model_cr def init(self): tools.drop_view_if_exists(self._cr, 'report_stock_forecast') self._cr.execute("""CREATE or REPLACE VIEW report_stock_forecast AS (SELECT - MIN(id) as id, - product_id as product_id, - date as date, - sum(product_qty) AS quantity, - sum(sum(product_qty)) OVER (PARTITION BY product_id ORDER BY date) AS cumulative_quantity, - company_id + row_number() OVER (ORDER BY final.date,final.id) AS id, + product_id as product_id, + date(final.date) as date, + sum(product_qty) AS quantity, + sum(sum(product_qty)) OVER (PARTITION BY final.product_id, final.company_id ORDER BY final.date) AS cumulative_quantity, + reference, + final.company_id FROM - (SELECT - MIN(id) as id, - MAIN.product_id as product_id, - SUB.date as date, - CASE WHEN MAIN.date = SUB.date THEN sum(MAIN.product_qty) ELSE 0 END as product_qty, - MAIN.company_id as company_id - FROM - (SELECT - MIN(sq.id) as id, - sq.product_id, - date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) as date, - SUM(sq.quantity) AS product_qty, - sq.company_id + (SELECT + MIN(id) as id, + MAIN.product_id as product_id, + date(SUB.date) as date, + CASE WHEN MAIN.date = SUB.date THEN sum(MAIN.product_qty) ELSE 0 END as product_qty, + MAIN.reference as reference, + MAIN.company_id as company_id FROM - stock_quant as sq + (SELECT + MIN(-product.id) as id, + product.id as product_id, + date(CURRENT_DATE) as date, + SUM(sq.quantity) AS product_qty, + 'Starting Inventory' as reference, + sq.company_id + FROM + product_product as product + LEFT JOIN + stock_quant sq ON product.id = sq.product_id + LEFT JOIN + stock_location location_id ON sq.location_id = location_id.id + WHERE + location_id.usage = 'internal' + GROUP BY + date, product.id, sq.company_id, sq.quantity + UNION ALL + SELECT + MIN(sm.id) as id, + sm.product_id, + CASE WHEN sm.date_expected > CURRENT_DATE + THEN date(sm.date_expected) + ELSE date(CURRENT_DATE) END + AS date, + SUM(sm.product_qty) AS product_qty, + sm.reference as reference, + sm.company_id + FROM + stock_move as sm + LEFT JOIN + product_product ON product_product.id = sm.product_id + LEFT JOIN + stock_location dest_location ON sm.location_dest_id = dest_location.id + LEFT JOIN + stock_location source_location ON sm.location_id = source_location.id + WHERE + sm.state IN ('confirmed','partially_available','assigned','waiting') and + source_location.usage != 'internal' and dest_location.usage = 'internal' + GROUP BY + sm.date_expected,sm.product_id, sm.company_id, sm.reference + UNION ALL + SELECT + MIN(sm.id) as id, + sm.product_id, + CASE WHEN sm.date_expected > CURRENT_DATE + THEN date(sm.date_expected) + ELSE date(CURRENT_DATE) END + AS date, + SUM(-(sm.product_qty)) AS product_qty, + sm.reference AS reference, + sm.company_id + FROM + stock_move as sm + LEFT JOIN + product_product ON product_product.id = sm.product_id + LEFT JOIN + stock_location source_location ON sm.location_id = source_location.id + LEFT JOIN + stock_location dest_location ON sm.location_dest_id = dest_location.id + WHERE + sm.state IN ('confirmed','partially_available','assigned','waiting') and + source_location.usage = 'internal' and dest_location.usage != 'internal' + GROUP BY + sm.date_expected,sm.product_id, sm.company_id, sm.reference + ) AS MAIN LEFT JOIN - product_product ON product_product.id = sq.product_id - LEFT JOIN - stock_location location_id ON sq.location_id = location_id.id - WHERE - location_id.usage = 'internal' - GROUP BY date, sq.product_id, sq.company_id - UNION ALL - SELECT - MIN(-sm.id) as id, - sm.product_id, - CASE WHEN sm.date_expected > CURRENT_DATE - THEN date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD')) - ELSE date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) END - AS date, - SUM(sm.product_qty) AS product_qty, - sm.company_id - FROM - stock_move as sm - LEFT JOIN - product_product ON product_product.id = sm.product_id - LEFT JOIN - stock_location dest_location ON sm.location_dest_id = dest_location.id - LEFT JOIN - stock_location source_location ON sm.location_id = source_location.id - WHERE - sm.state IN ('confirmed','partially_available','assigned','waiting') and - source_location.usage != 'internal' and dest_location.usage = 'internal' - GROUP BY sm.date_expected,sm.product_id, sm.company_id - UNION ALL - SELECT - MIN(-sm.id) as id, - sm.product_id, - CASE WHEN sm.date_expected > CURRENT_DATE - THEN date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD')) - ELSE date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) END - AS date, - SUM(-(sm.product_qty)) AS product_qty, - sm.company_id - FROM - stock_move as sm - LEFT JOIN - product_product ON product_product.id = sm.product_id - LEFT JOIN - stock_location source_location ON sm.location_id = source_location.id - LEFT JOIN - stock_location dest_location ON sm.location_dest_id = dest_location.id - WHERE - sm.state IN ('confirmed','partially_available','assigned','waiting') and - source_location.usage = 'internal' and dest_location.usage != 'internal' - GROUP BY sm.date_expected,sm.product_id, sm.company_id) - as MAIN - LEFT JOIN - (SELECT DISTINCT date - FROM - ( - SELECT date_trunc('week', CURRENT_DATE) AS DATE - UNION ALL - SELECT date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD')) AS date - FROM stock_move sm - LEFT JOIN - stock_location source_location ON sm.location_id = source_location.id - LEFT JOIN - stock_location dest_location ON sm.location_dest_id = dest_location.id - WHERE - sm.state IN ('confirmed','assigned','waiting') and sm.date_expected > CURRENT_DATE and - ((dest_location.usage = 'internal' AND source_location.usage != 'internal') - or (source_location.usage = 'internal' AND dest_location.usage != 'internal'))) AS DATE_SEARCH) - SUB ON (SUB.date IS NOT NULL) - GROUP BY MAIN.product_id,SUB.date, MAIN.date, MAIN.company_id - ) AS FINAL - GROUP BY product_id,date,company_id)""") + (SELECT + DISTINCT date + FROM + (SELECT + date_trunc('day', CURRENT_DATE) AS DATE + UNION ALL + SELECT + date(sm.date_expected) AS date + FROM + stock_move sm + LEFT JOIN + stock_location source_location ON sm.location_id = source_location.id + LEFT JOIN + stock_location dest_location ON sm.location_dest_id = dest_location.id + WHERE + sm.state IN ('confirmed','assigned','waiting') AND + sm.date_expected > CURRENT_DATE AND + ((dest_location.usage = 'internal' AND + source_location.usage != 'internal') OR + (source_location.usage = 'internal' AND + dest_location.usage != 'internal')) + ) AS DATE_SEARCH + ) AS SUB ON SUB.date IS NOT NULL + GROUP BY + MAIN.product_id,SUB.date, MAIN.date, MAIN.company_id,MAIN.reference + ) AS FINAL + LEFT JOIN + stock_picking sp ON final.reference=sp.name + WHERE + final.product_qty != 0 OR final.reference = 'Starting Inventory' + GROUP BY + product_id,final.date,final.id,final.company_id,final.reference,sp.priority,sp.date,sp.id)""") + + @api.model + def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): + """ Override read_group in order to display the correct cumulative + quantity. The cumulative quantity for a group of moves/quants + should be the last for the selected time interval (given by domain). + However select the 'last' record's value in a groupby is not possible + (only possible aggregation functions are the ones provided by + PostgreSQL). The cumulative quantity is recomputed since a search on + each group returned by the super would consume too much time. The + cumulative quantity is computed based on previous group. + - Group1: sum of the quantity of records in group1. + - Group2: cumulative_quantity of group1 + sum of quantity of records in group2 + - Group3: cumulative_quantity of group2 + sum of quantity of records in group3 + ... + Since read_group depends on domain, some records could be excluded and + the computed cumulative quantity could be different than the one computed + in SQL. It's wanted since the customer could play with the domain in + order to analyze different quantities in different situations. + """ + # Sort result by date_expected and id. + orderby = 'date, id' if not orderby else orderby + if 'cumulative_quantity' in fields and 'quantity' not in fields: + fields.append('quantity') + res = super(ReportStockForecat, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy) + total_cumulative_quantity = 0 + cumulative_quantity_by_products = {} + if 'cumulative_quantity' in fields: + for index, line in enumerate(res): + # If the stock forecast cell have a 0 quantity the read_group + # will not return their sum as 0 but False. + if 'quantity' in line and not line['quantity']: + line['quantity'] = 0.0 + if line.get('product_id'): + product_id = line['product_id'] + if not cumulative_quantity_by_products.get(product_id): + cumulative_quantity_by_products[product_id] = 0 + cumulative_quantity_by_products[product_id] += line['quantity'] # Get cumulative quantity product wise + line['cumulative_quantity'] = cumulative_quantity_by_products[product_id] # Sum of all quantities (i.e. cumulative quantity) product wise + elif line.get('reference'): + if line['reference'] == 'Starting Inventory': + if index == 0: + line['cumulative_quantity'] = line['quantity'] + else: + line['quantity'] = 0.0 + line['cumulative_quantity'] = res[index - 1]['cumulative_quantity'] # Set cumulatiove quantity if line does not have any cumulative + elif line.get('quantity', False): + total_cumulative_quantity += line['quantity'] + line['cumulative_quantity'] = total_cumulative_quantity # Sum of all Move Operations' quantity + else: + line['cumulative_quantity'] = 0 + return res diff --git a/addons/stock/report/report_stock_forecast.xml b/addons/stock/report/report_stock_forecast.xml index 8045b108369..e25e00acd50 100644 --- a/addons/stock/report/report_stock_forecast.xml +++ b/addons/stock/report/report_stock_forecast.xml @@ -7,19 +7,10 @@ + - - - - - - Stock.forecast.graph - report.stock.forecast - - - - + @@ -32,8 +23,10 @@ - - + + + + @@ -43,9 +36,9 @@ Stock Level Forecast report.stock.forecast form - graph,pivot + pivot - {'search_default_product_tmpl_id': active_id, 'group_by': ['company_id']} + {'search_default_product_tmpl_id': active_id, 'search_default_inventory_by_week':1} @@ -53,9 +46,25 @@ Stock Level Forecast report.stock.forecast form - graph,pivot + pivot - {'search_default_product_id': active_id, 'group_by': ['company_id']} + {'search_default_product_id': active_id, 'search_default_inventory_by_week':1} + + + + + Forecasted Inventory + report.stock.forecast + form + pivot + + [('product_id.stock_move_ids.state', 'not in', ('draft', 'done', 'cancel'))] + +

+ There is nothing to forecast yet. +

+
+ {'search_default_inventory_by_week':1, 'create': False}
diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml index fceaeb301ad..3aea1854604 100644 --- a/addons/stock/views/product_views.xml +++ b/addons/stock/views/product_views.xml @@ -210,6 +210,7 @@