[MERGE] forward port of branch saas-2 up to revid 9092 dle@openerp.com-20140115165506-yuux9km39gbv7k4n

bzr revid: chs@openerp.com-20140115214454-dhl8xzr429co0j5d
This commit is contained in:
Christophe Simonis 2014-01-15 22:44:54 +01:00
commit 91aaaec0a5
40 changed files with 231 additions and 123 deletions

View File

@ -1023,7 +1023,10 @@ class account_period(osv.osv):
if not result:
result = self.search(cr, uid, args, context=context)
if not result:
raise osv.except_osv(_('Error!'), _('There is no period defined for this date: %s.\nPlease create one.')%dt)
model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_fiscalyear')
msg = _('There is no period defined for this date: %s.\nPlease, go to Configuration/Periods and configure a fiscal year.') % dt
raise openerp.exceptions.RedirectWarning(msg, action_id, _('Go to the configuration panel'))
return result
def action_draft(self, cr, uid, ids, *args):

View File

@ -849,18 +849,17 @@ class account_move_line(osv.osv):
(tuple(ids), ))
r = cr.fetchall()
#TODO: move this check to a constraint in the account_move_reconcile object
if len(r) != 1:
raise osv.except_osv(_('Error'), _('Entries are not of the same account or already reconciled ! '))
if not unrec_lines:
raise osv.except_osv(_('Error!'), _('Entry is already reconciled.'))
account = account_obj.browse(cr, uid, account_id, context=context)
if not account.reconcile:
raise osv.except_osv(_('Error'), _('The account is not defined to be reconciled !'))
if r[0][1] != None:
raise osv.except_osv(_('Error!'), _('Some entries are already reconciled.'))
if context.get('fy_closing'):
# We don't want to generate any write-off when being called from the
# wizard used to close a fiscal year (and it doesn't give us any
# writeoff_acc_id).
pass
elif (not currency_obj.is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \
if (not currency_obj.is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \
(account.currency_id and (not currency_obj.is_zero(cr, uid, account.currency_id, currency))):
if not writeoff_acc_id:
raise osv.except_osv(_('Warning!'), _('You have to provide an account for the write off/exchange difference entry.'))
@ -1199,7 +1198,7 @@ class account_move_line(osv.osv):
break
# Automatically convert in the account's secondary currency if there is one and
# the provided values were not already multi-currency
if account.currency_id and (vals.get('amount_currency', False) is False) and account.currency_id.id != account.company_id.currency_id.id:
if account.currency_id and 'amount_currency' not in vals and account.currency_id.id != account.company_id.currency_id.id:
vals['currency_id'] = account.currency_id.id
ctx = {}
if 'date' in vals:

View File

@ -224,14 +224,6 @@ class account_fiscalyear_close(osv.osv_memory):
query_2nd_part = ""
query_2nd_part_args = []
for account in obj_acc_account.browse(cr, uid, account_ids, context={'fiscalyear': fy_id}):
balance_in_currency = 0.0
if account.currency_id:
cr.execute('SELECT sum(COALESCE(amount_currency,0.0)) as balance_in_currency FROM account_move_line ' \
'WHERE account_id = %s ' \
'AND ' + query_line + ' ' \
'AND currency_id = %s', (account.id, account.currency_id.id))
balance_in_currency = cr.dictfetchone()['balance_in_currency']
company_currency_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id
if not currency_obj.is_zero(cr, uid, company_currency_id, abs(account.balance)):
if query_2nd_part:
@ -246,7 +238,7 @@ class account_fiscalyear_close(osv.osv_memory):
period.id,
account.id,
account.currency_id and account.currency_id.id or None,
balance_in_currency,
account.foreign_balance if account.currency_id else 0.0,
account.company_id.id,
'draft')
if query_2nd_part:

View File

@ -43,6 +43,7 @@
parent_id: account.cash
type: other
user_type: account.data_account_type_asset
reconcile: True
-
Configure Creditor Account Payable.
-
@ -52,6 +53,7 @@
parent_id: account.a_pay
type: other
user_type: account.data_account_type_payable
reconcile: True
-
Configure Debtor Account Receivable.
-
@ -61,6 +63,7 @@
parent_id: account.a_recv
type: other
user_type: account.data_account_type_receivable
reconcile: True
-
Configure Cost of Good sale Account.
-

View File

@ -43,6 +43,7 @@
parent_id: account.cash
type: other
user_type: account.data_account_type_asset
reconcile: True
-
Configure Creditor Account Payable.
-
@ -52,6 +53,7 @@
parent_id: account.a_pay
type: other
user_type: account.data_account_type_payable
reconcile: True
-
Configure Debtor Account Receivable.
-
@ -61,6 +63,7 @@
parent_id: account.a_recv
type: other
user_type: account.data_account_type_receivable
reconcile: True
-
Configure Cost of Good sale Account.
-

View File

@ -217,7 +217,7 @@ class account_voucher(osv.osv):
if context.get('type', 'sale') in ('purchase', 'payment'):
nodes = doc.xpath("//field[@name='partner_id']")
for node in nodes:
node.set('context', "{'search_default_supplier': 1}")
node.set('context', "{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}")
if context.get('invoice_type','') in ('in_invoice', 'in_refund'):
node.set('string', _("Supplier"))
res['arch'] = etree.tostring(doc)
@ -1329,7 +1329,7 @@ class account_voucher(osv.osv):
'date': voucher.date,
'credit': diff > 0 and diff or 0.0,
'debit': diff < 0 and -diff or 0.0,
'amount_currency': company_currency <> current_currency and (sign * -1 * voucher.writeoff_amount) or False,
'amount_currency': company_currency <> current_currency and (sign * -1 * voucher.writeoff_amount) or 0.0,
'currency_id': company_currency <> current_currency and current_currency or False,
'analytic_account_id': voucher.analytic_id and voucher.analytic_id.id or False,
}

