Add reporting

issue6948
review37831002
This commit is contained in:
Cédric Krier committed 2018-03-26 12:51:47 +02:00
1 parent 5f386d9024
commit 9e79ed4ae7
34 files changed
+1804 -1

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Add reporting
* Check pending sale to erase party
* Add wizard to modify sale header after filling lines
* Make description on sale line optional
+2
View File
@@ -6,6 +6,7 @@ Prerequisites
* Python 2.7 or later (http://www.python.org/)
* trytond (http://www.tryton.org/)
* python-dateutil (http://labix.org/python-dateutil)
* python-sql 0.4 or later (http://code.google.com/p/python-sql/)
* trytond_company (http://www.tryton.org/)
* trytond_party (http://www.tryton.org/)
@@ -16,6 +17,7 @@ Prerequisites
* trytond_account_invoice_stock (http://www.tryton.org/)
* trytond_currency (http://www.tryton.org/)
* trytond_account_product (http://www.tryton.org/)
* Optional: pygal (http://pygal.org/)
Installation
------------
+15
View File
@@ -8,6 +8,7 @@ from .stock import *
from .configuration import *
from .invoice import *
from . import party
from . import sale_reporting
def register():
@@ -30,6 +31,19 @@ def register():
Configuration,
ConfigurationSequence,
ConfigurationSaleMethod,
sale_reporting.Context,
sale_reporting.Customer,
sale_reporting.CustomerTimeseries,
sale_reporting.Product,
sale_reporting.ProductTimeseries,
sale_reporting.Category,
sale_reporting.CategoryTimeseries,
sale_reporting.CategoryTree,
sale_reporting.Country,
sale_reporting.CountryTimeseries,
sale_reporting.Subdivision,
sale_reporting.SubdivisionTimeseries,
sale_reporting.Region,
Invoice,
InvoiceLine,
module='sale', type_='model')
@@ -41,6 +55,7 @@ def register():
ModifyHeader,
party.PartyReplace,
party.PartyErase,
sale_reporting.OpenRegion,
module='sale', type_='wizard')
Pool.register(
SaleReport,
+697
View File
@@ -0,0 +1,697 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from collections import defaultdict
from itertools import tee, izip_longest
try:
import pygal
except ImportError:
pygal = None
from dateutil.relativedelta import relativedelta
from sql import Null, Literal, Cast, Column
from sql.aggregate import Sum, Min, Count
from sql.conditionals import Case
from sql.functions import CurrentTimestamp, DateTrunc
from trytond import backend
from trytond.pool import Pool
from trytond.model import ModelSQL, ModelView, UnionMixin, fields
from trytond.tools import grouped_slice, reduce_ids
from trytond.transaction import Transaction
from trytond.pyson import Eval, If
from trytond.wizard import Wizard, StateTransition, StateAction
__all__ = ['Context',
'Customer', 'CustomerTimeseries',
'Product', 'ProductTimeseries',
'Category', 'CategoryTimeseries', 'CategoryTree',
'Country', 'CountryTimeseries',
'Subdivision', 'SubdivisionTimeseries',
'Region', 'OpenRegion']
def pairwise(iterable):
a, b = tee(iterable)
b.next()
return izip_longest(a, b)
class Abstract(ModelSQL):
company = fields.Many2One('company.company', "Company")
number = fields.Integer("#", help="Number of sales")
revenue = fields.Numeric(
"Revenue", digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
revenue_trend = fields.Function(
fields.Char("Revenue Trend"), 'get_trend')
time_series = None
currency_digits = fields.Function(
fields.Integer("Currency Digits"), 'get_currency_digits')
@classmethod
def table_query(cls):
from_item, tables = cls._joins()
return from_item.select(*cls._columns(tables),
where=cls._where(tables),
group_by=cls._group_by(tables))
@classmethod
def _joins(cls):
pool = Pool()
Company = pool.get('company.company')
Currency = pool.get('currency.currency')
Line = pool.get('sale.line')
Sale = pool.get('sale.sale')
tables = {}
tables['line'] = line = Line.__table__()
tables['line.sale'] = sale = Sale.__table__()
tables['line.sale.company'] = company = Company.__table__()
currency_sale = Currency.currency_rate_sql()
tables['currency_sale'] = currency_sale
currency_company = Currency.currency_rate_sql()
tables['currency_company'] = currency_company
from_item = (line
.join(sale, condition=line.sale == sale.id)
.join(currency_sale,
condition=(sale.currency == currency_sale.currency)
& (currency_sale.start_date <= sale.sale_date)
& ((currency_sale.end_date == Null)
| (currency_sale.end_date >= sale.sale_date))
)
.join(company, condition=sale.company == company.id)
.join(currency_company,
condition=(company.currency == currency_company.currency)
& (currency_company.start_date <= sale.sale_date)
& ((currency_company.end_date == Null)
| (currency_company.end_date >= sale.sale_date))
))
return from_item, tables
@classmethod
def _columns(cls, tables):
line = tables['line']
sale = tables['line.sale']
currency_company = tables['currency_company']
currency_sale = tables['currency_sale']
revenue = Cast(
Sum(line.quantity * line.unit_price
* currency_company.rate / currency_sale.rate),
cls.revenue.sql_type().base)
return [
cls._column_id(tables).as_('id'),
Literal(0).as_('create_uid'),
CurrentTimestamp().as_('create_date'),
Cast(Literal(Null), cls.write_uid.sql_type().base
).as_('write_uid'),
Cast(Literal(Null), cls.write_date.sql_type().base
).as_('write_date'),
sale.company.as_('company'),
revenue.as_('revenue'),
Count(sale.id, distinct=True).as_('number'),
]
@classmethod
def _column_id(cls, tables):
line = tables['line']
return Min(line.id)
@classmethod
def _group_by(cls, tables):
sale = tables['line.sale']
return [sale.company]
@classmethod
def _where(cls, tables):
context = Transaction().context
sale = tables['line.sale']
where = sale.company == context.get('company')
where &= sale.state.in_(cls._sale_states())
from_date = context.get('from_date')
if from_date:
where &= sale.sale_date >= from_date
to_date = context.get('to_date')
if to_date:
where &= sale.sale_date <= to_date
warehouse = context.get('warehouse')
if warehouse:
where &= sale.warehouse == warehouse
return where
@classmethod
def _sale_states(cls):
return ['confirmed', 'processing', 'done']
@property
def time_series_all(self):
delta = self._period_delta()
for ts, next_ts in pairwise(self.time_series or []):
yield ts
if delta and next_ts:
date = ts.date + delta
while date < next_ts.date:
yield None
date += delta
@classmethod
def _period_delta(cls):
context = Transaction().context
return {
'year': relativedelta(years=1),
'month': relativedelta(months=1),
'day': relativedelta(days=1),
}.get(context.get('period'))
def get_trend(self, name):
name = name[:-len('_trend')]
if pygal:
chart = pygal.Line()
chart.add('', [getattr(ts, name) if ts else None
for ts in self.time_series_all])
return chart.render_sparktext()
def get_currency_digits(self, name):
return self.company.currency.digits
class AbstractTimeseries(Abstract):
date = fields.Date("Date")
@classmethod
def __setup__(cls):
super(AbstractTimeseries, cls).__setup__()
cls._order = [('date', 'ASC')]
@classmethod
def _columns(cls, tables):
return super(AbstractTimeseries, cls)._columns(tables) + [
cls._column_date(tables).as_('date')]
@classmethod
def _column_date(cls, tables):
context = Transaction().context
sale = tables['line.sale']
date = DateTrunc(context.get('period'), sale.sale_date)
if backend.name() != 'sqlite':
date = Cast(date, cls.date.sql_type().base)
return date
@classmethod
def _group_by(cls, tables):
return super(AbstractTimeseries, cls)._group_by(tables) + [
cls._column_date(tables)]
class Context(ModelView):
"Sale Reporting Context"
__name__ = 'sale.reporting.context'
company = fields.Many2One('company.company', "Company", required=True)
from_date = fields.Date("From Date",
domain=[
If(Eval('to_date') & Eval('from_date'),
('from_date', '<=', Eval('to_date')),
()),
],
depends=['to_date'])
to_date = fields.Date("To Date",
domain=[
If(Eval('from_date') & Eval('to_date'),
('to_date', '>=', Eval('from_date')),
()),
],
depends=['from_date'])
period = fields.Selection([
('year', "Year"),
('month', "Month"),
('day', "Day"),
], "Period", required=True)
warehouse = fields.Many2One(
'stock.location', "Warehouse",
domain=[
('type', '=', 'warehouse'),
])
@classmethod
def default_company(cls):
return Transaction().context.get('company')
@classmethod
def default_from_date(cls):
pool = Pool()
Date = pool.get('ir.date')
context = Transaction().context
if 'from_date' in context:
return context['from_date']
return Date.today() - relativedelta(years=1)
@classmethod
def default_to_date(cls):
pool = Pool()
Date = pool.get('ir.date')
context = Transaction().context
if 'to_date' in context:
return context['to_date']
return Date.today()
@classmethod
def default_period(cls):
return Transaction().context.get('period', 'month')
@classmethod
def default_warehouse(cls):
return Transaction().context.get('warehouse')
class CustomerMixin(object):
customer = fields.Many2One('party.party', "Customer")
@classmethod
def _columns(cls, tables):
sale = tables['line.sale']
return super(CustomerMixin, cls)._columns(tables) + [
sale.party.as_('customer')]
@classmethod
def _group_by(cls, tables):
sale = tables['line.sale']
return super(CustomerMixin, cls)._group_by(tables) + [sale.party]
def get_rec_name(self, name):
return self.customer.rec_name
class Customer(CustomerMixin, Abstract, ModelView):
"Sale Reporting per Customer"
__name__ = 'sale.reporting.customer'
time_series = fields.One2Many(
'sale.reporting.customer.time_series', 'customer', "Time Series")
@classmethod
def _column_id(cls, tables):
sale = tables['line.sale']
return sale.party
class CustomerTimeseries(CustomerMixin, AbstractTimeseries, ModelView):
"Sale Reporting per Customer"
__name__ = 'sale.reporting.customer.time_series'
class ProductMixin(object):
product = fields.Many2One('product.product', "Product")
@classmethod
def _columns(cls, tables):
line = tables['line']
return super(ProductMixin, cls)._columns(tables) + [
line.product.as_('product')]
@classmethod
def _group_by(cls, tables):
line = tables['line']
return super(ProductMixin, cls)._group_by(tables) + [line.product]
@classmethod
def _where(cls, tables):
line = tables['line']
where = super(ProductMixin, cls)._where(tables)
where &= line.product != Null
return where
def get_rec_name(self, name):
return self.product.rec_name if self.product else None
class Product(ProductMixin, Abstract, ModelView):
"Sale Reporting per Product"
__name__ = 'sale.reporting.product'
time_series = fields.One2Many(
'sale.reporting.product.time_series', 'product', "Time Series")
@classmethod
def _column_id(cls, tables):
line = tables['line']
return line.product
class ProductTimeseries(ProductMixin, AbstractTimeseries, ModelView):
"Sale Reporting per Product"
__name__ = 'sale.reporting.product.time_series'
class CategoryMixin(object):
category = fields.Many2One('product.category', "Category")
@classmethod
def _joins(cls):
pool = Pool()
Product = pool.get('product.product')
TemplateCategory = pool.get('product.template-product.category.all')
from_item, tables = super(CategoryMixin, cls)._joins()
if 'line.product' not in tables:
product = Product.__table__()
tables['line.product'] = product
line = tables['line']
from_item = (from_item
.join(product, condition=line.product == product.id))
if 'line.product.template_category' not in tables:
template_category = TemplateCategory.__table__()
tables['line.product.template_category'] = template_category
product = tables['line.product']
from_item = (from_item
.join(template_category,
condition=product.template == template_category.template))
return from_item, tables
@classmethod
def _columns(cls, tables):
template_category = tables['line.product.template_category']
return super(CategoryMixin, cls)._columns(tables) + [
template_category.category.as_('category')]
@classmethod
def _column_id(cls, tables):
line = tables['line']
template_category = tables['line.product.template_category']
# Pairing function from http://szudzik.com/ElegantPairing.pdf
return Min(Case(
(line.id < template_category.id,
(template_category.id * template_category.id) + line.id),
else_=(line.id * line.id) + line.id + template_category.id))
@classmethod
def _group_by(cls, tables):
template_category = tables['line.product.template_category']
return super(CategoryMixin, cls)._group_by(tables) + [
template_category.category]
@classmethod
def _where(cls, tables):
template_category = tables['line.product.template_category']
where = super(CategoryMixin, cls)._where(tables)
where &= template_category.category != Null
return where
def get_rec_name(self, name):
return self.category.rec_name if self.category else None
class Category(CategoryMixin, Abstract, ModelView):
"Sale Reporting per Category"
__name__ = 'sale.reporting.category'
time_series = fields.One2Many(
'sale.reporting.category.time_series', 'category', "Time Series")
@classmethod
def _column_id(cls, tables):
template_category = tables['line.product.template_category']
return template_category.category
class CategoryTimeseries(CategoryMixin, AbstractTimeseries, ModelView):
"Sale Reporting per Category"
__name__ = 'sale.reporting.category.time_series'
class CategoryTree(ModelSQL, ModelView):
"Sale Reporting per Category"
__name__ = 'sale.reporting.category.tree'
name = fields.Function(fields.Char("Name"), 'get_name')
parent = fields.Many2One('sale.reporting.category.tree', "Parent")
children = fields.One2Many(
'sale.reporting.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 table_query(cls):
pool = Pool()
Category = pool.get('product.category')
return Category.__table__()
@classmethod
def get_name(cls, categories, name):
pool = Pool()
Category = pool.get('product.category')
categories = Category.browse(categories)
return {c.id: c.name for c in categories}
def time_series_all(self):
return []
@classmethod
def get_total(cls, categories, names):
pool = Pool()
ReportingCategory = pool.get('sale.reporting.category')
table = cls.__table__()
reporting_category = ReportingCategory.__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_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.fetchall())
where = reduce_ids(reporting_category.id, sub_ids)
cursor.execute(
*reporting_category.select(reporting_category.id, where=where))
reporting_categories.extend(r for r, in cursor.fetchall())
result = {}
reporting_categories = ReportingCategory.browse(reporting_categories)
for name in names:
values = dict.fromkeys(ids, 0)
values.update(
(c.id, getattr(c, name)) for c in reporting_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.itervalues())
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 CountryMixin(object):
country = fields.Many2One('country.country', "Country")
@classmethod
def _joins(cls):
pool = Pool()
Address = pool.get('party.address')
from_item, tables = super(CountryMixin, cls)._joins()
if 'line.sale.shipment_address' not in tables:
address = Address.__table__()
tables['line.sale.shipment_address'] = address
sale = tables['line.sale']
from_item = (from_item
.join(address, condition=sale.shipment_address == address.id))
return from_item, tables
@classmethod
def _columns(cls, tables):
address = tables['line.sale.shipment_address']
return super(CountryMixin, cls)._columns(tables) + [
address.country.as_('country')]
@classmethod
def _group_by(cls, tables):
address = tables['line.sale.shipment_address']
return super(CountryMixin, cls)._group_by(tables) + [address.country]
@classmethod
def _where(cls, tables):
address = tables['line.sale.shipment_address']
where = super(CountryMixin, cls)._where(tables)
where &= address.country != Null
return where
class Country(CountryMixin, Abstract):
"Sale Reporting per Country"
__name__ = 'sale.reporting.country'
time_series = fields.One2Many(
'sale.reporting.country.time_series', 'country', "Time Series")
@classmethod
def _column_id(cls, tables):
address = tables['line.sale.shipment_address']
return address.country
def get_rec_name(self, name):
return self.country.rec_name
class CountryTimeseries(CountryMixin, AbstractTimeseries, ModelView):
"Sale Reporting per Country"
__name__ = 'sale.reporting.country.time_series'
class SubdivisionMixin(CountryMixin):
subdivision = fields.Many2One('country.subdivision', "Subdivision")
@classmethod
def _columns(cls, tables):
address = tables['line.sale.shipment_address']
return super(SubdivisionMixin, cls)._columns(tables) + [
address.subdivision.as_('subdivision')]
@classmethod
def _group_by(cls, tables):
address = tables['line.sale.shipment_address']
return super(SubdivisionMixin, cls)._group_by(tables) + [
address.subdivision]
@classmethod
def _where(cls, tables):
address = tables['line.sale.shipment_address']
where = super(SubdivisionMixin, cls)._where(tables)
where &= address.subdivision != Null
return where
class Subdivision(SubdivisionMixin, Abstract):
"Sale Reporting per Subdivision"
__name__ = 'sale.reporting.country.subdivision'
time_series = fields.One2Many(
'sale.reporting.country.subdivision.time_series', 'subdivision',
"Time Series")
@classmethod
def _column_id(cls, tables):
address = tables['line.sale.shipment_address']
return address.subdivision
def get_rec_name(self, name):
return self.subdivision.rec_name
class SubdivisionTimeseries(SubdivisionMixin, AbstractTimeseries, ModelView):
"Sale Reporting per Subdivision"
__name__ = 'sale.reporting.country.subdivision.time_series'
class Region(UnionMixin, Abstract, ModelView):
"Sale Reporting per Region"
__name__ = 'sale.reporting.region'
region = fields.Function(fields.Char("Region"), 'get_rec_name')
parent = fields.Many2One('sale.reporting.region', "Parent")
children = fields.One2Many('sale.reporting.region', 'parent', "Children")
@classmethod
def union_models(cls):
return ['sale.reporting.country', 'sale.reporting.country.subdivision']
@classmethod
def union_column(cls, name, field, table, Model):
column = super(Region, cls).union_column(
name, field, table, Model)
if (name == 'parent'
and Model.__name__ == 'sale.reporting.country.subdivision'):
column = cls.union_shard(
Column(table, 'country'), 'sale.reporting.country')
return column
@classmethod
def get_rec_name(cls, records, name):
names = {}
classes = defaultdict(list)
for record in records:
record = cls.union_unshard(record.id)
classes[record.__class__].append(record)
for klass, records in classes.iteritems():
for record in klass.browse(records):
names[cls.union_shard(record.id, klass.__name__)] = (
record.rec_name)
return names
@property
def time_series(self):
record = self.union_unshard(self.id)
return record.time_series
class OpenRegion(Wizard):
"Open Region"
__name__ = 'sale.reporting.region.open'
start = StateTransition()
country = StateAction('sale.act_reporting_country_time_series')
subdivision = StateAction(
'sale.act_reporting_country_subdivision_time_series')
def transition_start(self):
pool = Pool()
Region = pool.get('sale.reporting.region')
Country = pool.get('sale.reporting.country')
Subdivision = pool.get('sale.reporting.country.subdivision')
region = Region(Transaction().context['active_id'])
record = Region.union_unshard(region.id)
if isinstance(record, Country):
return 'country'
elif isinstance(record, Subdivision):
return 'subdivision'
def _do_action(self, action):
pool = Pool()
Region = pool.get('sale.reporting.region')
region = Region(Transaction().context['active_id'])
record = Region.union_unshard(region.id)
data = {
'id': record.id,
'ids': [record.id],
}
action['name'] += ' (%s)' % record.rec_name
return action, data
do_country = _do_action
do_subdivision = _do_action
+572
View File
@@ -0,0 +1,572 @@
<?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. -->
<tryton>
<data>
<menuitem name="Reporting" parent="menu_sale"
id="menu_reporting" sequence="100"/>
<menuitem parent="menu_reporting" name="Sales"
id="menu_reporting_sale" icon="tryton-graph"/>
<record model="ir.ui.view" id="reporting_context_view_form">
<field name="model">sale.reporting.context</field>
<field name="type">form</field>
<field name="name">sale_reporting_context_form</field>
</record>
<!-- Customer -->
<record model="ir.ui.view" id="reporting_customer_view_list">
<field name="model">sale.reporting.customer</field>
<field name="type">tree</field>
<field name="name">sale_reporting_customer_list</field>
</record>
<record model="ir.ui.view" id="reporting_customer_view_graph_revenue">
<field name="model">sale.reporting.customer</field>
<field name="type">graph</field>
<field name="name">sale_reporting_customer_graph_revenue</field>
</record>
<record model="ir.ui.view" id="reporting_customer_view_graph_number">
<field name="model">sale.reporting.customer</field>
<field name="type">graph</field>
<field name="name">sale_reporting_customer_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_customer">
<field name="name">Sales per Customer</field>
<field name="res_model">sale.reporting.customer</field>
<field name="context_model">sale.reporting.context</field>
</record>
<record model="ir.action.act_window.view" id="act_reporting_customer_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_customer_view_list"/>
<field name="act_window" ref="act_reporting_customer"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_customer_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_customer_view_graph_revenue"/>
<field name="act_window" ref="act_reporting_customer"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_customer_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_customer_view_graph_number"/>
<field name="act_window" ref="act_reporting_customer"/>
</record>
<record model="ir.action.keyword" id="act_reporting_customer_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"/>
</record>
<record model="ir.model.access" id="access_reporting_customer">
<field name="model" search="[('model', '=', 'sale.reporting.customer')]"/>
<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_sale">
<field name="model" search="[('model', '=', 'sale.reporting.customer')]"/>
<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_time_series_view_list">
<field name="model">sale.reporting.customer.time_series</field>
<field name="type">tree</field>
<field name="name">sale_reporting_customer_time_series_list</field>
</record>
<record model="ir.ui.view" id="reporting_customer_time_series_view_graph_revenue">
<field name="model">sale.reporting.customer.time_series</field>
<field name="type">graph</field>
<field name="name">sale_reporting_customer_time_series_graph_revenue</field>
</record>
<record model="ir.ui.view" id="reporting_customer_time_series_view_graph_number">
<field name="model">sale.reporting.customer.time_series</field>
<field name="type">graph</field>
<field name="name">sale_reporting_customer_time_series_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_customer_time_series">
<field name="name">Sales per Customer</field>
<field name="res_model">sale.reporting.customer.time_series</field>
<field name="context_model">sale.reporting.context</field>
<field
name="domain"
eval="[('customer', '=', Eval('active_id', -1))]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_customer_time_series_list_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_customer_time_series_view_list"/>
<field name="act_window" ref="act_reporting_customer_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_customer_time_series_list_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_customer_time_series_view_graph_revenue"/>
<field name="act_window" ref="act_reporting_customer_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_customer_time_series_list_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_customer_time_series_view_graph_number"/>
<field name="act_window" ref="act_reporting_customer_time_series"/>
</record>
<record model="ir.action.keyword" id="act_reporting_customer_time_series_list_keyword1">
<field name="keyword">tree_open</field>
<field name="model">sale.reporting.customer,-1</field>
<field name="action" ref="act_reporting_customer_time_series"/>
</record>
<record model="ir.model.access" id="access_reporting_customer_time_series">
<field name="model" search="[('model', '=', 'sale.reporting.customer.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_time_series_sale">
<field name="model" search="[('model', '=', 'sale.reporting.customer.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">
<field name="model">sale.reporting.product</field>
<field name="type">tree</field>
<field name="name">sale_reporting_product_list</field>
</record>
<record model="ir.ui.view" id="reporting_product_view_graph_revenue">
<field name="model">sale.reporting.product</field>
<field name="type">graph</field>
<field name="name">sale_reporting_product_graph_revenue</field>
</record>
<record model="ir.ui.view" id="reporting_product_view_graph_number">
<field name="model">sale.reporting.product</field>
<field name="type">graph</field>
<field name="name">sale_reporting_product_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_product">
<field name="name">Sales per Product</field>
<field name="res_model">sale.reporting.product</field>
<field name="context_model">sale.reporting.context</field>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_product_view_list"/>
<field name="act_window" ref="act_reporting_product"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_product_view_graph_revenue"/>
<field name="act_window" ref="act_reporting_product"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_product_view_graph_number"/>
<field name="act_window" ref="act_reporting_product"/>
</record>
<record model="ir.action.keyword" id="act_reporting_product_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_product"/>
</record>
<record model="ir.model.access" id="access_reporting_product">
<field name="model" search="[('model', '=', 'sale.reporting.product')]"/>
<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_product_sale">
<field name="model" search="[('model', '=', 'sale.reporting.product')]"/>
<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_product_time_series_view_list">
<field name="model">sale.reporting.product.time_series</field>
<field name="type">tree</field>
<field name="name">sale_reporting_product_time_series_list</field>
</record>
<record model="ir.ui.view" id="reporting_product_time_series_view_graph_revenue">
<field name="model">sale.reporting.product.time_series</field>
<field name="type">graph</field>
<field name="name">sale_reporting_product_time_series_graph_revenue</field>
</record>
<record model="ir.ui.view" id="reporting_product_time_series_view_graph_number">
<field name="model">sale.reporting.product.time_series</field>
<field name="type">graph</field>
<field name="name">sale_reporting_product_time_series_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_product_time_series">
<field name="name">Sales per Product</field>
<field name="res_model">sale.reporting.product.time_series</field>
<field name="context_model">sale.reporting.context</field>
<field name="domain"
eval="[('product', '=', Eval('active_id', -1))]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_time_series_list_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_product_time_series_view_list"/>
<field name="act_window" ref="act_reporting_product_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_time_series_list_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_product_time_series_view_graph_revenue"/>
<field name="act_window" ref="act_reporting_product_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_time_series_list_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_product_time_series_view_graph_number"/>
<field name="act_window" ref="act_reporting_product_time_series"/>
</record>
<record model="ir.action.keyword" id="act_reporting_product_time_series_list_keyword1">
<field name="keyword">tree_open</field>
<field name="model">sale.reporting.product,-1</field>
<field name="action" ref="act_reporting_product_time_series"/>
</record>
<record model="ir.model.access" id="access_reporting_product_time_series">
<field name="model" search="[('model', '=', 'sale.reporting.product.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_product_time_series_sale">
<field name="model" search="[('model', '=', 'sale.reporting.product.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>
<!-- Category -->
<record model="ir.ui.view" id="reporting_category_view_tree">
<field name="model">sale.reporting.category.tree</field>
<field name="type">tree</field>
<field name="field_childs">children</field>
<field name="name">sale_reporting_category_tree</field>
</record>
<record model="ir.action.act_window" id="act_reporting_category_tree">
<field name="name">Sales per Category</field>
<field name="res_model">sale.reporting.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_category_tree_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_category_view_tree"/>
<field name="act_window" ref="act_reporting_category_tree"/>
</record>
<record model="ir.action.keyword" id="act_reporting_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_category_tree"/>
</record>
<record model="ir.ui.view" id="reporting_category_view_list">
<field name="model">sale.reporting.category</field>
<field name="type">tree</field>
<field name="name">sale_reporting_category_list</field>
</record>
<record model="ir.action.act_window" id="act_reporting_category">
<field name="name">Sales per Category</field>
<field name="res_model">sale.reporting.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_category_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_category_view_list"/>
<field name="act_window" ref="act_reporting_category"/>
</record>
<record model="ir.action.keyword" id="act_reporting_category_keyword1">
<field name="keyword">tree_open</field>
<field name="model">sale.reporting.category.tree,-1</field>
<field name="action" ref="act_reporting_category"/>
</record>
<record model="ir.model.access" id="access_reporting_category">
<field name="model" search="[('model', '=', 'sale.reporting.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_category_sale">
<field name="model" search="[('model', '=', 'sale.reporting.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_category_time_series_view_list">
<field name="model">sale.reporting.category.time_series</field>
<field name="type">tree</field>
<field name="name">sale_reporting_category_time_series_list</field>
</record>
<record model="ir.ui.view" id="reporting_category_time_series_view_graph_revenue">
<field name="model">sale.reporting.category.time_series</field>
<field name="type">graph</field>
<field name="name">sale_reporting_category_time_series_graph_revenue</field>
</record>
<record model="ir.ui.view" id="reporting_category_time_series_view_graph_number">
<field name="model">sale.reporting.category.time_series</field>
<field name="type">graph</field>
<field name="name">sale_reporting_category_time_series_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_category_time_series">
<field name="name">Sales per Category</field>
<field name="res_model">sale.reporting.category.time_series</field>
<field name="context_model">sale.reporting.context</field>
<field
name="domain"
eval="[('category', '=', Eval('active_id', -1))]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_category_time_series_list_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_category_time_series_view_list"/>
<field name="act_window" ref="act_reporting_category_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_category_time_series_list_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_category_time_series_view_graph_revenue"/>
<field name="act_window" ref="act_reporting_category_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_category_time_series_list_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_category_time_series_view_graph_number"/>
<field name="act_window" ref="act_reporting_category_time_series"/>
</record>
<record model="ir.action.keyword" id="act_reporting_category_time_series_list_keyword1">
<field name="keyword">tree_open</field>
<field name="model">sale.reporting.category,-1</field>
<field name="action" ref="act_reporting_category_time_series"/>
</record>
<record model="ir.model.access" id="access_reporting_category_time_series">
<field name="model" search="[('model', '=', 'sale.reporting.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_category_time_series_sale">
<field name="model" search="[('model', '=', 'sale.reporting.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>
<!-- Region -->
<record model="ir.ui.view" id="reporting_region_view_tree">
<field name="model">sale.reporting.region</field>
<field name="type">tree</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">
<field name="name">Sales per Region</field>
<field name="res_model">sale.reporting.region</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">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_region_view_tree"/>
<field name="act_window" ref="act_reporting_region"/>
</record>
<record model="ir.action.keyword" id="act_reporting_country_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_region"/>
</record>
<record model="ir.model.access" id="access_reporting_region">
<field name="model" search="[('model', '=', 'sale.reporting.region')]"/>
<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')]"/>
<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_country_time_series_view_list">
<field name="model">sale.reporting.country.time_series</field>
<field name="type">tree</field>
<field name="name">sale_reporting_country_time_series_list</field>
</record>
<record model="ir.ui.view" id="reporting_country_time_series_view_graph_revenue">
<field name="model">sale.reporting.country.time_series</field>
<field name="type">graph</field>
<field name="name">sale_reporting_country_time_series_graph_revenue</field>
</record>
<record model="ir.ui.view" id="reporting_country_time_series_view_graph_number">
<field name="model">sale.reporting.country.time_series</field>
<field name="type">graph</field>
<field name="name">sale_reporting_country_time_series_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_country_time_series">
<field name="name">Sales per Country</field>
<field name="res_model">sale.reporting.country.time_series</field>
<field name="context_model">sale.reporting.context</field>
<field name="domain"
eval="[('country', '=', Eval('active_id', -1))]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_country_time_series_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_country_time_series_view_list"/>
<field name="act_window" ref="act_reporting_country_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_country_time_series_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_country_time_series_view_graph_revenue"/>
<field name="act_window" ref="act_reporting_country_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_country_time_series_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_country_time_series_view_graph_number"/>
<field name="act_window" ref="act_reporting_country_time_series"/>
</record>
<record model="ir.model.access" id="access_reporting_country_time_series">
<field name="model" search="[('model', '=', 'sale.reporting.country.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_country_time_series_sale">
<field name="model" search="[('model', '=', 'sale.reporting.country.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>
<record model="ir.ui.view" id="reporting_country_subdivision_time_series_view_list">
<field name="model">sale.reporting.country.subdivision.time_series</field>
<field name="type">tree</field>
<field name="name">sale_reporting_country_subdivision_time_series_list</field>
</record>
<record model="ir.ui.view" id="reporting_country_subdivision_time_series_view_graph_revenue">
<field name="model">sale.reporting.country.subdivision.time_series</field>
<field name="type">graph</field>
<field name="name">sale_reporting_country_subdivision_time_series_graph_revenue</field>
</record>
<record model="ir.ui.view" id="reporting_country_subdivision_time_series_view_graph_number">
<field name="model">sale.reporting.country.subdivision.time_series</field>
<field name="type">graph</field>
<field name="name">sale_reporting_country_subdivision_time_series_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_country_subdivision_time_series">
<field name="name">Sales per Subdivision</field>
<field name="res_model">sale.reporting.country.subdivision.time_series</field>
<field name="context_model">sale.reporting.context</field>
<field
name="domain"
eval="[('subdivision', '=', Eval('active_id', -1))]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_country_subdivision_time_series_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_country_subdivision_time_series_view_list"/>
<field name="act_window" ref="act_reporting_country_subdivision_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_country_subdivision_time_series_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_country_subdivision_time_series_view_graph_revenue"/>
<field name="act_window" ref="act_reporting_country_subdivision_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_country_subdivision_time_series_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_country_subdivision_time_series_view_graph_number"/>
<field name="act_window" ref="act_reporting_country_subdivision_time_series"/>
</record>
<record model="ir.model.access" id="access_reporting_country_subdivision_time_series">
<field name="model" search="[('model', '=', 'sale.reporting.country.subdivision.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_country_subdivision_time_series_sale">
<field name="model" search="[('model', '=', 'sale.reporting.country.subdivision.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>
<record model="ir.action.wizard" id="wizard_reporting_region_open">
<field name="name">Open Region</field>
<field name="wiz_name">sale.reporting.region.open</field>
<field name="model">sale.reporting.region</field>
</record>
<record model="ir.action.keyword" id="wizard_reporting_region_open_keyword1">
<field name="keyword">tree_open</field>
<field name="model">sale.reporting.region,-1</field>
<field name="action" ref="wizard_reporting_region_open"/>
</record>
</data>
</tryton>
+5 -1
View File
@@ -47,7 +47,7 @@ if minor_version % 2:
'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
name[8:], name, version))
requires = ['python-sql >= 0.4']
requires = ['python-sql >= 0.4', 'python-dateutil']
for dep in info.get('depends', []):
if not re.match(r'(ir|res)(\W|$)', dep):
requires.append(get_require_version('trytond_%s' % dep))
@@ -112,6 +112,9 @@ setup(name=name,
],
license='GPL-3',
install_requires=requires,
extras_require={
'sparklines': ['pygal'],
},
dependency_links=dependency_links,
zip_safe=False,
entry_points="""
@@ -126,5 +129,6 @@ setup(name=name,
'tests/scenario_sale.rst',
'tests/scenario_sale_empty.rst',
'tests/scenario_sale_ship_other_product.rst',
'tests/scenario_sale_reporting.rst',
],
)
+252
View File
@@ -0,0 +1,252 @@
=======================
Sale Reporting Scenario
=======================
Imports::
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.tests.tools import activate_modules
>>> from trytond.modules.company.tests.tools import create_company, \
... get_company
>>> from trytond.modules.account.tests.tools import create_fiscalyear, \
... create_chart, get_accounts
>>> from trytond.modules.account_invoice.tests.tools import \
... set_fiscalyear_invoice_sequences
Install sale::
>>> config = activate_modules('sale')
Create company::
>>> _ = create_company()
>>> company = get_company()
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(
... create_fiscalyear(company))
>>> fiscalyear.click('create_period')
Create chart of accounts::
>>> _ = create_chart(company)
>>> accounts = get_accounts(company)
>>> revenue = accounts['revenue']
>>> expense = accounts['expense']
Create countries::
>>> Country = Model.get('country.country')
>>> Subdivision = Model.get('country.subdivision')
>>> country_us = Country(
... name="United States", code="US", code3="USA", code_numeric="840")
>>> country_us.save()
>>> california = Subdivision(
... name="California", code="US-CA", type='state', country=country_us)
>>> california.save()
>>> new_york = Subdivision(
... name="New York", code="US-NY", type='state', country=country_us)
>>> new_york.save()
Create parties::
>>> Party = Model.get('party.party')
>>> customer1 = Party(name='Customer1')
>>> address, = customer1.addresses
>>> address.country = country_us
>>> address.subdivision = california
>>> customer1.save()
>>> customer2 = Party(name='Customer2')
>>> address, = customer2.addresses
>>> address.country = country_us
>>> address.subdivision = new_york
>>> customer2.save()
Create products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> Category = Model.get('product.category')
>>> template1 = ProductTemplate()
>>> template1.name = "Product1"
>>> template1.default_uom = unit
>>> template1.type = 'service'
>>> template1.salable = True
>>> template1.list_price = Decimal('10')
>>> template1.account_revenue = revenue
>>> template1.save()
>>> product1, = template1.products
>>> template2, = template1.duplicate(default={'name': "Product2"})
>>> product2, = template2.products
>>> category_root1 = Category(name="Root1")
>>> category_root1.save()
>>> category_child1 = Category(name="Child1", parent=category_root1)
>>> category_child1.save()
>>> category_child2 = Category(name="Child2", parent=category_root1)
>>> category_child2.save()
>>> category_root2 = Category(name="Root2")
>>> category_root2.save()
>>> template1.categories.append(Category(category_child1.id))
>>> template1.categories.append(Category(category_root2.id))
>>> template1.save()
>>> template2.categories.append(Category(category_child2.id))
>>> template2.save()
Create sales::
>>> Sale = Model.get('sale.sale')
>>> sale1 = Sale()
>>> sale1.party = customer1
>>> sale1.sale_date = fiscalyear.start_date
>>> line = sale1.lines.new()
>>> line.product = product1
>>> line.quantity = 2
>>> line = sale1.lines.new()
>>> line.product = product2
>>> line.quantity = 1
>>> sale1.click('quote')
>>> sale1.click('confirm')
>>> sale2 = Sale()
>>> sale2.party = customer2
>>> sale2.sale_date = fiscalyear.start_date + relativedelta(months=1)
>>> line = sale2.lines.new()
>>> line.product = product1
>>> line.quantity = 1
>>> sale2.click('quote')
>>> sale2.click('confirm')
Check sale reporting per customer::
>>> Customer = Model.get('sale.reporting.customer')
>>> CustomerTimeseries = Model.get('sale.reporting.customer.time_series')
>>> with config.set_context(
... from_date=fiscalyear.start_date,
... to_date=fiscalyear.end_date,
... period='day',
... ):
... reports = Customer.find([])
... time_series = CustomerTimeseries.find([])
>>> len(reports)
2
>>> sorted((r.customer.id, r.number, r.revenue) for r in reports) == \
... sorted([(customer1.id, 1, Decimal('30')),
... (customer2.id, 1, Decimal('10'))])
True
>>> len(time_series)
2
>>> sorted((r.customer.id, str(r.date), r.number, r.revenue)
... for r in time_series) == sorted(
... [(customer1.id, str(sale1.sale_date.replace(day=1)), 1, Decimal('30')),
... (customer2.id, str(sale2.sale_date.replace(day=1)), 1, Decimal('10'))])
True
Check sale reporting per product::
>>> Product = Model.get('sale.reporting.product')
>>> ProductTimeseries = Model.get('sale.reporting.product.time_series')
>>> with config.set_context(
... from_date=fiscalyear.start_date,
... to_date=fiscalyear.end_date,
... period='day',
... ):
... reports = Product.find([])
... time_series = ProductTimeseries.find([])
>>> len(reports)
2
>>> sorted((r.product.id, r.number, r.revenue) for r in reports) == \
... sorted([(product1.id, 2, Decimal('30')),
... (product2.id, 1, Decimal('10'))])
True
>>> len(time_series)
3
>>> sorted((r.product.id, str(r.date), r.number, r.revenue)
... for r in time_series) == sorted(
... [(product1.id, str(sale1.sale_date.replace(day=1)), 1, Decimal('20')),
... (product2.id, str(sale1.sale_date.replace(day=1)), 1, Decimal('10')),
... (product1.id, str(sale2.sale_date.replace(day=1)), 1, Decimal('10'))])
True
Check sale reporting per categories::
>>> Category = Model.get('sale.reporting.category')
>>> CategoryTimeseries = Model.get('sale.reporting.category.time_series')
>>> CategoryTree = Model.get('sale.reporting.category.tree')
>>> with config.set_context(
... from_date=fiscalyear.start_date,
... to_date=fiscalyear.end_date,
... period='day',
... ):
... reports = Category.find([])
... time_series = CategoryTimeseries.find([])
... tree = CategoryTree.find([])
>>> len(reports)
3
>>> sorted((r.category.id, r.number, r.revenue) for r in reports) == \
... sorted([(category_child1.id, 2, Decimal('30')),
... (category_root2.id, 2, Decimal('30')),
... (category_child2.id, 1, Decimal('10'))])
True
>>> len(time_series)
5
>>> sorted((r.category.id, str(r.date), r.number, r.revenue)
... for r in time_series) == sorted(
... [(category_child1.id, str(sale1.sale_date.replace(day=1)), 1, Decimal('20')),
... (category_root2.id, str(sale1.sale_date.replace(day=1)), 1, Decimal('20')),
... (category_child2.id, str(sale1.sale_date.replace(day=1)), 1, Decimal('10')),
... (category_child1.id, str(sale2.sale_date.replace(day=1)), 1, Decimal('10')),
... (category_root2.id, str(sale2.sale_date.replace(day=1)), 1, Decimal('10'))])
True
>>> len(tree)
4
>>> sorted((r.name, r.revenue) for r in tree) == sorted([
... (u'Root1', Decimal('40')),
... (u'Child1', Decimal('30')),
... (u'Child2', Decimal('10')),
... (u'Root2', Decimal('30'))])
True
Check sale reporting per regions::
>>> Region = Model.get('sale.reporting.region')
>>> CountryTimeseries = Model.get('sale.reporting.country.time_series')
>>> SubdivisionTimeseries = Model.get(
... 'sale.reporting.country.subdivision.time_series')
>>> with config.set_context(
... from_date=fiscalyear.start_date,
... to_date=fiscalyear.end_date,
... period='day',
... ):
... reports = Region.find([])
... country_time_series = CountryTimeseries.find([])
... subdivision_time_series = SubdivisionTimeseries.find([])
>>> len(reports)
3
>>> sorted((r.region, r.number, r.revenue) for r in reports) == \
... sorted([('United States', 2, Decimal('40')),
... ('California', 1, Decimal('30')),
... ('New York', 1, Decimal('10'))])
True
>>> len(country_time_series)
2
>>> sorted((r.country.id, str(r.date), r.number, r.revenue)
... for r in country_time_series) == sorted(
... [(country_us.id, str(sale1.sale_date.replace(day=1)), 1, Decimal('30')),
... (country_us.id, str(sale2.sale_date.replace(day=1)), 1, Decimal('10'))])
True
>>> len(subdivision_time_series)
2
>>> sorted((r.subdivision.id, str(r.date), r.number, r.revenue)
... for r in subdivision_time_series) == sorted(
... [(california.id, str(sale1.sale_date.replace(day=1)), 1, Decimal('30')),
... (new_york.id, str(sale2.sale_date.replace(day=1)), 1, Decimal('10'))])
True
+5
View File
@@ -33,4 +33,9 @@ def suite():
tearDown=doctest_teardown, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE,
checker=doctest_checker))
suite.addTests(doctest.DocFileSuite(
'scenario_sale_reporting.rst',
tearDown=doctest_teardown, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE,
checker=doctest_checker))
return suite
+1
View File
@@ -15,6 +15,7 @@ depends:
xml:
sale.xml
configuration.xml
sale_reporting.xml
party.xml
stock.xml
product.xml
+9
View File
@@ -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"/>
<field name="number"/>
<field name="revenue"/>
<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"/>
</tree>
+7
View File
@@ -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"/>
<field name="revenue"/>
</tree>
+17
View File
@@ -0,0 +1,17 @@
<?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. -->
<form>
<group id="dates" colspan="2" col="4">
<label name="from_date"/>
<field name="from_date"/>
<label name="to_date"/>
<field name="to_date"/>
</group>
<label name="period"/>
<field name="period"/>
<label name="company"/>
<field name="company"/>
<label name="warehouse"/>
<field name="warehouse"/>
</form>
@@ -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"/>
</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="revenue"/>
</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"/>
</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="customer"/>
</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="customer"/>
</x>
<y>
<field name="revenue"/>
</y>
</graph>
+9
View File
@@ -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="customer"/>
<field name="number"/>
<field name="revenue"/>
<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"/>
</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="product"/>
</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="product"/>
</x>
<y>
<field name="revenue"/>
</y>
</graph>
+9
View File
@@ -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="product"/>
<field name="number"/>
<field name="revenue"/>
<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"/>
</tree>
+9
View File
@@ -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"/>
<field name="number"/>
<field name="revenue"/>
<field name="revenue_trend" expand="1"/>
</tree>