[IMP] stock, stock_account: inventory at date
Usability improvement because when the user click on inventory
report it's mainly in order to edit/check its current stock since
commit da33eb36b8
This change redirect the user directly on stock.quant tree view
and add an inventory at date button in the control panel. This
button will open the same wizard than previously and ask for a
date. The same principle is used for valuation layer.
Co-authored-by: svs-odoo <svs@odoo.com>
Task: 1880379
This commit is contained in:
co-authored by
svs-odoo
parent
c861edefaa
commit
ec02ca9176
@@ -78,6 +78,7 @@
|
||||
'data/stock_sequence_data.xml',
|
||||
],
|
||||
'qweb': [
|
||||
'static/src/xml/inventory_report.xml',
|
||||
'static/src/xml/inventory_lines.xml',
|
||||
'static/src/xml/stock_traceability_report_backend.xml',
|
||||
],
|
||||
|
||||
@@ -163,6 +163,31 @@ class StockQuant(models.Model):
|
||||
]
|
||||
return action
|
||||
|
||||
@api.model
|
||||
def action_view_quants(self):
|
||||
self = self.with_context(search_default_internal_loc=1)
|
||||
if self.user_has_groups('stock.group_production_lot,stock.group_stock_multi_locations'):
|
||||
# fixme: erase the following condition when it'll be possible to create a new record
|
||||
# from a empty grouped editable list without go through the form view.
|
||||
if self.search_count([
|
||||
('company_id', '=', self.env.user.company_id.id),
|
||||
('location_id.usage', 'in', ['internal', 'transit'])
|
||||
]):
|
||||
self = self.with_context(
|
||||
search_default_productgroup=1,
|
||||
search_default_locationgroup=1
|
||||
)
|
||||
if not self.user_has_groups('stock.group_stock_multi_locations'):
|
||||
company_user = self.env.user.company_id
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', company_user.id)], limit=1)
|
||||
if warehouse:
|
||||
self = self.with_context(default_location_id=warehouse.lot_stock_id.id)
|
||||
|
||||
# If user have rights to write on quant, we set quants in inventory mode.
|
||||
if self.user_has_groups('stock.group_stock_manager'):
|
||||
self = self.with_context(inventory_mode=True)
|
||||
return self._get_quants_action(extend=True)
|
||||
|
||||
@api.constrains('product_id')
|
||||
def check_product_id(self):
|
||||
if any(elem.product_id.type != 'product' for elem in self):
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
odoo.define('stock.InventoryReportListController', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var ListController = require('web.ListController');
|
||||
|
||||
var qweb = core.qweb;
|
||||
|
||||
|
||||
var InventoryReportListController = ListController.extend({
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
renderButtons: function ($node) {
|
||||
this._super.apply(this, arguments);
|
||||
var $buttonToDate = $(qweb.render('InventoryReport.Buttons'));
|
||||
$buttonToDate.on('click', this._onOpenWizard.bind(this));
|
||||
|
||||
$buttonToDate.prependTo($node.find('.o_list_buttons'));
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Handler called when the user clicked on the 'Inventory at Date' button.
|
||||
* Opens wizard to display, at choice, the products inventory or a computed
|
||||
* inventory at a given date.
|
||||
*/
|
||||
_onOpenWizard: function () {
|
||||
this.do_action({
|
||||
res_model: 'stock.quantity.history',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
type: 'ir.actions.act_window',
|
||||
context: {
|
||||
active_model: this.modelName,
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return InventoryReportListController;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
odoo.define('stock.InventoryReportListView', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ListView = require('web.ListView');
|
||||
var InventoryReportListController = require('stock.InventoryReportListController');
|
||||
var viewRegistry = require('web.view_registry');
|
||||
|
||||
|
||||
var InventoryReportListView = ListView.extend({
|
||||
config: _.extend({}, ListView.prototype.config, {
|
||||
Controller: InventoryReportListController,
|
||||
}),
|
||||
});
|
||||
|
||||
viewRegistry.add('inventory_report_list', InventoryReportListView);
|
||||
|
||||
return InventoryReportListView;
|
||||
|
||||
});
|
||||
@@ -2,7 +2,7 @@ odoo.define('stock.SingletonListController', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var ListController = require('web.ListController');
|
||||
var InventoryReportListController = require('stock.InventoryReportListController');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
@@ -16,7 +16,7 @@ var _t = core._t;
|
||||
* in the list view, so we refresh it.
|
||||
*/
|
||||
|
||||
var SingletonListController = ListController.extend({
|
||||
var SingletonListController = InventoryReportListController.extend({
|
||||
/**
|
||||
* @override
|
||||
* @return {Promise} rejected when update the list because we don't want
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
odoo.define('stock.SingletonListView', function (require) {
|
||||
'use strict';
|
||||
|
||||
var ListView = require('web.ListView');
|
||||
var InventoryReportListView = require('stock.InventoryReportListView');
|
||||
var SingletonListController = require('stock.SingletonListController');
|
||||
var viewRegistry = require('web.view_registry');
|
||||
|
||||
var SingletonListView = ListView.extend({
|
||||
config: _.extend({}, ListView.prototype.config, {
|
||||
var SingletonListView = InventoryReportListView.extend({
|
||||
config: _.extend({}, InventoryReportListView.prototype.config, {
|
||||
Controller: SingletonListController,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<button t-name="InventoryReport.Buttons" class="btn btn-primary" type="button">
|
||||
Inventory at Date
|
||||
</button>
|
||||
|
||||
</templates>
|
||||
@@ -114,13 +114,14 @@
|
||||
<field name="model">stock.quant</field>
|
||||
<field eval="10" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree create="0" delete="0" decoration-danger="quantity < 0">
|
||||
<tree string="Inventory Valuation" js_class="inventory_report_list"
|
||||
create="0" delete="0" decoration-danger="quantity < 0">
|
||||
<field name="product_id" invisible="context.get('single_product', False)"/>
|
||||
<field name="location_id" invisible="context.get('hide_location', False)"/>
|
||||
<field name="lot_id" groups="stock.group_production_lot"/>
|
||||
<field name="package_id" groups="stock.group_tracking_lot"/>
|
||||
<field name="owner_id" groups="stock.group_tracking_owner"/>
|
||||
<field name="reserved_quantity"/>
|
||||
<field name="reserved_quantity" string="Reserved"/>
|
||||
<field name="quantity"/>
|
||||
<field name="product_uom_id" groups="uom.group_uom"/>
|
||||
<field name='company_id' groups="base.group_multi_company"/>
|
||||
@@ -128,7 +129,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record model="ir.ui.view" id="view_stock_quant_pivot">
|
||||
<field name="name">stock.quant.pivot</field>
|
||||
<field name="model">stock.quant</field>
|
||||
@@ -152,23 +152,19 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="quantsact"> <!-- Menu + python -->
|
||||
<record model="ir.actions.server" id="action_view_quants"> <!-- Menu + python -->
|
||||
<field name="name">Inventory</field>
|
||||
<field name="res_model">stock.quant</field>
|
||||
<field name="view_mode">tree,form,pivot,graph</field>
|
||||
<field name="context">{
|
||||
'search_default_internal_loc': 1,
|
||||
'search_default_productgroup': 1,
|
||||
'search_default_locationgroup': 1
|
||||
}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No inventory found
|
||||
</p><p>
|
||||
This analysis gives you a fast overview on the current stock level of your products and their current inventory value.
|
||||
</p>
|
||||
<field name="model_id" ref="model_stock_quant"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
action = model.with_context(
|
||||
search_default_internal_loc=1,
|
||||
search_default_productgroup=1,
|
||||
search_default_locationgroup=1,
|
||||
).action_view_quants()
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="product_template_open_quants"> <!-- Not used -->
|
||||
<field name="name">Stock On Hand</field>
|
||||
<field name="context">{'search_default_internal_loc': 1, 'search_default_productgroup':1, 'search_default_locationgroup':1}</field>
|
||||
@@ -287,5 +283,7 @@
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_package" name="Packages" parent="menu_stock_inventory_control" action="action_package_view" groups="stock.group_tracking_lot" sequence="102"/>
|
||||
|
||||
<menuitem id="menu_valuation" name="Inventory Report"
|
||||
parent="stock.menu_warehouse_report" sequence="100"
|
||||
action="action_view_quants"/>
|
||||
</odoo>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
<template id="stock_assets_backend" name="mrp_workorder assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/stock/static/src/js/inventory_report_list_controller.js"></script>
|
||||
<script type="text/javascript" src="/stock/static/src/js/inventory_report_list_view.js"></script>
|
||||
<script type="text/javascript" src="/stock/static/src/js/inventory_singleton_list_controller.js"></script>
|
||||
<script type="text/javascript" src="/stock/static/src/js/inventory_singleton_list_view.js"></script>
|
||||
<script type="text/javascript" src="/stock/static/src/js/inventory_validate_button_controller.js"></script>
|
||||
|
||||
@@ -1,57 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import fields, models, _
|
||||
|
||||
|
||||
class StockQuantityHistory(models.TransientModel):
|
||||
_name = 'stock.quantity.history'
|
||||
_description = 'Stock Quantity History'
|
||||
|
||||
compute_at_date = fields.Selection([
|
||||
('0', 'Current Inventory'),
|
||||
('1', 'At a Specific Date')
|
||||
], default='0', string="Compute", help="Choose to analyze the current inventory or from a specific date in the past.")
|
||||
date = fields.Datetime('Inventory at Date', help="Choose a date to get the inventory at that date", default=fields.Datetime.now)
|
||||
inventory_datetime = fields.Datetime('Inventory at Date',
|
||||
help="Choose a date to get the inventory at that date",
|
||||
default=fields.Datetime.now, oldname='date')
|
||||
|
||||
def open_table(self):
|
||||
self.ensure_one()
|
||||
|
||||
if int(self.compute_at_date):
|
||||
tree_view_id = self.env.ref('stock.view_stock_product_tree').id
|
||||
form_view_id = self.env.ref('stock.product_form_view_procurement_button').id
|
||||
# We pass `to_date` in the context so that `qty_available` will be computed across
|
||||
# moves until date.
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'views': [(tree_view_id, 'tree'), (form_view_id, 'form')],
|
||||
'view_mode': 'tree,form',
|
||||
'name': _('Products'),
|
||||
'res_model': 'product.product',
|
||||
'domain': "[('type', '=', 'product')]",
|
||||
'context': dict(self.env.context, to_date=self.date),
|
||||
}
|
||||
return action
|
||||
else:
|
||||
self = self.with_context(search_default_internal_loc=1)
|
||||
if self.user_has_groups('stock.group_production_lot,stock.group_stock_multi_locations'):
|
||||
# fixme: erase the following condition when it'll be possible to create a new record
|
||||
# from a empty grouped editable list without go through the form view.
|
||||
if self.env['stock.quant'].search_count([
|
||||
('company_id', '=', self.env.user.company_id.id),
|
||||
('location_id.usage', 'in', ['internal', 'transit'])
|
||||
]) > 0:
|
||||
self = self.with_context(
|
||||
search_default_productgroup=1,
|
||||
search_default_locationgroup=1
|
||||
)
|
||||
if not self.user_has_groups('stock.group_stock_multi_locations'):
|
||||
company_user = self.env.user.company_id
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', company_user.id)], limit=1)
|
||||
if warehouse:
|
||||
self = self.with_context(default_location_id=warehouse.lot_stock_id.id)
|
||||
|
||||
# If user have rights to write on quant, we set quants in inventory mode.
|
||||
if self.user_has_groups('stock.group_stock_manager'):
|
||||
self = self.with_context(inventory_mode=True)
|
||||
return self.env['stock.quant']._get_quants_action(extend=True)
|
||||
def open_at_date(self):
|
||||
tree_view_id = self.env.ref('stock.view_stock_product_tree').id
|
||||
form_view_id = self.env.ref('stock.product_form_view_procurement_button').id
|
||||
# We pass `to_date` in the context so that `qty_available` will be computed across
|
||||
# moves until date.
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'views': [(tree_view_id, 'tree'), (form_view_id, 'form')],
|
||||
'view_mode': 'tree,form',
|
||||
'name': _('Products'),
|
||||
'res_model': 'product.product',
|
||||
'domain': "[('type', '=', 'product')]",
|
||||
'context': dict(self.env.context, to_date=self.inventory_datetime),
|
||||
}
|
||||
return action
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_stock_quantity_history" model="ir.ui.view">
|
||||
<field name="name">Inventory Report</field>
|
||||
<field name="name">Inventory Report at Date</field>
|
||||
<field name="model">stock.quantity.history</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Choose your date">
|
||||
<group>
|
||||
<group>
|
||||
<field name="compute_at_date" widget="radio"/>
|
||||
<field name="date" attrs="{'invisible': [('compute_at_date', '=', '0')]}"/>
|
||||
</group>
|
||||
<field name="inventory_datetime"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="open_table" string="Retrieve the Inventory Quantities" type="object" class="btn-primary"/>
|
||||
<button name="open_at_date" string="Confirm" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_stock_quantity_history" model="ir.actions.act_window">
|
||||
<field name="name">Inventory Report</field>
|
||||
<field name="res_model">stock.quantity.history</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_stock_quantity_history"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'default_compute_at_date': '0'}</field>
|
||||
</record>
|
||||
<menuitem id="menu_valuation" name="Inventory Report" parent="stock.menu_warehouse_report" sequence="100" action="action_stock_quantity_history"/>
|
||||
</odoo>
|
||||
|
||||
|
||||
@@ -55,26 +55,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- valuation wizard: current or at date -->
|
||||
<record id="view_stock_quantity_history" model="ir.ui.view">
|
||||
<field name="name">Valuation Report</field>
|
||||
<field name="model">stock.quantity.history</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Choose your date">
|
||||
<group>
|
||||
<group>
|
||||
<field name="compute_at_date" widget="radio"/>
|
||||
<field name="date" attrs="{'invisible': [('compute_at_date', '=', '0')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="open_table" string="Retrieve the inventory valuation" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_inventory_valuation_search" model="ir.ui.view">
|
||||
<field name="name">product.product</field>
|
||||
<field name="model">product.product</field>
|
||||
@@ -94,11 +74,8 @@
|
||||
|
||||
<record id="action_stock_inventory_valuation" model="ir.actions.act_window">
|
||||
<field name="name">Inventory Valuation</field>
|
||||
<field name="res_model">stock.quantity.history</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="stock_account.view_stock_quantity_history"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'default_compute_at_date': '0', 'valuation': True}</field>
|
||||
<field name="res_model">stock.valuation.layer</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<menuitem id="menu_valuation" name="Inventory Valuation" parent="stock.menu_warehouse_report" sequence="110" action="action_stock_inventory_valuation"/>
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
<field name="name">stock.valuation.layer.tree</field>
|
||||
<field name="model">stock.valuation.layer</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree default_order="create_date desc, id desc" create="0" import="0">
|
||||
<tree default_order="create_date desc, id desc" create="0"
|
||||
import="0" js_class="inventory_report_list">
|
||||
<field name="create_date" string="Date" />
|
||||
<field name="product_id" />
|
||||
<field name="quantity" />
|
||||
@@ -54,7 +55,7 @@
|
||||
</record>
|
||||
|
||||
<record id="stock_valuation_layer_search" model="ir.ui.view">
|
||||
<field name="name">stock.valuation.layer.tree</field>
|
||||
<field name="name">stock.valuation.layer.search</field>
|
||||
<field name="model">stock.valuation.layer</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Valuation Layer?">
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, _
|
||||
from odoo import models
|
||||
|
||||
|
||||
class StockQuantityHistory(models.TransientModel):
|
||||
_inherit = 'stock.quantity.history'
|
||||
|
||||
def open_table(self):
|
||||
if not self.env.context.get('valuation'):
|
||||
return super(StockQuantityHistory, self).open_table()
|
||||
|
||||
action = self.env.ref('stock_account.stock_valuation_layer_action').read()[0]
|
||||
if int(self.compute_at_date):
|
||||
action['domain'] = [('create_date', '<=', self.date)]
|
||||
return action
|
||||
else:
|
||||
def open_at_date(self):
|
||||
active_model = self.env.context.get('active_model')
|
||||
if active_model == 'stock.valuation.layer':
|
||||
action = self.env.ref('stock_account.stock_valuation_layer_action').read()[0]
|
||||
action['domain'] = [('create_date', '<=', self.inventory_datetime)]
|
||||
action['display_name'] = str(self.inventory_datetime)
|
||||
return action
|
||||
|
||||
return super(StockQuantityHistory, self).open_at_date()
|
||||
|
||||
Reference in New Issue
Block a user