[MERGE] forward port of bugfixes made in 7.0, up to revision 8969.

bzr revid: qdp-launchpad@openerp.com-20130404095257-oek77sijuxjyi0aj
This commit is contained in:
Quentin (OpenERP) 2013-04-04 11:52:57 +02:00
commit 3bb51ac696
68 changed files with 606 additions and 333 deletions

View File

@ -1027,6 +1027,9 @@ class account_period(osv.osv):
def action_draft(self, cr, uid, ids, *args): def action_draft(self, cr, uid, ids, *args):
mode = 'draft' mode = 'draft'
for period in self.browse(cr, uid, ids):
if period.fiscalyear_id.state == 'done':
raise osv.except_osv(_('Warning !'), _('You can not re-open a period which belongs to closed fiscal year'))
cr.execute('update account_journal_period set state=%s where period_id in %s', (mode, tuple(ids),)) cr.execute('update account_journal_period set state=%s where period_id in %s', (mode, tuple(ids),))
cr.execute('update account_period set state=%s where id in %s', (mode, tuple(ids),)) cr.execute('update account_period set state=%s where id in %s', (mode, tuple(ids),))
return True return True

View File

@ -742,7 +742,7 @@ class account_move_line(osv.osv):
def list_partners_to_reconcile(self, cr, uid, context=None): def list_partners_to_reconcile(self, cr, uid, context=None):
cr.execute( cr.execute(
"""SELECT partner_id FROM ( """SELECT partner_id FROM (
SELECT l.partner_id, p.last_reconciliation_date, SUM(l.debit) AS debit, SUM(l.credit) AS credit, MAX(l.date) AS max_date SELECT l.partner_id, p.last_reconciliation_date, SUM(l.debit) AS debit, SUM(l.credit) AS credit, MAX(l.create_date) AS max_date
FROM account_move_line l FROM account_move_line l
RIGHT JOIN account_account a ON (a.id = l.account_id) RIGHT JOIN account_account a ON (a.id = l.account_id)
RIGHT JOIN res_partner p ON (l.partner_id = p.id) RIGHT JOIN res_partner p ON (l.partner_id = p.id)
@ -753,9 +753,14 @@ class account_move_line(osv.osv):
) AS s ) AS s
WHERE debit > 0 AND credit > 0 AND (last_reconciliation_date IS NULL OR max_date > last_reconciliation_date) WHERE debit > 0 AND credit > 0 AND (last_reconciliation_date IS NULL OR max_date > last_reconciliation_date)
ORDER BY last_reconciliation_date""") ORDER BY last_reconciliation_date""")
ids = cr.fetchall() ids = [x[0] for x in cr.fetchall()]
ids = len(ids) and [x[0] for x in ids] or [] if not ids:
return self.pool.get('res.partner').name_get(cr, uid, ids, context=context) return []
# To apply the ir_rules
partner_obj = self.pool.get('res.partner')
ids = partner_obj.search(cr, uid, [('id', 'in', ids)], context=context)
return partner_obj.name_get(cr, uid, ids, context=context)
def reconcile_partial(self, cr, uid, ids, type='auto', context=None, writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False): def reconcile_partial(self, cr, uid, ids, type='auto', context=None, writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False):
move_rec_obj = self.pool.get('account.move.reconcile') move_rec_obj = self.pool.get('account.move.reconcile')

View File