View File

@ -194,7 +194,7 @@ class account_analytic_account(osv.osv):
'user_id': fields.many2one('res.users', 'Project Manager', track_visibility='onchange'),
'manager_id': fields.many2one('res.users', 'Account Manager', track_visibility='onchange'),
'date_start': fields.date('Start Date'),
'date': fields.date('End Date', select=True, track_visibility='onchange'),
'date': fields.date('Expiration Date', select=True, track_visibility='onchange'),
'company_id': fields.many2one('res.company', 'Company', required=False), #not required because we want to allow different companies to use the same chart of account, except for leaf accounts.
'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'),('pending','To Renew'),('close','Closed'),('cancelled', 'Cancelled')], 'Status', required=True, track_visibility='onchange'),
'currency_id': fields.function(_currency, fnct_inv=_set_company_currency, #the currency_id field is readonly except if it's a view account and if there is no company

View File

@ -1049,11 +1049,13 @@ class crm_lead(format_address, osv.osv):
def schedule_phonecall_send_note(self, cr, uid, ids, phonecall_id, action, context=None):
phonecall = self.pool.get('crm.phonecall').browse(cr, uid, [phonecall_id], context=context)[0]
if action == 'log':
prefix = 'Logged'
message = _('Logged a call for %(date)s. %(description)s')
else:
prefix = 'Scheduled'
suffix = ' %s' % phonecall.description
message = _("%s a call for %s.%s") % (prefix, phonecall.date, suffix)
message = _('Scheduled a call for %(date)s. %(description)s')
phonecall_date = datetime.strptime(phonecall.date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
phonecall_usertime = fields.datetime.context_timestamp(cr, uid, phonecall_date, context=context).strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
html_time = "<time datetime='%s+00:00'>%s</time>" % (phonecall.date, phonecall_usertime)
message = message % dict(date=html_time, description=phonecall.description)
return self.message_post(cr, uid, ids, body=message, context=context)
def log_meeting(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None):

View File

@ -1,11 +1,16 @@
openerp.crm_partner_assign = function (instance) {
instance.crm_partner_assign = instance.crm_partner_assign || {};
instance.crm_partner_assign.next_or_list = function(parent) {
var form = parent.inner_widget.views.form.controller;
form.dataset.remove_ids([form.dataset.ids[form.dataset.index]]);
form.reload();
if (!form.dataset.ids.length){
parent.inner_widget.switch_mode('list');
if (parent.inner_widget.active_view === "form"){
var form = parent.inner_widget.views.form.controller;
form.dataset.remove_ids([form.dataset.ids[form.dataset.index]]);
form.reload();
if (!form.dataset.ids.length){
parent.inner_widget.switch_mode('list');
}
}
else{
parent.inner_widget.views[parent.inner_widget.active_view].controller.reload();
}
parent.do_action({ type: 'ir.actions.act_window_close' });
};

View File

@ -53,7 +53,7 @@ class crm_lead_forward_to_partner(osv.TransientModel):
values = {'partner_assigned_id': False}
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
partner_ids = self.pool.get('res.partner').search(cr, SUPERUSER_ID, [('id', 'child_of', user.partner_id.commercial_partner_id.id)], context=context)
lead_obj.message_unsubscribe(cr, SUPERUSER_ID, context.get('active_ids'), partner_ids, context=None)
lead_obj.message_unsubscribe(cr, SUPERUSER_ID, context.get('active_ids', []), partner_ids, context=None)
try:
stage_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm_partner_assign', stage)[1]
except ValueError:
@ -62,11 +62,12 @@ class crm_lead_forward_to_partner(osv.TransientModel):
values.update({'stage_id': stage_id})
if wizard.comment:
message += '<p>%s</p>' % wizard.comment
lead_obj.message_post(cr, uid, context.get('active_ids'), body=message, context=context)
for active_id in context.get('active_ids', []):
lead_obj.message_post(cr, uid, active_id, body=message, context=context)
if values:
lead_obj.write(cr, SUPERUSER_ID, context.get('active_ids'), values)
lead_obj.write(cr, SUPERUSER_ID, context.get('active_ids', []), values)
if wizard.interested:
for lead in lead_obj.browse(cr, uid, context.get('active_ids'), context=context):
for lead in lead_obj.browse(cr, uid, context.get('active_ids', []), context=context):
lead_obj.convert_opportunity(cr, SUPERUSER_ID, [lead.id], lead.partner_id and lead.partner_id.id or None, context=None)
return {
'type': 'ir.actions.client',

View File

@ -139,6 +139,8 @@ class crm_lead_forward_to_partner(osv.TransientModel):
values = {'partner_assigned_id': partner_id, 'user_id': partner_leads['partner'].user_id.id}
if stage_id:
values['stage_id'] = stage_id
if partner_leads['partner'].user_id:
values['section_id'] = partner_leads['partner'].user_id.default_section_id.id
lead_obj.write(cr, uid, lead_ids, values)
self.pool.get('crm.lead').message_subscribe(cr, uid, lead_ids, [partner_id], context=context)
return True

View File

@ -23,6 +23,7 @@ import openerp
from openerp import SUPERUSER_ID
from openerp import tools
from openerp.osv import osv, fields
from openerp.modules.registry import RegistryManager
class decimal_precision(osv.osv):
_name = 'decimal.precision'
@ -44,23 +45,28 @@ class decimal_precision(osv.osv):
res = cr.fetchone()
return res[0] if res else 2
def clear_cache(self, cr):
"""clear cache and update models. Notify other workers to restart their registry."""
self.precision_get.clear_cache(self)
for obj in self.pool.obj_list():
for colname, col in self.pool.get(obj)._columns.items():
if isinstance(col, (fields.float, fields.function)):
col.digits_change(cr)
RegistryManager.signal_registry_change(cr.dbname)
def create(self, cr, uid, data, context=None):
res = super(decimal_precision, self).create(cr, uid, data, context=context)
self.precision_get.clear_cache(self)
self.clear_cache(cr)
return res
def unlink(self, cr, uid, ids, context=None):
res = super(decimal_precision, self).unlink(cr, uid, ids, context=context)
self.precision_get.clear_cache(self)
self.clear_cache(cr)
return res
def write(self, cr, uid, ids, data, *args, **argv):
res = super(decimal_precision, self).write(cr, uid, ids, data, *args, **argv)
self.precision_get.clear_cache(self)
for obj in self.pool.obj_list():
for colname, col in self.pool[obj]._columns.items():
if isinstance(col, (fields.float, fields.function)):
col.digits_change(cr)
self.clear_cache(cr)
return res

View File

@ -64,7 +64,7 @@ class test_message_compose(TestMail):
'body_html': '${object.description}',
'user_signature': True,
'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])],
'email_to': 'b@b.b c@c.c',
'email_to': 'b@b.b, c@c.c',
'email_cc': 'd@d.d'
})
@ -192,7 +192,7 @@ class test_message_compose(TestMail):
email_template.write(cr, uid, [email_template_id], {
'model_id': user_model_id,
'body_html': '${object.login}',
'email_to': '${object.email} c@c',
'email_to': '${object.email}, c@c',
'partner_to': '%i,%i' % (p_b_id, p_c_id),
'email_cc': 'd@d',
})
@ -217,7 +217,7 @@ class test_message_compose(TestMail):
'subject': '${object.name}',
'body_html': '${object.description}',
'user_signature': True,
'email_to': 'b@b.b c@c.c',
'email_to': 'b@b.b, c@c.c',
'email_cc': 'd@d.d',
'partner_to': '${user.partner_id.id},%s,%s,-1' % (self.user_raoul.partner_id.id, self.user_bert.partner_id.id)
})
@ -226,7 +226,7 @@ class test_message_compose(TestMail):
msg_id = email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, context=context)
mail = self.mail_mail.browse(cr, uid, msg_id, context=context)
self.assertEqual(mail.subject, 'Pigs', 'email_template: send_mail: wrong subject')
self.assertEqual(mail.email_to, 'b@b.b c@c.c', 'email_template: send_mail: wrong email_to')
self.assertEqual(mail.email_to, 'b@b.b, c@c.c', 'email_template: send_mail: wrong email_to')
self.assertEqual(mail.email_cc, 'd@d.d', 'email_template: send_mail: wrong email_cc')
self.assertEqual(
set([partner.id for partner in mail.recipient_ids]),

View File

@ -137,7 +137,7 @@ class mail_compose_message(osv.TransientModel):
def _get_or_create_partners_from_values(self, cr, uid, rendered_values, context=None):
""" Check for email_to, email_cc, partner_to """
partner_ids = []
mails = tools.email_split(rendered_values.pop('email_to', '') + ' ' + rendered_values.pop('email_cc', ''))
mails = tools.email_split(rendered_values.pop('email_to', '')) + tools.email_split(rendered_values.pop('email_cc', ''))
for mail in mails:
partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
partner_ids.append(partner_id)

View File

@ -120,7 +120,7 @@ class config(osv.Model):
res['url'] = content['alternateLink']
key = self._get_key_from_url(res['url'])
request_url = "https://www.googleapis.com/drive/v2/files/%s/permissions?emailMessage=This+is+a+drive+file+created+by+OpenERP&sendNotificationEmails=false&access_token=%s" % (key, access_token)
data = {'role': 'reader', 'type': 'anyone', 'value': '', 'withLink': True}
data = {'role': 'writer', 'type': 'anyone', 'value': '', 'withLink': True}
try:
req = urllib2.Request(request_url, json.dumps(data), headers)
urllib2.urlopen(req)
@ -133,7 +133,7 @@ class config(osv.Model):
req = urllib2.Request(request_url, json.dumps(data), headers)
urllib2.urlopen(req)
except urllib2.HTTPError:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("The permission 'writer' for your email '%s' has not been written on the document. Is this email a valid Google Account ?" % user.email), context=context)
pass
return res
def get_google_drive_config(self, cr, uid, res_model, res_id, context=None):

