diff --git a/addons/stock/__manifest__.py b/addons/stock/__manifest__.py index 217bec03858..7df37828b1e 100644 --- a/addons/stock/__manifest__.py +++ b/addons/stock/__manifest__.py @@ -26,6 +26,7 @@ 'data/stock_traceability_report_data.xml', 'data/procurement_data.xml', + 'report/report_stock_quantity.xml', 'report/stock_report_views.xml', 'report/report_package_barcode.xml', 'report/report_lot_barcode.xml', diff --git a/addons/stock/report/__init__.py b/addons/stock/report/__init__.py index 55ce8712c5c..437a4c100c9 100644 --- a/addons/stock/report/__init__.py +++ b/addons/stock/report/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import report_stock_quantity from . import report_stock_rule diff --git a/addons/stock/report/report_stock_quantity.py b/addons/stock/report/report_stock_quantity.py new file mode 100644 index 00000000000..d06a1a96b4d --- /dev/null +++ b/addons/stock/report/report_stock_quantity.py @@ -0,0 +1,99 @@ +# -*- 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) + + def init(self): + tools.drop_view_if_exists(self._cr, 'report_stock_quantity') + query = """ +CREATE or REPLACE VIEW report_stock_quantity AS ( +SELECT + m.id, + product_id, + CASE + WHEN (ls.usage = 'internal' OR ls.usage = 'transit' AND ls.company_id IS NOT NULL) AND ld.usage != 'internal' THEN 'out' + WHEN ls.usage != 'internal' AND (ld.usage = 'internal' OR ld.usage = 'transit' AND ld.company_id IS NOT NULL) THEN 'in' + END AS state, + date_expected::date AS date, + CASE + WHEN (ls.usage = 'internal' OR ls.usage = 'transit' AND ls.company_id IS NOT NULL) AND ld.usage != 'internal' THEN -product_qty + WHEN ls.usage != 'internal' AND (ld.usage = 'internal' OR ld.usage = 'transit' AND ld.company_id IS NOT NULL) THEN product_qty + END AS product_qty, + m.company_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) +WHERE + product_qty != 0 AND ( + (ls.usage = 'internal' OR ls.usage = 'transit' AND ls.company_id IS NOT NULL) AND ld.usage != 'internal' OR + ls.usage != 'internal' AND (ld.usage = 'internal' OR ld.usage = 'transit' AND ld.company_id IS NOT NULL)) AND + m.state NOT IN ('cancel', 'draft', 'done') +UNION +SELECT + -MIN(q.id) as id, + product_id, + 'forecast' as state, + date.*::date, + SUM(quantity) as product_qty, + q.company_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) +WHERE + l.usage = 'internal' +GROUP BY + product_id, date, q.company_id +UNION +SELECT + m.id, + product_id, + 'forecast' as state, + GENERATE_SERIES( + CASE + WHEN state = 'done' THEN (now() at time zone 'utc')::date - interval '3month' + ELSE date_expected::date + END, + CASE + WHEN state != 'done' THEN (now() at time zone 'utc')::date + interval '3 month' + ELSE date::date - interval '1 day' + END, '1 day'::interval)::date date, + CASE + WHEN (ls.usage = 'internal' OR ls.usage = 'transit' AND ls.company_id IS NOT NULL) AND ld.usage != 'internal' AND state = 'done' THEN product_qty + WHEN ls.usage != 'internal' AND (ld.usage = 'internal' OR ld.usage = 'transit' AND ld.company_id IS NOT NULL) AND state = 'done' THEN -product_qty + WHEN (ls.usage = 'internal' OR ls.usage = 'transit' AND ls.company_id IS NOT NULL) AND ld.usage != 'internal' THEN -product_qty + WHEN ls.usage != 'internal' AND (ld.usage = 'internal' OR ld.usage = 'transit' AND ld.company_id IS NOT NULL) THEN product_qty + END AS product_qty, + m.company_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) +WHERE + product_qty != 0 AND ( + (ls.usage = 'internal' OR ls.usage = 'transit' AND ls.company_id IS NOT NULL) AND ld.usage != 'internal' OR + ls.usage != 'internal' AND (ld.usage = 'internal' OR ld.usage = 'transit' AND ld.company_id IS NOT NULL)) AND + m.state NOT IN ('cancel', 'draft') +); +""" + self.env.cr.execute(query) diff --git a/addons/stock/report/report_stock_quantity.xml b/addons/stock/report/report_stock_quantity.xml new file mode 100644 index 00000000000..a0987a1ca94 --- /dev/null +++ b/addons/stock/report/report_stock_quantity.xml @@ -0,0 +1,55 @@ + + + + stock_report_view_graph + report.stock.quantity + + + + + + + + + + + report.stock.quantity.search + report.stock.quantity + + + + + + + + + + + + + + + + + + + Forecasted Inventory + report.stock.quantity + graph + { + 'search_default_filter_forecast': 1, + 'graph_groupbys': ['date:day', 'state'], + } + + + + Forecasted Inventory + report.stock.quantity + graph + { + 'search_default_filter_forecast': 1, + 'graph_groupbys': ['date:day', 'state', 'product_id'], + } + + + diff --git a/addons/stock/security/ir.model.access.csv b/addons/stock/security/ir.model.access.csv index b0eb20c8afc..599da9f91c9 100644 --- a/addons/stock/security/ir.model.access.csv +++ b/addons/stock/security/ir.model.access.csv @@ -70,3 +70,4 @@ access_product_attribute_value_manager,product.attribute manager value,product.m access_product_product_attribute_manager,product.product.attribute manager value,product.model_product_template_attribute_value,stock.group_stock_manager,1,1,1,1 access_product_template_attribute_exclusion_manager,product.attribute manager filter line,product.model_product_template_attribute_exclusion,stock.group_stock_manager,1,1,1,1 access_product_template_attribute_line_manager,product.attribute manager line,product.model_product_template_attribute_line,stock.group_stock_manager,1,1,1,1 +access_report_stock_quantity,access_report_stock_quantity,model_report_stock_quantity,base.group_user,1,0,0,0 diff --git a/addons/stock/security/stock_security.xml b/addons/stock/security/stock_security.xml index 0adb7c6db34..2ce2d742bdf 100644 --- a/addons/stock/security/stock_security.xml +++ b/addons/stock/security/stock_security.xml @@ -172,5 +172,12 @@ [('company_id', 'in', company_ids)] + + report_stock_quantity_flow multi-company + + + ['|',('company_id','=',False),('company_id', 'in', company_ids)] + + diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml index ae2c14b4714..660897759fc 100644 --- a/addons/stock/views/product_views.xml +++ b/addons/stock/views/product_views.xml @@ -224,6 +224,22 @@ On Hand + +