@ -210,8 +210,8 @@ class account_invoice_report(osv.osv):
cr.id IN (SELECT id cr.id IN (SELECT id
FROM res_currency_rate cr2 FROM res_currency_rate cr2
WHERE (cr2.currency_id = sub.currency_id) WHERE (cr2.currency_id = sub.currency_id)
AND ((sub.date IS NOT NULL AND cr.name <= sub.date) AND ((sub.date IS NOT NULL AND cr2.name <= sub.date)
OR (sub.date IS NULL AND cr.name <= NOW())) OR (sub.date IS NULL AND cr2.name <= NOW()))
ORDER BY name DESC LIMIT 1) ORDER BY name DESC LIMIT 1)
)""" % ( )""" % (
self._table, self._table,

View File

@ -168,7 +168,7 @@
</para> </para>
<para style="terp_default_8">Tel. : [[ (o.partner_id.phone) or removeParentNode('para') ]]</para> <para style="terp_default_8">Tel. : [[ (o.partner_id.phone) or removeParentNode('para') ]]</para>
<para style="terp_default_8">Fax : [[ (o.partner_id.fax) or removeParentNode('para') ]]</para> <para style="terp_default_8">Fax : [[ (o.partner_id.fax) or removeParentNode('para') ]]</para>
<para style="terp_default_8">VAT : [[ (o.partner_id.vat) or removeParentNode('para') ]]</para> <para style="terp_default_8">TIN : [[ (o.partner_id.vat) or removeParentNode('para') ]]</para>
</td> </td>
</tr> </tr>
</blockTable> </blockTable>

View File

@ -181,9 +181,9 @@ class account_invoice_refund(osv.osv_memory):
invoice = invoice[0] invoice = invoice[0]
del invoice['id'] del invoice['id']
invoice_lines = inv_line_obj.browse(cr, uid, invoice['invoice_line'], context=context) invoice_lines = inv_line_obj.browse(cr, uid, invoice['invoice_line'], context=context)
invoice_lines = inv_obj._refund_cleanup_lines(cr, uid, invoice_lines) invoice_lines = inv_obj._refund_cleanup_lines(cr, uid, invoice_lines, context=context)
tax_lines = inv_tax_obj.browse(cr, uid, invoice['tax_line'], context=context) tax_lines = inv_tax_obj.browse(cr, uid, invoice['tax_line'], context=context)
tax_lines = inv_obj._refund_cleanup_lines(cr, uid, tax_lines) tax_lines = inv_obj._refund_cleanup_lines(cr, uid, tax_lines, context=context)
invoice.update({ invoice.update({
'type': inv.type, 'type': inv.type,
'date_invoice': date, 'date_invoice': date,

View File

@ -352,11 +352,10 @@ class account_analytic_account(osv.osv):
res[account.id] = 0.0 res[account.id] = 0.0
sale_ids = sale_obj.search(cr, uid, [('project_id','=', account.id), ('state', '=', 'manual')], context=context) sale_ids = sale_obj.search(cr, uid, [('project_id','=', account.id), ('state', '=', 'manual')], context=context)
for sale in sale_obj.browse(cr, uid, sale_ids, context=context): for sale in sale_obj.browse(cr, uid, sale_ids, context=context):
if not sale.invoiced: res[account.id] += sale.amount_untaxed
res[account.id] += sale.amount_untaxed for invoice in sale.invoice_ids:
for invoice in sale.invoice_ids: if invoice.state != 'cancel':
if invoice.state not in ('draft', 'cancel'): res[account.id] -= invoice.amount_untaxed
res[account.id] -= invoice.amount_untaxed
return res return res
def _timesheet_ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None): def _timesheet_ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None):

View File

@ -5,6 +5,7 @@ openerp.auth_oauth = function(instance) {
start: function(parent, params) { start: function(parent, params) {
var self = this; var self = this;
var d = this._super.apply(this, arguments); var d = this._super.apply(this, arguments);
this.$el.hide();
this.$el.on('click', 'a.zocial', this.on_oauth_sign_in); this.$el.on('click', 'a.zocial', this.on_oauth_sign_in);
this.oauth_providers = []; this.oauth_providers = [];
if(this.params.oauth_error === 1) { if(this.params.oauth_error === 1) {
@ -24,6 +25,8 @@ openerp.auth_oauth = function(instance) {
var db = this.$("form [name=db]").val(); var db = this.$("form [name=db]").val();
if (db) { if (db) {
this.rpc("/auth_oauth/list_providers", { dbname: db }).done(this.on_oauth_loaded); this.rpc("/auth_oauth/list_providers", { dbname: db }).done(this.on_oauth_loaded);
} else {
this.$el.show();
} }
}, },
on_oauth_loaded: function(result) { on_oauth_loaded: function(result) {
@ -32,6 +35,7 @@ openerp.auth_oauth = function(instance) {
if (this.oauth_providers.length === 1 && params.type === 'signup') { if (this.oauth_providers.length === 1 && params.type === 'signup') {
this.do_oauth_sign_in(this.oauth_providers[0]); this.do_oauth_sign_in(this.oauth_providers[0]);
} else { } else {
this.$el.show();
this.$('.oe_oauth_provider_login_button').remove(); this.$('.oe_oauth_provider_login_button').remove();
var buttons = QWeb.render("auth_oauth.Login.button",{"widget":this}); var buttons = QWeb.render("auth_oauth.Login.button",{"widget":this});
this.$(".oe_login_pane form ul").after(buttons); this.$(".oe_login_pane form ul").after(buttons);
@ -57,7 +61,7 @@ openerp.auth_oauth = function(instance) {
state: JSON.stringify(state), state: JSON.stringify(state),
}; };
var url = provider.auth_endpoint + '?' + $.param(params); var url = provider.auth_endpoint + '?' + $.param(params);
window.location = url; instance.web.redirect(url);
}, },
_oauth_state: function(provider) { _oauth_state: function(provider) {
// return the state object sent back with the redirected uri // return the state object sent back with the redirected uri

View File

@ -419,8 +419,10 @@ instance.board.AddToDashboard = instance.web.search.Input.extend({
instance.web.SearchView.include({ instance.web.SearchView.include({
add_common_inputs: function() { add_common_inputs: function() {
this._super(); this._super();
(new instance.board.AddToDashboard(this)); var vm = this.getParent().getParent();
if (vm.inner_action && vm.inner_action.views) {
(new instance.board.AddToDashboard(this));
}
} }
}); });

View File

@ -103,16 +103,16 @@ Dashboard for CRM will include:
'crm_action_rule_demo.xml', 'crm_action_rule_demo.xml',
], ],
'test': [ 'test': [
'test/process/communication_with_customer.yml', 'test/crm_lead_message.yml',
'test/process/lead2opportunity2win.yml', 'test/lead2opportunity2win.yml',
'test/process/lead2opportunity_assign_salesmen.yml', 'test/lead2opportunity_assign_salesmen.yml',
'test/process/merge_opportunity.yml', 'test/crm_lead_merge.yml',
'test/process/cancel_lead.yml', 'test/crm_lead_cancel.yml',
'test/process/segmentation.yml', 'test/segmentation.yml',
'test/process/phonecalls.yml', 'test/phonecalls.yml',
'test/ui/crm_demo.yml', 'test/crm_lead_onchange.yml',
'test/ui/duplicate_lead.yml', 'test/crm_lead_copy.yml',
'test/ui/delete_lead.yml', 'test/crm_lead_unlink.yml',
], ],
'installable': True, 'installable': True,
'application': True, 'application': True,

View File

@ -22,6 +22,7 @@
from openerp.addons.base_status.base_stage import base_stage from openerp.addons.base_status.base_stage import base_stage
import crm import crm
from datetime import datetime from datetime import datetime
from operator import itemgetter
from openerp.osv import fields, osv from openerp.osv import fields, osv
import time import time
from openerp import tools from openerp import tools
@ -628,12 +629,13 @@ class crm_lead(base_stage, format_address, osv.osv):
opportunities = self.browse(cr, uid, ids, context=context) opportunities = self.browse(cr, uid, ids, context=context)
sequenced_opps = [] sequenced_opps = []
for opportunity in opportunities: for opportunity in opportunities:
sequence = -1
if opportunity.stage_id and opportunity.stage_id.state != 'cancel': if opportunity.stage_id and opportunity.stage_id.state != 'cancel':
sequenced_opps.append((opportunity.stage_id.sequence, opportunity)) sequence = opportunity.stage_id.sequence
else: sequenced_opps.append(((int(sequence != -1 and opportunity.type == 'opportunity'), sequence, -opportunity.id), opportunity))
sequenced_opps.append((-1, opportunity))
sequenced_opps.sort(key=lambda tup: tup[0], reverse=True) sequenced_opps.sort(reverse=True)
opportunities = [opportunity for sequence, opportunity in sequenced_opps] opportunities = map(itemgetter(1), sequenced_opps)
ids = [opportunity.id for opportunity in opportunities] ids = [opportunity.id for opportunity in opportunities]
highest = opportunities[0] highest = opportunities[0]
opportunities_rest = opportunities[1:] opportunities_rest = opportunities[1:]
@ -652,11 +654,10 @@ class crm_lead(base_stage, format_address, osv.osv):
opportunities.extend(opportunities_rest) opportunities.extend(opportunities_rest)
self._merge_notify(cr, uid, highest, opportunities, context=context) self._merge_notify(cr, uid, highest, opportunities, context=context)
# Check if the stage is in the stages of the sales team. If not, assign the stage with the lowest sequence # Check if the stage is in the stages of the sales team. If not, assign the stage with the lowest sequence
if merged_data.get('type') == 'opportunity' and merged_data.get('section_id'): if merged_data.get('section_id'):
section_stages = self.pool.get('crm.case.section').read(cr, uid, merged_data['section_id'], ['stage_ids'], context=context) section_stage_ids = self.pool.get('crm.case.stage').search(cr, uid, [('section_ids', 'in', merged_data['section_id']), ('type', '=', merged_data.get('type'))], order='sequence', context=context)
if merged_data.get('stage_id') not in section_stages['stage_ids']: if merged_data.get('stage_id') not in section_stage_ids:
stages_sequences = self.pool.get('crm.case.stage').search(cr, uid, [('id','in',section_stages['stage_ids'])], order='sequence', limit=1, context=context) merged_data['stage_id'] = section_stage_ids and section_stage_ids[0] or False
merged_data['stage_id'] = stages_sequences and stages_sequences[0] or False
# Write merged data into first opportunity # Write merged data into first opportunity
self.write(cr, uid, [highest.id], merged_data, context=context) self.write(cr, uid, [highest.id], merged_data, context=context)
# Delete tail opportunities # Delete tail opportunities

View File

@ -7,68 +7,68 @@
<field name="name">New</field> <field name="name">New</field>
<field eval="1" name="case_default"/> <field eval="1" name="case_default"/>
<field name="state">draft</field> <field name="state">draft</field>
<field eval="'10'" name="probability"/> <field eval="0" name="probability"/>
<field eval="'10'" name="sequence"/> <field eval="10" name="sequence"/>
<field name="type">both</field> <field name="type">both</field>
</record> </record>
<record model="crm.case.stage" id="stage_lead2"> <record model="crm.case.stage" id="stage_lead2">
<field name="name">Opportunity</field> <field name="name">Opportunity</field>
<field eval="1" name="case_default"/> <field eval="1" name="case_default"/>
<field name="state">open</field> <field name="state">open</field>
<field eval="'20'" name="probability"/> <field eval="20" name="probability"/>
<field eval="'11'" name="sequence"/> <field eval="20" name="sequence"/>
<field name="type">lead</field> <field name="type">lead</field>
</record> </record>
<record model="crm.case.stage" id="stage_lead3">
<field name="name">Qualification</field>
<field eval="1" name="case_default"/>
<field name="state">open</field>
<field eval="'20'" name="probability"/>
<field eval="'12'" name="sequence"/>
<field name="type">opportunity</field>
</record>
<record model="crm.case.stage" id="stage_lead4">
<field name="name">Proposition</field>
<field eval="1" name="case_default"/>
<field name="state">open</field>
<field eval="'40'" name="probability"/>
<field eval="'13'" name="sequence"/>
<field name="type">opportunity</field>
</record>
<record model="crm.case.stage" id="stage_lead5">
<field name="name">Negotiation</field>
<field eval="1" name="case_default"/>
<field name="state">open</field>
<field eval="'60'" name="probability"/>
<field eval="'14'" name="sequence"/>
<field name="type">opportunity</field>
</record>
<record model="crm.case.stage" id="stage_lead6">
<field name="name">Won</field>
<field eval="1" name="case_default"/>
<field name="state">done</field>
<field eval="'100'" name="probability"/>
<field eval="'15'" name="sequence"/>
<field eval="1" name="on_change"/>
<field name="type">opportunity</field>
</record>
<record model="crm.case.stage" id="stage_lead7"> <record model="crm.case.stage" id="stage_lead7">
<field name="name">Dead</field> <field name="name">Dead</field>
<field eval="1" name="case_default"/> <field eval="1" name="case_default"/>
<field eval="False" name="fold"/> <field eval="False" name="fold"/>
<field name="state">cancel</field> <field name="state">cancel</field>
<field eval="'0'" name="probability"/> <field eval="0" name="probability"/>
<field eval="'16'" name="sequence"/> <field eval="30" name="sequence"/>
<field name="type">lead</field> <field name="type">lead</field>
</record> </record>
<record model="crm.case.stage" id="stage_lead3">
<field name="name">Qualification</field>
<field eval="1" name="case_default"/>
<field name="state">open</field>
<field eval="20" name="probability"/>
<field eval="100" name="sequence"/>
<field name="type">opportunity</field>
</record>
<record model="crm.case.stage" id="stage_lead4">
<field name="name">Proposition</field>
<field eval="1" name="case_default"/>
<field name="state">open</field>
<field eval="40" name="probability"/>
<field eval="110" name="sequence"/>
<field name="type">opportunity</field>
</record>
<record model="crm.case.stage" id="stage_lead5">
<field name="name">Negotiation</field>
<field eval="1" name="case_default"/>
<field name="state">open</field>
<field eval="60" name="probability"/>
<field eval="120" name="sequence"/>
<field name="type">opportunity</field>
</record>
<record model="crm.case.stage" id="stage_lead6">
<field name="name">Won</field>
<field eval="1" name="case_default"/>
<field name="state">done</field>
<field eval="100" name="probability"/>
<field eval="130" name="sequence"/>
<field eval="1" name="on_change"/>
<field name="type">opportunity</field>
</record>
<record model="crm.case.stage" id="stage_lead8"> <record model="crm.case.stage" id="stage_lead8">
<field name="name">Lost</field> <field name="name">Lost</field>
<field eval="1" name="case_default"/> <field eval="1" name="case_default"/>
<field eval="True" name="fold"/> <field eval="True" name="fold"/>
<field eval="1" name="on_change"/> <field eval="1" name="on_change"/>
<field name="state">cancel</field> <field name="state">cancel</field>
<field eval="'0'" name="probability"/> <field eval="0" name="probability"/>
<field eval="'17'" name="sequence"/> <field eval="140" name="sequence"/>
<field name="type">opportunity</field> <field name="type">opportunity</field>
</record> </record>

View File

@ -100,6 +100,7 @@
<button name="case_cancel" string="Cancel Case" type="object" <button name="case_cancel" string="Cancel Case" type="object"
states="draft,open,pending"/> states="draft,open,pending"/>
<field name="stage_id" widget="statusbar" clickable="True" <field name="stage_id" widget="statusbar" clickable="True"
domain="['&amp;', '|', ('case_default', '=', True), ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"
on_change="onchange_stage_id(stage_id)"/> on_change="onchange_stage_id(stage_id)"/>
</header> </header>
<sheet> <sheet>

View File

@ -11,9 +11,7 @@ access_crm_lead_manager,crm.lead.manager,model_crm_lead,base.group_sale_manager,
access_crm_phonecall_manager,crm.phonecall.manager,model_crm_phonecall,base.group_sale_manager,1,1,1,1 access_crm_phonecall_manager,crm.phonecall.manager,model_crm_phonecall,base.group_sale_manager,1,1,1,1
access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_user,1,0,0,0 access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_user,1,0,0,0
access_crm_lead,crm.lead,model_crm_lead,base.group_sale_salesman,1,1,1,0 access_crm_lead,crm.lead,model_crm_lead,base.group_sale_salesman,1,1,1,0
access_crm_lead_all,crm.lead.all,model_crm_lead,base.group_user,1,0,0,0
access_crm_phonecall,crm.phonecall,model_crm_phonecall,base.group_sale_salesman,1,1,1,0 access_crm_phonecall,crm.phonecall,model_crm_phonecall,base.group_sale_salesman,1,1,1,0
access_crm_phonecall_all,crm.phonecall.all,model_crm_phonecall,base.group_user,1,0,0,0
access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,1,1,0 access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,1,1,0
access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1 access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1
access_crm_case_stage,crm.case.stage,model_crm_case_stage,,1,0,0,0 access_crm_case_stage,crm.case.stage,model_crm_case_stage,,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
11 access_crm_phonecall_manager crm.phonecall.manager model_crm_phonecall base.group_sale_manager 1 1 1 1
12 access_crm_case_categ crm.case.categ model_crm_case_categ base.group_user 1 0 0 0
13 access_crm_lead crm.lead model_crm_lead base.group_sale_salesman 1 1 1 0
access_crm_lead_all crm.lead.all model_crm_lead base.group_user 1 0 0 0
14 access_crm_phonecall crm.phonecall model_crm_phonecall base.group_sale_salesman 1 1 1 0
access_crm_phonecall_all crm.phonecall.all model_crm_phonecall base.group_user 1 0 0 0
15 access_crm_case_section_user crm.case.section.user model_crm_case_section base.group_sale_salesman 1 1 1 0
16 access_crm_case_section_manager crm.case.section.manager model_crm_case_section base.group_sale_manager 1 1 1 1
17 access_crm_case_stage crm.case.stage model_crm_case_stage 1 0 0 0

View File

@ -37,20 +37,19 @@
I check for the resulting merged opp (based on name and partner). I check for the resulting merged opp (based on name and partner).
- -
!python {model: crm.lead}: | !python {model: crm.lead}: |
merge_id = self.search(cr, uid, [('name', '=', 'Test lead 1'), ('partner_id','=', ref("base.res_partner_1"))]) merge_id = self.search(cr, uid, [('name', '=', 'Test opportunity 1'), ('partner_id','=', ref("base.res_partner_5"))])
assert merge_id, 'Fail to create merge opportunity wizard' assert merge_id, 'Fail to create merge opportunity wizard'
merge_result = self.browse(cr, uid, merge_id)[0] merge_result = self.browse(cr, uid, merge_id)[0]
assert merge_result.partner_id.id == ref("base.res_partner_1"), 'Partner mismatch: when merging leads/opps with different m2o values, the first not null value prevails (the other are dropped)' assert merge_result.description == 'This is the description of the test opp 1.\n\nThis is the description of the test lead 1.\n\nThis is the description of the test lead 2.', 'Description mismatch: when merging leads/opps with different text values, these values should get concatenated and separated with line returns'
assert merge_result.description == 'This is the description of the test lead 1.\n\nThis is the description of the test lead 2.\n\nThis is the description of the test opp 1.', 'Description mismatch: when merging leads/opps with different text values, these values should get concatenated and separated with line returns'
assert merge_result.type == 'opportunity', 'Type mismatch: when at least one opp in involved in the merge, the result should be a new opp (instead of %s)' % merge_result.type assert merge_result.type == 'opportunity', 'Type mismatch: when at least one opp in involved in the merge, the result should be a new opp (instead of %s)' % merge_result.type
- -
The other (tailing) leads/opps shouldn't exist anymore. The other (tailing) leads/opps shouldn't exist anymore.
- -
!python {model: crm.lead}: | !python {model: crm.lead}: |
tailing_lead = self.search(cr, uid, [('id', '=', ref('test_crm_lead_02'))]) tailing_lead = self.search(cr, uid, [('id', '=', ref('test_crm_lead_01'))])
assert not tailing_lead, 'This tailing lead (id %s) should not exist anymore' % ref('test_crm_lead_02') assert not tailing_lead, 'This tailing lead (id %s) should not exist anymore' % ref('test_crm_lead_02')
tailing_opp = self.search(cr, uid, [('id', '=', ref('test_crm_opp_01'))]) tailing_opp = self.search(cr, uid, [('id', '=', ref('test_crm_lead_02'))])
assert not tailing_opp, 'This tailing opp (id %s) should not exist anymore' % ref('test_crm_opp_01') assert not tailing_opp, 'This tailing opp (id %s) should not exist anymore' % ref('test_crm_opp_01')
- -
I want to test leads merge. Start by creating two leads (with the same partner). I want to test leads merge. Start by creating two leads (with the same partner).

View File

@ -258,7 +258,9 @@
name="Documents" name="Documents"
action="action_document_file_form" action="action_document_file_form"
id="menu_document_files" id="menu_document_files"
parent="menu_document_doc"/> parent="menu_document_doc"
sequence="0"
/>
<record model="ir.actions.act_window" id="action_document_file_directory_form"> <record model="ir.actions.act_window" id="action_document_file_directory_form">
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>

View File

@ -228,6 +228,8 @@ class hr_expense_expense(osv.osv):
for exp in self.browse(cr, uid, ids, context=context): for exp in self.browse(cr, uid, ids, context=context):
if not exp.employee_id.address_home_id: if not exp.employee_id.address_home_id:
raise osv.except_osv(_('Error!'), _('The employee must have a home address.')) raise osv.except_osv(_('Error!'), _('The employee must have a home address.'))
if not exp.employee_id.address_home_id.property_account_payable.id:
raise osv.except_osv(_('Error!'), _('The employee must have a payable account set on his home address.'))
company_currency = exp.company_id.currency_id.id company_currency = exp.company_id.currency_id.id
diff_currency_p = exp.currency_id.id <> company_currency diff_currency_p = exp.currency_id.id <> company_currency
@ -333,6 +335,8 @@ class hr_expense_expense(osv.osv):
acc = line.product_id.property_account_expense acc = line.product_id.property_account_expense
if not acc: if not acc:
acc = line.product_id.categ_id.property_account_expense_categ acc = line.product_id.categ_id.property_account_expense_categ
if not acc:
raise osv.except_osv(_('Error!'), _('No purchase account found for the product %s (or for his category), please configure one.') % (line.product_id.name))
else: else:
acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company.id}) acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company.id})
if not acc: if not acc:

View File

@ -112,7 +112,7 @@
<field name="date_value" string="Expense Date"/> <field name="date_value" string="Expense Date"/>
<field name="name"/> <field name="name"/>
<field name="ref"/> <field name="ref"/>
<field domain="[('type','in',['normal','contract']), ('parent_id','!=',False)]" name="analytic_account" groups="analytic.group_analytic_accounting"/> <field domain="[('type','in',['normal','contract'])]" name="analytic_account" groups="analytic.group_analytic_accounting"/>
<field name="uom_id" on_change="onchange_uom(product_id, uom_id, context)"/> <field name="uom_id" on_change="onchange_uom(product_id, uom_id, context)"/>
<field name="unit_amount"/> <field name="unit_amount"/>
<field name="unit_quantity"/> <field name="unit_quantity"/>

View File

@ -184,6 +184,16 @@ class hr_holidays(osv.osv):
('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be anterior to the end date."), ('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be anterior to the end date."),
('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."), ('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."),
] ]
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
if context is None:
context = {}
default = default.copy()
default['date_from'] = False
default['date_to'] = False
return super(hr_holidays, self).copy(cr, uid, id, default, context=context)
def _create_resource_leave(self, cr, uid, leaves, context=None): def _create_resource_leave(self, cr, uid, leaves, context=None):
'''This method will create entry in resource calendar leave object at the time of holidays validated ''' '''This method will create entry in resource calendar leave object at the time of holidays validated '''

View File

@ -169,7 +169,7 @@ class lunch_order(osv.Model):
line_ref = self.pool.get("lunch.order.line") line_ref = self.pool.get("lunch.order.line")
if view_type == 'form': if view_type == 'form':
doc = etree.XML(res['arch']) doc = etree.XML(res['arch'])
pref_ids = line_ref.search(cr, uid, [('user_id', '=', uid)], order='create_date desc', context=context) pref_ids = line_ref.search(cr, uid, [('user_id', '=', uid)], order='id desc', context=context)
xml_start = etree.Element("div") xml_start = etree.Element("div")
#If there are no preference (it's the first time for the user) #If there are no preference (it's the first time for the user)
if len(pref_ids)==0: if len(pref_ids)==0:
@ -215,63 +215,70 @@ class lunch_order(osv.Model):
currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
#For each preferences that we get, we will create the XML structure #For each preferences that we get, we will create the XML structure
for key,value in categories.items(): for key, value in categories.items():
xml_pref_1 = etree.Element("div") xml_pref_1 = etree.Element("div")
xml_pref_1.set('class','oe_lunch_30pc') xml_pref_1.set('class', 'oe_lunch_30pc')
xml_pref_2 = etree.Element("h2") xml_pref_2 = etree.Element("h2")
xml_pref_2.text = key xml_pref_2.text = key
xml_pref_1.append(xml_pref_2) xml_pref_1.append(xml_pref_2)
i = 0 i = 0
value = value.values() value = value.values()
#TODO: sorted_values is used for a quick and dirty hack in order to display the 5 last orders of each categories.
#It would be better to fetch only the 5 items to display instead of fetching everything then sorting them in order to keep only the 5 last.
#NB: The note could also be ignored + we could fetch the preferences on the most ordered products instead of the last ones...
sorted_values = {}
for val in value: for val in value:
for pref in val.values(): for elmt in val.values():
#We only show 5 preferences per category (or it will be too long) sorted_values[elmt.id] = elmt
if i==5: break for key, pref in sorted(sorted_values.iteritems(), key=lambda (k, v): (k, v), reverse=True):
i+=1 #We only show 5 preferences per category (or it will be too long)
xml_pref_3 = etree.Element("div") if i == 5:
xml_pref_3.set('class','oe_lunch_vignette') break
xml_pref_1.append(xml_pref_3) i += 1
xml_pref_3 = etree.Element("div")
xml_pref_3.set('class','oe_lunch_vignette')
xml_pref_1.append(xml_pref_3)
xml_pref_4 = etree.Element("span") xml_pref_4 = etree.Element("span")
xml_pref_4.set('class','oe_lunch_button') xml_pref_4.set('class','oe_lunch_button')
xml_pref_3.append(xml_pref_4) xml_pref_3.append(xml_pref_4)
xml_pref_5 = etree.Element("button") xml_pref_5 = etree.Element("button")
xml_pref_5.set('name',"add_preference_"+str(pref.id)) xml_pref_5.set('name',"add_preference_"+str(pref.id))
xml_pref_5.set('class','oe_link oe_i oe_button_plus') xml_pref_5.set('class','oe_link oe_i oe_button_plus')
xml_pref_5.set('type','object') xml_pref_5.set('type','object')
xml_pref_5.set('string','+') xml_pref_5.set('string','+')
xml_pref_4.append(xml_pref_5) xml_pref_4.append(xml_pref_5)
xml_pref_6 = etree.Element("button") xml_pref_6 = etree.Element("button")
xml_pref_6.set('name',"add_preference_"+str(pref.id)) xml_pref_6.set('name',"add_preference_"+str(pref.id))
xml_pref_6.set('class','oe_link oe_button_add') xml_pref_6.set('class','oe_link oe_button_add')
xml_pref_6.set('type','object') xml_pref_6.set('type','object')
xml_pref_6.set('string',_("Add")) xml_pref_6.set('string',_("Add"))
xml_pref_4.append(xml_pref_6) xml_pref_4.append(xml_pref_6)
xml_pref_7 = etree.Element("div") xml_pref_7 = etree.Element("div")
xml_pref_7.set('class','oe_group_text_button') xml_pref_7.set('class','oe_group_text_button')
xml_pref_3.append(xml_pref_7) xml_pref_3.append(xml_pref_7)
xml_pref_8 = etree.Element("div") xml_pref_8 = etree.Element("div")
xml_pref_8.set('class','oe_lunch_text') xml_pref_8.set('class','oe_lunch_text')
xml_pref_8.text = escape(pref.product_id.name)+str(" ") xml_pref_8.text = escape(pref.product_id.name)+str(" ")
xml_pref_7.append(xml_pref_8) xml_pref_7.append(xml_pref_8)
price = pref.product_id.price or 0.0 price = pref.product_id.price or 0.0
cur = currency.name or '' cur = currency.name or ''
xml_pref_9 = etree.Element("span") xml_pref_9 = etree.Element("span")
xml_pref_9.set('class','oe_tag') xml_pref_9.set('class','oe_tag')
xml_pref_9.text = str(price)+str(" ")+cur xml_pref_9.text = str(price)+str(" ")+cur
xml_pref_8.append(xml_pref_9) xml_pref_8.append(xml_pref_9)
xml_pref_10 = etree.Element("div") xml_pref_10 = etree.Element("div")
xml_pref_10.set('class','oe_grey') xml_pref_10.set('class','oe_grey')
xml_pref_10.text = escape(pref.note or '') xml_pref_10.text = escape(pref.note or '')
xml_pref_3.append(xml_pref_10) xml_pref_3.append(xml_pref_10)
xml_start.append(xml_pref_1) xml_start.append(xml_pref_1)
first_node = doc.xpath("//div[@name='preferences']") first_node = doc.xpath("//div[@name='preferences']")
if first_node and len(first_node)>0: if first_node and len(first_node)>0:

View File

@ -144,11 +144,19 @@ class mail_group(osv.Model):
search_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'view_message_search') search_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'view_message_search')
params = { params = {
'search_view_id': search_ref and search_ref[1] or False, 'search_view_id': search_ref and search_ref[1] or False,
'domain': [('model', '=', 'mail.group'), ('res_id', '=', mail_group_id)], 'domain': [
'context': {'default_model': 'mail.group', 'default_res_id': mail_group_id, 'search_default_message_unread': True}, ('model', '=', 'mail.group'),
('res_id', '=', mail_group_id),
],
'context': {
'default_model': 'mail.group',
'default_res_id': mail_group_id,
},
'res_model': 'mail.message', 'res_model': 'mail.message',
'thread_level': 1, 'thread_level': 1,
'header_description': self._generate_header_description(cr, uid, group, context=context) 'header_description': self._generate_header_description(cr, uid, group, context=context),
'view_mailbox': True,
'compose_placeholder': 'Send a message to the group',
} }
cobj = self.pool.get('ir.actions.client') cobj = self.pool.get('ir.actions.client')
newref = cobj.copy(cr, SUPERUSER_ID, ref[1], default={'params': str(params), 'name': vals['name']}, context=context) newref = cobj.copy(cr, SUPERUSER_ID, ref[1], default={'params': str(params), 'name': vals['name']}, context=context)

View File

@ -7,7 +7,6 @@
<field name="tag">mail.wall</field> <field name="tag">mail.wall</field>
<field name="res_model">mail.message</field> <field name="res_model">mail.message</field>
<field name="context">{ <field name="context">{
'search_default_message_unread': True
}</field> }</field>
<field name="params">{ <field name="params">{
'read_action': 'read' 'read_action': 'read'

View File

@ -256,7 +256,19 @@ class mail_mail(osv.Model):
body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context) body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context)
subject = self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context) subject = self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context)
body_alternative = tools.html2plaintext(body) body_alternative = tools.html2plaintext(body)
email_to = ['%s <%s>' % (partner.name, partner.email)] if partner else tools.email_split(mail.email_to)
# generate email_to, heuristic:
# 1. if 'partner' is specified and there is a related document: Followers of 'Doc' <email>
# 2. if 'partner' is specified, but no related document: Partner Name <email>
# 3; fallback on mail.email_to that we split to have an email addresses list
if partner and mail.record_name:
sanitized_record_name = re.sub(r'[^\w+.]+', '-', mail.record_name)
email_to = [_('"Followers of %s" <%s>') % (sanitized_record_name, partner.email)]
elif partner:
email_to = ['%s <%s>' % (partner.name, partner.email)]
else:
email_to = tools.email_split(mail.email_to)
return { return {
'body': body, 'body': body,
'body_alternative': body_alternative, 'body_alternative': body_alternative,

View File

@ -268,19 +268,25 @@ class mail_message(osv.Model):
domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)] domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)]
if not create_missing: if not create_missing:
domain += [('starred', '=', not starred)] domain += [('starred', '=', not starred)]
values = {
'starred': starred
}
if starred:
values['read'] = False
notif_ids = notification_obj.search(cr, uid, domain, context=context) notif_ids = notification_obj.search(cr, uid, domain, context=context)
# all message have notifications: already set them as (un)starred # all message have notifications: already set them as (un)starred
if len(notif_ids) == len(msg_ids) or not create_missing: if len(notif_ids) == len(msg_ids) or not create_missing:
notification_obj.write(cr, uid, notif_ids, {'starred': starred}, context=context) notification_obj.write(cr, uid, notif_ids, values, context=context)
return starred return starred
# some messages do not have notifications: find which one, create notification, update starred status # some messages do not have notifications: find which one, create notification, update starred status
notified_msg_ids = [notification.message_id.id for notification in notification_obj.browse(cr, uid, notif_ids, context=context)] notified_msg_ids = [notification.message_id.id for notification in notification_obj.browse(cr, uid, notif_ids, context=context)]
to_create_msg_ids = list(set(msg_ids) - set(notified_msg_ids)) to_create_msg_ids = list(set(msg_ids) - set(notified_msg_ids))
for msg_id in to_create_msg_ids: for msg_id in to_create_msg_ids:
notification_obj.create(cr, uid, {'partner_id': user_pid, 'starred': starred, 'message_id': msg_id}, context=context) notification_obj.create(cr, uid, dict(values, partner_id=user_pid, message_id=msg_id), context=context)
notification_obj.write(cr, uid, notif_ids, {'starred': starred}, context=context) notification_obj.write(cr, uid, notif_ids, values, context=context)
return starred return starred
#------------------------------------------------------ #------------------------------------------------------

View File

@ -468,6 +468,8 @@ class mail_thread(osv.AbstractModel):
""" """
assert isinstance(message, Message), 'message must be an email.message.Message at this point' assert isinstance(message, Message), 'message must be an email.message.Message at this point'
message_id = message.get('Message-Id') message_id = message.get('Message-Id')
email_from = decode_header(message, 'From')
email_to = decode_header(message, 'To')
references = decode_header(message, 'References') references = decode_header(message, 'References')
in_reply_to = decode_header(message, 'In-Reply-To') in_reply_to = decode_header(message, 'In-Reply-To')
@ -481,8 +483,8 @@ class mail_thread(osv.AbstractModel):
model_pool = self.pool.get(model) model_pool = self.pool.get(model)
if thread_id and model and model_pool and model_pool.exists(cr, uid, thread_id) \ if thread_id and model and model_pool and model_pool.exists(cr, uid, thread_id) \
and hasattr(model_pool, 'message_update'): and hasattr(model_pool, 'message_update'):
_logger.debug('Routing mail with Message-Id %s: direct reply to model: %s, thread_id: %s, custom_values: %s, uid: %s', _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',
message_id, model, thread_id, custom_values, uid) email_from, email_to, message_id, model, thread_id, custom_values, uid)
return [(model, thread_id, custom_values, uid)] return [(model, thread_id, custom_values, uid)]
# Verify whether this is a reply to a private message # Verify whether this is a reply to a private message
@ -493,8 +495,8 @@ class mail_thread(osv.AbstractModel):
], limit=1, context=context) ], limit=1, context=context)
if message_ids: if message_ids:
message = self.pool.get('mail.message').browse(cr, uid, message_ids[0], context=context) message = self.pool.get('mail.message').browse(cr, uid, message_ids[0], context=context)
_logger.debug('Routing mail with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s', _logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
message_id, message.id, custom_values, uid) email_from, email_to, message_id, message.id, custom_values, uid)
return [(message.model, message.res_id, custom_values, uid)] return [(message.model, message.res_id, custom_values, uid)]
# 2. Look for a matching mail.alias entry # 2. Look for a matching mail.alias entry
@ -521,10 +523,11 @@ class mail_thread(osv.AbstractModel):
# Note: recognized partners will be added as followers anyway # Note: recognized partners will be added as followers anyway
# user_id = self._message_find_user_id(cr, uid, message, context=context) # user_id = self._message_find_user_id(cr, uid, message, context=context)
user_id = uid user_id = uid
_logger.debug('No matching user_id for the alias %s', alias.alias_name) _logger.info('No matching user_id for the alias %s', alias.alias_name)
routes.append((alias.alias_model_id.model, alias.alias_force_thread_id, \ routes.append((alias.alias_model_id.model, alias.alias_force_thread_id, \
eval(alias.alias_defaults), user_id)) eval(alias.alias_defaults), user_id))
_logger.debug('Routing mail with Message-Id %s: direct alias match: %r', message_id, routes) _logger.info('Routing mail from %s to %s with Message-Id %s: direct alias match: %r',
email_from, email_to, message_id, routes)
return routes return routes
# 3. Fallback to the provided parameters, if they work # 3. Fallback to the provided parameters, if they work
@ -539,14 +542,14 @@ class mail_thread(osv.AbstractModel):
except: except:
thread_id = False thread_id = False
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \ assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
"No possible route found for incoming message with Message-Id %s. " \ "No possible route found for incoming message from %s to %s (Message-Id %s:)." \
"Create an appropriate mail.alias or force the destination model." % message_id "Create an appropriate mail.alias or force the destination model." % (email_from, email_to, message_id)
if thread_id and not model_pool.exists(cr, uid, thread_id): if thread_id and not model_pool.exists(cr, uid, thread_id):
_logger.warning('Received mail reply to missing document %s! Ignoring and creating new document instead for Message-Id %s', _logger.warning('Received mail reply to missing document %s! Ignoring and creating new document instead for Message-Id %s',
thread_id, message_id) thread_id, message_id)
thread_id = None thread_id = None
_logger.debug('Routing mail with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s', _logger.info('Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
message_id, model, thread_id, custom_values, uid) email_from, email_to, message_id, model, thread_id, custom_values, uid)
return [(model, thread_id, custom_values, uid)] return [(model, thread_id, custom_values, uid)]
def message_process(self, cr, uid, model, message, custom_values=None, def message_process(self, cr, uid, model, message, custom_values=None,
@ -596,12 +599,24 @@ class mail_thread(osv.AbstractModel):
if isinstance(message, unicode): if isinstance(message, unicode):
message = message.encode('utf-8') message = message.encode('utf-8')
msg_txt = email.message_from_string(message) msg_txt = email.message_from_string(message)
routes = self.message_route(cr, uid, msg_txt, model,
thread_id, custom_values, # parse the message, verify we are not in a loop by checking message_id is not duplicated
context=context)
msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context) msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context)
if strip_attachments: if strip_attachments:
msg.pop('attachments', None) msg.pop('attachments', None)
if msg.get('message_id'): # should always be True as message_parse generate one if missing
existing_msg_ids = self.pool.get('mail.message').search(cr, SUPERUSER_ID, [
('message_id', '=', msg.get('message_id')),
], context=context)
if existing_msg_ids:
_logger.info('Ignored mail from %s to %s with Message-Id %s:: found duplicated Message-Id during processing',
msg.get('from'), msg.get('to'), msg.get('message_id'))
return False
# find possible routes for the message
routes = self.message_route(cr, uid, msg_txt, model,
thread_id, custom_values,
context=context)
# postpone setting msg.partner_ids after message_post, to avoid double notifications # postpone setting msg.partner_ids after message_post, to avoid double notifications
partner_ids = msg.pop('partner_ids', []) partner_ids = msg.pop('partner_ids', [])
@ -798,7 +813,7 @@ class mail_thread(osv.AbstractModel):
# as RFC2822 requires timezone offset in Date headers. # as RFC2822 requires timezone offset in Date headers.
stored_date = parsed_date.replace(tzinfo=pytz.utc) stored_date = parsed_date.replace(tzinfo=pytz.utc)
else: else:
stored_date = parsed_date.astimezone(pytz.utc) stored_date = parsed_date.astimezone(tz=pytz.utc)
except Exception: except Exception:
_logger.warning('Failed to parse Date header %r in incoming mail ' _logger.warning('Failed to parse Date header %r in incoming mail '
'with message-id %r, assuming current date/time.', 'with message-id %r, assuming current date/time.',
@ -817,7 +832,7 @@ class mail_thread(osv.AbstractModel):
if parent_ids: if parent_ids:
msg_dict['parent_id'] = parent_ids[0] msg_dict['parent_id'] = parent_ids[0]
msg_dict['body'], msg_dict['attachments'] = self._message_extract_payload(message) msg_dict['body'], msg_dict['attachments'] = self._message_extract_payload(message, save_original=save_original)
return msg_dict return msg_dict
#------------------------------------------------------ #------------------------------------------------------
@ -956,12 +971,15 @@ class mail_thread(osv.AbstractModel):
], limit=1, context=context) ], limit=1, context=context)
if author_ids: if author_ids:
kwargs['author_id'] = author_ids[0] kwargs['author_id'] = author_ids[0]
author_id = kwargs.get('author_id')
if author_id is None: # keep False values
author_id = self.pool.get('mail.message')._get_default_author(cr, uid, context=context)
# 1: Handle content subtype: if plaintext, converto into HTML # 1: Handle content subtype: if plaintext, converto into HTML
if content_subtype == 'plaintext': if content_subtype == 'plaintext':
body = tools.plaintext2html(body) body = tools.plaintext2html(body)
# 2: Private message: add recipients (recipients and author of parent message) # 2: Private message: add recipients (recipients and author of parent message) - current author
# + legacy-code management (! we manage only 4 and 6 commands) # + legacy-code management (! we manage only 4 and 6 commands)
partner_ids = set() partner_ids = set()
kwargs_partner_ids = kwargs.pop('partner_ids', []) kwargs_partner_ids = kwargs.pop('partner_ids', [])
@ -974,11 +992,13 @@ class mail_thread(osv.AbstractModel):
partner_ids.add(partner_id) partner_ids.add(partner_id)
else: else:
pass # we do not manage anything else pass # we do not manage anything else
if parent_id and model == 'mail.thread': if parent_id and not model:
parent_message = mail_message.browse(cr, uid, parent_id, context=context) parent_message = mail_message.browse(cr, uid, parent_id, context=context)
partner_ids |= set([partner.id for partner in parent_message.partner_ids]) private_followers = set([partner.id for partner in parent_message.partner_ids])
if parent_message.author_id: if parent_message.author_id:
partner_ids.add(parent_message.author_id.id) private_followers.add(parent_message.author_id.id)
private_followers -= set([author_id])
partner_ids |= private_followers
# 3. Attachments # 3. Attachments
# - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message # - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
@ -1039,6 +1059,7 @@ class mail_thread(osv.AbstractModel):
values = kwargs values = kwargs
values.update({ values.update({
'author_id': author_id,
'model': model, 'model': model,
'res_id': thread_id or False, 'res_id': thread_id or False,
'body': body, 'body': body,

View File

@ -626,6 +626,8 @@
height: 32px; height: 32px;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis;
margin-right: 10px;
} }
.openerp .oe_followers .oe_partner img{ .openerp .oe_followers .oe_partner img{
width: 32px; width: 32px;

View File

@ -1208,7 +1208,7 @@ openerp.mail = function (session) {
this.author_id = datasets.author_id || false; this.author_id = datasets.author_id || false;
this.thread_level = (datasets.thread_level+1) || 0; this.thread_level = (datasets.thread_level+1) || 0;
datasets.partner_ids = datasets.partner_ids || []; datasets.partner_ids = datasets.partner_ids || [];
if (datasets.author_id && ! _.contains(datasets.partner_ids, datasets.author_id) && datasets.author_id[0]) { if (datasets.author_id && !_.contains(_.flatten(datasets.partner_ids),datasets.author_id[0]) && datasets.author_id[0]) {
datasets.partner_ids.push(datasets.author_id); datasets.partner_ids.push(datasets.author_id);
} }
this.partner_ids = datasets.partner_ids; this.partner_ids = datasets.partner_ids;
@ -1389,7 +1389,10 @@ openerp.mail = function (session) {
if (this.options.help) { if (this.options.help) {
no_message.html(this.options.help); no_message.html(this.options.help);
} }
no_message.appendTo(this.$el); if (!this.$el.find(".oe_view_nocontent").length)
{
no_message.appendTo(this.$el);
}
}, },
/** /**

View File

@ -246,7 +246,11 @@
<!-- message itself --> <!-- message itself -->
<div class="oe_msg_content"> <div class="oe_msg_content">
<h1 t-if="(widget.show_record_name or widget.subject) and !widget.thread_level" class="oe_msg_title"> <h1 t-if="(widget.show_record_name or widget.subject) and !widget.thread_level" class="oe_msg_title">
<a t-if="widget.options.show_link and widget.show_record_name" class="oe_mail_action_model" t-attf-href="#model=#{widget.model}&amp;id=#{widget.res_id}"><t t-raw="widget.record_name"/></a><span t-if="!widget.options.show_link and widget.show_record_name"><t t-raw="widget.record_name"/></span><t t-if="widget.show_record_name">: </t> <a t-if="widget.options.show_link and widget.show_record_name" class="oe_mail_action_model" t-attf-href="#model=#{widget.model}&amp;id=#{widget.res_id}">
<t t-raw="widget.record_name"/>
</a>
<span t-if="!widget.options.show_link and widget.show_record_name"><t t-raw="widget.record_name"/></span>
<t t-if="widget.show_record_name and widget.subject">: </t>
<t t-if="widget.subject" t-raw="widget.subject"/> <t t-if="widget.subject" t-raw="widget.subject"/>
</h1> </h1>
<div class="oe_msg_body"> <div class="oe_msg_body">

View File

@ -30,7 +30,7 @@
--> -->
<div t-name="mail.followers.partner" class='oe_partner'> <div t-name="mail.followers.partner" class='oe_partner'>
<img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/> <img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
<a t-attf-href="#model=res.partner&amp;id=#{record.id}"><t t-raw="record.name"/></a> <a t-attf-href="#model=res.partner&amp;id=#{record.id}" t-att-title="record.name"><t t-esc="record.name"/></a>
<span t-if="widget.view_is_editable" class="oe_remove_follower oe_e" title="Remove this follower" t-att-data-id="record.id">X</span> <span t-if="widget.view_is_editable" class="oe_remove_follower oe_e" title="Remove this follower" t-att-data-id="record.id">X</span>
</div> </div>

View File

@ -280,7 +280,8 @@ class test_mail(TestMailBase):
'message_post: mail.mail notifications should have been auto-deleted!') 'message_post: mail.mail notifications should have been auto-deleted!')
# Test: notifications emails: to a and b, c is email only, r is author # Test: notifications emails: to a and b, c is email only, r is author
test_emailto = ['Administrator <a@a>', 'Bert Tartopoils <b@b>'] # test_emailto = ['Administrator <a@a>', 'Bert Tartopoils <b@b>']
test_emailto = ['"Followers of -Pigs-" <a@a>', '"Followers of -Pigs-" <b@b>']
self.assertEqual(len(sent_emails), 2, self.assertEqual(len(sent_emails), 2,
'message_post: notification emails wrong number of send emails') 'message_post: notification emails wrong number of send emails')
self.assertEqual(set([m['email_to'][0] for m in sent_emails]), set(test_emailto), self.assertEqual(set([m['email_to'][0] for m in sent_emails]), set(test_emailto),
@ -353,7 +354,8 @@ class test_mail(TestMailBase):
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg2_id)]), 'mail.mail notifications should have been auto-deleted!') self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg2_id)]), 'mail.mail notifications should have been auto-deleted!')
# Test: emails send by server (to a, b, c, d) # Test: emails send by server (to a, b, c, d)
test_emailto = [u'Administrator <a@a>', u'Bert Tartopoils <b@b>', u'Carine Poilvache <c@c>', u'D\xe9d\xe9 Grosbedon <d@d>'] # test_emailto = [u'Administrator <a@a>', u'Bert Tartopoils <b@b>', u'Carine Poilvache <c@c>', u'D\xe9d\xe9 Grosbedon <d@d>']
test_emailto = [u'"Followers of Pigs" <a@a>', u'"Followers of Pigs" <b@b>', u'"Followers of Pigs" <c@c>', u'"Followers of Pigs" <d@d>']
# self.assertEqual(len(sent_emails), 3, 'sent_email number of sent emails incorrect') # self.assertEqual(len(sent_emails), 3, 'sent_email number of sent emails incorrect')
for sent_email in sent_emails: for sent_email in sent_emails:
self.assertEqual(sent_email['email_from'], 'Raoul Grosbedon <r@r>', self.assertEqual(sent_email['email_from'], 'Raoul Grosbedon <r@r>',

View File

@ -116,8 +116,11 @@ class TestMailgateway(TestMailBase):
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com') frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
sent_emails = self._build_email_kwargs_list sent_emails = self._build_email_kwargs_list
# Test: one group created by mailgateway administrator # Test: one group created by mailgateway administrator
self.assertTrue(len(frog_groups) == 1) self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
res = self.mail_group.perm_read(cr, uid, [frog_group.id], details=False)
self.assertEqual(res[0].get('create_uid'), uid,
'message_process: group should have been created by uid as alias_user__id is False on the alias')
# Test: one message that is the incoming email # Test: one message that is the incoming email
self.assertEqual(len(frog_group.message_ids), 1, self.assertEqual(len(frog_group.message_ids), 1,
'message_process: newly created group should have the incoming email in message_ids') 'message_process: newly created group should have the incoming email in message_ids')
@ -150,9 +153,12 @@ class TestMailgateway(TestMailBase):
self._init_mock_build_email() self._init_mock_build_email()
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com') frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
sent_emails = self._build_email_kwargs_list sent_emails = self._build_email_kwargs_list
# Test: one group created by raoul # Test: one group created by Raoul
self.assertTrue(len(frog_groups) == 1) self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
res = self.mail_group.perm_read(cr, uid, [frog_group.id], details=False)
self.assertEqual(res[0].get('create_uid'), self.user_raoul_id,
'message_process: group should have been created by alias_user_id')
# Test: one message that is the incoming email # Test: one message that is the incoming email
self.assertEqual(len(frog_group.message_ids), 1, self.assertEqual(len(frog_group.message_ids), 1,
'message_process: newly created group should have the incoming email in message_ids') 'message_process: newly created group should have the incoming email in message_ids')
@ -175,8 +181,8 @@ class TestMailgateway(TestMailBase):
# Do: incoming email from a known partner that is also an user that can create a mail.group # Do: incoming email from a known partner that is also an user that can create a mail.group
self.res_users.create(cr, uid, {'partner_id': p1id, 'login': 'sylvie', 'groups_id': [(6, 0, [self.group_employee_id])]}) self.res_users.create(cr, uid, {'partner_id': p1id, 'login': 'sylvie', 'groups_id': [(6, 0, [self.group_employee_id])]})
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com') frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
# Test: one group created by Sylvie # Test: one group created by Raoul (or Sylvie maybe, if we implement it)
self.assertTrue(len(frog_groups) == 1) self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: one message that is the incoming email # Test: one message that is the incoming email
self.assertEqual(len(frog_group.message_ids), 1, self.assertEqual(len(frog_group.message_ids), 1,
@ -195,6 +201,7 @@ class TestMailgateway(TestMailBase):
# Do: even with a wrong destination, a reply should end up in the correct thread # Do: even with a wrong destination, a reply should end up in the correct thread
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other@gmail.com', frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other@gmail.com',
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
to='erroneous@example.com>', subject='Re: news', 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: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
# Test: no group 'Re: news' created, still only 1 Frogs group # Test: no group 'Re: news' created, still only 1 Frogs group
@ -205,12 +212,30 @@ class TestMailgateway(TestMailBase):
'message_process: reply on Frogs should not have created a duplicate group with old subject') '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]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: one new message # Test: one new message
self.assertTrue(len(frog_group.message_ids) == 2, 'message_process: group should contain 2 messages after reply') self.assertEqual(len(frog_group.message_ids), 2, 'message_process: group should contain 2 messages after reply')
# Test: author (and not recipient) added as follower # Test: author (and not recipient) added as follower
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids]) frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
self.assertEqual(frog_follower_ids, set([p1id, p2id]), self.assertEqual(frog_follower_ids, set([p1id, p2id]),
'message_process: after reply, group should have 2 followers') 'message_process: after reply, group should have 2 followers')
# Do: due to some issue, same email goes back into the mailgateway
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other@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)
# 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: no new message
self.assertEqual(len(frog_group.message_ids), 2, '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,
'message_process: message with already existing message_id should not have been duplicated')
# -------------------------------------------------- # --------------------------------------------------
# Test3: email_from and partner finding # Test3: email_from and partner finding
# -------------------------------------------------- # --------------------------------------------------
@ -223,6 +248,7 @@ class TestMailgateway(TestMailBase):
# Do: post a new message, with a known partner -> duplicate emails -> partner # Do: post a new message, with a known partner -> duplicate emails -> partner
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>', format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
to='erroneous@example.com>', subject='Re: news (2)', 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: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
@ -236,6 +262,7 @@ class TestMailgateway(TestMailBase):
self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'}) self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>', format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
to='erroneous@example.com>', subject='Re: news (3)', 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: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
@ -250,6 +277,7 @@ class TestMailgateway(TestMailBase):
self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'}) self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>', format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
to='erroneous@example.com>', subject='Re: news (3)', 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: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')]) frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
@ -265,23 +293,32 @@ class TestMailgateway(TestMailBase):
# Do: incoming email with model that does not accepts incoming emails must raise # Do: incoming email with model that does not accepts incoming emails must raise
self.assertRaises(AssertionError, self.assertRaises(AssertionError,
format_and_process, format_and_process,
MAIL_TEMPLATE, to='noone@example.com', subject='spam', extra='', model='res.country') MAIL_TEMPLATE,
to='noone@example.com', subject='spam', extra='', model='res.country',
msg_id='<1198923581.41972151344608186760.JavaMail.new4@agrolait.com>')
# Do: incoming email without model and without alias must raise # Do: incoming email without model and without alias must raise
self.assertRaises(AssertionError, self.assertRaises(AssertionError,
format_and_process, format_and_process,
MAIL_TEMPLATE, to='noone@example.com', subject='spam', extra='') MAIL_TEMPLATE,
to='noone@example.com', subject='spam', extra='',
msg_id='<1198923581.41972151344608186760.JavaMail.new5@agrolait.com>')
# Do: incoming email with model that accepting incoming emails as fallback # Do: incoming email with model that accepting incoming emails as fallback
frog_groups = format_and_process(MAIL_TEMPLATE, to='noone@example.com', subject='Spammy', extra='', model='mail.group') frog_groups = format_and_process(MAIL_TEMPLATE,
to='noone@example.com',
subject='Spammy', extra='', model='mail.group',
msg_id='<1198923581.41972151344608186760.JavaMail.new6@agrolait.com>')
self.assertEqual(len(frog_groups), 1, self.assertEqual(len(frog_groups), 1,
'message_process: erroneous email but with a fallback model should have created a new mail.group') 'message_process: erroneous email but with a fallback model should have created a new mail.group')
# Do: incoming email in plaintext should be stored as html # Do: incoming email in plaintext should be stored as html
frog_groups = format_and_process(MAIL_TEMPLATE_PLAINTEXT, to='groups@example.com', subject='Frogs Return', extra='', msg_id='<deadcafe.1337@smtp.agrolait.com>') frog_groups = format_and_process(MAIL_TEMPLATE_PLAINTEXT,
to='groups@example.com', subject='Frogs Return', extra='',
msg_id='<deadcafe.1337@smtp.agrolait.com>')
# Test: one group created with one message # Test: one group created with one message
self.assertTrue(len(frog_groups) == 1) self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0]) frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
msg = frog_group.message_ids[0] msg = frog_group.message_ids[0]
# Test: plain text content should be wrapped and stored as html # Test: plain text content should be wrapped and stored as html
@ -305,19 +342,27 @@ class TestMailgateway(TestMailBase):
# Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms # Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
# 0. Direct alias match # 0. Direct alias match
reply_msg1 = format(MAIL_TEMPLATE, to='Pretty Pigs <group+pigs@example.com>', extra='In-Reply-To: %s' % msg1.message_id) reply_msg1 = format(MAIL_TEMPLATE, to='Pretty Pigs <group+pigs@example.com>',
extra='In-Reply-To: %s' % msg1.message_id,
msg_id='<1198923581.41972151344608186760.JavaMail.2@agrolait.com>')
self.mail_group.message_process(cr, uid, None, reply_msg1) self.mail_group.message_process(cr, uid, None, reply_msg1)
# 1. In-Reply-To header # 1. In-Reply-To header
reply_msg2 = format(MAIL_TEMPLATE, to='erroneous@example.com', extra='In-Reply-To: %s' % msg1.message_id) reply_msg2 = format(MAIL_TEMPLATE, to='erroneous@example.com',
extra='In-Reply-To: %s' % msg1.message_id,
msg_id='<1198923581.41972151344608186760.JavaMail.3@agrolait.com>')
self.mail_group.message_process(cr, uid, None, reply_msg2) self.mail_group.message_process(cr, uid, None, reply_msg2)
# 2. References header # 2. References header
reply_msg3 = format(MAIL_TEMPLATE, to='erroneous@example.com', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id) reply_msg3 = format(MAIL_TEMPLATE, to='erroneous@example.com',
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id,
msg_id='<1198923581.41972151344608186760.JavaMail.4@agrolait.com>')
self.mail_group.message_process(cr, uid, None, reply_msg3) self.mail_group.message_process(cr, uid, None, reply_msg3)
# 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, but not to mail (not in msg1.child_ids) # 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, but not to mail (not in msg1.child_ids)
reply_msg4 = format(MAIL_TEMPLATE, to='erroneous@example.com', extra='', subject='Re: [%s] 1' % self.group_pigs_id) reply_msg4 = format(MAIL_TEMPLATE, to='erroneous@example.com',
extra='', subject='Re: [%s] 1' % self.group_pigs_id,
msg_id='<1198923581.41972151344608186760.JavaMail.5@agrolait.com>')
self.mail_group.message_process(cr, uid, 'mail.group', reply_msg4) self.mail_group.message_process(cr, uid, 'mail.group', reply_msg4)
group_pigs.refresh() group_pigs.refresh()
@ -327,4 +372,55 @@ class TestMailgateway(TestMailBase):
def test_20_private_discussion(self): def test_20_private_discussion(self):
""" Testing private discussion between partners. """ """ Testing private discussion between partners. """
pass cr, uid = self.cr, self.uid
# Do: Raoul writes to Bert and Administrator, with a thread_model in context that should not be taken into account
msg1_pids = [self.partner_admin_id, self.partner_bert_id]
msg1_id = self.mail_thread.message_post(cr, self.user_raoul_id, False,
partner_ids=msg1_pids,
subtype='mail.mt_comment',
context={'thread_model': 'mail.group'})
# Test: message recipients
msg = self.mail_message.browse(cr, uid, msg1_id)
msg_pids = [p.id for p in msg.partner_ids]
msg_nids = [p.id for p in msg.notified_partner_ids]
test_pids = msg1_pids
test_nids = msg1_pids
self.assertEqual(set(msg_pids), set(test_pids),
'message_post: private discussion: incorrect recipients')
self.assertEqual(set(msg_nids), set(test_nids),
'message_post: private discussion: incorrect notified recipients')
self.assertEqual(msg.model, False,
'message_post: private discussion: context key "thread_model" not correctly ignored when having no res_id')
# Do: Bert replies through mailgateway (is a customer)
msg2_id = self.mail_thread.message_post(cr, uid, False,
author_id=self.partner_bert_id,
parent_id=msg1_id, subtype='mail.mt_comment')
# Test: message recipients
msg = self.mail_message.browse(cr, uid, msg2_id)
msg_pids = [p.id for p in msg.partner_ids]
msg_nids = [p.id for p in msg.notified_partner_ids]
test_pids = [self.partner_admin_id, self.partner_raoul_id]
test_nids = test_pids
self.assertEqual(set(msg_pids), set(test_pids),
'message_post: private discussion: incorrect recipients when replying')
self.assertEqual(set(msg_nids), set(test_nids),
'message_post: private discussion: incorrect notified recipients when replying')
# Do: Administrator replies
msg3_id = self.mail_thread.message_post(cr, uid, False,
parent_id=msg2_id, subtype='mail.mt_comment')
# Test: message recipients
msg = self.mail_message.browse(cr, uid, msg3_id)
msg_pids = [p.id for p in msg.partner_ids]
msg_nids = [p.id for p in msg.notified_partner_ids]
test_pids = [self.partner_bert_id, self.partner_raoul_id]
test_nids = test_pids
self.assertEqual(set(msg_pids), set(test_pids),
'message_post: private discussion: incorrect recipients when replying')
self.assertEqual(set(msg_nids), set(test_nids),
'message_post: private discussion: incorrect notified recipients when replying')

View File

@ -175,11 +175,17 @@ class mail_compose_message(osv.TransientModel):
:param int res_id: id of the document record this mail is related to :param int res_id: id of the document record this mail is related to
""" """
doc_name_get = self.pool.get(model).name_get(cr, uid, [res_id], context=context) doc_name_get = self.pool.get(model).name_get(cr, uid, [res_id], context=context)
record_name = False
if doc_name_get: if doc_name_get:
record_name = doc_name_get[0][1] record_name = doc_name_get[0][1]
else: values = {
record_name = False 'model': model,
return {'model': model, 'res_id': res_id, 'record_name': record_name} 'res_id': res_id,
'record_name': record_name,
}
if record_name:
values['subject'] = 'Re: %s' % record_name
return values
def get_message_data(self, cr, uid, message_id, context=None): def get_message_data(self, cr, uid, message_id, context=None):
""" Returns a defaults-like dict with initial values for the composition """ Returns a defaults-like dict with initial values for the composition
@ -197,7 +203,7 @@ class mail_compose_message(osv.TransientModel):
# create subject # create subject
re_prefix = _('Re:') re_prefix = _('Re:')
reply_subject = tools.ustr(message_data.subject or '') reply_subject = tools.ustr(message_data.subject or message_data.record_name or '')
if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)) and message_data.subject: if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)) and message_data.subject:
reply_subject = "%s %s" % (re_prefix, reply_subject) reply_subject = "%s %s" % (re_prefix, reply_subject)
# get partner_ids from original message # get partner_ids from original message

View File

@ -45,7 +45,7 @@ class StockMove(osv.osv):
procurement_obj = self.pool.get('procurement.order') procurement_obj = self.pool.get('procurement.order')
product_obj = self.pool.get('product.product') product_obj = self.pool.get('product.product')
processed_ids = [move.id] processed_ids = [move.id]
if move.product_id.supply_method == 'produce' and move.product_id.procure_method == 'make_to_order': if move.product_id.supply_method == 'produce':
bis = bom_obj.search(cr, uid, [ bis = bom_obj.search(cr, uid, [
('product_id','=',move.product_id.id), ('product_id','=',move.product_id.id),
('bom_id','=',False), ('bom_id','=',False),

View File

@ -4,7 +4,8 @@ Created on 18 oct. 2011
@author: openerp @author: openerp
''' '''
from openerp.osv import fields, osv from openerp.osv import osv
from openerp.tools.translate import _
class plugin_handler(osv.osv_memory): class plugin_handler(osv.osv_memory):
_name = 'plugin.handler' _name = 'plugin.handler'
@ -29,7 +30,7 @@ class plugin_handler(osv.osv_memory):
partner_ids = partner_obj.search(cr, uid, [('email', 'like', address_email)]) partner_ids = partner_obj.search(cr, uid, [('email', 'like', address_email)])
res_id = partner_ids and partner_ids[0] or 0 res_id = partner_ids and partner_ids[0] or 0
url = self._make_url(cr, uid, res_id, 'res.partner') url = self._make_url(cr, uid, res_id, 'res.partner')
return ('res.partner', res_id , url) return ('res.partner', res_id, url)
def document_get(self, cr, uid, email): def document_get(self, cr, uid, email):
""" """
@ -48,7 +49,7 @@ class plugin_handler(osv.osv_memory):
message_id = msg.get('message_id') message_id = msg.get('message_id')
msg_id = False msg_id = False
if message_id: if message_id:
msg_ids = mail_message_obj.search(cr, uid, [('message_id','=', message_id)]) msg_ids = mail_message_obj.search(cr, uid, [('message_id', '=', message_id)])
msg_id = len(msg_ids) and msg_ids[0] or False msg_id = len(msg_ids) and msg_ids[0] or False
if not msg_id and parent_id: if not msg_id and parent_id:
msg_id = parent_id msg_id = parent_id
@ -57,8 +58,8 @@ class plugin_handler(osv.osv_memory):
res_id = msg.res_id res_id = msg.res_id
model = msg.model model = msg.model
url = self._make_url(cr, uid, res_id, model) url = self._make_url(cr, uid, res_id, model)
name = self.pool.get(model).name_get(cr, uid, [res_id])[0][1] name = self.pool.get(model).name_get(cr, uid, [res_id])[0][1]
return (model,res_id, url,name) return (model, res_id, url, name)
def document_type(self, cr, uid, context=None): def document_type(self, cr, uid, context=None):
""" """
@ -94,26 +95,25 @@ class plugin_handler(osv.osv_memory):
model_obj = self.pool.get(model) model_obj = self.pool.get(model)
msg = self.pool.get('mail.thread').message_parse(cr, uid, email) msg = self.pool.get('mail.thread').message_parse(cr, uid, email)
message_id = msg.get('message-id') message_id = msg.get('message-id')
mail_ids = mail_message.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)]) mail_ids = mail_message.search(cr, uid, [('message_id', '=', message_id), ('res_id', '=', res_id), ('model', '=', model)])
if message_id and mail_ids:
if message_id and mail_ids :
mail_record = mail_message.browse(cr, uid, mail_ids)[0] mail_record = mail_message.browse(cr, uid, mail_ids)[0]
res_id = mail_record.res_id res_id = mail_record.res_id
notify = "Email already pushed" notify = _("Email already pushed")
elif res_id == 0: elif res_id == 0:
if model == 'res.partner': if model == 'res.partner':
notify = 'User the Partner button to create a new partner' notify = _('Use the Partner button to create a new partner')
else: else:
res_id = model_obj.message_process(cr, uid, model, email) res_id = model_obj.message_process(cr, uid, model, email)
notify = "Mail successfully pushed, a new %s has been created " % model notify = _("Mail successfully pushed, a new %s has been created.") % model
else: else:
model_obj.message_post(cr, uid, [res_id], model_obj.message_post(cr, uid, [res_id],
body= msg.get('body'), body=msg.get('body'),
subject= msg.get('subject'), subject=msg.get('subject'),
type= 'email', type='email',
parent_id= msg.get('parent_id'), parent_id=msg.get('parent_id'),
attachments= msg.get('attachments')) attachments=msg.get('attachments'))
notify = "Mail successfully pushed" notify = _("Mail successfully pushed")
url = self._make_url(cr, uid, res_id, model) url = self._make_url(cr, uid, res_id, model)
return (model, res_id, url, notify) return (model, res_id, url, notify)
@ -154,16 +154,17 @@ class plugin_handler(osv.osv_memory):
message_id = msg.get('message-id') message_id = msg.get('message-id')
push_mail = self.push_message(cr, uid, model, headers, res_id) push_mail = self.push_message(cr, uid, model, headers, res_id)
res_id = push_mail[1] res_id = push_mail[1]
model = push_mail[0] model = push_mail[0]
notify = push_mail[3]
for name in attachments.keys(): for name in attachments.keys():
attachment_ids = ir_attachment_obj.search(cr, uid, [('res_model', '=', model), ('res_id', '=', res_id), ('datas_fname', '=', name)]) attachment_ids = ir_attachment_obj.search(cr, uid, [('res_model', '=', model), ('res_id', '=', res_id), ('datas_fname', '=', name)])
if attachment_ids: if attachment_ids:
attach_ids.append( attachment_ids[0]) attach_ids.append(attachment_ids[0])
else: else:
vals = {"res_model": model, "res_id": res_id, "name": name, "datas" :attachments[name], "datas_fname" : name} vals = {"res_model": model, "res_id": res_id, "name": name, "datas": attachments[name], "datas_fname": name}
attach_ids.append(ir_attachment_obj.create(cr, uid, vals)) attach_ids.append(ir_attachment_obj.create(cr, uid, vals))
mail_ids = mail_message.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)]) mail_ids = mail_message.search(cr, uid, [('message_id', '=', message_id), ('res_id', '=', res_id), ('model', '=', model)])
if mail_ids: if mail_ids:
ids = mail_message.write(cr, uid, mail_ids[0], { 'attachment_ids': [(6, 0, attach_ids)],'body':body,'body_html':body_html}) mail_message.write(cr, uid, mail_ids[0], {'attachment_ids': [(6, 0, attach_ids)], 'body': body, 'body_html': body_html})
url = self._make_url(cr, uid, res_id, model) url = self._make_url(cr, uid, res_id, model)
return (model, res_id, url) return (model, res_id, url, notify)

View File

@ -17,6 +17,7 @@
<UpgradeBackupLocation> <UpgradeBackupLocation>
</UpgradeBackupLocation> </UpgradeBackupLocation>
<OldToolsVersion>3.5</OldToolsVersion> <OldToolsVersion>3.5</OldToolsVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl> <PublishUrl>publish\</PublishUrl>
<Install>true</Install> <Install>true</Install>
<InstallFrom>Disk</InstallFrom> <InstallFrom>Disk</InstallFrom>
@ -29,7 +30,6 @@
<MapFileExtensions>true</MapFileExtensions> <MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision> <ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion> <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup> </PropertyGroup>
@ -53,9 +53,8 @@
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="CookComputing.XmlRpcV2, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="CookComputing.XmlRpcV2">
<SpecificVersion>False</SpecificVersion> <HintPath>..\CookComputing.XmlRpcV2.dll</HintPath>
<HintPath>..\OpenERPOutlookPlugin\bin\Release\CookComputing.XmlRpcV2.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core"> <Reference Include="System.Core">
@ -81,7 +80,7 @@
<ItemGroup> <ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5"> <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible> <Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName> <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install> <Install>false</Install>
</BootstrapperPackage> </BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1"> <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
@ -91,7 +90,7 @@
</BootstrapperPackage> </BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1"> <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
<Visible>False</Visible> <Visible>False</Visible>
<ProductName>Windows Installer 3.1</ProductName> <ProductName>Windows Installer 3.1</ProductName>
<Install>true</Install> <Install>true</Install>
</BootstrapperPackage> </BootstrapperPackage>
</ItemGroup> </ItemGroup>

View File

@ -1,6 +1,6 @@
 
Microsoft Visual Studio Solution File, Format Version 10.00 Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2008 # Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenERPOutlookPlugin", "OpenERPOutlookPlugin\OpenERPOutlookPlugin.csproj", "{F4B2219B-F235-400F-81B4-92F15250BBA4}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenERPOutlookPlugin", "OpenERPOutlookPlugin\OpenERPOutlookPlugin.csproj", "{F4B2219B-F235-400F-81B4-92F15250BBA4}"
EndProject EndProject
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "OpenERPOutlookPluginSetup", "OpenERPOutlookPluginSetup\OpenERPOutlookPluginSetup.vdproj", "{96333293-0156-4998-9065-42721CEB0368}" Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "OpenERPOutlookPluginSetup", "OpenERPOutlookPluginSetup\OpenERPOutlookPluginSetup.vdproj", "{96333293-0156-4998-9065-42721CEB0368}"

View File

@ -303,6 +303,7 @@ namespace OpenERPOutlookPlugin
foreach (outlook.MailItem mailitem in Tools.MailItems()) foreach (outlook.MailItem mailitem in Tools.MailItems())
{ {
Object[] contact = Cache.OpenERPOutlookPlugin.RedirectPartnerPage(mailitem); Object[] contact = Cache.OpenERPOutlookPlugin.RedirectPartnerPage(mailitem);
if ((int)contact[1] > 0) if ((int)contact[1] > 0)

View File

@ -95,7 +95,6 @@ namespace OpenERPOutlookPlugin
/* /*
* Will open the url into the web browser. * Will open the url into the web browser.
*/ */
System.Diagnostics.Process.Start(web_url.ToString()); System.Diagnostics.Process.Start(web_url.ToString());
} }
@ -163,9 +162,16 @@ namespace OpenERPOutlookPlugin
args.Add(attachments); args.Add(attachments);
object push_mail = this.Connection.Execute("plugin.handler", "push_message_outlook", args.ToArray()); object push_mail = this.Connection.Execute("plugin.handler", "push_message_outlook", args.ToArray());
object[] push = (object[])push_mail; object[] push = (object[])push_mail;
this.RedirectWeb(push[2].ToString()); if (Convert.ToInt32(push[1]) == 0)
return true; {
MessageBox.Show(push[3].ToString());
}
else
{
this.RedirectWeb(push[2].ToString());
}
return true;
} }
public long CreatePartnerRecord(string name) public long CreatePartnerRecord(string name)
{ {

View File

@ -295,7 +295,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return; return;
} }
//try to push an order to the server //try to push an order to the server
(new instance.web.Model('pos.order')).get_func('create_from_ui')([order]) // shadow : true is to prevent a spinner to appear in case of timeout
(new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{ shadow:true })
.fail(function(unused, event){ .fail(function(unused, event){
//don't show error popup if it fails //don't show error popup if it fails
event.preventDefault(); event.preventDefault();

View File

@ -794,7 +794,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
renderElement: function() { renderElement: function() {
var self = this; var self = this;
this._super(); this._super();
this.$('.oe_pos_synch-notification-button').click(function(){ this.$el.click(function(){
self.pos.flush(); self.pos.flush();
}); });
}, },
@ -879,6 +879,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
instance.web.unblockUI(); instance.web.unblockUI();
self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();}); self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();});
self.pos.flush();
}).fail(function(){ // error when loading models data from the backend }).fail(function(){ // error when loading models data from the backend
instance.web.unblockUI(); instance.web.unblockUI();
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id']) return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])

View File

@ -21,7 +21,6 @@
<field name="params" eval="&quot;{ <field name="params" eval="&quot;{
'domain': [ 'domain': [
('to_read', '=', True), ('to_read', '=', True),
('starred', '=', False),
], ],
'show_compose_message': False, 'show_compose_message': False,
'show_link': False, 'show_link': False,
@ -40,6 +39,7 @@
</field> </field>
</record> </record>
<!-- TODO: remove me in 8.0 -->
<record id="action_mail_star_feeds_portal" model="ir.actions.client"> <record id="action_mail_star_feeds_portal" model="ir.actions.client">
<field name="name">To-do</field> <field name="name">To-do</field>
<field name="tag">mail.wall</field> <field name="tag">mail.wall</field>
@ -67,6 +67,9 @@
</p> </p>
</field> </field>
</record> </record>
<menuitem name="To-do" id="portal_mail_starfeeds" parent="portal_messages"
action="action_mail_star_feeds_portal" sequence="20" groups="base.group_no_one"/>
<!-- end -->
<record id="action_mail_archives_feeds_portal" model="ir.actions.client"> <record id="action_mail_archives_feeds_portal" model="ir.actions.client">
<field name="name">Archives</field> <field name="name">Archives</field>
@ -84,7 +87,6 @@
'show_compose_message': False, 'show_compose_message': False,
'show_link': False, 'show_link': False,
'view_mailbox': True, 'view_mailbox': True,
'read_action': 'read'
}&quot;"/> }&quot;"/>
<field name="help" type="html"> <field name="help" type="html">
<p> <p>
@ -98,8 +100,6 @@
<menuitem name="Inbox" id="portal_inbox" parent="portal_messages" <menuitem name="Inbox" id="portal_inbox" parent="portal_messages"
action="action_mail_inbox_feeds_portal" sequence="10" groups="portal.group_portal"/> action="action_mail_inbox_feeds_portal" sequence="10" groups="portal.group_portal"/>
<menuitem name="To-do" id="portal_mail_starfeeds" parent="portal_messages"
action="action_mail_star_feeds_portal" sequence="20" groups="portal.group_portal"/>
<menuitem name="Archives" id="portal_mail_archivesfeeds" parent="portal_messages" <menuitem name="Archives" id="portal_mail_archivesfeeds" parent="portal_messages"
action="action_mail_archives_feeds_portal" sequence="30" groups="portal.group_portal"/> action="action_mail_archives_feeds_portal" sequence="30" groups="portal.group_portal"/>
@ -107,7 +107,7 @@
Create menu items that we'll leave empty for now - they'll be Create menu items that we'll leave empty for now - they'll be
filled up by other portal modules. filled up by other portal modules.
--> -->
<menuitem name="Quotations and Sales Orders" id="portal_orders" parent="portal_menu" sequence="20"/> <menuitem name="Billing" id="portal_orders" parent="portal_menu" sequence="20"/>
<menuitem name="After Sale Services" id="portal_after_sales" parent="portal_menu" sequence="30"/> <menuitem name="After Sale Services" id="portal_after_sales" parent="portal_menu" sequence="30"/>
<menuitem name="Projects" id="portal_projects" parent="portal_menu" sequence="40"/> <menuitem name="Projects" id="portal_projects" parent="portal_menu" sequence="40"/>

View File

@ -59,19 +59,8 @@ class sale_order(osv.Model):
assert len(ids) == 1 assert len(ids) == 1
document = self.browse(cr, uid, ids[0], context=context) document = self.browse(cr, uid, ids[0], context=context)
partner = document.partner_id partner = document.partner_id
# TDE note: this code should be improved: used a real invite wizard instead of an ugly email
if partner.id not in document.message_follower_ids: if partner.id not in document.message_follower_ids:
self.message_subscribe(cr, uid, ids, [partner.id], context=context) self.message_subscribe(cr, uid, ids, [partner.id], context=context)
mail_values = {
'recipient_ids': [(4, partner.id)],
'subject': 'Invitation to follow %s' % document.name_get()[0][1],
'body_html': 'You have been invited to follow %s' % document.name_get()[0][1],
'auto_delete': True,
'type': 'email',
}
mail_obj = self.pool.get('mail.mail')
mail_id = mail_obj.create(cr, uid, mail_values, context=context)
mail_obj.send(cr, uid, [mail_id], context=context)
return super(sale_order, self).action_button_confirm(cr, uid, ids, context=context) return super(sale_order, self).action_button_confirm(cr, uid, ids, context=context)
def get_signup_url(self, cr, uid, ids, context=None): def get_signup_url(self, cr, uid, ids, context=None):

View File

@ -374,7 +374,7 @@ class product_pricelist_item(osv.osv):
result.append((line.id, line.name)) result.append((line.id, line.name))
result.append((-1, _('Other Pricelist'))) result.append((-1, _('Other Pricelist')))
result.append((-2, _('Partner section of the product form'))) result.append((-2, _('Supplier Prices on the product form')))
return result return result
_name = "product.pricelist.item" _name = "product.pricelist.item"

View File

@ -588,10 +588,13 @@ class product_product(osv.osv):
# Check if the product is last product of this template # Check if the product is last product of this template
other_product_ids = self.search(cr, uid, [('product_tmpl_id', '=', tmpl_id), ('id', '!=', product.id)], context=context) other_product_ids = self.search(cr, uid, [('product_tmpl_id', '=', tmpl_id), ('id', '!=', product.id)], context=context)
if not other_product_ids: if not other_product_ids:
unlink_product_tmpl_ids.append(tmpl_id) unlink_product_tmpl_ids.append(tmpl_id)
unlink_ids.append(product.id) unlink_ids.append(product.id)
res = super(product_product, self).unlink(cr, uid, unlink_ids, context=context)
# delete templates after calling super, as deleting template could lead to deleting
# products due to ondelete='cascade'
self.pool.get('product.template').unlink(cr, uid, unlink_product_tmpl_ids, context=context) self.pool.get('product.template').unlink(cr, uid, unlink_product_tmpl_ids, context=context)
return super(product_product, self).unlink(cr, uid, unlink_ids, context=context) return res
def onchange_uom(self, cursor, user, ids, uom_id, uom_po_id): def onchange_uom(self, cursor, user, ids, uom_id, uom_po_id):
if uom_id and uom_po_id: if uom_id and uom_po_id:

View File

@ -581,6 +581,7 @@
<field name="delay"/> <field name="delay"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/> <field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group> </group>
<p groups="product.group_purchase_pricelist" class="oe_grey">The prices below will only be taken into account when your pricelist is set as based on supplier prices.</p>
<field groups="product.group_purchase_pricelist" name="pricelist_ids"> <field groups="product.group_purchase_pricelist" name="pricelist_ids">
<tree editable="bottom" string="Pricelist"> <tree editable="bottom" string="Pricelist">
<field name="min_quantity"/> <field name="min_quantity"/>

View File

@ -21,6 +21,7 @@
<record id="all_projects_account" model="account.analytic.account"> <record id="all_projects_account" model="account.analytic.account">
<field name="name">Projects</field> <field name="name">Projects</field>
<field name="code">3</field> <field name="code">3</field>
<field name="type">view</field>
</record> </record>
<function id="parent_project_default_set" model="ir.values" name="set" eval="('default',False,'parent_id', [('project.project', False)], all_projects_account, True, False, False, False, True)"/> <function id="parent_project_default_set" model="ir.values" name="set" eval="('default',False,'parent_id', [('project.project', False)], all_projects_account, True, False, False, False, True)"/>

View File

@ -137,6 +137,13 @@ class project_issue(base_stage, osv.osv):
res = {} res = {}
for issue in self.browse(cr, uid, ids, context=context): for issue in self.browse(cr, uid, ids, context=context):
# if the working hours on the project are not defined, use default ones (8 -> 12 and 13 -> 17 * 5), represented by None
if not issue.project_id or not issue.project_id.resource_calendar_id:
working_hours = None
else:
working_hours = issue.project_id.resource_calendar_id.id
res[issue.id] = {} res[issue.id] = {}
for field in fields: for field in fields:
duration = 0 duration = 0
@ -150,20 +157,24 @@ class project_issue(base_stage, osv.osv):
ans = date_open - date_create ans = date_open - date_create
date_until = issue.date_open date_until = issue.date_open
#Calculating no. of working hours to open the issue #Calculating no. of working hours to open the issue
if issue.project_id.resource_calendar_id: hours = cal_obj._interval_hours_get(cr, uid, working_hours,
hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
date_create, date_create,
date_open) date_open,
timezone_from_uid=issue.user_id.id or uid,
exclude_leaves=False,
context=context)
elif field in ['working_hours_close','day_close']: elif field in ['working_hours_close','day_close']:
if issue.date_closed: if issue.date_closed:
date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S") date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
date_until = issue.date_closed date_until = issue.date_closed
ans = date_close - date_create ans = date_close - date_create
#Calculating no. of working hours to close the issue #Calculating no. of working hours to close the issue
if issue.project_id.resource_calendar_id: hours = cal_obj._interval_hours_get(cr, uid, working_hours,
hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id, date_create,
date_create, date_close,
date_close) timezone_from_uid=issue.user_id.id or uid,
exclude_leaves=False,
context=context)
elif field in ['days_since_creation']: elif field in ['days_since_creation']:
if issue.create_date: if issue.create_date:
days_since_creation = datetime.today() - datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S") days_since_creation = datetime.today() - datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
@ -182,27 +193,12 @@ class project_issue(base_stage, osv.osv):
resource_ids = res_obj.search(cr, uid, [('user_id','=',issue.user_id.id)]) resource_ids = res_obj.search(cr, uid, [('user_id','=',issue.user_id.id)])
if resource_ids and len(resource_ids): if resource_ids and len(resource_ids):
resource_id = resource_ids[0] resource_id = resource_ids[0]
duration = float(ans.days) duration = float(ans.days) + float(ans.seconds)/(24*3600)
if issue.project_id and issue.project_id.resource_calendar_id:
duration = float(ans.days) * 24
new_dates = cal_obj.interval_min_get(cr, uid,
issue.project_id.resource_calendar_id.id,
date_create,
duration, resource=resource_id)
no_days = []
date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
for in_time, out_time in new_dates:
if in_time.date not in no_days:
no_days.append(in_time.date)
if out_time > date_until:
break
duration = len(no_days)
if field in ['working_hours_open','working_hours_close']: if field in ['working_hours_open','working_hours_close']:
res[issue.id][field] = hours res[issue.id][field] = hours
else: elif field in ['day_open','day_close']:
res[issue.id][field] = abs(float(duration)) res[issue.id][field] = duration
return res return res