View File

@ -76,10 +76,11 @@ class account_analytic_account(osv.osv):
def on_change_partner_id(self, cr, uid, ids, partner_id, name, context=None):
res = super(account_analytic_account, self).on_change_partner_id(cr, uid, ids, partner_id, name, context=context)
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
if pricelist:
res['value']['pricelist_id'] = pricelist
if partner_id:
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
if pricelist:
res['value']['pricelist_id'] = pricelist
return res
def set_close(self, cr, uid, ids, context=None):

View File

@ -9,12 +9,14 @@ openerp.hr_timesheet_sheet = function(instance) {
},
init: function() {
this._super.apply(this, arguments);
var self = this;
this.set({
sheets: [],
date_to: false,
date_from: false,
});
this.updating = false;
this.defs = [];
this.field_manager.on("field_changed:timesheet_ids", this, this.query_sheets);
this.field_manager.on("field_changed:date_from", this, function() {
this.set({"date_from": instance.web.str_to_date(this.field_manager.get_field_value("date_from"))});
@ -29,6 +31,14 @@ openerp.hr_timesheet_sheet = function(instance) {
this.res_o2m_drop = new instance.web.DropMisordered();
this.render_drop = new instance.web.DropMisordered();
this.description_line = _t("/");
// Original save function is overwritten in order to wait all running deferreds to be done before actually applying the save.
this.view.original_save = _.bind(this.view.save, this.view);
this.view.save = function(prepend_on_create){
self.prepend_on_create = prepend_on_create;
return $.when.apply($, self.defs).then(function(){
return self.view.original_save(self.prepend_on_create);
});
};
},
go_to: function(event) {
var id = JSON.parse($(event.target).data("id"));
@ -192,11 +202,11 @@ openerp.hr_timesheet_sheet = function(instance) {
account.days[day_count].lines[0].unit_amount += num - self.sum_box(account, day_count);
var product = (account.days[day_count].lines[0].product_id instanceof Array) ? account.days[day_count].lines[0].product_id[0] : account.days[day_count].lines[0].product_id
var journal = (account.days[day_count].lines[0].journal_id instanceof Array) ? account.days[day_count].lines[0].journal_id[0] : account.days[day_count].lines[0].journal_id
new instance.web.Model("hr.analytic.timesheet").call("on_change_unit_amount", [[], product, account.days[day_count].lines[0].unit_amount, false, false, journal]).then(function(res) {
self.defs.push(new instance.web.Model("hr.analytic.timesheet").call("on_change_unit_amount", [[], product, account.days[day_count].lines[0].unit_amount, false, false, journal]).then(function(res) {
account.days[day_count].lines[0]['amount'] = res.value.amount || 0;
self.display_totals();
self.sync();
});
}));
if(!isNaN($(this).val())){
$(this).val(self.sum_box(account, day_count, true));
}

View File

@ -29,7 +29,7 @@ import openerp.tools.config
import openerp.modules.registry
from openerp import http
from openerp.http import request
from openerp.osv import osv, fields
from openerp.osv import osv, fields, expression
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
_logger = logging.getLogger(__name__)
@ -249,11 +249,31 @@ class im_user(osv.osv):
res[obj["id"]] = obj["im_last_status"] and (last_update + delta) > current
return res
def _status_search(self, cr, uid, obj, name, domain, context=None):
current = datetime.datetime.now()
delta = datetime.timedelta(0, DISCONNECTION_TIMER)
field, operator, value = domain[0]
if operator in expression.NEGATIVE_TERM_OPERATORS:
value = not value
if value:
return ['&', ('im_last_status', '=', True), ('im_last_status_update', '>', (current - delta).strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
else:
return ['|', ('im_last_status', '=', False), ('im_last_status_update', '<=', (current - delta).strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
def search_users(self, cr, uid, text_search, fields, limit, context=None):
my_id = self.get_my_id(cr, uid, None, context)
found = self.search(cr, uid, [["name", "ilike", text_search], ["id", "<>", my_id], ["uuid", "=", False]],
group_employee = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'group_user')[1]
found = self.search(cr, uid, [["name", "ilike", text_search], ["id", "<>", my_id], ["uuid", "=", False], ["im_status", "=", True], ["user_id.groups_id", "in", [group_employee]]],
order="name asc", limit=limit, context=context)
return self.read(cr, uid, found, fields, context=context)
if len(found) < limit:
found += self.search(cr, uid, [["name", "ilike", text_search], ["id", "<>", my_id], ["uuid", "=", False], ["im_status", "=", True], ["id", "not in", found]],
order="name asc", limit=limit, context=context)
if len(found) < limit:
found += self.search(cr, uid, [["name", "ilike", text_search], ["id", "<>", my_id], ["uuid", "=", False], ["im_status", "=", False], ["id", "not in", found]],
order="name asc", limit=limit-len(found), context=context)
users = self.read(cr, uid, found, fields, context=context)
users.sort(key=lambda obj: found.index(obj['id']))
return users
def im_connect(self, cr, uid, uuid=None, context=None):
assert_uuid(uuid)
@ -308,7 +328,7 @@ class im_user(osv.osv):
'im_last_received': fields.integer(string="Instant Messaging Last Received Message"),
'im_last_status': fields.boolean(strint="Instant Messaging Last Status"),
'im_last_status_update': fields.datetime(string="Instant Messaging Last Status Update"),
'im_status': fields.function(_im_status, string="Instant Messaging Status", type='boolean'),
'im_status': fields.function(_im_status, string="Instant Messaging Status", type='boolean', fnct_search=_status_search),
}
_defaults = {

Binary file not shown.

Binary file not shown.

View File

@ -259,7 +259,7 @@ function declare($, _, openerp) {
_.each(messages, function(message) {
if (! message.technical) {
defs.push(self.activate_session(message.session_id[0]).then(function(conv) {
received = true;
received = self.my_id !== message.from_id[0];
return conv.received_message(message);
}));
} else {
@ -268,12 +268,13 @@ function declare($, _, openerp) {
defs.push($.when(im_common.technical_messages_handlers[json.type](self, message)));
}
});
if (! this.get("window_focus") && received) {
this.set("waiting_messages", this.get("waiting_messages") + messages.length);
this.ting.play();
this.create_ting();
}
return $.when.apply($, defs);
return $.when.apply($, defs).then(function(){
if (! self.get("window_focus") && received) {
self.set("waiting_messages", self.get("waiting_messages") + messages.length);
self.ting.play();
self.create_ting();
}
});
},
calc_positions: function() {
var current = this.get("right_offset");
@ -520,7 +521,7 @@ function declare($, _, openerp) {
txt += _.escape(str.slice(last, result.index));
last = url_regex.lastIndex;
var url = _.escape(result[0]);
txt += '<a href="' + url + '">' + url + '</a>';
txt += '<a href="' + url + '" target="_blank">' + url + '</a>';
}
txt += _.escape(str.slice(last, str.length));
return txt;

View File

@ -252,10 +252,9 @@ class mail_thread(osv.AbstractModel):
new = set(command[2])
# remove partners that are no longer followers
self.message_unsubscribe(cr, uid, [id], list(old-new))
self.message_unsubscribe(cr, uid, [id], list(old-new), context=context)
# add new followers
self.message_subscribe(cr, uid, [id], list(new-old))
self.message_subscribe(cr, uid, [id], list(new-old), context=context)
def _search_followers(self, cr, uid, obj, name, args, context):
"""Search function for message_follower_ids
@ -291,7 +290,7 @@ class mail_thread(osv.AbstractModel):
'message_is_follower': fields.function(_get_followers, type='boolean',
fnct_search=_search_is_follower, string='Is a Follower', multi='_get_followers,'),
'message_follower_ids': fields.function(_get_followers, fnct_inv=_set_followers,
fnct_search=_search_followers, type='many2many',
fnct_search=_search_followers, type='many2many', priority=-10,
obj='res.partner', string='Followers', multi='_get_followers'),
'message_ids': fields.one2many('mail.message', 'res_id',
domain=lambda self: [('model', '=', self._name)],
@ -344,16 +343,22 @@ class mail_thread(osv.AbstractModel):
if context is None:
context = {}
thread_id = super(mail_thread, self).create(cr, uid, values, context=context)
# subscribe uid unless asked not to
if not context.get('mail_create_nosubscribe'):
pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid).partner_id.id
message_follower_ids = values.get('message_follower_ids') or [] # webclient can send None or False
message_follower_ids.append([4, pid])
values['message_follower_ids'] = message_follower_ids
# add operation to ignore access rule checking for subscription
context_operation = dict(context, operation='create')
else:
context_operation = context
thread_id = super(mail_thread, self).create(cr, uid, values, context=context_operation)
# automatic logging unless asked not to (mainly for various testing purpose)
if not context.get('mail_create_nolog'):
self.message_post(cr, uid, thread_id, body=_('%s created') % (self._description), context=context)
# subscribe uid unless asked not to
if not context.get('mail_create_nosubscribe'):
self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
# auto_subscribe: take values and defaults into account
create_values = dict(values)
for key, val in context.iteritems():
@ -1115,11 +1120,23 @@ class mail_thread(osv.AbstractModel):
alternative = True
if part.get_content_maintype() == 'multipart':
continue # skip container
filename = part.get_filename() # None if normal part
# part.get_filename returns decoded value if able to decode, coded otherwise.
# original get_filename is not able to decode iso-8859-1 (for instance).
# therefore, iso encoded attachements are not able to be decoded properly with get_filename
# code here partially copy the original get_filename method, but handle more encoding
filename=part.get_param('filename', None, 'content-disposition')
if not filename:
filename=part.get_param('name', None)
if filename:
if isinstance(filename, tuple):
# RFC2231
filename=email.utils.collapse_rfc2231_value(filename).strip()
else:
filename=decode(filename)
encoding = part.get_content_charset() # None if attachment
# 1) Explicit Attachments -> attachments
if filename or part.get('content-disposition', '').strip().startswith('attachment'):
attachments.append((decode(filename) or 'attachment', part.get_payload(decode=True)))
attachments.append((filename or 'attachment', part.get_payload(decode=True)))
continue
# 2) text/plain -> <pre/>
if part.get_content_type() == 'text/plain' and (not alternative or not body):
@ -1532,20 +1549,26 @@ class mail_thread(osv.AbstractModel):
def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None):
""" Add partners to the records followers. """
if context is None:
context = {}
mail_followers_obj = self.pool.get('mail.followers')
subtype_obj = self.pool.get('mail.message.subtype')
user_pid = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
if set(partner_ids) == set([user_pid]):
try:
self.check_access_rights(cr, uid, 'read')
except (osv.except_osv, orm.except_orm):
return
if context.get('operation', '') != 'create':
try:
self.check_access_rights(cr, uid, 'read')
self.check_access_rule(cr, uid, ids, 'read')
except (osv.except_osv, orm.except_orm):
return False
else:
self.check_access_rights(cr, uid, 'write')
self.check_access_rule(cr, uid, ids, 'write')
existing_pids_dict = {}
fol_ids = mail_followers_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)])
fol_ids = mail_followers_obj.search(cr, SUPERUSER_ID, ['&', '&', ('res_model', '=', self._name), ('res_id', 'in', ids), ('partner_id', 'in', partner_ids)])
for fol in mail_followers_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context):
existing_pids_dict.setdefault(fol.res_id, set()).add(fol.partner_id.id)
@ -1587,8 +1610,10 @@ class mail_thread(osv.AbstractModel):
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
if set(partner_ids) == set([user_pid]):
self.check_access_rights(cr, uid, 'read')
self.check_access_rule(cr, uid, ids, 'read')
else:
self.check_access_rights(cr, uid, 'write')
self.check_access_rule(cr, uid, ids, 'write')
fol_obj = self.pool['mail.followers']
fol_ids = fol_obj.search(
cr, SUPERUSER_ID, [

View File

@ -1438,7 +1438,7 @@ openerp.mail = function (session) {
message_fetch: function (replace_domain, replace_context, ids, callback) {
return this.ds_message.call('message_read', [
// ids force to read
ids == false ? undefined : ids,
ids === false ? undefined : ids,
// domain + additional
(replace_domain ? replace_domain : this.domain),
// ids allready loaded
@ -1814,7 +1814,7 @@ openerp.mail = function (session) {
if ('display_log_button' in this.options) {
this.node.params.display_log_button = this.options.display_log_button;
}
this.domain = this.node.params && this.node.params.domain || [];
this.domain = (this.node.params && this.node.params.domain) || (this.field && this.field.domain) || [];
if (!this.ParentViewManager.is_action_enabled('edit')) {
this.node.params.show_link = false;

View File

@ -5,7 +5,7 @@
<!-- Top menu item -->
<menuitem name="Marketing"
id="base.marketing_menu"
groups="marketing.group_marketing_user,marketing.group_marketing_manager"
groups="base.group_user"
sequence="85"/>
<record id="view_crm_lead_form" model="ir.ui.view">

View File

@ -16,8 +16,6 @@
</data>
<data>
<menuitem name="Configuration" id="menu_marketing_configuration" parent="base.marketing_menu" sequence="1"/>
<!-- Marketing Campaign -->
<record id="act_marketing_campaing_segment_opened" model="ir.actions.act_window">
@ -177,7 +175,7 @@
</field>
</record>
<menuitem name="Campaigns" id="menu_marketing_campaign" parent="base.marketing_menu"/>
<menuitem name="Campaigns" id="menu_marketing_campaign" parent="base.marketing_menu" groups="marketing.group_marketing_user,marketing.group_marketing_manager"/>
<menuitem id="menu_marketing_campaign_form" parent="menu_marketing_campaign" action="action_marketing_campaign_form" sequence="30"/>
<!-- Marketing Segments -->

View File

@ -358,7 +358,7 @@
</record>
<!-- Top menu item -->
<menuitem name="Marketing" id="base.marketing_menu" sequence="85"/>
<menuitem name="Marketing" id="base.marketing_menu" sequence="85" groups="base.group_user"/>
<!-- Add in marketing -->
<menuitem name="Mass Mailing" id="mass_mailing_campaign"

View File

@ -1,6 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,,1,1,1,0
access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,base.group_user,1,1,1,0
access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,,1,1,1,0
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,base.group_user,1,1,1,0
access_mass_mailing_system,mail.mass_mailing.system,model_mail_mass_mailing,base.group_system,1,1,1,1
access_mail_mail_statistics,mail.mail.statistics,model_mail_mail_statistics,,1,1,1,1
access_mail_mail_statistics,mail.mail.statistics,model_mail_mail_statistics,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mass_mailing_campaign mail.mass_mailing.campaign model_mail_mass_mailing_campaign base.group_user 1 1 1 0
3 access_mass_mailing_campaign_system mail.mass_mailing.campaign.system model_mail_mass_mailing_campaign base.group_system 1 1 1 1
4 access_mass_mailing mail.mass_mailing model_mail_mass_mailing base.group_user 1 1 1 0
5 access_mass_mailing_system mail.mass_mailing.system model_mail_mass_mailing base.group_system 1 1 1 1
6 access_mail_mail_statistics mail.mail.statistics model_mail_mail_statistics base.group_user 1 1 1 1

View File

@ -43,15 +43,18 @@ class MailComposeMessage(osv.TransientModel):
email mass mailing. """
res = super(MailComposeMessage, self).get_mail_values(cr, uid, wizard, res_ids, context=context)
if wizard.composition_mode == 'mass_mail' and wizard.mass_mailing_campaign_id: # TODO: which kind of mass mailing ?
current_date = fields.datetime.now()
mass_mailing_id = self.pool['mail.mass_mailing'].create(
cr, uid, {
'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
'name': '%s-%s' % (wizard.mass_mailing_campaign_id.name, current_date),
'date': current_date,
'domain': wizard.active_domain,
'template_id': wizard.template_id and wizard.template_id.id or False,
}, context=context)
if wizard.mass_mailing_id:
mass_mailing_id = wizard.mass_mailing_id.id
else:
current_date = fields.datetime.now()
mass_mailing_id = self.pool['mail.mass_mailing'].create(
cr, uid, {
'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
'name': '%s-%s' % (wizard.mass_mailing_campaign_id.name, current_date),
'date': current_date,
'domain': wizard.active_domain,
'template_id': wizard.template_id and wizard.template_id.id or False,
}, context=context)
for res_id in res_ids:
res[res_id]['statistics_ids'] = [(0, 0, {
'model': wizard.model,

View File

@ -33,11 +33,6 @@ class procurement_order(osv.osv):
'production_id': fields.many2one('mrp.production', 'Manufacturing Order'),
}
def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, context=None):
result = super(procurement_order, self)._prepare_order_line_procurement(cr, uid, order, line, move_id, date_planned, context)
result['property_ids'] = [(6, 0, [x.id for x in line.property_ids])]
return result
def check_produce_product(self, cr, uid, procurement, context=None):
''' Depict the capacity of the procurement workflow to produce products (not services)'''
return True
@ -126,6 +121,3 @@ class procurement_order(osv.osv):
for procurement in self.browse(cr, uid, ids, context=context):
body = _("Manufacturing Order <em>%s</em> created.") % ( procurement.production_id.name,)
self.message_post(cr, uid, [procurement.id], body=body, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -23,7 +23,7 @@ import logging
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.tools import email_re
from openerp.tools import email_split
from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
@ -53,8 +53,8 @@ http://www.openerp.com
def extract_email(email):
""" extract the email address from a user-friendly email address """
m = email_re.search(email or "")
return m and m.group(0) or ""
addresses = email_split(email)
return addresses[0] if addresses else ''

View File

@ -51,6 +51,12 @@ class TestPortalProjectBase(TestProjectBase):
'alias_name': 'donovan',
'groups_id': [(6, 0, [self.group_anonymous_id])]
})
self.user_manager_id = self.res_users.create(cr, uid, {
'name': 'Eustache Manager',
'login': 'eustache',
'alias_name': 'eustache',
'groups_id': [(6, 0, [self.group_project_manager_id])]
})
# Test 'Pigs' project
self.project_pigs_id = self.project_project.create(cr, uid, {
@ -220,7 +226,7 @@ class TestPortalProject(TestPortalProjectBase):
# Data: subscribe Alfred, Chell and Donovan as follower
self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_projectuser_id, self.user_portal_id, self.user_anonymous_id])
self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id, self.user_projectuser_id])
self.project_task.message_subscribe_users(cr, self.user_manager_id, [self.task_1_id, self.task_3_id], [self.user_portal_id, self.user_projectuser_id])
# Do: Alfred reads project -> ok (follower ok followers)
self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])

View File

@ -159,7 +159,7 @@ class TestPortalIssue(TestPortalProjectBase):
# Data: subscribe Alfred, Chell and Donovan as follower
self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_projectuser_id, self.user_portal_id, self.user_anonymous_id])
self.project_issue.message_subscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id, self.user_projectuser_id])
self.project_issue.message_subscribe_users(cr, self.user_manager_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id, self.user_projectuser_id])
# Do: Alfred reads project -> ok (follower ok followers)
# Test: followed + assigned issues visible

View File

@ -46,9 +46,15 @@ class project_task_type(osv.osv):
'there are no records in that stage to display.'),
}
def _get_default_project_ids(self, cr, uid, ctx={}):
project_id = self.pool['project.task']._get_default_project_id(cr, uid, context=ctx)
if project_id:
return [project_id]
return None
_defaults = {
'sequence': 1,
'project_ids': lambda self, cr, uid, ctx=None: self.pool['project.task']._get_default_project_id(cr, uid, context=ctx),
'project_ids': _get_default_project_ids,
}
_order = 'sequence'
@ -64,7 +70,7 @@ class project(osv.osv):
""" Installation hook: aliases, project.project """
# create aliases for all projects and avoid constraint errors
alias_context = dict(context, alias_model_name='project.task')
self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(project, self)._auto_init,
return self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(project, self)._auto_init,
'project.task', self._columns['alias_id'], 'id', alias_prefix='project+', alias_defaults={'project_id':'id'}, context=alias_context)
def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):

