Add reporting per customer category
issue10488 review331031003
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
* Add reporting per customer category
|
||||
|
||||
Version 6.0.0 - 2021-05-03
|
||||
* Bug fixes (see mercurial logs for details)
|
||||
* Add default lead time for products
|
||||
|
||||
@@ -37,6 +37,9 @@ def register():
|
||||
sale_reporting.Context,
|
||||
sale_reporting.Customer,
|
||||
sale_reporting.CustomerTimeseries,
|
||||
sale_reporting.CustomerCategory,
|
||||
sale_reporting.CustomerCategoryTimeseries,
|
||||
sale_reporting.CustomerCategoryTree,
|
||||
sale_reporting.Product,
|
||||
sale_reporting.ProductTimeseries,
|
||||
sale_reporting.ProductCategory,
|
||||
|
||||
@@ -321,6 +321,199 @@ class CustomerTimeseries(CustomerMixin, AbstractTimeseries, ModelView):
|
||||
__name__ = 'sale.reporting.customer.time_series'
|
||||
|
||||
|
||||
class CustomerCategoryMixin:
|
||||
__slots__ = ()
|
||||
category = fields.Many2One('party.category', "Category")
|
||||
|
||||
@classmethod
|
||||
def _joins(cls):
|
||||
pool = Pool()
|
||||
PartyCategory = pool.get('party.party-party.category')
|
||||
from_item, tables, withs = super()._joins()
|
||||
if 'line.sale.party.party_category' not in tables:
|
||||
party_category = PartyCategory.__table__()
|
||||
tables['line.sale.party.party_category'] = party_category
|
||||
sale = tables['line.sale']
|
||||
from_item = (from_item
|
||||
.join(party_category,
|
||||
condition=sale.party == party_category.party))
|
||||
return from_item, tables, withs
|
||||
|
||||
@classmethod
|
||||
def _columns(cls, tables, withs):
|
||||
party_category = tables['line.sale.party.party_category']
|
||||
return super()._columns(tables, withs) + [
|
||||
party_category.category.as_('category')]
|
||||
|
||||
@classmethod
|
||||
def _column_id(cls, tables, withs):
|
||||
pool = Pool()
|
||||
Category = pool.get('party.category')
|
||||
category = Category.__table__()
|
||||
line = tables['line']
|
||||
party_category = tables['line.sale.party.party_category']
|
||||
# Get a stable number of categories over time
|
||||
# by using a number one order bigger.
|
||||
nb_category = category.select(
|
||||
Power(10, (Ceil(Log(Max(category.id))) + Literal(1))))
|
||||
return Min(line.id * nb_category + party_category.id)
|
||||
|
||||
@classmethod
|
||||
def _group_by(cls, tables, withs):
|
||||
party_category = tables['line.sale.party.party_category']
|
||||
return super()._group_by(tables, withs) + [party_category.category]
|
||||
|
||||
@classmethod
|
||||
def _where(cls, tables, withs):
|
||||
party_category = tables['line.sale.party.party_category']
|
||||
where = super()._where(tables, withs)
|
||||
where &= party_category.category != Null
|
||||
return where
|
||||
|
||||
def get_rec_name(self, name):
|
||||
return self.category.rec_name if self.category else None
|
||||
|
||||
|
||||
class CustomerCategory(CustomerCategoryMixin, Abstract, ModelView):
|
||||
"Sale Reporting per Customer Category"
|
||||
__name__ = 'sale.reporting.customer.category'
|
||||
|
||||
time_series = fields.One2Many(
|
||||
'sale.reporting.customer.category.time_series', 'category',
|
||||
"Time Series")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order.insert(0, ('category', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def _column_id(cls, tables, withs):
|
||||
party_category = tables['line.sale.party.party_category']
|
||||
return party_category.category
|
||||
|
||||
|
||||
class CustomerCategoryTimeseries(
|
||||
CustomerCategoryMixin, AbstractTimeseries, ModelView):
|
||||
"Sale Reporting per Customer Category"
|
||||
__name__ = 'sale.reporting.customer.category.time_series'
|
||||
|
||||
|
||||
class CustomerCategoryTree(ModelSQL, ModelView):
|
||||
"Sale Reporting per Customer Category"
|
||||
__name__ = 'sale.reporting.customer.category.tree'
|
||||
|
||||
name = fields.Function(fields.Char("Name"), 'get_name')
|
||||
parent = fields.Many2One('sale.reporting.customer.category.tree', "Parent")
|
||||
children = fields.One2Many(
|
||||
'sale.reporting.customer.category.tree', 'parent', "Children")
|
||||
revenue = fields.Function(
|
||||
fields.Numeric("Revenue", digits=(16, Eval('currency_digits', 2)),
|
||||
depends=['currency_digits']), 'get_total')
|
||||
|
||||
currency_digits = fields.Function(
|
||||
fields.Integer("Currency Digits"), 'get_currency_digits')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order.insert(0, ('name', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def table_query(cls):
|
||||
pool = Pool()
|
||||
Category = pool.get('party.category')
|
||||
return Category.__table__()
|
||||
|
||||
@classmethod
|
||||
def get_name(cls, categories, name):
|
||||
pool = Pool()
|
||||
Category = pool.get('party.category')
|
||||
categories = Category.browse(categories)
|
||||
return {c.id: c.name for c in categories}
|
||||
|
||||
@classmethod
|
||||
def order_name(cls, tables):
|
||||
pool = Pool()
|
||||
Category = pool.get('party.category')
|
||||
table, _ = tables[None]
|
||||
if 'category' not in tables:
|
||||
category = Category.__table__()
|
||||
tables['category'] = {
|
||||
None: (category, table.id == category.id),
|
||||
}
|
||||
return Category.name.convert_order(
|
||||
'name', tables['category'], Category)
|
||||
|
||||
def time_series_all(self):
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def get_total(cls, categories, names):
|
||||
pool = Pool()
|
||||
ReportingCustomerCategory = pool.get(
|
||||
'sale.reporting.customer.category')
|
||||
table = cls.__table__()
|
||||
reporting_product_category = ReportingCustomerCategory.__table__()
|
||||
cursor = Transaction().connection.cursor()
|
||||
|
||||
categories = cls.search([
|
||||
('parent', 'child_of', [c.id for c in categories]),
|
||||
])
|
||||
ids = [c.id for c in categories]
|
||||
parents = {}
|
||||
reporting_customer_categories = []
|
||||
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)
|
||||
|
||||
where = reduce_ids(reporting_product_category.id, sub_ids)
|
||||
cursor.execute(
|
||||
*reporting_product_category.select(
|
||||
reporting_product_category.id, where=where))
|
||||
reporting_customer_categories.extend(r for r, in cursor)
|
||||
|
||||
result = {}
|
||||
reporting_product_categories = ReportingCustomerCategory.browse(
|
||||
reporting_customer_categories)
|
||||
for name in names:
|
||||
values = dict.fromkeys(ids, 0)
|
||||
values.update(
|
||||
(c.id, getattr(c, name)) for c in reporting_product_categories)
|
||||
result[name] = cls._sum_tree(categories, values, parents)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _sum_tree(cls, categories, values, parents):
|
||||
result = values.copy()
|
||||
categories = set((c.id for c in categories))
|
||||
leafs = categories - set(parents.values())
|
||||
while leafs:
|
||||
for category in leafs:
|
||||
categories.remove(category)
|
||||
parent = parents.get(category)
|
||||
if parent in result:
|
||||
result[parent] += result[category]
|
||||
next_leafs = set(categories)
|
||||
for category in categories:
|
||||
parent = parents.get(category)
|
||||
if not parent:
|
||||
continue
|
||||
if parent in next_leafs and parent in categories:
|
||||
next_leafs.remove(parent)
|
||||
leafs = next_leafs
|
||||
return result
|
||||
|
||||
def get_currency_digits(self, name):
|
||||
pool = Pool()
|
||||
Company = pool.get('company.company')
|
||||
company = Transaction().context.get('company')
|
||||
if company:
|
||||
return Company(company).currency.digits
|
||||
|
||||
|
||||
class ProductMixin(object):
|
||||
__slots__ = ()
|
||||
product = fields.Many2One(
|
||||
|
||||
@@ -46,6 +46,10 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="name">Sales per Customer</field>
|
||||
<field name="res_model">sale.reporting.customer</field>
|
||||
<field name="context_model">sale.reporting.context</field>
|
||||
<field
|
||||
name="domain"
|
||||
eval="If(Eval('active_model') == 'sale.reporting.customer.category.tree', [('customer', 'where', [('categories', 'child_of', Eval('active_ids', []), 'parent')])], [])"
|
||||
pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_customer_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
@@ -67,6 +71,11 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="model" eval="'ir.ui.menu,%s' % ref('menu_reporting_sale')"/>
|
||||
<field name="action" ref="act_reporting_customer"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_reporting_customer_keyword2">
|
||||
<field name="keyword">tree_open</field>
|
||||
<field name="model">sale.reporting.customer.category.tree,-1</field>
|
||||
<field name="action" ref="act_reporting_customer"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_reporting_customer_companies">
|
||||
<field name="name">User in companies</field>
|
||||
@@ -173,6 +182,163 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Customer Category -->
|
||||
|
||||
<record model="ir.ui.view" id="reporting_customer_category_view_tree">
|
||||
<field name="model">sale.reporting.customer.category.tree</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="field_childs">children</field>
|
||||
<field name="name">sale_reporting_customer_category_tree</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_reporting_customer_category_tree">
|
||||
<field name="name">Sales per Customer Category</field>
|
||||
<field name="res_model">sale.reporting.customer.category.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_customer_category_tree_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="reporting_customer_category_view_tree"/>
|
||||
<field name="act_window" ref="act_reporting_customer_category_tree"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_reporting_customer_category_tree_keyword1">
|
||||
<field name="keyword">tree_open</field>
|
||||
<field name="model" eval="'ir.ui.menu,%s' % ref('menu_reporting_sale')"/>
|
||||
<field name="action" ref="act_reporting_customer_category_tree"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_customer_category_view_list">
|
||||
<field name="model">sale.reporting.customer.category</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">sale_reporting_customer_category_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_reporting_customer_category">
|
||||
<field name="name">Sales per Customer Category</field>
|
||||
<field name="res_model">sale.reporting.customer.category</field>
|
||||
<field name="context_model">sale.reporting.context</field>
|
||||
<field
|
||||
name="domain"
|
||||
eval="[('category', 'child_of', Eval('active_ids', []), 'parent')]"
|
||||
pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_customer_category_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="reporting_customer_category_view_list"/>
|
||||
<field name="act_window" ref="act_reporting_customer_category"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_reporting_customer_category_keyword1">
|
||||
<field name="keyword">tree_open</field>
|
||||
<field name="model">sale.reporting.customer.category.tree,-1</field>
|
||||
<field name="action" ref="act_reporting_customer_category"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_reporting_customer_category_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model" search="[('model', '=', 'sale.reporting.customer.category')]"/>
|
||||
<field name="global_p" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.rule" id="rule_reporting_customer_category_companies">
|
||||
<field name="domain"
|
||||
eval="[('company', 'in', Eval('companies', []))]"
|
||||
pyson="1"/>
|
||||
<field name="rule_group" ref="rule_group_reporting_customer_category_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_reporting_customer_category">
|
||||
<field name="model" search="[('model', '=', 'sale.reporting.customer.category')]"/>
|
||||
<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_customer_category_sale">
|
||||
<field name="model" search="[('model', '=', 'sale.reporting.customer.category')]"/>
|
||||
<field name="group" ref="group_sale"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_customer_category_time_series_view_list">
|
||||
<field name="model">sale.reporting.customer.category.time_series</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">sale_reporting_customer_category_time_series_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_customer_category_time_series_view_graph_revenue">
|
||||
<field name="model">sale.reporting.customer.category.time_series</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">sale_reporting_customer_category_time_series_graph_revenue</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_customer_category_time_series_view_graph_number">
|
||||
<field name="model">sale.reporting.customer.category.time_series</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">sale_reporting_customer_category_time_series_graph_number</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_reporting_customer_category_time_series">
|
||||
<field name="name">Sales per Party Category</field>
|
||||
<field name="res_model">sale.reporting.customer.category.time_series</field>
|
||||
<field name="context_model">sale.reporting.context</field>
|
||||
<field
|
||||
name="domain"
|
||||
eval="[('category', '=', Eval('active_id', -1))]"
|
||||
pyson="1"/>
|
||||
<field name="order" eval="[('date', 'DESC')]" pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_customer_category_time_series_list_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="reporting_customer_category_time_series_view_list"/>
|
||||
<field name="act_window" ref="act_reporting_customer_category_time_series"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_customer_category_time_series_list_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="reporting_customer_category_time_series_view_graph_revenue"/>
|
||||
<field name="act_window" ref="act_reporting_customer_category_time_series"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_customer_category_time_series_list_view3">
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="view" ref="reporting_customer_category_time_series_view_graph_number"/>
|
||||
<field name="act_window" ref="act_reporting_customer_category_time_series"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_reporting_customer_category_time_series_list_keyword1">
|
||||
<field name="keyword">tree_open</field>
|
||||
<field name="model">sale.reporting.customer.category,-1</field>
|
||||
<field name="action" ref="act_reporting_customer_category_time_series"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_reporting_customer_category_time_series_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model" search="[('model', '=', 'sale.reporting.customer.category.time_series')]"/>
|
||||
<field name="global_p" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.rule" id="rule_reporting_customer_category_time_series_companies">
|
||||
<field name="domain"
|
||||
eval="[('company', 'in', Eval('companies', []))]"
|
||||
pyson="1"/>
|
||||
<field name="rule_group" ref="rule_group_reporting_customer_category_time_series_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_reporting_customer_category_time_series">
|
||||
<field name="model" search="[('model', '=', 'sale.reporting.customer.category.time_series')]"/>
|
||||
<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_customer_category_time_series_sale">
|
||||
<field name="model" search="[('model', '=', 'sale.reporting.customer.category.time_series')]"/>
|
||||
<field name="group" ref="group_sale"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Product -->
|
||||
|
||||
<record model="ir.ui.view" id="reporting_product_view_list">
|
||||
|
||||
@@ -51,15 +51,30 @@ Create countries::
|
||||
... name="New York", code="US-NY", type='state', country=country_us)
|
||||
>>> new_york.save()
|
||||
|
||||
Create party categories::
|
||||
|
||||
>>> PartyCategory = Model.get('party.category')
|
||||
>>> party_category_root1 = PartyCategory(name="Root1")
|
||||
>>> party_category_root1.save()
|
||||
>>> party_category_child1 = PartyCategory(name="Child1", parent=party_category_root1)
|
||||
>>> party_category_child1.save()
|
||||
>>> party_category_child2 = PartyCategory(name="Child2", parent=party_category_root1)
|
||||
>>> party_category_child2.save()
|
||||
>>> party_category_root2 = PartyCategory(name="Root2")
|
||||
>>> party_category_root2.save()
|
||||
|
||||
Create parties::
|
||||
|
||||
>>> Party = Model.get('party.party')
|
||||
>>> customer1 = Party(name='Customer1')
|
||||
>>> customer1.categories.append(PartyCategory(party_category_child1.id))
|
||||
>>> customer1.categories.append(PartyCategory(party_category_root2.id))
|
||||
>>> address, = customer1.addresses
|
||||
>>> address.country = country_us
|
||||
>>> address.subdivision = california
|
||||
>>> customer1.save()
|
||||
>>> customer2 = Party(name='Customer2')
|
||||
>>> customer2.categories.append(PartyCategory(party_category_child2.id))
|
||||
>>> address, = customer2.addresses
|
||||
>>> address.country = country_us
|
||||
>>> address.subdivision = new_york
|
||||
@@ -159,6 +174,43 @@ Check sale reporting per customer::
|
||||
... (customer2.id, sale2.sale_date.replace(day=1), 1, Decimal('10'))])
|
||||
True
|
||||
|
||||
Check sale reporting per customer categories::
|
||||
|
||||
>>> CustomerCategory = Model.get('sale.reporting.customer.category')
|
||||
>>> CustomerCategoryTimeseries = Model.get(
|
||||
... 'sale.reporting.customer.category.time_series')
|
||||
>>> CustomerCategoryTree = Model.get('sale.reporting.customer.category.tree')
|
||||
>>> with config.set_context(context=context):
|
||||
... reports = CustomerCategory.find([])
|
||||
... time_series = CustomerCategoryTimeseries.find([])
|
||||
... tree = CustomerCategoryTree.find([])
|
||||
>>> len(reports)
|
||||
3
|
||||
>>> with config.set_context(context=context):
|
||||
... sorted((r.category.id, r.number, r.revenue) for r in reports) == \
|
||||
... sorted([(party_category_child1.id, 1, Decimal('30')),
|
||||
... (party_category_root2.id, 1, Decimal('30')),
|
||||
... (party_category_child2.id, 1, Decimal('10'))])
|
||||
True
|
||||
>>> len(time_series)
|
||||
3
|
||||
>>> with config.set_context(context=context):
|
||||
... sorted((r.category.id, r.date, r.number, r.revenue)
|
||||
... for r in time_series) == sorted(
|
||||
... [(party_category_child1.id, sale1.sale_date.replace(day=1), 1, Decimal('30')),
|
||||
... (party_category_root2.id, sale1.sale_date.replace(day=1), 1, Decimal('30')),
|
||||
... (party_category_child2.id, sale2.sale_date.replace(day=1), 1, Decimal('10'))])
|
||||
True
|
||||
>>> len(tree)
|
||||
4
|
||||
>>> with config.set_context(context=context):
|
||||
... sorted((r.name, r.revenue) for r in tree) == sorted([
|
||||
... ('Root1', Decimal('40')),
|
||||
... ('Child1', Decimal('30')),
|
||||
... ('Child2', Decimal('10')),
|
||||
... ('Root2', Decimal('30'))])
|
||||
True
|
||||
|
||||
Check sale reporting per product::
|
||||
|
||||
>>> Product = Model.get('sale.reporting.product')
|
||||
|
||||
@@ -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="category" expand="1"/>
|
||||
<field name="number"/>
|
||||
<field name="revenue" symbol="currency"/>
|
||||
<field name="revenue_trend" expand="1"/>
|
||||
</tree>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?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. -->
|
||||
<graph>
|
||||
<x>
|
||||
<field name="date"/>
|
||||
</x>
|
||||
<y>
|
||||
<field name="number"/>
|
||||
</y>
|
||||
</graph>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?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. -->
|
||||
<graph>
|
||||
<x>
|
||||
<field name="date"/>
|
||||
</x>
|
||||
<y>
|
||||
<field name="revenue"/>
|
||||
</y>
|
||||
</graph>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
<field name="date"/>
|
||||
<field name="number"/>
|
||||
<field name="revenue" symbol="currency"/>
|
||||
</tree>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?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="name" expand="1"/>
|
||||
<field name="revenue"/>
|
||||
</tree>
|
||||
Reference in New Issue
Block a user