Add reporting
issue6948 review37831002
This commit is contained in:
1 parent
5f386d9024
commit
9e79ed4ae7
34 files changed
+1804
-1
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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',
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -15,6 +15,7 @@ depends:
|
||||
xml:
|
||||
sale.xml
|
||||
configuration.xml
|
||||
sale_reporting.xml
|
||||
party.xml
|
||||
stock.xml
|
||||
product.xml
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user