View File

@ -63,9 +63,9 @@ class project_issue_report(osv.osv):
'project_id':fields.many2one('project.project', 'Project',readonly=True), 'project_id':fields.many2one('project.project', 'Project',readonly=True),
'version_id': fields.many2one('project.issue.version', 'Version'), 'version_id': fields.many2one('project.issue.version', 'Version'),
'user_id' : fields.many2one('res.users', 'Assigned to',readonly=True), 'user_id' : fields.many2one('res.users', 'Assigned to',readonly=True),
'partner_id': fields.many2one('res.partner','Contact',domain="[('object_id.model', '=', 'project.issue')]"), 'partner_id': fields.many2one('res.partner','Contact'),
'channel_id': fields.many2one('crm.case.channel', 'Channel',readonly=True), 'channel_id': fields.many2one('crm.case.channel', 'Channel',readonly=True),
'task_id': fields.many2one('project.task', 'Task',domain="[('object_id.model', '=', 'project.issue')]" ), 'task_id': fields.many2one('project.task', 'Task'),
'email': fields.integer('# Emails', size=128, readonly=True), 'email': fields.integer('# Emails', size=128, readonly=True),
} }
@ -96,8 +96,8 @@ class project_issue_report(osv.osv):
c.channel_id, c.channel_id,
c.task_id, c.task_id,
date_trunc('day',c.create_date) as create_date, date_trunc('day',c.create_date) as create_date,
extract('epoch' from (c.date_open-c.create_date))/(3600*24) as delay_open, c.day_open as delay_open,
extract('epoch' from (c.date_closed-c.date_open))/(3600*24) as delay_close, c.day_close as delay_close,
(SELECT count(id) FROM mail_message WHERE model='project.issue' AND res_id=c.id) AS email (SELECT count(id) FROM mail_message WHERE model='project.issue' AND res_id=c.id) AS email
FROM FROM

