diff --git a/addons/account_test/i18n/de.po b/addons/account_test/i18n/de.po
new file mode 100644
index 00000000000..157223be184
--- /dev/null
+++ b/addons/account_test/i18n/de.po
@@ -0,0 +1,241 @@
+# German translation for openobject-addons
+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2014.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:05+0000\n"
+"PO-Revision-Date: 2014-05-13 09:39+0000\n"
+"Last-Translator: Claudia Haida \n"
+"Language-Team: German \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2014-05-14 05:45+0000\n"
+"X-Generator: Launchpad (build 17002)\n"
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid ""
+"Code should always set a variable named `result` with the result of your "
+"test, that can be a list or\n"
+"a dictionary. If `result` is an empty list, it means that the test was "
+"succesful. Otherwise it will\n"
+"try to translate and print what is inside `result`.\n"
+"\n"
+"If the result of your test is a dictionary, you can set a variable named "
+"`column_order` to choose in\n"
+"what order you want to print `result`'s content.\n"
+"\n"
+"Should you need them, you can also use the following variables into your "
+"code:\n"
+" * cr: cursor to the database\n"
+" * uid: ID of the current user\n"
+"\n"
+"In any ways, the code must be legal python statements with correct "
+"indentation (if needed).\n"
+"\n"
+"Example: \n"
+" sql = '''SELECT id, name, ref, date\n"
+" FROM account_move_line \n"
+" WHERE account_id IN (SELECT id FROM account_account WHERE type "
+"= 'view')\n"
+" '''\n"
+" cr.execute(sql)\n"
+" result = cr.dictfetchall()"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_02
+msgid "Test 2: Opening a fiscal year"
+msgstr "Test2: Eröffnung eines Geschäftsjahres"
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_05
+msgid ""
+"Check that reconciled invoice for Sales/Purchases has reconciled entries for "
+"Payable and Receivable Accounts"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_03
+msgid ""
+"Check if movement lines are balanced and have the same date and period"
+msgstr ""
+
+#. module: account_test
+#: field:accounting.assert.test,name:0
+msgid "Test Name"
+msgstr ""
+
+#. module: account_test
+#: report:account.test.assert.print:0
+msgid "Accouting tests on"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_01
+msgid "Test 1: General balance"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_06
+msgid "Check that paid/reconciled invoices are not in 'Open' state"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_05_2
+msgid ""
+"Check that reconciled account moves, that define Payable and Receivable "
+"accounts, are belonging to reconciled invoices"
+msgstr ""
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid "Tests"
+msgstr ""
+
+#. module: account_test
+#: field:accounting.assert.test,desc:0
+msgid "Test Description"
+msgstr ""
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid "Description"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_06_1
+msgid "Check that there's no move for any account with « View » account type"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_08
+msgid "Test 9 : Accounts and partners on account moves"
+msgstr ""
+
+#. module: account_test
+#: model:ir.actions.act_window,name:account_test.action_accounting_assert
+#: model:ir.actions.report.xml,name:account_test.account_assert_test_report
+#: model:ir.ui.menu,name:account_test.menu_action_license
+msgid "Accounting Tests"
+msgstr ""
+
+#. module: account_test
+#: code:addons/account_test/report/account_test_report.py:74
+#, python-format
+msgid "The test was passed successfully"
+msgstr ""
+
+#. module: account_test
+#: field:accounting.assert.test,active:0
+msgid "Active"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_06
+msgid "Test 6 : Invoices status"
+msgstr ""
+
+#. module: account_test
+#: model:ir.model,name:account_test.model_accounting_assert_test
+msgid "accounting.assert.test"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_05
+msgid ""
+"Test 5.1 : Payable and Receivable accountant lines of reconciled invoices"
+msgstr ""
+
+#. module: account_test
+#: field:accounting.assert.test,code_exec:0
+msgid "Python code"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_07
+msgid ""
+"Check on bank statement that the Closing Balance = Starting Balance + sum of "
+"statement lines"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_07
+msgid "Test 8 : Closing balance on bank statements"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_03
+msgid "Test 3: Movement lines"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_05_2
+msgid "Test 5.2 : Reconcilied invoices and Payable/Receivable accounts"
+msgstr ""
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid "Expression"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_04
+msgid "Test 4: Totally reconciled mouvements"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_04
+msgid "Check if the totally reconciled movements are balanced"
+msgstr ""
+
+#. module: account_test
+#: field:accounting.assert.test,sequence:0
+msgid "Sequence"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_02
+msgid ""
+"Check if the balance of the new opened fiscal year matches with last year's "
+"balance"
+msgstr ""
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid "Python Code"
+msgstr ""
+
+#. module: account_test
+#: model:ir.actions.act_window,help:account_test.action_accounting_assert
+msgid ""
+"
\n"
+" Click to create Accounting Test.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_01
+msgid "Check the balance: Debit sum = Credit sum"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_08
+msgid "Check that general accounts and partners on account moves are active"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_06_1
+msgid "Test 7: « View » account type"
+msgstr ""
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid "Code Help"
+msgstr ""
diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py
index c4b584100b6..46e8b024dd6 100644
--- a/addons/calendar/calendar.py
+++ b/addons/calendar/calendar.py
@@ -1,23 +1,4 @@
# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Business Applications
-# Copyright (c) 2011-2014 OpenERP S.A.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
import pytz
import re
@@ -188,7 +169,8 @@ class calendar_attendee(osv.Model):
res = cal.serialize()
return res
- def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False), template_xmlid='calendar_template_meeting_invitation', context=None):
+ def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False),
+ template_xmlid='calendar_template_meeting_invitation', context=None):
"""
Send mail for event invitation to event attendees.
@param email_from: email address for user sending the mail
@@ -273,7 +255,8 @@ class calendar_attendee(osv.Model):
meeting_obj = self.pool['calendar.event']
res = self.write(cr, uid, ids, {'state': 'accepted'}, context)
for attendee in self.browse(cr, uid, ids, context=context):
- meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)), subtype="calendar.subtype_invitation", context=context)
+ meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)),
+ subtype="calendar.subtype_invitation", context=context)
return res
@@ -1410,7 +1393,7 @@ class calendar_event(osv.Model):
new_args.append(new_arg)
if not context.get('virtual_id', True):
- return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, context=context, count=count)
+ return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, count=count, context=context)
# offset, limit, order and count must be treated separately as we may need to deal with virtual ids
res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=None, context=context, count=False)
@@ -1535,17 +1518,17 @@ class calendar_event(osv.Model):
if (values.get('start_date') or values.get('start_datetime', False)) and values.get('active', True):
the_id = new_id or (ids and int(ids[0]))
+ if the_id:
+ if attendees_create:
+ attendees_create = attendees_create[the_id]
+ mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids']))
+ else:
+ mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids]
- if attendees_create:
- attendees_create = attendees_create[the_id]
- mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids']))
- else:
- mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids]
-
- if mail_to_ids:
- current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
- if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context):
- self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"), subtype="calendar.subtype_invitation", context=context)
+ if mail_to_ids:
+ current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
+ if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context):
+ self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"), subtype="calendar.subtype_invitation", context=context)
return res or True and False
def create(self, cr, uid, vals, context=None):
@@ -1624,7 +1607,7 @@ class calendar_event(osv.Model):
continue
if r['class'] == 'private':
for f in r.keys():
- if f not in ('id', 'allday', 'start', 'stop', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date'):
+ if f not in ('id', 'allday', 'start', 'stop', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date', 'rrule'):
if isinstance(r[f], list):
r[f] = []
else:
diff --git a/addons/document/document_view.xml b/addons/document/document_view.xml
index b735d2ce4a4..3376655d29d 100644
--- a/addons/document/document_view.xml
+++ b/addons/document/document_view.xml
@@ -243,7 +243,7 @@
ir.actions.act_windowir.attachmentform
- kanban,form
+ kanban,tree,form
Click to create a new document.
diff --git a/addons/edi/i18n/ta.po b/addons/edi/i18n/ta.po
new file mode 100644
index 00000000000..7f0570794ac
--- /dev/null
+++ b/addons/edi/i18n/ta.po
@@ -0,0 +1,87 @@
+# Tamil translation for openobject-addons
+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2014.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:05+0000\n"
+"PO-Revision-Date: 2014-05-13 08:27+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Tamil \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2014-05-14 05:45+0000\n"
+"X-Generator: Launchpad (build 17002)\n"
+
+#. module: edi
+#. openerp-web
+#: code:addons/edi/static/src/js/edi.js:67
+#, python-format
+msgid "Reason:"
+msgstr ""
+
+#. module: edi
+#. openerp-web
+#: code:addons/edi/static/src/js/edi.js:60
+#, python-format
+msgid "The document has been successfully imported!"
+msgstr ""
+
+#. module: edi
+#. openerp-web
+#: code:addons/edi/static/src/js/edi.js:65
+#, python-format
+msgid "Sorry, the document could not be imported."
+msgstr ""
+
+#. module: edi
+#: model:ir.model,name:edi.model_res_company
+msgid "Companies"
+msgstr ""
+
+#. module: edi
+#: model:ir.model,name:edi.model_res_currency
+msgid "Currency"
+msgstr ""
+
+#. module: edi
+#. openerp-web
+#: code:addons/edi/static/src/js/edi.js:71
+#, python-format
+msgid "Document Import Notification"
+msgstr ""
+
+#. module: edi
+#: code:addons/edi/models/edi.py:130
+#, python-format
+msgid "Missing application."
+msgstr ""
+
+#. module: edi
+#: code:addons/edi/models/edi.py:131
+#, python-format
+msgid ""
+"The document you are trying to import requires the OpenERP `%s` application. "
+"You can install it by connecting as the administrator and opening the "
+"configuration assistant."
+msgstr ""
+
+#. module: edi
+#: code:addons/edi/models/edi.py:47
+#, python-format
+msgid "'%s' is an invalid external ID"
+msgstr ""
+
+#. module: edi
+#: model:ir.model,name:edi.model_res_partner
+msgid "Partner"
+msgstr ""
+
+#. module: edi
+#: model:ir.model,name:edi.model_edi_edi
+msgid "EDI Subsystem"
+msgstr ""
diff --git a/addons/google_account/google_account.py b/addons/google_account/google_account.py
index d42b0b67e59..7ffa4f95f48 100644
--- a/addons/google_account/google_account.py
+++ b/addons/google_account/google_account.py
@@ -1,28 +1,12 @@
# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
+import openerp
+from openerp.http import request
from openerp.osv import osv
from openerp import SUPERUSER_ID
from openerp.tools.translate import _
-from openerp.addons.web.http import request
+from datetime import datetime
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
import werkzeug.urls
import urllib2
@@ -31,6 +15,7 @@ import simplejson
import logging
_logger = logging.getLogger(__name__)
+
class google_service(osv.osv_memory):
_name = 'google.service'
@@ -48,7 +33,8 @@ class google_service(osv.osv_memory):
req = urllib2.Request("https://accounts.google.com/o/oauth2/token", data, headers)
content = urllib2.urlopen(req).read()
except urllib2.HTTPError:
- raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired"), context=context)
+ error_msg = "Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired"
+ raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
content = simplejson.loads(content)
return content.get('refresh_token')
@@ -65,8 +51,8 @@ class google_service(osv.osv_memory):
uri = 'https://accounts.google.com/o/oauth2/auth?%s' % werkzeug.url_encode(params)
return uri
- #If no scope is passed, we use service by default to get a default scope
- def _get_authorize_uri(self, cr, uid, from_url, service, scope = False, context=None):
+ # If no scope is passed, we use service by default to get a default scope
+ def _get_authorize_uri(self, cr, uid, from_url, service, scope=False, context=None):
""" This method return the url needed to allow this instance of OpenErp to access to the scope of gmail specified as parameters """
state_obj = dict(d=cr.dbname, s=service, f=from_url)
@@ -76,11 +62,11 @@ class google_service(osv.osv_memory):
params = {
'response_type': 'code',
'client_id': client_id,
- 'state' : simplejson.dumps(state_obj),
+ 'state': simplejson.dumps(state_obj),
'scope': scope or 'https://www.googleapis.com/auth/%s' % (service,),
'redirect_uri': base_url + '/google_account/authentication',
- 'approval_prompt':'force',
- 'access_type':'offline'
+ 'approval_prompt': 'force',
+ 'access_type': 'offline'
}
uri = self.get_uri_oauth(a='auth') + "?%s" % werkzeug.url_encode(params)
@@ -96,25 +82,24 @@ class google_service(osv.osv_memory):
'code': authorize_code,
'client_id': client_id,
'client_secret': client_secret,
- 'grant_type' : 'authorization_code',
+ 'grant_type': 'authorization_code',
'redirect_uri': base_url + '/google_account/authentication'
}
headers = {"content-type": "application/x-www-form-urlencoded"}
try:
+ uri = self.get_uri_oauth(a='token')
data = werkzeug.url_encode(params)
- req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers)
- content = urllib2.urlopen(req).read()
- res = simplejson.loads(content)
- except urllib2.HTTPError,e:
- raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during your token generation. Maybe your Authorization Code is invalid"), context=context)
+ st, res = self._do_request(cr, uid, uri, params=data, headers=headers, type='POST', preuri='', context=context)
+ except urllib2.HTTPError:
+ error_msg = "Something went wrong during your token generation. Maybe your Authorization Code is invalid"
+ raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
return res
- def _refresh_google_token_json(self, cr, uid, refresh_token, service, context=None): #exchange_AUTHORIZATION vs Token (service = calendar)
+ def _refresh_google_token_json(self, cr, uid, refresh_token, service, context=None): # exchange_AUTHORIZATION vs Token (service = calendar)
res = False
- base_url = self.get_base_url(cr, uid, context)
client_id = self.get_client_id(cr, uid, service, context)
client_secret = self.get_client_secret(cr, uid, service, context)
@@ -122,60 +107,79 @@ class google_service(osv.osv_memory):
'refresh_token': refresh_token,
'client_id': client_id,
'client_secret': client_secret,
- 'grant_type' : 'refresh_token'
+ 'grant_type': 'refresh_token',
}
headers = {"content-type": "application/x-www-form-urlencoded"}
try:
- data = werkzeug.url_encode(params)
- req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers)
- content = urllib2.urlopen(req).read()
- res = simplejson.loads(content)
- except urllib2.HTTPError:
- raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired"), context=context)
+ uri = self.get_uri_oauth(a='token')
+ data = werkzeug.url_encode(params)
+ st, res = self._do_request(cr, uid, uri, params=data, headers=headers, type='POST', preuri='', context=context)
+ except urllib2.HTTPError, e:
+ if e.code == 400: # invalid grant
+ registry = openerp.modules.registry.RegistryManager.get(request.session.db)
+ with registry.cursor() as cur:
+ self.pool['res.users'].write(cur, uid, [uid], {'google_%s_rtoken' % service: False}, context=context)
+ error_key = simplejson.loads(e.read()).get("error", "nc")
+ _logger.exception("Bad google request : %s !" % error_key)
+ error_msg = "Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired [%s]" % error_key
+ raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
return res
+ def _do_request(self, cr, uid, uri, params={}, headers={}, type='POST', preuri="https://www.googleapis.com", context=None):
+ if context is None:
+ context = {}
- def _do_request(self,cr,uid,uri,params={},headers={},type='POST', context=None):
- _logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri,type,headers,werkzeug.url_encode(params) if type =='GET' else params))
- res = False
+ """ Return a tuple ('HTTP_CODE', 'HTTP_RESPONSE') """
+ _logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri, type, headers, werkzeug.url_encode(params) if type == 'GET' else params))
+ status = 418
+ response = ""
try:
if type.upper() == 'GET' or type.upper() == 'DELETE':
data = werkzeug.url_encode(params)
- req = urllib2.Request(self.get_uri_api() + uri + "?" + data)
- elif type.upper() == 'POST' or type.upper() == 'PATCH' or type.upper() == 'PUT':
- req = urllib2.Request(self.get_uri_api() + uri, params, headers)
+ req = urllib2.Request(preuri + uri + "?" + data)
+ elif type.upper() == 'POST' or type.upper() == 'PATCH' or type.upper() == 'PUT':
+ req = urllib2.Request(preuri + uri, params, headers)
else:
raise ('Method not supported [%s] not in [GET, POST, PUT, PATCH or DELETE]!' % (type))
req.get_method = lambda: type.upper()
request = urllib2.urlopen(req)
+ status = request.getcode()
- if request.getcode() == 204: #No content returned, (ex: POST calendar/event/clear)
- res = True
- elif request.getcode() == 404: #Page not found
- res = False
+ if int(status) in (204, 404): # Page not found, no response
+ response = False
else:
- content=request.read()
- res = simplejson.loads(content)
- except urllib2.HTTPError,e:
+ content = request.read()
+ response = simplejson.loads(content)
+
+ if context.get('ask_time'):
+ try:
+ date = datetime.strptime(request.headers.get('date'), "%a, %d %b %Y %H:%M:%S %Z")
+ except:
+ date = datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+ context['ask_time'] = date
+ except urllib2.HTTPError, e:
+ if e.code in (400, 401, 410):
+ raise e
+
_logger.exception("Bad google request : %s !" % e.read())
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong with your request to google"), context=context)
- return res
+ return (status, response)
def get_base_url(self, cr, uid, context=None):
- return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url',default='http://www.openerp.com?NoBaseUrl',context=context)
+ return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://www.openerp.com?NoBaseUrl', context=context)
def get_client_id(self, cr, uid, service, context=None):
- return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_id' % (service,),default=False,context=context)
+ return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_id' % (service,), default=False, context=context)
def get_client_secret(self, cr, uid, service, context=None):
- return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_secret' % (service,),default=False,context=context)
+ return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_secret' % (service,), default=False, context=context)
- def get_uri_oauth(self,a=''): #a = optional action
+ def get_uri_oauth(self, a=''): # a = optional action
return "https://accounts.google.com/o/oauth2/%s" % (a,)
def get_uri_api(self):
diff --git a/addons/google_calendar/__openerp__.py b/addons/google_calendar/__openerp__.py
index 18f2806976b..7e7a34362f6 100644
--- a/addons/google_calendar/__openerp__.py
+++ b/addons/google_calendar/__openerp__.py
@@ -25,20 +25,20 @@
'version': '1.0',
'category': 'Tools',
'description': """
-The module adds the possibility to synchronize Google Calendar with OpenERP
+The module adds the possibility to synchronize Google Calendar with OpenERP
========================================
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
- 'depends': ['google_account','calendar'],
+ 'depends': ['google_account', 'calendar'],
'qweb': ['static/src/xml/*.xml'],
'data': [
'res_config_view.xml',
'security/ir.model.access.csv',
'views/google_calendar.xml',
+ 'views/res_users.xml',
],
'demo': [],
'installable': True,
'auto_install': False,
}
-
diff --git a/addons/google_calendar/controllers/main.py b/addons/google_calendar/controllers/main.py
index 49083a79bfc..1f307133ea0 100644
--- a/addons/google_calendar/controllers/main.py
+++ b/addons/google_calendar/controllers/main.py
@@ -1,51 +1,60 @@
-import simplejson
-import urllib
-import openerp
import openerp.addons.web.http as http
from openerp.addons.web.http import request
-import openerp.addons.web.controllers.main as webmain
-from openerp.addons.web.http import SessionExpiredException
-from werkzeug.exceptions import BadRequest
-import werkzeug.utils
+
class google_calendar_controller(http.Controller):
-
+
@http.route('/google_calendar/sync_data', type='json', auth='user')
- def sync_data(self, arch, fields, model,**kw):
- """
+ def sync_data(self, arch, fields, model, **kw):
+ """
This route/function is called when we want to synchronize openERP calendar with Google Calendar
Function return a dictionary with the status : need_config_from_admin, need_auth, need_refresh, success if not calendar_event
- The dictionary may contains an url, to allow OpenERP Client to redirect user on this URL for authorization for example
+ The dictionary may contains an url, to allow OpenERP Client to redirect user on this URL for authorization for example
"""
-
+
if model == 'calendar.event':
gs_obj = request.registry['google.service']
gc_obj = request.registry['google.calendar']
-
+
# Checking that admin have already configured Google API for google synchronization !
- client_id = gs_obj.get_client_id(request.cr, request.uid,'calendar',context=kw.get('local_context'))
+ client_id = gs_obj.get_client_id(request.cr, request.uid, 'calendar', context=kw.get('local_context'))
if not client_id or client_id == '':
action = ''
- if gc_obj.can_authorize_google(request.cr,request.uid):
- dummy, action = request.registry.get('ir.model.data').get_object_reference(request.cr, request.uid, 'google_calendar', 'action_config_settings_google_calendar')
-
+ if gc_obj.can_authorize_google(request.cr, request.uid):
+ dummy, action = request.registry.get('ir.model.data').get_object_reference(request.cr, request.uid,
+ 'google_calendar', 'action_config_settings_google_calendar')
+
return {
- "status" : "need_config_from_admin",
- "url" : '',
- "action" : action
- }
-
+ "status": "need_config_from_admin",
+ "url": '',
+ "action": action
+ }
+
# Checking that user have already accepted OpenERP to access his calendar !
- if gc_obj.need_authorize(request.cr, request.uid,context=kw.get('local_context')):
- url = gc_obj.authorize_google_uri(request.cr, request.uid, from_url=kw.get('fromurl'), context=kw.get('local_context'))
+ if gc_obj.need_authorize(request.cr, request.uid, context=kw.get('local_context')):
+ url = gc_obj.authorize_google_uri(request.cr, request.uid, from_url=kw.get('fromurl'), context=kw.get('local_context'))
return {
- "status" : "need_auth",
- "url" : url
- }
-
+ "status": "need_auth",
+ "url": url
+ }
+
# If App authorized, and user access accepted, We launch the synchronization
- return gc_obj.synchronize_events(request.cr, request.uid, [], kw.get('local_context'))
-
- return { "status" : "success" }
-
+ return gc_obj.synchronize_events(request.cr, request.uid, [], context=kw.get('local_context'))
+
+ return {"status": "success"}
+
+ @http.route('/google_calendar/remove_references', type='json', auth='user')
+ def remove_references(self, model, **kw):
+ """
+ This route/function is called when we want to remove all the references between one calendar OpenERP and one Google Calendar
+ """
+ status = "NOP"
+ if model == 'calendar.event':
+ gc_obj = request.registry['google.calendar']
+ # Checking that user have already accepted OpenERP to access his calendar !
+ if gc_obj.remove_references(request.cr, request.uid, context=kw.get('local_context')):
+ status = "OK"
+ else:
+ status = "KO"
+ return {"status": status}
diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py
index 177642f091d..543c81977aa 100644
--- a/addons/google_calendar/google_calendar.py
+++ b/addons/google_calendar/google_calendar.py
@@ -1,31 +1,15 @@
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2012 OpenERP SA ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
+# -*- coding: utf-8 -*-
import operator
import simplejson
-import urllib
+import urllib2
+import openerp
from openerp import tools
from openerp import SUPERUSER_ID
-
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
+from openerp.tools.translate import _
+from openerp.http import request
from datetime import datetime, timedelta
from dateutil import parser
import pytz
@@ -35,6 +19,13 @@ import logging
_logger = logging.getLogger(__name__)
+def status_response(status, substr=False):
+ if substr:
+ return int(str(status)[0])
+ else:
+ return status_response(status, substr=True) == 2
+
+
class Meta(type):
""" This Meta class allow to define class as a structure, and so instancied variable
in __init__ to avoid to have side effect alike 'static' variable """
@@ -90,7 +81,7 @@ class SyncEvent(object):
def __getitem__(self, key):
return getattr(self, key)
- def compute_OP(self):
+ def compute_OP(self, modeFull=True):
#If event are already in Gmail and in OpenERP
if self.OE.found and self.GG.found:
#If the event has been deleted from one side, we delete on other side !
@@ -120,7 +111,6 @@ class SyncEvent(object):
else:
if not self.OE.synchro or self.OE.synchro.split('.')[0] < self.OE.update.split('.')[0]:
self.OP = Update('OE', 'Event already updated by another user, but not synchro with my google calendar')
- #import ipdb; ipdb.set_trace();
else:
self.OP = NothingToDo("", 'Not update needed')
else:
@@ -128,11 +118,13 @@ class SyncEvent(object):
# New in openERP... Create on create_events of synchronize function
elif self.OE.found and not self.GG.found:
- #Has been deleted from gmail
if self.OE.status:
- self.OP = Delete('OE', 'Removed from GOOGLE')
+ self.OP = Delete('OE', 'Update or delete from GOOGLE')
else:
- self.OP = NothingToDo("", "Already Deleted in gmail and unlinked in OpenERP")
+ if not modeFull:
+ self.OP = Delete('GG', 'Deleted from OpenERP, need to delete it from Gmail if already created')
+ else:
+ self.OP = NothingToDo("", "Already Deleted in gmail and unlinked in OpenERP")
elif self.GG.found and not self.OE.found:
tmpSrc = 'GG'
if not self.GG.status and not self.GG.isInstance:
@@ -151,11 +143,11 @@ class SyncEvent(object):
return self.__repr__()
def __repr__(self):
- myPrint = "---- A SYNC EVENT ---"
+ myPrint = "\n\n---- A SYNC EVENT ---"
myPrint += "\n ID OE: %s " % (self.OE.event and self.OE.event.id)
myPrint += "\n ID GG: %s " % (self.GG.event and self.GG.event.get('id', False))
- myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name)
- myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', False))
+ myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name.encode('utf8'))
+ myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', '').encode('utf8'))
myPrint += "\n Found OE:%5s vs GG: %5s" % (self.OE.found, self.GG.found)
myPrint += "\n Recurrence OE:%5s vs GG: %5s" % (self.OE.isRecurrence, self.GG.isRecurrence)
myPrint += "\n Instance OE:%5s vs GG: %5s" % (self.OE.isInstance, self.GG.isInstance)
@@ -207,15 +199,15 @@ class google_calendar(osv.AbstractModel):
STR_SERVICE = 'calendar'
_name = 'google.%s' % STR_SERVICE
- def generate_data(self, cr, uid, event, context=None):
+ def generate_data(self, cr, uid, event, isCreating=False, context=None):
if event.allday:
- start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0]
- end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=event.duration), context=context).isoformat('T').split('T')[0]
+ start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0]
+ final_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=event.duration) + timedelta(days=isCreating and 1 or 0), context=context).isoformat('T').split('T')[0]
type = 'date'
vstype = 'dateTime'
else:
- start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
- end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
+ start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
+ final_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.stop, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
type = 'dateTime'
vstype = 'date'
attendee_list = []
@@ -235,7 +227,7 @@ class google_calendar(osv.AbstractModel):
'timeZone': 'UTC'
},
"end": {
- type: end_date,
+ type: final_date,
vstype: None,
'timeZone': 'UTC'
},
@@ -256,10 +248,9 @@ class google_calendar(osv.AbstractModel):
def create_an_event(self, cr, uid, event, context=None):
gs_pool = self.pool['google.service']
+ data = self.generate_data(cr, uid, event, isCreating=True, context=context)
- data = self.generate_data(cr, uid, event, context=context)
-
- url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary', urllib.quote('id,updated'), self.get_token(cr, uid, context))
+ url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary', urllib2.quote('id,updated'), self.get_token(cr, uid, context))
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
data_json = simplejson.dumps(data)
@@ -276,38 +267,89 @@ class google_calendar(osv.AbstractModel):
return gs_pool._do_request(cr, uid, url, params, headers, type='DELETE', context=context)
- def get_event_dict(self, cr, uid, token=False, nextPageToken=False, context=None):
+ def get_calendar_primary_id(self, cr, uid, context=None):
+ params = {
+ 'fields': 'id',
+ 'access_token': self.get_token(cr, uid, context)
+ }
+ headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
+
+ url = "/calendar/v3/calendars/primary"
+
+ try:
+ st, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context)
+ except Exception, e:
+
+ if (e.code == 401): # Token invalid / Acces unauthorized
+ error_msg = "Your token is invalid or has been revoked !"
+
+ registry = openerp.modules.registry.RegistryManager.get(request.session.db)
+ with registry.cursor() as cur:
+ self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_token': False, 'google_calendar_token_validity': False}, context=context)
+
+ raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
+ raise
+
+ return status_response(st) and content['id'] or False
+
+ def get_event_synchro_dict(self, cr, uid, lastSync=False, token=False, nextPageToken=False, context=None):
if not token:
token = self.get_token(cr, uid, context)
- gs_pool = self.pool['google.service']
-
params = {
'fields': 'items,nextPageToken',
'access_token': token,
'maxResults': 1000,
- 'timeMin': self.get_start_time_to_synchro(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
+ #'timeMin': self.get_minTime(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
}
+
+ if lastSync:
+ params['updatedMin'] = lastSync.strftime("%Y-%m-%dT%H:%M:%S.%fz")
+ params['showDeleted'] = True
+ else:
+ params['timeMin'] = self.get_minTime(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz")
+
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
url = "/calendar/v3/calendars/%s/events" % 'primary'
if nextPageToken:
params['pageToken'] = nextPageToken
- content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
+ status, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context)
google_events_dict = {}
-
for google_event in content['items']:
google_events_dict[google_event['id']] = google_event
- if content.get('nextPageToken', False):
- google_events_dict.update(self.get_event_dict(cr, uid, token, content['nextPageToken'], context=context))
+ if content.get('nextPageToken'):
+ google_events_dict.update(
+ self.get_event_synchro_dict(cr, uid, lastSync=lastSync, token=token, nextPageToken=content['nextPageToken'], context=context)
+ )
+
return google_events_dict
+ def get_one_event_synchro(self, cr, uid, google_id, context=None):
+ token = self.get_token(cr, uid, context)
+
+ params = {
+ 'access_token': token,
+ 'maxResults': 1000,
+ 'showDeleted': True,
+ }
+
+ headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
+
+ url = "/calendar/v3/calendars/%s/events/%s" % ('primary', google_id)
+ try:
+ status, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context)
+ except:
+ _logger.info("Calendar Synchro - In except of get_one_event_synchro")
+ pass
+
+ return status_response(status) and content or False
+
def update_to_google(self, cr, uid, oe_event, google_event, context):
calendar_event = self.pool['calendar.event']
- gs_pool = self.pool['google.service']
url = "/calendar/v3/calendars/%s/events/%s?fields=%s&access_token=%s" % ('primary', google_event['id'], 'id,updated', self.get_token(cr, uid, context))
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
@@ -315,7 +357,7 @@ class google_calendar(osv.AbstractModel):
data['sequence'] = google_event.get('sequence', 0)
data_json = simplejson.dumps(data)
- content = gs_pool._do_request(cr, uid, url, data_json, headers, type='PATCH', context=context)
+ status, content = self.pool['google.service']._do_request(cr, uid, url, data_json, headers, type='PATCH', context=context)
update_date = datetime.strptime(content['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
calendar_event.write(cr, uid, [oe_event.id], {'oe_update_date': update_date})
@@ -324,15 +366,13 @@ class google_calendar(osv.AbstractModel):
self.pool['calendar.attendee'].write(cr, uid, [context['curr_attendee']], {'oe_synchro_date': update_date}, context)
def update_an_event(self, cr, uid, event, context=None):
- gs_pool = self.pool['google.service']
-
data = self.generate_data(cr, uid, event, context=context)
url = "/calendar/v3/calendars/%s/events/%s" % ('primary', event.google_internal_event_id)
headers = {}
data['access_token'] = self.get_token(cr, uid, context)
- response = gs_pool._do_request(cr, uid, url, data, headers, type='GET', context=context)
+ status, response = self.pool['google.service']._do_request(cr, uid, url, data, headers, type='GET', context=context)
#TO_CHECK : , if http fail, no event, do DELETE ?
return response
@@ -379,7 +419,12 @@ class google_calendar(osv.AbstractModel):
if self.get_need_synchro_attendee(cr, uid, context=context):
attendee_id = res_partner_obj.search(cr, uid, [('email', '=', google_attendee['email'])], context=context)
if not attendee_id:
- attendee_id = [res_partner_obj.create(cr, uid, {'email': google_attendee['email'], 'customer': False, 'name': google_attendee.get("displayName", False) or google_attendee['email']}, context=context)]
+ data = {
+ 'email': google_attendee['email'],
+ 'customer': False,
+ 'name': google_attendee.get("displayName", False) or google_attendee['email']
+ }
+ attendee_id = [res_partner_obj.create(cr, uid, data, context=context)]
attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
partner_record.append((4, attendee.get('id')))
attendee['partner_id'] = attendee.pop('id')
@@ -388,26 +433,25 @@ class google_calendar(osv.AbstractModel):
UTC = pytz.timezone('UTC')
if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled
+
if single_event_dict['start'].get('dateTime', False) and single_event_dict['end'].get('dateTime', False):
date = parser.parse(single_event_dict['start']['dateTime'])
- date_deadline = parser.parse(single_event_dict['end']['dateTime'])
- delta = date_deadline.astimezone(UTC) - date.astimezone(UTC)
+ stop = parser.parse(single_event_dict['end']['dateTime'])
date = str(date.astimezone(UTC))[:-6]
- date_deadline = str(date_deadline.astimezone(UTC))[:-6]
+ stop = str(stop.astimezone(UTC))[:-6]
allday = False
else:
- date = (single_event_dict['start']['date'] + ' 00:00:00')
- date_deadline = (single_event_dict['end']['date'] + ' 00:00:00')
- d_start = datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
- d_end = datetime.strptime(date_deadline, "%Y-%m-%d %H:%M:%S")
- delta = (d_end - d_start)
+ date = (single_event_dict['start']['date'])
+ stop = (single_event_dict['end']['date'])
+ d_end = datetime.strptime(stop, DEFAULT_SERVER_DATE_FORMAT)
allday = True
+ d_end = d_end + timedelta(days=-1)
+ stop = d_end.strftime(DEFAULT_SERVER_DATE_FORMAT)
- result['duration'] = (delta.seconds / 60) / 60.0 + delta.days * 24
update_date = datetime.strptime(single_event_dict['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
result.update({
- 'date': date,
- 'date_deadline': date_deadline,
+ 'start': date,
+ 'stop': stop,
'allday': allday
})
result.update({
@@ -419,7 +463,6 @@ class google_calendar(osv.AbstractModel):
'location': single_event_dict.get('location', False),
'class': single_event_dict.get('visibility', 'public'),
'oe_update_date': update_date,
- # 'google_internal_event_id': single_event_dict.get('id',False),
})
if single_event_dict.get("recurrence", False):
@@ -431,7 +474,6 @@ class google_calendar(osv.AbstractModel):
elif type == "copy":
result['recurrence'] = True
res = calendar_event.write(cr, uid, [event['id']], result, context=context)
-
elif type == "create":
res = calendar_event.create(cr, uid, result, context=context)
@@ -439,18 +481,81 @@ class google_calendar(osv.AbstractModel):
self.pool['calendar.attendee'].write(cr, uid, [context['curr_attendee']], {'oe_synchro_date': update_date, 'google_internal_event_id': single_event_dict.get('id', False)}, context)
return res
- def synchronize_events(self, cr, uid, ids, context=None):
- # Create all new events from OpenERP into Gmail, if that is not recurrent event
- self.create_new_events(cr, uid, context=context)
- self.bind_recurring_events_to_google(cr, uid, context)
- res = self.update_events(cr, uid, context)
+ def remove_references(self, cr, uid, context=None):
+ current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
+ reset_data = {
+ 'google_calendar_rtoken': False,
+ 'google_calendar_token': False,
+ 'google_calendar_token_validity': False,
+ 'google_calendar_last_sync_date': False,
+ 'google_calendar_cal_id': False,
+ }
+ all_my_attendees = self.pool['calendar.attendee'].search(cr, uid, [('partner_id', '=', current_user.partner_id.id)], context=context)
+ self.pool['calendar.attendee'].write(cr, uid, all_my_attendees, {'oe_synchro_date': False, 'google_internal_event_id': False}, context=context)
+ current_user.write(reset_data, context=context)
+ return True
+
+ def synchronize_events(self, cr, uid, ids, lastSync=True, context=None):
+ if context is None:
+ context = {}
+
+ # def isValidSync(syncToken):
+ # gs_pool = self.pool['google.service']
+ # params = {
+ # 'maxResults': 1,
+ # 'fields': 'id',
+ # 'access_token': self.get_token(cr, uid, context),
+ # 'syncToken': syncToken,
+ # }
+ # url = "/calendar/v3/calendars/primary/events"
+ # status, response = gs_pool._do_request(cr, uid, url, params, type='GET', context=context)
+ # return int(status) != 410
+
+ current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
+
+ context_with_time = dict(context.copy(), ask_time=True)
+ current_google = self.get_calendar_primary_id(cr, uid, context=context_with_time)
+
+ if current_user.google_calendar_cal_id:
+ if current_google != current_user.google_calendar_cal_id:
+ return {
+ "status": "need_reset",
+ "info": {
+ "old_name": current_user.google_calendar_cal_id,
+ "new_name": current_google
+ },
+ "url": ''
+ }
+
+ if lastSync and self.get_last_sync_date(cr, uid, context=context) and not self.get_disable_since_synchro(cr, uid, context=context):
+ lastSync = self.get_last_sync_date(cr, uid, context)
+ _logger.info("Calendar Synchro - MODE SINCE_MODIFIED : %s !" % lastSync.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
+ else:
+ lastSync = False
+ _logger.info("Calendar Synchro - MODE FULL SYNCHRO FORCED")
+ else:
+ current_user.write({'google_calendar_cal_id': current_google}, context=context)
+ lastSync = False
+ _logger.info("Calendar Synchro - MODE FULL SYNCHRO - NEW CAL ID")
+
+ new_ids = []
+ new_ids += self.create_new_events(cr, uid, context=context)
+ new_ids += self.bind_recurring_events_to_google(cr, uid, context)
+
+ res = self.update_events(cr, uid, lastSync, context)
+
+ current_user.write({'google_calendar_last_sync_date': context_with_time.get('ask_time')}, context=context)
return {
"status": res and "need_refresh" or "no_new_event_form_google",
"url": ''
}
def create_new_events(self, cr, uid, context=None):
+ if context is None:
+ context = {}
+
+ new_ids = []
ev_obj = self.pool['calendar.event']
att_obj = self.pool['calendar.attendee']
user_obj = self.pool['res.users']
@@ -458,32 +563,43 @@ class google_calendar(osv.AbstractModel):
context_norecurrent = context.copy()
context_norecurrent['virtual_id'] = False
-
my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID),
('google_internal_event_id', '=', False),
'|',
- ('event_id.date_deadline', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
- ('event_id.end_date', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
+ ('event_id.stop', '>', self.get_minTime(cr, uid, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
+ ('event_id.final_date', '>', self.get_minTime(cr, uid, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
], context=context_norecurrent)
-
for att in att_obj.browse(cr, uid, my_att_ids, context=context):
if not att.event_id.recurrent_id or att.event_id.recurrent_id == 0:
- response = self.create_an_event(cr, uid, att.event_id, context=context)
- update_date = datetime.strptime(response['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
- ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date': update_date})
- att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date': update_date})
- cr.commit()
+ st, response = self.create_an_event(cr, uid, att.event_id, context=context)
+ if status_response(st):
+ update_date = datetime.strptime(response['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
+ ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date': update_date})
+ new_ids.append(response['id'])
+ att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date': update_date})
+ cr.commit()
+ else:
+ _logger.warning("Impossible to create event %s. [%s]" % (att.event_id.id, st))
+ _logger.warning("Response : %s" % response)
+ return new_ids
- def bind_recurring_events_to_google(self, cr, uid, context):
+ def get_context_no_virtual(self, context):
+ context_norecurrent = context.copy()
+ context_norecurrent['virtual_id'] = False
+ context_norecurrent['active_test'] = False
+ return context_norecurrent
+
+ def bind_recurring_events_to_google(self, cr, uid, context=None):
+ if context is None:
+ context = {}
+
+ new_ids = []
ev_obj = self.pool['calendar.event']
att_obj = self.pool['calendar.attendee']
user_obj = self.pool['res.users']
myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
- context_norecurrent = context.copy()
- context_norecurrent['virtual_id'] = False
- context_norecurrent['active_test'] = False
-
+ context_norecurrent = self.get_context_no_virtual(context)
my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('google_internal_event_id', '=', False)], context=context_norecurrent)
for att in att_obj.browse(cr, uid, my_att_ids, context=context):
@@ -500,11 +616,20 @@ class google_calendar(osv.AbstractModel):
if new_google_internal_event_id:
#TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE !
- self.update_recurrent_event_exclu(cr, uid, new_google_internal_event_id, source_attendee_record.google_internal_event_id, att.event_id, context=context)
- att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context)
- cr.commit()
+ try:
+ st, response = self.update_recurrent_event_exclu(cr, uid, new_google_internal_event_id, source_attendee_record.google_internal_event_id, att.event_id, context=context)
+ if status_response(st):
+ att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context)
+ new_ids.append(new_google_internal_event_id)
+ cr.commit()
+ else:
+ _logger.warning("Impossible to create event %s. [%s]" % (att.event_id.id, st))
+ _logger.warning("Response : %s" % response)
+ except:
+ pass
+ return new_ids
- def update_events(self, cr, uid, context=None):
+ def update_events(self, cr, uid, lastSync=False, context=None):
if context is None:
context = {}
@@ -512,20 +637,64 @@ class google_calendar(osv.AbstractModel):
user_obj = self.pool['res.users']
att_obj = self.pool['calendar.attendee']
myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
+ context_novirtual = self.get_context_no_virtual(context)
- context_novirtual = context.copy()
- context_novirtual['virtual_id'] = False
- context_novirtual['active_test'] = False
+ if lastSync:
+ try:
+ all_event_from_google = self.get_event_synchro_dict(cr, uid, lastSync=lastSync, context=context)
+ except urllib2.HTTPError, e:
+ if e.code == 410: # GONE, Google is lost.
+ # we need to force the rollback from this cursor, because it locks my res_users but I need to write in this tuple before to raise.
+ cr.rollback()
+ registry = openerp.modules.registry.RegistryManager.get(request.session.db)
+ with registry.cursor() as cur:
+ self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_last_sync_date': False}, context=context)
+ error_key = simplejson.loads(e.read())
+ error_key = error_key.get('error', {}).get('message', 'nc')
+ error_msg = "Google are lost... the next synchro will be a full synchro. \n\n %s" % error_key
+ raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
- all_event_from_google = self.get_event_dict(cr, uid, context=context)
+ my_google_att_ids = att_obj.search(cr, uid, [
+ ('partner_id', '=', myPartnerID),
+ ('google_internal_event_id', 'in', all_event_from_google.keys())
+ ], context=context_novirtual)
+
+ my_openerp_att_ids = att_obj.search(cr, uid, [
+ ('partner_id', '=', myPartnerID),
+ ('event_id.oe_update_date', '>', lastSync and lastSync.strftime(DEFAULT_SERVER_DATETIME_FORMAT) or self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
+ ('google_internal_event_id', '!=', False),
+ ], context=context_novirtual)
+
+ my_openerp_googleinternal_ids = att_obj.read(cr, uid, my_openerp_att_ids, ['google_internal_event_id', 'event_id'], context=context_novirtual)
+
+ if self.get_print_log(cr, uid, context=context):
+ _logger.info("Calendar Synchro - \n\nUPDATE IN GOOGLE\n%s\n\nRETRIEVE FROM OE\n%s\n\nUPDATE IN OE\n%s\n\nRETRIEVE FROM GG\n%s\n\n" % (all_event_from_google, my_google_att_ids, my_openerp_att_ids, my_openerp_googleinternal_ids))
+
+ for giid in my_openerp_googleinternal_ids:
+ active = True # if not sure, we request google
+ if giid.get('event_id'):
+ active = calendar_event.browse(cr, uid, int(giid.get('event_id')[0]), context=context_novirtual).active
+
+ if giid.get('google_internal_event_id') and not all_event_from_google.get(giid.get('google_internal_event_id')) and active:
+ one_event = self.get_one_event_synchro(cr, uid, giid.get('google_internal_event_id'), context=context)
+ if one_event:
+ all_event_from_google[one_event['id']] = one_event
+
+ my_att_ids = list(set(my_google_att_ids + my_openerp_att_ids))
+
+ else:
+ domain = [
+ ('partner_id', '=', myPartnerID),
+ ('google_internal_event_id', '!=', False),
+ '|',
+ ('event_id.stop', '>', self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
+ ('event_id.final_date', '>', self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
+ ]
+
+ # Select all events from OpenERP which have been already synchronized in gmail
+ my_att_ids = att_obj.search(cr, uid, domain, context=context_novirtual)
+ all_event_from_google = self.get_event_synchro_dict(cr, uid, lastSync=False, context=context)
- # Select all events from OpenERP which have been already synchronized in gmail
- my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID),
- ('google_internal_event_id', '!=', False),
- '|',
- ('event_id.date_deadline', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
- ('event_id.end_date', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
- ], context=context_novirtual)
event_to_synchronize = {}
for att in att_obj.browse(cr, uid, my_att_ids, context=context):
event = att.event_id
@@ -576,8 +745,10 @@ class google_calendar(osv.AbstractModel):
######################
for base_event in event_to_synchronize:
for current_event in event_to_synchronize[base_event]:
- event_to_synchronize[base_event][current_event].compute_OP()
- #print event_to_synchronize[base_event]
+ event_to_synchronize[base_event][current_event].compute_OP(modeFull=not lastSync)
+ if self.get_print_log(cr, uid, context=context):
+ if not isinstance(event_to_synchronize[base_event][current_event].OP, NothingToDo):
+ _logger.info(event_to_synchronize[base_event])
######################
# DO ACTION #
@@ -615,24 +786,35 @@ class google_calendar(osv.AbstractModel):
if actSrc == 'OE':
self.delete_an_event(cr, uid, current_event[0], context=context)
elif actSrc == 'GG':
- new_google_event_id = event.GG.event['id'].split('_')[1]
- if 'T' in new_google_event_id:
- new_google_event_id = new_google_event_id.replace('T', '')[:-1]
- else:
- new_google_event_id = new_google_event_id + "000000"
+ new_google_event_id = event.GG.event['id'].split('_')[1]
+ if 'T' in new_google_event_id:
+ new_google_event_id = new_google_event_id.replace('T', '')[:-1]
+ else:
+ new_google_event_id = new_google_event_id + "000000"
- if event.GG.status:
- parent_event = {}
- parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id)
- res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)
- else:
- if event_to_synchronize[base_event][0][1].OE.event_id:
- parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id
- calendar_event.unlink(cr, uid, "%s-%s" % (parent_oe_id, new_google_event_id), can_be_deleted=True, context=context)
+ if event.GG.status:
+ parent_event = {}
+ if not event_to_synchronize[base_event][0][1].OE.event_id:
+ event_to_synchronize[base_event][0][1].OE.event_id = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].split('_')[0])], ['event_id'], context=context_novirtual)[0].get('event_id')[0]
+
+ parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id)
+ res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)
+ else:
+ parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id
+ calendar_event.unlink(cr, uid, "%s-%s" % (parent_oe_id, new_google_event_id), can_be_deleted=True, context=context)
elif isinstance(actToDo, Delete):
if actSrc == 'GG':
- self.delete_an_event(cr, uid, current_event[0], context=context)
+ try:
+ self.delete_an_event(cr, uid, current_event[0], context=context)
+ except Exception, e:
+ error = simplejson.loads(e.read())
+ error_nr = error.get('error', {}).get('code')
+ # if already deleted from gmail or never created
+ if error_nr in (404, 410,):
+ pass
+ else:
+ raise e
elif actSrc == 'OE':
calendar_event.unlink(cr, uid, event.OE.event_id, can_be_deleted=False, context=context)
return True
@@ -655,7 +837,7 @@ class google_calendar(osv.AbstractModel):
url = "/calendar/v3/calendars/%s/events/%s" % ('primary', instance_id)
- content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
+ st, content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
return content.get('sequence', 0)
#################################
## MANAGE CONNEXION TO GMAIL ##
@@ -663,13 +845,16 @@ class google_calendar(osv.AbstractModel):
def get_token(self, cr, uid, context=None):
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
-
- if datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], "%Y-%m-%d %H:%M:%S") < (datetime.now() + timedelta(minutes=1)):
+ if not current_user.google_calendar_token_validity or \
+ datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT) < (datetime.now() + timedelta(minutes=1)):
self.do_refresh_token(cr, uid, context=context)
current_user.refresh()
-
return current_user.google_calendar_token
+ def get_last_sync_date(self, cr, uid, context=None):
+ current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
+ return current_user.google_calendar_last_sync_date and datetime.strptime(current_user.google_calendar_last_sync_date, DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(minutes=0) or False
+
def do_refresh_token(self, cr, uid, context=None):
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
gs_pool = self.pool['google.service']
@@ -707,14 +892,18 @@ class google_calendar(osv.AbstractModel):
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
self.pool['res.users'].write(cr, SUPERUSER_ID, uid, vals, context=context)
- def get_start_time_to_synchro(self, cr, uid, context=None):
- # WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
- number_of_week = 13
+ def get_minTime(self, cr, uid, context=None):
+ number_of_week = self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.week_synchro', default=13)
return datetime.now() - timedelta(weeks=number_of_week)
def get_need_synchro_attendee(self, cr, uid, context=None):
- # WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
- return True
+ return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_synchro_attendee', default=True)
+
+ def get_disable_since_synchro(self, cr, uid, context=None):
+ return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_since_synchro', default=False)
+
+ def get_print_log(self, cr, uid, context=None):
+ return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.debug_print', default=False)
class res_users(osv.Model):
@@ -724,16 +913,22 @@ class res_users(osv.Model):
'google_calendar_rtoken': fields.char('Refresh Token'),
'google_calendar_token': fields.char('User token'),
'google_calendar_token_validity': fields.datetime('Token Validity'),
+ 'google_calendar_last_sync_date': fields.datetime('Last synchro date'),
+ 'google_calendar_cal_id': fields.char('Calendar ID', help='Last Calendar ID who has been synchronized. If it is changed, we remove \
+all links between GoogleID and OpenERP Google Internal ID')
}
class calendar_event(osv.Model):
_inherit = "calendar.event"
+ def get_fields_need_update_google(self, cr, uid, context=None):
+ return ['name', 'description', 'allday', 'date', 'date_end', 'stop', 'attendee_ids', 'location', 'class', 'active']
+
def write(self, cr, uid, ids, vals, context=None):
if context is None:
context = {}
- sync_fields = set(['name', 'description', 'date', 'date_closed', 'date_deadline', 'attendee_ids', 'location', 'class'])
+ sync_fields = set(self.get_fields_need_update_google(cr, uid, context))
if (set(vals.keys()) & sync_fields) and 'oe_update_date' not in vals.keys() and 'NewMeeting' not in context:
vals['oe_update_date'] = datetime.now()
@@ -762,7 +957,7 @@ class calendar_attendee(osv.Model):
_inherit = 'calendar.attendee'
_columns = {
- 'google_internal_event_id': fields.char('Google Calendar Event Id', size=256),
+ 'google_internal_event_id': fields.char('Google Calendar Event Id'),
'oe_synchro_date': fields.datetime('OpenERP Synchro Date'),
}
_sql_constraints = [('google_id_uniq', 'unique(google_internal_event_id,partner_id,event_id)', 'Google ID should be unique!')]
diff --git a/addons/google_calendar/static/src/js/calendar_sync.js b/addons/google_calendar/static/src/js/calendar_sync.js
index fdc4aeffb77..fc05ca05ab0 100644
--- a/addons/google_calendar/static/src/js/calendar_sync.js
+++ b/addons/google_calendar/static/src/js/calendar_sync.js
@@ -11,38 +11,54 @@ openerp.google_calendar = function(instance) {
});
return this._super(r);
},
- sync_calendar: function(res,button) {
+ sync_calendar: function(res, button) {
var self = this;
var context = instance.web.pyeval.eval('context');
//$('div.oe_cal_sync_button').hide();
- $('div.oe_cal_sync_button').prop('disabled',true);
-
+ $('div.oe_cal_sync_button').prop('disabled', true);
+
self.rpc('/google_calendar/sync_data', {
arch: res.arch,
fields: res.fields,
- model:res.model,
+ model: res.model,
fromurl: window.location.href,
- local_context:context
+ local_context: context
}).done(function(o) {
- if (o.status == "need_auth") {
+ if (o.status === "need_auth") {
alert(_t("You will be redirected on gmail to authorize your OpenErp to access your calendar !"));
instance.web.redirect(o.url);
}
- else if (o.status == "need_config_from_admin") {
-
- if (!_.isUndefined(o.action) && parseInt(o.action)) {
- if (confirm(_t("An admin need to configure Google Synchronization before to use it, do you want to configure it now ? !"))) {
- self.do_action(o.action);
+ else if (o.status === "need_config_from_admin"){
+ if (!_.isUndefined(o.action) && parseInt(o.action)){
+ if (confirm(_t("An admin need to configure Google Synchronization before to use it, do you want to configure it now ? !"))){
+ self.do_action(o.action);
}
}
- else {
+ else{
alert(_t("An admin need to configure Google Synchronization before to use it !"));
}
}
- else if (o.status == "need_refresh"){
+ else if (o.status === "need_refresh"){
self.$calendar.fullCalendar('refetchEvents');
}
+ else if (o.status === "need_reset"){
+ if (confirm(_t("The account that you are trying to synchronize (" + o.info.new_name + "), is not the same that the last one used \
+(" + o.info.old_name + "! )" + "\r\n\r\nDo you want remove all references from the old account ?"))){
+ self.rpc('/google_calendar/remove_references', {
+ model:res.model,
+ local_context:context
+ }).done(function(o) {
+ if (o.status === "OK") {
+ alert(_t("All old references have been deleted. You can now restart the synchronization"));
+ }
+ else if (o.status === "KO") {
+ alert(_t("An error has occured when we was removing all old references. Please retry or contact your administrator."));
+ }
+ //else NOP
+ });
+ }
+ }
}).always(function(o) { $('div.oe_cal_sync_button').prop('disabled',false); });
}
});
diff --git a/addons/google_calendar/views/res_users.xml b/addons/google_calendar/views/res_users.xml
new file mode 100644
index 00000000000..8adc3429090
--- /dev/null
+++ b/addons/google_calendar/views/res_users.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ res.users.form
+ res.users
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml
index 1c4a7f55c10..6c15114cde6 100644
--- a/addons/mail/mail_message_view.xml
+++ b/addons/mail/mail_message_view.xml
@@ -115,7 +115,7 @@
- kanban,form
+ kanban,tree,form
diff --git a/addons/project/project.py b/addons/project/project.py
index 883db8fd0be..2249cf07dd5 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -214,7 +214,7 @@ class project(osv.osv):
'res_model': 'ir.attachment',
'type': 'ir.actions.act_window',
'view_id': False,
- 'view_mode': 'kanban,form',
+ 'view_mode': 'kanban,tree,form',
'view_type': 'form',
'limit': 80,
'context': "{'default_res_model': '%s','default_res_id': %d}" % (self._name, res_id)
diff --git a/addons/sale/i18n/fr_CA.po b/addons/sale/i18n/fr_CA.po
new file mode 100644
index 00000000000..867db031b73
--- /dev/null
+++ b/addons/sale/i18n/fr_CA.po
@@ -0,0 +1,2288 @@
+# French (Canada) translation for openobject-addons
+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2014.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:04+0000\n"
+"PO-Revision-Date: 2014-05-13 02:46+0000\n"
+"Last-Translator: Maxime Chambreuil (http://www.savoirfairelinux.com) "
+"\n"
+"Language-Team: French (Canada) \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2014-05-14 05:45+0000\n"
+"X-Generator: Launchpad (build 17002)\n"
+
+#. module: sale
+#: model:res.groups,name:sale.group_analytic_accounting
+msgid "Analytic Accounting for Sales"
+msgstr ""
+
+#. module: sale
+#: model:process.transition,name:sale.process_transition_confirmquotation0
+msgid "Confirm Quotation"
+msgstr "Confirmer la soumission"
+
+#. module: sale
+#: view:board.board:0
+msgid "Sales Dashboard"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/wizard/sale_make_invoice_advance.py:92
+#, python-format
+msgid "There is no income account defined as global property."
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_order_line_tree2
+#: model:ir.ui.menu,name:sale.menu_invoicing_sales_order_lines
+msgid "Order Lines to Invoice"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,date_confirm:0
+msgid "Confirmation Date"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+#: view:sale.order.line:0
+#: view:sale.report:0
+msgid "Group By..."
+msgstr ""
+
+#. module: sale
+#: field:sale.order.line,address_allotment_id:0
+msgid "Allotment Partner"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_view_sale_advance_payment_inv
+msgid "Invoice Order"
+msgstr ""
+
+#. module: sale
+#: view:sale.config.settings:0
+msgid "Product Features"
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,module_account_analytic_analysis:0
+msgid ""
+"Allows to define your customer contracts conditions: invoicing\n"
+" method (fixed price, on timesheet, advance invoice), the exact "
+"pricing\n"
+" (650€/day for a developer), the duration (one year support "
+"contract).\n"
+" You will be able to follow the progress of the contract and "
+"invoice automatically.\n"
+" It installs the account_analytic_analysis module."
+msgstr ""
+
+#. module: sale
+#: model:email.template,report_name:sale.email_template_edi_sale
+msgid ""
+"${(object.name or '').replace('/','_')}_${object.state == 'draft' and "
+"'draft' or ''}"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+#: field:sale.report,product_uom:0
+msgid "Unit of Measure"
+msgstr ""
+
+#. module: sale
+#: field:sale.order.line,type:0
+msgid "Procurement Method"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,date_confirm:0
+msgid "Date on which sales order is confirmed."
+msgstr ""
+
+#. module: sale
+#: field:account.config.settings,module_sale_analytic_plans:0
+msgid "Use multiple analytic accounts on sales"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "March"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:565
+#, python-format
+msgid "First cancel all invoices attached to this sales order."
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Quotation Number"
+msgstr "Numéro de soumission"
+
+#. module: sale
+#: view:sale.order:0
+#: field:sale.order,message_unread:0
+msgid "Unread Messages"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,company_id:0
+#: field:sale.order.line,company_id:0
+#: view:sale.report:0
+#: field:sale.report,company_id:0
+#: field:sale.shop,company_id:0
+msgid "Company"
+msgstr ""
+
+#. module: sale
+#: field:sale.make.invoice,invoice_date:0
+msgid "Invoice Date"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_order_line_tree3
+msgid "Uninvoiced and Delivered Lines"
+msgstr ""
+
+#. module: sale
+#: model:mail.message.subtype,description:sale.mt_order_confirmed
+msgid "Quotation confirmed"
+msgstr "Soumission confirmée"
+
+#. module: sale
+#: selection:sale.order,state:0
+#: selection:sale.report,state:0
+msgid "Invoice Exception"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Quotation "
+msgstr "Soumission "
+
+#. module: sale
+#: selection:sale.order,state:0
+msgid "Draft Quotation"
+msgstr "Soumission en brouillon"
+
+#. module: sale
+#: field:sale.order,partner_shipping_id:0
+msgid "Delivery Address"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+#: field:sale.report,analytic_account_id:0
+#: field:sale.shop,project_id:0
+msgid "Analytic Account"
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,module_sale_journal:0
+msgid "Allow batch invoicing of delivery orders through journals"
+msgstr ""
+
+#. module: sale
+#: field:sale.order.line,price_subtotal:0
+msgid "Subtotal"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+#: field:sale.report,day:0
+msgid "Day"
+msgstr ""
+
+#. module: sale
+#: model:process.transition.action,name:sale.process_transition_action_cancelorder0
+#: view:sale.order:0
+msgid "Cancel Order"
+msgstr ""
+
+#. module: sale
+#: field:sale.order.line,th_weight:0
+msgid "Weight"
+msgstr ""
+
+#. module: sale
+#: view:sale.config.settings:0
+msgid "Warehouse Features"
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,time_unit:0
+msgid "The default working time unit for services is"
+msgstr ""
+
+#. module: sale
+#: field:sale.order.line,product_uom:0
+msgid "Unit of Measure "
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/wizard/sale_make_invoice_advance.py:101
+#, python-format
+msgid "Incorrect Data"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/wizard/sale_make_invoice_advance.py:102
+#, python-format
+msgid "The value of Advance Amount must be positive."
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,group_discount_per_so_line:0
+msgid "Allows you to apply some discount per sales order line."
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Sales Order Lines that are in 'done' state"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order.line,type:0
+msgid "on order"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,message_ids:0
+msgid "Messages"
+msgstr ""
+
+#. module: sale
+#: field:sale.report,state:0
+msgid "Order Status"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,amount_tax:0
+#: field:sale.order.line,tax_id:0
+msgid "Taxes"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,amount_untaxed:0
+msgid "Untaxed Amount"
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,module_project:0
+msgid "Project"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:185
+#: code:addons/sale/sale.py:363
+#: code:addons/sale/sale.py:504
+#: code:addons/sale/sale.py:598
+#: code:addons/sale/sale.py:763
+#: code:addons/sale/sale.py:780
+#, python-format
+msgid "Error!"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Net Total :"
+msgstr ""
+
+#. module: sale
+#: help:sale.order.line,type:0
+msgid ""
+"From stock: When needed, the product is taken from the stock or we wait for "
+"replenishment.\n"
+"On order: When needed, the product is purchased or produced."
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,module_analytic_user_function:0
+msgid ""
+"Allows you to define what is the default function of a specific user on a "
+"given account.\n"
+" This is mostly used when a user encodes his timesheet. The "
+"values are retrieved and the fields are auto-filled.\n"
+" But the possibility to change these values is still "
+"available.\n"
+" This installs the module analytic_user_function."
+msgstr ""
+
+#. module: sale
+#: selection:sale.order,state:0
+#: selection:sale.order.line,state:0
+#: selection:sale.report,state:0
+msgid "Cancelled"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Sales Order Lines related to a Sales Order of mine"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order,state:0
+msgid "Quotation Sent"
+msgstr "Soumission envoyée"
+
+#. module: sale
+#: model:ir.model,name:sale.model_mail_compose_message
+msgid "Email composition wizard"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,message_unread:0
+msgid "If checked new messages require your attention."
+msgstr ""
+
+#. module: sale
+#: selection:sale.order,state:0
+#: selection:sale.report,state:0
+msgid "Shipping Exception"
+msgstr ""
+
+#. module: sale
+#: field:sale.order.line,product_uos_qty:0
+msgid "Quantity (UoS)"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_shop_form
+#: field:sale.order,shop_id:0
+#: view:sale.report:0
+#: field:sale.report,shop_id:0
+msgid "Shop"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_orders_exception
+msgid "Sales in Exception"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,partner_invoice_id:0
+msgid "Invoice Address"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,create_date:0
+msgid "Date on which sales order is created."
+msgstr ""
+
+#. module: sale
+#: view:res.partner:0
+msgid "False"
+msgstr ""
+
+#. module: sale
+#: help:sale.advance.payment.inv,advance_payment_method:0
+msgid ""
+"Use All to create the final invoice.\n"
+" Use Percentage to invoice a percentage of the total amount.\n"
+" Use Fixed Price to invoice a specific amound in advance.\n"
+" Use Some Order Lines to invoice a selection of the sales "
+"order lines."
+msgstr ""
+
+#. module: sale
+#: view:sale.make.invoice:0
+#: view:sale.order.line.make.invoice:0
+msgid "Create Invoices"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Tax"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:277
+#: code:addons/sale/sale.py:820
+#: code:addons/sale/sale.py:983
+#, python-format
+msgid "Invalid Action!"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,state:0
+msgid ""
+"Gives the status of the quotation or sales order. \n"
+"The exception status is automatically set when a cancel operation occurs "
+" in the invoice validation (Invoice Exception) or in the picking "
+"list process (Shipping Exception).\n"
+"The 'Waiting Schedule' status is set when the invoice is confirmed "
+" but waiting for the scheduler to run on the order date."
+msgstr ""
+"Donne le statut d'une soumission ou d'une commande de vente.\n"
+"Le statut 'Exception' est automatiquement défini quand une opération "
+"d'annulation est effectuée dans la validation de la facture (Exception de "
+"facturation) ou dans le processus de colisage (Exception de colisage).\n"
+"Le statut 'En attente' est défini quand une facture est confirmée mais on "
+"attend le lancement du planificateur à la date de la commande."
+
+#. module: sale
+#: field:sale.report,date_confirm:0
+msgid "Date Confirm"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+#: field:sale.report,nbr:0
+msgid "# of Lines"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,message_summary:0
+msgid ""
+"Holds the Chatter summary (number of messages, ...). This summary is "
+"directly in html format in order to be inserted in kanban views."
+msgstr ""
+
+#. module: sale
+#: help:sale.order.line,sequence:0
+msgid "Gives the sequence order when displaying a list of sales order lines."
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+#: field:sale.report,product_uom_qty:0
+msgid "# of Qty"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Fax :"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:277
+#, python-format
+msgid ""
+"In order to delete a confirmed sales order, you must cancel it before !"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "(update)"
+msgstr ""
+
+#. module: sale
+#: model:ir.model,name:sale.model_res_partner
+#: view:sale.report:0
+#: field:sale.report,partner_id:0
+msgid "Partner"
+msgstr ""
+
+#. module: sale
+#: view:sale.config.settings:0
+msgid "Contract Features"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:287
+#: code:addons/sale/sale.py:584
+#: model:ir.model,name:sale.model_sale_order
+#: model:process.node,name:sale.process_node_order0
+#: model:process.node,name:sale.process_node_saleorder0
+#: field:res.partner,sale_order_ids:0
+#: model:res.request.link,name:sale.req_link_sale_order
+#: view:sale.order:0
+#: selection:sale.order,state:0
+#, python-format
+msgid "Sales Order"
+msgstr ""
+
+#. module: sale
+#: model:res.groups,name:sale.group_invoice_so_lines
+msgid "Enable Invoicing Sales order lines"
+msgstr ""
+
+#. module: sale
+#: model:ir.model,name:sale.model_sale_order_line
+msgid "Sales Order Line"
+msgstr ""
+
+#. module: sale
+#: field:sale.advance.payment.inv,amount:0
+msgid "Advance Amount"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,invoice_exists:0
+msgid "It indicates that sales order has at least one invoice."
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,group_sale_pricelist:0
+msgid ""
+"Allows to manage different prices based on rules per category of customers.\n"
+"Example: 10% for retailers, promotion of 5 EUR on this product, etc."
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,module_analytic_user_function:0
+msgid "One employee can have different roles per contract"
+msgstr ""
+
+#. module: sale
+#: selection:sale.advance.payment.inv,advance_payment_method:0
+msgid "Invoice the whole sales order"
+msgstr ""
+
+#. module: sale
+#: field:sale.shop,payment_default_id:0
+msgid "Default Payment Term"
+msgstr ""
+
+#. module: sale
+#: model:process.transition.action,name:sale.process_transition_action_confirm0
+msgid "Confirm"
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,timesheet:0
+msgid "Prepare invoices based on timesheets"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:820
+#, python-format
+msgid "You cannot cancel a sales order line that has already been invoiced."
+msgstr ""
+
+#. module: sale
+#: view:account.invoice.report:0
+#: view:board.board:0
+#: model:ir.actions.act_window,name:sale.action_turnover_by_month
+msgid "Monthly Turnover"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order,invoice_quantity:0
+msgid "Shipped Quantities"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+#: field:sale.report,year:0
+msgid "Year"
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,group_uom:0
+msgid "Allow using different units of measures"
+msgstr ""
+
+#. module: sale
+#: model:mail.message.subtype,name:sale.mt_order_confirmed
+msgid "Sales Order Confirmed"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Sales Order that haven't yet been confirmed"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Print"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Order N°"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+#: field:sale.order,order_line:0
+msgid "Order Lines"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Disc.(%)"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:764
+#, python-format
+msgid "Please define income account for this product: \"%s\" (id:%d)."
+msgstr ""
+
+#. module: sale
+#: field:sale.order.line,invoice_lines:0
+msgid "Invoice Lines"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+#: field:sale.report,price_total:0
+msgid "Total Price"
+msgstr ""
+
+#. module: sale
+#: help:account.config.settings,group_analytic_account_for_sales:0
+msgid "Allows you to specify an analytic account on sales orders."
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,module_sale_journal:0
+msgid ""
+"Allows you to categorize your sales and deliveries (picking lists) between "
+"different journals,\n"
+" and perform batch operations on journals.\n"
+" This installs the module sale_journal."
+msgstr ""
+
+#. module: sale
+#: help:sale.make.invoice,grouped:0
+msgid "Check the box to group the invoices for the same customers"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_sale_order_make_invoice
+#: model:ir.actions.act_window,name:sale.action_view_sale_order_line_make_invoice
+msgid "Make Invoices"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/res_config.py:97
+#, python-format
+msgid "Hour"
+msgstr ""
+
+#. module: sale
+#: field:res.partner,sale_order_count:0
+msgid "# of Sales Order"
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,timesheet:0
+msgid ""
+"For modifying account analytic view to show important data to project "
+"manager of services companies.\n"
+" You can also view the report of account analytic summary "
+"user-wise as well as month wise.\n"
+" This installs the module account_analytic_analysis."
+msgstr ""
+
+#. module: sale
+#: field:sale.order,create_date:0
+msgid "Creation Date"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+#: view:sale.order.line:0
+msgid "To Invoice"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,partner_invoice_id:0
+msgid "Invoice address for current sales order."
+msgstr ""
+
+#. module: sale
+#: selection:sale.order,invoice_quantity:0
+msgid "Ordered Quantities"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+msgid "Ordered Year of the sales order"
+msgstr ""
+
+#. module: sale
+#: model:res.groups,name:sale.group_delivery_invoice_address
+msgid "Addresses in Sales Orders"
+msgstr ""
+
+#. module: sale
+#: field:sale.advance.payment.inv,qtty:0
+#: report:sale.order:0
+#: field:sale.order.line,product_uom_qty:0
+msgid "Quantity"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Total :"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Sales Order Lines ready to be invoiced"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+msgid "My Sales"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,name:0
+#: field:sale.order.line,order_id:0
+msgid "Order Reference"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,fiscal_position:0
+msgid "Fiscal Position"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "July"
+msgstr ""
+
+#. module: sale
+#: help:sale.order.line,state:0
+msgid ""
+"* The 'Draft' status is set when the related sales order in draft status. "
+" \n"
+"* The 'Confirmed' status is set when the related sales order is confirmed. "
+" \n"
+"* The 'Exception' status is set when the related sales order is set as "
+"exception. \n"
+"* The 'Done' status is set when the sales order line has been picked. "
+" \n"
+"* The 'Cancelled' status is set when a user cancel the sales order related."
+msgstr ""
+
+#. module: sale
+#: view:sale.config.settings:0
+msgid "Default Options"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:960
+#: code:addons/sale/wizard/sale_make_invoice_advance.py:91
+#: code:addons/sale/wizard/sale_make_invoice_advance.py:95
+#, python-format
+msgid "Configuration Error!"
+msgstr ""
+
+#. module: sale
+#: field:account.config.settings,group_analytic_account_for_sales:0
+msgid "Analytic accounting for sales"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+#: field:sale.order,state:0
+#: view:sale.order.line:0
+#: field:sale.order.line,state:0
+#: view:sale.report:0
+msgid "Status"
+msgstr ""
+
+#. module: sale
+#: view:sale.advance.payment.inv:0
+msgid ""
+"After clicking 'Show Lines to Invoice', select lines to invoice and create "
+"the invoice from the 'More' dropdown menu."
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Send by Email"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/edi/sale_order.py:140
+#, python-format
+msgid "EDI Pricelist (%s)"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order,order_policy:0
+msgid "On Delivery Order"
+msgstr ""
+
+#. module: sale
+#: view:sale.config.settings:0
+msgid "Invoicing Process"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Order Date"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Sales Order done"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:364
+#, python-format
+msgid "Please define sales journal for this company: \"%s\" (id:%d)."
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.act_res_partner_2_sale_order
+#: view:res.partner:0
+msgid "Quotations and Sales"
+msgstr "Soumissions et ventes"
+
+#. module: sale
+#: field:sale.order,invoiced:0
+msgid "Paid"
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,group_uom:0
+msgid ""
+"Allows you to select and maintain different units of measure for products."
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+msgid "Reference Unit of Measure"
+msgstr ""
+
+#. module: sale
+#: view:sale.advance.payment.inv:0
+msgid "Create and View Invoice"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Sales order lines done"
+msgstr ""
+
+#. module: sale
+#: field:sale.make.invoice,grouped:0
+msgid "Group the invoices"
+msgstr ""
+
+#. module: sale
+#: help:sale.advance.payment.inv,amount:0
+msgid "The amount to be invoiced in advance."
+msgstr ""
+
+#. module: sale
+#: model:ir.model,name:sale.model_sale_make_invoice
+msgid "Sales Make Invoice"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:307
+#, python-format
+msgid "Pricelist Warning!"
+msgstr ""
+
+#. module: sale
+#: field:sale.order.line,discount:0
+msgid "Discount (%)"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/wizard/sale_line_invoice.py:107
+#, python-format
+msgid ""
+"Invoice cannot be created for this Sales Order Line due to one of the "
+"following reasons:\n"
+"1.The state of this sales order line is either \"draft\" or \"cancel\"!\n"
+"2.The Sales Order Line is Invoiced!"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line.make.invoice:0
+msgid "Create & View Invoice"
+msgstr ""
+
+#. module: sale
+#: view:board.board:0
+#: model:ir.actions.act_window,name:sale.action_quotation_for_sale
+msgid "My Quotations"
+msgstr "Mes soumissions"
+
+#. module: sale
+#: field:sale.order,invoice_ids:0
+msgid "Invoices"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "December"
+msgstr ""
+
+#. module: sale
+#: view:sale.config.settings:0
+msgid "Contracts Management"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Shipped"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+#: field:sale.report,month:0
+msgid "Month"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,currency_id:0
+msgid "Currency"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Uninvoiced"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+#: field:sale.report,categ_id:0
+msgid "Category of Product"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:564
+#, python-format
+msgid "Cannot cancel this sales order!"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Recreate Invoice"
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,module_warning:0
+msgid "Allow configuring alerts by customer or products"
+msgstr ""
+
+#. module: sale
+#: field:sale.shop,name:0
+msgid "Shop Name"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "My Sales Orders"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Taxes :"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,invoice_exists:0
+#: field:sale.order.line,invoiced:0
+msgid "Invoiced"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/wizard/sale_make_invoice_advance.py:202
+#, python-format
+msgid "Advance Invoice"
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,group_invoice_so_lines:0
+msgid ""
+"To allow your salesman to make invoices for sales order lines using the menu "
+"'Lines to Invoice'."
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.client,name:sale.action_client_sale_menu
+msgid "Open Sale Menu"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:598
+#, python-format
+msgid "You cannot confirm a sales order which has no line."
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,state:0
+msgid "In Progress"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:865
+#, python-format
+msgid "No Customer Defined !"
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,module_sale_stock:0
+msgid "Trigger delivery orders automatically from sales orders"
+msgstr ""
+
+#. module: sale
+#: view:sale.make.invoice:0
+#: view:sale.order.line.make.invoice:0
+msgid "Create invoices"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order.line,state:0
+msgid "Confirmed"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/wizard/sale_make_invoice_advance.py:106
+#, python-format
+msgid "Advance of %s %%"
+msgstr ""
+
+#. module: sale
+#: model:ir.model,name:sale.model_sale_order_line_make_invoice
+msgid "Sale OrderLine Make_invoice"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order.line,state:0
+msgid "Draft"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_email_templates
+msgid "Email Templates"
+msgstr ""
+
+#. module: sale
+#: help:sale.order.line,address_allotment_id:0
+msgid "A partner to whom the particular product needs to be allotted."
+msgstr ""
+
+#. module: sale
+#: field:sale.order,project_id:0
+msgid "Contract / Analytic"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order,state:0
+#: selection:sale.report,state:0
+msgid "Waiting Schedule"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,note:0
+msgid "Terms and conditions"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_orders
+#: model:ir.ui.menu,name:sale.menu_sale_order
+#: view:sale.order:0
+msgid "Sales Orders"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,amount_tax:0
+msgid "The tax amount."
+msgstr ""
+
+#. module: sale
+#: field:sale.order,invoiced_rate:0
+msgid "Invoiced Ratio"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order,order_policy:0
+msgid "On Demand"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "August"
+msgstr ""
+
+#. module: sale
+#: model:process.node,note:sale.process_node_saleorder0
+msgid "Drives procurement and invoicing"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "To Do"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "June"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,help:sale.action_shop_form
+msgid ""
+"
\n"
+" Click to define a new sale shop.\n"
+"
\n"
+" Each quotation or sales order must be linked to a shop. The\n"
+" shop also defines the warehouse from which the products will "
+"be\n"
+" delivered for each particular sales.\n"
+"
\n"
+" "
+msgstr ""
+"
\n"
+" Cliquez pour définir un nouveau magasin.\n"
+"
\n"
+" Chaque soumission ou vente doit être liée à un magasin.\n"
+" Le magasin définit également de quel entrepôt les produits "
+"seront\n"
+" livrés pour chaque vente.\n"
+"
\n"
+" "
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Order"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_order_report_all
+#: model:ir.ui.menu,name:sale.menu_report_product_all
+#: view:sale.report:0
+msgid "Sales Analysis"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,message_is_follower:0
+msgid "Is a Follower"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid ""
+"Sales Order Lines that are confirmed, done or in exception state and haven't "
+"yet been invoiced"
+msgstr ""
+
+#. module: sale
+#: model:ir.model,name:sale.model_sale_report
+msgid "Sales Orders Statistics"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,date_order:0
+msgid "Date"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+#: view:sale.order:0
+#: field:sale.order,user_id:0
+#: view:sale.order.line:0
+#: field:sale.order.line,salesman_id:0
+#: view:sale.report:0
+#: field:sale.report,user_id:0
+msgid "Salesperson"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "November"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+msgid "Extended Filters..."
+msgstr ""
+
+#. module: sale
+#: model:process.node,name:sale.process_node_quotation0
+#: view:sale.order:0
+#: selection:sale.report,state:0
+msgid "Quotation"
+msgstr "Soumission"
+
+#. module: sale
+#: field:sale.advance.payment.inv,product_id:0
+msgid "Advance Product"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order.line,state:0
+msgid "Exception"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "October"
+msgstr ""
+
+#. module: sale
+#: model:process.transition,note:sale.process_transition_invoice0
+msgid ""
+"The Salesman creates an invoice manually, if the sales order shipping policy "
+"is 'Shipping and Manual in Progress'. The invoice is created automatically "
+"if the shipping policy is 'Payment before Delivery'."
+msgstr ""
+
+#. module: sale
+#: model:ir.model,name:sale.model_sale_shop
+#: view:sale.shop:0
+msgid "Sales Shop"
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,module_sale_stock:0
+msgid ""
+"Allows you to Make Quotation, Sale Order using different Order policy and "
+"Manage Related Stock.\n"
+" This installs the module sale_stock."
+msgstr ""
+"Vous permet de faire des soumissions, des commandes de vente en utilisant "
+"différentes politique de commande et de gérer les stock associés.\n"
+" Ceci installe le module 'sale_stock'."
+
+#. module: sale
+#: help:sale.advance.payment.inv,product_id:0
+msgid ""
+"Select a product of type service which is called 'Advance Product'.\n"
+" You may have to create it and set it as a default value on "
+"this field."
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "January"
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,group_discount_per_so_line:0
+msgid "Allow setting a discount on the sales order lines"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_orders_in_progress
+msgid "Sales Order in Progress"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line.make.invoice:0
+msgid ""
+"All items in these order lines will be invoiced. You can also invoice a "
+"percentage of the sales order\n"
+" or a fixed price (for advances) directly from the sales "
+"order form if you prefer."
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,module_warning:0
+msgid ""
+"Allow to configure notification on products and trigger them when a user "
+"wants to sale a given product or a given customer.\n"
+"Example: Product: this product is deprecated, do not purchase more than 5.\n"
+" Supplier: don't forget to ask for an express delivery."
+msgstr ""
+
+#. module: sale
+#: field:sale.order,paypal_url:0
+msgid "Paypal Url"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,project_id:0
+msgid "The analytic account related to a sales order."
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "View Invoice"
+msgstr ""
+
+#. module: sale
+#: field:sale.advance.payment.inv,advance_payment_method:0
+msgid "What do you want to invoice?"
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,group_sale_pricelist:0
+msgid "Use pricelists to adapt your price per customers"
+msgstr ""
+
+#. module: sale
+#: model:process.transition,note:sale.process_transition_confirmquotation0
+msgid ""
+"The salesman confirms the quotation. The state of the sales order becomes "
+"'In progress' or 'Manual in progress'."
+msgstr ""
+"Le vendeur confirme la soumission. L'état du bon de commande devient \"En "
+"cours\" ou \"Manuel, en cours\""
+
+#. module: sale
+#: code:addons/sale/sale.py:942
+#, python-format
+msgid ""
+"You have to select a pricelist or a customer in the sales form !\n"
+"Please set one before choosing a product."
+msgstr ""
+
+#. module: sale
+#: help:sale.order,origin:0
+msgid "Reference of the document that generated this sales order request."
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:955
+#, python-format
+msgid "No valid pricelist line found ! :"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/wizard/sale_make_invoice_advance.py:113
+#: code:addons/sale/wizard/sale_make_invoice_advance.py:115
+#, python-format
+msgid "Advance of %s %s"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+#: field:sale.report,delay:0
+msgid "Commitment Delay"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_quotations
+#: model:ir.ui.menu,name:sale.menu_sale_quotations
+#: view:sale.order:0
+#: view:sale.report:0
+msgid "Quotations"
+msgstr "Soumissions"
+
+#. module: sale
+#: help:account.config.settings,module_sale_analytic_plans:0
+msgid "This allows install module sale_analytic_plans."
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Ignore Exception"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,partner_shipping_id:0
+msgid "Delivery address for current sales order."
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,module_sale_margin:0
+msgid "Display margins on sales orders"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,invoice_ids:0
+msgid ""
+"This is the list of invoices that have been generated for this sales order. "
+"The same sales order may have been invoiced in several times (by line for "
+"example)."
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Your Reference"
+msgstr ""
+
+#. module: sale
+#: view:sale.advance.payment.inv:0
+msgid "Show Lines to Invoice"
+msgstr ""
+
+#. module: sale
+#: field:sale.report,date:0
+msgid "Date Order"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,pricelist_id:0
+#: field:sale.report,pricelist_id:0
+#: field:sale.shop,pricelist_id:0
+msgid "Pricelist"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "TVA :"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:444
+#, python-format
+msgid "Customer Invoices"
+msgstr ""
+
+#. module: sale
+#: model:process.node,note:sale.process_node_order0
+msgid "Confirmed sales order to invoice."
+msgstr ""
+
+#. module: sale
+#: field:sale.order.line,sequence:0
+msgid "Sequence"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.open_board_sales
+#: model:ir.ui.menu,name:sale.menu_dashboard_sales
+#: model:process.process,name:sale.process_process_salesprocess0
+#: view:res.partner:0
+#: view:sale.order:0
+#: view:sale.report:0
+msgid "Sales"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:308
+#, python-format
+msgid ""
+"If you change the pricelist of this order (and eventually the currency), "
+"prices of existing order lines will not be updated."
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Qty"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order.line,type:0
+msgid "from stock"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Quotation Date"
+msgstr "Date de soumission"
+
+#. module: sale
+#: field:sale.order,amount_total:0
+#: view:sale.order.line:0
+msgid "Total"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+#: selection:sale.order,state:0
+#: view:sale.order.line:0
+#: selection:sale.order.line,state:0
+#: selection:sale.report,state:0
+msgid "Done"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Invoice address :"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/wizard/sale_line_invoice.py:121
+#: model:ir.model,name:sale.model_account_invoice
+#: model:process.node,name:sale.process_node_invoice0
+#: view:sale.order:0
+#, python-format
+msgid "Invoice"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "My Sales Order Lines"
+msgstr ""
+
+#. module: sale
+#: model:process.transition.action,name:sale.process_transition_action_cancel0
+#: view:sale.advance.payment.inv:0
+#: view:sale.make.invoice:0
+#: view:sale.order.line.make.invoice:0
+msgid "Cancel"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,message_follower_ids:0
+msgid "Followers"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:944
+#, python-format
+msgid "No Pricelist ! : "
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Sales Order "
+msgstr ""
+
+#. module: sale
+#: model:mail.message.subtype,description:sale.mt_order_sent
+#: model:mail.message.subtype,name:sale.mt_order_sent
+msgid "Quotation send"
+msgstr "Envoi de la soumission"
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Search Uninvoiced Lines"
+msgstr ""
+
+#. module: sale
+#: model:ir.model,name:sale.model_account_config_settings
+msgid "account.config.settings"
+msgstr ""
+
+#. module: sale
+#: sql_constraint:sale.order:0
+msgid "Order Reference must be unique per Company!"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,help:sale.action_order_line_tree2
+msgid ""
+"
\n"
+" Here is a list of each sales order line to be invoiced. You "
+"can\n"
+" invoice sales orders partially, by lines of sales order. You "
+"do\n"
+" not need this list if you invoice from the delivery orders "
+"or\n"
+" if you invoice sales totally.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Shipping address :"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:505
+#, python-format
+msgid ""
+"You cannot group sales having different currencies for the same partner."
+msgstr ""
+
+#. module: sale
+#: view:sale.advance.payment.inv:0
+msgid "Invoice Sales Order"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,invoice_quantity:0
+msgid ""
+"The sales order will automatically create the invoice proposition (draft "
+"invoice). You have to choose "
+"if you want your invoice based on ordered "
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,module_account_analytic_analysis:0
+msgid "Use contracts management"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:952
+#, python-format
+msgid ""
+"Cannot find a pricelist line matching this product and quantity.\n"
+"You have to change either the product, the quantity or the pricelist."
+msgstr ""
+
+#. module: sale
+#: model:ir.model,name:sale.model_sale_advance_payment_inv
+msgid "Sales Advance Payment Invoice"
+msgstr ""
+
+#. module: sale
+#: model:process.transition,name:sale.process_transition_invoice0
+#: model:process.transition.action,name:sale.process_transition_action_createinvoice0
+#: view:sale.advance.payment.inv:0
+#: view:sale.order:0
+#: field:sale.order,order_policy:0
+#: view:sale.order.line:0
+msgid "Create Invoice"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,amount_untaxed:0
+msgid "The amount without tax."
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Order reference"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,invoiced:0
+msgid "It indicates that an invoice has been paid."
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+#: field:sale.order.line,price_unit:0
+msgid "Unit Price"
+msgstr ""
+
+#. module: sale
+#: selection:sale.advance.payment.inv,advance_payment_method:0
+msgid "Percentage"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Other Information"
+msgstr ""
+
+#. module: sale
+#: model:email.template,body_html:sale.email_template_edi_sale
+msgid ""
+"\n"
+"
\n"
+"\n"
+"
Hello ${object.partner_id.name},
\n"
+" \n"
+"
Here is your ${object.state in ('draft', 'sent') and 'quotation' or "
+"'order confirmation'} from ${object.company_id.name}:
\n"
+"\n"
+"
\n"
+" REFERENCES \n"
+" Order number: ${object.name} \n"
+" Order total: ${object.amount_total} "
+"${object.pricelist_id.currency_id.name} \n"
+" Order date: ${object.date_order} \n"
+" % if object.origin:\n"
+" Order reference: ${object.origin} \n"
+" % endif\n"
+" % if object.client_order_ref:\n"
+" Your reference: ${object.client_order_ref} \n"
+" % endif\n"
+" % if object.user_id:\n"
+" Your contact: ${object.user_id.name}\n"
+" % endif\n"
+"
Veuillez trouver ci-joint votre ${object.state in ('draft', 'sent') "
+"and 'soumission' or 'confirmation de commande'} de la part de "
+"${object.company_id.name}:
\n"
+" "
+
+#. module: sale
+#: view:sale.order.line:0
+#: field:sale.order.line,product_id:0
+#: view:sale.report:0
+#: field:sale.report,product_id:0
+msgid "Product"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,amount_total:0
+msgid "The total amount."
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Confirm Sale"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/wizard/sale_make_invoice_advance.py:96
+#, python-format
+msgid "There is no income account defined for this product: \"%s\" (id:%d)."
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "May"
+msgstr ""
+
+#. module: sale
+#: model:email.template,subject:sale.email_template_edi_sale
+msgid ""
+"${object.company_id.name} ${object.state in ('draft', 'sent') and "
+"'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })"
+msgstr ""
+"${object.company_id.name} ${object.state in ('draft', 'sent') and "
+"'Soumission' or 'Commande'} (Réf. ${object.name or 'n/a' })"
+
+#. module: sale
+#: report:sale.order:0
+msgid "Price"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,order_policy:0
+msgid ""
+"On demand: A draft invoice can be created from the sales order when needed. "
+"\n"
+"On delivery order: A draft invoice can be created from the delivery order "
+"when the products have been delivered. \n"
+"Before delivery: A draft invoice is created from the sales order and must be "
+"paid before the products can be delivered."
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,help:sale.action_order_report_all
+msgid ""
+"This report performs analysis on your quotations and sales orders. Analysis "
+"check your sales revenues and sort it by different group criteria (salesman, "
+"partner, product, etc.) Use this report to perform analysis on sales not "
+"having invoiced yet. If you want to analyse your turnover, you should use "
+"the Invoice Analysis report in the Accounting application."
+msgstr ""
+"Ce rapport analyse vos soumissions et commandes clients. Il vérifie les "
+"revenus liés aux ventes et les classifie selon différents critères (vendeur, "
+"partenaire, article, etc.). Vous pouvez utiliser ce rapport pour analyser "
+"vos ventes non encore facturées. Si vous souhaitez analyser votre chiffre "
+"d'affaires, vous devriez utiliser le rapport \"Analyse des factures "
+"clients\" dans l'application comptabilité."
+
+#. module: sale
+#: model:ir.actions.report.xml,name:sale.report_sale_order
+msgid "Quotation / Order"
+msgstr "Soumission / Commande"
+
+#. module: sale
+#: report:sale.order:0
+msgid "Tel. :"
+msgstr ""
+
+#. module: sale
+#: view:sale.make.invoice:0
+msgid "Do you really want to create the invoice(s)?"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,help:sale.action_quotations
+msgid ""
+"
\n"
+" Click to create a quotation, the first step of a new sale.\n"
+"
\n"
+" OpenERP will help you handle efficiently the complete sale "
+"flow:\n"
+" from the quotation to the sales order, the\n"
+" delivery, the invoicing and the payment collection.\n"
+"
\n"
+" The social feature helps you organize discussions on each "
+"sales\n"
+" order, and allow your customers to keep track of the "
+"evolution\n"
+" of the sales order.\n"
+"
\n"
+" "
+msgstr ""
+"
\n"
+" Cliquez pour créer une soumission, la première étape d'une "
+"commande de vente.\n"
+"
\n"
+" OpenERP vous aidera à compléter efficacement le flux de "
+"vente :\n"
+" de la soumission à la commande de vente, la \n"
+" livraison, la facturation et les paiements.\n"
+"
\n"
+" La fonctionnalité sociale vous aide à organiser les "
+"discussions sur chaque\n"
+" commande de vente, et permet à vos clients de garder une "
+"trace de \n"
+" l'évolution des commandes de vente.\n"
+"
\n"
+" "
+
+#. module: sale
+#: view:res.partner:0
+msgid "sale.group_delivery_invoice_address"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:781
+#, python-format
+msgid ""
+"There is no Fiscal Position defined or Income category account defined for "
+"default properties of Product categories."
+msgstr ""
+
+#. module: sale
+#: model:process.node,note:sale.process_node_invoice0
+msgid "To be reviewed by the accountant."
+msgstr ""
+
+#. module: sale
+#: model:res.groups,name:sale.group_mrp_properties
+msgid "Properties on lines"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order,state:0
+msgid "Sale to Invoice"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Order Number"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+#: field:sale.order,partner_id:0
+#: field:sale.order.line,order_partner_id:0
+msgid "Customer"
+msgstr ""
+
+#. module: sale
+#: model:product.template,name:sale.advance_product_0_product_template
+msgid "Advance"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "February"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,help:sale.action_orders
+msgid ""
+"
\n"
+" Click to create a quotation that can be converted into a "
+"sales\n"
+" order.\n"
+"
\n"
+" OpenERP will help you efficiently handle the complete sales "
+"flow:\n"
+" quotation, sales order, delivery, invoicing and payment.\n"
+"
\n"
+" "
+msgstr ""
+"
\n"
+" Cliquez pour créer une soumission qui pourra être convertie "
+"en commande\n"
+" de vente.\n"
+"
\n"
+" OpenERP vous aidera à compléter efficacement le flux de "
+"vente :\n"
+" soumission, commande, livraison, facturation et paiement.\n"
+"
\n"
+" "
+
+#. module: sale
+#: field:sale.order,invoice_quantity:0
+msgid "Invoice on"
+msgstr ""
+
+#. module: sale
+#: selection:sale.advance.payment.inv,advance_payment_method:0
+msgid "Fixed price (deposit)"
+msgstr ""
+
+#. module: sale
+#: model:ir.model,name:sale.model_sale_config_settings
+msgid "sale.config.settings"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "September"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Date Ordered"
+msgstr ""
+
+#. module: sale
+#: selection:sale.order,order_policy:0
+msgid "Before Delivery"
+msgstr ""
+
+#. module: sale
+#: field:sale.order.line,product_uos:0
+msgid "Product UoS"
+msgstr ""
+
+#. module: sale
+#: model:process.node,note:sale.process_node_quotation0
+msgid "Draft state of sales order"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,origin:0
+msgid "Source Document"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,month:0
+msgid "April"
+msgstr ""
+
+#. module: sale
+#: selection:sale.report,state:0
+msgid "Manual In Progress"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/wizard/sale_line_invoice.py:107
+#: code:addons/sale/wizard/sale_make_invoice.py:42
+#: code:addons/sale/wizard/sale_make_invoice.py:55
+#, python-format
+msgid "Warning!"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_order_tree
+msgid "Old Quotations"
+msgstr "Anciennes soumissions"
+
+#. module: sale
+#: model:ir.actions.act_window,help:sale.act_res_partner_2_sale_order
+msgid ""
+"
\n"
+" Click to create a quotation or sales order for this "
+"customer.\n"
+"
\n"
+" OpenERP will help you efficiently handle the complete sale "
+"flow:\n"
+" quotation, sales order, delivery, invoicing and\n"
+" payment.\n"
+"
\n"
+" The social feature helps you organize discussions on each "
+"sales\n"
+" order, and allow your customer to keep track of the "
+"evolution\n"
+" of the sales order.\n"
+"
\n"
+" "
+msgstr ""
+"
\n"
+" Cliquez pour créer une soumission ou une commande de vente "
+"pour votre client.\n"
+"
\n"
+" OpenERP vous aidera à compléter efficacement le flux de "
+"vente :\n"
+" soumission, commande, livraison, facturation et paiement.\n"
+"
\n"
+" La fonctionnalité sociale vous aide à organiser les "
+"discussions sur chaque\n"
+" commande de vente, et permet à vos clients de garder une "
+"trace de \n"
+" l'évolution des commandes de vente.\n"
+"\n"
+"
\n"
+" "
+
+#. module: sale
+#: field:sale.order,message_summary:0
+msgid "Summary"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,message_ids:0
+msgid "Messages and communication history"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+#: view:sale.order.line:0
+msgid "Search Sales Order"
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+#: field:sale.order.line,name:0
+msgid "Description"
+msgstr ""
+
+#. module: sale
+#: view:sale.config.settings:0
+msgid ""
+"Use contract to be able to manage your services with\n"
+" multiple invoicing as part of the same contract "
+"with\n"
+" your customer."
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+msgid "Ordered month of the sales order"
+msgstr ""
+
+#. module: sale
+#: model:process.transition,name:sale.process_transition_saleinvoice0
+msgid "From a sales order"
+msgstr ""
+
+#. module: sale
+#: view:sale.order.line:0
+msgid "Confirmed sales order lines, not yet delivered"
+msgstr ""
+
+#. module: sale
+#: model:process.transition,note:sale.process_transition_saleinvoice0
+msgid ""
+"Depending on the Invoicing control of the sales order, the invoice can be "
+"based on delivered or on ordered quantities. Thus, a sales order can "
+"generates an invoice or a delivery order as soon as it is confirmed by the "
+"salesman."
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "UoS"
+msgstr ""
+
+#. module: sale
+#: selection:sale.advance.payment.inv,advance_payment_method:0
+msgid "Some order lines"
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:983
+#, python-format
+msgid "Cannot delete a sales order line which is in state '%s'."
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+#: field:sale.order,payment_term:0
+msgid "Payment Term"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Sales Order ready to be invoiced"
+msgstr ""
+
+#. module: sale
+#: field:sale.config.settings,group_invoice_so_lines:0
+msgid "Generate invoices based on the sales order lines"
+msgstr ""
+
+#. module: sale
+#: view:sale.advance.payment.inv:0
+#: view:sale.make.invoice:0
+#: view:sale.order.line.make.invoice:0
+msgid "or"
+msgstr ""
+
+#. module: sale
+#: model:ir.actions.act_window,name:sale.action_order_line_product_tree
+#: view:sale.order:0
+#: view:sale.order.line:0
+msgid "Sales Order Lines"
+msgstr ""
+
+#. module: sale
+#: help:sale.order,pricelist_id:0
+msgid "Pricelist for current sales order."
+msgstr ""
+
+#. module: sale
+#: report:sale.order:0
+msgid "Quotation N°"
+msgstr "Soumission N°"
+
+#. module: sale
+#: model:res.groups,name:sale.group_discount_per_so_line
+msgid "Discount on lines"
+msgstr ""
+
+#. module: sale
+#: field:sale.order,client_order_ref:0
+msgid "Customer Reference"
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+msgid "Picked"
+msgstr ""
+
+#. module: sale
+#: help:sale.config.settings,module_sale_margin:0
+msgid ""
+"This adds the 'Margin' on sales order.\n"
+" This gives the profitability by calculating the difference "
+"between the Unit Price and Cost Price.\n"
+" This installs the module sale_margin."
+msgstr ""
+
+#. module: sale
+#: code:addons/sale/sale.py:865
+#, python-format
+msgid ""
+"Before choosing a product,\n"
+" select a customer in the sales form."
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "Total Tax Included"
+msgstr ""
+
+#. module: sale
+#: view:sale.order:0
+msgid "New Copy of Quotation"
+msgstr "Nouvelle copie de soumission"
+
+#. module: sale
+#: view:sale.advance.payment.inv:0
+msgid ""
+"Select how you want to invoice this order. This\n"
+" will create a draft invoice that can be modified\n"
+" before validation."
+msgstr ""
+
+#. module: sale
+#: view:sale.report:0
+msgid "Ordered date of the sales order"
+msgstr ""
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index 358f105b0d0..b25509180b0 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -46,10 +46,12 @@ class ir_http(orm.AbstractModel):
try:
func, arguments = self._find_handler()
request.website_enabled = func.routing.get('website', False)
+ request.website_multilang = func.routing.get('multilang', False)
except werkzeug.exceptions.NotFound:
# either we have a language prefixed route, either a real 404
# in all cases, website processes them
request.website_enabled = True
+ request.website_multilang = True
if request.website_enabled:
if func:
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 890d6080039..751a37b3455 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -16,8 +16,6 @@
-
-
@@ -83,10 +81,12 @@
and main_object.website_meta_keywords or website_meta_keywords"/>
-
-
-
-
+
+
+
+
+
+
@@ -177,7 +177,7 @@
their performance.