changeset 3144fd7d8485 in modules/sale:default
details: https://hg.tryton.org/modules/sale?cmd=changeset&node=3144fd7d8485
description:
Add sale reporting per region
issue11669
review423711003
diffstat:
CHANGELOG | 1 +
__init__.py | 6 +-
doc/design.rst | 1 +
sale_reporting.py | 151 ++++++++++++++++++++++++++++++++--
sale_reporting.xml | 78 ++++++++++++-----
tests/scenario_sale_reporting.rst | 20 +++-
view/sale_reporting_country_tree.xml | 9 ++
view/sale_reporting_region_tree.xml | 4 +-
8 files changed, 226 insertions(+), 44 deletions(-)
diffs (429 lines):
diff -r abb45176fb28 -r 3144fd7d8485 CHANGELOG
--- a/CHANGELOG Sun Sep 11 01:29:22 2022 +0200
+++ b/CHANGELOG Mon Sep 12 21:31:32 2022 +0200
@@ -1,3 +1,4 @@
+* Add sale reporting per region
* Support warehouse pickup
* Use MultiValue for product lead time
* Remove CompanyValueMixin for default lead time
diff -r abb45176fb28 -r 3144fd7d8485 __init__.py
--- a/__init__.py Sun Sep 11 01:29:22 2022 +0200
+++ b/__init__.py Mon Sep 12 21:31:32 2022 +0200
@@ -47,11 +47,12 @@
sale_reporting.ProductCategory,
sale_reporting.ProductCategoryTimeseries,
sale_reporting.ProductCategoryTree,
+ sale_reporting.RegionTree,
sale_reporting.Country,
sale_reporting.CountryTimeseries,
sale_reporting.Subdivision,
sale_reporting.SubdivisionTimeseries,
- sale_reporting.Region,
+ sale_reporting.CountryTree,
invoice.Invoice,
invoice.Line,
module='sale', type_='model')
@@ -63,7 +64,8 @@
sale.ModifyHeader,
party.Replace,
party.Erase,
- sale_reporting.OpenRegion,
+ sale_reporting.OpenRegionTree,
+ sale_reporting.OpenCountryTree,
module='sale', type_='wizard')
Pool.register(
sale.SaleReport,
diff -r abb45176fb28 -r 3144fd7d8485 doc/design.rst
--- a/doc/design.rst Sun Sep 11 01:29:22 2022 +0200
+++ b/doc/design.rst Mon Sep 12 21:31:32 2022 +0200
@@ -209,6 +209,7 @@
The sales reporting that is done *By Region* shows sales based on where the
customer is located.
This is done by combining together in a tree structure the sales
+`By Regions <country:country.region>` which can be opened to the sales
`By Country <model-sale.reporting.country>` below which are the sales
`By Subdivision <model-sale.reporting.country.subdivision>`.
diff -r abb45176fb28 -r 3144fd7d8485 sale_reporting.py
--- a/sale_reporting.py Sun Sep 11 01:29:22 2022 +0200
+++ b/sale_reporting.py Mon Sep 12 21:31:32 2022 +0200
@@ -866,6 +866,138 @@
return where
+class RegionTree(ModelSQL, ModelView):
+ "Sale Reporting per Region"
+ __name__ = 'sale.reporting.region.tree'
+
+ name = fields.Function(fields.Char("Name"), 'get_name')
+ parent = fields.Many2One('sale.reporting.region.tree', "Parent")
+ subregions = fields.One2Many(
+ 'sale.reporting.region.tree', 'parent', "Subregions")
+
+ revenue = fields.Function(Monetary(
+ "Revenue", digits='currency'), 'get_total')
+
+ currency = fields.Function(fields.Many2One(
+ 'currency.currency', "Currency"), 'get_currency')
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls._order.insert(0, ('name', 'ASC'))
+
+ @classmethod
+ def table_query(cls):
+ pool = Pool()
+ Region = pool.get('country.region')
+ return Region.__table__()
+
+ @classmethod
+ def get_name(cls, regions, name):
+ pool = Pool()
+ Region = pool.get('country.region')
+ regions = Region.browse(regions)
+ return {r.id: r.name for r in regions}
+
+ @classmethod
+ def order_name(cls, tables):
+ pool = Pool()
+ Region = pool.get('country.region')
+ table, _ = tables[None]
+ if 'region' not in tables:
+ region = Region.__table__()
+ tables['region'] = {
+ None: (region, table.id == region.id),
+ }
+ return Region.name.convert_order(
+ 'name', tables['region'], Region)
+
+ @classmethod
+ def get_total(cls, regions, names):
+ pool = Pool()
+ ReportingCountry = pool.get('sale.reporting.country')
+ table = cls.__table__()
+ cursor = Transaction().connection.cursor()
+
+ regions = cls.search([
+ ('parent', 'child_of', [r.id for r in regions]),
+ ])
+ ids = [c.id for c in regions]
+ parents = {}
+ reporting_countries = []
+ for sub_ids in grouped_slice(ids):
+ sub_ids = list(sub_ids)
+ where = reduce_ids(table.id, sub_ids)
+ cursor.execute(*table.select(table.id, table.parent, where=where))
+ parents.update(cursor)
+
+ result = {}
+ reporting_countries = ReportingCountry.search([
+ ('country.region', 'in', [r.id for r in regions]),
+ ])
+ for name in names:
+ values = dict.fromkeys(ids, 0)
+ for reporting_country in reporting_countries:
+ values[reporting_country.country.region.id] += (
+ getattr(reporting_country, name))
+ result[name] = cls._sum_tree(regions, values, parents)
+ return result
+
+ @classmethod
+ def _sum_tree(cls, regions, values, parents):
+ result = values.copy()
+ regions = set((c.id for c in regions))
+ leafs = regions - set(parents.values())
+ while leafs:
+ for region in leafs:
+ regions.remove(region)
+ parent = parents.get(region)
+ if parent in result:
+ result[parent] += result[region]
+ next_leafs = set(regions)
+ for region in regions:
+ parent = parents.get(region)
+ if not parent:
+ continue
+ if parent in next_leafs and parent in regions:
+ next_leafs.remove(parent)
+ leafs = next_leafs
+ return result
+
+ def get_currency(self, name):
+ pool = Pool()
+ Company = pool.get('company.company')
+ company = Transaction().context.get('company')
+ if company:
+ return Company(company).currency.id
+
+
+class OpenRegionTree(Wizard):
+ "Open Region"
+ __name__ = 'sale.reporting.region.tree.open'
+
+ start = StateAction('sale.act_reporting_country_tree')
+
+ def do_start(self, action):
+ pool = Pool()
+ Country = pool.get('country.country')
+ CountryTree = pool.get('sale.reporting.country.tree')
+ countries = Country.search([
+ ('region', 'child_of', [r.id for r in self.records], 'parent'),
+ ])
+ ids = [
+ CountryTree.union_shard(c.id, 'sale.reporting.country')
+ for c in countries]
+ data = {
+ 'ids': ids,
+ }
+ name_suffix = ', '.join(r.rec_name for r in self.records[:5])
+ if len(self.records) > 5:
+ name_suffix += ',...'
+ action['name'] += ' (%s)' % name_suffix
+ return action, data
+
+
class Country(CountryMixin, Abstract):
"Sale Reporting per Country"
__name__ = 'sale.reporting.country'
@@ -944,13 +1076,14 @@
__name__ = 'sale.reporting.country.subdivision.time_series'
-class Region(UnionMixin, Abstract, ModelView):
- "Sale Reporting per Region"
- __name__ = 'sale.reporting.region'
+class CountryTree(UnionMixin, Abstract, ModelView):
+ "Sale Reporting per Country"
+ __name__ = 'sale.reporting.country.tree'
region = fields.Function(fields.Char("Region"), 'get_rec_name')
- parent = fields.Many2One('sale.reporting.region', "Parent")
- children = fields.One2Many('sale.reporting.region', 'parent', "Children")
+ parent = fields.Many2One('sale.reporting.country.tree', "Parent")
+ children = fields.One2Many(
+ 'sale.reporting.country.tree', 'parent', "Children")
@classmethod
def union_models(cls):
@@ -958,7 +1091,7 @@
@classmethod
def union_column(cls, name, field, table, Model):
- column = super(Region, cls).union_column(
+ column = super().union_column(
name, field, table, Model)
if (name == 'parent'
and Model.__name__ == 'sale.reporting.country.subdivision'):
@@ -985,9 +1118,9 @@
return record.time_series
-class OpenRegion(Wizard):
- "Open Region"
- __name__ = 'sale.reporting.region.open'
+class OpenCountryTree(Wizard):
+ "Open Country"
+ __name__ = 'sale.reporting.country.tree.open'
start = StateTransition()
country = StateAction('sale.act_reporting_country_time_series')
diff -r abb45176fb28 -r 3144fd7d8485 sale_reporting.xml
--- a/sale_reporting.xml Sun Sep 11 01:29:22 2022 +0200
+++ b/sale_reporting.xml Mon Sep 12 21:31:32 2022 +0200
@@ -802,51 +802,81 @@
<!-- Region -->
- <record model="ir.ui.view" id="reporting_region_view_tree">
- <field name="model">sale.reporting.region</field>
+ <record model="ir.ui.view" id="reporting_region_tree_view_tree">
+ <field name="model">sale.reporting.region.tree</field>
<field name="type">tree</field>
+ <field name="field_childs">subregions</field>
<field name="name">sale_reporting_region_tree</field>
- <field name="field_childs">children</field>
</record>
- <record model="ir.action.act_window" id="act_reporting_region">
+ <record model="ir.action.act_window" id="act_reporting_region_tree">
<field name="name">Sales per Region</field>
- <field name="res_model">sale.reporting.region</field>
+ <field name="res_model">sale.reporting.region.tree</field>
<field name="context_model">sale.reporting.context</field>
<field name="domain" eval="[('parent', '=', None)]" pyson="1"/>
</record>
- <record model="ir.action.act_window.view"
id="act_reporting_region_view1">
+ <record model="ir.action.act_window.view"
id="act_reporting_region_tree_view1">
<field name="sequence" eval="10"/>
- <field name="view" ref="reporting_region_view_tree"/>
- <field name="act_window" ref="act_reporting_region"/>
+ <field name="view" ref="reporting_region_tree_view_tree"/>
+ <field name="act_window" ref="act_reporting_region_tree"/>
</record>
- <record model="ir.action.keyword" id="act_reporting_country_keyword1">
+ <record model="ir.action.keyword"
id="act_reporting_region_tree_keyword1">
<field name="keyword">tree_open</field>
<field name="model" ref="menu_reporting_sale"/>
- <field name="action" ref="act_reporting_region"/>
+ <field name="action" ref="act_reporting_region_tree"/>
+ </record>
+
+ <record model="ir.action.wizard"
id="wizard_reporting_region_tree_open">
+ <field name="name">Open Region</field>
+ <field name="wiz_name">sale.reporting.region.tree.open</field>
+ <field name="model">sale.reporting.region.tree</field>
+ </record>
+ <record model="ir.action.keyword"
id="wizard_reporting_region_tree_open_keyword1">
+ <field name="keyword">tree_open</field>
+ <field name="model">sale.reporting.region.tree,-1</field>
+ <field name="action" ref="wizard_reporting_region_tree_open"/>
</record>
- <record model="ir.rule.group"
id="rule_group_reporting_region_companies">
+ <record model="ir.ui.view" id="reporting_country_tree_view_tree">
+ <field name="model">sale.reporting.country.tree</field>
+ <field name="type">tree</field>
+ <field name="name">sale_reporting_country_tree</field>
+ <field name="field_childs">children</field>
+ </record>
+
+ <record model="ir.action.act_window" id="act_reporting_country_tree">
+ <field name="name">Sales per Country</field>
+ <field name="res_model">sale.reporting.country.tree</field>
+ <field name="context_model">sale.reporting.context</field>
+ <field name="domain" eval="[('parent', '=', None), ('id', 'in',
Eval('active_ids', []))]" pyson="1"/>
+ </record>
+ <record model="ir.action.act_window.view"
id="act_reporting_country_tree_view1">
+ <field name="sequence" eval="10"/>
+ <field name="view" ref="reporting_country_tree_view_tree"/>
+ <field name="act_window" ref="act_reporting_country_tree"/>
+ </record>
+
+ <record model="ir.rule.group"
id="rule_group_reporting_country_tree_companies">
<field name="name">User in companies</field>
- <field name="model" search="[('model', '=',
'sale.reporting.region')]"/>
+ <field name="model" search="[('model', '=',
'sale.reporting.country.tree')]"/>
<field name="global_p" eval="True"/>
</record>
- <record model="ir.rule" id="rule_reporting_region_companies">
+ <record model="ir.rule" id="rule_reporting_country_tree_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
- <field name="rule_group"
ref="rule_group_reporting_region_companies"/>
+ <field name="rule_group"
ref="rule_group_reporting_country_tree_companies"/>
</record>
- <record model="ir.model.access" id="access_reporting_region">
- <field name="model" search="[('model', '=',
'sale.reporting.region')]"/>
+ <record model="ir.model.access" id="access_reporting_country_tree">
+ <field name="model" search="[('model', '=',
'sale.reporting.country.tree')]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
- <record model="ir.model.access" id="access_reporting_region_sale">
- <field name="model" search="[('model', '=',
'sale.reporting.region')]"/>
+ <record model="ir.model.access"
id="access_reporting_country_tree_sale">
+ <field name="model" search="[('model', '=',
'sale.reporting.country.tree')]"/>
<field name="group" ref="group_sale"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
@@ -997,15 +1027,15 @@
<field name="perm_delete" eval="False"/>
</record>
- <record model="ir.action.wizard" id="wizard_reporting_region_open">
+ <record model="ir.action.wizard"
id="wizard_reporting_country_tree_open">
<field name="name">Open Region</field>
- <field name="wiz_name">sale.reporting.region.open</field>
- <field name="model">sale.reporting.region</field>
+ <field name="wiz_name">sale.reporting.country.tree.open</field>
+ <field name="model">sale.reporting.country.tree</field>
</record>
- <record model="ir.action.keyword"
id="wizard_reporting_region_open_keyword1">
+ <record model="ir.action.keyword"
id="wizard_reporting_country_tree_open_keyword1">
<field name="keyword">tree_open</field>
- <field name="model">sale.reporting.region,-1</field>
- <field name="action" ref="wizard_reporting_region_open"/>
+ <field name="model">sale.reporting.country.tree,-1</field>
+ <field name="action" ref="wizard_reporting_country_tree_open"/>
</record>
</data>
diff -r abb45176fb28 -r 3144fd7d8485 tests/scenario_sale_reporting.rst
--- a/tests/scenario_sale_reporting.rst Sun Sep 11 01:29:22 2022 +0200
+++ b/tests/scenario_sale_reporting.rst Mon Sep 12 21:31:32 2022 +0200
@@ -39,9 +39,11 @@
Create countries::
+ >>> Region = Model.get('country.region')
>>> Country = Model.get('country.country')
>>> Subdivision = Model.get('country.subdivision')
- >>> country_us = Country(name="United States")
+ >>> north_america, = Region.find([('code_numeric', '=', '021')])
+ >>> country_us = Country(name="United States", region=north_america)
>>> country_us.save()
>>> california = Subdivision(
... name="California", type='state', country=country_us)
@@ -279,20 +281,26 @@
... ('Account Category', Decimal('40'))])
True
-Check sale reporting per regions::
+Check sale reporting per countries::
- >>> Region = Model.get('sale.reporting.region')
+ >>> RegionTree = Model.get('sale.reporting.region.tree')
+ >>> CountryTree = Model.get('sale.reporting.country.tree')
>>> CountryTimeseries = Model.get('sale.reporting.country.time_series')
>>> SubdivisionTimeseries = Model.get(
... 'sale.reporting.country.subdivision.time_series')
>>> with config.set_context(context=context):
- ... reports = Region.find([])
+ ... region = RegionTree(north_america.id)
+ ... countries = CountryTree.find([])
... country_time_series = CountryTimeseries.find([])
... subdivision_time_series = SubdivisionTimeseries.find([])
- >>> len(reports)
+ >>> region.revenue == Decimal('40')
+ True
+ >>> region.parent.revenue == Decimal('40')
+ True
+ >>> len(countries)
3
>>> with config.set_context(context=context):
- ... sorted((r.region, r.number, r.revenue) for r in reports) == \
+ ... sorted((c.region, c.number, c.revenue) for c in countries) == \
... sorted([('United States', 2, Decimal('40')),
... ('California', 1, Decimal('30')),
... ('New York', 1, Decimal('10'))])
diff -r abb45176fb28 -r 3144fd7d8485 view/sale_reporting_country_tree.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/view/sale_reporting_country_tree.xml Mon Sep 12 21:31:32 2022 +0200
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tree keyword_open="1">
+ <field name="region" expand="1"/>
+ <field name="number"/>
+ <field name="revenue"/>
+ <field name="revenue_trend" expand="1"/>
+</tree>
diff -r abb45176fb28 -r 3144fd7d8485 view/sale_reporting_region_tree.xml
--- a/view/sale_reporting_region_tree.xml Sun Sep 11 01:29:22 2022 +0200
+++ b/view/sale_reporting_region_tree.xml Mon Sep 12 21:31:32 2022 +0200
@@ -2,8 +2,6 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree keyword_open="1">
- <field name="region" expand="1"/>
- <field name="number"/>
+ <field name="name" expand="1"/>
<field name="revenue"/>
- <field name="revenue_trend" expand="1"/>
</tree>