[MERGE] sync with trunk

bzr revid: mat@openerp.com-20131209154236-jjawy50a8tvmyayb
bzr revid: mat@openerp.com-20131209163936-6n9z81etuejkdpsg
This commit is contained in:
Martin Trigaux 2013-12-09 17:39:36 +01:00
commit 7328a64425
39 changed files with 600 additions and 252 deletions

View File

@ -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 = []

View File

@ -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):

View File

@ -1,4 +1,7 @@
from . import test_tax
from . import test_search
fast_suite = [test_tax,
]
fast_suite = [
test_tax,
test_search,
]

View File

@ -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")

View File

@ -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,

View File

@ -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'''

View File

@ -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">

View File

@ -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)

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,2 @@
crm.css: crm.sass
sass --trace -t expanded crm.sass:crm.css

View File

@ -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;
}

View File

@ -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)

View File

@ -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) {
}
},
});
};

View File

@ -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"),
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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); ">

View File

@ -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

View File

@ -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:

View File

@ -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"/>

View File

@ -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 = {}

View File

@ -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>

View File

@ -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',

View File

@ -20,6 +20,7 @@
##############################################################################
import sales_crm_account_invoice_report
import sale_report
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -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:

View File

@ -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>

View File

@ -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):

View File

@ -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>

View File

@ -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)

View File

@ -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:

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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"