View File

@ -21,7 +21,7 @@
import time
import pytz
from openerp import SUPERUSER_ID
from openerp import SUPERUSER_ID, workflow
from datetime import datetime
from dateutil.relativedelta import relativedelta
from operator import attrgetter
@ -245,7 +245,7 @@ class purchase_order(osv.osv):
_name = "purchase.order"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_description = "Purchase Order"
_order = "name desc"
_order = 'date_order desc, id desc'
def create(self, cr, uid, vals, context=None):
if vals.get('name','/')=='/':
@ -1290,6 +1290,7 @@ class account_invoice(osv.Model):
po_ids = purchase_order_obj.search(cr, user_id, [('invoice_ids', 'in', ids)], context=context)
for po_id in po_ids:
purchase_order_obj.message_post(cr, user_id, po_id, body=_("Invoice received"), context=context)
workflow.trg_write(uid, 'purchase.order', po_id, cr)
return res
def confirm_paid(self, cr, uid, ids, context=None):

View File

@ -147,7 +147,7 @@
<record id="trans_router_invoice_no_order" model="workflow.transition">
<field name="act_from" ref="act_router"/>
<field name="act_to" ref="act_invoice_end"/>
<field name="condition">invoice_method&lt;&gt;'order' and invoiced</field>
<field name="condition">invoice_method&lt;&gt;'order'</field>
</record>
<record id="trans_except_picking_picking_done" model="workflow.transition">
<field name="act_from" ref="act_except_picking"/>
@ -200,6 +200,7 @@
<record id="trans_invoice_end_done" model="workflow.transition">
<field name="act_from" ref="act_invoice_end"/>
<field name="act_to" ref="act_done"/>
<field name="condition">invoiced</field>
</record>
<!-- Procurement -->

