Initial commit

This commit is contained in:
Cédric Krier committed 2010-07-05 23:16:33 +02:00
commit 05d9947ca3
12 files changed
+1867

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
Copyright (C) 2010 Open Labs Business Solutions.
Copyright (C) 2010 Cédric Krier.
Copyright (C) 2010 B2CK SPRL.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
+33
View File
@@ -0,0 +1,33 @@
Installing trytond_sale_opportunity
===================================
Prerequisites
-------------
* Python 2.4 or later (http://www.python.org/)
* trytond (http://www.tryton.org/)
* trytond_party (http://www.tryton.org/)
* trytond_company (http://www.tryton.org/)
* trytond_product (http://www.tryton.org/)
* trytond_sale (http://www.tryton.org/)
Installation
------------
Once you've downloaded and unpacked the trytond_sale_opportunity source
release, enter the directory where the archive was unpacked, and run:
python setup.py install
Note that you may need administrator/root privileges for this step, as
this command will by default attempt to install module to the Python
site-packages directory on your system.
For advanced options, please refer to the easy_install and/or the distutils
documentation:
http://peak.telecommunity.com/DevCenter/EasyInstall
http://docs.python.org/inst/inst.html
To use without installation, extract the archive into ``trytond/modules`` with
the directory name sale_opportunity.
+11
View File
@@ -0,0 +1,11 @@
include INSTALL
include README
include TODO
include COPYRIGHT
include CHANGELOG
include LICENSE
include *.xml
include *.odt
include *.csv
include tests/*
include doc/*
+36
View File
@@ -0,0 +1,36 @@
trytond_sale_opportunity
========================
The sale_opportunity module of the Tryton application platform.
See __tryton__.py
Installing
----------
See INSTALL
Support
-------
If you encounter any problems with Tryton, please don't hesitate to ask
questions on the Tryton bug tracker, mailing list, wiki or IRC channel:
http://bugs.tryton.org/
http://groups.tryton.org/
http://wiki.tryton.org/
irc://irc.freenode.net/tryton
License
-------
See LICENSE
Copyright
---------
See COPYRIGHT
For more information please visit the Tryton web site:
http://www.tryton.org/
+4
View File
@@ -0,0 +1,4 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from opportunity import *
+31
View File
@@ -0,0 +1,31 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
{
'name': 'Sales Leads and Opportunities',
'version': '0.0.1',
'author': 'Sharoon Thomas, Openlabs',
'email': 'info@openlabs.co.in',
'website': 'http://www.openlabs.co.in/',
'description': '''Provides:
- Leads
- Leads to Opportunities conversion
- Conversion Management
- Record of lost leads
- Opportunities to sale orders
''',
'depends': [
'party',
'company',
'product',
'sale',
'account',
'stock',
'currency',
],
'xml': [
'opportunity.xml',
'party.xml',
],
'translation': [
],
}
+846
View File
@@ -0,0 +1,846 @@
#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
from trytond.model import ModelView, ModelSQL, ModelWorkflow, fields
from trytond.wizard import Wizard
from trytond.backend import FIELDS
from trytond.pyson import Equal, Eval, Not, In, If, Get, PYSONEncoder
STATES = [
('lead', 'Lead'),
('opportunity', 'Opportunity'),
('converted', 'Converted'),
('cancelled', 'Cancelled'),
('lost', 'Lost'),
]
_STATES_START = {
'readonly': Not(Equal(Eval('state'), 'lead')),
}
_STATES_STOP = {
'readonly': In(Eval('state'), ['converted', 'lost', 'cancelled']),
}
class SaleOpportunity(ModelWorkflow, ModelSQL, ModelView):
'Sale Opportunity'
_name = "sale.opportunity"
_description = __doc__
_history = True
_rec_name = 'description'
party = fields.Many2One('party.party', 'Party', required=True, select=1,
states=_STATES_STOP, depends=['state'])
address = fields.Many2One('party.address', 'Address',
domain=[('party', '=', Eval('party'))],
select=2, depends=['party', 'state'],
states=_STATES_STOP)
company = fields.Many2One('company.company', 'Company', required=True,
select=1, states=_STATES_STOP, domain=[
('id', If(In('company', Eval('context', {})), '=', '!='),
Get(Eval('context', {}), 'company', 0)),
], on_change=['company'], depends=['state'])
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')
warehouse = fields.Many2One('stock.location', 'Warehouse',
domain=[('type', '=', 'warehouse')], states={
'required': Equal(Eval('state'), 'converted'),
'readonly': In(Eval('state'),
['converted', 'lost', 'cancelled']),
})
payment_term = fields.Many2One('account.invoice.payment_term',
'Payment Term', states={
'required': Equal(Eval('state'), 'converted'),
'readonly': In(Eval('state'),
['converted', 'lost', 'cancelled']),
})
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=1,
states=_STATES_START, depends=['state'])
end_date = fields.Date('End Date', select=2, readonly=True, states={
'invisible': Not(In(Eval('state'),
['converted', 'cancelled', 'lost'])),
}, depends=['state'])
description = fields.Char('Description', required=True,
states=_STATES_STOP, depends=['state'])
comment = fields.Text('Comment', states=_STATES_STOP)
lines = fields.One2Many('sale.opportunity.line', 'opportunity', 'Lines',
states=_STATES_STOP, depends=['state'])
state = fields.Selection(STATES, 'State', required=True, select=1,
sort=False, readonly=True)
probability = fields.Integer('Conversion Probability',
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': Not(Equal(Eval('state'), 'lost')),
}, depends=['state'])
sale = fields.Many2One('sale.sale', 'Sale', readonly=True, states={
'invisible': Not(Equal(Eval('state'), 'converted')),
}, depends=['state'])
def __init__(self):
super(SaleOpportunity, self).__init__()
self._order.insert(0, ('start_date', 'DESC'))
self._sql_constraints += [
('check_percentage',
'CHECK(probability >= 0 AND probability <= 100)',
'Probability must be between 0 and 100!')
]
self._rpc.update({
'button_lead': True,
})
def default_state(self, cursor, user, context=None):
return 'lead'
def default_start_date(self, cursor, user, context=None):
date_obj = self.pool.get('ir.date')
return date_obj.today(cursor, user, context=context)
def default_probability(self, cursor, user, context=None):
return 50
def default_company(self, cursor, user, context=None):
if context is None:
context = {}
return context.get('company', False)
def default_employee(self, cursor, user_id, context=None):
user_obj = self.pool.get('res.user')
employee_obj = self.pool.get('company.employee')
if context is None:
context = {}
employee_id = None
if context.get('employee'):
employee_id = context['employee']
else:
user = user_obj.browse(cursor, user_id, user_id, context=context)
if user.employee:
employee_id = user.employee.id
if employee_id:
return employee_id
return False
def default_payment_term(self, cursor, user, context=None):
payment_term_obj = self.pool.get('account.invoice.payment_term')
payment_term_ids = payment_term_obj.search(cursor, user,
self.payment_term.domain, context=context)
if len(payment_term_ids) == 1:
return payment_term_ids[0]
return False
def default_warehouse(self, cursor, user, context=None):
location_obj = self.pool.get('stock.location')
location_ids = location_obj.search(cursor, user,
self.warehouse.domain, context=context)
if len(location_ids) == 1:
return location_ids[0]
return False
def get_currency(self, cursor, user, ids, name, context=None):
res = {}
for opportunity in self.browse(cursor, user, ids, context=context):
res[opportunity.id] = opportunity.company.currency.id
return res
def get_currency_digits(self, cursor, user, ids, name, context=None):
res = {}
for opportunity in self.browse(cursor, user, ids, context=context):
res[opportunity.id] = opportunity.company.currency.digits
return res
def on_change_company(self, cursor, user, values, context=None):
company_obj = self.pool.get('company.company')
res = {}
if values.get('company'):
company = company_obj.browse(cursor, user, values['company'],
context=context)
res['currency'] = company.currency.id
res['currency.rec_name'] = company.currency.rec_name
res['currency_digits'] = company.currency.digits
return res
def on_change_party(self, cursor, user, values, context=None):
party_obj = self.pool.get('party.party')
payment_term_obj = self.pool.get('account.invoice.payment_term')
res = {
'payment_term': False,
}
if vals.get('party'):
party = party_obj.browse(cursor, user, vals['party'],
context=context)
if party.payment_term:
res['payment_term'] = party.payment_term.id
if not res['payment_term']:
res['payment_term'] = self.default_payment_term(cursor, user,
context=context)
if res['payment_term']:
res['payment_term.rec_name'] = payment_term_obj.browse(cursor, user,
res['payment_term'], context=context).rec_name
return res
def set_end_date(self, cursor, user, opportunity_id, state, context=None):
"""
This will fill the end_date for a lead/opportunity and change the state
:param cursor: the database cursor
:param user: the user id
:param opportunity_id: the id of the opportunity
:param state: the target state
:param context: the context
"""
date_obj = self.pool.get('ir.date')
self.write(cursor, user, opportunity_id, {
'end_date': date_obj.today(cursor, user, context=context),
'state': state,
}, context=context)
def button_lead(self, cursor, user, ids, context=None):
self.workflow_trigger_create(cursor, user, ids, context=context)
return True
def _get_sale_line_opportunity_line(self, cursor, user, opportunity,
context=None):
'''
Return sale line values for each opportunity line
:param cursor: the database cursor
:param user: the user id
:param opportunity: the BrowseRecord of opportunity
:param context: the context
:return: a dictionary with opportunity line id as key
and a dictionary of sale line values as value
'''
line_obj = self.pool.get('sale.opportunity.line')
res = {}
for line in opportunity.lines:
val = line_obj.get_sale_line(cursor, user, line, context=context)
if val:
res[line.id] = val
return res
def _get_sale_opportunity(self, cursor, user, opportunity, context=None):
'''
Return sale values for an opportunity
:param cursor: the database cursor
:param user: the user id
:param opportunity: the BrowseRecord of the opportunity
:param context: the context
:return: a dictionary with sale fields as key and sale values as value
'''
res = {
'description': opportunity.description,
'party': opportunity.party.id,
'payment_term': opportunity.payment_term.id,
'company': opportunity.company.id,
'warehouse': opportunity.warehouse.id,
'invoice_address': opportunity.address and opportunity.address.id,
'shipment_address': opportunity.address and opportunity.address.id,
'currency': opportunity.company.currency.id,
'comment': opportunity.comment,
}
return res
def create_sale(self, cursor, user, opportunity_id, context=None):
'''
Create a sale for the opportunity
:param cursor: the database cursor
:param user: the user id
:param opportunity_id: the id of the opportunity
:param context: the context
'''
sale_obj = self.pool.get('sale.sale')
sale_line_obj = self.pool.get('sale.line')
line_obj = self.pool.get('sale.opportunity.line')
if context is None:
context = {}
opportunity = self.browse(cursor, user, opportunity_id,
context=context)
values = self._get_sale_opportunity(cursor, user, opportunity,
context=context)
sale_line_values = self._get_sale_line_opportunity_line(cursor, user,
opportunity, context=context)
ctx = context.copy()
ctx['user'] = user
sale_id = sale_obj.create(cursor, 0, values, context=ctx)
for line_id, values in sale_line_values.iteritems():
values['sale'] = sale_id
sale_line_id = sale_line_obj.create(cursor, 0, values,
context=ctx)
line_obj.write(cursor, user, line_id, {
'sale_line': sale_line_id,
}, context=context)
self.write(cursor, user, opportunity_id, {
'sale': sale_id,
}, context=context)
return sale_id
SaleOpportunity()
class SaleOpportunityLine(ModelSQL, ModelView):
'Sale Opportunity Line'
_name = "sale.opportunity.line"
_description = __doc__
_rec_name = "product"
_history = True
opportunity = fields.Many2One('sale.opportunity', 'Opportunity')
sequence = fields.Integer('Sequence')
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']), 'get_unit_digits')
sale_line = fields.Many2One('sale.line', 'Sale Line', readonly=True,
states={
'invisible': Not(Equal(Eval('_parent_opportunity.state'),
'converted')),
})
def __init__(self):
super(SaleOpportunityLine, self).__init__()
self._order.insert(0, ('sequence', 'ASC'))
def on_change_with_unit_digits(self, cursor, user, vals, context=None):
uom_obj = self.pool.get('product.uom')
if vals.get('unit'):
uom = uom_obj.browse(cursor, user, vals['unit'],
context=context)
return uom.digits
return 2
def get_unit_digits(self, cursor, user, ids, name, context=None):
res = {}
for line in self.browse(cursor, user, ids, context=context):
if line.unit:
res[line.id] = line.unit.digits
else:
res[line.id] = 2
return res
def on_change_product(self, cursor, user, vals, context=None):
product_obj = self.pool.get('product.product')
if context is None:
context = {}
if not vals.get('product'):
return {}
res = {}
product = product_obj.browse(cursor, user, vals['product'],
context=context)
category = product.sale_uom.category
if not vals.get('unit') \
or vals.get('unit') not in [x.id for x in category.uoms]:
res['unit'] = product.sale_uom.id
res['unit.rec_name'] = product.sale_uom.rec_name
res['unit_digits'] = product.sale_uom.digits
return res
def get_sale_line(self, cursor, user, line, context=None):
'''
Return sale line values for opportunity line
:param cursor: the database cursor
:param user: the user id
:param line: the BrowseRecord of the line
:param context: the context
:return: a dictionary with sale line fields as key
and sale line values as value
'''
sale_line_obj = self.pool.get('sale.line')
res = {
'type': 'line',
'quantity': line.quantity,
'unit': line.unit.id,
'product': line.product.id,
}
res.update(sale_line_obj.on_change_product(cursor, user, {
'product': line.product.id,
'unit': line.unit.id,
'quantity': line.quantity,
'_parent_sale.party': line.opportunity.party.id,
'_parent_sale.currency': line.opportunity.company.currency.id,
}, context=context))
return res
SaleOpportunityLine()
class One2ManyHistory(fields.One2Many):
def get(self, cursor, user, ids, model, name, values=None,
context=None):
opportunity2id = {}
histories = model.browse(cursor, user, ids, context=context)
opportunity2id = dict((history.opportunity.id, history.id) for
history in histories)
res = super(One2ManyHistory, self).get(cursor, user,
opportunity2id.keys(), model, name, values=values,
context=context)
return dict((opportunity2id[i], j) for i, j in res.iteritems())
class SaleOpportunityHistory(ModelSQL, ModelView):
'Sale Opportunity History'
_name = 'sale.opportunity.history'
_description = __doc__
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 = One2ManyHistory('sale.opportunity.line', 'opportunity', 'Lines',
datetime_field='date')
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'])
def __init__(self):
super(SaleOpportunityHistory, self).__init__()
self._order.insert(0, ('date', 'DESC'))
def _table_query_fields(self, context=None):
return [
'MIN(__id) AS id',
'id AS opportunity',
'MIN(COALESCE(write_date, create_date)) AS date',
'COALESCE(write_uid, create_uid) AS user',
] + [name for name, field in self._columns.iteritems()
if name not in ('id', 'opportunity', 'date', 'user')
and not hasattr(field, 'set')]
def _table_query_group(self, context=None):
return [
'id',
'COALESCE(write_uid, create_uid)',
] + [name for name, field in self._columns.iteritems()
if name not in ('id', 'opportunity', 'date', 'user')
and not hasattr(field, 'set')]
def table_query(self, context=None):
opportunity_obj = self.pool.get('sale.opportunity')
return (('SELECT ' + \
(', '.join(self._table_query_fields(context=context))) + \
' FROM "%s__history" '
'GROUP BY ' + \
(', '.join(self._table_query_group(context=context)))) % \
opportunity_obj._table, [])
def read(self, cursor, user, ids, fields_names=None, context=None):
res = super(SaleOpportunityHistory, self).read(cursor, user, ids,
fields_names=fields_names, context=context)
# Remove microsecond from timestamp
for values in res:
if 'date' in values:
values['date'] = values['date'].replace(microsecond=0)
return res
SaleOpportunityHistory()
class SaleOpportunityEmployee(ModelSQL, ModelView):
'Sale Opportunity per Employee'
_name = 'sale.opportunity_employee'
_description = __doc__
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')
def _converted_state(self, context=None):
return ['converted']
def _lost_state(self, context=None):
return ['lost']
def table_query(self, context=None):
opportunity_obj = self.pool.get('sale.opportunity')
if context is None:
context = {}
clause = ' '
args = [True]
if context.get('start_date'):
clause += 'AND start_date >= %s '
args.append(context['start_date'])
if context.get('end_date'):
clause += 'AND start_date <= %s '
args.append(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 self._converted_state(context=context)) + ') '
'THEN 1 ELSE 0 END) AS converted, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in self._lost_state(context=context)) + ') '
'THEN 1 ELSE 0 END) AS lost, '
'SUM(amount) AS amount, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in self._converted_state(context=context)) + ') '
'THEN amount ELSE 0 END) AS converted_amount '
'FROM "' + opportunity_obj._table + '" '
'WHERE %s ' \
+ clause + \
'GROUP BY employee, company', args)
def get_conversion_rate(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
if record.number:
res[record.id] = float(record.converted) / record.number * 100.0
else:
res[record.id] = 0.0
return res
def get_currency(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
res[record.id] = record.company.currency.id
return res
def get_currency_digits(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
res[record.id] = record.company.currency.digits
return res
def get_conversion_amount_rate(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
if record.amount:
res[record.id] = (float(record.converted_amount) /
float(record.amount) * 100.0)
else:
res[record.id] = 0.0
return res
SaleOpportunityEmployee()
class OpenSaleOpportunityEmployeeInit(ModelView):
'Open Sale Opportunity per Employee Init'
_name = 'sale.open_opportunity_employee.init'
_description = __doc__
start_date = fields.Date('Start Date')
end_date = fields.Date('End Date')
OpenSaleOpportunityEmployeeInit()
class OpenSaleOpportunityEmployee(Wizard):
'Open Sale Opportunity per Employee'
_name = 'sale.open_opportunity_employee'
states = {
'init': {
'result': {
'type': 'form',
'object': 'sale.open_opportunity_employee.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('open', 'Open', 'tryton-ok', True),
],
},
},
'open': {
'result': {
'type': 'action',
'action': '_action_open',
'state': 'end',
},
},
}
def _action_open(self, cursor, user, data, context=None):
model_data_obj = self.pool.get('ir.model.data')
act_window_obj = self.pool.get('ir.action.act_window')
act_window_id = model_data_obj.get_id(cursor, user, 'sale_opportunity',
'act_opportunity_employee_form', context=context)
res = act_window_obj.read(cursor, user, act_window_id, context=context)
res['pyson_context'] = PYSONEncoder().encode({
'start_date': data['form']['start_date'],
'end_date': data['form']['end_date'],
})
return res
OpenSaleOpportunityEmployee()
class SaleOpportunityMonthly(ModelSQL, ModelView):
'Sale Opportunity per Month'
_name = 'sale.opportunity_monthly'
_description = __doc__
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')
def __init__(self):
super(SaleOpportunityMonthly, self).__init__()
self._order.insert(0, ('year', 'DESC'))
self._order.insert(1, ('month', 'DESC'))
def _converted_state(self, context=None):
return ['converted']
def _lost_state(self, context=None):
return ['lost']
def table_query(self, context=None):
opportunity_obj = self.pool.get('sale.opportunity')
type_id = FIELDS[self.id._type].sql_type(self.id)[0]
type_year = FIELDS[self.year._type].sql_type(self.year)[0]
return ('SELECT CAST(id AS ' + type_id + '), create_uid, create_date, '
'write_uid, write_date, '
'CAST(year AS ' + type_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 self._converted_state(context=context)) + ') '
'THEN 1 ELSE 0 END) AS converted, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in self._lost_state(context=context)) + ') '
'THEN 1 ELSE 0 END) AS lost, '
'SUM(amount) AS amount, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in self._converted_state(context=context)) + ') '
'THEN amount ELSE 0 END) AS converted_amount '
'FROM "' + opportunity_obj._table + '" '
'GROUP BY year, month, company) AS "' + self._table + '"',
[])
def get_conversion_rate(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
if record.number:
res[record.id] = float(record.converted) / record.number * 100.0
else:
res[record.id] = 0.0
return res
def get_year_month(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
res[record.id] = '%s-%s' % (record.year, int(record.month))
return res
def get_currency(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
res[record.id] = record.company.currency.id
return res
def get_currency_digits(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
res[record.id] = record.company.currency.digits
return res
def get_conversion_amount_rate(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
if record.amount:
res[record.id] = (float(record.converted_amount) /
float(record.amount) * 100.0)
else:
res[record.id] = 0.0
return res
SaleOpportunityMonthly()
class SaleOpportunityEmployeeMonthly(ModelSQL, ModelView):
'Sale Opportunity per Employee per Month'
_name = 'sale.opportunity_employee_monthly'
_description = __doc__
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')
def __init__(self):
super(SaleOpportunityEmployeeMonthly, self).__init__()
self._order.insert(0, ('year', 'DESC'))
self._order.insert(1, ('month', 'DESC'))
self._order.insert(2, ('employee', 'ASC'))
def _converted_state(self, context=None):
return ['converted']
def _lost_state(self, context=None):
return ['lost']
def table_query(self, context=None):
opportunity_obj = self.pool.get('sale.opportunity')
type_id = FIELDS[self.id._type].sql_type(self.id)[0]
type_year = FIELDS[self.year._type].sql_type(self.year)[0]
return ('SELECT CAST(id AS ' + type_id + '), create_uid, create_date, '
'write_uid, write_date, '
'CAST(year AS ' + type_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 self._converted_state(context=context)) + ') '
'THEN 1 ELSE 0 END) AS converted, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in self._lost_state(context=context)) + ') '
'THEN 1 ELSE 0 END) AS lost, '
'SUM(amount) AS amount, '
'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x
for x in self._converted_state(context=context)) + ') '
'THEN amount ELSE 0 END) AS converted_amount '
'FROM "' + opportunity_obj._table + '" '
'GROUP BY year, month, employee, company) '
'AS "' + self._table + '"', [])
def get_conversion_rate(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
if record.number:
res[record.id] = float(record.converted) / record.number * 100.0
else:
res[record.id] = 0.0
return res
def get_currency(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
res[record.id] = record.company.currency.id
return res
def get_currency_digits(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
res[record.id] = record.company.currency.digits
return res
def get_conversion_amount_rate(self, cursor, user, ids, name, context=None):
res = {}
for record in self.browse(cursor, user, ids, context=context):
if record.amount:
res[record.id] = (float(record.converted_amount) /
float(record.amount) * 100.0)
else:
res[record.id] = 0.0
return res
SaleOpportunityEmployeeMonthly()
+766
View File
@@ -0,0 +1,766 @@
<?xml version="1.0" encoding="UTF-8"?>
<tryton>
<data>
<menuitem name="Reporting" parent="sale.menu_sale"
id="menu_reporting" sequence="100"/>
<record model="res.group" id="group_opportunity">
<field name="name">Sale Opportunity</field>
</record>
<record model="res.user" id="res.user_admin">
<field name="groups" eval="[('add', ref('group_opportunity'))]"/>
</record>
<record model="ir.ui.view" id="opportunity_view_form">
<field name="model">sale.opportunity</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Sale Lead/Opportunity">
<label name="party"/>
<field name="party"/>
<label name="address"/>
<field name="address"/>
<label name="amount"/>
<group col="2" id="amount_currency">
<field name="amount"/>
<field name="currency"/>
</group>
<label name="probability"/>
<field name="probability"/>
<label name="description"/>
<field name="description" colspan="3"/>
<notebook colspan="4">
<page string="Lead/Opportunity" id="opportunity">
<label name="company"/>
<field name="company"/>
<label name="employee"/>
<field name="employee"/>
<label name="start_date"/>
<field name="start_date"/>
<label name="end_date"/>
<field name="end_date"/>
<label name="warehouse"/>
<field name="warehouse"/>
<label name="payment_term"/>
<field name="payment_term"/>
<label name="sale"/>
<field name="sale"/>
<separator name="comment" colspan="4"/>
<field name="comment" colspan="4"/>
<separator name="lost_reason" colspan="4"/>
<field name="lost_reason" colspan="4"/>
<group col="2" colspan="2" id="state">
<label name="state"/>
<field name="state"/>
</group>
<group col="6" colspan="2" id="buttons">
<button name="cancelled" string="Cancel"
states="{'invisible': Not(In(Eval('state'), ['lead', 'opportunity']))}"
icon="tryton-cancel"/>
<button name="lost_lead" string="Mark as Lost"
states="{'invisible':Not(In(Eval('state'), ['lead','opportunity']))}"
icon="tryton-cancel"/>
<button name="set_as_lead" string="Set as Lead"
states="{'invisible':Not(In(Eval('state'), ['opportunity']))}"
icon="tryton-go-previous"/>
<button name="convert_to_opportunity" string="Convert to Opportunity"
states="{'invisible': Not(In(Eval('state'), ['lead']))}"
icon="tryton-go-next"/>
<button name="convert_to_sale" string="Convert to Sale"
states="{'invisible': Not(In(Eval('state'), ['opportunity']))}"
icon="tryton-go-next"/>
<button name="button_lead" string="Reset as Lead"
type="object"
states="{'invisible': Not(In(Eval('state'), ['lost', 'cancelled']))}"
icon="tryton-clear"/>
</group>
</page>
<page name="lines">
<field name="lines"/>
</page>
<page name="history">
<field name="history"/>
</page>
</notebook>
</form>
]]>
</field>
</record>
<record model="ir.ui.view" id="opportunity_view_tree">
<field name="model">sale.opportunity</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree string="Sales Leads/Opportunities">
<field name="start_date" select="2"/>
<field name="end_date" select="2"/>
<field name="description" select="2"/>
<field name="party" select="1"/>
<field name="amount" select="1"/>
<field name="currency"/>
<field name="state" select="1"/>
<field name="probability" select="2"/>
</tree>
]]>
</field>
</record>
<record model="ir.action.act_window" id="act_opportunity_form">
<field name="name">Leads and Opportunities</field>
<field name="res_model">sale.opportunity</field>
<field name="view_type">form</field>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="opportunity_view_tree"/>
<field name="act_window" ref="act_opportunity_form"/>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="opportunity_view_form"/>
<field name="act_window" ref="act_opportunity_form"/>
</record>
<menuitem parent="sale.menu_sale" action="act_opportunity_form"
id="menu_opportunity_form" sequence="5"/>
<record model="ir.action.act_window" id="act_opportunity_form_new">
<field name="name">New Lead</field>
<field name="res_model">sale.opportunity</field>
<field name="view_type">form</field>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_new_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="opportunity_view_form"/>
<field name="act_window" ref="act_opportunity_form_new"/>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_new_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="opportunity_view_tree"/>
<field name="act_window" ref="act_opportunity_form_new"/>
</record>
<menuitem parent="menu_opportunity_form" action="act_opportunity_form_new"
id="menu_opportunity_form_new" sequence="10"/>
<record model="ir.action.act_window" id="act_opportunity_form_lead">
<field name="name">Opened Leads</field>
<field name="res_model">sale.opportunity</field>
<field name="view_type">form</field>
<field name="domain">[("state", "=", "lead")]</field>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_lead_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="opportunity_view_tree"/>
<field name="act_window" ref="act_opportunity_form_lead"/>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_lead_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="opportunity_view_form"/>
<field name="act_window" ref="act_opportunity_form_lead"/>
</record>
<menuitem parent="menu_opportunity_form" action="act_opportunity_form_lead"
id="menu_opportunity_form_lead" sequence="20"/>
<record model="ir.action.act_window" id="act_opportunity_form_opportunity">
<field name="name">Opportunities</field>
<field name="res_model">sale.opportunity</field>
<field name="view_type">form</field>
<field name="domain">[("state", "=", "opportunity")]</field>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_opportunity_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="opportunity_view_tree"/>
<field name="act_window" ref="act_opportunity_form_opportunity"/>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_opportunity_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="opportunity_view_form"/>
<field name="act_window" ref="act_opportunity_form_opportunity"/>
</record>
<menuitem parent="menu_opportunity_form" action="act_opportunity_form_opportunity"
id="menu_opportunity_form_opportunity" sequence="30"/>
<record model="ir.action.act_window" id="act_opportunity_form_converted">
<field name="name">Converted Opportunities</field>
<field name="res_model">sale.opportunity</field>
<field name="view_type">form</field>
<field name="domain">[("state", "=", "converted")]</field>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_converted_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="opportunity_view_tree"/>
<field name="act_window" ref="act_opportunity_form_converted"/>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_converted_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="opportunity_view_form"/>
<field name="act_window" ref="act_opportunity_form_converted"/>
</record>
<menuitem parent="menu_opportunity_form" action="act_opportunity_form_converted"
id="menu_opportunity_form_converted" sequence="40"/>
<record model="ir.action.act_window" id="act_opportunity_form_cancelled">
<field name="name">Cancelled Leads and Opportunities</field>
<field name="res_model">sale.opportunity</field>
<field name="view_type">form</field>
<field name="domain">[("state", "=", "cancelled")]</field>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_cancelled_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="opportunity_view_tree"/>
<field name="act_window" ref="act_opportunity_form_cancelled"/>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_cancelled_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="opportunity_view_form"/>
<field name="act_window" ref="act_opportunity_form_cancelled"/>
</record>
<menuitem parent="menu_opportunity_form" action="act_opportunity_form_cancelled"
id="menu_opportunity_form_cancelled" sequence="50"/>
<record model="ir.action.act_window" id="act_opportunity_form_lost">
<field name="name">Lost Leads and Opportunities</field>
<field name="res_model">sale.opportunity</field>
<field name="view_type">form</field>
<field name="domain">[("state", "=", "lost")]</field>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_lost_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="opportunity_view_tree"/>
<field name="act_window" ref="act_opportunity_form_lost"/>
</record>
<record model="ir.action.act_window.view" id="act_opportunity_form_lost_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="opportunity_view_form"/>
<field name="act_window" ref="act_opportunity_form_lost"/>
</record>
<menuitem parent="menu_opportunity_form" action="act_opportunity_form_lost"
id="menu_opportunity_form_lost" sequence="60"/>
<record model="ir.model.access" id="access_opportunity">
<field name="model" search="[('model', '=', 'sale.opportunity')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_opportunity_oppotunity">
<field name="model" search="[('model', '=', 'sale.opportunity')]"/>
<field name="group" ref="group_opportunity"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.rule.group" id="rule_group_opportunity">
<field name="model" search="[('model', '=', 'sale.opportunity')]"/>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_group_opportunity1">
<field name="field" search="[('name', '=', 'company'), ('model.model', '=', 'sale.opportunity')]"/>
<field name="operator">=</field>
<field name="operand">User/Current Company</field>
<field name="rule_group" ref="rule_group_opportunity"/>
</record>
<record model="workflow" id="opportunity_workflow">
<field name="name">Sales Opportunity Workflow</field>
<field name="model">sale.opportunity</field>
<field name="on_create" eval="True" />
</record>
<record model="workflow.activity" id="opportunity_activity_lead">
<field name="name">Lead</field>
<field name="workflow" ref="opportunity_workflow"/>
<field name="kind">function</field>
<field name="action">write({'state': 'lead'})</field>
<field name="flow_start" eval="True"/>
</record>
<record model="workflow.activity" id="opportunity_activity_opportunity">
<field name="name">Opportunity</field>
<field name="workflow" ref="opportunity_workflow"/>
<field name="kind">function</field>
<field name="action">write({'state': 'opportunity'})</field>
</record>
<record model="workflow.activity" id="opportunity_activity_converted">
<field name="name">Converted</field>
<field name="workflow" ref="opportunity_workflow"/>
<field name="kind">function</field>
<field name="action">set_end_date('converted')
create_sale()</field>
<field name="flow_stop" eval="True"/>
</record>
<record model="workflow.activity" id="opportunity_activity_cancelled">
<field name="name">Cancelled</field>
<field name="workflow" ref="opportunity_workflow"/>
<field name="kind">function</field>
<field name="action">set_end_date('cancelled')</field>
<field name="flow_stop" eval="True"/>
</record>
<record model="workflow.activity" id="opportunity_activity_lost">
<field name="name">Lost</field>
<field name="workflow" ref="opportunity_workflow"/>
<field name="kind">function</field>
<field name="action">set_end_date('lost')</field>
<field name="flow_stop" eval="True"/>
</record>
<record model="workflow.transition" id="opportunity_transition_lead_opportunity">
<field name="act_from" ref="opportunity_activity_lead"/>
<field name="act_to" ref="opportunity_activity_opportunity"/>
<field name="signal">convert_to_opportunity</field>
<field name="group" ref="sale.group_sale"/>
</record>
<record model="workflow.transition" id="opportunity_transition_opportunity_converted">
<field name="act_from" ref="opportunity_activity_opportunity"/>
<field name="act_to" ref="opportunity_activity_converted"/>
<field name="signal">convert_to_sale</field>
<field name="group" ref="sale.group_sale"/>
</record>
<record model="workflow.transition" id="opportunity_transition_opportunity_lead">
<field name="act_from" ref="opportunity_activity_opportunity"/>
<field name="act_to" ref="opportunity_activity_lead"/>
<field name="signal">set_as_lead</field>
<field name="group" ref="sale.group_sale"/>
</record>
<record model="workflow.transition" id="opportunity_transition_cancelled_lead">
<field name="act_from" ref="opportunity_activity_cancelled"/>
<field name="act_to" ref="opportunity_activity_lead"/>
<field name="signal">set_as_lead</field>
<field name="group" ref="sale.group_sale"/>
</record>
<record model="workflow.transition" id="opportunity_transition_lead_lost">
<field name="act_from" ref="opportunity_activity_lead"/>
<field name="act_to" ref="opportunity_activity_lost"/>
<field name="signal">lost_lead</field>
<field name="group" ref="sale.group_sale"/>
</record>
<record model="workflow.transition" id="opportunity_transition_opportunity_lost">
<field name="act_from" ref="opportunity_activity_opportunity"/>
<field name="act_to" ref="opportunity_activity_lost"/>
<field name="signal">lost_lead</field>
<field name="group" ref="sale.group_sale"/>
</record>
<record model="workflow.transition" id="opportunity_transition_lead_cancelled">
<field name="act_from" ref="opportunity_activity_lead"/>
<field name="act_to" ref="opportunity_activity_cancelled"/>
<field name="signal">cancelled</field>
<field name="group" ref="sale.group_sale"/>
</record>
<record model="workflow.transition" id="opportunity_transition_opp_cancelled">
<field name="act_from" ref="opportunity_activity_opportunity"/>
<field name="act_to" ref="opportunity_activity_cancelled"/>
<field name="signal">cancelled</field>
<field name="group" ref="sale.group_sale"/>
</record>
<record model="ir.ui.view" id="opportunity_line_view_form">
<field name="model">sale.opportunity.line</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Sale Lead/Opportunity Line">
<label name="opportunity"/>
<field name="opportunity" colspan="3"/>
<label name="product"/>
<field name="product"/>
<label name="sequence"/>
<field name="sequence"/>
<label name="quantity"/>
<field name="quantity"/>
<label name="unit"/>
<field name="unit"/>
<label name="sale_line"/>
<field name="sale_line"/>
</form>
]]>
</field>
</record>
<record model="ir.ui.view" id="opportunity_line_view_tree">
<field name="model">sale.opportunity.line</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree string="Sales Leads/Opportunities Lines" fill="1" sequence="sequence">
<field name="opportunity" select="1"/>
<field name="product" select="1"/>
<field name="quantity" select="2"/>
<field name="unit" select="2"/>
<field name="sequence" tree_invisible="1"/>
</tree>
]]>
</field>
</record>
<record model="ir.model.access" id="access_opportunity_line">
<field name="model" search="[('model', '=', 'sale.opportunity.line')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_opportunity_line_opportunity">
<field name="model" search="[('model', '=', 'sale.opportunity.line')]"/>
<field name="group" ref="group_opportunity"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="opportunity_history_view_form">
<field name="model">sale.opportunity.history</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="History">
<label name="opportunity"/>
<field name="opportunity" colspan="4"/>
<label name="user"/>
<field name="user"/>
<label name="date"/>
<field name="date"/>
<label name="party"/>
<field name="party"/>
<label name="address"/>
<field name="address"/>
<label name="description"/>
<field name="description"/>
<label name="probability"/>
<field name="probability"/>
<notebook colspan="4">
<page string="Lead/Opportunity" id="opportunity">
<label name="company"/>
<field name="company"/>
<label name="employee"/>
<field name="employee"/>
<label name="start_date"/>
<field name="start_date"/>
<label name="end_date"/>
<field name="end_date"/>
<separator name="comment" colspan="4"/>
<field name="comment" colspan="4"/>
<separator name="lost_reason" colspan="4"/>
<field name="lost_reason" colspan="4"/>
<group col="2" colspan="2" id="state">
<label name="state"/>
<field name="state"/>
</group>
</page>
<page name="lines">
<field name="lines"/>
</page>
</notebook>
</form>
]]>
</field>
</record>
<record model="ir.ui.view" id="opportunity_history_view_tree">
<field name="model">sale.opportunity.history</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree string="History">
<field name="date" select="1"/>
<field name="user" select="1"/>
<field name="start_date" select="2"/>
<field name="end_date" select="2"/>
<field name="description" select="2"/>
<field name="party" select="2"/>
<field name="state" select="1"/>
<field name="probability" select="2"/>
</tree>
]]>
</field>
</record>
<record model="ir.model.access" id="access_opportunity_history">
<field name="model" search="[('model', '=', 'sale.opportunity.history')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_opportunity_history_opportunity">
<field name="model" search="[('model', '=', 'sale.opportunity.history')]"/>
<field name="group" ref="group_opportunity"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.rule.group" id="rule_group_opportunity_history">
<field name="model" search="[('model', '=', 'sale.opportunity.history')]"/>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_group_opportunity_history1">
<field name="field" search="[('name', '=', 'company'), ('model.model', '=', 'sale.opportunity.history')]"/>
<field name="operator">=</field>
<field name="operand">User/Current Company</field>
<field name="rule_group" ref="rule_group_opportunity_history"/>
</record>
<record model="ir.ui.view" id="opportunity_employee_view_tree">
<field name="model">sale.opportunity_employee</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree string="Opportunities per Employee">
<field name="employee" select="1"/>
<field name="number" select="1"/>
<field name="converted" select="2"/>
<field name="lost" select="2"/>
<field name="conversion_rate"/>
<field name="amount" select="1"/>
<field name="converted_amount" select="2"/>
<field name="currency" select="2"/>
<field name="conversion_amount_rate"/>
</tree>
]]>
</field>
</record>
<record model="ir.ui.view" id="opportunity_employee_view_graph1">
<field name="model">sale.opportunity_employee</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<![CDATA[
<graph string="Opportunities per Employee">
<x>
<field name="employee"/>
</x>
<y>
<field name="number"/>
<field name="converted"/>
<field name="lost"/>
</y>
</graph>
]]>
</field>
</record>
<record model="ir.ui.view" id="opportunity_employee_view_graph2">
<field name="model">sale.opportunity_employee</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<![CDATA[
<graph string="Opportunities per Employee">
<x>
<field name="employee"/>
</x>
<y>
<field name="amount"/>
<field name="converted_amount"/>
</y>
</graph>
]]>
</field>
</record>
<record model="ir.action.act_window" id="act_opportunity_employee_form">
<field name="name">Opportunities per Employee</field>
<field name="res_model">sale.opportunity_employee</field>
<field name="view_type">form</field>
</record>
<record model="ir.action.act_window.view"
id="act_opportunity_employee_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="opportunity_employee_view_tree"/>
<field name="act_window" ref="act_opportunity_employee_form"/>
</record>
<record model="ir.action.act_window.view"
id="act_opportunity_employee_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="opportunity_employee_view_graph1"/>
<field name="act_window" ref="act_opportunity_employee_form"/>
</record>
<record model="ir.action.act_window.view"
id="act_opportunity_employee_form_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="opportunity_employee_view_graph2"/>
<field name="act_window" ref="act_opportunity_employee_form"/>
</record>
<record model="ir.rule.group" id="rule_group_opportunity_employee">
<field name="model" search="[('model', '=', 'sale.opportunity_employee')]"/>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_group_opportunity_employee1">
<field name="field" search="[('name', '=', 'company'), ('model.model', '=', 'sale.opportunity_employee')]"/>
<field name="operator">=</field>
<field name="operand">User/Current Company</field>
<field name="rule_group" ref="rule_group_opportunity_employee"/>
</record>
<record model="ir.ui.view" id="open_sale_opportunity_employee_init_view_form">
<field name="model">sale.open_opportunity_employee.init</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Sale Opportunities per Employee">
<label name="start_date"/>
<field name="start_date"/>
<label name="end_date"/>
<field name="end_date"/>
</form>
]]>
</field>
</record>
<record model="ir.action.wizard" id="act_open_sale_opportunity_employee">
<field name="name">Opportunities per Employee</field>
<field name="wiz_name">sale.open_opportunity_employee</field>
</record>
<menuitem parent="menu_reporting" action="act_open_sale_opportunity_employee"
icon="tryton-list" id="menu_open_sale_opportunity_employee"/>
<record model="ir.ui.view" id="opportunity_monthly_view_tree">
<field name="model">sale.opportunity_monthly</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree string="Sale Opportunities per Month">
<field name="year" select="1"/>
<field name="month" select="1"/>
<field name="number" select="2"/>
<field name="converted" select="2"/>
<field name="lost" select="2"/>
<field name="conversion_rate"/>
<field name="amount" select="2"/>
<field name="converted_amount" select="2"/>
<field name="currency"/>
<field name="conversion_amount_rate"/>
</tree>
]]>
</field>
</record>
<record model="ir.ui.view" id="opportunity_monthly_view_graph1">
<field name="model">sale.opportunity_monthly</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<![CDATA[
<graph string="Sale Opportunities per Month">
<x>
<field name="year_month"/>
</x>
<y>
<field name="number"/>
<field name="converted"/>
<field name="lost"/>
</y>
</graph>
]]>
</field>
</record>
<record model="ir.ui.view" id="opportunity_monthly_view_graph2">
<field name="model">sale.opportunity_monthly</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<![CDATA[
<graph string="Sale Opportunities per Month">
<x>
<field name="year_month"/>
</x>
<y>
<field name="amount"/>
<field name="converted_amount"/>
</y>
</graph>
]]>
</field>
</record>
<record model="ir.action.act_window" id="act_opportunity_monthly_form">
<field name="name">Opportunities per Month</field>
<field name="res_model">sale.opportunity_monthly</field>
<field name="view_type">form</field>
</record>
<record model="ir.action.act_window.view"
id="act_opportunity_monthly_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="opportunity_monthly_view_tree"/>
<field name="act_window" ref="act_opportunity_monthly_form"/>
</record>
<record model="ir.action.act_window.view"
id="act_opportunity_monthly_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="opportunity_monthly_view_graph1"/>
<field name="act_window" ref="act_opportunity_monthly_form"/>
</record>
<record model="ir.action.act_window.view"
id="act_opportunity_monthly_form_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="opportunity_monthly_view_graph2"/>
<field name="act_window" ref="act_opportunity_monthly_form"/>
</record>
<menuitem parent="menu_reporting" action="act_opportunity_monthly_form"
id="menu_opportunity_monthly_form"/>
<record model="ir.rule.group" id="rule_group_opportunity_monthly">
<field name="model" search="[('model', '=', 'sale.opportunity_monthly')]"/>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_group_opportunity_monthly1">
<field name="field" search="[('name', '=', 'company'), ('model.model', '=', 'sale.opportunity_monthly')]"/>
<field name="operator">=</field>
<field name="operand">User/Current Company</field>
<field name="rule_group" ref="rule_group_opportunity_monthly"/>
</record>
<record model="ir.ui.view" id="opportunity_employee_monthly_view_tree">
<field name="model">sale.opportunity_employee_monthly</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree string="Sale Opportunities per Month">
<field name="year" select="1"/>
<field name="month" select="1"/>
<field name="employee" select="1"/>
<field name="number" select="2"/>
<field name="converted" select="2"/>
<field name="lost" select="2"/>
<field name="conversion_rate"/>
<field name="amount" select="2"/>
<field name="converted_amount" select="2"/>
<field name="currency"/>
<field name="conversion_amount_rate"/>
</tree>
]]>
</field>
</record>
<record model="ir.action.act_window" id="act_opportunity_employee_monthly_form">
<field name="name">Opportunities per Employee per Month</field>
<field name="res_model">sale.opportunity_employee_monthly</field>
<field name="view_type">form</field>
</record>
<record model="ir.action.act_window.view"
id="act_opportunity_employee_monthly_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="opportunity_employee_monthly_view_tree"/>
<field name="act_window" ref="act_opportunity_employee_monthly_form"/>
</record>
<menuitem parent="menu_reporting"
action="act_opportunity_employee_monthly_form"
id="menu_opportunity_employee_monthly_form"/>
<record model="ir.rule.group" id="rule_group_opportunity_employee_monthly">
<field name="model" search="[('model', '=', 'sale.opportunity_employee_monthly')]"/>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_group_opportunity_employee_monthly1">
<field name="field" search="[('name', '=', 'company'), ('model.model', '=', 'sale.opportunity_employee_monthly')]"/>
<field name="operator">=</field>
<field name="operand">User/Current Company</field>
<field name="rule_group" ref="rule_group_opportunity_employee_monthly"/>
</record>
</data>
</tryton>
+20
View File
@@ -0,0 +1,20 @@
<?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>
<record model="ir.action.act_window" id="act_opportunity_form2">
<field name="name">Sales Leads/Opportunities</field>
<field name="res_model">sale.opportunity</field>
<field name="view_type">form</field>
<field name="domain">[("party", "=", Eval('active_id'))]</field>
</record>
<record model="ir.action.keyword"
id="act_open_sale_keyword1">
<field name="keyword">form_relate</field>
<field name="model">party.party,0</field>
<field name="action" ref="act_opportunity_form2"/>
</record>
</data>
</tryton>
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env 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.
from setuptools import setup
import re
info = eval(open('__tryton__.py').read())
major_version, minor_version, _ = info.get('version', '0.0.1').split('.', 2)
major_version = int(major_version)
minor_version = int(minor_version)
requires = []
for dep in info.get('depends', []):
if not re.match(r'(ir|res|workflow|webdav)(\W|$)', dep):
requires.append('trytond_%s >= %s.%s, < %s.%s' %
(dep, major_version, minor_version, major_version,
minor_version + 1))
requires.append('trytond >= %s.%s, < %s.%s' %
(major_version, minor_version, major_version, minor_version + 1))
setup(name='trytond_sale_opportunity',
version=info.get('version', '0.0.1'),
description=info.get('description', ''),
author=info.get('author', ''),
author_email=info.get('email', ''),
url=info.get('website', ''),
download_url="http://downloads.tryton.org/" + \
info.get('version', '0.0.1').rsplit('.', 1)[0] + '/',
package_dir={'trytond.modules.sale_opportunity': '.'},
packages=[
'trytond.modules.sale_opportunity',
'trytond.modules.sale_opportunity.tests',
],
package_data={
'trytond.modules.sale_opportunity': info.get('xml', []) \
+ info.get('translation', []),
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
'Intended Audience :: Developers',
'Intended Audience :: Financial and Insurance Industry',
'Intended Audience :: Legal Industry',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Natural Language :: English',
'Natural Language :: French',
'Natural Language :: German',
'Natural Language :: Spanish',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Office/Business',
],
license='GPL-3',
install_requires=requires,
zip_safe=False,
entry_points="""
[trytond.modules]
sale_opportunity = trytond.modules.sale_opportunity
""",
test_suite='tests',
test_loader='trytond.test_loader:Loader',
)
+4
View File
@@ -0,0 +1,4 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from test_sale_opportunity import suite
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env 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.
import sys, os
DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
'..', '..', '..', '..', '..', 'trytond')))
if os.path.isdir(DIR):
sys.path.insert(0, os.path.dirname(DIR))
import unittest
import trytond.tests.test_tryton
from trytond.tests.test_tryton import test_view
class SaleOpportunityTestCase(unittest.TestCase):
'''
Test SaleOpportunity module.
'''
def setUp(self):
trytond.tests.test_tryton.install_module('sale_opportunity')
def test0005views(self):
'''
Test views.
'''
self.assertRaises(Exception, test_view('sale_opportunity'))
def suite():
suite = trytond.tests.test_tryton.suite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
SaleOpportunityTestCase))
return suite
if __name__ == '__main__':
unittest.TextTestRunner(verbosity=2).run(suite())