View File

@ -65,6 +65,11 @@ Example: Product: this product is deprecated, do not purchase more than 5.
'default_invoice_method': 'manual', 'default_invoice_method': 'manual',
} }
def onchange_purchase_analytic_plans(self, cr, uid, ids, module_purchase_analytic_plans, context=None):
""" change group_analytic_account_for_purchases following module_purchase_analytic_plans """
if not module_purchase_analytic_plans:
return {}
return {'value': {'group_analytic_account_for_purchases': module_purchase_analytic_plans}}
@ -81,6 +86,8 @@ class account_config_settings(osv.osv_memory):
def onchange_purchase_analytic_plans(self, cr, uid, ids, module_purchase_analytic_plans, context=None): def onchange_purchase_analytic_plans(self, cr, uid, ids, module_purchase_analytic_plans, context=None):
""" change group_analytic_account_for_purchases following module_purchase_analytic_plans """ """ change group_analytic_account_for_purchases following module_purchase_analytic_plans """
if not module_purchase_analytic_plans:
return {}
return {'value': {'group_analytic_account_for_purchases': module_purchase_analytic_plans}} return {'value': {'group_analytic_account_for_purchases': module_purchase_analytic_plans}}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -67,7 +67,7 @@
<label for="module_purchase_requisition"/> <label for="module_purchase_requisition"/>
</div> </div>
<div> <div>
<field name="module_purchase_analytic_plans" class="oe_inline"/> <field name="module_purchase_analytic_plans" on_change="onchange_purchase_analytic_plans(module_purchase_analytic_plans, context)" class="oe_inline"/>
<label for="module_purchase_analytic_plans"/> <label for="module_purchase_analytic_plans"/>
</div> </div>
<div> <div>
@ -109,7 +109,7 @@
<label for="group_analytic_account_for_purchases"/> <label for="group_analytic_account_for_purchases"/>
</div> </div>
<div> <div>
<field name="module_purchase_analytic_plans" on_change="onchange_purchase_analytic_plans(module_purchase_analytic_plans)" class="oe_inline"/> <field name="module_purchase_analytic_plans" on_change="onchange_purchase_analytic_plans(module_purchase_analytic_plans, context)" class="oe_inline"/>
<label for="module_purchase_analytic_plans"/> <label for="module_purchase_analytic_plans"/>
</div> </div>
</xpath> </xpath>

