[IMP] stock{_account}: inventory => locations report update
Inventory report has been updated to have: - better applicability: report renamed to "Locations" and is only visible w/Locations, Consignment, or debug mode active - improved UX: list view rearranged, buttons added, grouping removed, sums added to bottom of list - improved quant "Value": instead of the accounting value, this is now the average unit cost (i.e. sum(valuation layer values)/sum(valuation layer quantities) per product x on hand qty (of quant). - single click load for products - search based on Warehouse option (including storing location.warehouse_id to avoid overly complex search function) - always show "Location" column in this view even if multi-locations is not active "inventory report" part of b2b task: 2882539 Upgrade PR: odoo/upgrade#3819 Part-of: odoo/odoo#97109
This commit is contained in:
@@ -21,9 +21,9 @@
|
||||
<field name="model">stock.quant</field>
|
||||
<field name="inherit_id" ref="stock.view_stock_quant_tree_editable"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='inventory_quantity_auto_apply']" position="before">
|
||||
<xpath expr="//field[@name='lot_id']" position="after">
|
||||
<field name="use_expiration_date" invisible="1"/>
|
||||
<field name="removal_date" optional="show"
|
||||
<field name="removal_date" optional="hide"
|
||||
invisible="context.get('hide_removal_date')" attrs="{'readonly': ['|', ('tracking', '=', 'none'), ('use_expiration_date', '=', False)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
@@ -90,7 +90,7 @@ class Location(models.Model):
|
||||
last_inventory_date = fields.Date("Last Effective Inventory", readonly=True, help="Date of the last inventory at this location.")
|
||||
next_inventory_date = fields.Date("Next Expected Inventory", compute="_compute_next_inventory_date", store=True, help="Date for next planned inventory based on cyclic schedule.")
|
||||
warehouse_view_ids = fields.One2many('stock.warehouse', 'view_location_id', readonly=True)
|
||||
warehouse_id = fields.Many2one('stock.warehouse', compute='_compute_warehouse_id')
|
||||
warehouse_id = fields.Many2one('stock.warehouse', compute='_compute_warehouse_id', store=True)
|
||||
storage_category_id = fields.Many2one('stock.storage.category', string='Storage Category', check_company=True)
|
||||
outgoing_move_line_ids = fields.One2many('stock.move.line', 'location_id') # used to compute weight
|
||||
incoming_move_line_ids = fields.One2many('stock.move.line', 'location_dest_id') # used to compute weight
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import logging
|
||||
|
||||
from ast import literal_eval
|
||||
from psycopg2 import Error
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
@@ -67,6 +68,7 @@ class StockQuant(models.Model):
|
||||
'stock.location', 'Location',
|
||||
domain=lambda self: self._domain_location_id(),
|
||||
auto_join=True, ondelete='restrict', required=True, index=True, check_company=True)
|
||||
warehouse_id = fields.Many2one('stock.warehouse', related='location_id.warehouse_id')
|
||||
storage_category_id = fields.Many2one(related='location_id.storage_category_id', store=True)
|
||||
cyclic_inventory_frequency = fields.Integer(related='location_id.cyclic_inventory_frequency')
|
||||
lot_id = fields.Many2one(
|
||||
@@ -340,6 +342,11 @@ class StockQuant(models.Model):
|
||||
]
|
||||
return action
|
||||
|
||||
def action_view_orderpoints(self):
|
||||
action = self.env['product.product'].action_view_orderpoints()
|
||||
action['domain'] = [('product_id', '=', self.product_id.id)]
|
||||
return action
|
||||
|
||||
@api.model
|
||||
def action_view_quants(self):
|
||||
self = self.with_context(search_default_internal_loc=1)
|
||||
@@ -923,7 +930,7 @@ class StockQuant(models.Model):
|
||||
company_user = self.env.company
|
||||
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, hide_location=True)
|
||||
self = self.with_context(default_location_id=warehouse.lot_stock_id.id, hide_location=not self.env.context.get('always_show_loc', False))
|
||||
|
||||
# If user have rights to write on quant, we set quants in inventory mode.
|
||||
if self.user_has_groups('stock.group_stock_user'):
|
||||
@@ -945,7 +952,7 @@ class StockQuant(models.Model):
|
||||
ctx['inventory_report_mode'] = True
|
||||
ctx.pop('group_by', None)
|
||||
action = {
|
||||
'name': _('Stock On Hand'),
|
||||
'name': _('Locations'),
|
||||
'view_type': 'tree',
|
||||
'view_mode': 'list,form',
|
||||
'res_model': 'stock.quant',
|
||||
|
||||
@@ -144,6 +144,10 @@ class Warehouse(models.Model):
|
||||
if vals.get('partner_id'):
|
||||
self._update_partner_data(vals['partner_id'], vals.get('company_id'))
|
||||
|
||||
# manually update locations' warehouse since it didn't exist at their creation time
|
||||
view_location_id = self.env['stock.location'].browse(vals.get('view_location_id'))
|
||||
(view_location_id | view_location_id.with_context(active_test=False).child_ids).write({'warehouse_id': warehouse.id})
|
||||
|
||||
self._check_multiwarehouse_group()
|
||||
|
||||
return warehouses
|
||||
|
||||
@@ -44,6 +44,9 @@ var SingletonListController = InventoryReportListController.extend({
|
||||
*/
|
||||
renderButtons: function ($node) {
|
||||
this._super(...arguments);
|
||||
if (this.context.inventory_report_mode) {
|
||||
this.$buttons.find('button.o_button_apply_all').hide();
|
||||
}
|
||||
this.$buttons.on('click', '.o_button_apply_all', this._onApplyAll.bind(this));
|
||||
},
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_mode">tree,kanban,pivot,form</field>
|
||||
<field name="view_id" ref="view_move_line_tree"/>
|
||||
<field name="context">{'search_default_filter_last_12_months': 1, 'search_default_done': 1, 'search_default_groupby_product_id': 1, 'create': 0}</field>
|
||||
<field name="context">{'search_default_filter_last_12_months': 1, 'search_default_done': 1, 'create': 0}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
There's no product move yet
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<button name="action_product_forecast_report" type="object" icon="fa-area-chart" title="Forecast Report" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<field name="visibility_days" optional="hidden"/>
|
||||
<field name="route_id" options="{'no_create': True, 'no_open': True}"/>
|
||||
<button name="action_stock_replenishment_info" type="object" icon="fa-info-circle" title="Resplenishment Information" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<button name="action_stock_replenishment_info" type="object" icon="fa-info-circle" title="Replenishment Information" attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
<field name="trigger" optional="hide"/>
|
||||
<field name="group_id" optional="hide" groups="stock.group_adv_location"/>
|
||||
<field name="product_min_qty" optional="show"/>
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.actions.server" id="action_view_inventory_tree"> <!-- Menu + python -->
|
||||
<field name="name">Inventory</field>
|
||||
<field name="model_id" ref="model_stock_quant"/>
|
||||
<field name="state">code</field>
|
||||
<field name="groups_id" eval="[(4, ref('stock.group_stock_user'))]"/>
|
||||
<field name="code">
|
||||
action = model.action_view_inventory()
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="quant_search_view" model="ir.ui.view">
|
||||
<field name="name">stock.quant.search</field>
|
||||
<field name="model">stock.quant</field>
|
||||
@@ -9,6 +19,7 @@
|
||||
<search string="Quants">
|
||||
<field name="product_id"/>
|
||||
<field name="location_id"/>
|
||||
<field name="warehouse_id"/>
|
||||
<field name="storage_category_id" groups="stock.group_stock_storage_categories" />
|
||||
<field name="user_id"/>
|
||||
<field name="inventory_date"/>
|
||||
@@ -83,11 +94,11 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Inventory Report + Update Quantity (via product) view -->
|
||||
<!-- Location Report view -->
|
||||
<record model="ir.ui.view" id="view_stock_quant_tree_editable">
|
||||
<field name="name">stock.quant.tree.editable</field>
|
||||
<field name="model">stock.quant</field>
|
||||
<field eval="10" name="priority"/>
|
||||
<field eval="5" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="bottom"
|
||||
create="1" edit="1" js_class="singleton_list"
|
||||
@@ -95,13 +106,17 @@
|
||||
<field name="id" invisible="1"/>
|
||||
<field name="tracking" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="product_id" attrs="{'readonly': [('id', '!=', False)]}"
|
||||
readonly="context.get('single_product', False)" force_save="1"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="product_categ_id" optional="hide"/>
|
||||
<field name="location_id" attrs="{'readonly': [('id', '!=', False)]}"
|
||||
invisible="context.get('hide_location', False)"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="storage_category_id" optional="hide"/>
|
||||
<field name="product_id" attrs="{'readonly': [('id', '!=', False)]}"
|
||||
readonly="context.get('single_product', False)" force_save="1"
|
||||
options="{'no_create': True}" widget="many2one"/>
|
||||
<field name="product_categ_id" optional="hide"/>
|
||||
<field name='company_id' groups="base.group_multi_company" optional="hidden"/>
|
||||
<field name="package_id" groups="stock.group_tracking_lot"
|
||||
attrs="{'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="lot_id" groups="stock.group_production_lot"
|
||||
attrs="{
|
||||
'readonly': ['|', ('id', '!=', False), ('tracking', 'not in', ['serial', 'lot'])],
|
||||
@@ -109,15 +124,15 @@
|
||||
}"
|
||||
invisible="context.get('hide_lot', False)"
|
||||
context="{'default_product_id': product_id, 'default_company_id': company_id}"/>
|
||||
<field name="package_id" groups="stock.group_tracking_lot"
|
||||
attrs="{'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="owner_id" groups="stock.group_tracking_owner"
|
||||
attrs="{'readonly': [('id', '!=', False)]}"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="inventory_quantity_auto_apply" string="On Hand Quantity" readonly="0"/>
|
||||
<field name="available_quantity" optional="show"/>
|
||||
<field name="product_uom_id" groups="uom.group_uom"/>
|
||||
<field name='company_id' groups="base.group_multi_company" optional="show"/>
|
||||
<field name="inventory_quantity_auto_apply" string="On Hand Quantity" readonly="0" sum="Total On Hand"/>
|
||||
<button name="%(action_view_inventory_tree)d" title="Inventory Adjustment" type="action" class="btn-link" icon="fa-pencil" context="{'search_default_product_id': product_id, 'default_product_id': product_id}"/>
|
||||
<field name="reserved_quantity" optional="show" sum="Total Reserved"/>
|
||||
<field name="product_uom_id" string="Unit" groups="uom.group_uom"/>
|
||||
<button name="action_view_stock_moves" string="History" type="object" class="btn-link" icon="fa-history"/>
|
||||
<button name="action_view_orderpoints" string="Replenishment" type="object" context="{'default_product_id': product_id, 'search_default_location_id': location_id}" class="btn-link" icon="fa-refresh"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -174,16 +189,15 @@
|
||||
<field name="code">
|
||||
action = model.with_context(
|
||||
search_default_internal_loc=1,
|
||||
search_default_productgroup=1,
|
||||
search_default_locationgroup=1,
|
||||
always_show_loc=1,
|
||||
).action_view_quants()
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="dashboard_open_quants"> <!-- Used in dashboard -->
|
||||
<field name="name">Stock On Hand</field>
|
||||
<field name="context">{'search_default_internal_loc': 1, 'search_default_productgroup':1, 'search_default_locationgroup':1}</field>
|
||||
<field name="name">Locations</field>
|
||||
<field name="res_model">stock.quant</field>
|
||||
<field name="context">{}</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="location_open_quants"> <!-- Used in location -->
|
||||
@@ -322,7 +336,7 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Inventory Adjustments view -->
|
||||
<!-- Inventory Adjustments + Update Quantity (via product) view -->
|
||||
<record model="ir.ui.view" id="view_stock_quant_tree_inventory_editable">
|
||||
<field name="name">stock.quant.inventory.tree.editable</field>
|
||||
<field name="model">stock.quant</field>
|
||||
@@ -384,19 +398,9 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.server" id="action_view_inventory_tree"> <!-- Menu + python -->
|
||||
<field name="name">Inventory</field>
|
||||
<field name="model_id" ref="model_stock_quant"/>
|
||||
<field name="state">code</field>
|
||||
<field name="groups_id" eval="[(4, ref('stock.group_stock_user'))]"/>
|
||||
<field name="code">
|
||||
action = model.action_view_inventory()
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_action_inventory_tree" name="Inventory Adjustments" parent="menu_stock_warehouse_mgmt" sequence="10" action="action_view_inventory_tree" />
|
||||
<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"
|
||||
<menuitem id="menu_valuation" name="Locations"
|
||||
parent="stock.menu_warehouse_report" sequence="150"
|
||||
action="action_view_quants" groups="stock.group_stock_multi_locations,stock.group_tracking_owner"/>
|
||||
action="action_view_quants" groups="stock.group_stock_multi_locations,stock.group_tracking_owner,base.group_no_one"/>
|
||||
</odoo>
|
||||
|
||||
@@ -30,9 +30,9 @@ Dashboard / Reports for Warehouse Management includes:
|
||||
'views/res_config_settings_views.xml',
|
||||
'data/product_data.xml',
|
||||
'views/product_views.xml',
|
||||
'views/stock_quant_views.xml',
|
||||
'views/report_invoice.xml',
|
||||
'views/stock_valuation_layer_views.xml',
|
||||
'views/stock_quant_views.xml',
|
||||
'wizard/stock_request_count.xml',
|
||||
'wizard/stock_valuation_layer_revaluation_views.xml',
|
||||
'report/report_stock_forecasted.xml',
|
||||
|
||||
@@ -19,33 +19,21 @@ class StockQuant(models.Model):
|
||||
|
||||
@api.depends('company_id', 'location_id', 'owner_id', 'product_id', 'quantity')
|
||||
def _compute_value(self):
|
||||
""" For standard and AVCO valuation, compute the current accounting
|
||||
valuation of the quants by multiplying the quantity by
|
||||
the standard price. Instead for FIFO, use the quantity times the
|
||||
average cost (valuation layers are not manage by location so the
|
||||
average cost is the same for all location and the valuation field is
|
||||
a estimation more than a real value).
|
||||
""" (Product.value_svl / Product.quantity_svl) * quant.quantity, i.e. average unit cost * on hand qty
|
||||
"""
|
||||
for quant in self:
|
||||
quant.currency_id = quant.company_id.currency_id
|
||||
# If the user didn't enter a location yet while enconding a quant.
|
||||
if not quant.location_id:
|
||||
quant.value = 0
|
||||
return
|
||||
|
||||
if not quant.location_id._should_be_valued() or\
|
||||
(quant.owner_id and quant.owner_id != quant.company_id.partner_id):
|
||||
if not quant.location_id or not quant.product_id or\
|
||||
not quant.location_id._should_be_valued() or\
|
||||
(quant.owner_id and quant.owner_id != quant.company_id.partner_id) or\
|
||||
float_is_zero(quant.quantity, precision_rounding=quant.product_id.uom_id.rounding):
|
||||
quant.value = 0
|
||||
continue
|
||||
if quant.product_id.cost_method == 'fifo':
|
||||
quantity = quant.product_id.with_company(quant.company_id).quantity_svl
|
||||
if float_is_zero(quantity, precision_rounding=quant.product_id.uom_id.rounding):
|
||||
quant.value = 0.0
|
||||
continue
|
||||
average_cost = quant.product_id.with_company(quant.company_id).value_svl / quantity
|
||||
quant.value = quant.quantity * average_cost
|
||||
else:
|
||||
quant.value = quant.quantity * quant.product_id.with_company(quant.company_id).standard_price
|
||||
quantity = quant.product_id.with_company(quant.company_id).quantity_svl
|
||||
if float_is_zero(quantity, precision_rounding=quant.product_id.uom_id.rounding):
|
||||
quant.value = 0.0
|
||||
continue
|
||||
quant.value = quant.quantity * quant.product_id.with_company(quant.company_id).value_svl / quantity
|
||||
|
||||
@api.model
|
||||
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='product_uom_id']" position="after">
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="value" optional="hidden"/>
|
||||
<field name="value" optional="hidden" sum="Total Value"/>
|
||||
<button name="%(stock_valuation_layer_action)d" title="Stock Valuation" type="action" class="btn-link" icon="fa-bar-chart" context="{'search_default_product_id': product_id}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Reference in New Issue
Block a user