[MERGE] sync with trunk
bzr revid: mat@openerp.com-20131209154236-jjawy50a8tvmyayb bzr revid: mat@openerp.com-20131209163936-6n9z81etuejkdpsg
This commit is contained in:
commit
7328a64425
|
@ -28,7 +28,7 @@ import time
|
|||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import tools
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.osv import fields, osv, expression
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools.float_utils import float_round
|
||||
|
||||
|
@ -579,15 +579,18 @@ class account_account(osv.osv):
|
|||
except:
|
||||
pass
|
||||
if name:
|
||||
ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
|
||||
if not ids:
|
||||
ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
|
||||
if not ids:
|
||||
ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
|
||||
if not ids and len(name.split()) >= 2:
|
||||
#Separating code and name of account for searching
|
||||
operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
|
||||
ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit)
|
||||
if operator not in expression.NEGATIVE_TERM_OPERATORS:
|
||||
ids = self.search(cr, user, ['|', ('code', '=like', name+"%"), '|', ('shortcut', '=', name), ('name', operator, name)]+args, limit=limit)
|
||||
if not ids and len(name.split()) >= 2:
|
||||
#Separating code and name of account for searching
|
||||
operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
|
||||
ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit)
|
||||
else:
|
||||
ids = self.search(cr, user, ['&','!', ('code', '=like', name+"%"), ('name', operator, name)]+args, limit=limit)
|
||||
# as negation want to restric, do if already have results
|
||||
if ids and len(name.split()) >= 2:
|
||||
operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
|
||||
ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2), ('id', 'in', ids)]+ args, limit=limit)
|
||||
else:
|
||||
ids = self.search(cr, user, args, context=context, limit=limit)
|
||||
return self.name_get(cr, user, ids, context=context)
|
||||
|
@ -1573,11 +1576,6 @@ class account_move(osv.osv):
|
|||
obj_analytic_line = self.pool.get('account.analytic.line')
|
||||
obj_move_line = self.pool.get('account.move.line')
|
||||
for move in self.browse(cr, uid, ids, context):
|
||||
# Unlink old analytic lines on move_lines
|
||||
for obj_line in move.line_id:
|
||||
for obj in obj_line.analytic_lines:
|
||||
obj_analytic_line.unlink(cr,uid,obj.id)
|
||||
|
||||
journal = move.journal_id
|
||||
amount = 0
|
||||
line_ids = []
|
||||
|
|
|
@ -193,6 +193,8 @@ class account_move_line(osv.osv):
|
|||
if obj_line.analytic_account_id:
|
||||
if not obj_line.journal_id.analytic_journal_id:
|
||||
raise osv.except_osv(_('No Analytic Journal!'),_("You have to define an analytic journal on the '%s' journal!") % (obj_line.journal_id.name, ))
|
||||
if obj_line.analytic_lines:
|
||||
acc_ana_line_obj.unlink(cr,uid,[obj.id for obj in obj_line.analytic_lines])
|
||||
vals_line = self._prepare_analytic_line(cr, uid, obj_line, context=context)
|
||||
acc_ana_line_obj.create(cr, uid, vals_line)
|
||||
return True
|
||||
|
@ -1207,20 +1209,6 @@ class account_move_line(osv.osv):
|
|||
if not ok:
|
||||
raise osv.except_osv(_('Bad Account!'), _('You cannot use this general account in this journal, check the tab \'Entry Controls\' on the related journal.'))
|
||||
|
||||
if vals.get('analytic_account_id',False):
|
||||
if journal.analytic_journal_id:
|
||||
vals['analytic_lines'] = [(0,0, {
|
||||
'name': vals['name'],
|
||||
'date': vals.get('date', time.strftime('%Y-%m-%d')),
|
||||
'account_id': vals.get('analytic_account_id', False),
|
||||
'unit_amount': vals.get('quantity', 1.0),
|
||||
'amount': vals.get('debit', 0.0) or vals.get('credit', 0.0),
|
||||
'general_account_id': vals.get('account_id', False),
|
||||
'journal_id': journal.analytic_journal_id.id,
|
||||
'ref': vals.get('ref', False),
|
||||
'user_id': uid
|
||||
})]
|
||||
|
||||
result = super(account_move_line, self).create(cr, uid, vals, context=context)
|
||||
# CREATE Taxes
|
||||
if vals.get('account_tax_id', False):
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
from . import test_tax
|
||||
from . import test_search
|
||||
|
||||
fast_suite = [test_tax,
|
||||
]
|
||||
fast_suite = [
|
||||
test_tax,
|
||||
test_search,
|
||||
]
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
from openerp.tests.common import TransactionCase
|
||||
|
||||
class TestSearch(TransactionCase):
|
||||
"""Tests for search on name_search (account.account)
|
||||
|
||||
The name search on account.account is quite complexe, make sure
|
||||
we have all the correct results
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestSearch, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
self.account_model = self.registry('account.account')
|
||||
self.account_type_model = self.registry('account.account.type')
|
||||
ac_ids = self.account_type_model.search(cr, uid, [], limit=1)
|
||||
self.atax = (int(self.account_model.create(cr, uid, dict(
|
||||
name="Tax Received",
|
||||
code="121",
|
||||
user_type=ac_ids[0],
|
||||
))), "121 Tax Received")
|
||||
|
||||
self.apurchase = (int(self.account_model.create(cr, uid, dict(
|
||||
name="Purchased Stocks",
|
||||
code="1101",
|
||||
user_type=ac_ids[0],
|
||||
))), "1101 Purchased Stocks")
|
||||
|
||||
self.asale = (int(self.account_model.create(cr, uid, dict(
|
||||
name="Product Sales",
|
||||
code="200",
|
||||
user_type=ac_ids[0],
|
||||
))), "200 Product Sales")
|
||||
|
||||
self.all_ids = [self.atax[0], self.apurchase[0], self.asale[0]]
|
||||
|
||||
def test_name_search(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
atax_ids = self.account_model.name_search(cr, uid, name="Tax", operator='ilike', args=[('id', 'in', self.all_ids)])
|
||||
self.assertEqual(set([self.atax[0]]), set([a[0] for a in atax_ids]), "name_search 'ilike Tax' should have returned Tax Received account only")
|
||||
|
||||
atax_ids = self.account_model.name_search(cr, uid, name="Tax", operator='not ilike', args=[('id', 'in', self.all_ids)])
|
||||
self.assertEqual(set([self.apurchase[0], self.asale[0]]), set([a[0] for a in atax_ids]), "name_search 'not ilike Tax' should have returned all but Tax Received account")
|
||||
|
||||
apur_ids = self.account_model.name_search(cr, uid, name='1101', operator='ilike', args=[('id', 'in', self.all_ids)])
|
||||
self.assertEqual(set([self.apurchase[0]]), set([a[0] for a in apur_ids]), "name_search 'ilike 1101' should have returned Purchased Stocks account only")
|
||||
|
||||
apur_ids = self.account_model.name_search(cr, uid, name='1101', operator='not ilike', args=[('id', 'in', self.all_ids)])
|
||||
self.assertEqual(set([self.atax[0], self.asale[0]]), set([a[0] for a in apur_ids]), "name_search 'not ilike 1101' should have returned all but Purchased Stocks account")
|
||||
|
||||
asale_ids = self.account_model.name_search(cr, uid, name='200 Sales', operator='ilike', args=[('id', 'in', self.all_ids)])
|
||||
self.assertEqual(set([self.asale[0]]), set([a[0] for a in asale_ids]), "name_search 'ilike 200 Sales' should have returned Product Sales account only")
|
||||
|
||||
asale_ids = self.account_model.name_search(cr, uid, name='200 Sales', operator='not ilike', args=[('id', 'in', self.all_ids)])
|
||||
self.assertEqual(set([self.atax[0], self.apurchase[0]]), set([a[0] for a in asale_ids]), "name_search 'not ilike 200 Sales' should have returned all but Product Sales account")
|
||||
|
||||
asale_ids = self.account_model.name_search(cr, uid, name='Product Sales', operator='ilike', args=[('id', 'in', self.all_ids)])
|
||||
self.assertEqual(set([self.asale[0]]), set([a[0] for a in asale_ids]), "name_search 'ilike Product Sales' should have returned Product Sales account only")
|
||||
|
||||
asale_ids = self.account_model.name_search(cr, uid, name='Product Sales', operator='not ilike', args=[('id', 'in', self.all_ids)])
|
||||
self.assertEqual(set([self.atax[0], self.apurchase[0]]), set([a[0] for a in asale_ids]), "name_search 'not ilike Product Sales' should have returned all but Product Sales account")
|
|
@ -21,21 +21,25 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import osv
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
class account_invoice_line(osv.osv):
|
||||
_inherit = "account.invoice.line"
|
||||
|
||||
_columns = {
|
||||
'move_id': fields.many2one('stock.move', string="Move line", help="If the invoice was generated from a stock.picking, reference to the related move line."),
|
||||
}
|
||||
|
||||
def move_line_get(self, cr, uid, invoice_id, context=None):
|
||||
res = super(account_invoice_line,self).move_line_get(cr, uid, invoice_id, context=context)
|
||||
inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context)
|
||||
company_currency = inv.company_id.currency_id.id
|
||||
def get_price(cr, uid, inv, company_currency,i_line):
|
||||
def get_price(cr, uid, inv, company_currency, i_line, price_unit):
|
||||
cur_obj = self.pool.get('res.currency')
|
||||
if inv.currency_id.id != company_currency:
|
||||
price = cur_obj.compute(cr, uid, company_currency, inv.currency_id.id, i_line.product_id.standard_price * i_line.quantity, context={'date': inv.date_invoice})
|
||||
price = cur_obj.compute(cr, uid, company_currency, inv.currency_id.id, price_unit * i_line.quantity, context={'date': inv.date_invoice})
|
||||
else:
|
||||
price = i_line.product_id.standard_price * i_line.quantity
|
||||
price = price_unit * i_line.quantity
|
||||
return price
|
||||
|
||||
if inv.type in ('out_invoice','out_refund'):
|
||||
|
@ -60,12 +64,13 @@ class account_invoice_line(osv.osv):
|
|||
if not cacc:
|
||||
cacc = i_line.product_id.categ_id.property_account_expense_categ and i_line.product_id.categ_id.property_account_expense_categ.id
|
||||
if dacc and cacc:
|
||||
price_unit = i_line.move_id and i_line.move_id.price_unit or i_line.product_id.standard_price
|
||||
res.append({
|
||||
'type':'src',
|
||||
'name': i_line.name[:64],
|
||||
'price_unit':i_line.product_id.standard_price,
|
||||
'price_unit':price_unit,
|
||||
'quantity':i_line.quantity,
|
||||
'price':get_price(cr, uid, inv, company_currency, i_line),
|
||||
'price':get_price(cr, uid, inv, company_currency, i_line, price_unit),
|
||||
'account_id':dacc,
|
||||
'product_id':i_line.product_id.id,
|
||||
'uos_id':i_line.uos_id.id,
|
||||
|
@ -76,9 +81,9 @@ class account_invoice_line(osv.osv):
|
|||
res.append({
|
||||
'type':'src',
|
||||
'name': i_line.name[:64],
|
||||
'price_unit':i_line.product_id.standard_price,
|
||||
'price_unit':price_unit,
|
||||
'quantity':i_line.quantity,
|
||||
'price': -1 * get_price(cr, uid, inv, company_currency, i_line),
|
||||
'price': -1 * get_price(cr, uid, inv, company_currency, i_line, price_unit),
|
||||
'account_id':cacc,
|
||||
'product_id':i_line.product_id.id,
|
||||
'uos_id':i_line.uos_id.id,
|
||||
|
|
|
@ -28,6 +28,15 @@ class stock_picking(osv.osv):
|
|||
_inherit = "stock.picking"
|
||||
_description = "Picking List"
|
||||
|
||||
def _prepare_invoice_line(self, cr, uid, group, picking, move_line, invoice_id,
|
||||
invoice_vals, context=None):
|
||||
"""Overwrite to add move_id reference"""
|
||||
res = super(stock_picking, self)._prepare_invoice_line(cr, uid, group, picking, move_line, invoice_id, invoice_vals, context=context)
|
||||
res.update({
|
||||
'move_id': move_line.id,
|
||||
})
|
||||
return res
|
||||
|
||||
def action_invoice_create(self, cr, uid, ids, journal_id=False,
|
||||
group=False, type='out_invoice', context=None):
|
||||
'''Return ids of created invoices for the pickings'''
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<field name="active" eval="False"/>
|
||||
<!-- Avoid auto-including this user in any default group, just like a typical portal member -->
|
||||
<field name="groups_id" eval="[(5,)]"/>
|
||||
<!-- no alias for portal users -->
|
||||
<field name="alias_name" eval="False"/>
|
||||
<!-- allow signuped users to have a alias -->
|
||||
<field name="alias_name">_usertemplate</field>
|
||||
</record>
|
||||
|
||||
<record id="default_template_user_config" model="ir.config_parameter">
|
||||
|
|
|
@ -234,8 +234,6 @@ class res_users(osv.Model):
|
|||
|
||||
# create a copy of the template user (attached to a specific partner_id if given)
|
||||
values['active'] = True
|
||||
if 'alias_name' not in values: # allow behavior change via inheritance (like using the name)
|
||||
values['alias_name'] = False
|
||||
context = dict(context or {}, no_reset_password=True)
|
||||
return self.copy(cr, uid, template_user_id, values, context=context)
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
<field name="search_view_id" ref="crm.view_crm_case_leads_filter"/>
|
||||
<field name="context">{
|
||||
'search_default_section_id': [active_id],
|
||||
'search_default_open': 1,
|
||||
'default_section_id': active_id,
|
||||
'default_type': 'lead',
|
||||
'stage_type': 'lead',
|
||||
|
@ -43,7 +42,6 @@
|
|||
<field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
|
||||
<field name="context">{
|
||||
'search_default_section_id': [active_id],
|
||||
'search_default_assigned_to_me': 1,
|
||||
'default_section_id': active_id,
|
||||
'stage_type': 'opportunity',
|
||||
'default_type': 'opportunity',
|
||||
|
@ -64,6 +62,26 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_crm_lead_salesteam" model="ir.actions.act_window">
|
||||
<field name="name">Leads Analysis</field>
|
||||
<field name="res_model">crm.lead.report</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="context">{"search_default_month":1}</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="domain">[('type','=', 'lead'),('section_id', '=', active_id)]</field>
|
||||
<field name="help">Leads Analysis allows you to check different CRM related information like the treatment delays or number of leads per state. You can sort out your leads analysis by different groups to get accurate grained analysis.</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_crm_opportunity_salesteam" model="ir.actions.act_window">
|
||||
<field name="name">Opportunities Analysis</field>
|
||||
<field name="res_model">crm.lead.report</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="context">{"search_default_month":1}</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="domain">[('type','=', 'opportunity'), ('section_id', '=', active_id)]</field>
|
||||
<field name="help">Opportunities Analysis gives you an instant access to your opportunities with information such as the expected revenue, planned cost, missed deadlines or the number of interactions per opportunity. This report is mainly used by the sales manager in order to do the periodic review with the teams of the sales pipeline.</field>
|
||||
</record>
|
||||
|
||||
<!-- Case Sections Salesteams kanban view -->
|
||||
|
||||
<record model="ir.ui.view" id="crm_case_section_salesteams_view_kanban">
|
||||
|
@ -99,13 +117,25 @@
|
|||
<div class="oe_items_list">
|
||||
<div class="oe_salesteams_leads" t-if="record.use_leads.raw_value">
|
||||
<a name="%(crm_case_form_view_salesteams_lead)d" type="action">Leads</a>
|
||||
<a name="%(action_report_crm_lead)d" type="action" class="oe_sparkline_bar_link"><field name="monthly_open_leads" widget="sparkline_bar" options="{'height': '20px', 'barWidth': 4, 'barSpacing': 1}">Open Leads per Month<br/>Click to see a detailed analysis of leads.</field></a>
|
||||
<a name="%(action_report_crm_lead_salesteam)d" type="action" class="oe_sparkline_bar_link">
|
||||
<field name="monthly_open_leads" widget="sparkline_bar"
|
||||
options="{'height': '20px', 'barWidth': 4, 'barSpacing': 1, 'delayIn': '3000', 'tooltip_suffix': 'Leads'}">Open Leads per Month<br/>Click to see a detailed analysis of leads.</field>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oe_salesteams_opportunities">
|
||||
<a name="%(crm_case_form_view_salesteams_opportunity)d" type="action">Opportunities</a>
|
||||
<a name="%(action_report_crm_opportunity)d" type="action"><field name="monthly_planned_revenue" widget="sparkline_bar" height="20px" barWidth="4" barSpacing="1">Planned Revenue per Month<br/>Click to see a detailed analysis of opportunities.</field></a>
|
||||
<a name="%(action_report_crm_opportunity_salesteam)d" type="action">
|
||||
<field name="monthly_planned_revenue" widget="sparkline_bar"
|
||||
options="{'height': '20px', 'barWidth': '4', 'barSpacing': '1', 'delayIn': '3000', 'tooltip_suffix': 'Opportunities'}">Planned Revenue per Month<br/>Click to see a detailed analysis of opportunities.</field>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_clear"></div>
|
||||
<div class="oe_kanban_salesteams_avatars">
|
||||
<t t-foreach="record.member_ids.raw_value.slice(0,10)" t-as="member">
|
||||
<img t-att-src="kanban_image('res.users', 'image_small', member)" t-att-data-member_id="member"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
|
|
@ -6,14 +6,20 @@
|
|||
<field name="groups_id" eval="[(4,ref('base.group_sale_salesman'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="crm.section_sales_department" model="crm.case.section">
|
||||
<field name="member_ids" eval="[(4, ref('base.user_demo'))]"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.section" id="crm_case_section_1">
|
||||
<field name="name">Indirect Sales</field>
|
||||
<field name="code">IM</field>
|
||||
<field name="member_ids" eval="[(4, ref('base.user_root')),(4, ref('base.user_demo'))]"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.section" id="crm_case_section_2">
|
||||
<field name="name">Marketing</field>
|
||||
<field name="code">SPD</field>
|
||||
<field name="member_ids" eval="[(4, ref('base.user_root')),(4, ref('base.user_demo'))]"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.segmentation" id="crm_segmentation0">
|
||||
|
|
|
@ -66,9 +66,6 @@
|
|||
<field name="model">crm.lead.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Leads Analysis">
|
||||
<filter icon="terp-personal" name="lead" string="Lead" domain="[('type','=', 'lead')]" help="Show only lead"/>
|
||||
<filter icon="terp-personal+" string="Opportunity" name="opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
|
||||
<separator/>
|
||||
<filter string="New" name="new"
|
||||
domain="[('probability', '=', 0), ('stage_id.sequence', '=', 1)]"/>
|
||||
<filter string="Won" name="won"
|
||||
|
@ -115,7 +112,7 @@
|
|||
<separator orientation="vertical" />
|
||||
<filter string="Year" icon="terp-go-year"
|
||||
domain="[]" context="{'group_by':'creation_year'}"/>
|
||||
<filter string="Month" icon="terp-go-month"
|
||||
<filter string="Month" name="month" icon="terp-go-month"
|
||||
domain="[]" context="{'group_by':'creation_month'}"/>
|
||||
<filter string="Day" icon="terp-go-today"
|
||||
domain="[]" context="{'group_by':'creation_day'}"/>
|
||||
|
@ -168,9 +165,9 @@
|
|||
<field name="name">Leads Analysis</field>
|
||||
<field name="res_model">crm.lead.report</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="context">{'search_default_year': 1,'search_default_lead': 1, "search_default_user":1, "search_default_this_month":1, 'group_by_no_leaf':1, 'group_by':[]}</field>
|
||||
<field name="context">{'search_default_year': 1, "search_default_user":1, "search_default_month":1, 'group_by_no_leaf':1, 'group_by':[]}</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="domain">[('type','=', 'lead')]</field>
|
||||
<field name="help">Leads Analysis allows you to check different CRM related information like the treatment delays or number of leads per state. You can sort out your leads analysis by different groups to get accurate grained analysis.</field>
|
||||
</record>
|
||||
<record model="ir.actions.act_window.view" id="action_report_crm_lead_tree">
|
||||
|
@ -190,8 +187,9 @@
|
|||
<field name="name">Opportunities Analysis</field>
|
||||
<field name="res_model">crm.lead.report</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="context">{"search_default_year":1,"search_default_opportunity":1, "search_default_user":1,"search_default_this_month":1,'group_by_no_leaf':1,'group_by':[]}</field>
|
||||
<field name="context">{"search_default_year":1, "search_default_user":1,"search_default_month":1,'group_by_no_leaf':1,'group_by':[]}</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="domain">[('type','=', 'opportunity')]</field>
|
||||
<field name="help">Opportunities Analysis gives you an instant access to your opportunities with information such as the expected revenue, planned cost, missed deadlines or the number of interactions per opportunity. This report is mainly used by the sales manager in order to do the periodic review with the teams of the sales pipeline.</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -91,5 +91,19 @@
|
|||
<field name="groups" eval="[(4, ref('base.group_sale_salesman_all_leads'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_rule_personal_lead_report" model="ir.rule">
|
||||
<field name="name">Personal Leads Analysis</field>
|
||||
<field ref="model_crm_lead_report" name="model_id"/>
|
||||
<field name="domain_force">['|',('user_id','=',user.id),('user_id','=',False)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_sale_salesman'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_rule_all_lead_report" model="ir.rule">
|
||||
<field name="name">All Leads Analysis</field>
|
||||
<field ref="model_crm_lead_report" name="model_id"/>
|
||||
<field name="domain_force">[(1,'=',1)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_sale_salesman_all_leads'))]"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
crm.css: crm.sass
|
||||
sass --trace -t expanded crm.sass:crm.css
|
|
@ -1,67 +1,62 @@
|
|||
@charset "utf-8";
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams {
|
||||
/* Customize width and height of kanban according bootstrap3 */
|
||||
width: 357px;
|
||||
min-height: 254px !important;
|
||||
/* End of customize */
|
||||
/* Customize width and height of kanban according bootstrap3 */
|
||||
width: 357px;
|
||||
min-height: 254px !important;
|
||||
/* End of customize */
|
||||
cursor: default;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_avatars {
|
||||
text-align: right;
|
||||
margin: -5px 0 -10px 0;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_avatars img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding-left: 0px;
|
||||
margin-top: 3px;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list {
|
||||
position: relative;
|
||||
margin: 10px 0 10px 9px; /* Improved margin to set alignment of list items according bootstrap3 */
|
||||
position: relative;
|
||||
/* Improved margin to set alignment of list items according bootstrap3 */
|
||||
margin: 10px 0 10px 9px;
|
||||
min-height: 10px;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div {
|
||||
width: 160px;
|
||||
height: 22px;
|
||||
margin: 0 !important;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list a:hover {
|
||||
text-decoration: underline !important;
|
||||
width: 160px;
|
||||
height: 22px;
|
||||
margin: 0 !important;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div a:nth-child(2n) {
|
||||
position: absolute;
|
||||
left: 90px;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
left: 90px;
|
||||
top: 0;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div:nth-child(2n) a:nth-child(2n) {
|
||||
left: 110px;
|
||||
left: 110px;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list a:hover {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center {
|
||||
text-align: center;
|
||||
margin: 3px 0;
|
||||
text-align: center;
|
||||
margin: 3px 0;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center .oe_sum {
|
||||
margin: 0;
|
||||
font-size: 40px;
|
||||
margin: 0;
|
||||
font-size: 40px;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center .oe_subsum {
|
||||
font-size: 10px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_justgage {
|
||||
color: black;
|
||||
display: inline-block;
|
||||
.openerp .oe_kanban_view .oe_salesteams_help {
|
||||
display: inline-block;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_salesteams_avatars {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_salesteams_avatars img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding-left: 0px;
|
||||
margin-top: 3px;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_sparkline_bar {
|
||||
height: 20px;
|
||||
width: 36px;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
@charset "utf-8"
|
||||
|
||||
.openerp
|
||||
.oe_kanban_view
|
||||
.oe_kanban_crm_salesteams
|
||||
/* Customize width and height of kanban according bootstrap3 */
|
||||
width: 357px
|
||||
min-height: 254px !important
|
||||
/* End of customize */
|
||||
cursor: default
|
||||
.oe_items_list
|
||||
position: relative
|
||||
/* Improved margin to set alignment of list items according bootstrap3 */
|
||||
margin: 10px 0 10px 9px
|
||||
min-height: 10px
|
||||
div
|
||||
width: 160px
|
||||
height: 22px
|
||||
margin: 0 !important
|
||||
position: relative
|
||||
display: inline-block
|
||||
float: left
|
||||
a:nth-child(2n)
|
||||
position: absolute
|
||||
left: 90px
|
||||
top: 0
|
||||
div:nth-child(2n)
|
||||
a:nth-child(2n)
|
||||
left: 110px
|
||||
a:hover
|
||||
text-decoration: underline !important
|
||||
.oe_center
|
||||
text-align: center
|
||||
margin: 3px 0
|
||||
.oe_sum
|
||||
margin: 0
|
||||
font-size: 40px
|
||||
.oe_subsum
|
||||
font-size: 10px
|
||||
.oe_salesteams_help
|
||||
display: inline-block
|
||||
.oe_kanban_salesteams_avatars
|
||||
margin-top: 20px
|
||||
.oe_kanban_salesteams_avatars
|
||||
img
|
||||
width: 30px
|
||||
height: 30px
|
||||
padding-left: 0px
|
||||
margin-top: 3px
|
||||
-moz-border-radius: 2px
|
||||
-webkit-border-radius: 2px
|
||||
border-radius: 2px
|
||||
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2)
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2)
|
||||
-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2)
|
|
@ -1,4 +1,40 @@
|
|||
openerp.crm = function(openerp) {
|
||||
openerp.web_kanban.KanbanView.include({
|
||||
crm_display_members_names: function() {
|
||||
/*
|
||||
* Set avatar title for members.
|
||||
* In kanban views, many2many fields only return a list of ids.
|
||||
* We can implement return value of m2m fields like [(1,"Adminstration"),...].
|
||||
*/
|
||||
var self = this;
|
||||
var members_ids = [];
|
||||
|
||||
// Collect members ids
|
||||
self.$el.find('img[data-member_id]').each(function() {
|
||||
members_ids.push($(this).data('member_id'));
|
||||
});
|
||||
|
||||
// Find their matching names
|
||||
var dataset = new openerp.web.DataSetSearch(self, 'res.users', self.session.context, [['id', 'in', _.uniq(members_ids)]]);
|
||||
dataset.read_slice(['id', 'name']).done(function(result) {
|
||||
_.each(result, function(v, k) {
|
||||
// Set the proper value in the DOM
|
||||
self.$el.find('img[data-member_id=' + v.id + ']').attr('title', v.name).tipsy({
|
||||
offset: 10
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
on_groups_started: function() {
|
||||
var self = this;
|
||||
self._super.apply(self, arguments);
|
||||
|
||||
if (self.dataset.model === 'crm.case.section') {
|
||||
self.crm_display_members_names();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
openerp.web_kanban.KanbanRecord.include({
|
||||
on_card_clicked: function() {
|
||||
if (this.view.dataset.model === 'crm.case.section') {
|
||||
|
@ -8,5 +44,4 @@ openerp.crm = function(openerp) {
|
|||
}
|
||||
},
|
||||
});
|
||||
|
||||
};
|
||||
|
|
|
@ -211,5 +211,5 @@ class im_session(osv.osv):
|
|||
_inherit = 'im.session'
|
||||
|
||||
_columns = {
|
||||
'channel_id': fields.many2one("im.user", "Channel"),
|
||||
'channel_id': fields.many2one("im_livechat.channel", "Channel"),
|
||||
}
|
||||
|
|
|
@ -817,12 +817,11 @@ class mail_message(osv.Model):
|
|||
return email_reply_to
|
||||
|
||||
def _get_message_id(self, cr, uid, values, context=None):
|
||||
message_id = None
|
||||
if not values.get('message_id') and values.get('reply_to'):
|
||||
if values.get('reply_to'):
|
||||
message_id = tools.generate_tracking_message_id('reply_to')
|
||||
elif not values.get('message_id') and values.get('res_id') and values.get('model'):
|
||||
elif values.get('res_id') and values.get('model'):
|
||||
message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
|
||||
elif not values.get('message_id'):
|
||||
else:
|
||||
message_id = tools.generate_tracking_message_id('private')
|
||||
return message_id
|
||||
|
||||
|
@ -833,7 +832,7 @@ class mail_message(osv.Model):
|
|||
|
||||
if 'email_from' not in values: # needed to compute reply_to
|
||||
values['email_from'] = self._get_default_from(cr, uid, context=context)
|
||||
if not values.get('message_id'):
|
||||
if 'message_id' not in values:
|
||||
values['message_id'] = self._get_message_id(cr, uid, values, context=context)
|
||||
if 'reply_to' not in values:
|
||||
values['reply_to'] = self._get_reply_to(cr, uid, values, context=context)
|
||||
|
|
|
@ -252,13 +252,10 @@ class mail_thread(osv.AbstractModel):
|
|||
new = set(command[2])
|
||||
|
||||
# remove partners that are no longer followers
|
||||
fol_ids = fol_obj.search(cr, SUPERUSER_ID,
|
||||
[('res_model', '=', self._name), ('res_id', '=', id), ('partner_id', 'not in', list(new))])
|
||||
fol_obj.unlink(cr, SUPERUSER_ID, fol_ids)
|
||||
self.message_unsubscribe(cr, uid, [id], list(old-new))
|
||||
|
||||
# add new followers
|
||||
for partner_id in new - old:
|
||||
fol_obj.create(cr, SUPERUSER_ID, {'res_model': self._name, 'res_id': id, 'partner_id': partner_id})
|
||||
self.message_subscribe(cr, uid, [id], list(new-old))
|
||||
|
||||
def _search_followers(self, cr, uid, obj, name, args, context):
|
||||
"""Search function for message_follower_ids
|
||||
|
@ -346,6 +343,7 @@ class mail_thread(osv.AbstractModel):
|
|||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
thread_id = super(mail_thread, self).create(cr, uid, values, context=context)
|
||||
|
||||
# automatic logging unless asked not to (mainly for various testing purpose)
|
||||
|
@ -355,6 +353,7 @@ class mail_thread(osv.AbstractModel):
|
|||
# subscribe uid unless asked not to
|
||||
if not context.get('mail_create_nosubscribe'):
|
||||
self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
|
||||
|
||||
# auto_subscribe: take values and defaults into account
|
||||
create_values = dict(values)
|
||||
for key, val in context.iteritems():
|
||||
|
@ -723,8 +722,8 @@ class mail_thread(osv.AbstractModel):
|
|||
# Private message: should not contain any thread_id
|
||||
if not model and thread_id:
|
||||
if assert_model:
|
||||
assert thread_id == 0, 'Routing: posting a message without model should be with a null res_id (private message).'
|
||||
_warn('posting a message without model should be with a null res_id (private message), resetting thread_id')
|
||||
assert thread_id == 0, 'Routing: posting a message without model should be with a null res_id (private message), received %s.' % thread_id
|
||||
_warn('posting a message without model should be with a null res_id (private message), received %s, resetting thread_id' % thread_id)
|
||||
thread_id = 0
|
||||
# Private message: should have a parent_id (only answers)
|
||||
if not model and not message_dict.get('parent_id'):
|
||||
|
@ -821,6 +820,7 @@ class mail_thread(osv.AbstractModel):
|
|||
:return: list of [model, thread_id, custom_values, user_id, alias]
|
||||
"""
|
||||
assert isinstance(message, Message), 'message must be an email.message.Message at this point'
|
||||
mail_msg_obj = self.pool['mail.message']
|
||||
fallback_model = model
|
||||
|
||||
# Get email.message.Message variables for future processing
|
||||
|
@ -829,31 +829,54 @@ class mail_thread(osv.AbstractModel):
|
|||
email_to = decode_header(message, 'To')
|
||||
references = decode_header(message, 'References')
|
||||
in_reply_to = decode_header(message, 'In-Reply-To')
|
||||
|
||||
# 1. Verify if this is a reply to an existing thread
|
||||
thread_references = references or in_reply_to
|
||||
|
||||
# 1. message is a reply to an existing message (exact match of message_id)
|
||||
msg_references = thread_references.split()
|
||||
mail_message_ids = mail_msg_obj.search(cr, uid, [('message_id', 'in', msg_references)], context=context)
|
||||
if mail_message_ids:
|
||||
original_msg = mail_msg_obj.browse(cr, SUPERUSER_ID, mail_message_ids[0], context=context)
|
||||
model, thread_id = original_msg.model, original_msg.res_id
|
||||
_logger.info(
|
||||
'Routing mail from %s to %s with Message-Id %s: direct reply to msg: model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
route = self.message_route_verify(
|
||||
cr, uid, message, message_dict,
|
||||
(model, thread_id, custom_values, uid, None),
|
||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||
return route and [route] or []
|
||||
|
||||
# 2. message is a reply to an existign thread (6.1 compatibility)
|
||||
ref_match = thread_references and tools.reference_re.search(thread_references)
|
||||
if ref_match:
|
||||
thread_id = int(ref_match.group(1))
|
||||
model = ref_match.group(2) or fallback_model
|
||||
if thread_id and model in self.pool:
|
||||
model_obj = self.pool[model]
|
||||
if model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'):
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
route = self.message_route_verify(cr, uid, message, message_dict,
|
||||
(model, thread_id, custom_values, uid, None),
|
||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||
compat_mail_msg_ids = mail_msg_obj.search(
|
||||
cr, uid, [
|
||||
('message_id', '=', False),
|
||||
('model', '=', model),
|
||||
('res_id', '=', thread_id),
|
||||
], context=context)
|
||||
if compat_mail_msg_ids and model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'):
|
||||
_logger.info(
|
||||
'Routing mail from %s to %s with Message-Id %s: direct thread reply (compat-mode) to model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
route = self.message_route_verify(
|
||||
cr, uid, message, message_dict,
|
||||
(model, thread_id, custom_values, uid, None),
|
||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||
return route and [route] or []
|
||||
|
||||
# 2. Reply to a private message
|
||||
if in_reply_to:
|
||||
mail_message_ids = self.pool.get('mail.message').search(cr, uid, [
|
||||
mail_message_ids = mail_msg_obj.search(cr, uid, [
|
||||
('message_id', '=', in_reply_to),
|
||||
'!', ('message_id', 'ilike', 'reply_to')
|
||||
], limit=1, context=context)
|
||||
if mail_message_ids:
|
||||
mail_message = self.pool.get('mail.message').browse(cr, uid, mail_message_ids[0], context=context)
|
||||
mail_message = mail_msg_obj.browse(cr, uid, mail_message_ids[0], context=context)
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, mail_message.id, custom_values, uid)
|
||||
route = self.message_route_verify(cr, uid, message, message_dict,
|
||||
|
@ -1521,35 +1544,33 @@ class mail_thread(osv.AbstractModel):
|
|||
else:
|
||||
self.check_access_rights(cr, uid, 'write')
|
||||
|
||||
for record in self.browse(cr, SUPERUSER_ID, ids, context=context):
|
||||
existing_pids = set([f.id for f in record.message_follower_ids
|
||||
if f.id in partner_ids])
|
||||
existing_pids_dict = {}
|
||||
fol_ids = mail_followers_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)])
|
||||
for fol in mail_followers_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context):
|
||||
existing_pids_dict.setdefault(fol.res_id, set()).add(fol.partner_id.id)
|
||||
|
||||
# subtype_ids specified: update already subscribed partners
|
||||
if subtype_ids and fol_ids:
|
||||
mail_followers_obj.write(cr, SUPERUSER_ID, fol_ids, {'subtype_ids': [(6, 0, subtype_ids)]}, context=context)
|
||||
# subtype_ids not specified: do not update already subscribed partner, fetch default subtypes for new partners
|
||||
if subtype_ids is None:
|
||||
subtype_ids = subtype_obj.search(
|
||||
cr, uid, [
|
||||
('default', '=', True), '|', ('res_model', '=', self._name), ('res_model', '=', False)], context=context)
|
||||
|
||||
for id in ids:
|
||||
existing_pids = existing_pids_dict.get(id, set())
|
||||
new_pids = set(partner_ids) - existing_pids
|
||||
|
||||
# subtype_ids specified: update already subscribed partners
|
||||
if subtype_ids and existing_pids:
|
||||
fol_ids = mail_followers_obj.search(cr, SUPERUSER_ID, [
|
||||
('res_model', '=', self._name),
|
||||
('res_id', '=', record.id),
|
||||
('partner_id', 'in', list(existing_pids)),
|
||||
], context=context)
|
||||
mail_followers_obj.write(cr, SUPERUSER_ID, fol_ids, {'subtype_ids': [(6, 0, subtype_ids)]}, context=context)
|
||||
# subtype_ids not specified: do not update already subscribed partner, fetch default subtypes for new partners
|
||||
elif subtype_ids is None:
|
||||
subtype_ids = subtype_obj.search(cr, uid, [
|
||||
('default', '=', True),
|
||||
'|',
|
||||
('res_model', '=', self._name),
|
||||
('res_model', '=', False)
|
||||
], context=context)
|
||||
# subscribe new followers
|
||||
for new_pid in new_pids:
|
||||
mail_followers_obj.create(cr, SUPERUSER_ID, {
|
||||
'res_model': self._name,
|
||||
'res_id': record.id,
|
||||
'partner_id': new_pid,
|
||||
'subtype_ids': [(6, 0, subtype_ids)],
|
||||
}, context=context)
|
||||
mail_followers_obj.create(
|
||||
cr, SUPERUSER_ID, {
|
||||
'res_model': self._name,
|
||||
'res_id': id,
|
||||
'partner_id': new_pid,
|
||||
'subtype_ids': [(6, 0, subtype_ids)],
|
||||
}, context=context)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -1568,7 +1589,14 @@ class mail_thread(osv.AbstractModel):
|
|||
self.check_access_rights(cr, uid, 'read')
|
||||
else:
|
||||
self.check_access_rights(cr, uid, 'write')
|
||||
return self.write(cr, SUPERUSER_ID, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
|
||||
fol_obj = self.pool['mail.followers']
|
||||
fol_ids = fol_obj.search(
|
||||
cr, SUPERUSER_ID, [
|
||||
('res_model', '=', self._name),
|
||||
('res_id', 'in', ids),
|
||||
('partner_id', 'in', partner_ids)
|
||||
], context=context)
|
||||
return fol_obj.unlink(cr, SUPERUSER_ID, fol_ids, context=context)
|
||||
|
||||
def _message_get_auto_subscribe_fields(self, cr, uid, updated_fields, auto_follow_fields=['user_id'], context=None):
|
||||
""" Returns the list of relational fields linking to res.users that should
|
||||
|
|
|
@ -81,6 +81,12 @@ class res_users(osv.Model):
|
|||
self._create_welcome_message(cr, uid, user, context=context)
|
||||
return user_id
|
||||
|
||||
def copy_data(self, *args, **kwargs):
|
||||
data = super(res_users, self).copy_data(*args, **kwargs)
|
||||
if data.get('alias_name'):
|
||||
data['alias_name'] = data['login']
|
||||
return data
|
||||
|
||||
def _create_welcome_message(self, cr, uid, user, context=None):
|
||||
if not self.has_group(cr, uid, 'base.group_user'):
|
||||
return False
|
||||
|
|
|
@ -378,7 +378,7 @@ class TestMailgateway(TestMail):
|
|||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
|
||||
to='erroneous@example.com>', subject='Re: news',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n')
|
||||
# Test: no group 'Re: news' created, still only 1 Frogs group
|
||||
self.assertEqual(len(frog_groups), 0,
|
||||
'message_process: reply on Frogs should not have created a new group with new subject')
|
||||
|
@ -387,16 +387,41 @@ class TestMailgateway(TestMail):
|
|||
'message_process: reply on Frogs should not have created a duplicate group with old subject')
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: one new message
|
||||
self.assertEqual(len(frog_group.message_ids), 3, 'message_process: group should contain 2 messages after reply')
|
||||
self.assertEqual(len(frog_group.message_ids), 3, 'message_process: group should contain 3 messages after reply')
|
||||
# Test: author (and not recipient) added as follower
|
||||
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
|
||||
self.assertEqual(frog_follower_ids, set([p1id, p2id]),
|
||||
'message_process: after reply, group should have 2 followers')
|
||||
|
||||
# Do: incoming email with ref holding model / res_id but that does not match any message in the thread: must raise since OpenERP saas-3
|
||||
self.assertRaises(AssertionError,
|
||||
format_and_process,
|
||||
MAIL_TEMPLATE, email_from='other5@gmail.com',
|
||||
to='noone@example.com', subject='spam',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id,
|
||||
msg_id='<1.1.JavaMail.new@agrolait.com>')
|
||||
# There are 6.1 messages, activate compat mode
|
||||
tmp_msg_id = self.mail_message.create(cr, uid, {'message_id': False, 'model': 'mail.group', 'res_id': frog_group.id})
|
||||
# Do: compat mode accepts partial-matching emails
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other5@gmail.com',
|
||||
msg_id='<1.2.JavaMail.new@agrolait.com>',
|
||||
to='noone@example.com>', subject='spam',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id)
|
||||
self.mail_message.unlink(cr, uid, [tmp_msg_id])
|
||||
# Test: no group 'Re: news' created, still only 1 Frogs group
|
||||
self.assertEqual(len(frog_groups), 0,
|
||||
'message_process: reply on Frogs should not have created a new group with new subject')
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
self.assertEqual(len(frog_groups), 1,
|
||||
'message_process: reply on Frogs should not have created a duplicate group with old subject')
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: one new message
|
||||
self.assertEqual(len(frog_group.message_ids), 4, 'message_process: group should contain 4 messages after reply')
|
||||
|
||||
# Do: due to some issue, same email goes back into the mailgateway
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
|
||||
subject='Re: news', extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
subject='Re: news', extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n')
|
||||
# Test: no group 'Re: news' created, still only 1 Frogs group
|
||||
self.assertEqual(len(frog_groups), 0,
|
||||
'message_process: reply on Frogs should not have created a new group with new subject')
|
||||
|
@ -405,7 +430,7 @@ class TestMailgateway(TestMail):
|
|||
'message_process: reply on Frogs should not have created a duplicate group with old subject')
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: no new message
|
||||
self.assertEqual(len(frog_group.message_ids), 3, 'message_process: message with already existing message_id should not have been duplicated')
|
||||
self.assertEqual(len(frog_group.message_ids), 4, 'message_process: message with already existing message_id should not have been duplicated')
|
||||
# Test: message_id is still unique
|
||||
msg_ids = self.mail_message.search(cr, uid, [('message_id', 'ilike', '<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>')])
|
||||
self.assertEqual(len(msg_ids), 1,
|
||||
|
@ -422,7 +447,7 @@ class TestMailgateway(TestMail):
|
|||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||
to='erroneous@example.com>', subject='Re: news (2)',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.new1@agrolait.com>',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: author is A-Raoul (only existing)
|
||||
|
@ -436,7 +461,7 @@ class TestMailgateway(TestMail):
|
|||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||
to='erroneous@example.com>', subject='Re: news (3)',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.new2@agrolait.com>',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: author is Raoul (user), not A-Raoul
|
||||
|
@ -451,7 +476,7 @@ class TestMailgateway(TestMail):
|
|||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||
to='erroneous@example.com>', subject='Re: news (3)',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.new3@agrolait.com>',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: author is Raoul (user), not A-Raoul
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<field name="auto_delete" eval="True"/>
|
||||
<field name="report_template" ref="sale.report_sale_order"/>
|
||||
<field name="report_name">${(object.name or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field>
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
|
||||
|
||||
|
@ -102,6 +103,7 @@
|
|||
<field name="auto_delete" eval="True"/>
|
||||
<field name="report_template" ref="account.account_invoices"/>
|
||||
<field name="report_name">Invoice_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field>
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
|
||||
|
||||
|
|
|
@ -915,8 +915,11 @@ class purchase_order_line(osv.osv):
|
|||
"""
|
||||
onchange handler of product_uom.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if not uom_id:
|
||||
return {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
|
||||
context = dict(context, purchase_uom_check=True)
|
||||
return self.onchange_product_id(cr, uid, ids, pricelist_id, product_id, qty, uom_id,
|
||||
partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
|
||||
name=name, price_unit=price_unit, context=context)
|
||||
|
@ -990,7 +993,7 @@ class purchase_order_line(osv.osv):
|
|||
uom_id = product_uom_po_id
|
||||
|
||||
if product.uom_id.category_id.id != product_uom.browse(cr, uid, uom_id, context=context).category_id.id:
|
||||
if self._check_product_uom_group(cr, uid, context=context):
|
||||
if context.get('purchase_uom_check') and self._check_product_uom_group(cr, uid, context=context):
|
||||
res['warning'] = {'title': _('Warning!'), 'message': _('Selected Unit of Measure does not belong to the same category as the product Unit of Measure.')}
|
||||
uom_id = product_uom_po_id
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ class sale_report(osv.osv):
|
|||
_description = "Sales Orders Statistics"
|
||||
_auto = False
|
||||
_rec_name = 'date'
|
||||
|
||||
_columns = {
|
||||
'date': fields.date('Date Order', readonly=True),
|
||||
'date_confirm': fields.date('Date Confirm', readonly=True),
|
||||
|
@ -60,12 +61,9 @@ class sale_report(osv.osv):
|
|||
}
|
||||
_order = 'date desc'
|
||||
|
||||
def init(self, cr):
|
||||
tools.drop_view_if_exists(cr, 'sale_report')
|
||||
cr.execute("""
|
||||
create or replace view sale_report as (
|
||||
select
|
||||
min(l.id) as id,
|
||||
def _select(self):
|
||||
select_str = """
|
||||
SELECT min(l.id) as id,
|
||||
l.product_id as product_id,
|
||||
t.uom_id as product_uom,
|
||||
sum(l.product_uom_qty / u.factor * u2.factor) as product_uom_qty,
|
||||
|
@ -84,15 +82,23 @@ class sale_report(osv.osv):
|
|||
t.categ_id as categ_id,
|
||||
s.pricelist_id as pricelist_id,
|
||||
s.project_id as analytic_account_id
|
||||
from
|
||||
sale_order_line l
|
||||
"""
|
||||
return select_str
|
||||
|
||||
def _from(self):
|
||||
from_str = """
|
||||
sale_order_line l
|
||||
join sale_order s on (l.order_id=s.id)
|
||||
left join product_product p on (l.product_id=p.id)
|
||||
left join product_template t on (p.product_tmpl_id=t.id)
|
||||
left join product_uom u on (u.id=l.product_uom)
|
||||
left join product_uom u2 on (u2.id=t.uom_id)
|
||||
group by
|
||||
l.product_id,
|
||||
"""
|
||||
return from_str
|
||||
|
||||
def _group_by(self):
|
||||
group_by_str = """
|
||||
GROUP BY l.product_id,
|
||||
l.order_id,
|
||||
t.uom_id,
|
||||
t.categ_id,
|
||||
|
@ -104,7 +110,16 @@ class sale_report(osv.osv):
|
|||
s.state,
|
||||
s.pricelist_id,
|
||||
s.project_id
|
||||
)
|
||||
""")
|
||||
"""
|
||||
return group_by_str
|
||||
|
||||
def init(self, cr):
|
||||
# self._table = sale_report
|
||||
tools.drop_view_if_exists(cr, self._table)
|
||||
cr.execute("""CREATE or REPLACE VIEW %s as (
|
||||
%s
|
||||
FROM ( %s )
|
||||
%s
|
||||
)""" % (self._table, self._select(), self._from(), self._group_by()))
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -47,10 +47,8 @@
|
|||
<search string="Sales Analysis">
|
||||
<field name="date"/>
|
||||
<field name="date_confirm"/>
|
||||
<filter icon="terp-document-new" string="Quotations" domain="[('state','=','draft')]"/>
|
||||
<filter icon="terp-check" string="Sales" domain="[('state','not in',('draft','done','cancel'))]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-accessories-archiver" string="Picked" domain="[('shipped','=',True)]"/>
|
||||
<filter icon="terp-document-new" name="Quotations" domain="[('state','=','draft')]"/>
|
||||
<filter icon="terp-check" name="Sales" domain="[('state','not in',('draft','done','cancel'))]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-personal" string="My Sales" help="My Sales" domain="[('user_id','=',uid)]"/>
|
||||
<field name="partner_id"/>
|
||||
|
|
|
@ -318,6 +318,9 @@ class sale_order(osv.osv):
|
|||
context = {}
|
||||
if vals.get('name', '/') == '/':
|
||||
vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'sale.order') or '/'
|
||||
if vals.get('partner_id') and any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']):
|
||||
defaults = self.onchange_partner_id(cr, uid, [], vals['partner_id'], context)['value']
|
||||
vals = dict(defaults, **vals)
|
||||
context.update({'mail_create_nolog': True})
|
||||
new_id = super(sale_order, self).create(cr, uid, vals, context=context)
|
||||
self.message_post(cr, uid, [new_id], body=_("Quotation created"), context=context)
|
||||
|
@ -831,6 +834,24 @@ class sale_order_line(osv.osv):
|
|||
pass
|
||||
return {'value': value}
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
if values.get('order_id') and values.get('product_id') and any(f not in values for f in ['name', 'price_unit', 'type', 'product_uom_qty', 'product_uom']):
|
||||
order = self.pool['sale.order'].read(cr, uid, values['order_id'], ['pricelist_id', 'partner_id', 'date_order', 'fiscal_position'], context=context)
|
||||
defaults = self.product_id_change(cr, uid, [], order['pricelist_id'][0], values['product_id'],
|
||||
qty=float(values.get('product_uom_qty', False)),
|
||||
uom=values.get('product_uom', False),
|
||||
qty_uos=float(values.get('product_uos_qty', False)),
|
||||
uos=values.get('product_uos', False),
|
||||
name=values.get('name', False),
|
||||
partner_id=order['partner_id'][0],
|
||||
date_order=order['date_order'],
|
||||
fiscal_position=order['fiscal_position'][0] if order['fiscal_position'] else False,
|
||||
flag=False, # Force name update
|
||||
context=context
|
||||
)['value']
|
||||
values = dict(defaults, **values)
|
||||
return super(sale_order_line, self).create(cr, uid, values, context=context)
|
||||
|
||||
def copy_data(self, cr, uid, id, default=None, context=None):
|
||||
if not default:
|
||||
default = {}
|
||||
|
|
|
@ -78,6 +78,13 @@
|
|||
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule" id="sale_order_report_comp_rule">
|
||||
<field name="name">Sales Order Analysis multi-company</field>
|
||||
<field name="model_id" ref="model_sale_report"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
||||
</record>
|
||||
|
||||
<!-- Multi - Salesmen sales order assignation rules -->
|
||||
|
||||
<record id="sale_order_personal_rule" model="ir.rule">
|
||||
|
@ -93,6 +100,19 @@
|
|||
<field name="groups" eval="[(4, ref('base.group_sale_salesman_all_leads'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="sale_order_report_personal_rule" model="ir.rule">
|
||||
<field name="name">Personal Orders Analysis</field>
|
||||
<field ref="model_sale_report" name="model_id"/>
|
||||
<field name="domain_force">['|',('user_id','=',user.id),('user_id','=',False)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_sale_salesman'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="sale_order_report_see_all" model="ir.rule">
|
||||
<field name="name">All Orders Analysis</field>
|
||||
<field ref="model_sale_report" name="model_id"/>
|
||||
<field name="domain_force">[(1,'=',1)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_sale_salesman_all_leads'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="sale_order_line_personal_rule" model="ir.rule">
|
||||
<field name="name">Personal Order Lines</field>
|
||||
|
|
|
@ -46,6 +46,7 @@ modules.
|
|||
'security/sale_crm_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'report/sale_crm_account_invoice_report_view.xml',
|
||||
'report/sale_report_view.xml',
|
||||
],
|
||||
'js': [
|
||||
'static/src/js/sale_crm.js',
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
##############################################################################
|
||||
|
||||
import sales_crm_account_invoice_report
|
||||
import sale_report
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
|
||||
class sale_report(osv.osv):
|
||||
_inherit = "sale.report"
|
||||
_columns = {
|
||||
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
|
||||
}
|
||||
|
||||
def _select(self):
|
||||
return super(sale_report, self)._select() + ", s.section_id as section_id"
|
||||
|
||||
def _group_by(self):
|
||||
return super(sale_report, self)._group_by() + ", s.section_id"
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_order_product_tree_sale_crm_inherit" model="ir.ui.view">
|
||||
<field name="name">sale.report.tree.sale.crm</field>
|
||||
<field name="model">sale.report</field>
|
||||
<field name="inherit_id" ref="sale.view_order_product_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="product_uom_qty" position="after">
|
||||
<field name="section_id" groups="base.group_multi_salesteams"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_order_product_search_sale_crm_inherit" model="ir.ui.view">
|
||||
<field name="name">sale.report.search.sale.crm</field>
|
||||
<field name="model">sale.report</field>
|
||||
<field name="inherit_id" ref="sale.view_order_product_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<filter name="User" position="after">
|
||||
<separator/>
|
||||
<filter string="Sales Team" context="{'group_by':'section_id'}"/>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
|
@ -53,10 +53,10 @@ class crm_case_section(osv.osv):
|
|||
date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
for id in ids:
|
||||
res[id] = dict()
|
||||
created_domain = [('section_id', '=', id), ('state', 'in', ['draft', 'sent']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)]
|
||||
created_domain = [('section_id', '=', id), ('state', '=', ['draft']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)]
|
||||
res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context)
|
||||
validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent']), ('date_confirm', '>=', date_begin), ('date_order', '<=', date_end)]
|
||||
res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_confirm'], 'amount_total', 'date_confirm', context=context)
|
||||
validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent', 'cancel']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)]
|
||||
res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context)
|
||||
return res
|
||||
|
||||
def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None):
|
||||
|
@ -90,7 +90,7 @@ class crm_case_section(osv.osv):
|
|||
}
|
||||
|
||||
def action_forecast(self, cr, uid, id, value, context=None):
|
||||
return self.write(cr, uid, [id], {'invoiced_forecast': value}, context=context)
|
||||
return self.write(cr, uid, [id], {'invoiced_forecast': round(float(value))}, context=context)
|
||||
|
||||
|
||||
class res_users(osv.Model):
|
||||
|
|
|
@ -211,6 +211,36 @@
|
|||
<field name="act_window_id" ref="sale_crm.action_invoice_salesteams"/>
|
||||
</record>
|
||||
|
||||
<record id="action_order_report_quotation_salesteam" model="ir.actions.act_window">
|
||||
<field name="name">Quotations Analysis</field>
|
||||
<field name="res_model">sale.report</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="domain">[('state','=','draft'),('section_id', '=', active_id)]</field>
|
||||
<field name="context">{'search_default_order_month':1}</field>
|
||||
<field name="help">This report performs analysis on your quotations. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application.</field>
|
||||
</record>
|
||||
|
||||
<record id="action_order_report_so_salesteam" model="ir.actions.act_window">
|
||||
<field name="name">Sales Analysis</field>
|
||||
<field name="res_model">sale.report</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="domain">[('state','not in',('draft','sent','cancel')),('section_id', '=', active_id)]</field>
|
||||
<field name="context">{'search_default_order_month':1}</field>
|
||||
<field name="help">This report performs analysis on your sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application.</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_invoice_report_salesteam" model="ir.actions.act_window">
|
||||
<field name="name">Invoices Analysis</field>
|
||||
<field name="res_model">account.invoice.report</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="domain">[('section_id', '=', active_id),('state', 'not in', ['draft', 'cancel'])]</field>
|
||||
<field name="context">{'search_default_month':1}</field>
|
||||
<field name="help">From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs.</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_section_salesteams_view_form" model="ir.ui.view">
|
||||
<field name="name">crm.case.section.form</field>
|
||||
<field name="model">crm.case.section</field>
|
||||
|
@ -241,9 +271,9 @@
|
|||
<xpath expr="//div[@class='oe_salesteams_leads']" position="after">
|
||||
<div class="oe_salesteams_orders">
|
||||
<a name="%(action_orders_salesteams)d" type="action">Sales Orders</a>
|
||||
<a name="%(sale.action_order_report_all)d" type="action" class="oe_sparkline_bar_link">
|
||||
<field name="monthly_confirmed" widget="sparkline_bar">
|
||||
Revenue of confirmed sales orders per month.<br/>Click the acces to Sales Analysis
|
||||
<a name="%(action_order_report_so_salesteam)d" type="action" class="oe_sparkline_bar_link">
|
||||
<field name="monthly_confirmed" widget="sparkline_bar" options="{'delayIn': '3000'}">
|
||||
Revenue of confirmed sales orders per month.<br/>Click to acces the Sales Analysis.
|
||||
</field>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -251,27 +281,29 @@
|
|||
<xpath expr="//div[@class='oe_salesteams_opportunities']" position="after">
|
||||
<div class="oe_salesteams_invoices" groups="account.group_account_invoice">
|
||||
<a name="%(action_invoice_salesteams)d" type="action">Invoices</a>
|
||||
<a name="%(account.action_account_invoice_report_all)d" type="action" class="oe_sparkline_bar_link">
|
||||
<field name="monthly_invoiced" widget="sparkline_bar">
|
||||
<a name="%(action_account_invoice_report_salesteam)d" type="action" class="oe_sparkline_bar_link">
|
||||
<field name="monthly_invoiced" widget="sparkline_bar" options="{'delayIn': '3000'}">
|
||||
Revenue of sent invoices per month.<br/>Click to see a detailed analysis of invoices.
|
||||
</field>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oe_salesteams_quotations">
|
||||
<a name="%(action_quotations_salesteams)d" type="action" class="oe_sparkline_bar_link">Quotations</a>
|
||||
<a name="%(sale.action_order_report_all)d" type="action" class="oe_sparkline_bar_link">
|
||||
<field name="monthly_quoted" widget="sparkline_bar">
|
||||
Revenue of created quotation per month.<br/>Click to see a detailed analysis of sales.
|
||||
<a name="%(action_order_report_quotation_salesteam)d" type="action" class="oe_sparkline_bar_link">
|
||||
<field name="monthly_quoted" widget="sparkline_bar" options="{'delayIn': '3000'}">
|
||||
Revenue of created quotations per month.<br/>Click to see a detailed analysis.
|
||||
</field>
|
||||
</a>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[@class='oe_items_list']" position="after">
|
||||
<xpath expr="//div[@class='oe_clear']" position="after">
|
||||
<div class="oe_center" t-if="record.invoiced_target.raw_value">
|
||||
<field name="monthly_invoiced" widget="gauge" style="width:160px; height: 120px;" options="{'max_field': 'invoiced_target'}">Invoiced</field>
|
||||
<field name="invoiced_forecast" widget="gauge" style="width:160px; height: 120px;" options="{'max_field': 'invoiced_target', 'action_change': 'action_forecast'}">Forecast</field>
|
||||
<field name="monthly_invoiced" widget="gauge" style="width:160px; height: 120px; cursor: pointer;"
|
||||
options="{'max_field': 'invoiced_target'}">Invoiced</field>
|
||||
<field name="invoiced_forecast" widget="gauge" style="width:160px; height: 120px; cursor: pointer;"
|
||||
options="{'max_field': 'invoiced_target', 'action_change': 'action_forecast'}">Forecast</field>
|
||||
</div>
|
||||
<div class="oe_center" style="color:#bbbbbb;" t-if="!record.invoiced_target.raw_value">
|
||||
<div class="oe_center oe_salesteams_help" style="color:#bbbbbb;" t-if="!record.invoiced_target.raw_value">
|
||||
<br/>Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance.
|
||||
</div>
|
||||
</xpath>
|
||||
|
|
|
@ -47,7 +47,7 @@ class crm_make_sale(osv.osv_memory):
|
|||
return False
|
||||
|
||||
lead = lead_obj.read(cr, uid, active_id, ['partner_id'], context=context)
|
||||
return lead['partner_id'][0]
|
||||
return lead['partner_id'][0] if lead['partner_id'] else False
|
||||
|
||||
def view_init(self, cr, uid, fields_list, context=None):
|
||||
return super(crm_make_sale, self).view_init(cr, uid, fields_list, context=context)
|
||||
|
|
|
@ -39,59 +39,11 @@ class sale_report(osv.osv):
|
|||
('cancel', 'Cancelled')
|
||||
], 'Order Status', readonly=True),
|
||||
}
|
||||
|
||||
def init(self, cr):
|
||||
tools.drop_view_if_exists(cr, 'sale_report')
|
||||
# TODO: make parent view extensible similarly to invoice analysis and
|
||||
# remove the duplication
|
||||
cr.execute("""
|
||||
create or replace view sale_report as (
|
||||
select
|
||||
min(l.id) as id,
|
||||
l.product_id as product_id,
|
||||
t.uom_id as product_uom,
|
||||
sum(l.product_uom_qty / u.factor * u2.factor) as product_uom_qty,
|
||||
sum(l.product_uom_qty * l.price_unit * (100.0-l.discount) / 100.0) as price_total,
|
||||
count(*) as nbr,
|
||||
s.date_order as date,
|
||||
s.date_confirm as date_confirm,
|
||||
to_char(s.date_order, 'YYYY') as year,
|
||||
to_char(s.date_order, 'MM') as month,
|
||||
to_char(s.date_order, 'YYYY-MM-DD') as day,
|
||||
s.partner_id as partner_id,
|
||||
s.user_id as user_id,
|
||||
s.company_id as company_id,
|
||||
s.warehouse_id as warehouse_id,
|
||||
extract(epoch from avg(date_trunc('day',s.date_confirm)-date_trunc('day',s.create_date)))/(24*60*60)::decimal(16,2) as delay,
|
||||
s.state,
|
||||
t.categ_id as categ_id,
|
||||
s.shipped,
|
||||
s.shipped::integer as shipped_qty_1,
|
||||
s.pricelist_id as pricelist_id,
|
||||
s.project_id as analytic_account_id
|
||||
from
|
||||
sale_order_line l
|
||||
join sale_order s on (l.order_id=s.id)
|
||||
left join product_product p on (l.product_id=p.id)
|
||||
left join product_template t on (p.product_tmpl_id=t.id)
|
||||
left join product_uom u on (u.id=l.product_uom)
|
||||
left join product_uom u2 on (u2.id=t.uom_id)
|
||||
group by
|
||||
l.product_id,
|
||||
l.order_id,
|
||||
t.uom_id,
|
||||
t.categ_id,
|
||||
s.date_order,
|
||||
s.date_confirm,
|
||||
s.partner_id,
|
||||
s.user_id,
|
||||
s.warehouse_id,
|
||||
s.company_id,
|
||||
s.state,
|
||||
s.shipped,
|
||||
s.pricelist_id,
|
||||
s.project_id
|
||||
)
|
||||
""")
|
||||
|
||||
def _select(self):
|
||||
return super(sale_report, self)._select() + ", s.warehouse_id as warehouse_id, s.shipped, s.shipped::integer as shipped_qty_1"
|
||||
|
||||
def _group_by(self):
|
||||
return super(sale_report, self)._group_by() + ", s.warehouse_id, s.shipped"
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -17,6 +17,10 @@
|
|||
<field name="model">sale.report</field>
|
||||
<field name="inherit_id" ref="sale.view_order_product_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<filter name="Sales" position="after">
|
||||
<separator/>
|
||||
<filter icon="terp-accessories-archiver" string="Picked" domain="[('shipped','=',True)]"/>
|
||||
</filter>
|
||||
<xpath expr="//group/filter[@string='Status']" position="after">
|
||||
<filter string="Warehouse" context="{'group_by':'warehouse_id'}"/>
|
||||
</xpath>
|
||||
|
|
|
@ -70,7 +70,7 @@ class sale_order(osv.osv):
|
|||
company_id = self.pool.get('res.users')._get_company(cr, uid, context=context)
|
||||
warehouse_ids = self.pool.get('stock.warehouse').search(cr, uid, [('company_id', '=', company_id)], context=context)
|
||||
if not warehouse_ids:
|
||||
raise osv.except_osv(_('Error!'), _('There is no warehouse defined for current company.'))
|
||||
return False
|
||||
return warehouse_ids[0]
|
||||
|
||||
# This is False
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<!-- Add menu: Billing - Deliveries to invoice -->
|
||||
<record id="outgoing_picking_list_to_invoice" model="ir.actions.act_window">
|
||||
<field name="name">Deliveries to Invoice</field>
|
||||
<field name="res_model">stock.picking</field>
|
||||
<field name="res_model">stock.picking.out</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,calendar</field>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</record>
|
||||
|
||||
|
||||
<act_window name="Create Invoice"
|
||||
<act_window name="Create Draft Invoices"
|
||||
res_model="stock.invoice.onshipping"
|
||||
src_model="stock.picking.out"
|
||||
key2="client_action_multi"
|
||||
|
|
Loading…
Reference in New Issue