Criptomart/red-supermercados-coop#6 stock_valuation_layer_category_groupby: add group_by parent categories
This commit is contained in:
parent
51a53f54ba
commit
9d426ff176
5 changed files with 157 additions and 17 deletions
|
|
@ -2,40 +2,60 @@
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
This module allows grouping by product category in the stock valuation layer reports.
|
This module allows grouping by product category at different hierarchy levels in the stock valuation layer reports.
|
||||||
|
|
||||||
### Problem
|
### Problem
|
||||||
|
|
||||||
By default, the `categ_id` field in `stock.valuation.layer` is a related field without storage (`store=False`). This means it cannot be used for grouping in reports and pivot views, even though it can be used for filtering and searching.
|
By default, the `categ_id` field in `stock.valuation.layer` is a related field without storage (`store=False`). This means it cannot be used for grouping in reports and pivot views, even though it can be used for filtering and searching.
|
||||||
|
|
||||||
|
Additionally, when dealing with hierarchical category structures (e.g., Food > Dairy > Cheese > Cheddar), it's often useful to group by different levels of the hierarchy.
|
||||||
|
|
||||||
### Solution
|
### Solution
|
||||||
|
|
||||||
This module makes the `categ_id` field stored (`store=True`) and indexed, which enables:
|
This module makes the `categ_id` field stored (`store=True`) and indexed, and adds computed fields for each category hierarchy level (up to 4 levels), enabling:
|
||||||
- Grouping by product category in list views
|
- Grouping by product category (full path) in list, pivot, and graph views
|
||||||
- Grouping by product category in pivot views
|
- Grouping by category level 1 (top-level category)
|
||||||
- Grouping by product category in graph views
|
- Grouping by category level 2 (second-level category)
|
||||||
|
- Grouping by category level 3 (third-level category)
|
||||||
|
- Grouping by category level 4 (fourth-level category)
|
||||||
- Better performance when filtering by category
|
- Better performance when filtering by category
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Install the module from the Apps menu
|
1. Install the module from the Apps menu
|
||||||
2. The field will be automatically populated for existing records
|
2. The fields will be automatically populated for existing records
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
After installation:
|
After installation:
|
||||||
1. Go to Inventory > Reporting > Inventory Valuation
|
1. Go to Inventory > Reporting > Inventory Valuation
|
||||||
2. Switch to Pivot or Graph view
|
2. Switch to Pivot or Graph view
|
||||||
3. Click on "Measures" or group options
|
3. Click on "Group By" options
|
||||||
4. You will now see "Product Category" available for grouping
|
4. You will now see multiple category grouping options:
|
||||||
|
- **Product Category**: Groups by the full category (leaf node)
|
||||||
|
- **Category Level 1**: Groups by top-level category
|
||||||
|
- **Category Level 2**: Groups by second-level category
|
||||||
|
- **Category Level 3**: Groups by third-level category
|
||||||
|
- **Category Level 4**: Groups by fourth-level category
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
If you have a category structure like:
|
||||||
|
- Food (Level 1)
|
||||||
|
- Dairy (Level 2)
|
||||||
|
- Cheese (Level 3)
|
||||||
|
- Cheddar (Level 4)
|
||||||
|
|
||||||
|
You can now group your inventory valuation by "Food" (Level 1), "Dairy" (Level 2), "Cheese" (Level 3), or "Cheddar" (Level 4) as needed.
|
||||||
|
|
||||||
## Technical Details
|
## Technical Details
|
||||||
|
|
||||||
The module extends `stock.valuation.layer` model and modifies the `categ_id` field to:
|
The module extends `stock.valuation.layer` model and:
|
||||||
- `store=True`: Store the value in the database
|
- Modifies the `categ_id` field to: `store=True` and `index=True`
|
||||||
- `index=True`: Add database index for better performance
|
- Adds computed stored fields: `categ_level_1_id`, `categ_level_2_id`, `categ_level_3_id`, `categ_level_4_id`
|
||||||
|
- Uses the `parent_path` field from `product.category` to determine hierarchy levels
|
||||||
|
|
||||||
The field remains a related field, so it will automatically update when the product category changes.
|
All fields are indexed for better performance and are automatically updated when product categories change.
|
||||||
|
|
||||||
## Bug Tracker
|
## Bug Tracker
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Odoo Server 16.0\n"
|
"Project-Id-Version: Odoo Server 16.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-12-04 17:24+0000\n"
|
"POT-Creation-Date: 2025-12-10 17:24+0000\n"
|
||||||
"PO-Revision-Date: 2025-12-04 17:24+0000\n"
|
"PO-Revision-Date: 2025-12-10 17:24+0000\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
|
@ -21,6 +21,30 @@ msgstr ""
|
||||||
msgid "Product Category"
|
msgid "Product Category"
|
||||||
msgstr "Categoría de producto"
|
msgstr "Categoría de producto"
|
||||||
|
|
||||||
|
#. module: stock_valuation_layer_category_groupby
|
||||||
|
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__categ_level_1_id
|
||||||
|
#: model_terms:ir.ui.view,arch_db:stock_valuation_layer_category_groupby.view_inventory_valuation_search_category
|
||||||
|
msgid "Category Level 1"
|
||||||
|
msgstr "Categoría Nivel 1"
|
||||||
|
|
||||||
|
#. module: stock_valuation_layer_category_groupby
|
||||||
|
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__categ_level_2_id
|
||||||
|
#: model_terms:ir.ui.view,arch_db:stock_valuation_layer_category_groupby.view_inventory_valuation_search_category
|
||||||
|
msgid "Category Level 2"
|
||||||
|
msgstr "Categoría Nivel 2"
|
||||||
|
|
||||||
|
#. module: stock_valuation_layer_category_groupby
|
||||||
|
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__categ_level_3_id
|
||||||
|
#: model_terms:ir.ui.view,arch_db:stock_valuation_layer_category_groupby.view_inventory_valuation_search_category
|
||||||
|
msgid "Category Level 3"
|
||||||
|
msgstr "Categoría Nivel 3"
|
||||||
|
|
||||||
|
#. module: stock_valuation_layer_category_groupby
|
||||||
|
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__categ_level_4_id
|
||||||
|
#: model_terms:ir.ui.view,arch_db:stock_valuation_layer_category_groupby.view_inventory_valuation_search_category
|
||||||
|
msgid "Category Level 4"
|
||||||
|
msgstr "Categoría Nivel 4"
|
||||||
|
|
||||||
#. module: stock_valuation_layer_category_groupby
|
#. module: stock_valuation_layer_category_groupby
|
||||||
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__smart_search
|
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__smart_search
|
||||||
msgid "Smart Search"
|
msgid "Smart Search"
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Odoo Server 16.0\n"
|
"Project-Id-Version: Odoo Server 16.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-12-04 17:24+0000\n"
|
"POT-Creation-Date: 2025-12-10 17:24+0000\n"
|
||||||
"PO-Revision-Date: 2025-12-04 17:24+0000\n"
|
"PO-Revision-Date: 2025-12-10 17:24+0000\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
|
@ -21,6 +21,30 @@ msgstr ""
|
||||||
msgid "Product Category"
|
msgid "Product Category"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: stock_valuation_layer_category_groupby
|
||||||
|
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__categ_level_1_id
|
||||||
|
#: model_terms:ir.ui.view,arch_db:stock_valuation_layer_category_groupby.view_inventory_valuation_search_category
|
||||||
|
msgid "Category Level 1"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: stock_valuation_layer_category_groupby
|
||||||
|
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__categ_level_2_id
|
||||||
|
#: model_terms:ir.ui.view,arch_db:stock_valuation_layer_category_groupby.view_inventory_valuation_search_category
|
||||||
|
msgid "Category Level 2"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: stock_valuation_layer_category_groupby
|
||||||
|
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__categ_level_3_id
|
||||||
|
#: model_terms:ir.ui.view,arch_db:stock_valuation_layer_category_groupby.view_inventory_valuation_search_category
|
||||||
|
msgid "Category Level 3"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: stock_valuation_layer_category_groupby
|
||||||
|
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__categ_level_4_id
|
||||||
|
#: model_terms:ir.ui.view,arch_db:stock_valuation_layer_category_groupby.view_inventory_valuation_search_category
|
||||||
|
msgid "Category Level 4"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. module: stock_valuation_layer_category_groupby
|
#. module: stock_valuation_layer_category_groupby
|
||||||
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__smart_search
|
#: model:ir.model.fields,field_description:stock_valuation_layer_category_groupby.field_stock_valuation_layer__smart_search
|
||||||
msgid "Smart Search"
|
msgid "Smart Search"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright 2025 Criptomart
|
# Copyright 2025 Criptomart
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class StockValuationLayer(models.Model):
|
class StockValuationLayer(models.Model):
|
||||||
|
|
@ -9,3 +9,55 @@ class StockValuationLayer(models.Model):
|
||||||
|
|
||||||
# Make categ_id stored to allow grouping in reports
|
# Make categ_id stored to allow grouping in reports
|
||||||
categ_id = fields.Many2one(store=True, index=True)
|
categ_id = fields.Many2one(store=True, index=True)
|
||||||
|
|
||||||
|
# Category hierarchy levels for grouping
|
||||||
|
categ_level_1_id = fields.Many2one(
|
||||||
|
comodel_name="product.category",
|
||||||
|
string="Category Level 1",
|
||||||
|
compute="_compute_category_levels",
|
||||||
|
store=True,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
categ_level_2_id = fields.Many2one(
|
||||||
|
comodel_name="product.category",
|
||||||
|
string="Category Level 2",
|
||||||
|
compute="_compute_category_levels",
|
||||||
|
store=True,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
categ_level_3_id = fields.Many2one(
|
||||||
|
comodel_name="product.category",
|
||||||
|
string="Category Level 3",
|
||||||
|
compute="_compute_category_levels",
|
||||||
|
store=True,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
categ_level_4_id = fields.Many2one(
|
||||||
|
comodel_name="product.category",
|
||||||
|
string="Category Level 4",
|
||||||
|
compute="_compute_category_levels",
|
||||||
|
store=True,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("categ_id", "categ_id.parent_path")
|
||||||
|
def _compute_category_levels(self):
|
||||||
|
"""Compute category hierarchy levels for better grouping options."""
|
||||||
|
for layer in self:
|
||||||
|
if not layer.categ_id or not layer.categ_id.parent_path:
|
||||||
|
layer.categ_level_1_id = False
|
||||||
|
layer.categ_level_2_id = False
|
||||||
|
layer.categ_level_3_id = False
|
||||||
|
layer.categ_level_4_id = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
# parent_path format: "1/2/3/" where each number is a category ID
|
||||||
|
path_ids = [
|
||||||
|
int(x) for x in layer.categ_id.parent_path.split("/") if x.isdigit()
|
||||||
|
]
|
||||||
|
|
||||||
|
# Assign levels based on hierarchy
|
||||||
|
layer.categ_level_1_id = path_ids[0] if len(path_ids) >= 1 else False
|
||||||
|
layer.categ_level_2_id = path_ids[1] if len(path_ids) >= 2 else False
|
||||||
|
layer.categ_level_3_id = path_ids[2] if len(path_ids) >= 3 else False
|
||||||
|
layer.categ_level_4_id = path_ids[3] if len(path_ids) >= 4 else False
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,26 @@
|
||||||
name="group_by_categ_id"
|
name="group_by_categ_id"
|
||||||
context="{'group_by': 'categ_id'}"
|
context="{'group_by': 'categ_id'}"
|
||||||
/>
|
/>
|
||||||
|
<filter
|
||||||
|
string="Category Level 1"
|
||||||
|
name="group_by_categ_level_1"
|
||||||
|
context="{'group_by': 'categ_level_1_id'}"
|
||||||
|
/>
|
||||||
|
<filter
|
||||||
|
string="Category Level 2"
|
||||||
|
name="group_by_categ_level_2"
|
||||||
|
context="{'group_by': 'categ_level_2_id'}"
|
||||||
|
/>
|
||||||
|
<filter
|
||||||
|
string="Category Level 3"
|
||||||
|
name="group_by_categ_level_3"
|
||||||
|
context="{'group_by': 'categ_level_3_id'}"
|
||||||
|
/>
|
||||||
|
<filter
|
||||||
|
string="Category Level 4"
|
||||||
|
name="group_by_categ_level_4"
|
||||||
|
context="{'group_by': 'categ_level_4_id'}"
|
||||||
|
/>
|
||||||
</filter>
|
</filter>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue