trytonpsk-sale_goal/goal.py

715 lines
24 KiB
Python

# 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 datetime import date
from trytond.pool import Pool
from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.pyson import Eval
from trytond.wizard import Wizard, StateView, Button, StateAction, StateReport
from trytond.report import Report
from trytond.transaction import Transaction
_STATES = {
'readonly': Eval('state') != 'draft',
}
KIND = [
('salesman', 'Salesman'),
('product', 'Product'),
('category', 'Category'),
]
TYPE = [
('monthly', 'Monthly'),
('bimonthly', 'Bimonthly'),
('quarterly', 'Quarterly'),
('annual', 'Annual'),
]
_ZERO = Decimal(0)
class Goal(Workflow, ModelSQL, ModelView):
"Goal"
__name__ = 'sale.goal'
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscalyear',
required=True, states=_STATES, context={
'fiscalyear': Eval('fiscalyear')
})
company = fields.Many2One('company.company', 'Company', required=True,
states=_STATES)
lines = fields.One2Many('sale.goal.line', 'goal', 'Lines',
states=_STATES)
state = fields.Selection([
('draft', 'Draft'),
('open', 'Open'),
('closed', 'Closed'),
], 'State', readonly=True)
@classmethod
def __setup__(cls):
super(Goal, cls).__setup__()
cls._buttons.update({
'draft': {
'invisible': Eval('state') != 'open',
},
'open': {
'invisible': Eval('state') != 'draft',
},
'close': {
'invisible': Eval('state') != 'open',
},
})
cls._transitions |= set((
('draft', 'open'),
('open', 'draft'),
('open', 'closed'),
))
def get_rec_name(self, name):
if self.fiscalyear:
return self.fiscalyear.name
@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)
@staticmethod
def default_state():
return 'draft'
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, lines):
pass
@classmethod
@ModelView.button
@Workflow.transition('open')
def open(cls, lines):
pass
@classmethod
@ModelView.button
@Workflow.transition('closed')
def close(cls, lines):
pass
@classmethod
def get_periods_zeros(cls, fiscalyear):
periods_zeros = {}
for p in fiscalyear.periods:
month = p.name[-2:]
if month.isdigit() and int(month) >= 1 and int(month) <= 12:
periods_zeros[month] = _ZERO
return periods_zeros
@classmethod
def get_periods_goal(cls, fiscalyear):
periods_goal = []
for p in fiscalyear.periods:
month = p.name[-2:]
if month.isdigit() and int(month) >= 1 and int(month) <= 12:
periods_goal.append(p)
return periods_goal
class GoalLine(ModelSQL, ModelView):
"Goal Line"
__name__ = 'sale.goal.line'
goal = fields.Many2One('sale.goal', 'Goal', required=True)
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')
res = _ZERO
if self.indicator.kind in ['salesman', 'shop']:
dom_invoice = [
('invoice_date', '>=', period.start_date),
('invoice_date', '<=', period.end_date),
('type', '=', 'out'),
('state', 'in', ['posted', 'paid', 'validated']),
]
if self.indicator.kind == 'salesman':
dom_invoice.append(('salesman', '=', self.indicator.salesman))
elif self.indicator.kind == 'shop':
dom_invoice.append(('shop', '=', self.indicator.shop))
res = sum(s.untaxed_amount for s in Invoice.search(dom_invoice))
else:
dom_invoice = [
('invoice.invoice_date', '>=', period.start_date),
('invoice.invoice_date', '<=', period.end_date),
('invoice.state', 'in', ['posted', 'paid', 'validated']),
]
if self.indicator.kind == 'product':
dom_invoice.append(('product', '=', self.indicator.product))
elif self.indicator.kind == 'category':
dom_invoice.append(('product.template.account_category', '=', self.indicator.category))
res = sum([sl.amount for sl in InvoiceLine.search(dom_invoice)])
return res
class SaleIndicator(ModelSQL, ModelView):
"Sale Indicator"
__name__ = 'sale.indicator'
# _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', 'in', ['goods', 'services']),
('template.salable', '=', True),
])
category = fields.Many2One('product.category', 'Category')
amount = fields.Numeric('Amount', digits=(16, 2), required=False)
@classmethod
def __register__(cls, module_name):
super(SaleIndicator, cls).__register__(module_name)
table = cls.__table_handler__(module_name)
if table.column_exist('name'):
table.drop_column('name')
if table.column_exist('kind'):
table.drop_column('kind')
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):
__name__ = 'sale.goal.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Goal = pool.get('sale.goal')
total_amounts = []
lines = {}
for obj in records:
report_context.update(obj.get_periods_zeros(obj.fiscalyear))
for line in obj.lines:
if line.indicator.id not in lines.keys():
lines[line.indicator.id] = {
'name': line.indicator.name,
'total': _ZERO,
}
lines[line.indicator.id].update(Goal.get_periods_zeros(obj.fiscalyear))
month = line.period.name[-2:]
lines[line.indicator.id][month] = line.amount
lines[line.indicator.id]['total'] += line.amount
data[month] += line.amount
total_amounts.append(line.amount)
report_context['fiscalyear'] = obj.fiscalyear.name
report_context['data'] = data
report_context['sum_total_amount'] = sum(total_amounts)
report_context['objects'] = lines.values()
return report_context
class SaleGoalJournalStart(ModelView):
'Sale Goal Journal Start'
__name__ = 'sale.goal_journal.start'
company = fields.Many2One('company.company', 'Company', required=True)
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscalyear', required=True)
indicator = fields.Many2One('sale.indicator', 'Indicator', required=True,
domain=[('kind', 'in', ('salesman', 'shop'))])
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():
return Transaction().context.get('company')
@fields.depends('fiscalyear')
def on_change_fiscalyear(self):
self.period = None
@staticmethod
def default_fiscalyear():
FiscalYear = Pool().get('account.fiscalyear')
return FiscalYear.find(
Transaction().context.get('company'), exception=False)
class SaleGoalJournal(Wizard):
'Sale Goal Journal'
__name__ = 'sale.goal_journal'
start = StateView('sale.goal_journal.start',
'sale_goal.sale_goal_journal_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('sale_goal.goal_journal_report')
def do_print_(self, action):
indicator_id = None
if self.start.indicator:
indicator_id = self.start.indicator.id
data = {
'company': self.start.company.id,
'fiscalyear': self.start.fiscalyear.id,
'period': self.start.period.id,
'indicator': indicator_id,
}
return action, data
def transition_print_(self):
return 'end'
class SaleGoalJournalReport(Report):
__name__ = 'sale_goal.goal_journal_report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Invoice = pool.get('account.invoice')
Goal = pool.get('sale.goal')
Fiscalyear = pool.get('account.fiscalyear')
Period = pool.get('account.period')
Indicator = pool.get('sale.indicator')
GoalLine = pool.get('sale.goal.line')
fiscalyear = Fiscalyear(data['fiscalyear'])
goals = Goal.search([
('fiscalyear', '=', data['fiscalyear']),
])
period = Period(data['period'])
indicator = Indicator(data['indicator'])
goal_amount = _ZERO
if goals:
goal = goals[0]
lines = GoalLine.search([
('goal', '=', goal.id),
('period', '=', data['period']),
('indicator', '=', data['indicator']),
])
if lines:
goal_amount = lines[0].amount
kind = getattr(indicator, 'kind')
val_id = getattr(indicator, kind)
dom_sale = [
('invoice_date', '>=', period.start_date),
('invoice_date', '<=', period.end_date),
('type', '=', 'out'),
('state', 'in', ['posted', 'paid', 'validated']),
(kind, '=', val_id),
]
records = Invoice.search(dom_sale, order=[('invoice_date', 'ASC')])
result = sum(invoice.untaxed_amount for invoice in records)
report_context['records'] = records
report_context['year'] = fiscalyear.name
report_context['total_amount'] = result
report_context['today'] = date.today()
report_context['period'] = period.name
report_context['goal_amount'] = goal_amount
report_context['result'] = result
report_context['indicator'] = indicator.name
if goal_amount > _ZERO:
achieve = (float(result) / float(goal_amount)) * 100
else:
achieve = _ZERO
report_context['achieve'] = achieve
return report_context
class SaleGoalMonthRankingStart(ModelView):
'Goal Month Ranking Start'
__name__ = 'sale_goal.month_ranking.start'
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year', required=True)
company = fields.Many2One('company.company', 'Company', 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')
@fields.depends('fiscalyear')
def on_change_fiscalyear(self):
self.period = None
@staticmethod
def default_fiscalyear():
FiscalYear = Pool().get('account.fiscalyear')
return FiscalYear.find(
Transaction().context.get('company'), exception=False)
class SaleGoalMonthRanking(Wizard):
'Goal Month Ranking'
__name__ = 'sale_goal.month_ranking'
start = StateView('sale_goal.month_ranking.start',
'sale_goal.sale_goal_month_ranking_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateAction('sale_goal.report_sale_goal_month_ranking')
def do_print_(self, action):
data = {
'company': self.start.company.id,
'fiscalyear': self.start.fiscalyear.id,
'period': self.start.period.id,
}
return action, data
def transition_print_(self):
return 'end'
class SaleGoalMonthRankingReport(Report):
__name__ = 'sale_goal.month_ranking.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
GoalLine = pool.get('sale.goal.line')
Fiscalyear = pool.get('account.fiscalyear')
Period = pool.get('account.period')
fiscalyear = Fiscalyear(data['fiscalyear'])
period = Period(data['period'])
goal_lines = GoalLine.search([
('goal.fiscalyear', '=', data['fiscalyear']),
('period', '=', data['period']),
])
sum_goal_amount = 0
sum_total_amount = 0
objects = []
for line in goal_lines:
sales_period = line.get_sales_period(period)
# setattr(line, 'total_amount', sales_period)
achieve = (float(sales_period) / float(line.amount)) * 100
# setattr(line, 'achieve', achieve)
sum_goal_amount += line.amount
sum_total_amount += sales_period
objects.append({'line': line, 'total_amount': sales_period, 'achieve': achieve})
avg_achieve = 0
if sum_goal_amount:
avg_achieve = (float(sum_total_amount) / float(sum_goal_amount)) * 100
report_context['records'] = objects
report_context['fiscalyear'] = fiscalyear.name
report_context['period'] = period.name
report_context['sum_goal_amount'] = sum_goal_amount
report_context['sum_total_amount'] = sum_total_amount
report_context['avg_achieve'] = avg_achieve
return report_context
class SaleGoalAnnualRankingStart(ModelView):
'Goal Annual Ranking Start'
__name__ = 'sale_goal.annual_ranking.start'
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year', required=True)
company = fields.Many2One('company.company', 'Company', required=True)
indicator = fields.Selection([
('salesman', 'Salesman'),
('by_category', 'By Category'),
], 'Indicator', required=True)
report_kind = fields.Selection([
('profit', 'Profit'),
('total_sale', 'Total Sale'),
], 'Report Kind', required=True)
@staticmethod
def default_report_class():
return 'total_sale'
@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)
class SaleGoalAnnualRanking(Wizard):
'Goal Annual Ranking'
__name__ = 'sale_goal.annual_ranking'
start = StateView('sale_goal.annual_ranking.start',
'sale_goal.sale_goal_annual_ranking_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('sale_goal.annual_ranking_report')
def do_print_(self, action):
data = {
'report_kind': self.start.report_kind,
'company': self.start.company.id,
'fiscalyear': self.start.fiscalyear.id,
'indicator': self.start.indicator,
}
return action, data
def transition_print_(self):
return 'end'
class SaleGoalAnnualRankingReport(Report):
'Goal Annual Ranking Report'
__name__ = 'sale_goal.annual_ranking_report'
@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')
indicator_field = data['indicator']
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']),
(indicator_field, '=', indicator_id),
]
invoices = Invoice.search(dom_)
if data['report_kind'] == '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 _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.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':
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_inv_cost(cls, invoice):
res = []
for line in invoice.lines:
expense = 0
if line.type != 'line':
return res
if hasattr(line.product.template, 'expense'):
expense = line.product.template.expense or Decimal('0.0')
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')
Shop = pool.get('sale.shop')
Goal = pool.get('sale.goal')
fiscalyear = Fiscalyear(data['fiscalyear'])
indicators = {}
shop_name = ''
periods_zeros = Goal.get_periods_zeros(fiscalyear)
periods_goal = Goal.get_periods_goal(fiscalyear)
sum_annual_goal = []
data.update(periods_zeros.copy())
if data['indicator'] == 'shop':
records = Shop.search([])
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':
ind_name = ind.name
elif data['indicator'] == 'by_category':
ind_name = ind.name
else:
ind_name = ind.party.name
ind_id = ind.id
else:
ind_id = 0
ind_name = 'N.D.'
if ind_id not in indicators.keys():
indicators[ind_id] = {
'indicator': ind_name,
'total': _ZERO,
'annual_goal': _ZERO,
'achieve': _ZERO,
}
indicators[ind_id].update(periods_zeros.copy())
dom_goal = [
('goal.fiscalyear', '=', data['fiscalyear'])
]
if data['indicator'] == 'shop':
attribute = 'indicator.shop'
elif data['indicator'] == 'salesman':
attribute = 'indicator.salesman'
else:
attribute = None
if attribute:
dom_goal.append(
(attribute, '=', ind_id),
)
goal_lines = GoalLine.search(dom_goal)
annual_goal = sum([l.amount for l in goal_lines])
indicators[ind_id]['annual_goal'] = annual_goal
sum_annual_goal.append(annual_goal)
res_total = _ZERO
for p in periods_goal:
nperiod = p.name[-2:]
if data['indicator'] in ('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)
indicators[ind_id]['total'] = res_total
if annual_goal > 0:
achieve = (float(res_total) / float(annual_goal)) * 100
else:
achieve = '---'
indicators[ind_id]['achieve'] = achieve
report_context['data'] = data
report_context['shop'] = shop_name
report_context['records'] = indicators.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