View File

@ -19,7 +19,9 @@
# #
############################################################################## ##############################################################################
import pytz
from datetime import datetime, timedelta from datetime import datetime, timedelta
from dateutil import rrule
import math import math
from faces import * from faces import *
from openerp.osv import fields, osv from openerp.osv import fields, osv
@ -212,45 +214,108 @@ class resource_calendar(osv.osv):
@return : Total number of working hours based dt_from and dt_end and @return : Total number of working hours based dt_from and dt_end and
resource if supplied. resource if supplied.
""" """
if not id: return self._interval_hours_get(cr, uid, id, dt_from, dt_to, resource_id=resource)
return 0.0
dt_leave = self._get_leaves(cr, uid, id, resource)
hours = 0.0
current_hour = dt_from.hour def _interval_hours_get(self, cr, uid, id, dt_from, dt_to, resource_id=False, timezone_from_uid=None, exclude_leaves=True, context=None):
""" Calculates the Total Working hours based on given start_date to
end_date, If resource id is supplied that it will consider the source
leaves also in calculating the hours.
while (dt_from <= dt_to): @param dt_from : date start to calculate hours
cr.execute("select hour_from,hour_to from resource_calendar_attendance where dayofweek='%s' and calendar_id=%s order by hour_from", (dt_from.weekday(),id)) @param dt_end : date end to calculate hours
der = cr.fetchall() @param resource_id: optional resource id, If given resource leave will be
for (hour_from,hour_to) in der: considered.
if hours != 0.0:#For first time of the loop only,hours will be 0 @param timezone_from_uid: optional uid, if given we will considerer
current_hour = hour_from working hours in that user timezone
leave_flag = False @param exclude_leaves: optionnal, if set to True (default) we will exclude
if (hour_to>=current_hour): resource leaves from working hours
dt_check = dt_from.strftime('%Y-%m-%d') @param context: current request context
for leave in dt_leave: @return : Total number of working hours based dt_from and dt_end and
if dt_check == leave: resource if supplied.
dt_check = datetime.strptime(dt_check, "%Y-%m-%d") + timedelta(days=1) """
leave_flag = True utc_tz = pytz.timezone('UTC')
local_tz = utc_tz
if leave_flag: if timezone_from_uid:
break users_obj = self.pool.get('res.users')
else: user_timezone = users_obj.browse(cr, uid, timezone_from_uid, context=context).partner_id.tz
d1 = dt_from if user_timezone:
d2 = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(hour_to)), int((hour_to%1) * 60)) try:
local_tz = pytz.timezone(user_timezone)
except pytz.UnknownTimeZoneError:
pass # fallback to UTC as local timezone
if hours != 0.0:#For first time of the loop only,hours will be 0 def utc_to_local_zone(naive_datetime):
d1 = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(current_hour)), int((current_hour%1) * 60)) utc_dt = utc_tz.localize(naive_datetime, is_dst=False)
return utc_dt.astimezone(local_tz)
if dt_from.day == dt_to.day: def float_time_convert(float_val):
if hour_from <= dt_to.hour <= hour_to: factor = float_val < 0 and -1 or 1
d2 = dt_to val = abs(float_val)
dt_from = d2 return (factor * int(math.floor(val)), int(round((val % 1) * 60)))
hours += (d2-d1).seconds
dt_from = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(current_hour)), int((current_hour%1) * 60)) + timedelta(days=1)
current_hour = 0.0
return (hours/3600) # Get slots hours per day
# {day_of_week: [(8, 12), (13, 17), ...], ...}
hours_range_per_weekday = {}
if id:
cr.execute("select dayofweek, hour_from,hour_to from resource_calendar_attendance where calendar_id=%s order by hour_from", (id,))
for weekday, hour_from, hour_to in cr.fetchall():
weekday = int(weekday)
hours_range_per_weekday.setdefault(weekday, [])
hours_range_per_weekday[weekday].append((hour_from, hour_to))
else:
# considering default working hours (Monday -> Friday, 8 -> 12, 13 -> 17)
for weekday in range(5):
hours_range_per_weekday[weekday] = [(8, 12), (13, 17)]
## Interval between dt_from - dt_to
##
## dt_from dt_to
## =============|==================|============
## [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]
##
## [ : start of range
## ] : end of range
##
## case 1: range end before interval start (skip)
## case 2: range overlap interval start (fit start to internal)
## case 3: range within interval
## case 4: range overlap interval end (fit end to interval)
## case 5: range start after interval end (skip)
interval_start = utc_to_local_zone(dt_from)
interval_end = utc_to_local_zone(dt_to)
hours_timedelta = timedelta()
# Get leaves for requested resource
dt_leaves = set([])
if exclude_leaves and id:
dt_leaves = set(self._get_leaves(cr, uid, id, resource=resource_id))
for day in rrule.rrule(rrule.DAILY, dtstart=interval_start,
until=interval_end+timedelta(days=1),
byweekday=hours_range_per_weekday.keys()):
if exclude_leaves and day.strftime('%Y-%m-%d') in dt_leaves:
# XXX: futher improve leave management to allow for partial day leave
continue
for (range_from, range_to) in hours_range_per_weekday.get(day.weekday(), []):
range_from_hour, range_from_min = float_time_convert(range_from)
range_to_hour, range_to_min = float_time_convert(range_to)
daytime_start = local_tz.localize(day.replace(hour=range_from_hour, minute=range_from_min, second=0, tzinfo=None))
daytime_end = local_tz.localize(day.replace(hour=range_to_hour, minute=range_to_min, second=0, tzinfo=None))
# case 1 & 5: time range out of interval
if daytime_end < interval_start or daytime_start > interval_end:
continue
# case 2 & 4: adjust start, end to fit within interval
daytime_start = max(daytime_start, interval_start)
daytime_end = min(daytime_end, interval_end)
# case 2+, 4+, 3
hours_timedelta += (daytime_end - daytime_start)
# return timedelta converted to hours
return (hours_timedelta.days * 24.0 + hours_timedelta.seconds / 3600.0)
resource_calendar() resource_calendar()

