Files
tryton/opportunity.py
T
Vincent van der Leun 1bc8fa688a Fix typo on state 'cancelled' in delete
issue3303
review942002
(transplanted from 3d064c98434e733508cb8d2986a464ab82b2e756)
2013-07-16 11:17:30 +02:00

775 lines
30 KiB
Python

#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
"Sales extension for managing leads and opportunities"
import datetime
import time
from trytond.model import ModelView, ModelSQL, Workflow, fields
from trytond.wizard import Wizard, StateView, StateAction, Button
from trytond.backend import FIELDS, TableHandler
from trytond.pyson import Equal, Eval, Not, In, If, Get, PYSONEncoder
from trytond.transaction import Transaction
from trytond.pool import Pool
__all__ = ['SaleOpportunity', 'SaleOpportunityLine',
'SaleOpportunityHistory', 'SaleOpportunityEmployee',
'OpenSaleOpportunityEmployeeStart', 'OpenSaleOpportunityEmployee',
'SaleOpportunityMonthly', 'SaleOpportunityEmployeeMonthly']
STATES = [
('lead', 'Lead'),
('opportunity', 'Opportunity'),
('converted', 'Converted'),
('cancelled', 'Cancelled'),
('lost', 'Lost'),
]
_STATES_START = {
'readonly': Eval('state') != 'lead',
}
_DEPENDS_START = ['state']
_STATES_STOP = {
'readonly': In(Eval('state'), ['converted', 'lost', 'cancelled']),
}
_DEPENDS_STOP = ['state']
class SaleOpportunity(Workflow, ModelSQL, ModelView):
'Sale Opportunity'
__name__ = "sale.opportunity"
_history = True
_rec_name = 'description'
party = fields.Many2One('party.party', 'Party', required=True, select=True,
on_change=['party'], states=_STATES_STOP, depends=_DEPENDS_STOP)
address = fields.Many2One('party.address', 'Address',
domain=[('party', '=', Eval('party'))],
select=True, depends=['party', 'state'],
states=_STATES_STOP)
company = fields.Many2One('company.company', 'Company', required=True,
select=True, states=_STATES_STOP, domain=[
('id', If(In('company', Eval('context', {})), '=', '!='),
Get(Eval('context', {}), 'company', 0)),
], on_change=['company'], depends=_DEPENDS_STOP)
currency = fields.Function(fields.Many2One('currency.currency',
'Currency'), 'get_currency')
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'get_currency_digits')
amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'], help='Estimated revenue amount')
payment_term = fields.Many2One('account.invoice.payment_term',
'Payment Term', states={
'required': Eval('state') == 'converted',
'readonly': In(Eval('state'),
['converted', 'lost', 'cancelled']),
},
depends=['state'])
employee = fields.Many2One('company.employee', 'Employee', required=True,
states=_STATES_STOP, depends=['state', 'company'],
domain=[('company', '=', Eval('company'))])
start_date = fields.Date('Start Date', required=True, select=True,
states=_STATES_START, depends=_DEPENDS_START)
end_date = fields.Date('End Date', select=True, readonly=True, states={
'invisible': Not(In(Eval('state'),
['converted', 'cancelled', 'lost'])),
}, depends=['state'])
description = fields.Char('Description', required=True,
states=_STATES_STOP, depends=_DEPENDS_STOP)
comment = fields.Text('Comment', states=_STATES_STOP,
depends=_DEPENDS_STOP)
lines = fields.One2Many('sale.opportunity.line', 'opportunity', 'Lines',
states=_STATES_STOP, depends=_DEPENDS_STOP)
state = fields.Selection(STATES, 'State', required=True, select=True,
sort=False, readonly=True)
probability = fields.Integer('Conversion Probability', required=True,
states={
'readonly': Not(In(Eval('state'), ['opportunity', 'lead'])),
}, depends=['state'], help="Percentage between 0 and 100")
history = fields.One2Many('sale.opportunity.history', 'opportunity',
'History', readonly=True)
lost_reason = fields.Text('Reason for loss', states={
'invisible': Eval('state') != 'lost',
}, depends=['state'])
sale = fields.Many2One('sale.sale', 'Sale', readonly=True, states={
'invisible': Eval('state') != 'converted',
}, depends=['state'])
@classmethod
def __setup__(cls):
super(SaleOpportunity, cls).__setup__()
cls._order.insert(0, ('start_date', 'DESC'))
cls._sql_constraints += [
('check_percentage',
'CHECK(probability >= 0 AND probability <= 100)',
'Probability must be between 0 and 100!')
]
cls._error_messages.update({
'delete_cancel': 'Sale Opportunity "%s" must be cancelled '\
'before deletion!',
})
cls._transitions |= set((
('lead', 'opportunity'),
('lead', 'lost'),
('lead', 'cancelled'),
('opportunity', 'converted'),
('opportunity', 'lead'),
('opportunity', 'lost'),
('opportunity', 'cancelled'),
('lost', 'lead'),
('cancelled', 'lead'),
))
cls._buttons.update({
'lead': {
'invisible': ~Eval('state').in_(
['cancelled', 'lost', 'opportunity']),
'icon': If(Eval('state').in_(['cancelled', 'lost']),
'tryton-clear', 'tryton-go-previous'),
},
'opportunity': {
'invisible': ~Eval('state').in_(['lead']),
},
'convert': {
'invisible': ~Eval('state').in_(['opportunity']),
},
'lost': {
'invisible': ~Eval('state').in_(['lead', 'opportunity']),
},
'cancel': {
'invisible': ~Eval('state').in_(['lead', 'opportunity']),
},
})
@staticmethod
def default_state():
return 'lead'
@staticmethod
def default_start_date():
Date = Pool().get('ir.date')
return Date.today()
@staticmethod
def default_probability():
return 50
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_employee():
User = Pool().get('res.user')
if Transaction().context.get('employee'):
return Transaction().context['employee']
else:
user = User(Transaction().user)
if user.employee:
return user.employee.id
@classmethod
def default_payment_term(cls):
PaymentTerm = Pool().get('account.invoice.payment_term')
payment_terms = PaymentTerm.search(cls.payment_term.domain)
if len(payment_terms) == 1:
return payment_terms[0].id
def get_currency(self, name):
return self.company.currency.id
def get_currency_digits(self, name):
return self.company.currency.digits
def on_change_company(self):
res = {}
if self.company:
res['currency'] = self.company.currency.id
res['currency.rec_name'] = self.company.currency.rec_name
res['currency_digits'] = self.company.currency.digits
return res
def on_change_party(self):
PaymentTerm = Pool().get('account.invoice.payment_term')
res = {
'payment_term': None,
}
if self.party:
if self.party.customer_payment_term:
res['payment_term'] = self.party.customer_payment_term.id
res['payment_term.rec_name'] = \
self.party.customer_payment_term.rec_name
if not res['payment_term']:
res['payment_term'] = self.default_payment_term()
if res['payment_term']:
res['payment_term.rec_name'] = PaymentTerm(
res['payment_term']).rec_name
return res
def _get_sale_line_opportunity_line(self, sale):
'''
Return sale lines for each opportunity line
'''
res = {}
for line in self.lines:
if line.sale_line:
continue
sale_line = line.get_sale_line(sale)
if sale_line:
res[line.id] = sale_line
return res
def _get_sale_opportunity(self):
'''
Return sale for an opportunity
'''
Sale = Pool().get('sale.sale')
with Transaction().set_user(0, set_context=True):
return Sale(
description=self.description,
party=self.party,
payment_term=self.payment_term,
company=self.company,
invoice_address=self.address,
shipment_address=self.address,
currency=self.company.currency,
comment=self.comment,
sale_date=None,
)
def create_sale(self):
'''
Create a sale for the opportunity and return the sale
'''
Line = Pool().get('sale.opportunity.line')
if self.sale:
return
sale = self._get_sale_opportunity()
sale_lines = self._get_sale_line_opportunity_line(sale)
sale.save()
for line_id, sale_line in sale_lines.iteritems():
sale_line.sale = sale
sale_line.save()
Line.write([Line(line_id)], {
'sale_line': sale_line.id,
})
self.write([self], {
'sale': sale.id,
})
return sale
@classmethod
def delete(cls, opportunities):
# Cancel before delete
cls.cancel(opportunities)
for opportunity in opportunities:
if opportunity.state != 'cancelled':
cls.raise_user_error('delete_cancel', opportunity.rec_name)
super(SaleOpportunity, cls).delete(opportunities)
@classmethod
@ModelView.button
@Workflow.transition('lead')
def lead(cls, opportunities):
pass
@classmethod
@ModelView.button
@Workflow.transition('opportunity')
def opportunity(cls, opportunities):
pass
@classmethod
@ModelView.button
@Workflow.transition('converted')
def convert(cls, opportunities):
Date = Pool().get('ir.date')
cls.write(opportunities, {
'end_date': Date.today(),
})
for opportunity in opportunities:
opportunity.create_sale()
@classmethod
@ModelView.button
@Workflow.transition('lost')
def lost(cls, opportunities):
Date = Pool().get('ir.date')
cls.write(opportunities, {
'end_date': Date.today(),
})
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, opportunities):
Date = Pool().get('ir.date')
cls.write(opportunities, {
'end_date': Date.today(),
})
class SaleOpportunityLine(ModelSQL, ModelView):
'Sale Opportunity Line'
__name__ = "sale.opportunity.line"
_rec_name = "product"
_history = True
opportunity = fields.Many2One('sale.opportunity', 'Opportunity')
sequence = fields.Integer('Sequence',
order_field='(%(table)s.sequence IS NULL) %(order)s, '
'%(table)s.sequence %(order)s')
product = fields.Many2One('product.product', 'Product', required=True,
domain=[('salable', '=', True)], on_change=['product', 'unit'])
quantity = fields.Float('Quantity', required=True,
digits=(16, Eval('unit_digits', 2)), depends=['unit_digits'])
unit = fields.Many2One('product.uom', 'Unit', required=True)
unit_digits = fields.Function(fields.Integer('Unit Digits',
on_change_with=['unit']), 'on_change_with_unit_digits')
sale_line = fields.Many2One('sale.line', 'Sale Line', readonly=True,
states={
'invisible': (Eval('_parent_opportunity', {}).get('state')
!= 'converted'),
})
@classmethod
def __setup__(cls):
super(SaleOpportunityLine, cls).__setup__()
cls._order.insert(0, ('sequence', 'ASC'))
@classmethod
def __register__(cls, module_name):
cursor = Transaction().cursor
table = TableHandler(cursor, cls, module_name)
super(SaleOpportunityLine, cls).__register__(module_name)
# Migration from 2.4: drop required on sequence
table.not_null_action('sequence', action='remove')
def on_change_with_unit_digits(self, name=None):
if self.unit:
return self.unit.digits
return 2
def on_change_product(self):
if not self.product:
return {}
res = {}
category = self.product.sale_uom.category
if (not self.unit
or self.unit not in category.uoms):
res['unit'] = self.product.sale_uom.id
res['unit.rec_name'] = self.product.sale_uom.rec_name
res['unit_digits'] = self.product.sale_uom.digits
return res
def get_sale_line(self, sale):
'''
Return sale line for opportunity line
'''
SaleLine = Pool().get('sale.line')
with Transaction().set_user(0, set_context=True):
sale_line = SaleLine(
type='line',
quantity=self.quantity,
unit=self.unit,
product=self.product,
sale=sale,
description=None,
)
for k, v in sale_line.on_change_product().iteritems():
setattr(sale_line, k, v)
return sale_line
class SaleOpportunityHistory(ModelSQL, ModelView):
'Sale Opportunity History'
__name__ = 'sale.opportunity.history'
date = fields.DateTime('Change Date')
opportunity = fields.Many2One('sale.opportunity', 'Sale Opportunity')
user = fields.Many2One('res.user', 'User')
party = fields.Many2One('party.party', 'Party', datetime_field='date')
address = fields.Many2One('party.address', 'Address',
datetime_field='date')
company = fields.Many2One('company.company', 'Company',
datetime_field='date')
employee = fields.Many2One('company.employee', 'Employee',
datetime_field='date')
start_date = fields.Date('Start Date')
end_date = fields.Date('End Date', states={
'invisible': Not(In(Eval('state'),
['converted', 'cancelled', 'lost'])),
}, depends=['state'])
description = fields.Char('Description')
comment = fields.Text('Comment')
lines = fields.Function(fields.One2Many('sale.opportunity.line', None,
'Lines', datetime_field='date'), 'get_lines')
state = fields.Selection(STATES, 'State')
probability = fields.Integer('Conversion Probability')
lost_reason = fields.Text('Reason for loss', states={
'invisible': Not(Equal(Eval('state'), 'lost')),
}, depends=['state'])
@classmethod
def __setup__(cls):
super(SaleOpportunityHistory, cls).__setup__()
cls._order.insert(0, ('date', 'DESC'))
@classmethod
def _table_query_fields(cls):
Opportunity = Pool().get('sale.opportunity')
table = '%s__history' % Opportunity._table
return [
'MIN("%s".__id) AS id' % table,
'"%s".id AS opportunity' % table,
('MIN(COALESCE("%s".write_date, "%s".create_date)) AS date'
% (table, table)),
('COALESCE("%s".write_uid, "%s".create_uid) AS user'
% (table, table)),
] + ['"%s"."%s"' % (table, name)
for name, field in cls._fields.iteritems()
if name not in ('id', 'opportunity', 'date', 'user')
and not hasattr(field, 'set')]
@classmethod
def _table_query_group(cls):
Opportunity = Pool().get('sale.opportunity')
table = '%s__history' % Opportunity._table
return [
'"%s".id' % table,
'COALESCE("%s".write_uid, "%s".create_uid)' % (table, table),
] + ['"%s"."%s"' % (table, name)
for name, field in cls._fields.iteritems()
if name not in ('id', 'opportunity', 'date', 'user')
and not hasattr(field, 'set')]
@classmethod
def table_query(cls):
Opportunity = Pool().get('sale.opportunity')
return (('SELECT ' + (', '.join(cls._table_query_fields()))
+ ' FROM "%s__history" GROUP BY '
+ (', '.join(cls._table_query_group())))
% Opportunity._table, [])
def get_lines(self, name):
Line = Pool().get('sale.opportunity.line')
# We will always have only one id per call due to datetime_field
lines = Line.search([
('opportunity', '=', self.opportunity.id),
])
return [l.id for l in lines]
@classmethod
def read(cls, ids, fields_names=None):
res = super(SaleOpportunityHistory, cls).read(ids,
fields_names=fields_names)
# Remove microsecond from timestamp
for values in res:
if 'date' in values:
if isinstance(values['date'], basestring):
values['date'] = datetime.datetime(
*time.strptime(values['date'],
'%Y-%m-%d %H:%M:%S.%f')[:6])
values['date'] = values['date'].replace(microsecond=0)
return res
class SaleOpportunityEmployee(ModelSQL, ModelView):
'Sale Opportunity per Employee'
__name__ = 'sale.opportunity_employee'
employee = fields.Many2One('company.employee', 'Employee')
number = fields.Integer('Number')
converted = fields.Integer('Converted')
conversion_rate = fields.Function(fields.Float('Conversion Rate',
help='In %'), 'get_conversion_rate')
lost = fields.Integer('Lost')
company = fields.Many2One('company.company', 'Company')
currency = fields.Function(fields.Many2One('currency.currency',
'Currency'), 'get_currency')
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'get_currency_digits')
amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
converted_amount = fields.Numeric('Converted Amount',
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
conversion_amount_rate = fields.Function(fields.Float(
'Conversion Amount Rate', help='In %'), 'get_conversion_amount_rate')
@staticmethod
def _converted_state():
return ['converted']
@staticmethod
def _lost_state():
return ['lost']
@classmethod
def table_query(cls):
Opportunity = Pool().get('sale.opportunity')
clause = ' '
args = [True]
if Transaction().context.get('start_date'):
clause += 'AND start_date >= %s '
args.append(Transaction().context['start_date'])
if Transaction().context.get('end_date'):
clause += 'AND start_date <= %s '
args.append(Transaction().context['end_date'])
return ('SELECT DISTINCT(employee) AS id, '
'MAX(create_uid) AS create_uid, '
'MAX(create_date) AS create_date, '
'MAX(write_uid) AS write_uid, '
'MAX(write_date) AS write_date, '
'employee, '
'company, '
'COUNT(1) AS number, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in cls._converted_state()) + ') '
'THEN 1 ELSE 0 END) AS converted, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in cls._lost_state()) + ') '
'THEN 1 ELSE 0 END) AS lost, '
'SUM(amount) AS amount, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in cls._converted_state()) + ') '
'THEN amount ELSE 0 END) AS converted_amount '
'FROM "' + Opportunity._table + '" '
'WHERE %s ' + clause +
'GROUP BY employee, company', args)
def get_conversion_rate(self, name):
if self.number:
return float(self.converted) / self.number * 100.0
else:
return 0.0
def get_currency(self, name):
return self.company.currency.id
def get_currency_digits(self, name):
return self.company.currency.digits
def get_conversion_amount_rate(self, name):
if self.amount:
return float(self.converted_amount) / float(self.amount) * 100.0
else:
return 0.0
class OpenSaleOpportunityEmployeeStart(ModelView):
'Open Sale Opportunity per Employee'
__name__ = 'sale.opportunity_employee.open.start'
start_date = fields.Date('Start Date')
end_date = fields.Date('End Date')
class OpenSaleOpportunityEmployee(Wizard):
'Open Sale Opportunity per Employee'
__name__ = 'sale.opportunity_employee.open'
start = StateView('sale.opportunity_employee.open.start',
'sale_opportunity.opportunity_employee_open_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'open_', 'tryton-ok', default=True),
])
open_ = StateAction('sale_opportunity.act_opportunity_employee_form')
def do_open_(self, action):
action['pyson_context'] = PYSONEncoder().encode({
'start_date': self.start.start_date,
'end_date': self.start.end_date,
})
return action, {}
class SaleOpportunityMonthly(ModelSQL, ModelView):
'Sale Opportunity per Month'
__name__ = 'sale.opportunity_monthly'
year = fields.Char('Year')
month = fields.Integer('Month')
year_month = fields.Function(fields.Char('Year-Month'),
'get_year_month')
number = fields.Integer('Number')
converted = fields.Integer('Converted')
conversion_rate = fields.Function(fields.Float('Conversion Rate',
help='In %'), 'get_conversion_rate')
lost = fields.Integer('Lost')
company = fields.Many2One('company.company', 'Company')
currency = fields.Function(fields.Many2One('currency.currency',
'Currency'), 'get_currency')
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'get_currency_digits')
amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
converted_amount = fields.Numeric('Converted Amount',
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
conversion_amount_rate = fields.Function(fields.Float(
'Conversion Amount Rate', help='In %'), 'get_conversion_amount_rate')
@classmethod
def __setup__(cls):
super(SaleOpportunityMonthly, cls).__setup__()
cls._order.insert(0, ('year', 'DESC'))
cls._order.insert(1, ('month', 'DESC'))
@staticmethod
def _converted_state():
return ['converted']
@staticmethod
def _lost_state():
return ['lost']
@classmethod
def table_query(cls):
Opportunity = Pool().get('sale.opportunity')
type_id = FIELDS[cls.id._type].sql_type(cls.id)[0]
type_year = FIELDS[cls.year._type].sql_type(cls.year)[0]
return ('SELECT CAST(id AS ' + type_id + ') AS id, create_uid, '
'create_date, write_uid, write_date, '
'CAST(year AS ' + type_year + ') AS year, month, company, '
'number, converted, lost, amount, converted_amount '
'FROM ('
'SELECT EXTRACT(MONTH FROM start_date) + '
'EXTRACT(YEAR FROM start_date) * 100 AS id, '
'MAX(create_uid) AS create_uid, '
'MAX(create_date) AS create_date, '
'MAX(write_uid) AS write_uid, '
'MAX(write_date) AS write_date, '
'EXTRACT(YEAR FROM start_date) AS year, '
'EXTRACT(MONTH FROM start_date) AS month, '
'company, '
'COUNT(1) AS number, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in cls._converted_state()) + ') '
'THEN 1 ELSE 0 END) AS converted, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in cls._lost_state()) + ') '
'THEN 1 ELSE 0 END) AS lost, '
'SUM(amount) AS amount, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in cls._converted_state()) + ') '
'THEN amount ELSE 0 END) AS converted_amount '
'FROM "' + Opportunity._table + '" '
'GROUP BY year, month, company) AS "' + cls._table + '"',
[])
def get_conversion_rate(self, name):
if self.number:
return float(self.converted) / self.number * 100.0
else:
return 0.0
def get_year_month(self, name):
return '%s-%s' % (self.year, int(self.month))
def get_currency(self, name):
return self.company.currency.id
def get_currency_digits(self, name):
return self.company.currency.digits
def get_conversion_amount_rate(self, name):
if self.amount:
return float(self.converted_amount) / float(self.amount) * 100.0
else:
return 0.0
class SaleOpportunityEmployeeMonthly(ModelSQL, ModelView):
'Sale Opportunity per Employee per Month'
__name__ = 'sale.opportunity_employee_monthly'
year = fields.Char('Year')
month = fields.Integer('Month')
employee = fields.Many2One('company.employee', 'Employee')
number = fields.Integer('Number')
converted = fields.Integer('Converted')
conversion_rate = fields.Function(fields.Float('Conversion Rate',
help='In %'), 'get_conversion_rate')
lost = fields.Integer('Lost')
company = fields.Many2One('company.company', 'Company')
currency = fields.Function(fields.Many2One('currency.currency',
'Currency'), 'get_currency')
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'get_currency_digits')
amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
converted_amount = fields.Numeric('Converted Amount',
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
conversion_amount_rate = fields.Function(fields.Float(
'Conversion Amount Rate', help='In %'), 'get_conversion_amount_rate')
@classmethod
def __setup__(cls):
super(SaleOpportunityEmployeeMonthly, cls).__setup__()
cls._order.insert(0, ('year', 'DESC'))
cls._order.insert(1, ('month', 'DESC'))
cls._order.insert(2, ('employee', 'ASC'))
@staticmethod
def _converted_state():
return ['converted']
@staticmethod
def _lost_state():
return ['lost']
@classmethod
def table_query(cls):
Opportunity = Pool().get('sale.opportunity')
type_id = FIELDS[cls.id._type].sql_type(cls.id)[0]
type_year = FIELDS[cls.year._type].sql_type(cls.year)[0]
return ('SELECT CAST(id AS ' + type_id + ') AS id, create_uid, '
'create_date, write_uid, write_date, '
'CAST(year AS ' + type_year + ') AS year, month, '
'employee, company, number, converted, lost, amount, '
'converted_amount '
'FROM ('
'SELECT EXTRACT(MONTH FROM start_date) + '
'EXTRACT(YEAR FROM start_date) * 100 + '
'employee * 1000000 AS id, '
'MAX(create_uid) AS create_uid, '
'MAX(create_date) AS create_date, '
'MAX(write_uid) AS write_uid, '
'MAX(write_date) AS write_date, '
'EXTRACT(YEAR FROM start_date) AS year, '
'EXTRACT(MONTH FROM start_date) AS month, '
'employee, '
'company, '
'COUNT(1) AS number, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in cls._converted_state()) + ') '
'THEN 1 ELSE 0 END) AS converted, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in cls._lost_state()) + ') '
'THEN 1 ELSE 0 END) AS lost, '
'SUM(amount) AS amount, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in cls._converted_state()) + ') '
'THEN amount ELSE 0 END) AS converted_amount '
'FROM "' + Opportunity._table + '" '
'GROUP BY year, month, employee, company) '
'AS "' + cls._table + '"', [])
def get_conversion_rate(self, name):
if self.number:
return float(self.converted) / self.number * 100.0
else:
return 0.0
def get_currency(self, name):
return self.company.currency.id
def get_currency_digits(self, name):
return self.company.currency.digits
def get_conversion_amount_rate(self, name):
if self.amount:
return float(self.converted_amount) / float(self.amount) * 100.0
else:
return 0.0