Add reporting
issue9831 review312881002
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
* Add reporting
|
||||
|
||||
Version 6.4.0 - 2022-05-02
|
||||
* Bug fixes (see mercurial logs for details)
|
||||
* Add supplier currency on party
|
||||
|
||||
+10
-1
@@ -3,7 +3,9 @@
|
||||
|
||||
from trytond.pool import Pool
|
||||
|
||||
from . import configuration, invoice, party, product, purchase, stock
|
||||
from . import (
|
||||
configuration, invoice, party, product, purchase, purchase_reporting,
|
||||
stock)
|
||||
|
||||
|
||||
def register():
|
||||
@@ -35,6 +37,13 @@ def register():
|
||||
party.SupplierLeadTime,
|
||||
party.PartySupplierCurrency,
|
||||
purchase.ReturnPurchaseStart,
|
||||
purchase_reporting.Context,
|
||||
purchase_reporting.Main,
|
||||
purchase_reporting.MainTimeseries,
|
||||
purchase_reporting.Supplier,
|
||||
purchase_reporting.SupplierTimeseries,
|
||||
purchase_reporting.Product,
|
||||
purchase_reporting.ProductTimeseries,
|
||||
module='purchase', type_='model')
|
||||
Pool.register(
|
||||
purchase.PurchaseReport,
|
||||
|
||||
@@ -143,6 +143,54 @@ quantities.
|
||||
.. |Products --> Products --> Suppliers| replace:: :menuselection:`Products --> Products --> Suppliers`
|
||||
__ https://demo.tryton.org/model/purchase.product_supplier
|
||||
|
||||
.. _concept-purchase.reporting:
|
||||
|
||||
Purchase Reporting
|
||||
==================
|
||||
|
||||
Each of the different *Purchase Reporting* concepts are based on either an
|
||||
``Abstract`` purchases report, or an ``AbstractTimeseries``.
|
||||
There is also a base ``Context`` that is inherited and used to specify things
|
||||
such as the date range that is used by the report.
|
||||
|
||||
The ``Abstract`` provides the basic properties that makes up a purchases report
|
||||
including the number of purchases and expense.
|
||||
The ``AbstractTimeseries`` is used to extend this with a date.
|
||||
This is used in purchases reports that cover multiple periods of time.
|
||||
These are combined together with additional specific properties to create the
|
||||
different purchases reports.
|
||||
|
||||
.. seealso::
|
||||
|
||||
Purchases reports can be accessed from the main menu item:
|
||||
|
||||
:menuselection:`Purchases --> Reporting --> Purchases`
|
||||
|
||||
.. _model-purchase.reporting.supplier:
|
||||
.. _model-purchase.reporting.supplier.time_series:
|
||||
|
||||
By Supplier
|
||||
-----------
|
||||
|
||||
The purchases reporting that is done *By Supplier* splits the purchases up
|
||||
based on what each `Supplier <party:model-party.party>` bought.
|
||||
This is done in two separate parts.
|
||||
One that shows the purchases, in total, for the selected period from the
|
||||
``Context``.
|
||||
Another that breaks them down by date into smaller chunks.
|
||||
|
||||
.. _model-purchase.reporting.product:
|
||||
.. _model-purchase.reporting.product.time_series:
|
||||
|
||||
By Product
|
||||
----------
|
||||
|
||||
Purchases reporting that is done *By Product* splits up the purchases based on
|
||||
the `Product <product:concept-product>` that was purchased.
|
||||
This is structured as two parts.
|
||||
One that shows the total purchases for the selected period of time, and another
|
||||
that shows how the purchases were distributed over time.
|
||||
|
||||
.. _model-purchase.configuration:
|
||||
|
||||
Configuration
|
||||
|
||||
+24
@@ -39,5 +39,29 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<record model="ir.message" id="msg_purchase_line_tax_unique">
|
||||
<field name="text">A tax can be added only once to a purchase line.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_purchase_reporting_company">
|
||||
<field name="text">Company</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_purchase_reporting_number">
|
||||
<field name="text">#</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_purchase_reporting_number_help">
|
||||
<field name="text">Number of purchases.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_purchase_reporting_expense">
|
||||
<field name="text">Expense</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_purchase_reporting_expense_trend">
|
||||
<field name="text">Expense Trend</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_purchase_reporting_currency">
|
||||
<field name="text">Currency</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_purchase_reporting_date">
|
||||
<field name="text">Date</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_purchase_reporting_time_series">
|
||||
<field name="text">Time Series</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
parent="menu_purchase"
|
||||
sequence="100"
|
||||
id="menu_reporting"
|
||||
active="0"/>
|
||||
active="True"/>
|
||||
|
||||
<record model="ir.model.access" id="access_purchase">
|
||||
<field name="model" search="[('model', '=', 'purchase.purchase')]"/>
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
# 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 itertools import tee, zip_longest
|
||||
|
||||
try:
|
||||
import pygal
|
||||
except ImportError:
|
||||
pygal = None
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from sql import Literal, Null
|
||||
from sql.aggregate import Count, Min, Sum
|
||||
from sql.conditionals import Coalesce
|
||||
from sql.functions import CurrentTimestamp, DateTrunc
|
||||
|
||||
from trytond.i18n import lazy_gettext
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.modules.currency.fields import Monetary
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval, If
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
|
||||
def pairwise(iterable):
|
||||
a, b = tee(iterable)
|
||||
next(b)
|
||||
return zip_longest(a, b)
|
||||
|
||||
|
||||
class Abstract(ModelSQL):
|
||||
|
||||
company = fields.Many2One(
|
||||
'company.company',
|
||||
lazy_gettext("purchase.msg_purchase_reporting_company"))
|
||||
number = fields.Integer(
|
||||
lazy_gettext("purchase.msg_purchase_reporting_number"),
|
||||
help=lazy_gettext("purchase.msg_purchase_reporting_number_help"))
|
||||
expense = Monetary(
|
||||
lazy_gettext("purchase.msg_purchase_reporting_expense"),
|
||||
digits='currency', currency='currency')
|
||||
expense_trend = fields.Function(
|
||||
fields.Char(
|
||||
lazy_gettext("purchase.msg_purchase_reporting_expense_trend")),
|
||||
'get_trend')
|
||||
time_series = None
|
||||
currency = fields.Many2One(
|
||||
'currency.currency',
|
||||
lazy_gettext("purchase.msg_purchase_reporting_currency"))
|
||||
|
||||
@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()
|
||||
Line = pool.get('purchase.line')
|
||||
Purchase = pool.get('purchase.purchase')
|
||||
|
||||
tables = {}
|
||||
tables['line'] = line = Line.__table__()
|
||||
tables['line.purchase'] = purchase = Purchase.__table__()
|
||||
|
||||
from_item = (line
|
||||
.join(purchase, condition=line.purchase == purchase.id))
|
||||
return from_item, tables
|
||||
|
||||
@classmethod
|
||||
def _columns(cls, tables):
|
||||
line = tables['line']
|
||||
purchase = tables['line.purchase']
|
||||
|
||||
quantity = Coalesce(line.actual_quantity, line.quantity)
|
||||
expense = cls.expense.sql_cast(
|
||||
Sum(quantity * line.unit_price))
|
||||
return [
|
||||
cls._column_id(tables).as_('id'),
|
||||
Literal(0).as_('create_uid'),
|
||||
CurrentTimestamp().as_('create_date'),
|
||||
cls.write_uid.sql_cast(Literal(Null)).as_('write_uid'),
|
||||
cls.write_date.sql_cast(Literal(Null)).as_('write_date'),
|
||||
purchase.company.as_('company'),
|
||||
purchase.currency.as_('currency'),
|
||||
expense.as_('expense'),
|
||||
Count(purchase.id, distinct=True).as_('number'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _column_id(cls, tables):
|
||||
line = tables['line']
|
||||
return Min(line.id)
|
||||
|
||||
@classmethod
|
||||
def _group_by(cls, tables):
|
||||
purchase = tables['line.purchase']
|
||||
return [purchase.company, purchase.currency]
|
||||
|
||||
@classmethod
|
||||
def _where(cls, tables):
|
||||
context = Transaction().context
|
||||
purchase = tables['line.purchase']
|
||||
|
||||
where = purchase.company == context.get('company')
|
||||
where &= purchase.state.in_(cls._purchase_states())
|
||||
from_date = context.get('from_date')
|
||||
if from_date:
|
||||
where &= purchase.purchase_date >= from_date
|
||||
to_date = context.get('to_date')
|
||||
if to_date:
|
||||
where &= purchase.purchase_date <= to_date
|
||||
warehouse = context.get('warehouse')
|
||||
if warehouse:
|
||||
where &= purchase.warehouse == warehouse
|
||||
return where
|
||||
|
||||
@classmethod
|
||||
def _purchase_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 0
|
||||
for ts in self.time_series_all])
|
||||
return chart.render_sparktext()
|
||||
|
||||
|
||||
class AbstractTimeseries(Abstract):
|
||||
|
||||
date = fields.Date(lazy_gettext('purchase.msg_purchase_reporting_date'))
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order = [('date', 'ASC')]
|
||||
|
||||
@classmethod
|
||||
def _columns(cls, tables):
|
||||
return super()._columns(tables) + [
|
||||
cls._column_date(tables).as_('date')]
|
||||
|
||||
@classmethod
|
||||
def _column_date(cls, tables):
|
||||
context = Transaction().context
|
||||
purchase = tables['line.purchase']
|
||||
date = DateTrunc(context.get('period'), purchase.purchase_date)
|
||||
date = cls.date.sql_cast(date)
|
||||
return date
|
||||
|
||||
@classmethod
|
||||
def _group_by(cls, tables):
|
||||
return super()._group_by(tables) + [cls._column_date(tables)]
|
||||
|
||||
|
||||
class Context(ModelView):
|
||||
"Purchase Reporting Context"
|
||||
__name__ = 'purchase.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 Main(Abstract, ModelView):
|
||||
"Purchase Reporting"
|
||||
__name__ = 'purchase.reporting.main'
|
||||
|
||||
time_series = fields.Function(fields.One2Many(
|
||||
'purchase.reporting.main.time_series', None,
|
||||
lazy_gettext('purchase.msg_purchase_reporting_time_series')),
|
||||
'get_time_series')
|
||||
|
||||
def get_rec_name(self, name):
|
||||
return ''
|
||||
|
||||
def get_time_series(self, name):
|
||||
pool = Pool()
|
||||
Timeseries = pool.get('purchase.reporting.main.time_series')
|
||||
return [t.id for t in Timeseries.search([])]
|
||||
|
||||
|
||||
class MainTimeseries(AbstractTimeseries, ModelView):
|
||||
"Purchase Reporting"
|
||||
__name__ = 'purchase.reporting.main.time_series'
|
||||
|
||||
|
||||
class SupplierMixin(object):
|
||||
__slots__ = ()
|
||||
supplier = fields.Many2One(
|
||||
'party.party', "Supplier",
|
||||
context={
|
||||
'company': Eval('company', -1),
|
||||
},
|
||||
depends=['company'])
|
||||
|
||||
@classmethod
|
||||
def _columns(cls, tables):
|
||||
purchase = tables['line.purchase']
|
||||
return super()._columns(tables) + [purchase.party.as_('supplier')]
|
||||
|
||||
@classmethod
|
||||
def _group_by(cls, tables):
|
||||
purchase = tables['line.purchase']
|
||||
return super()._group_by(tables) + [purchase.party]
|
||||
|
||||
def get_rec_name(self, name):
|
||||
return self.supplier.rec_name
|
||||
|
||||
|
||||
class Supplier(SupplierMixin, Abstract, ModelView):
|
||||
"Purchase Reporting per Supplier"
|
||||
__name__ = 'purchase.reporting.supplier'
|
||||
|
||||
time_series = fields.One2Many(
|
||||
'purchase.reporting.supplier.time_series', 'supplier',
|
||||
lazy_gettext('purchase.msg_purchase_reporting_time_series')),
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order.insert(0, ('supplier', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def _column_id(cls, tables):
|
||||
purchase = tables['line.purchase']
|
||||
return purchase.party
|
||||
|
||||
|
||||
class SupplierTimeseries(SupplierMixin, AbstractTimeseries, ModelView):
|
||||
"Purchase Reporting per Supplier"
|
||||
__name__ = 'purchase.reporting.supplier.time_series'
|
||||
|
||||
|
||||
class ProductMixin(object):
|
||||
__slots__ = ()
|
||||
product = fields.Many2One(
|
||||
'product.product', "Product",
|
||||
context={
|
||||
'company': Eval('company', -1),
|
||||
},
|
||||
depends=['company'])
|
||||
product_supplier = fields.Many2One(
|
||||
'purchase.product_supplier', "Supplier's Product")
|
||||
|
||||
@classmethod
|
||||
def _columns(cls, tables):
|
||||
line = tables['line']
|
||||
return super()._columns(tables) + [
|
||||
line.product.as_('product'),
|
||||
line.product_supplier.as_('product_supplier'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _group_by(cls, tables):
|
||||
line = tables['line']
|
||||
return super()._group_by(tables) + [
|
||||
line.product, line.product_supplier]
|
||||
|
||||
@classmethod
|
||||
def _where(cls, tables):
|
||||
context = Transaction().context
|
||||
line = tables['line']
|
||||
purchase = tables['line.purchase']
|
||||
where = super()._where(tables)
|
||||
where &= line.product != Null
|
||||
where &= purchase.party == context.get('supplier')
|
||||
return where
|
||||
|
||||
def get_rec_name(self, name):
|
||||
pool = Pool()
|
||||
Party = pool.get('party.party')
|
||||
context = Transaction().context
|
||||
|
||||
name = self.product.rec_name if self.product else None
|
||||
if context.get('supplier'):
|
||||
supplier = Party(context['supplier'])
|
||||
name += '@%s' % supplier.rec_name
|
||||
return name
|
||||
|
||||
|
||||
class Product(ProductMixin, Abstract, ModelView):
|
||||
"Purchase Reporting per Product"
|
||||
__name__ = 'purchase.reporting.product'
|
||||
|
||||
time_series = fields.One2Many(
|
||||
'purchase.reporting.product.time_series', 'product',
|
||||
lazy_gettext('purchase.msg_purchase_reporting_time_series')),
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order.insert(0, ('product', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def _column_id(cls, tables):
|
||||
line = tables['line']
|
||||
return line.product
|
||||
|
||||
|
||||
class ProductTimeseries(ProductMixin, AbstractTimeseries, ModelView):
|
||||
"Purchase Reporting per Product"
|
||||
__name__ = 'purchase.reporting.product.time_series'
|
||||
@@ -0,0 +1,456 @@
|
||||
<?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 parent="menu_reporting"
|
||||
name="Purchases"
|
||||
sequence="10"
|
||||
id="menu_reporting_purchase"
|
||||
icon="tryton-graph"/>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_context_view_form">
|
||||
<field name="model">purchase.reporting.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">purchase_reporting_context_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_main_view_list">
|
||||
<field name="model">purchase.reporting.main</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">purchase_reporting_main_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_main_view_graph_expense">
|
||||
<field name="model">purchase.reporting.main</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">purchase_reporting_main_graph_expense</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_main_view_graph_number">
|
||||
<field name="model">purchase.reporting.main</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">purchase_reporting_main_graph_number</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_reporting_main">
|
||||
<field name="name">Purchases</field>
|
||||
<field name="res_model">purchase.reporting.main</field>
|
||||
<field name="context_model">purchase.reporting.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_main_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="reporting_main_view_list"/>
|
||||
<field name="act_window" ref="act_reporting_main"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_reporting_main_keyword1">
|
||||
<field name="keyword">tree_open</field>
|
||||
<field name="model" ref="menu_reporting_purchase"/>
|
||||
<field name="action" ref="act_reporting_main"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_reporting_main_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.main')]"/>
|
||||
<field name="global_p" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.rule" id="rule_reporting_main_companies">
|
||||
<field name="domain"
|
||||
eval="[('company', 'in', Eval('companies', []))]"
|
||||
pyson="1"/>
|
||||
<field name="rule_group" ref="rule_group_reporting_main_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_reporting_main">
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.main')]"/>
|
||||
<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_main_purchase">
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.main')]"/>
|
||||
<field name="group" ref="group_purchase"/>
|
||||
<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_main_time_series_view_list">
|
||||
<field name="model">purchase.reporting.main.time_series</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">purchase_reporting_main_time_series_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_main_time_series_view_graph_expense">
|
||||
<field name="model">purchase.reporting.main.time_series</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">purchase_reporting_main_time_series_graph_expense</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_main_time_series_view_graph_number">
|
||||
<field name="model">purchase.reporting.main.time_series</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">purchase_reporting_main_time_series_graph_number</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_reporting_main_time_series">
|
||||
<field name="name">Purchases</field>
|
||||
<field name="res_model">purchase.reporting.main.time_series</field>
|
||||
<field name="context_model">purchase.reporting.context</field>
|
||||
<field name="order" eval="[('date', 'DESC')]" pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_main_time_series_list_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="reporting_main_time_series_view_list"/>
|
||||
<field name="act_window" ref="act_reporting_main_time_series"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_main_time_series_list_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="reporting_main_time_series_view_graph_expense"/>
|
||||
<field name="act_window" ref="act_reporting_main_time_series"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_main_time_series_list_view3">
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="view" ref="reporting_main_time_series_view_graph_number"/>
|
||||
<field name="act_window" ref="act_reporting_main_time_series"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_reporting_main_time_series_list_keyword1">
|
||||
<field name="keyword">tree_open</field>
|
||||
<field name="model">purchase.reporting.main,-1</field>
|
||||
<field name="action" ref="act_reporting_main_time_series"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_reporting_main_time_series_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.main.time_series')]"/>
|
||||
<field name="global_p" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.rule" id="rule_reporting_main_time_series_companies">
|
||||
<field name="domain"
|
||||
eval="[('company', 'in', Eval('companies', []))]"
|
||||
pyson="1"/>
|
||||
<field name="rule_group" ref="rule_group_reporting_main_time_series_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_reporting_main_time_series">
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.main.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_main_time_series_purchase">
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.main.time_series')]"/>
|
||||
<field name="group" ref="group_purchase"/>
|
||||
<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>
|
||||
|
||||
<!-- Supplier -->
|
||||
|
||||
<record model="ir.ui.view" id="reporting_supplier_view_list">
|
||||
<field name="model">purchase.reporting.supplier</field>
|
||||
<field name="inherit" ref="reporting_main_view_list"/>
|
||||
<field name="name">purchase_reporting_supplier_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_supplier_view_graph_expense">
|
||||
<field name="model">purchase.reporting.supplier</field>
|
||||
<field name="inherit" ref="reporting_main_view_graph_expense"/>
|
||||
<field name="name">purchase_reporting_supplier_graph_expense</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_supplier_view_graph_number">
|
||||
<field name="model">purchase.reporting.supplier</field>
|
||||
<field name="inherit" ref="reporting_main_view_graph_number"/>
|
||||
<field name="name">purchase_reporting_supplier_graph_number</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_reporting_supplier">
|
||||
<field name="name">Purchases per Supplier</field>
|
||||
<field name="res_model">purchase.reporting.supplier</field>
|
||||
<field name="context_model">purchase.reporting.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_supplier_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="reporting_supplier_view_list"/>
|
||||
<field name="act_window" ref="act_reporting_supplier"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_supplier_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="reporting_supplier_view_graph_expense"/>
|
||||
<field name="act_window" ref="act_reporting_supplier"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_supplier_view3">
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="view" ref="reporting_supplier_view_graph_number"/>
|
||||
<field name="act_window" ref="act_reporting_supplier"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_reporting_supplier_keyword1">
|
||||
<field name="keyword">tree_open</field>
|
||||
<field name="model" ref="menu_reporting_purchase"/>
|
||||
<field name="action" ref="act_reporting_supplier"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_reporting_supplier_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.supplier')]"/>
|
||||
<field name="global_p" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.rule" id="rule_reporting_supplier_companies">
|
||||
<field name="domain"
|
||||
eval="[('company', 'in', Eval('companies', []))]"
|
||||
pyson="1"/>
|
||||
<field name="rule_group" ref="rule_group_reporting_supplier_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_reporting_supplier">
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.supplier')]"/>
|
||||
<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_supplier_purchase">
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.supplier')]"/>
|
||||
<field name="group" ref="group_purchase"/>
|
||||
<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_supplier_time_series_view_list">
|
||||
<field name="model">purchase.reporting.supplier.time_series</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">purchase_reporting_main_time_series_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_supplier_time_series_view_graph_expense">
|
||||
<field name="model">purchase.reporting.supplier.time_series</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">purchase_reporting_main_time_series_graph_expense</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_supplier_time_series_view_graph_number">
|
||||
<field name="model">purchase.reporting.supplier.time_series</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">purchase_reporting_main_time_series_graph_number</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_reporting_supplier_time_series">
|
||||
<field name="name">Purchases per Supplier</field>
|
||||
<field name="res_model">purchase.reporting.supplier.time_series</field>
|
||||
<field name="context_model">purchase.reporting.context</field>
|
||||
<field
|
||||
name="domain"
|
||||
eval="[('supplier', '=', Eval('active_id', -1))]"
|
||||
pyson="1"/>
|
||||
<field name="order" eval="[('date', 'DESC')]" pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_supplier_time_series_list_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="reporting_supplier_time_series_view_list"/>
|
||||
<field name="act_window" ref="act_reporting_supplier_time_series"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_supplier_time_series_list_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="reporting_supplier_time_series_view_graph_expense"/>
|
||||
<field name="act_window" ref="act_reporting_supplier_time_series"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_supplier_time_series_list_view3">
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="view" ref="reporting_supplier_time_series_view_graph_number"/>
|
||||
<field name="act_window" ref="act_reporting_supplier_time_series"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_reporting_supplier_time_series_list_keyword1">
|
||||
<field name="keyword">tree_open</field>
|
||||
<field name="model">purchase.reporting.supplier,-1</field>
|
||||
<field name="action" ref="act_reporting_supplier_time_series"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_reporting_supplier_time_series_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.supplier.time_series')]"/>
|
||||
<field name="global_p" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.rule" id="rule_reporting_supplier_time_series_companies">
|
||||
<field name="domain"
|
||||
eval="[('company', 'in', Eval('companies', []))]"
|
||||
pyson="1"/>
|
||||
<field name="rule_group" ref="rule_group_reporting_supplier_time_series_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_reporting_supplier_time_series">
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.supplier.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_supplier_time_series_purchase">
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.supplier.time_series')]"/>
|
||||
<field name="group" ref="group_purchase"/>
|
||||
<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">purchase.reporting.product</field>
|
||||
<field name="inherit" ref="reporting_main_view_list"/>
|
||||
<field name="name">purchase_reporting_product_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_product_view_graph_expense">
|
||||
<field name="model">purchase.reporting.product</field>
|
||||
<field name="inherit" ref="reporting_main_view_graph_expense"/>
|
||||
<field name="name">purchase_reporting_product_graph_expense</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_product_view_graph_number">
|
||||
<field name="model">purchase.reporting.product</field>
|
||||
<field name="inherit" ref="reporting_main_view_graph_number"/>
|
||||
<field name="name">purchase_reporting_product_graph_number</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_reporting_product">
|
||||
<field name="name">Purchases per Product</field>
|
||||
<field name="res_model">purchase.reporting.product</field>
|
||||
<field name="context_model">purchase.reporting.context</field>
|
||||
<field name="context" eval="{'supplier': Eval('active_id')}" pyson="1"/>
|
||||
</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_expense"/>
|
||||
<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">purchase.reporting.supplier,-1</field>
|
||||
<field name="action" ref="act_reporting_product"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_reporting_product_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.product')]"/>
|
||||
<field name="global_p" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.rule" id="rule_reporting_product_companies">
|
||||
<field name="domain"
|
||||
eval="[('company', 'in', Eval('companies', []))]"
|
||||
pyson="1"/>
|
||||
<field name="rule_group" ref="rule_group_reporting_product_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_reporting_product">
|
||||
<field name="model" search="[('model', '=', 'purchase.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_purchase">
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.product')]"/>
|
||||
<field name="group" ref="group_purchase"/>
|
||||
<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">purchase.reporting.product.time_series</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">purchase_reporting_main_time_series_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_product_time_series_view_graph_expense">
|
||||
<field name="model">purchase.reporting.product.time_series</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">purchase_reporting_main_time_series_graph_expense</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_product_time_series_view_graph_number">
|
||||
<field name="model">purchase.reporting.product.time_series</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">purchase_reporting_main_time_series_graph_number</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_reporting_product_time_series">
|
||||
<field name="name">Purchases per Product</field>
|
||||
<field name="res_model">purchase.reporting.product.time_series</field>
|
||||
<field name="context_model">purchase.reporting.context</field>
|
||||
<field name="domain"
|
||||
eval="[('product', '=', Eval('active_id', -1))]"
|
||||
pyson="1"/>
|
||||
<field name="order" eval="[('date', 'DESC')]" pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_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_expense"/>
|
||||
<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">purchase.reporting.product,-1</field>
|
||||
<field name="action" ref="act_reporting_product_time_series"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_reporting_product_time_series_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.product.time_series')]"/>
|
||||
<field name="global_p" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.rule" id="rule_reporting_product_time_series_companies">
|
||||
<field name="domain"
|
||||
eval="[('company', 'in', Eval('companies', []))]"
|
||||
pyson="1"/>
|
||||
<field name="rule_group" ref="rule_group_reporting_product_time_series_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_reporting_product_time_series">
|
||||
<field name="model" search="[('model', '=', 'purchase.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_purchase">
|
||||
<field name="model" search="[('model', '=', 'purchase.reporting.product.time_series')]"/>
|
||||
<field name="group" ref="group_purchase"/>
|
||||
<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>
|
||||
</data>
|
||||
</tryton>
|
||||
@@ -142,6 +142,7 @@ setup(name=name,
|
||||
python_requires='>=3.7',
|
||||
install_requires=requires,
|
||||
extras_require={
|
||||
'sparklines': ['pygal'],
|
||||
'test': tests_require,
|
||||
},
|
||||
dependency_links=dependency_links,
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
===========================
|
||||
Purchase 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)
|
||||
|
||||
Activate modules::
|
||||
|
||||
>>> config = activate_modules('purchase')
|
||||
>>> Party = Model.get('party.party')
|
||||
>>> ProductUom = Model.get('product.uom')
|
||||
>>> ProductTemplate = Model.get('product.template')
|
||||
>>> Purchase = Model.get('purchase.purchase')
|
||||
>>> Supplier = Model.get('purchase.reporting.supplier')
|
||||
>>> SupplierTimeseries = Model.get(
|
||||
... 'purchase.reporting.supplier.time_series')
|
||||
>>> Product = Model.get('purchase.reporting.product')
|
||||
>>> ProductTimeseries = Model.get('purchase.reporting.product.time_series')
|
||||
|
||||
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)
|
||||
>>> expense = accounts['expense']
|
||||
|
||||
Create parties::
|
||||
|
||||
>>> supplier1 = Party(name='Supplier1')
|
||||
>>> supplier1.save()
|
||||
>>> supplier2 = Party(name='Supplier2')
|
||||
>>> supplier2.save()
|
||||
|
||||
Create products::
|
||||
|
||||
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
|
||||
|
||||
>>> template1 = ProductTemplate()
|
||||
>>> template1.name = "Product1"
|
||||
>>> template1.default_uom = unit
|
||||
>>> template1.type = 'service'
|
||||
>>> template1.purchasable = True
|
||||
>>> template1.list_price = Decimal('20')
|
||||
>>> template1.save()
|
||||
>>> product1, = template1.products
|
||||
>>> product1.cost_price = Decimal('10')
|
||||
>>> product1.save()
|
||||
|
||||
>>> template2, = template1.duplicate(default={'name': "Product2"})
|
||||
>>> product2, = template2.products
|
||||
>>> product2.cost_price = Decimal('10')
|
||||
>>> product2.save()
|
||||
|
||||
Create purchases::
|
||||
|
||||
>>> purchase1 = Purchase()
|
||||
>>> purchase1.party = supplier1
|
||||
>>> purchase1.purchase_date = fiscalyear.start_date
|
||||
>>> line = purchase1.lines.new()
|
||||
>>> line.product = product1
|
||||
>>> line.quantity = 2
|
||||
>>> line = purchase1.lines.new()
|
||||
>>> line.product = product2
|
||||
>>> line.quantity = 1
|
||||
>>> purchase1.click('quote')
|
||||
>>> purchase1.click('confirm')
|
||||
|
||||
>>> purchase2 = Purchase()
|
||||
>>> purchase2.party = supplier2
|
||||
>>> purchase2.purchase_date = (
|
||||
... fiscalyear.start_date + relativedelta(months=1))
|
||||
>>> line = purchase2.lines.new()
|
||||
>>> line.product = product1
|
||||
>>> line.quantity = 1
|
||||
>>> purchase2.click('quote')
|
||||
>>> purchase2.click('confirm')
|
||||
|
||||
Check purchase reporting per supplier::
|
||||
|
||||
>>> context = dict(
|
||||
... from_date=fiscalyear.start_date,
|
||||
... to_date=fiscalyear.end_date,
|
||||
... period='month')
|
||||
>>> with config.set_context(context=context):
|
||||
... reports = Supplier.find([])
|
||||
... time_series = SupplierTimeseries.find([])
|
||||
>>> len(reports)
|
||||
2
|
||||
>>> with config.set_context(context=context):
|
||||
... sorted((r.supplier.id, r.number, r.expense) for r in reports) == \
|
||||
... sorted([(supplier1.id, 1, Decimal('30')),
|
||||
... (supplier2.id, 1, Decimal('10'))])
|
||||
True
|
||||
>>> len(time_series)
|
||||
2
|
||||
>>> purchase1_ts_date = purchase1.purchase_date.replace(day=1)
|
||||
>>> purchase2_ts_date = purchase2.purchase_date.replace(day=1)
|
||||
>>> with config.set_context(context=context):
|
||||
... sorted((r.supplier.id, r.date, r.number, r.expense)
|
||||
... for r in time_series) == sorted(
|
||||
... [(supplier1.id, purchase1_ts_date, 1, Decimal('30')),
|
||||
... (supplier2.id, purchase2_ts_date, 1, Decimal('10'))])
|
||||
True
|
||||
|
||||
Check purchase reporting per product without supplier::
|
||||
|
||||
>>> with config.set_context(context=context):
|
||||
... reports = Product.find([])
|
||||
... time_series = ProductTimeseries.find([])
|
||||
>>> len(reports)
|
||||
0
|
||||
|
||||
Check purchase reporting per product with supplier::
|
||||
|
||||
>>> context['supplier'] = supplier1.id
|
||||
>>> with config.set_context(context=context):
|
||||
... reports = Product.find([])
|
||||
... time_series = ProductTimeseries.find([])
|
||||
>>> len(reports)
|
||||
2
|
||||
>>> with config.set_context(context=context):
|
||||
... sorted((r.product.id, r.number, r.expense) for r in reports) == \
|
||||
... sorted([(product1.id, 1, Decimal('20')),
|
||||
... (product2.id, 1, Decimal('10'))])
|
||||
True
|
||||
>>> len(time_series)
|
||||
2
|
||||
>>> with config.set_context(context=context):
|
||||
... sorted((r.product.id, r.date, r.number, r.expense)
|
||||
... for r in time_series) == sorted(
|
||||
... [(product1.id, purchase1_ts_date, 1, Decimal('20')),
|
||||
... (product2.id, purchase1_ts_date, 1, Decimal('10'))])
|
||||
True
|
||||
@@ -14,6 +14,7 @@ depends:
|
||||
stock
|
||||
xml:
|
||||
purchase.xml
|
||||
purchase_reporting.xml
|
||||
configuration.xml
|
||||
party.xml
|
||||
stock.xml
|
||||
|
||||
@@ -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="id"/>
|
||||
</x>
|
||||
<y>
|
||||
<field name="expense"/>
|
||||
</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="id"/>
|
||||
</x>
|
||||
<y>
|
||||
<field name="number"/>
|
||||
</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 keyword_open="1">
|
||||
<field name="number" sum="Number"/>
|
||||
<field name="expense" sum="Expense"/>
|
||||
<field name="expense_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="expense"/>
|
||||
</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="number"/>
|
||||
</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" sum="Number"/>
|
||||
<field name="expense" sum="Expense"/>
|
||||
</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. -->
|
||||
<data>
|
||||
<xpath expr="//field[@name='number']" position="before">
|
||||
<field name="product" expand="1"/>
|
||||
<field name="product_supplier" expand="1"/>
|
||||
</xpath>
|
||||
</data>
|
||||
@@ -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. -->
|
||||
<data>
|
||||
<xpath expr="//field[@name='number']" position="before">
|
||||
<field name="supplier" expand="1"/>
|
||||
</xpath>
|
||||
</data>
|
||||
Reference in New Issue
Block a user