View File

@ -238,7 +238,7 @@ class sale_order(osv.osv):
_sql_constraints = [
('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
]
_order = 'name desc'
_order = 'date_order desc, id desc'
# Form filling
def unlink(self, cr, uid, ids, context=None):

View File

@ -88,4 +88,10 @@ class mrp_production(osv.osv):
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
class sale_order(osv.Model):
_inherit ='sale.order'
def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, context=None):
result = super(sale_order, self)._prepare_order_line_procurement(cr, uid, order, line, move_id, date_planned, context)
result['property_ids'] = [(6, 0, [x.id for x in line.property_ids])]
return result

View File

@ -124,10 +124,22 @@ class stock_picking(osv.osv):
def _invoice_hook(self, cursor, user, picking, invoice_id):
sale_obj = self.pool.get('sale.order')
order_line_obj = self.pool.get('sale.order.line')
invoice_obj = self.pool.get('account.invoice')
invoice_line_obj = self.pool.get('account.invoice.line')
if picking.sale_id:
sale_obj.write(cursor, user, [picking.sale_id.id], {
'invoice_ids': [(4, invoice_id)],
})
})
for sale_line in picking.sale_id.order_line:
if sale_line.product_id.type == 'service' and not sale_line.invoiced:
vals = order_line_obj._prepare_order_line_invoice_line(cursor, user, sale_line, False)
vals['invoice_id'] = invoice_id
invoice_line_id = invoice_line_obj.create(cursor, user, vals)
order_line_obj.write(cursor, user, [sale_line.id], {
'invoice_lines': [(6, 0, [invoice_line_id])],
})
invoice_obj.button_compute(cursor, user, [invoice_id])
return super(stock_picking, self)._invoice_hook(cursor, user, picking, invoice_id)
def action_done(self, cr, uid, ids, context=None):