View File

@ -40,12 +40,13 @@
I check Actual working hours on resource 'Developer' from this week I check Actual working hours on resource 'Developer' from this week
- -
!python {model: resource.calendar}: | !python {model: resource.calendar}: |
from datetime import datetime, timedelta from datetime import datetime
from dateutil.relativedelta import relativedelta
now = datetime.now() now = datetime.now()
dt_from = now - timedelta(days=now.weekday()) dt_from = now - relativedelta(days=now.weekday(), hour=8, minute=30)
dt_to = dt_from+ timedelta(days=6) dt_to = dt_from + relativedelta(days=6, hour=17)
hours = self.interval_hours_get(cr, uid, ref('timesheet_group1'), dt_from, dt_to, resource=ref('resource_developer')) hours = self.interval_hours_get(cr, uid, ref('timesheet_group1'), dt_from, dt_to, resource=ref('resource_developer'))
assert hours > 27 , 'Invalid Total Week working hour calculated' assert hours > 27 , 'Invalid Total Week working hour calculated, got %r, expected > 27' % hours
- -
Project Analysis work is of 20 hours which will start from Week start so i will calculate working schedule for resource Analyst for the same. Project Analysis work is of 20 hours which will start from Week start so i will calculate working schedule for resource Analyst for the same.
- -

