add goal reporting
This commit is contained in:
parent
75493bdbde
commit
a05df151f4
|
@ -3,6 +3,7 @@
|
|||
# the full copyright notices and license terms.
|
||||
from trytond.pool import Pool
|
||||
from . import goal
|
||||
from . import goal_reporting
|
||||
from . import sale
|
||||
|
||||
|
||||
|
@ -15,14 +16,14 @@ def register():
|
|||
goal.SaleGoalAnnualRankingStart,
|
||||
goal.SaleGoalJournalStart,
|
||||
sale.SaleDailyStart,
|
||||
sale.SaleMonthByShopStart,
|
||||
goal_reporting.Context,
|
||||
goal_reporting.GoalIndicator,
|
||||
module='sale_goal', type_='model')
|
||||
Pool.register(
|
||||
goal.SaleGoalMonthRanking,
|
||||
goal.SaleGoalAnnualRanking,
|
||||
goal.SaleGoalJournal,
|
||||
sale.SaleDaily,
|
||||
sale.SaleMonthByShop,
|
||||
module='sale_goal', type_='wizard')
|
||||
Pool.register(
|
||||
goal.SaleGoalReport,
|
||||
|
@ -30,5 +31,4 @@ def register():
|
|||
goal.SaleGoalMonthRankingReport,
|
||||
goal.SaleGoalAnnualRankingReport,
|
||||
sale.SaleDailyReport,
|
||||
sale.SaleMonthByShopReport,
|
||||
module='sale_goal', type_='report')
|
||||
|
|
391
goal.py
391
goal.py
|
@ -15,6 +15,19 @@ _STATES = {
|
|||
'readonly': Eval('state') != 'draft',
|
||||
}
|
||||
|
||||
KIND = [
|
||||
('salesman', 'Salesman'),
|
||||
('product', 'Product'),
|
||||
('category', 'Category'),
|
||||
]
|
||||
|
||||
TYPE = [
|
||||
('monthly', 'Monthly'),
|
||||
('bimonthly', 'Bimonthly'),
|
||||
('quarterly', 'Quarterly'),
|
||||
('annual', 'Annual'),
|
||||
]
|
||||
|
||||
_ZERO = Decimal(0)
|
||||
|
||||
|
||||
|
@ -109,206 +122,38 @@ class Goal(Workflow, ModelSQL, ModelView):
|
|||
periods_goal.append(p)
|
||||
return periods_goal
|
||||
|
||||
@classmethod
|
||||
def _get_sales_by_category_month(cls, period, indicator_id, data):
|
||||
#FIXME bug when any period is missing in fiscal year
|
||||
pool = Pool()
|
||||
InvoiceLine = pool.get('account.invoice.line')
|
||||
if indicator_id == 0:
|
||||
indicator_id = None
|
||||
dom_ = [
|
||||
('invoice.company', '=', data['company']),
|
||||
('invoice.invoice_date', '>=', period.start_date),
|
||||
('invoice.invoice_date', '<=', period.end_date),
|
||||
('invoice.type', '=', 'out'),
|
||||
('invoice.shop', '=', data['shop']),
|
||||
('invoice.state', 'in', ['posted', 'paid']),
|
||||
('product.template.account_category', '=', indicator_id),
|
||||
]
|
||||
|
||||
lines = InvoiceLine.search(dom_)
|
||||
if data['report_kind'] == 'total_sale':
|
||||
amount = sum([i.amount for i in lines])
|
||||
else:
|
||||
amount = sum([i.amount - l.product.template.cost_price for l in lines])
|
||||
return amount
|
||||
|
||||
@classmethod
|
||||
def _get_sales_month(cls, period, indicator_id, data):
|
||||
#FIXME bug when any period is missing in fiscal year
|
||||
pool = Pool()
|
||||
Invoice = pool.get('account.invoice')
|
||||
if indicator_id == 0:
|
||||
indicator_id = None
|
||||
dom_ = [
|
||||
('company', '=', data['company']),
|
||||
('invoice_date', '>=', period.start_date),
|
||||
('invoice_date', '<=', period.end_date),
|
||||
('type', '=', 'out'),
|
||||
('state', 'in', ['posted', 'paid', 'validated']),
|
||||
]
|
||||
# if data.get('indicator'):
|
||||
# indicator_field = data['indicator']
|
||||
# dom.append((indicator_field, '=', indicator_id))
|
||||
|
||||
invoices = Invoice.search(dom_)
|
||||
|
||||
if data.get('report_kind') in [None, 'total_sale']:
|
||||
amount = sum([i.total_amount for i in invoices])
|
||||
else:
|
||||
amount = sum([i.untaxed_amount - cls.get_inv_cost(i) for i in invoices])
|
||||
return amount
|
||||
|
||||
@classmethod
|
||||
def sales_monthly(cls, data):
|
||||
pool = Pool()
|
||||
Fiscalyear = pool.get('account.fiscalyear')
|
||||
Employee = pool.get('company.employee')
|
||||
Company = pool.get('company.company')
|
||||
Shop = pool.get('sale.shop')
|
||||
fiscalyear_id = data.get('fiscalyear', None)
|
||||
|
||||
if not fiscalyear_id:
|
||||
dom = [
|
||||
('start_date', '<=', date.today()),
|
||||
('end_date', '>=', date.today()),
|
||||
]
|
||||
fiscalyears = Fiscalyear.search(dom)
|
||||
if fiscalyears:
|
||||
fiscalyear = fiscalyears[0]
|
||||
else:
|
||||
fiscalyear = Fiscalyear(fiscalyear_id)
|
||||
|
||||
fiscalyear_id = fiscalyear.id
|
||||
|
||||
report_context = {}
|
||||
indicators = {}
|
||||
shop_name = ''
|
||||
periods_zeros = cls.get_periods_zeros(fiscalyear)
|
||||
periods_goal = cls.get_periods_goal(fiscalyear)
|
||||
sum_annual_goal = []
|
||||
# data.update(periods_zeros.copy())
|
||||
if not data.get('indicator'):
|
||||
records = [Company(data['company'])]
|
||||
elif data['indicator'] == 'shop':
|
||||
records = Shop.search([])
|
||||
elif data['indicator'] == 'by_category':
|
||||
shop = Shop(data['shop'])
|
||||
records = shop.product_categories
|
||||
shop_name = shop.name
|
||||
else:
|
||||
records = []
|
||||
cursor = Transaction().connection.cursor()
|
||||
query = "SELECT salesman FROM account_invoice WHERE invoice_date>='%s' AND invoice_date<='%s' GROUP BY salesman"
|
||||
cursor.execute(query % (fiscalyear.start_date, fiscalyear.end_date))
|
||||
values = [i[0] for i in cursor.fetchall()]
|
||||
if None in values:
|
||||
records.append(None)
|
||||
values.remove(None)
|
||||
records.extend(Employee.browse(values))
|
||||
|
||||
total_year = []
|
||||
for ind in records:
|
||||
if ind:
|
||||
if not data.get('indicator'):
|
||||
# By company
|
||||
ind_name = ind.party.name
|
||||
elif data['indicator'] == 'shop':
|
||||
ind_name = ind.name
|
||||
elif data['indicator'] == 'by_category':
|
||||
ind_name = ind.name
|
||||
ind_id = ind.id
|
||||
else:
|
||||
ind_id = 0
|
||||
ind_name = 'N.D.'
|
||||
|
||||
if ind_id not in indicators.keys():
|
||||
indicators[ind_id] = {}
|
||||
indicators[ind_id].update(periods_zeros.copy())
|
||||
|
||||
dom_goal = [
|
||||
('goal.fiscalyear', '=', fiscalyear_id)
|
||||
]
|
||||
|
||||
if not data.get('indicator'):
|
||||
attribute = None
|
||||
elif data['indicator'] == 'shop':
|
||||
attribute = 'indicator.shop'
|
||||
elif data['indicator'] == 'salesman':
|
||||
attribute = 'indicator.salesman'
|
||||
|
||||
if attribute:
|
||||
dom_goal.append(
|
||||
(attribute, '=', ind_id),
|
||||
)
|
||||
goal_lines = GoalLine.search(dom_goal)
|
||||
annual_goal = sum([l.amount for l in goal_lines])
|
||||
sum_annual_goal.append(annual_goal)
|
||||
|
||||
res_total = _ZERO
|
||||
for p in periods_goal:
|
||||
nperiod = p.name[-2:]
|
||||
if data.get('indicator') in (None, 'salesman', 'shop'):
|
||||
res = cls._get_sales_month(
|
||||
p,
|
||||
ind_id,
|
||||
data,
|
||||
)
|
||||
else: # by_category
|
||||
res = cls._get_sales_by_category_month(
|
||||
p,
|
||||
ind_id,
|
||||
data,
|
||||
)
|
||||
indicators[ind_id][nperiod] = res
|
||||
|
||||
res_total += res
|
||||
# data[nperiod] += res
|
||||
total_year.append(res)
|
||||
|
||||
if annual_goal > 0:
|
||||
achieve = (float(res_total) / float(annual_goal)) * 100
|
||||
else:
|
||||
achieve = '---'
|
||||
|
||||
labels = []
|
||||
values = []
|
||||
for ind in indicators.values():
|
||||
for k, v in ind.items():
|
||||
labels.append(calendar.month_abbr[int(k)])
|
||||
values.append(v)
|
||||
|
||||
report_context['data'] = data
|
||||
report_context['shop'] = shop_name
|
||||
report_context['labels'] = labels
|
||||
report_context['values'] = values
|
||||
report_context['year'] = fiscalyear.name
|
||||
report_context['total_year'] = sum(total_year)
|
||||
report_context['sum_annual_goal'] = sum(sum_annual_goal)
|
||||
if sum(sum_annual_goal) > 0:
|
||||
res = (float(report_context['total_year']) / float(sum(sum_annual_goal))) * 100
|
||||
else:
|
||||
res = '---'
|
||||
report_context['avg_achieve'] = res
|
||||
return report_context
|
||||
|
||||
|
||||
class GoalLine(Workflow, ModelSQL, ModelView):
|
||||
class GoalLine(ModelSQL, ModelView):
|
||||
"Goal Line"
|
||||
__name__ = 'sale.goal.line'
|
||||
goal = fields.Many2One('sale.goal', 'Goal', required=True)
|
||||
indicator = fields.Many2One('sale.indicator', 'Indicator',
|
||||
required=True)
|
||||
period = fields.Many2One('account.period', 'Period', required=True,
|
||||
domain=[
|
||||
('fiscalyear', '=', Eval('_parent_goal', {}).get('fiscalyear')),
|
||||
])
|
||||
amount = fields.Numeric('Amount', digits=(16, 2), required=False)
|
||||
kind = fields.Selection(KIND, 'kind', states={
|
||||
'required': True
|
||||
})
|
||||
type = fields.Selection(TYPE, 'Type', required=True)
|
||||
|
||||
start_date = fields.Date('Start Date', required=True)
|
||||
end_date = fields.Date('End Date', required=True)
|
||||
lines = fields.One2Many('sale.indicator', 'goal_line', 'Goal Lines')
|
||||
fixed_amount = fields.Numeric('Fixed Amount', digits=(16, 2))
|
||||
type_string = type.translated('type')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(GoalLine, cls).__setup__()
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
super(GoalLine, cls).__register__(module_name)
|
||||
sql_table = cls.__table_handler__(module_name)
|
||||
|
||||
if sql_table.column_exist('indicator'):
|
||||
sql_table.drop_column('indicator')
|
||||
if sql_table.column_exist('period'):
|
||||
sql_table.drop_column('period')
|
||||
if sql_table.column_exist('amount'):
|
||||
sql_table.column_rename('amount', 'fixed_amount')
|
||||
|
||||
def get_sales_period(self, period):
|
||||
Invoice = Pool().get('account.invoice')
|
||||
InvoiceLine = Pool().get('account.invoice.line')
|
||||
|
@ -342,80 +187,42 @@ class GoalLine(Workflow, ModelSQL, ModelView):
|
|||
class SaleIndicator(ModelSQL, ModelView):
|
||||
"Sale Indicator"
|
||||
__name__ = 'sale.indicator'
|
||||
_rec_name = 'name'
|
||||
name = fields.Char('Name', states={
|
||||
'required': True,
|
||||
'readonly': True,
|
||||
}, depends=['salesman', 'shop'])
|
||||
kind = fields.Selection([
|
||||
('salesman', 'Salesman'),
|
||||
('shop', 'Shop'),
|
||||
('product', 'Product'),
|
||||
('category', 'Category'),
|
||||
], 'Kind', required=True)
|
||||
salesman = fields.Many2One('company.employee', 'Salesman',
|
||||
states={
|
||||
'required': Eval('kind') == 'salesman',
|
||||
'invisible': Eval('kind') != 'salesman',
|
||||
}, depends=['kind'])
|
||||
shop = fields.Many2One('sale.shop', 'Shop',
|
||||
states={
|
||||
'required': Eval('kind') == 'shop',
|
||||
'invisible': Eval('kind') != 'shop',
|
||||
}, depends=['kind'])
|
||||
# _rec_name = 'name'
|
||||
|
||||
name = fields.Function(fields.Char('Name'), 'get_rec_name')
|
||||
goal_line = fields.Many2One('sale.goal.line', 'Goal Line', required=True)
|
||||
salesman = fields.Many2One('company.employee', 'Salesman')
|
||||
product = fields.Many2One('product.product', 'Product',
|
||||
domain=[
|
||||
('template.type', '=', 'goods'),
|
||||
('template.type', 'in', ['goods', 'services']),
|
||||
('template.salable', '=', True),
|
||||
],
|
||||
states={
|
||||
'required': Eval('kind') == 'product',
|
||||
'invisible': Eval('kind') != 'product',
|
||||
}, depends=['kind'])
|
||||
category = fields.Many2One('product.category', 'Category',
|
||||
states={
|
||||
'required': Eval('kind') == 'category',
|
||||
'invisible': Eval('kind') != 'category',
|
||||
}, depends=['kind'])
|
||||
])
|
||||
category = fields.Many2One('product.category', 'Category')
|
||||
|
||||
@fields.depends('kind', 'name', 'shop', 'salesman')
|
||||
def on_change_kind(self):
|
||||
self.shop = None
|
||||
self.salesman = None
|
||||
self.product = None
|
||||
self.category = None
|
||||
amount = fields.Numeric('Amount', digits=(16, 2), required=False)
|
||||
|
||||
@fields.depends('kind', 'name', 'salesman', 'shop', 'product', 'category')
|
||||
def on_change_shop(self):
|
||||
if self.kind == 'shop' and self.shop:
|
||||
self.name = self.shop.name
|
||||
self.salesman = None
|
||||
self.product = None
|
||||
self.category = None
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
super(SaleIndicator, cls).__register__(module_name)
|
||||
table = cls.__table_handler__(module_name)
|
||||
|
||||
@fields.depends('kind', 'name', 'salesman', 'shop', 'product', 'category')
|
||||
def on_change_salesman(self):
|
||||
if self.kind == 'salesman' and self.salesman:
|
||||
self.name = self.salesman.party.name
|
||||
self.shop = None
|
||||
self.product = None
|
||||
self.category = None
|
||||
if table.column_exist('name'):
|
||||
table.drop_column('name')
|
||||
|
||||
@fields.depends('kind', 'name', 'salesman', 'shop', 'product', 'category')
|
||||
def on_change_product(self):
|
||||
if self.kind == 'product' and self.product:
|
||||
self.name = self.product.rec_name
|
||||
self.salesman = None
|
||||
self.shop = None
|
||||
self.category = None
|
||||
if table.column_exist('kind'):
|
||||
table.drop_column('kind')
|
||||
|
||||
@fields.depends('kind', 'name', 'salesman', 'shop', 'product', 'category')
|
||||
def on_change_category(self):
|
||||
if self.kind == 'category' and self.category:
|
||||
self.name = self.category.name
|
||||
self.salesman = None
|
||||
self.shop = None
|
||||
self.product = None
|
||||
def get_rec_name(self, name):
|
||||
if self.goal_line:
|
||||
type = ''
|
||||
if self.goal_line.type:
|
||||
type = self.goal_line.type
|
||||
attribute = self.goal_line.kind
|
||||
return getattr(self, attribute).name + '_' + type
|
||||
|
||||
def get_period_name(self, name):
|
||||
if self.goal_line and self.goal_line.type:
|
||||
pass
|
||||
|
||||
|
||||
class SaleGoalReport(Report):
|
||||
|
@ -458,9 +265,17 @@ class SaleGoalJournalStart(ModelView):
|
|||
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscalyear', required=True)
|
||||
indicator = fields.Many2One('sale.indicator', 'Indicator', required=True,
|
||||
domain=[('kind', 'in', ('salesman', 'shop'))])
|
||||
period = fields.Many2One('account.period', 'Period', domain=[
|
||||
('fiscalyear', '=', Eval('fiscalyear')),
|
||||
], depends=['fiscalyear'], required=True)
|
||||
type = fields.Selection([
|
||||
('monthly', 'Monthly'),
|
||||
('annual', 'Annual'),
|
||||
], 'Type', required=True)
|
||||
period = fields.Many2One('account.period', 'Period',
|
||||
states = {
|
||||
'required': Eval('type') == 'monthly',
|
||||
},
|
||||
domain=[
|
||||
('fiscalyear', '=', Eval('fiscalyear')),
|
||||
], depends=['fiscalyear'],)
|
||||
|
||||
@staticmethod
|
||||
def default_company():
|
||||
|
@ -661,7 +476,6 @@ class SaleGoalAnnualRankingStart(ModelView):
|
|||
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year', required=True)
|
||||
company = fields.Many2One('company.company', 'Company', required=True)
|
||||
indicator = fields.Selection([
|
||||
('shop', 'Shop'),
|
||||
('salesman', 'Salesman'),
|
||||
('by_category', 'By Category'),
|
||||
], 'Indicator', required=True)
|
||||
|
@ -669,10 +483,6 @@ class SaleGoalAnnualRankingStart(ModelView):
|
|||
('profit', 'Profit'),
|
||||
('total_sale', 'Total Sale'),
|
||||
], 'Report Kind', required=True)
|
||||
shop = fields.Many2One('sale.shop', 'Shop', states={
|
||||
'required': Eval('indicator') == 'by_category',
|
||||
'invisible': Eval('indicator') != 'by_category'
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def default_report_class():
|
||||
|
@ -682,10 +492,6 @@ class SaleGoalAnnualRankingStart(ModelView):
|
|||
def default_company():
|
||||
return Transaction().context.get('company')
|
||||
|
||||
@staticmethod
|
||||
def default_indicator():
|
||||
return 'shop'
|
||||
|
||||
@staticmethod
|
||||
def default_fiscalyear():
|
||||
FiscalYear = Pool().get('account.fiscalyear')
|
||||
|
@ -704,16 +510,11 @@ class SaleGoalAnnualRanking(Wizard):
|
|||
print_ = StateReport('sale_goal.annual_ranking_report')
|
||||
|
||||
def do_print_(self, action):
|
||||
shop_id = None
|
||||
if self.start.shop:
|
||||
shop_id = self.start.shop.id
|
||||
|
||||
data = {
|
||||
'report_kind': self.start.report_kind,
|
||||
'company': self.start.company.id,
|
||||
'fiscalyear': self.start.fiscalyear.id,
|
||||
'indicator': self.start.indicator,
|
||||
'shop': shop_id
|
||||
}
|
||||
return action, data
|
||||
|
||||
|
@ -761,10 +562,11 @@ class SaleGoalAnnualRankingReport(Report):
|
|||
('invoice.invoice_date', '>=', period.start_date),
|
||||
('invoice.invoice_date', '<=', period.end_date),
|
||||
('invoice.type', '=', 'out'),
|
||||
('invoice.shop', '=', data['shop']),
|
||||
('invoice.state', 'in', ['posted', 'paid']),
|
||||
('product.template.account_category', '=', indicator_id),
|
||||
]
|
||||
if data.get('shop'):
|
||||
dom_.append([('invoice.shop', '=', data['shop']), ])
|
||||
|
||||
lines = InvoiceLine.search(dom_)
|
||||
if data['report_kind'] == 'total_sale':
|
||||
|
@ -785,14 +587,32 @@ class SaleGoalAnnualRankingReport(Report):
|
|||
res.append((line.product.template.cost_price + expense) * Decimal(line.quantity))
|
||||
return sum(res)
|
||||
|
||||
@classmethod
|
||||
def get_records_to_eval(cls, fiscalyear, data):
|
||||
pool = Pool()
|
||||
Employee = pool.get('company.employee')
|
||||
Category = pool.get('product.category')
|
||||
records = []
|
||||
if data['indicator'] == 'by_category':
|
||||
records = Category.search([('templates', '!=', None)])
|
||||
else:
|
||||
cursor = Transaction().connection.cursor()
|
||||
query = "SELECT salesman FROM account_invoice WHERE invoice_date>='%s' AND invoice_date<='%s' GROUP BY salesman"
|
||||
cursor.execute(query % (fiscalyear.start_date, fiscalyear.end_date))
|
||||
values = [i[0] for i in cursor.fetchall()]
|
||||
if None in values:
|
||||
records.append(None)
|
||||
values.remove(None)
|
||||
records.extend(Employee.browse(values))
|
||||
return records
|
||||
|
||||
@classmethod
|
||||
def get_context(cls, records, header, data):
|
||||
report_context = super().get_context(records, header, data)
|
||||
pool = Pool()
|
||||
Fiscalyear = pool.get('account.fiscalyear')
|
||||
Employee = pool.get('company.employee')
|
||||
|
||||
Shop = pool.get('sale.shop')
|
||||
# Category = pool.get('product.category')
|
||||
Goal = pool.get('sale.goal')
|
||||
fiscalyear = Fiscalyear(data['fiscalyear'])
|
||||
|
||||
|
@ -805,22 +625,11 @@ class SaleGoalAnnualRankingReport(Report):
|
|||
data.update(periods_zeros.copy())
|
||||
if data['indicator'] == 'shop':
|
||||
records = Shop.search([])
|
||||
elif data['indicator'] == 'by_category':
|
||||
shop = Shop(data['shop'])
|
||||
records = shop.product_categories
|
||||
shop_name = shop.name
|
||||
else:
|
||||
records = []
|
||||
cursor = Transaction().connection.cursor()
|
||||
query = "SELECT salesman FROM account_invoice WHERE invoice_date>='%s' AND invoice_date<='%s' GROUP BY salesman"
|
||||
cursor.execute(query % (fiscalyear.start_date, fiscalyear.end_date))
|
||||
values = [i[0] for i in cursor.fetchall()]
|
||||
if None in values:
|
||||
records.append(None)
|
||||
values.remove(None)
|
||||
records.extend(Employee.browse(values))
|
||||
|
||||
records = cls.get_records_to_eval(fiscalyear, data)
|
||||
total_year = []
|
||||
if len(records) < 1:
|
||||
records = [None]
|
||||
for ind in records:
|
||||
if ind:
|
||||
if data['indicator'] == 'shop':
|
||||
|
|
25
goal.xml
25
goal.xml
|
@ -58,12 +58,29 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<field name="priority" eval="10"/>
|
||||
<field name="name">goal_line_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="goal_line_view_tree2">
|
||||
<record model="ir.ui.view" id="goal_line_view_list">
|
||||
<field name="model">sale.goal.line</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">goal_line_tree2</field>
|
||||
<field name="name">goal_line_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_sale_goal_line">
|
||||
<field name="name">Sale Goal Line</field>
|
||||
<field name="res_model">sale.goal.line</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_sale_goal_line_form_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="goal_line_view_list"/>
|
||||
<field name="act_window" ref="act_sale_goal_line"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_sale_goal_line_form_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="goal_line_view_form"/>
|
||||
<field name="act_window" ref="act_sale_goal_line"/>
|
||||
</record>
|
||||
<menuitem name="Sale Goal Line" parent="sale.menu_configuration"
|
||||
sequence="7" id="menu_sale_goal_line" action="act_sale_goal_line"/>
|
||||
|
||||
<record model="ir.ui.view" id="sale_indicator_view_form">
|
||||
<field name="model">sale.indicator</field>
|
||||
<field name="type">form</field>
|
||||
|
@ -74,7 +91,7 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<field name="type">tree</field>
|
||||
<field name="name">sale_indicator_tree</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_sale_indicator_tree">
|
||||
<!-- <record model="ir.action.act_window" id="act_sale_indicator_tree">
|
||||
<field name="name">Sale Indicator</field>
|
||||
<field name="res_model">sale.indicator</field>
|
||||
</record>
|
||||
|
@ -89,7 +106,7 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<field name="act_window" ref="act_sale_indicator_tree"/>
|
||||
</record>
|
||||
<menuitem name="Sale Indicator" parent="sale.menu_configuration"
|
||||
sequence="7" id="menu_sale_indicator" action="act_sale_indicator_tree"/>
|
||||
sequence="8" id="menu_sale_indicator" action="act_sale_indicator_tree"/> -->
|
||||
|
||||
<record model="ir.model.access" id="sale_goal">
|
||||
<field name="model" search="[('model', '=', 'sale.goal')]"/>
|
||||
|
|
|
@ -0,0 +1,445 @@
|
|||
# This file is part sale_shop module for Tryton.
|
||||
# The COPYRIGHT file at the top level of this repository contains the full
|
||||
# copyright notices and license terms.
|
||||
import calendar
|
||||
from decimal import Decimal
|
||||
from trytond.pool import Pool
|
||||
from trytond.model import Workflow, ModelView, ModelSQL, fields
|
||||
from trytond.pyson import Eval, If
|
||||
from trytond.wizard import Wizard, StateView, Button, StateAction, StateReport
|
||||
from trytond.report import Report
|
||||
from sql import Null, Literal, Column, With
|
||||
from sql.aggregate import Sum, Min
|
||||
from sql.conditionals import Case
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.i18n import lazy_gettext
|
||||
from itertools import tee, zip_longest
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from sql.functions import CurrentTimestamp, DateTrunc, Extract, Trunc
|
||||
from sql.operators import Concat
|
||||
from .goal import KIND, TYPE
|
||||
|
||||
type_dict = {
|
||||
'monthly': Extract('MONTH', ''),
|
||||
'bimonthly': Trunc((Extract('MONTH', '')+1)/2),
|
||||
'quarterly': Extract('QUARTER', ''),
|
||||
'annual': Extract('YEAR', ''),
|
||||
}
|
||||
|
||||
_ZERO = Decimal(0)
|
||||
|
||||
|
||||
def pairwise(iterable):
|
||||
a, b = tee(iterable)
|
||||
next(b)
|
||||
return zip_longest(a, b)
|
||||
|
||||
|
||||
class Abstract(ModelSQL):
|
||||
|
||||
company = fields.Many2One(
|
||||
'company.company', lazy_gettext("sale_goal.msg_goal_reporting_company"))
|
||||
quantity = fields.Integer(lazy_gettext("sale_goal.msg_goal_reporting_number"),
|
||||
help=lazy_gettext("sale_goal.msg_goal_reporting_number_help"))
|
||||
amount_target = fields.Numeric(
|
||||
lazy_gettext("sale_goal.msg_goal_reporting_amount_target"),
|
||||
digits=(16, Eval('currency_digits', 2)),
|
||||
depends=['currency_digits'])
|
||||
amount_achieved = fields.Numeric(
|
||||
lazy_gettext("sale_goal.msg_goal_reporting_amount_achieved"),
|
||||
digits=(16, Eval('currency_digits', 2)),
|
||||
depends=['currency_digits'])
|
||||
# percentage = fields.Numeric(
|
||||
# lazy_gettext("sale_goal.msg_goal_reporting_percentage"),
|
||||
# digits=(16, Eval('currency_digits', 2)),
|
||||
# depends=['currency_digits'])
|
||||
|
||||
time_series = None
|
||||
|
||||
currency = fields.Function(fields.Many2One(
|
||||
'currency.currency',
|
||||
lazy_gettext("sale.msg_sale_reporting_currency")),
|
||||
'get_currency')
|
||||
currency_digits = fields.Function(
|
||||
fields.Integer(
|
||||
lazy_gettext("sale.msg_sale_reporting_currency_digits")),
|
||||
'get_currency_digits')
|
||||
|
||||
@classmethod
|
||||
def table_query(cls):
|
||||
from_item, tables, withs = cls._joins()
|
||||
print(from_item.select(*cls._columns(tables, withs),
|
||||
where=cls._where(tables, withs),
|
||||
group_by=cls._group_by(tables, withs),
|
||||
with_=withs.values()))
|
||||
return from_item.select(*cls._columns(tables, withs),
|
||||
where=cls._where(tables, withs),
|
||||
group_by=cls._group_by(tables, withs),
|
||||
with_=withs.values())
|
||||
|
||||
@classmethod
|
||||
def _joins(cls):
|
||||
pool = Pool()
|
||||
Company = pool.get('company.company')
|
||||
Currency = pool.get('currency.currency')
|
||||
Line = pool.get('account.invoice.line')
|
||||
Invoice = pool.get('account.invoice')
|
||||
|
||||
tables = {}
|
||||
tables['line'] = line = Line.__table__()
|
||||
tables['line.invoice'] = invoice = Invoice.__table__()
|
||||
tables['line.invoice.company'] = company = Company.__table__()
|
||||
|
||||
withs = {}
|
||||
currency_invoice = With(query=Currency.currency_rate_sql())
|
||||
withs['currency_invoice'] = currency_invoice
|
||||
currency_company = With(query=Currency.currency_rate_sql())
|
||||
withs['currency_company'] = currency_company
|
||||
|
||||
from_item = (line
|
||||
.join(invoice, condition=line.invoice == invoice.id)
|
||||
.join(currency_invoice,
|
||||
condition=(invoice.currency == currency_invoice.currency)
|
||||
& (currency_invoice.start_date <= invoice.invoice_date)
|
||||
& ((currency_invoice.end_date == Null)
|
||||
| (currency_invoice.end_date >= invoice.invoice_date))
|
||||
)
|
||||
.join(company, condition=invoice.company == company.id)
|
||||
.join(currency_company,
|
||||
condition=(company.currency == currency_company.currency)
|
||||
& (currency_company.start_date <= invoice.invoice_date)
|
||||
& ((currency_company.end_date == Null)
|
||||
| (currency_company.end_date >= invoice.invoice_date))
|
||||
))
|
||||
return from_item, tables, withs
|
||||
|
||||
@classmethod
|
||||
def _columns(cls, tables, withs):
|
||||
line = tables['line']
|
||||
invoice = tables['line.invoice']
|
||||
currency_company = withs['currency_company']
|
||||
currency_invoice = withs['currency_invoice']
|
||||
|
||||
quantity = line.quantity
|
||||
amount_achieved = cls.amount_achieved.sql_cast(
|
||||
Sum(quantity * line.unit_price
|
||||
* currency_company.rate / currency_invoice.rate))
|
||||
return [
|
||||
cls._column_id(tables, withs).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'),
|
||||
invoice.company.as_('company'),
|
||||
amount_achieved.as_('amount_achieved'),
|
||||
Sum(quantity).as_('quantity'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _column_id(cls, tables, withs):
|
||||
line = tables['line']
|
||||
return Min(line.id)
|
||||
|
||||
@classmethod
|
||||
def _group_by(cls, tables, withs):
|
||||
invoice = tables['line.invoice']
|
||||
return [invoice.company]
|
||||
|
||||
@classmethod
|
||||
def _having(cls, tables, withs):
|
||||
having = (0 == 0)
|
||||
return having
|
||||
|
||||
@classmethod
|
||||
def _where(cls, tables, withs):
|
||||
context = Transaction().context
|
||||
invoice = tables['line.invoice']
|
||||
|
||||
where = invoice.company == context.get('company')
|
||||
where &= invoice.state.in_(cls._invoice_states())
|
||||
from_date = context.get('from_date')
|
||||
if from_date:
|
||||
where &= invoice.invoice_date >= from_date
|
||||
to_date = context.get('to_date')
|
||||
if to_date:
|
||||
where &= invoice.invoice_date <= to_date
|
||||
return where
|
||||
|
||||
# @classmethod
|
||||
# def _column_amount_achieved(cls, tables, withs, sign):
|
||||
# move = tables['move']
|
||||
# currency = withs['currency_rate']
|
||||
# currency_company = withs['currency_rate_company']
|
||||
# return Case(Sum(
|
||||
# sign * cls.revenue.sql_cast(move.quantity) * move.unit_price
|
||||
# * currency_company.rate / currency.rate))
|
||||
|
||||
@classmethod
|
||||
def _invoice_states(cls):
|
||||
return ['posted', 'paid', 'validated']
|
||||
|
||||
@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()
|
||||
|
||||
def get_currency(self, name):
|
||||
return self.company.currency.id
|
||||
|
||||
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, withs):
|
||||
return super(AbstractTimeseries, cls)._columns(tables, withs) + [
|
||||
cls._column_date(tables, withs).as_('date')]
|
||||
|
||||
@classmethod
|
||||
def _column_date(cls, tables, withs):
|
||||
context = Transaction().context
|
||||
move = tables['line.move']
|
||||
date = DateTrunc(context.get('period'), move.date)
|
||||
date = cls.date.sql_cast(date)
|
||||
return date
|
||||
|
||||
@classmethod
|
||||
def _group_by(cls, tables, withs):
|
||||
return super(AbstractTimeseries, cls)._group_by(tables, withs) + [
|
||||
cls._column_date(tables, withs)]
|
||||
|
||||
|
||||
class Context(ModelView):
|
||||
"Goal Reporting Context"
|
||||
__name__ = 'goal.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)
|
||||
type = fields.Selection(TYPE, 'Type', required=True)
|
||||
|
||||
kind = fields.Selection(KIND, 'kind', states={
|
||||
'required': True
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def default_company(cls):
|
||||
return Transaction().context.get('company')
|
||||
|
||||
@classmethod
|
||||
def default_type(cls):
|
||||
return 'monthly'
|
||||
|
||||
@classmethod
|
||||
def default_kind(cls):
|
||||
return 'salesman'
|
||||
|
||||
@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')
|
||||
|
||||
|
||||
class GoalIndicatorMixin(object):
|
||||
__slots__ = ()
|
||||
indicator = fields.Many2One(
|
||||
'sale.indicator', "Indicator",
|
||||
context={
|
||||
'company': Eval('company', -1),
|
||||
},
|
||||
depends=['company'])
|
||||
year = fields.Numeric('Year')
|
||||
period = fields.Numeric('Period')
|
||||
year_period = fields.Char('Year Period')
|
||||
|
||||
@classmethod
|
||||
def _joins(cls):
|
||||
from_item, tables, withs = super(GoalIndicatorMixin, cls)._joins()
|
||||
pool = Pool()
|
||||
Indicator = pool.get('sale.indicator')
|
||||
GoalLine = pool.get('sale.goal.line')
|
||||
Goal = pool.get('sale.goal')
|
||||
Product = pool.get('product.product')
|
||||
TemplateCategory = pool.get('product.template-product.category.all')
|
||||
|
||||
tables['line.product'] = product = Product.__table__()
|
||||
tables['line.product.template_category'] = template_category = TemplateCategory.__table__()
|
||||
tables['sale.indicator'] = indicator = Indicator.__table__()
|
||||
tables['sale.goal.line'] = goal_line = GoalLine.__table__()
|
||||
tables['sale.goal'] = goal = Goal.__table__()
|
||||
invoice = tables['line.invoice']
|
||||
line = tables['line']
|
||||
|
||||
context = Transaction().context
|
||||
kind = context['kind']
|
||||
if kind == 'salesman':
|
||||
from_item = (from_item
|
||||
.join(indicator, condition=indicator.salesman == invoice.salesman))
|
||||
elif kind == 'category':
|
||||
from_item = (from_item
|
||||
.join(product, condition=line.product == product.id)
|
||||
.join(template_category,
|
||||
condition=product.template == template_category.template)
|
||||
.join(indicator,
|
||||
condition=indicator.category == template_category.id))
|
||||
elif kind == 'product':
|
||||
from_item = (from_item
|
||||
.join(indicator, condition=indicator.product == line.product))
|
||||
from_item = (from_item
|
||||
.join(goal_line, condition=goal_line.id == indicator.goal_line))
|
||||
return from_item, tables, withs
|
||||
|
||||
@classmethod
|
||||
def _columns(cls, tables, withs):
|
||||
indicator = tables['sale.indicator']
|
||||
invoice = tables['line.invoice']
|
||||
type_year_period = cls.year_period.sql_type().base
|
||||
columns_ = [
|
||||
indicator.id.as_('indicator'),
|
||||
Extract('YEAR', invoice.invoice_date).as_('year'),
|
||||
cls._column_date(tables, withs).as_('period'),
|
||||
indicator.amount.as_('amount_target'),
|
||||
Concat(Extract('YEAR', invoice.invoice_date).cast(type_year_period), cls._column_date(tables, withs).cast(type_year_period)).as_('year_period')
|
||||
]
|
||||
return super(GoalIndicatorMixin, cls)._columns(tables, withs) + columns_
|
||||
|
||||
@classmethod
|
||||
def _column_date(cls, tables, withs):
|
||||
context = Transaction().context
|
||||
invoice = tables['line.invoice']
|
||||
type = context.get('type')
|
||||
if type == 'monthly':
|
||||
date = Extract('MONTH', invoice.invoice_date)
|
||||
elif type == 'bimonthly':
|
||||
date = Trunc((Extract('MONTH', invoice.date)+1)/2)
|
||||
elif type == 'quarterly':
|
||||
date = Extract('QUARTER', invoice.date),
|
||||
elif type == 'annual':
|
||||
date = Extract('YEAR', invoice.date),
|
||||
|
||||
date = cls.period.sql_cast(date)
|
||||
return date
|
||||
|
||||
@classmethod
|
||||
def _group_by(cls, tables, withs):
|
||||
indicator = tables['sale.indicator']
|
||||
invoice = tables['line.invoice']
|
||||
type_year_period = cls.year_period.sql_type().base
|
||||
group_by_ = [
|
||||
indicator.id,
|
||||
Extract('YEAR', invoice.invoice_date),
|
||||
cls._column_date(tables, withs),
|
||||
indicator.amount.as_('amount_target'),
|
||||
Concat(Extract('YEAR', invoice.invoice_date).cast(type_year_period), cls._column_date(tables, withs).cast(type_year_period))
|
||||
]
|
||||
return super(GoalIndicatorMixin, cls)._group_by(tables, withs) + group_by_
|
||||
|
||||
@classmethod
|
||||
def _having(cls, tables, withs):
|
||||
having = super(GoalIndicatorMixin, cls)._having(tables, withs)
|
||||
|
||||
having &= (0 == 0)
|
||||
return having
|
||||
|
||||
# def get_rec_name(self, name):
|
||||
# return self.indicator.rec_name
|
||||
|
||||
@classmethod
|
||||
def _where(cls, tables, withs):
|
||||
where = super(GoalIndicatorMixin, cls)._where(tables, withs)
|
||||
context = Transaction().context
|
||||
invoice = tables['line.invoice']
|
||||
|
||||
where = invoice.company == context.get('company')
|
||||
where &= invoice.state.in_(cls._invoice_states())
|
||||
from_date = context.get('from_date')
|
||||
if from_date:
|
||||
where &= invoice.invoice_date >= from_date
|
||||
to_date = context.get('to_date')
|
||||
if to_date:
|
||||
where &= invoice.invoice_date <= to_date
|
||||
return where
|
||||
|
||||
|
||||
class GoalIndicator(GoalIndicatorMixin, Abstract, ModelView):
|
||||
"Goal Reporting per Indicator"
|
||||
__name__ = 'goal.reporting.indicator'
|
||||
|
||||
# time_series = fields.One2Many(
|
||||
# 'goal.reporting.indicator.time_series', 'Indicator', "Time Series")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
# cls._order.insert(0, ('indicator', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def _column_id(cls, tables, withs):
|
||||
indicator = tables['sale.indicator']
|
||||
return indicator.id
|
|
@ -0,0 +1,101 @@
|
|||
<?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="Goals"
|
||||
parent="sale.menu_reporting"
|
||||
sequence="10"
|
||||
id="menu_reporting_goal"
|
||||
icon="tryton-graph"/>
|
||||
|
||||
<record model="ir.ui.menu-res.group"
|
||||
id="menu_reporting_goal_group_sale_admin">
|
||||
<field name="menu" ref="menu_reporting_goal"/>
|
||||
<field name="group" ref="sale.group_sale_admin"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="goal_reporting_context_view_form">
|
||||
<field name="model">goal.reporting.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">goal_reporting_context_form</field>
|
||||
</record>
|
||||
|
||||
<!-- Goal -->
|
||||
|
||||
<record model="ir.ui.view" id="reporting_indicator_view_list">
|
||||
<field name="model">goal.reporting.indicator</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">goal_reporting_indicator_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_indicator_view_graph_amount_achieved">
|
||||
<field name="model">goal.reporting.indicator</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">goal_reporting_indicator_graph_amount_achieved</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="reporting_indicator_view_graph_percentage">
|
||||
<field name="model">goal.reporting.indicator</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">goal_reporting_indicator_graph_percentage</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_reporting_indicator">
|
||||
<field name="name">Goal Indicators</field>
|
||||
<field name="res_model">goal.reporting.indicator</field>
|
||||
<field name="context_model">goal.reporting.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_indicator_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="reporting_indicator_view_list"/>
|
||||
<field name="act_window" ref="act_reporting_indicator"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_indicator_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="reporting_indicator_view_graph_amount_achieved"/>
|
||||
<field name="act_window" ref="act_reporting_indicator"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_reporting_indicator_view3">
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="view" ref="reporting_indicator_view_graph_percentage"/>
|
||||
<field name="act_window" ref="act_reporting_indicator"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_reporting_indicator_keyword1">
|
||||
<field name="keyword">tree_open</field>
|
||||
<field name="model" eval="'ir.ui.menu,%s' % ref('menu_reporting_goal')"/>
|
||||
<field name="action" ref="act_reporting_indicator"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_reporting_indicator_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model" search="[('model', '=', 'goal.reporting.indicator')]"/>
|
||||
<field name="global_p" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.rule" id="rule_reporting_indicator_companies">
|
||||
<field name="domain"
|
||||
eval="[('company', 'in', Eval('companies', []))]"
|
||||
pyson="1"/>
|
||||
<field name="rule_group" ref="rule_group_reporting_indicator_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_reporting_indicator">
|
||||
<field name="model" search="[('model', '=', 'goal.reporting.indicator')]"/>
|
||||
<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_indicator_goal">
|
||||
<field name="model" search="[('model', '=', 'goal.reporting.indicator')]"/>
|
||||
<field name="group" ref="sale.group_sale_admin"/>
|
||||
<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>
|
|
@ -0,0 +1,35 @@
|
|||
<?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 grouped="1">
|
||||
<record model="ir.message" id="msg_goal_reporting_company">
|
||||
<field name="text">Company</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_goal_reporting_number">
|
||||
<field name="text">#</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_account_reporting_number_help">
|
||||
<field name="text">Number of products</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_goal_reporting_amount_target">
|
||||
<field name="text">Amount Target</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_goal_reporting_amount_achieved">
|
||||
<field name="text">Amount Achieved</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_goal_reporting_percentage">
|
||||
<field name="text">Percentage</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_goal_reporting_salesman">
|
||||
<field name="text">Salesman</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_goal_reporting_category">
|
||||
<field name="text">Category</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_goal_reporting_product">
|
||||
<field name="text">Product</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
136
sale.py
136
sale.py
|
@ -3,7 +3,6 @@
|
|||
# copyright notices and license terms.
|
||||
from decimal import Decimal
|
||||
from datetime import date
|
||||
import calendar
|
||||
from trytond.pool import Pool
|
||||
from trytond.model import ModelView, fields
|
||||
from trytond.pyson import Eval
|
||||
|
@ -133,138 +132,3 @@ class SaleDailyReport(Report):
|
|||
report_context['journal'] = journal_name
|
||||
report_context['total_journal_paid'] = sum(total_journal_paid_)
|
||||
return report_context
|
||||
|
||||
|
||||
class SaleMonthByShopStart(ModelView):
|
||||
'Sale Month By Shop Start'
|
||||
__name__ = 'sale_goal.sale_month_shop.start'
|
||||
company = fields.Many2One('company.company', 'Company', required=True)
|
||||
shop = fields.Many2One('sale.shop', 'Shop', required=True,
|
||||
depends=['company'], domain=[
|
||||
('company', '=', Eval('company'))
|
||||
])
|
||||
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
||||
required=True)
|
||||
period = fields.Many2One('account.period', 'Period',
|
||||
depends=['fiscalyear'], required=True, domain=[
|
||||
('fiscalyear', '=', Eval('fiscalyear')),
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def default_company():
|
||||
return Transaction().context.get('company')
|
||||
|
||||
@staticmethod
|
||||
def default_fiscalyear():
|
||||
FiscalYear = Pool().get('account.fiscalyear')
|
||||
return FiscalYear.find(
|
||||
Transaction().context.get('company'), exception=False)
|
||||
|
||||
@fields.depends('fiscalyear')
|
||||
def on_change_fiscalyear(self):
|
||||
self.period = None
|
||||
|
||||
|
||||
class SaleMonthByShop(Wizard):
|
||||
'Sale MonthByShop'
|
||||
__name__ = 'sale_goal.sale_month_shop'
|
||||
start = StateView('sale_goal.sale_month_shop.start',
|
||||
'sale_goal.sale_month_shop_start_view_form', [
|
||||
Button('Cancel', 'end', 'tryton-cancel'),
|
||||
Button('Print', 'print_', 'tryton-ok', default=True),
|
||||
])
|
||||
print_ = StateReport('sale_goal.sale_month_shop_report')
|
||||
|
||||
def do_print_(self, action):
|
||||
data = {
|
||||
'company': self.start.company.id,
|
||||
'shop': self.start.shop.id,
|
||||
'period': self.start.period.id,
|
||||
'fiscalyear': self.start.fiscalyear.id,
|
||||
}
|
||||
return action, data
|
||||
|
||||
def transition_print_(self):
|
||||
return 'end'
|
||||
|
||||
|
||||
class SaleMonthByShopReport(Report):
|
||||
'Sale Month By Shop'
|
||||
__name__ = 'sale_goal.sale_month_shop_report'
|
||||
|
||||
@classmethod
|
||||
def get_context(cls, records, header, data):
|
||||
report_context = super().get_context(records, header, data)
|
||||
pool = Pool()
|
||||
Company = pool.get('company.company')
|
||||
Invoice = pool.get('account.invoice')
|
||||
Period = pool.get('account.period')
|
||||
Shop = pool.get('sale.shop')
|
||||
company = Company(data['company'])
|
||||
period = Period(data['period'])
|
||||
|
||||
invoices = Invoice.search([
|
||||
('company', '=', data['company']),
|
||||
('shop', '=', data['shop']),
|
||||
('invoice_date', '>=', period.start_date),
|
||||
('invoice_date', '<=', period.end_date),
|
||||
('type', '=', 'out'),
|
||||
('state', 'in', ['posted', 'paid', 'validated']),
|
||||
], order=[('invoice_date', 'ASC')])
|
||||
year = period.start_date.year
|
||||
month = period.start_date.month
|
||||
_, last_day = calendar.monthrange(year, period.start_date.month)
|
||||
|
||||
mdays = {(nday + 1): {
|
||||
'date': date(year, month, nday + 1),
|
||||
'num_invoices': [],
|
||||
'untaxed_amount': [],
|
||||
'tax_amount': [],
|
||||
'total_amount': [],
|
||||
'cash': [],
|
||||
'credit': []
|
||||
} for nday in range(last_day)}
|
||||
|
||||
sum_untaxed_amount = []
|
||||
sum_total_cash = []
|
||||
sum_total_credit = []
|
||||
sum_tax_amount = []
|
||||
sum_total_amount = []
|
||||
|
||||
def _is_credit(payment_term):
|
||||
res = []
|
||||
for line in payment_term.lines:
|
||||
res.append(sum([d.months + d.weeks + d.days
|
||||
for d in line.relativedeltas]))
|
||||
if sum(res) > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
for invoice in invoices:
|
||||
if _is_credit(invoice.payment_term):
|
||||
mdays[invoice.invoice_date.day]['credit'].append(invoice.total_amount)
|
||||
sum_total_credit.append(invoice.total_amount)
|
||||
else:
|
||||
mdays[invoice.invoice_date.day]['cash'].append(invoice.total_amount)
|
||||
sum_total_cash.append(invoice.total_amount)
|
||||
|
||||
mdays[invoice.invoice_date.day]['num_invoices'].append(1)
|
||||
mdays[invoice.invoice_date.day]['untaxed_amount'].append(invoice.untaxed_amount)
|
||||
mdays[invoice.invoice_date.day]['tax_amount'].append(invoice.tax_amount)
|
||||
mdays[invoice.invoice_date.day]['total_amount'].append(invoice.total_amount)
|
||||
|
||||
sum_untaxed_amount.append(invoice.untaxed_amount)
|
||||
sum_tax_amount.append(invoice.tax_amount)
|
||||
sum_total_amount.append(invoice.total_amount)
|
||||
|
||||
report_context['records'] = mdays.values()
|
||||
report_context['total_credit'] = sum(sum_total_credit)
|
||||
report_context['total_cash'] = sum(sum_total_cash)
|
||||
report_context['untaxed_amount'] = sum(sum_untaxed_amount)
|
||||
report_context['tax_amount'] = sum(sum_tax_amount)
|
||||
report_context['total_amount'] = sum(sum_total_amount)
|
||||
report_context['shop'] = Shop(data['shop']).name
|
||||
report_context['period'] = period.name
|
||||
report_context['company'] = company.party.name
|
||||
|
||||
return report_context
|
||||
|
|
20
sale.xml
20
sale.xml
|
@ -24,25 +24,5 @@ The COPYRIGHT file at the top level of this repository contains the full copyrig
|
|||
<menuitem parent="sale.menu_reporting" id="menu_sale_daily"
|
||||
action="wizard_print_sale_daily"/>
|
||||
|
||||
<record model="ir.action.report" id="report_sale_month_shop">
|
||||
<field name="name">Report Sale Month By Shop</field>
|
||||
<field name="model"></field>
|
||||
<field name="report_name">sale_goal.sale_month_shop_report</field>
|
||||
<field name="report">sale_goal/sale_month_by_shop.ods</field>
|
||||
<field name="template_extension">ods</field>
|
||||
<field name="translatable">False</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="sale_month_shop_start_view_form">
|
||||
<field name="model">sale_goal.sale_month_shop.start</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">sale_month_shop_start_form</field>
|
||||
</record>
|
||||
<record model="ir.action.wizard" id="wizard_print_sale_month_shop">
|
||||
<field name="name">Sale Month By Shop</field>
|
||||
<field name="wiz_name">sale_goal.sale_month_shop</field>
|
||||
</record>
|
||||
<menuitem parent="sale.menu_reporting" id="menu_sale_month_shop"
|
||||
action="wizard_print_sale_month_shop"/>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
|
|
Binary file not shown.
|
@ -1,9 +1,10 @@
|
|||
[tryton]
|
||||
version=6.0.1
|
||||
version=6.0.2
|
||||
depends:
|
||||
account
|
||||
sale
|
||||
sale_shop
|
||||
xml:
|
||||
sale.xml
|
||||
goal.xml
|
||||
goal_reporting.xml
|
||||
message.xml
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?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="operation_center" expand="1"/>
|
||||
<field name="account"/>
|
||||
<field name="debit" sum="Debit" symbol="currency"/>
|
||||
<field name="credit" sum="Credit" symbol="currency"/>
|
||||
<field name="balance" sum="Balance" symbol="currency"/>
|
||||
</tree>
|
|
@ -1,7 +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. -->
|
||||
<form >
|
||||
<form>
|
||||
<label name="fiscalyear"/>
|
||||
<field name="fiscalyear" widget="selection"/>
|
||||
<label name="company"/>
|
||||
|
@ -10,6 +10,4 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<field name="indicator"/>
|
||||
<label name="report_kind"/>
|
||||
<field name="report_kind"/>
|
||||
<label name="shop"/>
|
||||
<field name="shop"/>
|
||||
</form>
|
||||
|
|
|
@ -2,15 +2,23 @@
|
|||
<!-- 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 col="4">
|
||||
<label name="indicator"/>
|
||||
<field name="indicator"/>
|
||||
<label name="period"/>
|
||||
<field name="period"/>
|
||||
<label name="amount"/>
|
||||
<field name="amount"/>
|
||||
<label name="kind"/>
|
||||
<field name="kind"/>
|
||||
<label name="type"/>
|
||||
<field name="type"/>
|
||||
<label name="start_date"/>
|
||||
<field name="start_date"/>
|
||||
<label name="end_date"/>
|
||||
<field name="end_date"/>
|
||||
<label name="fixed_amount"/>
|
||||
<field name="fixed_amount"/>
|
||||
<label name="goal"/>
|
||||
<field name="goal"/>
|
||||
<field name="lines" colspan="4"
|
||||
view_ids="sale_goal.sale_indicator_view_tree,sale_goal.sale_indicator_view_form"/>
|
||||
<group col="4" colspan="6" id="state_buttons">
|
||||
<label name="state"/>
|
||||
<field name="state"/>
|
||||
<!-- <label name="state"/>
|
||||
<field name="state"/> -->
|
||||
<group col="5" colspan="2" id="buttons">
|
||||
<button name="done" string="Open"
|
||||
icon="tryton-forward"/>
|
||||
|
|
|
@ -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. -->
|
||||
<tree>
|
||||
<field name="kind"/>
|
||||
<field name="type"/>
|
||||
<field name="goal"/>
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="fixed_amount"/>
|
||||
</tree>
|
|
@ -1,8 +1,10 @@
|
|||
<?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 editable="1">
|
||||
<field name="indicator"/>
|
||||
<field name="period"/>
|
||||
<field name="amount"/>
|
||||
<tree>
|
||||
<field name="kind"/>
|
||||
<field name="type"/>
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="fixed_amount"/>
|
||||
</tree>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?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>
|
||||
<label name="company"/>
|
||||
<field name="company"/>
|
||||
<label name="period"/>
|
||||
<field name="period"/>
|
||||
<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="type"/>
|
||||
<field name="type"/>
|
||||
<label name="kind"/>
|
||||
<field name="kind"/>
|
||||
</form>
|
|
@ -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="indicator" expand="1"/>
|
||||
<field name="amount_target" sum="Target Amount" symbol="currency"/>
|
||||
<field name="amount_achieved" sum="Achieved Amount" symbol="currency"/>
|
||||
<!-- <field name="percentage" sum="%" /> -->
|
||||
</tree>
|
|
@ -1,7 +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 >
|
||||
<tree>
|
||||
<field name="fiscalyear"/>
|
||||
<field name="company"/>
|
||||
<field name="state"/>
|
||||
|
|
|
@ -2,16 +2,14 @@
|
|||
<!-- 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>
|
||||
<label name="kind"/>
|
||||
<field name="kind"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="salesman"/>
|
||||
<field name="salesman"/>
|
||||
<label name="shop"/>
|
||||
<field name="shop"/>
|
||||
<label name="product"/>
|
||||
<field name="product"/>
|
||||
<label name="category"/>
|
||||
<field name="category"/>
|
||||
<label name="amount"/>
|
||||
<field name="amount"/>
|
||||
</form>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<!-- 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="name"/>
|
||||
<field name="kind"/>
|
||||
<field name="salesman"/>
|
||||
<field name="shop"/>
|
||||
<field name="product"/>
|
||||
<field name="category"/>
|
||||
<field name="amount"/>
|
||||
</tree>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<?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 >
|
||||
<label name="company"/>
|
||||
<field name="company" widget="selection"/>
|
||||
<label name="fiscalyear"/>
|
||||
<field name="fiscalyear" widget="selection"/>
|
||||
<label name="shop"/>
|
||||
<field name="shop"/>
|
||||
<label name="period"/>
|
||||
<field name="period" widget="selection"/>
|
||||
</form>
|
Loading…
Reference in New Issue