View File

@ -3,6 +3,16 @@
-
!context
uid: 'res_sale_stock_salesman'
-
Add SO line with service type product in SO to check flow which contain service type product in SO(BUG#1167330).
-
!record {model: sale.order.line, id: sale_order_1}:
name: 'On Site Assistance'
product_id: product.product_product_2
product_uom_qty: 1.0
product_uom: 1
price_unit: 150.0
order_id: sale.sale_order_6
-
First I check the total amount of the Quotation before Approved.
-
@ -75,7 +85,7 @@
assert picking.partner_id.id == sale_order.partner_shipping_id.id,"Shipping Address is not correspond with sale order."
assert picking.note == sale_order.note,"Note is not correspond with sale order."
assert picking.invoice_state == (sale_order.order_policy=='picking' and '2binvoiced') or 'none',"Invoice policy is not correspond with sale order."
assert len(picking.move_lines) == len(sale_order.order_line), "Total move of delivery order are not corresposning with total sale order lines."
assert len(picking.move_lines) == len(sale_order.order_line) - 1, "Total move of delivery order are not corresposning with total sale order lines."
location_id = sale_order.warehouse_id.lot_stock_id.id
output_id = sale_order.warehouse_id.lot_output_id.id
for move in picking.move_lines:

View File

@ -144,7 +144,7 @@ class stock_partial_picking(osv.osv_memory):
def _partial_move_for(self, cr, uid, move):
partial_move = {
'product_id' : move.product_id.id,
'quantity' : move.product_qty if move.state == 'assigned' else 0,
'quantity' : move.product_qty if move.state == 'assigned' or move.picking_id.type == 'in' else 0,
'product_uom' : move.product_uom.id,
'prodlot_id' : move.prodlot_id.id,
'move_id' : move.id,