View File

@ -143,6 +143,8 @@ class account_config_settings(osv.osv_memory):
def onchange_sale_analytic_plans(self, cr, uid, ids, module_sale_analytic_plans, context=None): def onchange_sale_analytic_plans(self, cr, uid, ids, module_sale_analytic_plans, context=None):
""" change group_analytic_account_for_sales following module_sale_analytic_plans """ """ change group_analytic_account_for_sales following module_sale_analytic_plans """
if not module_sale_analytic_plans:
return {}
return {'value': {'group_analytic_account_for_sales': module_sale_analytic_plans}} return {'value': {'group_analytic_account_for_sales': module_sale_analytic_plans}}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -543,6 +543,9 @@ class sale_order(osv.osv):
invoice_ref += o.name + '|' invoice_ref += o.name + '|'
self.write(cr, uid, [o.id], {'state': 'progress'}) self.write(cr, uid, [o.id], {'state': 'progress'})
cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res)) cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
#remove last '|' in invoice_ref
if len(invoice_ref) >= 1:
invoice_ref = invoice_ref[:-1]
invoice.write(cr, uid, [res], {'origin': invoice_ref, 'name': invoice_ref}) invoice.write(cr, uid, [res], {'origin': invoice_ref, 'name': invoice_ref})
else: else:
for order, il in val: for order, il in val:

