[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:
commit
3bb51ac696
|
@ -1027,6 +1027,9 @@ class account_period(osv.osv):
|
|||
|
||||
def action_draft(self, cr, uid, ids, *args):
|
||||
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_period set state=%s where id in %s', (mode, tuple(ids),))
|
||||
return True
|
||||
|
|
|
@ -742,7 +742,7 @@ class account_move_line(osv.osv):
|
|||
def list_partners_to_reconcile(self, cr, uid, context=None):
|
||||
cr.execute(
|
||||
"""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
|
||||
RIGHT JOIN account_account a ON (a.id = l.account_id)
|
||||
RIGHT JOIN res_partner p ON (l.partner_id = p.id)
|
||||
|
@ -753,9 +753,14 @@ class account_move_line(osv.osv):
|
|||
) AS s
|
||||
WHERE debit > 0 AND credit > 0 AND (last_reconciliation_date IS NULL OR max_date > last_reconciliation_date)
|
||||
ORDER BY last_reconciliation_date""")
|
||||
ids = cr.fetchall()
|
||||
ids = len(ids) and [x[0] for x in ids] or []
|
||||
return self.pool.get('res.partner').name_get(cr, uid, ids, context=context)
|
||||
ids = [x[0] for x in cr.fetchall()]
|
||||
if not ids:
|
||||
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):
|
||||
move_rec_obj = self.pool.get('account.move.reconcile')
|
||||
|
|
|
@ -210,8 +210,8 @@ class account_invoice_report(osv.osv):
|
|||
cr.id IN (SELECT id
|
||||
FROM res_currency_rate cr2
|
||||
WHERE (cr2.currency_id = sub.currency_id)
|
||||
AND ((sub.date IS NOT NULL AND cr.name <= sub.date)
|
||||
OR (sub.date IS NULL AND cr.name <= NOW()))
|
||||
AND ((sub.date IS NOT NULL AND cr2.name <= sub.date)
|
||||
OR (sub.date IS NULL AND cr2.name <= NOW()))
|
||||
ORDER BY name DESC LIMIT 1)
|
||||
)""" % (
|
||||
self._table,
|
||||
|
|
|
@ -168,7 +168,7 @@
|
|||
</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">VAT : [[ (o.partner_id.vat) or removeParentNode('para') ]]</para>
|
||||
<para style="terp_default_8">TIN : [[ (o.partner_id.vat) or removeParentNode('para') ]]</para>
|
||||
</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
|
|
|
@ -181,9 +181,9 @@ class account_invoice_refund(osv.osv_memory):
|
|||
invoice = invoice[0]
|
||||
del invoice['id']
|
||||
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_obj._refund_cleanup_lines(cr, uid, tax_lines)
|
||||
tax_lines = inv_obj._refund_cleanup_lines(cr, uid, tax_lines, context=context)
|
||||
invoice.update({
|
||||
'type': inv.type,
|
||||
'date_invoice': date,
|
||||
|
|
|
@ -352,11 +352,10 @@ class account_analytic_account(osv.osv):
|
|||
res[account.id] = 0.0
|
||||
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):
|
||||
if not sale.invoiced:
|
||||
res[account.id] += sale.amount_untaxed
|
||||
for invoice in sale.invoice_ids:
|
||||
if invoice.state not in ('draft', 'cancel'):
|
||||
res[account.id] -= invoice.amount_untaxed
|
||||
res[account.id] += sale.amount_untaxed
|
||||
for invoice in sale.invoice_ids:
|
||||
if invoice.state != 'cancel':
|
||||
res[account.id] -= invoice.amount_untaxed
|
||||
return res
|
||||
|
||||
def _timesheet_ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
|
||||
|
|
|
@ -5,6 +5,7 @@ openerp.auth_oauth = function(instance) {
|
|||
start: function(parent, params) {
|
||||
var self = this;
|
||||
var d = this._super.apply(this, arguments);
|
||||
this.$el.hide();
|
||||
this.$el.on('click', 'a.zocial', this.on_oauth_sign_in);
|
||||
this.oauth_providers = [];
|
||||
if(this.params.oauth_error === 1) {
|
||||
|
@ -24,6 +25,8 @@ openerp.auth_oauth = function(instance) {
|
|||
var db = this.$("form [name=db]").val();
|
||||
if (db) {
|
||||
this.rpc("/auth_oauth/list_providers", { dbname: db }).done(this.on_oauth_loaded);
|
||||
} else {
|
||||
this.$el.show();
|
||||
}
|
||||
},
|
||||
on_oauth_loaded: function(result) {
|
||||
|
@ -32,6 +35,7 @@ openerp.auth_oauth = function(instance) {
|
|||
if (this.oauth_providers.length === 1 && params.type === 'signup') {
|
||||
this.do_oauth_sign_in(this.oauth_providers[0]);
|
||||
} else {
|
||||
this.$el.show();
|
||||
this.$('.oe_oauth_provider_login_button').remove();
|
||||
var buttons = QWeb.render("auth_oauth.Login.button",{"widget":this});
|
||||
this.$(".oe_login_pane form ul").after(buttons);
|
||||
|
@ -57,7 +61,7 @@ openerp.auth_oauth = function(instance) {
|
|||
state: JSON.stringify(state),
|
||||
};
|
||||
var url = provider.auth_endpoint + '?' + $.param(params);
|
||||
window.location = url;
|
||||
instance.web.redirect(url);
|
||||
},
|
||||
_oauth_state: function(provider) {
|
||||
// return the state object sent back with the redirected uri
|
||||
|
|
|
@ -419,8 +419,10 @@ instance.board.AddToDashboard = instance.web.search.Input.extend({
|
|||
instance.web.SearchView.include({
|
||||
add_common_inputs: function() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -103,16 +103,16 @@ Dashboard for CRM will include:
|
|||
'crm_action_rule_demo.xml',
|
||||
],
|
||||
'test': [
|
||||
'test/process/communication_with_customer.yml',
|
||||
'test/process/lead2opportunity2win.yml',
|
||||
'test/process/lead2opportunity_assign_salesmen.yml',
|
||||
'test/process/merge_opportunity.yml',
|
||||
'test/process/cancel_lead.yml',
|
||||
'test/process/segmentation.yml',
|
||||
'test/process/phonecalls.yml',
|
||||
'test/ui/crm_demo.yml',
|
||||
'test/ui/duplicate_lead.yml',
|
||||
'test/ui/delete_lead.yml',
|
||||
'test/crm_lead_message.yml',
|
||||
'test/lead2opportunity2win.yml',
|
||||
'test/lead2opportunity_assign_salesmen.yml',
|
||||
'test/crm_lead_merge.yml',
|
||||
'test/crm_lead_cancel.yml',
|
||||
'test/segmentation.yml',
|
||||
'test/phonecalls.yml',
|
||||
'test/crm_lead_onchange.yml',
|
||||
'test/crm_lead_copy.yml',
|
||||
'test/crm_lead_unlink.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
from openerp.addons.base_status.base_stage import base_stage
|
||||
import crm
|
||||
from datetime import datetime
|
||||
from operator import itemgetter
|
||||
from openerp.osv import fields, osv
|
||||
import time
|
||||
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)
|
||||
sequenced_opps = []
|
||||
for opportunity in opportunities:
|
||||
sequence = -1
|
||||
if opportunity.stage_id and opportunity.stage_id.state != 'cancel':
|
||||
sequenced_opps.append((opportunity.stage_id.sequence, opportunity))
|
||||
else:
|
||||
sequenced_opps.append((-1, opportunity))
|
||||
sequenced_opps.sort(key=lambda tup: tup[0], reverse=True)
|
||||
opportunities = [opportunity for sequence, opportunity in sequenced_opps]
|
||||
sequence = opportunity.stage_id.sequence
|
||||
sequenced_opps.append(((int(sequence != -1 and opportunity.type == 'opportunity'), sequence, -opportunity.id), opportunity))
|
||||
|
||||
sequenced_opps.sort(reverse=True)
|
||||
opportunities = map(itemgetter(1), sequenced_opps)
|
||||
ids = [opportunity.id for opportunity in opportunities]
|
||||
highest = opportunities[0]
|
||||
opportunities_rest = opportunities[1:]
|
||||
|
@ -652,11 +654,10 @@ class crm_lead(base_stage, format_address, osv.osv):
|
|||
opportunities.extend(opportunities_rest)
|
||||
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
|
||||
if merged_data.get('type') == 'opportunity' and merged_data.get('section_id'):
|
||||
section_stages = self.pool.get('crm.case.section').read(cr, uid, merged_data['section_id'], ['stage_ids'], context=context)
|
||||
if merged_data.get('stage_id') not in section_stages['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'] = stages_sequences and stages_sequences[0] or False
|
||||
if merged_data.get('section_id'):
|
||||
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_stage_ids:
|
||||
merged_data['stage_id'] = section_stage_ids and section_stage_ids[0] or False
|
||||
# Write merged data into first opportunity
|
||||
self.write(cr, uid, [highest.id], merged_data, context=context)
|
||||
# Delete tail opportunities
|
||||
|
|
|
@ -7,68 +7,68 @@
|
|||
<field name="name">New</field>
|
||||
<field eval="1" name="case_default"/>
|
||||
<field name="state">draft</field>
|
||||
<field eval="'10'" name="probability"/>
|
||||
<field eval="'10'" name="sequence"/>
|
||||
<field eval="0" name="probability"/>
|
||||
<field eval="10" name="sequence"/>
|
||||
<field name="type">both</field>
|
||||
</record>
|
||||
<record model="crm.case.stage" id="stage_lead2">
|
||||
<field name="name">Opportunity</field>
|
||||
<field eval="1" name="case_default"/>
|
||||
<field name="state">open</field>
|
||||
<field eval="'20'" name="probability"/>
|
||||
<field eval="'11'" name="sequence"/>
|
||||
<field eval="20" name="probability"/>
|
||||
<field eval="20" name="sequence"/>
|
||||
<field name="type">lead</field>
|
||||
</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">
|
||||
<field name="name">Dead</field>
|
||||
<field eval="1" name="case_default"/>
|
||||
<field eval="False" name="fold"/>
|
||||
<field name="state">cancel</field>
|
||||
<field eval="'0'" name="probability"/>
|
||||
<field eval="'16'" name="sequence"/>
|
||||
<field eval="0" name="probability"/>
|
||||
<field eval="30" name="sequence"/>
|
||||
<field name="type">lead</field>
|
||||
</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">
|
||||
<field name="name">Lost</field>
|
||||
<field eval="1" name="case_default"/>
|
||||
<field eval="True" name="fold"/>
|
||||
<field eval="1" name="on_change"/>
|
||||
<field name="state">cancel</field>
|
||||
<field eval="'0'" name="probability"/>
|
||||
<field eval="'17'" name="sequence"/>
|
||||
<field eval="0" name="probability"/>
|
||||
<field eval="140" name="sequence"/>
|
||||
<field name="type">opportunity</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
<button name="case_cancel" string="Cancel Case" type="object"
|
||||
states="draft,open,pending"/>
|
||||
<field name="stage_id" widget="statusbar" clickable="True"
|
||||
domain="['&', '|', ('case_default', '=', True), ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"
|
||||
on_change="onchange_stage_id(stage_id)"/>
|
||||
</header>
|
||||
<sheet>
|
||||
|
|
|
@ -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_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_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_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_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
|
||||
|
|
|
|
@ -37,20 +37,19 @@
|
|||
I check for the resulting merged opp (based on name and partner).
|
||||
-
|
||||
!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'
|
||||
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 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.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.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.
|
||||
-
|
||||
!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')
|
||||
|
||||
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')
|
||||
-
|
||||
I want to test leads merge. Start by creating two leads (with the same partner).
|
|
@ -258,7 +258,9 @@
|
|||
name="Documents"
|
||||
action="action_document_file_form"
|
||||
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">
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
|
|
|
@ -228,6 +228,8 @@ class hr_expense_expense(osv.osv):
|
|||
for exp in self.browse(cr, uid, ids, context=context):
|
||||
if not exp.employee_id.address_home_id:
|
||||
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
|
||||
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
|
||||
if not acc:
|
||||
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:
|
||||
acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company.id})
|
||||
if not acc:
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
<field name="date_value" string="Expense Date"/>
|
||||
<field name="name"/>
|
||||
<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="unit_amount"/>
|
||||
<field name="unit_quantity"/>
|
||||
|
|
|
@ -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_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):
|
||||
'''This method will create entry in resource calendar leave object at the time of holidays validated '''
|
||||
|
|
|
@ -169,7 +169,7 @@ class lunch_order(osv.Model):
|
|||
line_ref = self.pool.get("lunch.order.line")
|
||||
if view_type == 'form':
|
||||
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")
|
||||
#If there are no preference (it's the first time for the user)
|
||||
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
|
||||
|
||||
#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.set('class','oe_lunch_30pc')
|
||||
xml_pref_1.set('class', 'oe_lunch_30pc')
|
||||
xml_pref_2 = etree.Element("h2")
|
||||
xml_pref_2.text = key
|
||||
xml_pref_1.append(xml_pref_2)
|
||||
i = 0
|
||||
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 pref in val.values():
|
||||
#We only show 5 preferences per category (or it will be too long)
|
||||
if i==5: break
|
||||
i+=1
|
||||
xml_pref_3 = etree.Element("div")
|
||||
xml_pref_3.set('class','oe_lunch_vignette')
|
||||
xml_pref_1.append(xml_pref_3)
|
||||
for elmt in val.values():
|
||||
sorted_values[elmt.id] = elmt
|
||||
for key, pref in sorted(sorted_values.iteritems(), key=lambda (k, v): (k, v), reverse=True):
|
||||
#We only show 5 preferences per category (or it will be too long)
|
||||
if i == 5:
|
||||
break
|
||||
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.set('class','oe_lunch_button')
|
||||
xml_pref_3.append(xml_pref_4)
|
||||
xml_pref_4 = etree.Element("span")
|
||||
xml_pref_4.set('class','oe_lunch_button')
|
||||
xml_pref_3.append(xml_pref_4)
|
||||
|
||||
xml_pref_5 = etree.Element("button")
|
||||
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('type','object')
|
||||
xml_pref_5.set('string','+')
|
||||
xml_pref_4.append(xml_pref_5)
|
||||
xml_pref_5 = etree.Element("button")
|
||||
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('type','object')
|
||||
xml_pref_5.set('string','+')
|
||||
xml_pref_4.append(xml_pref_5)
|
||||
|
||||
xml_pref_6 = etree.Element("button")
|
||||
xml_pref_6.set('name',"add_preference_"+str(pref.id))
|
||||
xml_pref_6.set('class','oe_link oe_button_add')
|
||||
xml_pref_6.set('type','object')
|
||||
xml_pref_6.set('string',_("Add"))
|
||||
xml_pref_4.append(xml_pref_6)
|
||||
xml_pref_6 = etree.Element("button")
|
||||
xml_pref_6.set('name',"add_preference_"+str(pref.id))
|
||||
xml_pref_6.set('class','oe_link oe_button_add')
|
||||
xml_pref_6.set('type','object')
|
||||
xml_pref_6.set('string',_("Add"))
|
||||
xml_pref_4.append(xml_pref_6)
|
||||
|
||||
xml_pref_7 = etree.Element("div")
|
||||
xml_pref_7.set('class','oe_group_text_button')
|
||||
xml_pref_3.append(xml_pref_7)
|
||||
xml_pref_7 = etree.Element("div")
|
||||
xml_pref_7.set('class','oe_group_text_button')
|
||||
xml_pref_3.append(xml_pref_7)
|
||||
|
||||
xml_pref_8 = etree.Element("div")
|
||||
xml_pref_8.set('class','oe_lunch_text')
|
||||
xml_pref_8.text = escape(pref.product_id.name)+str(" ")
|
||||
xml_pref_7.append(xml_pref_8)
|
||||
xml_pref_8 = etree.Element("div")
|
||||
xml_pref_8.set('class','oe_lunch_text')
|
||||
xml_pref_8.text = escape(pref.product_id.name)+str(" ")
|
||||
xml_pref_7.append(xml_pref_8)
|
||||
|
||||
price = pref.product_id.price or 0.0
|
||||
cur = currency.name or ''
|
||||
xml_pref_9 = etree.Element("span")
|
||||
xml_pref_9.set('class','oe_tag')
|
||||
xml_pref_9.text = str(price)+str(" ")+cur
|
||||
xml_pref_8.append(xml_pref_9)
|
||||
price = pref.product_id.price or 0.0
|
||||
cur = currency.name or ''
|
||||
xml_pref_9 = etree.Element("span")
|
||||
xml_pref_9.set('class','oe_tag')
|
||||
xml_pref_9.text = str(price)+str(" ")+cur
|
||||
xml_pref_8.append(xml_pref_9)
|
||||
|
||||
xml_pref_10 = etree.Element("div")
|
||||
xml_pref_10.set('class','oe_grey')
|
||||
xml_pref_10.text = escape(pref.note or '')
|
||||
xml_pref_3.append(xml_pref_10)
|
||||
xml_pref_10 = etree.Element("div")
|
||||
xml_pref_10.set('class','oe_grey')
|
||||
xml_pref_10.text = escape(pref.note or '')
|
||||
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']")
|
||||
if first_node and len(first_node)>0:
|
||||
|
|
|
@ -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')
|
||||
params = {
|
||||
'search_view_id': search_ref and search_ref[1] or False,
|
||||
'domain': [('model', '=', 'mail.group'), ('res_id', '=', mail_group_id)],
|
||||
'context': {'default_model': 'mail.group', 'default_res_id': mail_group_id, 'search_default_message_unread': True},
|
||||
'domain': [
|
||||
('model', '=', 'mail.group'),
|
||||
('res_id', '=', mail_group_id),
|
||||
],
|
||||
'context': {
|
||||
'default_model': 'mail.group',
|
||||
'default_res_id': mail_group_id,
|
||||
},
|
||||
'res_model': 'mail.message',
|
||||
'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')
|
||||
newref = cobj.copy(cr, SUPERUSER_ID, ref[1], default={'params': str(params), 'name': vals['name']}, context=context)
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
<field name="tag">mail.wall</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="context">{
|
||||
'search_default_message_unread': True
|
||||
}</field>
|
||||
<field name="params">{
|
||||
'read_action': 'read'
|
||||
|
|
|
@ -256,7 +256,19 @@ class mail_mail(osv.Model):
|
|||
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)
|
||||
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 {
|
||||
'body': body,
|
||||
'body_alternative': body_alternative,
|
||||
|
|
|
@ -268,19 +268,25 @@ class mail_message(osv.Model):
|
|||
domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)]
|
||||
if not create_missing:
|
||||
domain += [('starred', '=', not starred)]
|
||||
values = {
|
||||
'starred': starred
|
||||
}
|
||||
if starred:
|
||||
values['read'] = False
|
||||
|
||||
notif_ids = notification_obj.search(cr, uid, domain, context=context)
|
||||
|
||||
# all message have notifications: already set them as (un)starred
|
||||
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
|
||||
|
||||
# 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)]
|
||||
to_create_msg_ids = list(set(msg_ids) - set(notified_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.write(cr, uid, notif_ids, {'starred': starred}, 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, values, context=context)
|
||||
return starred
|
||||
|
||||
#------------------------------------------------------
|
||||
|
|
|
@ -468,6 +468,8 @@ class mail_thread(osv.AbstractModel):
|
|||
"""
|
||||
assert isinstance(message, Message), 'message must be an email.message.Message at this point'
|
||||
message_id = message.get('Message-Id')
|
||||
email_from = decode_header(message, 'From')
|
||||
email_to = decode_header(message, 'To')
|
||||
references = decode_header(message, 'References')
|
||||
in_reply_to = decode_header(message, 'In-Reply-To')
|
||||
|
||||
|
@ -481,8 +483,8 @@ class mail_thread(osv.AbstractModel):
|
|||
model_pool = self.pool.get(model)
|
||||
if thread_id and model and model_pool and model_pool.exists(cr, uid, thread_id) \
|
||||
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',
|
||||
message_id, model, thread_id, custom_values, uid)
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
return [(model, thread_id, custom_values, uid)]
|
||||
|
||||
# Verify whether this is a reply to a private message
|
||||
|
@ -493,8 +495,8 @@ class mail_thread(osv.AbstractModel):
|
|||
], limit=1, context=context)
|
||||
if message_ids:
|
||||
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',
|
||||
message_id, message.id, custom_values, uid)
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
|
||||
email_from, email_to, message_id, message.id, custom_values, uid)
|
||||
return [(message.model, message.res_id, custom_values, uid)]
|
||||
|
||||
# 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
|
||||
# user_id = self._message_find_user_id(cr, uid, message, context=context)
|
||||
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, \
|
||||
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
|
||||
|
||||
# 3. Fallback to the provided parameters, if they work
|
||||
|
@ -539,14 +542,14 @@ class mail_thread(osv.AbstractModel):
|
|||
except:
|
||||
thread_id = False
|
||||
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. " \
|
||||
"Create an appropriate mail.alias or force the destination model." % message_id
|
||||
"No possible route found for incoming message from %s to %s (Message-Id %s:)." \
|
||||
"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):
|
||||
_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
|
||||
_logger.debug('Routing mail with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
|
||||
message_id, model, thread_id, custom_values, uid)
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
|
||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||
return [(model, thread_id, custom_values, uid)]
|
||||
|
||||
def message_process(self, cr, uid, model, message, custom_values=None,
|
||||
|
@ -596,12 +599,24 @@ class mail_thread(osv.AbstractModel):
|
|||
if isinstance(message, unicode):
|
||||
message = message.encode('utf-8')
|
||||
msg_txt = email.message_from_string(message)
|
||||
routes = self.message_route(cr, uid, msg_txt, model,
|
||||
thread_id, custom_values,
|
||||
context=context)
|
||||
|
||||
# parse the message, verify we are not in a loop by checking message_id is not duplicated
|
||||
msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context)
|
||||
if strip_attachments:
|
||||
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
|
||||
partner_ids = msg.pop('partner_ids', [])
|
||||
|
@ -798,7 +813,7 @@ class mail_thread(osv.AbstractModel):
|
|||
# as RFC2822 requires timezone offset in Date headers.
|
||||
stored_date = parsed_date.replace(tzinfo=pytz.utc)
|
||||
else:
|
||||
stored_date = parsed_date.astimezone(pytz.utc)
|
||||
stored_date = parsed_date.astimezone(tz=pytz.utc)
|
||||
except Exception:
|
||||
_logger.warning('Failed to parse Date header %r in incoming mail '
|
||||
'with message-id %r, assuming current date/time.',
|
||||
|
@ -817,7 +832,7 @@ class mail_thread(osv.AbstractModel):
|
|||
if parent_ids:
|
||||
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
|
||||
|
||||
#------------------------------------------------------
|
||||
|
@ -956,12 +971,15 @@ class mail_thread(osv.AbstractModel):
|
|||
], limit=1, context=context)
|
||||
if author_ids:
|
||||
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
|
||||
if content_subtype == 'plaintext':
|
||||
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)
|
||||
partner_ids = set()
|
||||
kwargs_partner_ids = kwargs.pop('partner_ids', [])
|
||||
|
@ -974,11 +992,13 @@ class mail_thread(osv.AbstractModel):
|
|||
partner_ids.add(partner_id)
|
||||
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)
|
||||
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:
|
||||
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
|
||||
# - 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.update({
|
||||
'author_id': author_id,
|
||||
'model': model,
|
||||
'res_id': thread_id or False,
|
||||
'body': body,
|
||||
|
|
|
@ -626,6 +626,8 @@
|
|||
height: 32px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.openerp .oe_followers .oe_partner img{
|
||||
width: 32px;
|
||||
|
|
|
@ -1208,7 +1208,7 @@ openerp.mail = function (session) {
|
|||
this.author_id = datasets.author_id || false;
|
||||
this.thread_level = (datasets.thread_level+1) || 0;
|
||||
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);
|
||||
}
|
||||
this.partner_ids = datasets.partner_ids;
|
||||
|
@ -1389,7 +1389,10 @@ openerp.mail = function (session) {
|
|||
if (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);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -246,7 +246,11 @@
|
|||
<!-- message itself -->
|
||||
<div class="oe_msg_content">
|
||||
<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}&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}&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"/>
|
||||
</h1>
|
||||
<div class="oe_msg_body">
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
-->
|
||||
<div t-name="mail.followers.partner" class='oe_partner'>
|
||||
<img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
|
||||
<a t-attf-href="#model=res.partner&id=#{record.id}"><t t-raw="record.name"/></a>
|
||||
<a t-attf-href="#model=res.partner&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>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -280,7 +280,8 @@ class test_mail(TestMailBase):
|
|||
'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_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,
|
||||
'message_post: notification emails wrong number of send emails')
|
||||
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!')
|
||||
|
||||
# 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')
|
||||
for sent_email in sent_emails:
|
||||
self.assertEqual(sent_email['email_from'], 'Raoul Grosbedon <r@r>',
|
||||
|
|
|
@ -116,8 +116,11 @@ class TestMailgateway(TestMailBase):
|
|||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
# 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])
|
||||
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
|
||||
self.assertEqual(len(frog_group.message_ids), 1,
|
||||
'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()
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
# Test: one group created by raoul
|
||||
self.assertTrue(len(frog_groups) == 1)
|
||||
# Test: one group created by Raoul
|
||||
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])
|
||||
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
|
||||
self.assertEqual(len(frog_group.message_ids), 1,
|
||||
'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
|
||||
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')
|
||||
# Test: one group created by Sylvie
|
||||
self.assertTrue(len(frog_groups) == 1)
|
||||
# Test: one group created by Raoul (or Sylvie maybe, if we implement it)
|
||||
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])
|
||||
# Test: one message that is the incoming email
|
||||
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
|
||||
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',
|
||||
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
|
||||
|
@ -205,12 +212,30 @@ class TestMailgateway(TestMailBase):
|
|||
'message_process: reply on Frogs should not have created a duplicate group with old subject')
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: one new message
|
||||
self.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
|
||||
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
|
||||
self.assertEqual(frog_follower_ids, set([p1id, p2id]),
|
||||
'message_process: after reply, group should have 2 followers')
|
||||
|
||||
# Do: 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
|
||||
# --------------------------------------------------
|
||||
|
@ -223,6 +248,7 @@ class TestMailgateway(TestMailBase):
|
|||
# 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>',
|
||||
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)
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
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'})
|
||||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||
to='erroneous@example.com>', subject='Re: news (3)',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.new2@agrolait.com>',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
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'})
|
||||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||
to='erroneous@example.com>', subject='Re: news (3)',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.new3@agrolait.com>',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
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
|
||||
self.assertRaises(AssertionError,
|
||||
format_and_process,
|
||||
MAIL_TEMPLATE, to='noone@example.com', subject='spam', extra='', model='res.country')
|
||||
format_and_process,
|
||||
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
|
||||
self.assertRaises(AssertionError,
|
||||
format_and_process,
|
||||
MAIL_TEMPLATE, to='noone@example.com', subject='spam', extra='')
|
||||
format_and_process,
|
||||
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
|
||||
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,
|
||||
'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
|
||||
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
|
||||
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])
|
||||
msg = frog_group.message_ids[0]
|
||||
# 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
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
group_pigs.refresh()
|
||||
|
@ -327,4 +372,55 @@ class TestMailgateway(TestMailBase):
|
|||
|
||||
def test_20_private_discussion(self):
|
||||
""" 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')
|
||||
|
|
|
@ -175,11 +175,17 @@ class mail_compose_message(osv.TransientModel):
|
|||
: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)
|
||||
record_name = False
|
||||
if doc_name_get:
|
||||
record_name = doc_name_get[0][1]
|
||||
else:
|
||||
record_name = False
|
||||
return {'model': model, 'res_id': res_id, 'record_name': record_name}
|
||||
values = {
|
||||
'model': model,
|
||||
'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):
|
||||
""" Returns a defaults-like dict with initial values for the composition
|
||||
|
@ -197,7 +203,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
|
||||
# create subject
|
||||
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:
|
||||
reply_subject = "%s %s" % (re_prefix, reply_subject)
|
||||
# get partner_ids from original message
|
||||
|
|
|
@ -45,7 +45,7 @@ class StockMove(osv.osv):
|
|||
procurement_obj = self.pool.get('procurement.order')
|
||||
product_obj = self.pool.get('product.product')
|
||||
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, [
|
||||
('product_id','=',move.product_id.id),
|
||||
('bom_id','=',False),
|
||||
|
|
|
@ -4,7 +4,8 @@ Created on 18 oct. 2011
|
|||
@author: openerp
|
||||
'''
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.osv import osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
class plugin_handler(osv.osv_memory):
|
||||
_name = 'plugin.handler'
|
||||
|
@ -29,7 +30,7 @@ class plugin_handler(osv.osv_memory):
|
|||
partner_ids = partner_obj.search(cr, uid, [('email', 'like', address_email)])
|
||||
res_id = partner_ids and partner_ids[0] or 0
|
||||
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):
|
||||
"""
|
||||
|
@ -48,7 +49,7 @@ class plugin_handler(osv.osv_memory):
|
|||
message_id = msg.get('message_id')
|
||||
msg_id = False
|
||||
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
|
||||
if not msg_id and parent_id:
|
||||
msg_id = parent_id
|
||||
|
@ -57,8 +58,8 @@ class plugin_handler(osv.osv_memory):
|
|||
res_id = msg.res_id
|
||||
model = msg.model
|
||||
url = self._make_url(cr, uid, res_id, model)
|
||||
name = self.pool.get(model).name_get(cr, uid, [res_id])[0][1]
|
||||
return (model,res_id, url,name)
|
||||
name = self.pool.get(model).name_get(cr, uid, [res_id])[0][1]
|
||||
return (model, res_id, url, name)
|
||||
|
||||
def document_type(self, cr, uid, context=None):
|
||||
"""
|
||||
|
@ -94,26 +95,25 @@ class plugin_handler(osv.osv_memory):
|
|||
model_obj = self.pool.get(model)
|
||||
msg = self.pool.get('mail.thread').message_parse(cr, uid, email)
|
||||
message_id = msg.get('message-id')
|
||||
mail_ids = mail_message.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)])
|
||||
|
||||
if message_id and mail_ids :
|
||||
mail_ids = mail_message.search(cr, uid, [('message_id', '=', message_id), ('res_id', '=', res_id), ('model', '=', model)])
|
||||
if message_id and mail_ids:
|
||||
mail_record = mail_message.browse(cr, uid, mail_ids)[0]
|
||||
res_id = mail_record.res_id
|
||||
notify = "Email already pushed"
|
||||
notify = _("Email already pushed")
|
||||
elif res_id == 0:
|
||||
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:
|
||||
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:
|
||||
model_obj.message_post(cr, uid, [res_id],
|
||||
body= msg.get('body'),
|
||||
subject= msg.get('subject'),
|
||||
type= 'email',
|
||||
parent_id= msg.get('parent_id'),
|
||||
attachments= msg.get('attachments'))
|
||||
notify = "Mail successfully pushed"
|
||||
body=msg.get('body'),
|
||||
subject=msg.get('subject'),
|
||||
type='email',
|
||||
parent_id=msg.get('parent_id'),
|
||||
attachments=msg.get('attachments'))
|
||||
notify = _("Mail successfully pushed")
|
||||
url = self._make_url(cr, uid, res_id, model)
|
||||
return (model, res_id, url, notify)
|
||||
|
||||
|
@ -154,16 +154,17 @@ class plugin_handler(osv.osv_memory):
|
|||
message_id = msg.get('message-id')
|
||||
push_mail = self.push_message(cr, uid, model, headers, res_id)
|
||||
res_id = push_mail[1]
|
||||
model = push_mail[0]
|
||||
model = push_mail[0]
|
||||
notify = push_mail[3]
|
||||
for name in attachments.keys():
|
||||
attachment_ids = ir_attachment_obj.search(cr, uid, [('res_model', '=', model), ('res_id', '=', res_id), ('datas_fname', '=', name)])
|
||||
if attachment_ids:
|
||||
attach_ids.append( attachment_ids[0])
|
||||
attach_ids.append(attachment_ids[0])
|
||||
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))
|
||||
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:
|
||||
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)
|
||||
return (model, res_id, url)
|
||||
return (model, res_id, url, notify)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<UpgradeBackupLocation>
|
||||
</UpgradeBackupLocation>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
|
@ -29,7 +30,6 @@
|
|||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
</PropertyGroup>
|
||||
|
@ -53,9 +53,8 @@
|
|||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CookComputing.XmlRpcV2, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\OpenERPOutlookPlugin\bin\Release\CookComputing.XmlRpcV2.dll</HintPath>
|
||||
<Reference Include="CookComputing.XmlRpcV2">
|
||||
<HintPath>..\CookComputing.XmlRpcV2.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
|
@ -81,7 +80,7 @@
|
|||
<ItemGroup>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
|
||||
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
|
@ -91,7 +90,7 @@
|
|||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Windows Installer 3.1</ProductName>
|
||||
<ProductName>Windows Installer 3.1</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 10.00
|
||||
# Visual Studio 2008
|
||||
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual Studio 2010
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenERPOutlookPlugin", "OpenERPOutlookPlugin\OpenERPOutlookPlugin.csproj", "{F4B2219B-F235-400F-81B4-92F15250BBA4}"
|
||||
EndProject
|
||||
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "OpenERPOutlookPluginSetup", "OpenERPOutlookPluginSetup\OpenERPOutlookPluginSetup.vdproj", "{96333293-0156-4998-9065-42721CEB0368}"
|
||||
|
|
|
@ -303,6 +303,7 @@ namespace OpenERPOutlookPlugin
|
|||
|
||||
foreach (outlook.MailItem mailitem in Tools.MailItems())
|
||||
{
|
||||
|
||||
|
||||
Object[] contact = Cache.OpenERPOutlookPlugin.RedirectPartnerPage(mailitem);
|
||||
if ((int)contact[1] > 0)
|
||||
|
|
|
@ -95,7 +95,6 @@ namespace OpenERPOutlookPlugin
|
|||
/*
|
||||
* Will open the url into the web browser.
|
||||
*/
|
||||
|
||||
System.Diagnostics.Process.Start(web_url.ToString());
|
||||
}
|
||||
|
||||
|
@ -163,9 +162,16 @@ namespace OpenERPOutlookPlugin
|
|||
args.Add(attachments);
|
||||
object push_mail = this.Connection.Execute("plugin.handler", "push_message_outlook", args.ToArray());
|
||||
object[] push = (object[])push_mail;
|
||||
this.RedirectWeb(push[2].ToString());
|
||||
return true;
|
||||
if (Convert.ToInt32(push[1]) == 0)
|
||||
{
|
||||
MessageBox.Show(push[3].ToString());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RedirectWeb(push[2].ToString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public long CreatePartnerRecord(string name)
|
||||
{
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -295,7 +295,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
return;
|
||||
}
|
||||
//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){
|
||||
//don't show error popup if it fails
|
||||
event.preventDefault();
|
||||
|
|
|
@ -794,7 +794,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
renderElement: function() {
|
||||
var self = this;
|
||||
this._super();
|
||||
this.$('.oe_pos_synch-notification-button').click(function(){
|
||||
this.$el.click(function(){
|
||||
self.pos.flush();
|
||||
});
|
||||
},
|
||||
|
@ -879,6 +879,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
instance.web.unblockUI();
|
||||
self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();});
|
||||
|
||||
self.pos.flush();
|
||||
|
||||
}).fail(function(){ // error when loading models data from the backend
|
||||
instance.web.unblockUI();
|
||||
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
<field name="params" eval=""{
|
||||
'domain': [
|
||||
('to_read', '=', True),
|
||||
('starred', '=', False),
|
||||
],
|
||||
'show_compose_message': False,
|
||||
'show_link': False,
|
||||
|
@ -40,6 +39,7 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- TODO: remove me in 8.0 -->
|
||||
<record id="action_mail_star_feeds_portal" model="ir.actions.client">
|
||||
<field name="name">To-do</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
|
@ -67,6 +67,9 @@
|
|||
</p>
|
||||
</field>
|
||||
</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">
|
||||
<field name="name">Archives</field>
|
||||
|
@ -84,7 +87,6 @@
|
|||
'show_compose_message': False,
|
||||
'show_link': False,
|
||||
'view_mailbox': True,
|
||||
'read_action': 'read'
|
||||
}""/>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
|
@ -98,8 +100,6 @@
|
|||
|
||||
<menuitem name="Inbox" id="portal_inbox" parent="portal_messages"
|
||||
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"
|
||||
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
|
||||
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="Projects" id="portal_projects" parent="portal_menu" sequence="40"/>
|
||||
|
||||
|
|
|
@ -59,19 +59,8 @@ class sale_order(osv.Model):
|
|||
assert len(ids) == 1
|
||||
document = self.browse(cr, uid, ids[0], context=context)
|
||||
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:
|
||||
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)
|
||||
|
||||
def get_signup_url(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -374,7 +374,7 @@ class product_pricelist_item(osv.osv):
|
|||
result.append((line.id, line.name))
|
||||
|
||||
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
|
||||
|
||||
_name = "product.pricelist.item"
|
||||
|
|
|
@ -588,10 +588,13 @@ class product_product(osv.osv):
|
|||
# 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)
|
||||
if not other_product_ids:
|
||||
unlink_product_tmpl_ids.append(tmpl_id)
|
||||
unlink_product_tmpl_ids.append(tmpl_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)
|
||||
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):
|
||||
if uom_id and uom_po_id:
|
||||
|
|
|
@ -581,6 +581,7 @@
|
|||
<field name="delay"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
</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">
|
||||
<tree editable="bottom" string="Pricelist">
|
||||
<field name="min_quantity"/>
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<record id="all_projects_account" model="account.analytic.account">
|
||||
<field name="name">Projects</field>
|
||||
<field name="code">3</field>
|
||||
<field name="type">view</field>
|
||||
</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)"/>
|
||||
|
||||
|
|
|
@ -137,6 +137,13 @@ class project_issue(base_stage, osv.osv):
|
|||
|
||||
res = {}
|
||||
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] = {}
|
||||
for field in fields:
|
||||
duration = 0
|
||||
|
@ -150,20 +157,24 @@ class project_issue(base_stage, osv.osv):
|
|||
ans = date_open - date_create
|
||||
date_until = issue.date_open
|
||||
#Calculating no. of working hours to open the issue
|
||||
if issue.project_id.resource_calendar_id:
|
||||
hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
|
||||
hours = cal_obj._interval_hours_get(cr, uid, working_hours,
|
||||
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']:
|
||||
if issue.date_closed:
|
||||
date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
|
||||
date_until = issue.date_closed
|
||||
ans = date_close - date_create
|
||||
#Calculating no. of working hours to close the issue
|
||||
if issue.project_id.resource_calendar_id:
|
||||
hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
|
||||
date_create,
|
||||
date_close)
|
||||
hours = cal_obj._interval_hours_get(cr, uid, working_hours,
|
||||
date_create,
|
||||
date_close,
|
||||
timezone_from_uid=issue.user_id.id or uid,
|
||||
exclude_leaves=False,
|
||||
context=context)
|
||||
elif field in ['days_since_creation']:
|
||||
if issue.create_date:
|
||||
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)])
|
||||
if resource_ids and len(resource_ids):
|
||||
resource_id = resource_ids[0]
|
||||
duration = float(ans.days)
|
||||
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)
|
||||
duration = float(ans.days) + float(ans.seconds)/(24*3600)
|
||||
|
||||
if field in ['working_hours_open','working_hours_close']:
|
||||
res[issue.id][field] = hours
|
||||
else:
|
||||
res[issue.id][field] = abs(float(duration))
|
||||
elif field in ['day_open','day_close']:
|
||||
res[issue.id][field] = duration
|
||||
|
||||
return res
|
||||
|
||||
|
|
|
@ -63,9 +63,9 @@ class project_issue_report(osv.osv):
|
|||
'project_id':fields.many2one('project.project', 'Project',readonly=True),
|
||||
'version_id': fields.many2one('project.issue.version', 'Version'),
|
||||
'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),
|
||||
'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),
|
||||
}
|
||||
|
||||
|
@ -96,8 +96,8 @@ class project_issue_report(osv.osv):
|
|||
c.channel_id,
|
||||
c.task_id,
|
||||
date_trunc('day',c.create_date) as create_date,
|
||||
extract('epoch' from (c.date_open-c.create_date))/(3600*24) as delay_open,
|
||||
extract('epoch' from (c.date_closed-c.date_open))/(3600*24) as delay_close,
|
||||
c.day_open as delay_open,
|
||||
c.day_close as delay_close,
|
||||
(SELECT count(id) FROM mail_message WHERE model='project.issue' AND res_id=c.id) AS email
|
||||
|
||||
FROM
|
||||
|
|
|
@ -65,6 +65,11 @@ Example: Product: this product is deprecated, do not purchase more than 5.
|
|||
'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):
|
||||
""" 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}}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
<label for="module_purchase_requisition"/>
|
||||
</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"/>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -109,7 +109,7 @@
|
|||
<label for="group_analytic_account_for_purchases"/>
|
||||
</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"/>
|
||||
</div>
|
||||
</xpath>
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import pytz
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil import rrule
|
||||
import math
|
||||
from faces import *
|
||||
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
|
||||
resource if supplied.
|
||||
"""
|
||||
if not id:
|
||||
return 0.0
|
||||
dt_leave = self._get_leaves(cr, uid, id, resource)
|
||||
hours = 0.0
|
||||
return self._interval_hours_get(cr, uid, id, dt_from, dt_to, resource_id=resource)
|
||||
|
||||
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):
|
||||
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))
|
||||
der = cr.fetchall()
|
||||
for (hour_from,hour_to) in der:
|
||||
if hours != 0.0:#For first time of the loop only,hours will be 0
|
||||
current_hour = hour_from
|
||||
leave_flag = False
|
||||
if (hour_to>=current_hour):
|
||||
dt_check = dt_from.strftime('%Y-%m-%d')
|
||||
for leave in dt_leave:
|
||||
if dt_check == leave:
|
||||
dt_check = datetime.strptime(dt_check, "%Y-%m-%d") + timedelta(days=1)
|
||||
leave_flag = True
|
||||
@param dt_from : date start to calculate hours
|
||||
@param dt_end : date end to calculate hours
|
||||
@param resource_id: optional resource id, If given resource leave will be
|
||||
considered.
|
||||
@param timezone_from_uid: optional uid, if given we will considerer
|
||||
working hours in that user timezone
|
||||
@param exclude_leaves: optionnal, if set to True (default) we will exclude
|
||||
resource leaves from working hours
|
||||
@param context: current request context
|
||||
@return : Total number of working hours based dt_from and dt_end and
|
||||
resource if supplied.
|
||||
"""
|
||||
utc_tz = pytz.timezone('UTC')
|
||||
local_tz = utc_tz
|
||||
|
||||
if leave_flag:
|
||||
break
|
||||
else:
|
||||
d1 = dt_from
|
||||
d2 = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(hour_to)), int((hour_to%1) * 60))
|
||||
if timezone_from_uid:
|
||||
users_obj = self.pool.get('res.users')
|
||||
user_timezone = users_obj.browse(cr, uid, timezone_from_uid, context=context).partner_id.tz
|
||||
if user_timezone:
|
||||
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
|
||||
d1 = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(current_hour)), int((current_hour%1) * 60))
|
||||
def utc_to_local_zone(naive_datetime):
|
||||
utc_dt = utc_tz.localize(naive_datetime, is_dst=False)
|
||||
return utc_dt.astimezone(local_tz)
|
||||
|
||||
if dt_from.day == dt_to.day:
|
||||
if hour_from <= dt_to.hour <= hour_to:
|
||||
d2 = dt_to
|
||||
dt_from = d2
|
||||
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
|
||||
def float_time_convert(float_val):
|
||||
factor = float_val < 0 and -1 or 1
|
||||
val = abs(float_val)
|
||||
return (factor * int(math.floor(val)), int(round((val % 1) * 60)))
|
||||
|
||||
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()
|
||||
|
||||
|
|
|
@ -40,12 +40,13 @@
|
|||
I check Actual working hours on resource 'Developer' from this week
|
||||
-
|
||||
!python {model: resource.calendar}: |
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
now = datetime.now()
|
||||
dt_from = now - timedelta(days=now.weekday())
|
||||
dt_to = dt_from+ timedelta(days=6)
|
||||
dt_from = now - relativedelta(days=now.weekday(), hour=8, minute=30)
|
||||
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'))
|
||||
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.
|
||||
-
|
||||
|
|
|
@ -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):
|
||||
""" 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}}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -543,6 +543,9 @@ class sale_order(osv.osv):
|
|||
invoice_ref += o.name + '|'
|
||||
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))
|
||||
#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})
|
||||
else:
|
||||
for order, il in val:
|
||||
|
|
|
@ -25,6 +25,7 @@ from openerp.tools.translate import _
|
|||
class sale_order_line_make_invoice(osv.osv_memory):
|
||||
_name = "sale.order.line.make.invoice"
|
||||
_description = "Sale OrderLine Make_invoice"
|
||||
|
||||
def make_invoices(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
To make invoices.
|
||||
|
@ -81,34 +82,30 @@ class sale_order_line_make_invoice(osv.osv_memory):
|
|||
sales_order_obj = self.pool.get('sale.order')
|
||||
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.order_id.id in invoices:
|
||||
invoices[line.order_id.id] = []
|
||||
line_id = sales_order_line_obj.invoice_line_create(cr, uid,
|
||||
[line.id])
|
||||
if not line.order_id in invoices:
|
||||
invoices[line.order_id] = []
|
||||
line_id = sales_order_line_obj.invoice_line_create(cr, uid, [line.id])
|
||||
for lid in line_id:
|
||||
invoices[line.order_id.id].append((line, lid))
|
||||
for result in invoices.values():
|
||||
order = result[0][0].order_id
|
||||
il = map(lambda x: x[1], result)
|
||||
invoices[line.order_id].append(lid)
|
||||
for order, il in invoices.items():
|
||||
res = make_invoice(order, il)
|
||||
cr.execute('INSERT INTO sale_order_invoice_rel \
|
||||
(order_id,invoice_id) values (%s,%s)', (order.id, res))
|
||||
|
||||
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)
|
||||
data_sale = sales_order_obj.browse(cr, uid, order.id, context=context)
|
||||
for line in data_sale.order_line:
|
||||
if not line.invoiced:
|
||||
flag = False
|
||||
break
|
||||
if flag:
|
||||
sales_order_obj.signal_manual_invoice(cr, uid, [line.order_id.id])
|
||||
sales_order_obj.write(cr, uid, [line.order_id.id], {'state': 'progress'})
|
||||
wf_service.trg_validate(uid, 'sale.order', order.id, 'manual_invoice', cr)
|
||||
sales_order_obj.write(cr, uid, [order.id], {'state': 'progress'})
|
||||
|
||||
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!'))
|
||||
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'}
|
||||
|
||||
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
|
||||
tree_res = ir_model_data.get_object_reference(cr, uid, 'account', 'invoice_tree')
|
||||
tree_id = tree_res and tree_res[1] or False
|
||||
|
||||
|
||||
return {
|
||||
'name': _('Invoice'),
|
||||
'view_type': 'form',
|
||||
|
|
|
@ -39,7 +39,7 @@ class sale_make_invoice(osv.osv_memory):
|
|||
record_id = context and context.get('active_id', False)
|
||||
order = self.pool.get('sale.order').browse(cr, uid, record_id, context=context)
|
||||
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
|
||||
|
||||
def make_invoices(self, cr, uid, ids, context=None):
|
||||
|
@ -50,8 +50,11 @@ class sale_make_invoice(osv.osv_memory):
|
|||
if context is None:
|
||||
context = {}
|
||||
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'])
|
||||
order_obj.signal_manual_invoice(cr, uid, context.get(('active_ids'), []))
|
||||
for sale_order in order_obj.browse(cr, uid, context.get(('active_ids'), []), context=context):
|
||||
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 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')
|
||||
id = result and result[1] or False
|
||||
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
|
||||
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
<field name="model">sale.make.invoice</field>
|
||||
<field name="arch" type="xml">
|
||||
<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)?" />
|
||||
<group>
|
||||
<field name="grouped"/>
|
||||
<field name="invoice_date"/>
|
||||
</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>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -53,6 +53,7 @@ class action_traceability(osv.osv_memory):
|
|||
'domain': "[('id','in',["+','.join(map(str, ids))+"])]",
|
||||
'name': ((type1=='move_history_ids2') and _('Upstream Traceability')) or _('Downstream Traceability'),
|
||||
'view_mode': 'tree',
|
||||
'view_type': 'tree',
|
||||
'res_model': 'stock.move',
|
||||
'field_parent': type1,
|
||||
'view_id': (view_id,'View'),
|
||||
|
|
Loading…
Reference in New Issue