View File

@ -25,6 +25,7 @@ from openerp.tools.translate import _
class sale_order_line_make_invoice(osv.osv_memory): class sale_order_line_make_invoice(osv.osv_memory):
_name = "sale.order.line.make.invoice" _name = "sale.order.line.make.invoice"
_description = "Sale OrderLine Make_invoice" _description = "Sale OrderLine Make_invoice"
def make_invoices(self, cr, uid, ids, context=None): def make_invoices(self, cr, uid, ids, context=None):
""" """
To make invoices. To make invoices.
@ -81,34 +82,30 @@ class sale_order_line_make_invoice(osv.osv_memory):
sales_order_obj = self.pool.get('sale.order') sales_order_obj = self.pool.get('sale.order')
for line in sales_order_line_obj.browse(cr, uid, context.get('active_ids', []), context=context): for line in sales_order_line_obj.browse(cr, uid, context.get('active_ids', []), context=context):
if (not line.invoiced) and (line.state not in ('draft', 'cancel')): if (not line.invoiced) and (line.state not in ('draft', 'cancel')):
if not line.order_id.id in invoices: if not line.order_id in invoices:
invoices[line.order_id.id] = [] invoices[line.order_id] = []
line_id = sales_order_line_obj.invoice_line_create(cr, uid, line_id = sales_order_line_obj.invoice_line_create(cr, uid, [line.id])
[line.id])
for lid in line_id: for lid in line_id:
invoices[line.order_id.id].append((line, lid)) invoices[line.order_id].append(lid)
for result in invoices.values(): for order, il in invoices.items():
order = result[0][0].order_id
il = map(lambda x: x[1], result)
res = make_invoice(order, il) res = make_invoice(order, il)
cr.execute('INSERT INTO sale_order_invoice_rel \ cr.execute('INSERT INTO sale_order_invoice_rel \
(order_id,invoice_id) values (%s,%s)', (order.id, res)) (order_id,invoice_id) values (%s,%s)', (order.id, res))
flag = True flag = True
data_sale = sales_order_obj.browse(cr, uid, line.order_id.id, context=context)
sales_order_obj.message_post(cr, uid, [order.id], body=_("Invoice created"), context=context) sales_order_obj.message_post(cr, uid, [order.id], body=_("Invoice created"), context=context)
data_sale = sales_order_obj.browse(cr, uid, order.id, context=context)
for line in data_sale.order_line: for line in data_sale.order_line:
if not line.invoiced: if not line.invoiced:
flag = False flag = False
break break
if flag: if flag:
sales_order_obj.signal_manual_invoice(cr, uid, [line.order_id.id]) wf_service.trg_validate(uid, 'sale.order', order.id, 'manual_invoice', cr)
sales_order_obj.write(cr, uid, [line.order_id.id], {'state': 'progress'}) sales_order_obj.write(cr, uid, [order.id], {'state': 'progress'})
if not invoices: if not invoices:
raise osv.except_osv(_('Warning!'), _('Invoice cannot be created for this Sales Order Line due to one of the following reasons:\n1.The state of this sales order line is either "draft" or "cancel"!\n2.The Sales Order Line is Invoiced!')) raise osv.except_osv(_('Warning!'), _('Invoice cannot be created for this Sales Order Line due to one of the following reasons:\n1.The state of this sales order line is either "draft" or "cancel"!\n2.The Sales Order Line is Invoiced!'))
if context.get('open_invoices', False): if context.get('open_invoices', False):
return self.open_invoices( cr, uid, ids, res, context=context) return self.open_invoices(cr, uid, ids, res, context=context)
return {'type': 'ir.actions.act_window_close'} return {'type': 'ir.actions.act_window_close'}
def open_invoices(self, cr, uid, ids, invoice_ids, context=None): def open_invoices(self, cr, uid, ids, invoice_ids, context=None):
@ -118,7 +115,7 @@ class sale_order_line_make_invoice(osv.osv_memory):
form_id = form_res and form_res[1] or False form_id = form_res and form_res[1] or False
tree_res = ir_model_data.get_object_reference(cr, uid, 'account', 'invoice_tree') tree_res = ir_model_data.get_object_reference(cr, uid, 'account', 'invoice_tree')
tree_id = tree_res and tree_res[1] or False tree_id = tree_res and tree_res[1] or False
return { return {
'name': _('Invoice'), 'name': _('Invoice'),
'view_type': 'form', 'view_type': 'form',

View File

@ -39,7 +39,7 @@ class sale_make_invoice(osv.osv_memory):
record_id = context and context.get('active_id', False) record_id = context and context.get('active_id', False)
order = self.pool.get('sale.order').browse(cr, uid, record_id, context=context) order = self.pool.get('sale.order').browse(cr, uid, record_id, context=context)
if order.state == 'draft': if order.state == 'draft':
raise osv.except_osv(_('Warning!'),'You cannot create invoice when sales order is not confirmed.') raise osv.except_osv(_('Warning!'), _('You cannot create invoice when sales order is not confirmed.'))
return False return False
def make_invoices(self, cr, uid, ids, context=None): def make_invoices(self, cr, uid, ids, context=None):
@ -50,8 +50,11 @@ class sale_make_invoice(osv.osv_memory):
if context is None: if context is None:
context = {} context = {}
data = self.read(cr, uid, ids)[0] data = self.read(cr, uid, ids)[0]
order_obj.action_invoice_create(cr, uid, context.get(('active_ids'), []), data['grouped'], date_invoice = data['invoice_date']) for sale_order in order_obj.browse(cr, uid, context.get(('active_ids'), []), context=context):
order_obj.signal_manual_invoice(cr, uid, context.get(('active_ids'), [])) if sale_order.state != 'manual':
raise osv.except_osv(_('Warning!'), _("You shouldn't manually invoice the following sale order %s") % (sale_order.name))
order_obj.action_invoice_create(cr, uid, context.get(('active_ids'), []), data['grouped'], date_invoice=data['invoice_date'])
for o in order_obj.browse(cr, uid, context.get(('active_ids'), []), context=context): for o in order_obj.browse(cr, uid, context.get(('active_ids'), []), context=context):
for i in o.invoice_ids: for i in o.invoice_ids:
@ -60,7 +63,7 @@ class sale_make_invoice(osv.osv_memory):
result = mod_obj.get_object_reference(cr, uid, 'account', 'action_invoice_tree1') result = mod_obj.get_object_reference(cr, uid, 'account', 'action_invoice_tree1')
id = result and result[1] or False id = result and result[1] or False
result = act_obj.read(cr, uid, [id], context=context)[0] result = act_obj.read(cr, uid, [id], context=context)[0]
result['domain'] = "[('id','in', ["+','.join(map(str,newinv))+"])]" result['domain'] = "[('id','in', [" + ','.join(map(str, newinv)) + "])]"
return result return result

View File

@ -6,16 +6,16 @@
<field name="model">sale.make.invoice</field> <field name="model">sale.make.invoice</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Create invoices" version="7.0"> <form string="Create invoices" version="7.0">
<header>
<button name="make_invoices" string="Create Invoices" type="object" class="oe_highlight" />
or
<button string="Cancel" class="oe_link" special="cancel" />
</header>
<separator colspan="4" string="Do you really want to create the invoice(s)?" /> <separator colspan="4" string="Do you really want to create the invoice(s)?" />
<group> <group>
<field name="grouped"/> <field name="grouped"/>
<field name="invoice_date"/> <field name="invoice_date"/>
</group> </group>
<footer>
<button name="make_invoices" string="Create Invoices" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form> </form>
</field> </field>
</record> </record>

View File

@ -53,6 +53,7 @@ class action_traceability(osv.osv_memory):
'domain': "[('id','in',["+','.join(map(str, ids))+"])]", 'domain': "[('id','in',["+','.join(map(str, ids))+"])]",
'name': ((type1=='move_history_ids2') and _('Upstream Traceability')) or _('Downstream Traceability'), 'name': ((type1=='move_history_ids2') and _('Upstream Traceability')) or _('Downstream Traceability'),
'view_mode': 'tree', 'view_mode': 'tree',
'view_type': 'tree',
'res_model': 'stock.move', 'res_model': 'stock.move',
'field_parent': type1, 'field_parent': type1,
'view_id': (view_id,'View'), 'view_id': (view_id,'View'),