diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py
index 3e86c4963b7..35afac5a508 100644
--- a/addons/account/account_invoice.py
+++ b/addons/account/account_invoice.py
@@ -391,29 +391,33 @@ class account_invoice(osv.osv):
'''
This function opens a window to compose an email, with the edi invoice template message loaded by default
'''
- mod_obj = self.pool.get('ir.model.data')
- template = mod_obj.get_object_reference(cr, uid, 'account', 'email_template_edi_invoice')
- template_id = template and template[1] or False
- res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
- res_id = res and res[1] or False
+ assert len(ids) == 1, 'This option should only be used for a single id at a time.'
+ ir_model_data = self.pool.get('ir.model.data')
+ try:
+ template_id = ir_model_data.get_object_reference(cr, uid, 'account', 'email_template_edi_invoice')[1]
+ except ValueError:
+ template_id = False
+ try:
+ compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
+ except ValueError:
+ compose_form_id = False
ctx = dict(context)
ctx.update({
'default_model': 'account.invoice',
'default_res_id': ids[0],
- 'default_use_template': True,
+ 'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
})
return {
+ 'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
- 'views': [(res_id, 'form')],
- 'view_id': res_id,
- 'type': 'ir.actions.act_window',
+ 'views': [(compose_form_id, 'form')],
+ 'view_id': compose_form_id,
'target': 'new',
'context': ctx,
- 'nodestroy': True,
}
def confirm_paid(self, cr, uid, ids, context=None):
diff --git a/addons/account/edi/invoice.py b/addons/account/edi/invoice.py
index 751eb8656f7..3e27bc5ea99 100644
--- a/addons/account/edi/invoice.py
+++ b/addons/account/edi/invoice.py
@@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Business Applications
-# Copyright (c) 2011 OpenERP S.A.
% endif
+ % if object.user_id:
Your contact: ${object.user_id.name}
+ % endif
- You can view the invoice document, download it and pay online using the following link: -
- View Invoice - % if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'): <% comp_name = quote(object.company_id.name) diff --git a/addons/account/res_config_view.xml b/addons/account/res_config_view.xml index 52c8c8daaa9..4bfd3697d89 100644 --- a/addons/account/res_config_view.xml +++ b/addons/account/res_config_view.xml @@ -227,7 +227,7 @@ styling */
- white-space: pre-line;
- width: 90%;
-}
-.oe_edi_data_row .oe_edi_inner_note {
- /* prevent wide notes from disrupting layout due to styling */
- width: 25em;
-}
-.oe_edi_shade {
- background: #e8e8e8;
-}
-.oe_edi_company_name {
- text-transform: uppercase;
- font-weight: bold;
-}
-.oe_edi_address_from {
- float: left;
-}
-.oe_edi_address_to {
- float: right;
- margin-top: 25px;
- margin-bottom: 30px;
-}
-.oe_edi_company_block_title {
- width: 375px;
- margin: 0px;
- padding: 2px 14px;
- background-color: #252525;
- border-top-left-radius: 5px 5px;
- border-top-right-radius: 5px 5px;
- background-repeat: repeat no-repeat;
-}
-.oe_edi_company_block_title .oe_edi_company_name {
- margin: 0px;
- font-size: 1em;
- color: #FFF;
-}
-.oe_edi_company_block_body {
- width: 375px;
- margin: 0px;
- padding: 5px 14px;
- line-height: 16px;
- background-color: rgb(242, 242, 242);
-}
-.oe_edi_company_block_body p {
- color: #222;
- margin: 5px 0px;
-}
-.oe_edi_summary_label {
- float: left;
-}
-.oe_edi_summary_value {
- float: right;
-}
-
-
-/** Python code highlighting **/
-/* GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann
- (http://qbnz.com/highlighter/ and http://geshi.org/) */
-.python .de1, .python .de2 {font: normal normal 1em/1.2em monospace; margin:0; padding:0; background:none; vertical-align:top;}
-.python {font-family:monospace;}
-.python .imp {font-weight: bold; color: red;}
-.python li, .python .li1 {background: #ffffff; list-style: none;}
-.python .ln {width:1px;text-align:right;margin:0;padding:0 2px;vertical-align:top;}
-.python .li2 {background: #f8f8f8;}
-.python .kw1 {color: #ff7700;font-weight:bold;}
-.python .kw2 {color: #008000;}
-.python .kw3 {color: #dc143c;}
-.python .kw4 {color: #0000cd;}
-.python .co1 {color: #808080; font-style: italic;}
-.python .coMULTI {color: #808080; font-style: italic;}
-.python .es0 {color: #000099; font-weight: bold;}
-.python .br0 {color: black;}
-.python .sy0 {color: #66cc66;}
-.python .st0 {color: #483d8b;}
-.python .nu0 {color: #ff4500;}
-.python .me1 {color: black;}
-.python span.xtra { display:block; }
-.python ol { padding: 0px; }
diff --git a/addons/edi/static/src/img/pdf.png b/addons/edi/static/src/img/pdf.png
deleted file mode 100644
index d35dfd20c2d..00000000000
Binary files a/addons/edi/static/src/img/pdf.png and /dev/null differ
diff --git a/addons/edi/static/src/js/edi.js b/addons/edi/static/src/js/edi.js
index a9c6981c6be..ee4ddbb5e87 100644
--- a/addons/edi/static/src/js/edi.js
+++ b/addons/edi/static/src/js/edi.js
@@ -1,119 +1,9 @@
-openerp.edi = function(openerp) {
-openerp.edi = {}
+openerp.edi = function(instance) {
+var _t = instance.web._t;
+instance.edi = {}
-openerp.edi.EdiView = openerp.web.Widget.extend({
- init: function(parent, db, token) {
- this._super();
- this.db = db;
- this.token = token;
- this.session = openerp.session;
- this.template = "EdiEmpty";
- this.content = "";
- this.sidebar = "";
- },
- start: function() {
- this._super();
- var self = this;
- var param = {"db": self.db, "token": self.token};
- return self.rpc('/edi/get_edi_document', param).done(this.on_document_loaded).fail(this.on_document_failed);
- },
- on_document_loaded: function(docs){
- this.doc = docs[0];
- var template_content = "Edi." + this.doc.__model + ".content";
- var template_sidebar = "Edi." + this.doc.__model + ".sidebar";
- var param = {"widget":this, "doc":this.doc};
- if (openerp.web.qweb.templates[template_sidebar]) {
- this.sidebar = openerp.web.qweb.render(template_sidebar, param);
- }
- if (openerp.web.qweb.templates[template_content]) {
- this.content = openerp.web.qweb.render(template_content, param);
- }
- this.$el.html(openerp.web.qweb.render("EdiView", param));
- this.$el.find('button.oe_edi_action_print').bind('click', this.do_print);
- this.$el.find('button#oe_edi_import_existing').bind('click', this.do_import_existing);
- this.$el.find('button#oe_edi_import_create').bind('click', this.do_import_create);
- this.$el.find('button#oe_edi_download').bind('click', this.do_download);
- this.$el.find('.oe_edi_import_choice, .oe_edi_import_choice_label').bind('click', this.toggle_choice('import'));
- this.$el.find('.oe_edi_pay_choice, .oe_edi_pay_choice_label').bind('click', this.toggle_choice('pay'));
- this.$el.find('#oe_edi_download_show_code').bind('click', this.show_code);
- },
- on_document_failed: function(response) {
- var self = this;
- var params = {
- error: response,
- //TODO: should this be _t() wrapped?
- message: "Sorry, this document cannot be located. Perhaps the link you are using has expired?"
- }
- $(openerp.web.qweb.render("DialogWarning", params)).dialog({
- title: "Document not found",
- modal: true,
- });
- },
- show_code: function($event) {
- $('#oe_edi_download_code').toggle();
- },
- get_download_url: function() {
- var l = window.location;
- var url_prefix = l.protocol + '//' + l.host;
- return url_prefix +'/edi/download?db=' + this.db + '&token=' + this.token;
- },
- get_paypal_url: function(document_type, ref_field) {
- var comp_name = encodeURIComponent(this.doc.company_id[1]);
- var doc_ref = encodeURIComponent(this.doc[ref_field]);
- var paypal_account = encodeURIComponent(this.doc.company_address.paypal_account);
- var amount = encodeURIComponent(this.doc.amount_total);
- var cur_code = encodeURIComponent(this.doc.currency.code);
- var paypal_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick" +
- "&business=" + paypal_account +
- "&item_name=" + document_type + "%20" + comp_name + "%20" + doc_ref +
- "&invoice=" + doc_ref +
- "&amount=" + amount +
- "¤cy_code=" + cur_code +
- "&button_subtype=services&no_note=1&bn=OpenERP_PayNow_" + cur_code;
- return paypal_url;
- },
- toggle_choice: function(mode) {
- return function($e) {
- $('.oe_edi_nested_block_'+mode).hide();
- $('.'+$e.target.id+'_nested').show();
- return true;
- }
- },
- do_print: function(e){
- var l = window.location;
- window.location = l.protocol + '//' + l.host + "/edi/download_attachment?db=" + this.db + "&token=" + this.token;
- },
- do_import_existing: function(e) {
- var url_download = this.get_download_url();
- var $edi_text_server_input = this.$el.find('#oe_edi_txt_server_url');
- var server_url = $edi_text_server_input.val();
- $edi_text_server_input.removeClass('invalid');
- if (!server_url) {
- $edi_text_server_input.addClass('invalid');
- return false;
- }
- var protocol = "http://";
- if (server_url.toLowerCase().lastIndexOf('http', 0) == 0 ) {
- protocol = '';
- }
- window.location = protocol + server_url + '/edi/import_url?url=' + encodeURIComponent(url_download);
- },
- do_import_create: function(e){
- var url_download = this.get_download_url();
- window.location = "https://cc.my.openerp.com/odms/create_edi?url=" + encodeURIComponent(url_download);
- },
- do_download: function(e){
- window.location = this.get_download_url();
- }
-});
+instance.edi.EdiImport = instance.web.Widget.extend({
-openerp.edi.edi_view = function (db, token) {
- openerp.session.session_bind().done(function () {
- new openerp.edi.EdiView(null,db,token).appendTo($("body").addClass('openerp'));
- });
-}
-
-openerp.edi.EdiImport = openerp.web.Widget.extend({
init: function(parent,url) {
this._super();
this.url = url;
@@ -137,7 +27,7 @@ openerp.edi.EdiImport = openerp.web.Widget.extend({
show_login: function() {
this.destroy_content();
- this.login = new openerp.web.Login(this);
+ this.login = new instance.web.Login(this);
this.login.appendTo(this.$el);
},
@@ -167,18 +57,18 @@ openerp.edi.EdiImport = openerp.web.Widget.extend({
window.location = "/";
}
}
- }).html('The document has been successfully imported!');
+ }).html(_t('The document has been successfully imported!'));
}
},
on_imported_error: function(response){
var self = this;
- var msg = "Sorry, the document could not be imported.";
+ var msg = _t("Sorry, the document could not be imported.");
if (response.data.fault_code) {
- msg += "\n Reason:" + response.data.fault_code;
+ msg += "\n " + _t("Reason:") + response.data.fault_code;
}
var params = {error: response, message: msg};
- $(openerp.web.qweb.render("CrashManagerWarning", params)).dialog({
- title: "Document Import Notification",
+ $(instance.web.qweb.render("CrashManager.warning", params)).dialog({
+ title: _t("Document Import Notification"),
modal: true,
buttons: {
Ok: function() { $(this).dialog("close"); }
@@ -187,9 +77,9 @@ openerp.edi.EdiImport = openerp.web.Widget.extend({
}
});
-openerp.edi.edi_import = function (url) {
- openerp.session.session_bind().done(function () {
- new openerp.edi.EdiImport(null,url).appendTo($("body").addClass('openerp'));
+instance.edi.edi_import = function (url) {
+ instance.session.session_bind().done(function () {
+ new instance.edi.EdiImport(null,url).appendTo($("body").addClass('openerp'));
});
}
diff --git a/addons/edi/static/src/xml/edi.xml b/addons/edi/static/src/xml/edi.xml
deleted file mode 100644
index 111cb6d9d26..00000000000
--- a/addons/edi/static/src/xml/edi.xml
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- OpenERP's Electronic Data Interchange documents are based on a generic and language
- independent JSON serialization of the document's attribute.
- It is usually very quick and straightforward to create a small plug-in for your preferred
- application that will be capable of importing any OpenERP EDI document.
- You can find out more details about how to do this and what the content of OpenERP EDI documents
- is like in the OpenERP documentation.
-
- To get started immediately, see is all it takes to use this EDI document in Python.
-
-
- - import urllib2, simplejson
- - edi_document = urllib2.urlopen('
').read()
- - document_data = simplejson.loads(edi_document)[0]
- - print "Amount: ", document_data['amount_total']
-
-
- You can download the raw EDI document here:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/edi/static/src/xml/edi_account.xml b/addons/edi/static/src/xml/edi_account.xml
deleted file mode 100644
index 3ce1daebf93..00000000000
--- a/addons/edi/static/src/xml/edi_account.xml
+++ /dev/null
@@ -1,163 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Invoice :
-
-
- Description
- Date
- Your Reference
-
-
-
-
-
-
-
-
-
-
- Product Description
- Quantity
- Unit Price
- Discount
- Price
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Net Total:
-
-
-
-
-
-
-
-
-
-
- Taxes:
-
-
-
-
-
-
-
-
-
-
- Total:
-
-
-
-
-
-
-
-
-
-
- Tax
- Base Amount
- Amount
-
-
-
-
-
-
-
-
-
-
-
-
- Notes:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You may directly pay this invoice online via Paypal's secure payment gateway:
-
-
-
-
-
-
-
-
-
-
-
- Please transfer to
- (postal address on the invoice header)
- using one of the following bank accounts. Be sure to mention the invoice
- reference on the transfer:
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/edi/static/src/xml/edi_sale_purchase.xml b/addons/edi/static/src/xml/edi_sale_purchase.xml
deleted file mode 100644
index 8d6bab67761..00000000000
--- a/addons/edi/static/src/xml/edi_sale_purchase.xml
+++ /dev/null
@@ -1,169 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Quotation :
-
-
- Request for Quotation :
-
-
- Order :
-
-
-
-
- Your Reference
- Date
- Salesperson
- Payment terms
-
-
-
-
-
-
-
-
-
-
-
-
-
- Product Description
- Quantity
- Unit Price
- Discount(%)
- Price
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Net Total:
-
-
-
-
-
-
-
-
-
-
- Taxes:
-
-
-
-
-
-
-
-
-
-
- Total:
-
-
-
-
-
-
-
-
- Notes:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You may directly pay this order online via Paypal's secure payment gateway:
-
-
-
-
-
-
-
-
-
-
-
- Please transfer to
- (postal address on the order header)
- using one of the following bank accounts. Be sure to mention the document
- reference on the transfer:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/edi/test/edi_partner_test.yml b/addons/edi/test/edi_partner_test.yml
index 9fc523bf84f..97ae669e52e 100644
--- a/addons/edi/test/edi_partner_test.yml
+++ b/addons/edi/test/edi_partner_test.yml
@@ -6,11 +6,10 @@
with an attached file, check the result, the alter the data
and reimport it.
-
- !python {model: edi.document}: |
+ !python {model: edi.edi}: |
import json
- partner_obj = self.pool.get('res.partner')
- tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, ref('base.res_partner_2'))])
- doc = self.get_document(cr, uid, tokens[0], context=context)
+ res_partner = self.pool.get('res.partner')
+ doc = self.generate_edi(cr, uid, [res_partner.browse(cr, uid, ref('base.res_partner_2'))])
edi_doc, = json.loads(doc)
# check content of the document
@@ -36,8 +35,7 @@
"Expected (%r,> %r) after import 1, got %r" % ('res.partner', ref('base.res_partner_2'), result)
# export the same partner we just created, and see if the output matches the input
- tokens = self.export_edi(cr, uid, [partner_obj.browse(cr, uid, result[1])])
- doc_output = self.get_document(cr, uid, tokens[0], context=context)
+ doc_output = self.generate_edi(cr, uid, [res_partner.browse(cr, uid, result[1])])
edi_doc_output, = json.loads(doc_output)
for attribute in ('__model', '__module', '__id', 'name', '__attachments'):
assert edi_doc_output.get(attribute) == edi_doc.get(attribute), \
diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py
index 7c69295ce9f..8837796bf9f 100644
--- a/addons/email_template/tests/test_mail.py
+++ b/addons/email_template/tests/test_mail.py
@@ -100,25 +100,27 @@ class test_message_compose(test_mail_mockup.TestMailMockups):
# ----------------------------------------
# 1. Comment on pigs
- compose_id = mail_compose.create(cr, uid,
- {'subject': 'Forget me subject', 'body': 'Dummy body'},
- {'default_composition_mode': 'comment',
- 'default_model': 'mail.group',
- 'default_res_id': self.group_pigs_id,
- 'default_use_template': False,
- 'default_template_id': email_template_id,
- 'active_ids': [self.group_pigs_id, self.group_bird_id]})
- compose = mail_compose.browse(cr, uid, compose_id)
-
- # 2. Perform 'toggle_template', to set use_template and use template_id
- mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
+ context = {
+ 'default_composition_mode': 'comment',
+ 'default_model': 'mail.group',
+ 'default_res_id': self.group_pigs_id,
+ 'default_use_template': False,
+ 'default_template_id': email_template_id,
+ 'active_ids': [self.group_pigs_id, self.group_bird_id]
+ }
+ compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context)
+ compose = mail_compose.browse(cr, uid, compose_id, context)
+ onchange_res = compose.onchange_template_id(email_template_id, 'comment', 'mail.group', self.group_pigs_id)['value']
+ onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
+ onchange_res['attachment_ids'] = [(4, attachment_id) for attachment_id in onchange_res.pop('attachment_ids', [])]
+ compose.write(onchange_res)
compose.refresh()
+
message_pids = [partner.id for partner in compose.partner_ids]
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
- # Test: mail.compose.message: subject, body, content_subtype, partner_ids
+ # Test: mail.compose.message: subject, body, partner_ids
self.assertEqual(compose.subject, _subject1, 'mail.compose.message subject incorrect')
self.assertEqual(compose.body, _body_html1, 'mail.compose.message body incorrect')
- self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
# Test: mail.compose.message: attachments
# Test: mail.message: attachments
@@ -128,41 +130,34 @@ class test_message_compose(test_mail_mockup.TestMailMockups):
self.assertIn((attach.name, base64.b64decode(attach.datas)), _attachments_test,
'mail.message attachment name / data incorrect')
- # 3. Perform 'toggle_template': template is not set anymore
- mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
- compose.refresh()
- # Test: subject, body, partner_ids
- self.assertEqual(compose.subject, False, 'mail.compose.message subject incorrect')
- self.assertEqual(compose.body, '', 'mail.compose.message body incorrect')
-
# ----------------------------------------
# CASE3: mass_mail with template
# ----------------------------------------
# 1. Mass_mail on pigs and bird, with a default_partner_ids set to check he is correctly added
- compose_id = mail_compose.create(cr, uid,
- {'subject': 'Forget me subject', 'body': 'Dummy body'},
- {'default_composition_mode': 'mass_mail',
- 'default_model': 'mail.group',
- 'default_res_id': self.group_pigs_id,
- 'default_use_template': False,
- 'default_template_id': email_template_id,
- 'default_partner_ids': [p_a_id],
- 'active_ids': [self.group_pigs_id, self.group_bird_id]})
- compose = mail_compose.browse(cr, uid, compose_id)
-
- # 2. Perform 'toggle_template', to set use_template and use template_id
- mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
+ context = {
+ 'default_composition_mode': 'mass_mail',
+ 'default_model': 'mail.group',
+ 'default_res_id': self.group_pigs_id,
+ 'default_template_id': email_template_id,
+ 'default_partner_ids': [p_a_id],
+ 'active_ids': [self.group_pigs_id, self.group_bird_id]
+ }
+ compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context)
+ compose = mail_compose.browse(cr, uid, compose_id, context)
+ onchange_res = compose.onchange_template_id(email_template_id, 'mass_mail', 'mail.group', self.group_pigs_id)['value']
+ onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
+ onchange_res['attachment_ids'] = [(4, attachment_id) for attachment_id in onchange_res.pop('attachment_ids', [])]
+ compose.write(onchange_res)
compose.refresh()
+
message_pids = [partner.id for partner in compose.partner_ids]
partner_ids = [p_a_id]
- # Test: mail.compose.message: subject, body, content_subtype, partner_ids
self.assertEqual(compose.subject, '${object.name}', 'mail.compose.message subject incorrect')
self.assertEqual(compose.body, '${object.description}', 'mail.compose.message body incorrect')
- self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
- # 3. Post the comment, get created message
+ # 2. Post the comment, get created message
mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id, self.group_bird_id]})
group_pigs.refresh()
group_bird.refresh()
diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py
index e9141029166..168d93cdbe5 100644
--- a/addons/email_template/wizard/mail_compose_message.py
+++ b/addons/email_template/wizard/mail_compose_message.py
@@ -19,11 +19,8 @@
#
##############################################################################
-import tools
-
-from osv import osv
-from osv import fields
-
+from openerp import tools
+from openerp.osv import osv, fields
def _reopen(self, res_id, model):
return {'type': 'ir.actions.act_window',
@@ -39,7 +36,6 @@ def _reopen(self, res_id, model):
},
}
-
class mail_compose_message(osv.TransientModel):
_inherit = 'mail.compose.message'
@@ -60,37 +56,19 @@ class mail_compose_message(osv.TransientModel):
record_ids = email_template_obj.search(cr, uid, [('model', '=', model)], context=context)
return email_template_obj.name_get(cr, uid, record_ids, context) + [(False, '')]
- def default_get(self, cr, uid, fields, context=None):
- if context is None:
- context = {}
- result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
- result['template_id'] = context.get('default_template_id', context.get('mail.compose.template_id', False))
-
- # pre-render the template if any
- if result.get('use_template') and result.get('template_id'):
- onchange_res = self.onchange_use_template(cr, uid, [], result.get('use_template'), result.get('template_id'),
- result.get('composition_mode'), result.get('model'), result.get('res_id'), context=context)
- result.update(onchange_res['value'])
- return result
-
_columns = {
- 'use_template': fields.boolean('Use Template'),
# incredible hack of the day: size=-1 means we want an int db column instead of an str one
'template_id': fields.selection(_get_templates, 'Template', size=-1),
}
- _defaults = {
- 'use_template': True,
- }
-
- def onchange_template_id(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
- """ - use_template not set: return default_get
- - use_template set in mass_mailing: we cannot render, so return the template values
- - use_template set: return rendered values """
- if use_template and template_id and composition_mode == 'mass_mail':
+ def onchange_template_id(self, cr, uid, ids, template_id, composition_mode, model, res_id, context=None):
+ """ - mass_mailing: we cannot render, so return the template values
+ - normal mode: return rendered values """
+ if template_id and composition_mode == 'mass_mail':
values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html'], context)
values.pop('id')
- elif use_template and template_id:
+ elif template_id:
+ # FIXME odo: change the mail generation to avoid attachment duplication
values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
# transform attachments into attachment_ids
values['attachment_ids'] = []
@@ -102,39 +80,16 @@ class mail_compose_message(osv.TransientModel):
'datas_fname': attach_fname,
'res_model': model,
'res_id': res_id,
- 'type': 'binary', # overwrite the context default_value
+ 'type': 'binary', # override default_type from context, possibly meant for another model!
}
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
else:
- values = self.default_get(cr, uid, ['body', 'body_html', 'subject', 'partner_ids', 'attachment_ids'], context=context)
+ values = self.default_get(cr, uid, ['body', 'subject', 'partner_ids', 'attachment_ids'], context=context)
if values.get('body_html'):
values['body'] = values.pop('body_html')
- values.update(use_template=use_template, template_id=template_id)
return {'value': values}
- def toggle_template(self, cr, uid, ids, context=None):
- """ hit toggle template mode button: calls onchange_use_template to
- emulate an on_change, then writes the values to update the form. """
- for record in self.browse(cr, uid, ids, context=context):
- onchange_res = self.onchange_use_template(cr, uid, ids, not record.use_template,
- record.template_id, record.composition_mode, record.model, record.res_id, context=context).get('value', {})
- # update partner_ids and attachment_ids
- onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
- onchange_res['attachment_ids'] = [(4, attachment_id) for attachment_id in onchange_res.pop('attachment_ids', [])]
- record.write(onchange_res)
- return _reopen(self, record.id, record.model)
-
- def onchange_use_template(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
- """ onchange_use_template (values: True or False). If use_template is
- False, we do as an onchange with template_id False for values """
- values = self.onchange_template_id(cr, uid, ids, use_template,
- template_id, composition_mode, model, res_id, context=context)
- # force html when using templates
- if use_template:
- values['value']['content_subtype'] = 'html'
- return values
-
def save_as_template(self, cr, uid, ids, context=None):
""" hit save as template button: current form value will be a new
template attached to the current document. """
@@ -155,7 +110,7 @@ class mail_compose_message(osv.TransientModel):
'attachment_ids': [(6, 0, [att.id for att in record.attachment_ids])]
}
template_id = email_template.create(cr, uid, values, context=context)
- record.write(record.onchange_template_id(True, template_id, record.composition_mode, record.model, record.res_id)['value'])
+ record.write(record.onchange_template_id(template_id, record.composition_mode, record.model, record.res_id)['value'])
return _reopen(self, record.id, record.model)
#------------------------------------------------------
@@ -167,7 +122,7 @@ class mail_compose_message(osv.TransientModel):
mail.compose.message, transform email_cc and email_to into partner_ids """
template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
# filter template values
- fields = ['body', 'body_html', 'subject', 'email_to', 'email_recipients', 'email_cc', 'attachments']
+ fields = ['body_html', 'subject', 'email_to', 'email_recipients', 'email_cc', 'attachments']
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
values['body'] = values.pop('body_html', '')
# transform email_to, email_cc into partner_ids
diff --git a/addons/email_template/wizard/mail_compose_message_view.xml b/addons/email_template/wizard/mail_compose_message_view.xml
index 5afaf9745e1..34a2112e1ac 100644
--- a/addons/email_template/wizard/mail_compose_message_view.xml
+++ b/addons/email_template/wizard/mail_compose_message_view.xml
@@ -7,25 +7,17 @@
mail.compose.message
-
-
-
-
-
-
- Use template
-
-
-
-
-
-
+
+
+ Use template
+
+
+
+
+
diff --git a/addons/mail/tests/test_mail.py b/addons/mail/tests/test_mail.py
index 99ccd41e1f7..bc6df813ef7 100644
--- a/addons/mail/tests/test_mail.py
+++ b/addons/mail/tests/test_mail.py
@@ -437,9 +437,8 @@ class test_mail(test_mail_mockup.TestMailMockups):
# Mail data
_subject = 'Pigs'
- _body_text = 'Pigs rules'
- _msg_reply = 'Re: Pigs'
- _msg_body = 'Pigs rules
'
+ _body = 'Pigs rule'
+ _reply_subject = 'Re: Pigs'
_attachments = [
{'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')},
{'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')}
@@ -462,9 +461,9 @@ class test_mail(test_mail_mockup.TestMailMockups):
# 1. Comment group_pigs with body_text and subject
compose_id = mail_compose.create(cr, uid,
- {'subject': _subject, 'body_text': _body_text, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
+ {'subject': _subject, 'body': _body, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id,
- 'default_content_subtype': 'plaintext'})
+ 'default_content_subtype': 'plaintext'})
compose = mail_compose.browse(cr, uid, compose_id)
# Test: mail.compose.message: composition_mode, model, res_id
self.assertEqual(compose.composition_mode, 'comment', 'mail.compose.message incorrect composition_mode')
@@ -476,8 +475,8 @@ class test_mail(test_mail_mockup.TestMailMockups):
group_pigs.refresh()
message = group_pigs.message_ids[0]
# Test: mail.message: subject, body inside pre
- self.assertEqual(message.subject, False, 'mail.message incorrect subject')
- self.assertEqual(message.body, _msg_body, 'mail.message incorrect body')
+ self.assertEqual(message.subject, _subject, 'mail.message incorrect subject')
+ self.assertEqual(message.body, _body, 'mail.message incorrect body')
# Test: mail.message: notified_partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
msg_pids = [partner.id for partner in message.notified_partner_ids]
test_pids = [p_b_id, p_c_id, p_d_id]
@@ -495,13 +494,12 @@ class test_mail(test_mail_mockup.TestMailMockups):
{'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
{'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id})
compose = mail_compose.browse(cr, uid, compose_id)
- # Test: model, res_id, parent_id, content_subtype
+ # Test: model, res_id, parent_id
self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
- self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
# Test: mail.message: subject as Re:.., body in html, parent_id
- self.assertEqual(compose.subject, _msg_reply, 'mail.message incorrect subject')
+ self.assertEqual(compose.subject, _reply_subject, 'mail.message incorrect subject')
# self.assertIn('Administrator wrote:Pigs rules
', compose.body, 'mail.message body is incorrect')
self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'mail.message parent_id incorrect')
# Test: mail.message: attachments
@@ -518,8 +516,6 @@ class test_mail(test_mail_mockup.TestMailMockups):
{'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': False,
'active_ids': [self.group_pigs_id, group_bird_id]})
compose = mail_compose.browse(cr, uid, compose_id)
- # Test: content_subtype is html
- self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
# 2. Post the comment, get created message for each group
mail_compose.send_mail(cr, uid, [compose_id],
diff --git a/addons/mail/tests/test_mail_access_rights.py b/addons/mail/tests/test_mail_access_rights.py
index 7594de87faf..1b2f7f85d71 100644
--- a/addons/mail/tests/test_mail_access_rights.py
+++ b/addons/mail/tests/test_mail_access_rights.py
@@ -21,7 +21,7 @@
from openerp.addons.mail.tests import test_mail_mockup
from openerp.osv.orm import except_orm
-from openerp.tools.misc import mute_logger
+from openerp.tools import mute_logger
class test_mail_access_rights(test_mail_mockup.TestMailMockups):
@@ -52,6 +52,7 @@ class test_mail_access_rights(test_mail_mockup.TestMailMockups):
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
self.partner_raoul_id = self.user_raoul.partner_id.id
+ @mute_logger('openerp.addons.base.ir.ir_model','openerp.osv.orm')
def test_00_mail_message_search_access_rights(self):
""" Test mail_message search override about access rights. """
cr, uid, group_pigs_id = self.cr, self.uid, self.group_pigs_id
@@ -85,7 +86,7 @@ class test_mail_access_rights(test_mail_mockup.TestMailMockups):
msg_ids = self.mail_message.search(cr, uid, [('subject', 'like', '_Test')])
self.assertEqual(set([msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
- @mute_logger('openerp.osv.orm')
+ @mute_logger('openerp.addons.base.ir.ir_model','openerp.osv.orm')
def test_05_mail_message_read_access_rights(self):
""" Test basic mail_message read access rights. """
cr, uid = self.cr, self.uid
@@ -133,8 +134,7 @@ class test_mail_access_rights(test_mail_mockup.TestMailMockups):
self.assertRaises(except_orm, self.mail_message.read,
cr, user_bert_id, message_id)
- @mute_logger('openerp.addons.base.ir.ir_model')
- @mute_logger('openerp.osv.orm')
+ @mute_logger('openerp.addons.base.ir.ir_model','openerp.osv.orm')
def test_10_mail_flow_access_rights(self):
""" Test a Chatter-looks alike flow. """
cr, uid = self.cr, self.uid
@@ -182,14 +182,14 @@ class test_mail_access_rights(test_mail_mockup.TestMailMockups):
# Do: Bert create a mail.compose.message record, because he uses the wizard
compose_id = mail_compose.create(cr, user_bert_id,
- {'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': []},
+ {'subject': 'Subject', 'body': 'Body text', 'partner_ids': []},
# {'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_jobs_id})
mail_compose.send_mail(cr, user_bert_id, [compose_id])
self.user_demo_id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, 'base', 'user_demo')[1]
compose_id = mail_compose.create(cr, self.user_demo_id,
- {'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': []},
+ {'subject': 'Subject', 'body': 'Body text', 'partner_ids': []},
# {'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_jobs_id})
mail_compose.send_mail(cr, self.user_demo_id, [compose_id])
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index c409d4ab076..58066c6f2c2 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -81,7 +81,7 @@ class mail_compose_message(osv.TransientModel):
elif composition_mode == 'comment' and model and res_id:
vals = self.get_record_data(cr, uid, model, res_id, context=context)
elif composition_mode == 'mass_mail' and model and active_ids:
- vals = {'model': model, 'res_id': res_id, 'content_subtype': 'html'}
+ vals = {'model': model, 'res_id': res_id}
else:
vals = {'model': model, 'res_id': res_id}
if composition_mode:
@@ -106,16 +106,10 @@ class mail_compose_message(osv.TransientModel):
'mail_compose_message_ir_attachments_rel',
'wizard_id', 'attachment_id', 'Attachments'),
'filter_id': fields.many2one('ir.filters', 'Filters'),
- 'body_text': fields.text('Plain-text Contents'),
- 'content_subtype': fields.char('Message content subtype', size=32, readonly=1,
- help="Type of message, usually 'html' or 'plain', used to select "\
- "plain-text or rich-text contents accordingly"),
}
_defaults = {
'composition_mode': 'comment',
- 'content_subtype': lambda self, cr, uid, ctx={}: 'html',
- 'body_text': lambda self, cr, uid, ctx={}: False,
'body': lambda self, cr, uid, ctx={}: '',
'subject': lambda self, cr, uid, ctx={}: False,
'partner_ids': lambda self, cr, uid, ctx={}: [],
@@ -172,32 +166,9 @@ class mail_compose_message(osv.TransientModel):
'parent_id': message_data.id,
'subject': reply_subject,
'partner_ids': partner_ids,
- 'content_subtype': 'html',
}
return result
- def toggle_content_subtype(self, cr, uid, ids, context=None):
- """ toggle content_subtype: calls onchange_formatting to emulate an
- on_change, then writes the value to update the form. """
- for record in self.browse(cr, uid, ids, context=context):
- content_st_new_value = 'plain' if record.content_subtype == 'html' else 'html'
- onchange_res = self.onchange_content_subtype(cr, uid, ids, content_st_new_value, record.model, record.res_id, context=context)
- self.write(cr, uid, [record.id], onchange_res['value'], context=context)
- return True
-
- def onchange_content_subtype(self, cr, uid, ids, value, model, res_id, context=None):
- """ This onchange allows to have some specific behavior when switching
- between text or html mode. This method can be overridden.
- :param values: 'plain' or 'html'
- """
- return {'value': {'content_subtype': value}}
-
- def dummy(self, cr, uid, ids, context=None):
- """ TDE: defined to have buttons that do basically nothing. It is
- currently impossible to have buttons that do nothing special
- in views (if type not specified, considered as 'object'). """
- return True
-
#------------------------------------------------------
# Wizard validation and send
#------------------------------------------------------
@@ -218,8 +189,8 @@ class mail_compose_message(osv.TransientModel):
for res_id in res_ids:
# default values, according to the wizard options
post_values = {
- 'subject': wizard.subject if wizard.content_subtype == 'html' else False,
- 'body': wizard.body if wizard.content_subtype == 'html' else '%s
' % tools.ustr(wizard.body_text),
+ 'subject': wizard.subject,
+ 'body': wizard.body,
'parent_id': wizard.parent_id and wizard.parent_id.id,
'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml
index 01ceb770f49..57fc0aaf729 100644
--- a/addons/mail/wizard/mail_compose_message_view.xml
+++ b/addons/mail/wizard/mail_compose_message_view.xml
@@ -12,7 +12,6 @@
-
@@ -25,8 +24,7 @@
-
+
diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js
index 929d62681c8..9e1b73e1f3b 100644
--- a/addons/pad/static/src/js/pad.js
+++ b/addons/pad/static/src/js/pad.js
@@ -37,7 +37,10 @@ openerp.pad = function(instance) {
}else{
this.content = '... Loading pad ...';
$.get(value+'/export/html').success(function(data){
- self.$('.oe_pad_content').html(''+data+'');
+ groups = /\<\s*body\s*\>(.*?)\<\s*\/body\s*\>/.exec(data);
+ data = (groups || []).length >= 2 ? groups[1] : '';
+ self.$('.oe_pad_content').html('');
+ self.$('.oe_pad_readonly').html(data);
}).error(function(){
self.$('.oe_pad_content').text('Unable to load pad');
});
diff --git a/addons/portal/__init__.py b/addons/portal/__init__.py
index 2bfc6df0d96..0045a557a49 100644
--- a/addons/portal/__init__.py
+++ b/addons/portal/__init__.py
@@ -22,6 +22,6 @@
import portal
import mail_mail
import wizard
-
+import acquirer
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/portal/__openerp__.py b/addons/portal/__openerp__.py
index 66a736c6048..4f37a2205d7 100644
--- a/addons/portal/__openerp__.py
+++ b/addons/portal/__openerp__.py
@@ -49,8 +49,10 @@ very handy when used in combination with the module 'share'.
'portal_view.xml',
'wizard/portal_wizard_view.xml',
'wizard/share_wizard_view.xml',
+ 'acquirer_view.xml',
],
'demo': ['portal_demo.xml'],
+ 'css': ['static/src/css/portal.css'],
'installable': True,
}
diff --git a/addons/portal/acquirer.py b/addons/portal/acquirer.py
new file mode 100644
index 00000000000..1361d627105
--- /dev/null
+++ b/addons/portal/acquirer.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2012-TODAY 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 logging
+from urllib import quote as quote
+
+from openerp.osv import osv, fields
+from openerp.tools.translate import _
+from openerp.tools import float_repr
+
+_logger = logging.getLogger(__name__)
+try:
+ from mako.template import Template as MakoTemplate
+except ImportError:
+ _logger.warning("payment_acquirer: mako templates not available, payment acquirer will not work!")
+
+
+class acquirer(osv.Model):
+ _name = 'portal.payment.acquirer'
+ _description = 'Online Payment Acquirer'
+
+ _columns = {
+ 'name': fields.char('Name', required=True),
+ 'form_template': fields.text('Payment form template (HTML)', translate=True, required=True),
+ 'visible': fields.boolean('Visible', help="Make this payment acquirer available in portal forms (Customer invoices, etc.)"),
+ }
+
+ _default = {
+ 'visible': True,
+ }
+
+ def render(self, cr, uid, id, object, reference, currency, amount, context=None, **kwargs):
+ """ Renders the form template of the given acquirer as a mako template """
+ if not isinstance(id, (int,long)):
+ id = id[0]
+ this = self.browse(cr, uid, id)
+ if context is None:
+ context = {}
+ try:
+ i18n_kind = _(object._description) # may fail to translate, but at least we try
+ result = MakoTemplate(this.form_template).render_unicode(object=object,
+ reference=reference,
+ currency=currency,
+ amount=amount,
+ kind=i18n_kind,
+ quote=quote,
+ # context kw would clash with mako internals
+ ctx=context,
+ format_exceptions=True)
+ return result.strip()
+ except Exception:
+ _logger.exception("failed to render mako template value for payment.acquirer %s: %r", this.name, this.form_template)
+ return
+
+ def _wrap_payment_block(self, cr, uid, html_block, amount, currency, context=None):
+ payment_header = _('Pay safely online')
+ amount_str = float_repr(amount, self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
+ currency_str = currency.symbol or currency.name
+ amount = u"%s %s" % ((currency_str, amount_str) if currency.position == 'before' else (amount_str, currency_str))
+ result = """
+
+ %s
+ %s
+
+ %%s
+ """ % (amount, payment_header)
+ return result % html_block
+
+ def render_payment_block(self, cr, uid, object, reference, currency, amount, context=None, **kwargs):
+ """ Renders all visible payment acquirer forms for the given rendering context, and
+ return them wrapped in an appropriate HTML block, ready for direct inclusion
+ in an OpenERP v7 form view """
+ acquirer_ids = self.search(cr, uid, [('visible', '=', True)])
+ if not acquirer_ids:
+ return
+ html_forms = []
+ for this in self.browse(cr, uid, acquirer_ids):
+ html_forms.append(this.render(object, reference, currency, amount, context=context, **kwargs))
+ html_block = '\n'.join(filter(None,html_forms))
+ return self._wrap_payment_block(cr, uid, html_block, amount, currency, context=context)
diff --git a/addons/portal/acquirer_view.xml b/addons/portal/acquirer_view.xml
new file mode 100644
index 00000000000..a415a431ad2
--- /dev/null
+++ b/addons/portal/acquirer_view.xml
@@ -0,0 +1,65 @@
+
+
+
+
+ portal.payment.acquirer
+
+
+
+
+
+ portal.payment.acquirer
+
+
+
+
+
+
+
+
+ portal.payment.acquirer
+
+
+
+
+
+
+
+
+
+ Payment Acquirers
+ portal.payment.acquirer
+
+
+
+
+
diff --git a/addons/portal/portal_data.xml b/addons/portal/portal_data.xml
index 90a570bc1d5..d7550e0f2b4 100644
--- a/addons/portal/portal_data.xml
+++ b/addons/portal/portal_data.xml
@@ -27,6 +27,23 @@
form
+
+
+
+ Paypal
+
+
+
+
+
+
+
+
+% endif
+ ]]>
+
diff --git a/addons/portal/security/ir.model.access.csv b/addons/portal/security/ir.model.access.csv
index 9202c7f4087..b6d932bf7ee 100644
--- a/addons/portal/security/ir.model.access.csv
+++ b/addons/portal/security/ir.model.access.csv
@@ -4,3 +4,5 @@ access_res_partner,res.partner,base.model_res_partner,portal.group_portal,1,0,0,
access_res_partner_address,res.partner_address,base.model_res_partner_address,portal.group_portal,1,0,0,0
access_res_partner_category,res.partner_category,base.model_res_partner_category,portal.group_portal,1,0,0,0
access_res_partner_title,res.partner_title,base.model_res_partner_title,portal.group_portal,1,0,0,0
+access_acquirer,portal.payment.acquirer,portal.model_portal_payment_acquirer,,1,0,0,0
+access_acquirer_all,portal.payment.acquirer,portal.model_portal_payment_acquirer,base.group_system,1,1,1,1
diff --git a/addons/portal/static/src/css/portal.css b/addons/portal/static/src/css/portal.css
new file mode 100644
index 00000000000..45c0cdd7805
--- /dev/null
+++ b/addons/portal/static/src/css/portal.css
@@ -0,0 +1,81 @@
+
+.openerp .oe_application .oe_form_sheetbg {
+ /* Establish a stacking context on top of which the
+ payment_acquirers::before element can be positioned */
+ position: relative;
+ z-index: 0;
+}
+
+.openerp .payment_acquirers {
+ margin: -40px 0 -32px -24px;
+ position: relative;
+ padding: 10px 15px;
+ right: -153px;
+
+ background: #729FCF;
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#729FCF), to(#3465A4));
+ background-image: -webkit-linear-gradient(top, #729FCF, #3465A4);
+ background-image: -moz-linear-gradient(top, #729FCF, #3465A4);
+ background-image: -ms-linear-gradient(top, #729FCF, #3465A4);
+ background-image: -o-linear-gradient(top, #729FCF, #3465A4);
+ background-image: linear-gradient(to bottom, #729FCF, #3465A4);
+ border-bottom: 1px solid #043574;
+
+ -webkit-box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45);
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45);
+}
+
+.openerp .payment_acquirers form {
+ display: inline-block;
+ vertical-align: top;
+}
+
+.openerp .payment_acquirers form input,
+.openerp .payment_acquirers form textarea,
+.openerp .payment_acquirers form select
+{
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ background: transparent;
+ border: none;
+ padding: none;
+}
+
+.openerp .payment_acquirers::after {
+ content: " ";
+ display: block;
+ width: 10px;
+ height: 20px;
+ position: absolute;
+ bottom: 0;
+ right: 1px;
+
+ margin-bottom: -6px;
+ background: #043574;
+
+ -webkit-transform: skewY(-45deg);
+ -moz-transform: skewY(-45deg);
+ -ms-transform: skewY(-45deg);
+ -o-transform: skewY(-45deg);
+ transform: skewY(-45deg);
+
+ -webkit-box-shadow: inset 1px -1px 2px black, -1px 1px 3px black;
+ box-shadow: inset 1px -1px 2px black, -1px 1px 3px black;
+
+ /* push it under all its siblings, just on top of its root
+ in the z-index stack: div.oe_form_sheetbg */
+ z-index: -1;
+}
+
+.openerp .payment_acquirers .payment_header {
+ display: inline-block;
+ font-weight: bold;
+ font-size: 110%;
+ padding-right: 15px;
+ color: white;
+ text-shadow: 0 1px 1px #729FCF, 0 -1px 1px #3465A4;
+}
+.openerp .payment_acquirers .payment_header .payment_amount {
+ font-size: 130%;
+ padding: 6px 0px;
+}
\ No newline at end of file
diff --git a/addons/portal_sale/__init__.py b/addons/portal_sale/__init__.py
index 26c654db9dd..f8c8c7d6446 100644
--- a/addons/portal_sale/__init__.py
+++ b/addons/portal_sale/__init__.py
@@ -19,3 +19,5 @@
#
##############################################################################
+import portal_sale
+import res_config
\ No newline at end of file
diff --git a/addons/portal_sale/__openerp__.py b/addons/portal_sale/__openerp__.py
index 8d7cd8b3935..a993ab9c462 100644
--- a/addons/portal_sale/__openerp__.py
+++ b/addons/portal_sale/__openerp__.py
@@ -26,18 +26,34 @@
'category': 'Tools',
'complexity': 'easy',
'description': """
-This module adds sale menu and features to your portal if sale and portal are installed.
-========================================================================================
+This module adds a Sales menu to your portal as soon as sale and portal are installed.
+======================================================================================
+
+After installing this module, portal users will be able to access their own documents
+via the following menus:
+
+ - Quotations
+ - Sale Orders
+ - Delivery Orders
+ - Products (public ones)
+ - Invoices
+ - Payments/Refunds
+
+If online payment acquirers are configured, portal users will also be given the opportunity to
+pay online on their Sale Orders and Invoices that are not paid yet. Paypal is included
+by default, you simply need to configure a Paypal account in the Accounting/Invoicing settings.
""",
'author': 'OpenERP SA',
'depends': ['sale_stock','portal'],
'data': [
'security/portal_security.xml',
'portal_sale_view.xml',
+ 'portal_sale_data.xml',
+ 'res_config_view.xml',
'security/ir.model.access.csv',
],
- 'installable': True,
'auto_install': True,
'category': 'Hidden',
}
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/portal_sale/portal_sale.py b/addons/portal_sale/portal_sale.py
new file mode 100644
index 00000000000..818da5e8784
--- /dev/null
+++ b/addons/portal_sale/portal_sale.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2012 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 .
+#
+##############################################################################
+
+from openerp.osv import osv, fields
+
+class sale_order(osv.Model):
+ _inherit = 'sale.order'
+
+ # make the real method inheritable
+ _payment_block_proxy = lambda self,*a,**kw: self._portal_payment_block(*a, **kw)
+
+ _columns = {
+ 'portal_payment_options': fields.function(_payment_block_proxy, type="html", string="Portal Payment Options"),
+ }
+
+ def _portal_payment_block(self, cr, uid, ids, fieldname, arg, context=None):
+ result = dict.fromkeys(ids, False)
+ payment_acquirer = self.pool.get('portal.payment.acquirer')
+ for this in self.browse(cr, uid, ids, context=context):
+ if this.state not in ('draft','cancel') and not this.invoiced:
+ result[this.id] = payment_acquirer.render_payment_block(cr, uid, this, this.name,
+ this.pricelist_id.currency_id, this.amount_total, context=context)
+ return result
+
+ def action_quotation_send(self, cr, uid, ids, context=None):
+ ''' Override to use a modified template that includes a portal signup link '''
+ action_dict = super(sale_order, self).action_quotation_send(cr, uid, ids, context=context)
+ try:
+ template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal_sale', 'email_template_edi_sale')[1]
+ # assume context is still a dict, as prepared by super
+ ctx = action_dict['context']
+ ctx['default_template_id'] = template_id
+ ctx['default_use_template'] = True
+ except Exception:
+ pass
+ return action_dict
+
+class account_invoice(osv.Model):
+ _inherit = 'account.invoice'
+
+ # make the real method inheritable
+ _payment_block_proxy = lambda self,*a,**kw: self._portal_payment_block(*a, **kw)
+
+ _columns = {
+ 'portal_payment_options': fields.function(_payment_block_proxy, type="html", string="Portal Payment Options"),
+ }
+
+ def _portal_payment_block(self, cr, uid, ids, fieldname, arg, context=None):
+ result = dict.fromkeys(ids, False)
+ payment_acquirer = self.pool.get('portal.payment.acquirer')
+ for this in self.browse(cr, uid, ids, context=context):
+ if this.state not in ('draft','done') and not this.reconciled:
+ result[this.id] = payment_acquirer.render_payment_block(cr, uid, this, this.number,
+ this.currency_id, this.residual, context=context)
+ return result
+
+ def action_invoice_sent(self, cr, uid, ids, context=None):
+ ''' Override to use a modified template that includes a portal signup link '''
+ action_dict = super(account_invoice, self).action_invoice_sent(cr, uid, ids, context=context)
+ try:
+ template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal_sale', 'email_template_edi_invoice')[1]
+ # assume context is still a dict, as prepared by super
+ ctx = action_dict['context']
+ ctx['default_template_id'] = template_id
+ ctx['default_use_template'] = True
+ except Exception:
+ pass
+ return action_dict
diff --git a/addons/portal_sale/portal_sale_data.xml b/addons/portal_sale/portal_sale_data.xml
new file mode 100644
index 00000000000..2abb70b068c
--- /dev/null
+++ b/addons/portal_sale/portal_sale_data.xml
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+ Sale Order - Send by Email (Portal)
+ ${object.user_id.email or ''}
+ ${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })
+ ${object.partner_invoice_id.id}
+
+
+
+ ${(object.name or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}
+
+
+ Hello${object.partner_id.name and ' ' or ''}${object.partner_id.name or ''},
+
+ Here is your ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} from ${object.company_id.name}:
+
+
+ REFERENCES
+ Order number: ${object.name}
+ Order total: ${object.amount_total} ${object.pricelist_id.currency_id.name}
+ Order date: ${object.date_order}
+ % if object.origin:
+ Order reference: ${object.origin}
+ % endif
+ % if object.client_order_ref:
+ Your reference: ${object.client_order_ref}
+ % endif
+ % if object.user_id:
+ Your contact: ${object.user_id.name}
+ % endif
+
+
+ <%
+ action = 'portal_sale.action_quotations_portal' if object.state in ('draft', 'sent') else 'portal_sale.action_orders_portal'
+ object.partner_id.signup_prepare()
+ signup_url = object.partner_id._get_signup_url_for_action(action=action,view_type='form',res_id=object.id)[object.partner_id.id]
+ %>
+ % if signup_url:
+
+ You can access this document and pay online via our Customers Portal:
+
+ View ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'}
+ % endif
+
+ % if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state != 'draft':
+ <%
+ comp_name = quote(object.company_id.name)
+ order_name = quote(object.name)
+ paypal_account = quote(object.company_id.paypal_account)
+ order_amount = quote(str(object.residual))
+ cur_name = quote(object.pricelist_id.currency_id.name)
+ paypal_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=%s&item_name=%s%%20Order%%20%s" \
+ "&invoice=%s&amount=%s¤cy_code=%s&button_subtype=services&no_note=1" \
+ "&bn=OpenERP_Order_PayNow_%s" % \
+ (paypal_account,comp_name,order_name,order_name,order_amount,cur_name,cur_name)
+ %>
+
+ It is also possible to directly pay with Paypal:
+
+
+
+ % endif
+
+
+ If you have any question, do not hesitate to contact us.
+ Thank you for choosing ${object.company_id.name or 'us'}!
+
+
+
+
+ ${object.company_id.name}
+
+
+
+ % if object.company_id.street:
+ ${object.company_id.street}
+ % endif
+ % if object.company_id.street2:
+ ${object.company_id.street2}
+ % endif
+ % if object.company_id.city or object.company_id.zip:
+ ${object.company_id.zip} ${object.company_id.city}
+ % endif
+ % if object.company_id.country_id:
+ ${object.company_id.state_id and ('%s, ' % object.company_id.state_id.name) or ''} ${object.company_id.country_id.name or ''}
+ % endif
+
+ % if object.company_id.phone:
+
+ Phone: ${object.company_id.phone}
+
+ % endif
+ % if object.company_id.website:
+
+ Web : ${object.company_id.website}
+
+ %endif
+
+
+
+ ]]>
+
+
+
+ Invoice - Send by Email (Portal)
+ ${object.user_id.email or object.company_id.email or 'noreply@localhost'}
+ ${object.company_id.name} Invoice (Ref ${object.number or 'n/a' })
+ ${object.partner_id.id}
+
+
+
+ Invoice_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}
+
+
+ Hello${object.partner_id.name and ' ' or ''}${object.partner_id.name or ''},
+
+ A new invoice is available for ${object.partner_id.name}:
+
+
+ REFERENCES
+ Invoice number: ${object.number}
+ Invoice total: ${object.amount_total} ${object.currency_id.name}
+ Invoice date: ${object.date_invoice}
+ % if object.origin:
+ Order reference: ${object.origin}
+ % endif
+ % if object.user_id:
+ Your contact: ${object.user_id.name}
+ % endif
+
+
+ <%
+ action = 'portal_sale.portal_action_invoices'
+ object.partner_id.signup_prepare()
+ signup_url = object.partner_id._get_signup_url_for_action(action=action,view_type='form',res_id=object.id)[object.partner_id.id]
+ %>
+ % if signup_url:
+
+ You can access the invoice document and pay online via our Customers Portal:
+
+ View Invoice
+ % endif
+
+ % if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'):
+ <%
+ comp_name = quote(object.company_id.name)
+ inv_number = quote(object.number)
+ paypal_account = quote(object.company_id.paypal_account)
+ inv_amount = quote(str(object.amount_residual))
+ cur_name = quote(object.currency_id.name)
+ paypal_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=%s&item_name=%s%%20Invoice%%20%s&" \
+ "invoice=%s&amount=%s¤cy_code=%s&button_subtype=services&no_note=1&bn=OpenERP_Invoice_PayNow_%s" % \
+ (paypal_account,comp_name,inv_number,inv_number,inv_amount,cur_name,cur_name)
+ %>
+
+ It is also possible to directly pay with Paypal:
+
+
+
+ % endif
+
+
+ If you have any question, do not hesitate to contact us.
+ Thank you for choosing ${object.company_id.name or 'us'}!
+
+
+
+
+ ${object.company_id.name}
+
+
+
+ % if object.company_id.street:
+ ${object.company_id.street}
+ % endif
+ % if object.company_id.street2:
+ ${object.company_id.street2}
+ % endif
+ % if object.company_id.city or object.company_id.zip:
+ ${object.company_id.zip} ${object.company_id.city}
+ % endif
+ % if object.company_id.country_id:
+ ${object.company_id.state_id and ('%s, ' % object.company_id.state_id.name) or ''} ${object.company_id.country_id.name or ''}
+ % endif
+
+ % if object.company_id.phone:
+
+ Phone: ${object.company_id.phone}
+
+ % endif
+ % if object.company_id.website:
+
+ Web : ${object.company_id.website}
+
+ %endif
+
+
+
+ ]]>
+
+
+
+
\ No newline at end of file
diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml
index b46175f7feb..d6c2f288205 100644
--- a/addons/portal_sale/portal_sale_view.xml
+++ b/addons/portal_sale/portal_sale_view.xml
@@ -1,12 +1,35 @@
+
+
+
+ sale.order.form.payment
+ sale.order
+
+
+
+
+
+
+
+
+ account.invoice.form.payment
+ account.invoice
+
+
+
+
+
+
+
+
-
+
Quotations
ir.actions.act_window
sale.order
@@ -16,8 +39,8 @@
You don't have any quotation.
-
- Sales Orders
+
+ Sale Orders
ir.actions.act_window
sale.order
tree,form,calendar,graph
@@ -48,8 +71,8 @@
There are no public products.
-
- Customer Invoices
+
+ Invoices
account.invoice
tree,form,calendar,graph
[('type','=','out_invoice')]
@@ -57,9 +80,21 @@
You don't have any invoice.
+
+
+
+ tree
+
+
+
+
+
+ form
+
+
-
- Customer Payment
+
+ Refunds/Payments
account.voucher
[('journal_id.type', 'in', ['bank', 'cash']), ('type','=','receipt')]
{'type':'receipt'}
@@ -68,19 +103,17 @@
You don't have any refunds or payments.
-
-
-
+
+
-
-
-
-
-
+
+
diff --git a/addons/portal_sale/res_config.py b/addons/portal_sale/res_config.py
new file mode 100644
index 00000000000..d96a63f173e
--- /dev/null
+++ b/addons/portal_sale/res_config.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2012-TODAY 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 .
+#
+##############################################################################
+
+from openerp.osv import fields, osv
+
+class sale_portal_config_settings(osv.TransientModel):
+ _inherit = 'account.config.settings'
+
+ _columns = {
+ 'group_payment_options': fields.boolean('Show payment buttons to employees too',
+ implied_group='portal_sale.group_payment_options',
+ help="Show online payment options on Sale Orders and Customer Invoices to employees. "
+ "If not checked, these options are only visible to portal users."),
+ }
\ No newline at end of file
diff --git a/addons/portal_sale/res_config_view.xml b/addons/portal_sale/res_config_view.xml
new file mode 100644
index 00000000000..ce33a1a1a72
--- /dev/null
+++ b/addons/portal_sale/res_config_view.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ account.config.settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/portal_sale/security/portal_security.xml b/addons/portal_sale/security/portal_security.xml
index 19d44947f03..e86258dcdd2 100644
--- a/addons/portal_sale/security/portal_security.xml
+++ b/addons/portal_sale/security/portal_security.xml
@@ -2,6 +2,18 @@
+
+ View Online Payment Options
+
+ Members of this group see the online payment options
+on Sale Orders and Customer Invoices. These options are meant for customers who are accessing
+their documents through the portal.
+
+
+
+
+
+
Portal Personal Quotations/Sales Orders
diff --git a/addons/product/product.py b/addons/product/product.py
index 27cd4249a60..1333c9581a7 100644
--- a/addons/product/product.py
+++ b/addons/product/product.py
@@ -100,6 +100,23 @@ class product_uom(osv.osv):
def _factor_inv_write(self, cursor, user, id, name, value, arg, context=None):
return self.write(cursor, user, id, {'factor': self._compute_factor_inv(value)}, context=context)
+ def name_create(self, cr, uid, name, context=None):
+ """ The UoM category and factor are required, so we'll have to add temporary values
+ for imported UoMs """
+ uom_categ = self.pool.get('product.uom.categ')
+ # look for the category based on the english name, i.e. no context on purpose!
+ # TODO: should find a way to have it translated but not created until actually used
+ categ_misc = 'Unsorted/Imported Units'
+ categ_id = uom_categ.search(cr, uid, [('name', '=', categ_misc)])
+ if categ_id:
+ categ_id = categ_id[0]
+ else:
+ categ_id, _ = uom_categ.name_create(cr, uid, categ_misc)
+ uom_id = self.create(cr, uid, {self._rec_name: name,
+ 'category_id': categ_id,
+ 'factor': 1})
+ return self.name_get(cr, uid, [uom_id], context=context)[0]
+
def create(self, cr, uid, data, context=None):
if 'factor_inv' in data:
if data['factor_inv'] <> 1:
diff --git a/addons/purchase/edi/purchase_order.py b/addons/purchase/edi/purchase_order.py
index d1c00a3e60d..cdb4c0ca73d 100644
--- a/addons/purchase/edi/purchase_order.py
+++ b/addons/purchase/edi/purchase_order.py
@@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Business Applications
-# Copyright (c) 2011 OpenERP S.A.
+# Copyright (c) 2011-2012 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
@@ -19,13 +19,8 @@
#
##############################################################################
-from datetime import datetime, timedelta
-from dateutil.relativedelta import relativedelta
-
-from osv import fields, osv, orm
+from openerp.osv import osv
from edi import EDIMixin
-from edi.models import edi
-from tools import DEFAULT_SERVER_DATE_FORMAT
from tools.translate import _
PURCHASE_ORDER_LINE_EDI_STRUCT = {
@@ -62,16 +57,6 @@ PURCHASE_ORDER_EDI_STRUCT = {
class purchase_order(osv.osv, EDIMixin):
_inherit = 'purchase.order'
- def wkf_send_rfq(self, cr, uid, ids, context=None):
- """"Override this method to add a link to mail"""
- if context is None:
- context = {}
- purchase_objs = self.browse(cr, uid, ids, context=context)
- edi_token = self.pool.get('edi.document').export_edi(cr, uid, purchase_objs, context = context)[0]
- web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
- ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token))
- return super(purchase_order, self).wkf_send_rfq(cr, uid, ids, context=ctx)
-
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
"""Exports a purchase order"""
edi_struct = dict(edi_struct or PURCHASE_ORDER_EDI_STRUCT)
@@ -106,22 +91,26 @@ class purchase_order(osv.osv, EDIMixin):
# the desired company among the user's allowed companies
self._edi_requires_attributes(('company_id','company_address'), edi_document)
- res_partner_obj = self.pool.get('res.partner')
+ res_partner = self.pool.get('res.partner')
- # imported company_address = new partner address
- src_company_id, src_company_name = edi_document.pop('company_id')
- address_info = edi_document.pop('company_address')
- address_info['customer'] = True
- if 'name' not in address_info:
- address_info['name'] = src_company_name
- address_id = res_partner_obj.edi_import(cr, uid, address_info, context=context)
+ xid, company_name = edi_document.pop('company_id')
+ # Retrofit address info into a unified partner info (changed in v7 - used to keep them separate)
+ company_address_edi = edi_document.pop('company_address')
+ company_address_edi['name'] = company_name
+ company_address_edi['is_company'] = True
+ company_address_edi['__import_model'] = 'res.partner'
+ company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway
+ if company_address_edi.get('logo'):
+ company_address_edi['image'] = company_address_edi.pop('logo')
+ company_address_edi['supplier'] = True
+ partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context)
- # modify edi_document to refer to new partner/address
- partner_address = res_partner_obj.browse(cr, uid, address_id, context=context)
- edi_document.pop('partner_address', False) # ignored
- edi_document['partner_id'] = self.edi_m2o(cr, uid, partner_address, context=context)
-
- return address_id
+ # modify edi_document to refer to new partner
+ partner = res_partner.browse(cr, uid, partner_id, context=context)
+ partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context)
+ edi_document['partner_id'] = partner_edi_m2o
+ edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address!
+ return partner_id
def _edi_get_pricelist(self, cr, uid, partner_id, currency, context=None):
# TODO: refactor into common place for purchase/sale, e.g. into product module
diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml
index fab7eeacf6c..4190a625fdb 100644
--- a/addons/purchase/edi/purchase_order_action_data.xml
+++ b/addons/purchase/edi/purchase_order_action_data.xml
@@ -1,18 +1,6 @@
-
-
-
Email Templates
@@ -25,24 +13,19 @@
-
-
-
-
- Automated Purchase Order Notification Mail
+ Purchase Order - Send by mail
${object.validator.email or ''}
${object.company_id.name} Order (Ref ${object.name or 'n/a' })
${object.partner_id.id}
+
+ RFQ_${(object.name or '').replace('/','_')}
@@ -61,15 +44,11 @@
% if object.partner_ref:
Your reference: ${object.partner_ref}
% endif
+ % if object.user_id:
Your contact: ${object.validator.name}
+ % endif
-
- You can view the ${object.state in ('draft', 'sent') and 'request for quotation' or 'order confirmation'} document and download it using the following link:
-
- View Order
-
If you have any question, do not hesitate to contact us.
Thank you!
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 5bb38316604..ad7563140b2 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -384,29 +384,32 @@ class purchase_order(osv.osv):
'''
This function opens a window to compose an email, with the edi purchase template message loaded by default
'''
- mod_obj = self.pool.get('ir.model.data')
- template = mod_obj.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')
- template_id = template and template[1] or False
- res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
- res_id = res and res[1] or False
+ ir_model_data = self.pool.get('ir.model.data')
+ try:
+ template_id = ir_model_data.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')[1]
+ except ValueError:
+ template_id = False
+ try:
+ compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
+ except ValueError:
+ compose_form_id = False
ctx = dict(context)
ctx.update({
'default_model': 'purchase.order',
'default_res_id': ids[0],
- 'default_use_template': True,
+ 'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
- })
+ })
return {
+ 'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
- 'views': [(res_id, 'form')],
- 'view_id': res_id,
- 'type': 'ir.actions.act_window',
+ 'views': [(compose_form_id, 'form')],
+ 'view_id': compose_form_id,
'target': 'new',
'context': ctx,
- 'nodestroy': True,
}
#TODO: implement messages system
diff --git a/addons/purchase/test/process/edi_purchase_order.yml b/addons/purchase/test/process/edi_purchase_order.yml
index d0e90f3788d..8e34606afb4 100644
--- a/addons/purchase/test/process/edi_purchase_order.yml
+++ b/addons/purchase/test/process/edi_purchase_order.yml
@@ -27,36 +27,36 @@
-
Then I export the purchase order via EDI
-
- !python {model: edi.document}: |
- order_pool = self.pool.get('purchase.order')
- order = order_pool.browse(cr, uid, ref("purchase_order_edi_1"))
- token = self.export_edi(cr, uid, [order])
- assert token, 'Invalid EDI Token'
-
+ !python {model: edi.edi}: |
+ import json
+ order_pool = self.pool.get('purchase.order')
+ order = order_pool.browse(cr, uid, ref("purchase_order_edi_1"))
+ edi_doc = self.generate_edi(cr, uid, [order])
+ assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict'
-
- Then I import a sample EDI document of a sale order
+ Then I import a sample EDI document of a sale order (v7.0)
-
- !python {model: edi.document}: |
+ !python {model: edi.edi}: |
purchase_order_pool = self.pool.get('purchase.order')
edi_document = {
- "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_test",
+ "__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_test",
"__module": "sale",
"__model": "sale.order",
"__import_module": "purchase",
"__import_model": "purchase.order",
- "__version": [6,1,0],
+ "__version": [7,0,0],
"name": "SO008",
"currency": {
- "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.EUR",
+ "__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.EUR",
"__module": "base",
"__model": "res.currency",
"code": "EUR",
"symbol": "€",
},
"date_order": "2011-09-13",
- "partner_id": ["sale:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_test22", "Junjun wala"],
+ "partner_id": ["sale:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_test22", "Junjun wala"],
"partner_address": {
- "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_address_7wdsjasdjh",
+ "__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_address_7wdsjasdjh",
"__module": "base",
"__model": "res.partner",
"phone": "(+32).81.81.37.00",
@@ -65,48 +65,48 @@
"zip": "1367",
"country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
},
- "company_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_company", "Supplier S.A."],
+ "company_id": ["base:724f9v70-dv70-1v70-8v70-701a04e25v70.main_company", "Supplier S.A."],
"company_address": {
- "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_address",
+ "__id": "base:724f9v70-dv70-1v70-8v70-701a04e25v70.main_address",
"__module": "base",
"__model": "res.partner",
"city": "Gerompont",
"zip": "1367",
- "country_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.be", "Belgium"],
+ "country_id": ["base:724f9v70-dv70-1v70-8v70-701a04e25v70.be", "Belgium"],
"phone": "(+32).81.81.37.00",
"street": "Chaussee de Namur 40",
"street2": "mailbox 34",
"bank_ids": [
- ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_bank-XiwqnxKWzGbp","Guys bank: 123477777-156113"]
+ ["base:724f9v70-dv70-1v70-8v70-701a04e25v70.res_partner_bank-XiwqnxKWzGbp","Guys bank: 123477777-156113"]
],
},
"order_line": [{
- "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeuI-SSP0",
+ "__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_line-LXEqeuI-SSP0",
"__module": "sale",
"__model": "sale.order.line",
"__import_module": "purchase",
"__import_model": "purchase.order.line",
"name": "PC Assemble SC234",
- "product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "Unit"],
+ "product_uom": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_uom_unit", "Unit"],
"product_qty": 1.0,
"date_planned": "2011-09-30",
"sequence": 10,
"price_unit": 150.0,
- "product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"],
+ "product_id": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_product_3", "[PCSC234] PC Assemble SC234"],
},
{
- "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeadasdad",
+ "__id": "sale:724f9v70-dv70-1v70-8v70-701a04e25v70.sale_order_line-LXEqeadasdad",
"__module": "sale",
"__model": "sale.order.line",
"__import_module": "purchase",
"__import_model": "purchase.order.line",
"name": "PC on Demand",
- "product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "Unit"],
+ "product_uom": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_uom_unit", "Unit"],
"product_qty": 10.0,
"sequence": 11,
"date_planned": "2011-09-15",
"price_unit": 20.0,
- "product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"],
+ "product_id": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_product_5", "[PC-DEM] PC on Demand"],
}],
}
new_purchase_order_id = purchase_order_pool.edi_import(cr, uid, edi_document, context=context)
@@ -114,6 +114,7 @@
order_new = purchase_order_pool.browse(cr, uid, new_purchase_order_id)
# check bank info on partner
+ assert order_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a purchase order"
assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
bank_info = order_new.partner_id.bank_ids[0]
assert bank_info.acc_number == "Guys bank: 123477777-156113", 'Expected "Guys bank: 123477777-156113", got %s' % bank_info.acc_number
@@ -133,3 +134,104 @@
assert purchase_line.product_qty == 10 , "product qty is not same"
else:
raise AssertionError('unknown order line: %s' % purchase_line)
+-
+ "Then I import a sample EDI document of a sale order (v6.1 - to test backwards compatibility)"
+-
+ !python {model: edi.edi}: |
+ purchase_order_pool = self.pool.get('purchase.order')
+ edi_document = {
+ "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_test",
+ "__module": "sale",
+ "__model": "sale.order",
+ "__import_module": "purchase",
+ "__import_model": "purchase.order",
+ "__version": [6,1,0],
+ "name": "SO08v61",
+ "currency": {
+ "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.EUR",
+ "__module": "base",
+ "__model": "res.currency",
+ "code": "EUR",
+ "symbol": "€",
+ },
+ "date_order": "2011-09-13",
+ "partner_id": ["sale:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_test22", "Junjun wala"],
+ "partner_address": {
+ "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_address_7wdsjasdjh",
+ "__module": "base",
+ "__model": "res.partner.address",
+ "phone": "(+32).81.81.37.00",
+ "street": "Chaussee de Namur 40",
+ "city": "Gerompont",
+ "zip": "1367",
+ "country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
+ },
+ "company_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_company", "Supplier S.A."],
+ "company_address": {
+ "__id": "base:724f93ec-ddd0-11e0-88ec-701a04e25543.main_address",
+ "__module": "base",
+ "__model": "res.partner.address",
+ "city": "Gerompont",
+ "zip": "1367",
+ "country_id": ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.be", "Belgium"],
+ "phone": "(+32).81.81.37.00",
+ "street": "Chaussee de Namur 40",
+ "street2": "mailbox 34",
+ "bank_ids": [
+ ["base:724f93ec-ddd0-11e0-88ec-701a04e25543.res_partner_bank-XiwqnxKWzGbp","Another bank: 123477700-156113"]
+ ],
+ },
+ "order_line": [{
+ "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeuI-SSP0",
+ "__module": "sale",
+ "__model": "sale.order.line",
+ "__import_module": "purchase",
+ "__import_model": "purchase.order.line",
+ "name": "Basic PC",
+ "product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "PCE"],
+ "product_qty": 1.0,
+ "date_planned": "2011-09-30",
+ "sequence": 10,
+ "price_unit": 150.0,
+ "product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_pc1", "[PC1] Basic PC"],
+ },
+ {
+ "__id": "sale:724f93ec-ddd0-11e0-88ec-701a04e25543.sale_order_line-LXEqeadasdad",
+ "__module": "sale",
+ "__model": "sale.order.line",
+ "__import_module": "purchase",
+ "__import_model": "purchase.order.line",
+ "name": "Medium PC",
+ "product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "PCE"],
+ "product_qty": 10.0,
+ "sequence": 11,
+ "date_planned": "2011-09-15",
+ "price_unit": 20.0,
+ "product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_pc3", "[PC3] Medium PC"],
+ }],
+ }
+ new_purchase_order_id = purchase_order_pool.edi_import(cr, uid, edi_document, context=context)
+ assert new_purchase_order_id, 'Purchase order import failed'
+ order_new = purchase_order_pool.browse(cr, uid, new_purchase_order_id)
+
+ # check bank info on partner
+ assert order_new.partner_id.supplier, "Imported partner should be a supplier, as we just imported the document as a purchase order"
+ assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
+ bank_info = order_new.partner_id.bank_ids[0]
+ assert bank_info.acc_number == "Another bank: 123477700-156113", 'Expected "Another bank: 123477700-156113", got %s' % bank_info.acc_number
+
+ assert order_new.pricelist_id.name == 'Default Purchase Pricelist' , "Default Purchase Pricelist was not automatically assigned"
+ assert order_new.amount_total == 350, "Amount total is not same"
+ assert order_new.amount_untaxed == 350, "untaxed amount is not same"
+ assert len(order_new.order_line) == 2, "Purchase order lines number mismatch"
+ for purchase_line in order_new.order_line:
+ if purchase_line.name == 'Basic PC':
+ assert purchase_line.product_uom.name == "PCE" , "uom is not same"
+ assert purchase_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(purchase_line.price_unit,)
+ assert purchase_line.product_qty == 1 , "product qty is not same"
+ elif purchase_line.name == 'Medium PC':
+ assert purchase_line.product_uom.name == "PCE" , "uom is not same"
+ assert purchase_line.price_unit == 20 , "unit price is not same, got %s, expected 20"%(purchase_line.price_unit,)
+ assert purchase_line.product_qty == 10 , "product qty is not same"
+ else:
+ raise AssertionError('unknown order line: %s' % purchase_line)
\ No newline at end of file
diff --git a/addons/sale/edi/sale_order.py b/addons/sale/edi/sale_order.py
index edec15a86ae..15389f1481c 100644
--- a/addons/sale/edi/sale_order.py
+++ b/addons/sale/edi/sale_order.py
@@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Business Applications
-# Copyright (c) 2011 OpenERP S.A.
+# Copyright (c) 2011-2012 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
@@ -19,13 +19,8 @@
#
##############################################################################
-from datetime import datetime, timedelta
-from dateutil.relativedelta import relativedelta
-
-from osv import fields, osv, orm
+from openerp.osv import osv
from edi import EDIMixin
-from edi.models import edi
-from tools import DEFAULT_SERVER_DATE_FORMAT
SALE_ORDER_LINE_EDI_STRUCT = {
'sequence': True,
@@ -65,15 +60,6 @@ SALE_ORDER_EDI_STRUCT = {
class sale_order(osv.osv, EDIMixin):
_inherit = 'sale.order'
- def action_quotation_send(self, cr, uid, ids, context=None):
- if context is None:
- context = {}
- sale_objs = self.browse(cr, uid, ids, context=context)
- edi_token = self.pool.get('edi.document').export_edi(cr, uid, sale_objs, context = context)[0]
- web_root_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
- ctx = dict(context, edi_web_url_view=edi.EDI_VIEW_WEB_URL % (web_root_url, cr.dbname, edi_token))
- return super(sale_order, self).action_quotation_send(cr, uid, ids, context=ctx)
-
def edi_export(self, cr, uid, records, edi_struct=None, context=None):
"""Exports a Sale order"""
edi_struct = dict(edi_struct or SALE_ORDER_EDI_STRUCT)
@@ -102,34 +88,36 @@ class sale_order(osv.osv, EDIMixin):
edi_doc_list.append(edi_doc)
return edi_doc_list
-
def _edi_import_company(self, cr, uid, edi_document, context=None):
# TODO: for multi-company setups, we currently import the document in the
# user's current company, but we should perhaps foresee a way to select
# the desired company among the user's allowed companies
self._edi_requires_attributes(('company_id','company_address'), edi_document)
- res_partner_obj = self.pool.get('res.partner')
+ res_partner = self.pool.get('res.partner')
- # imported company_address = new partner address
- src_company_id, src_company_name = edi_document.pop('company_id')
+ xid, company_name = edi_document.pop('company_id')
+ # Retrofit address info into a unified partner info (changed in v7 - used to keep them separate)
+ company_address_edi = edi_document.pop('company_address')
+ company_address_edi['name'] = company_name
+ company_address_edi['is_company'] = True
+ company_address_edi['__import_model'] = 'res.partner'
+ company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway
+ if company_address_edi.get('logo'):
+ company_address_edi['image'] = company_address_edi.pop('logo')
+ company_address_edi['customer'] = True
+ partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context)
- address_info = edi_document.pop('company_address')
- address_info['supplier'] = True
- if 'name' not in address_info:
- address_info['name'] = src_company_name
+ # modify edi_document to refer to new partner
+ partner = res_partner.browse(cr, uid, partner_id, context=context)
+ partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context)
+ edi_document['partner_id'] = partner_edi_m2o
+ edi_document['partner_invoice_id'] = partner_edi_m2o
+ edi_document['partner_shipping_id'] = partner_edi_m2o
- address_id = res_partner_obj.edi_import(cr, uid, address_info, context=context)
+ edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address!
+ return partner_id
- # modify edi_document to refer to new partner/address
- partner_address = res_partner_obj.browse(cr, uid, address_id, context=context)
- edi_document.pop('partner_address', False) # ignored
- address_edi_m2o = self.edi_m2o(cr, uid, partner_address, context=context)
- edi_document['partner_id'] = address_edi_m2o
- edi_document['partner_invoice_id'] = address_edi_m2o
- edi_document['partner_shipping_id'] = address_edi_m2o
-
- return address_id
def _edi_get_pricelist(self, cr, uid, partner_id, currency, context=None):
# TODO: refactor into common place for purchase/sale, e.g. into product module
@@ -171,7 +159,6 @@ class sale_order(osv.osv, EDIMixin):
currency_id = res_currency.edi_import(cr, uid, currency_info, context=context)
order_currency = res_currency.browse(cr, uid, currency_id)
- date_order = edi_document['date_order']
partner_ref = edi_document.pop('partner_ref', False)
edi_document['client_order_ref'] = edi_document['name']
edi_document['name'] = partner_ref or edi_document['name']
@@ -185,7 +172,7 @@ class sale_order(osv.osv, EDIMixin):
order_lines = edi_document['order_line']
for order_line in order_lines:
- self._edi_requires_attributes(( 'product_id', 'product_uom', 'product_qty', 'price_unit'), order_line)
+ self._edi_requires_attributes(('product_id', 'product_uom', 'product_qty', 'price_unit'), order_line)
order_line['product_uom_qty'] = order_line['product_qty']
del order_line['product_qty']
diff --git a/addons/sale/edi/sale_order_action_data.xml b/addons/sale/edi/sale_order_action_data.xml
index da1a1ceb393..92611cea180 100644
--- a/addons/sale/edi/sale_order_action_data.xml
+++ b/addons/sale/edi/sale_order_action_data.xml
@@ -1,7 +1,6 @@
-
Email Templates
@@ -15,19 +14,20 @@
-
- Automated Sale Order Notification Mail
+ Sale Order - Send by Email
${object.user_id.email or ''}
- ${object.company_id.name} Order (Ref ${object.name or 'n/a' })
+ ${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })
${object.partner_invoice_id.id}
+
+ ${(object.name or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}
@@ -46,16 +46,12 @@
% if object.client_order_ref:
Your reference: ${object.client_order_ref}
% endif
+ % if object.user_id:
Your contact: ${object.user_id.name}
+ % endif
-
- You can view the ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} document, download it and pay online using the following link:
-
- View Order
-
- % if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state not in ('draft', 'sent'):
+ % if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state != 'draft':
<%
comp_name = quote(object.company_id.name)
order_name = quote(object.name)
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index c1af024ad9c..3c568cc8227 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -636,30 +636,33 @@ class sale_order(osv.osv):
This function opens a window to compose an email, with the edi sale template message loaded by default
'''
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
- mod_obj = self.pool.get('ir.model.data')
- template = mod_obj.get_object_reference(cr, uid, 'sale', 'email_template_edi_sale')
- template_id = template and template[1] or False
- res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
- res_id = res and res[1] or False
+ ir_model_data = self.pool.get('ir.model.data')
+ try:
+ template_id = ir_model_data.get_object_reference(cr, uid, 'sale', 'email_template_edi_sale')[1]
+ except ValueError:
+ template_id = False
+ try:
+ compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
+ except ValueError:
+ compose_form_id = False
ctx = dict(context)
ctx.update({
'default_model': 'sale.order',
'default_res_id': ids[0],
- 'default_use_template': True,
+ 'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
'mark_so_as_sent': True
})
return {
+ 'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
- 'views': [(res_id, 'form')],
- 'view_id': res_id,
- 'type': 'ir.actions.act_window',
+ 'views': [(compose_form_id, 'form')],
+ 'view_id': compose_form_id,
'target': 'new',
'context': ctx,
- 'nodestroy': True,
}
def action_done(self, cr, uid, ids, context=None):
diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml
index 9f385339173..419047dbbd2 100644
--- a/addons/sale/sale_view.xml
+++ b/addons/sale/sale_view.xml
@@ -166,8 +166,8 @@
-
-
+
+
@@ -358,7 +358,7 @@
-
+
Sale Orders
ir.actions.act_window
sale.order
@@ -377,9 +377,9 @@
-
+
-
+
Sales in Exception
ir.actions.act_window
sale.order
@@ -390,7 +390,7 @@
-
+
Sales Order in Progress
ir.actions.act_window
sale.order
@@ -401,7 +401,7 @@
-
+
Quotations
ir.actions.act_window
sale.order
@@ -427,7 +427,7 @@
diff --git a/addons/sale/test/edi_sale_order.yml b/addons/sale/test/edi_sale_order.yml
index 967aaa1337c..24fdfdf2786 100644
--- a/addons/sale/test/edi_sale_order.yml
+++ b/addons/sale/test/edi_sale_order.yml
@@ -25,70 +25,71 @@
-
Then I export the sale order via EDI
-
- !python {model: edi.document}: |
+ !python {model: edi.edi}: |
+ import json
sale_order = self.pool.get('sale.order')
so = sale_order.browse(cr, uid, ref("sale_order_edi_1"))
- token = self.export_edi(cr, uid, [so])
- assert token, 'Invalid EDI Token'
+ edi_doc = self.generate_edi(cr, uid, [so])
+ assert isinstance(json.loads(edi_doc)[0], dict), 'EDI doc should be a JSON dict'
-
- Then I import a sample EDI document of a purchase order
+ "Then I import a sample EDI document of a purchase order (v7.0)"
-
- !python {model: edi.document}: |
+ !python {model: edi.edi}: |
sale_order_pool = self.pool.get('sale.order')
edi_document = {
- "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_test",
+ "__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_test",
"__module": "purchase",
"__model": "purchase.order",
"__import_module": "sale",
"__import_model": "sale.order",
- "__version": [6,1,0],
+ "__version": [7,0,0],
"name": "PO00011",
"date_order": "2011-09-12",
"currency": {
- "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.EUR",
+ "__id": "base:5af12v70-dv70-1v70-bv70-701a04e25v70.EUR",
"__module": "base",
"__model": "res.currency",
"code": "EUR",
"symbol": "€",
},
- "company_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.main_company", "Client S.A."],
+ "company_id": ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.main_company", "Client S.A."],
"company_address": {
- "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.some_address",
+ "__id": "base:5af12v70-dv70-1v70-bv70-701a04e25v70.some_address",
"__module": "base",
"__model": "res.partner",
"phone": "(+32).81.81.37.00",
"street": "Chaussee de Namur 40",
"city": "Gerompont",
"zip": "1367",
- "country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
+ "country_id": ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.be", "Belgium"],
"bank_ids": [
- ["base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_bank-adaWadsadasdDJzGbp","Ladies bank: 032465789-156113"]
+ ["base:5af12v70-dv70-1v70-bv70-701a04e25v70.res_partner_bank-adaWadsadasdDJzGbp","Another bank: 032465700-156700"]
],
},
- "partner_id": ["purchase:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_test20", "jones white"],
+ "partner_id": ["purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.res_partner_test20", "jones white"],
"order_line": [{
- "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-AlhsVDZGoKvJ",
+ "__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_line-AlhsVDZGoKvJ",
"__module": "purchase",
"__model": "purchase.order.line",
"__import_module": "sale",
"__import_model": "sale.order.line",
"name": "PC Assemble SC234",
"price_unit": 150.0,
- "product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"],
+ "product_id": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_product_3", "[PCSC234] PC Assemble SC234"],
"product_qty": 1.0,
- "product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "Unit"],
+ "product_uom": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_uom_unit", "Unit"],
},
{
- "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-Alsads33e",
+ "__id": "purchase:5af12v70-dv70-1v70-bv70-701a04e25v70.purchase_order_line-Alsads33e",
"__module": "purchase",
"__model": "purchase.order.line",
"__import_module": "sale",
"__import_model": "sale.order.line",
"name": "PC on Demand",
"price_unit": 100.0,
- "product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"],
+ "product_id": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_product_5", "[PC-DEM] PC on Demand"],
"product_qty": 2.0,
- "product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "Unit"],
+ "product_uom": ["product:5af12v70-dv70-1v70-bv70-701a04e25v70.product_uom_unit", "Unit"],
}],
}
new_sale_order_id = sale_order_pool.edi_import(cr, uid, edi_document, context=context)
@@ -96,9 +97,10 @@
order_new = sale_order_pool.browse(cr, uid, new_sale_order_id)
# check bank info on partner
+ assert order_new.partner_id.customer, "Imported partner should be a customer, as we just imported the document as a sale order"
assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
bank_info = order_new.partner_id.bank_ids[0]
- assert bank_info.acc_number == "Ladies bank: 032465789-156113", 'Expected "Ladies bank: 032465789-156113", got %s' % bank_info.acc_number
+ assert bank_info.acc_number == "Another bank: 032465700-156700", 'Expected "Another bank: 032465700-156700", got %s' % bank_info.acc_number
assert order_new.pricelist_id.name == 'Public Pricelist' , "Public Price list was not automatically assigned"
assert order_new.amount_total == 350, "Amount total is wrong"
@@ -115,3 +117,101 @@
assert sale_line.product_uom_qty == 2 , "product qty is not same"
else:
raise AssertionError('unknown order line: %s' % sale_line)
+-
+ "Then I import a sample EDI document of a purchase order (v6.1 - to test backwards compatibility)"
+-
+ !python {model: edi.edi}: |
+ sale_order_pool = self.pool.get('sale.order')
+ edi_document = {
+ "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_test",
+ "__module": "purchase",
+ "__model": "purchase.order",
+ "__import_module": "sale",
+ "__import_model": "sale.order",
+ "__version": [6,1,0],
+ "name": "PO00011-v61",
+ "date_order": "2011-09-12",
+ "currency": {
+ "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.EUR",
+ "__module": "base",
+ "__model": "res.currency",
+ "code": "EUR",
+ "symbol": "€",
+ },
+ "company_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.main_company", "Client S.A."],
+ "company_address": {
+ "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.some_address",
+ "__module": "base",
+ "__model": "res.partner.address",
+ "phone": "(+32).81.81.37.00",
+ "street": "Chaussee de Namur 40",
+ "city": "Gerompont",
+ "zip": "1367",
+ "country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
+ "bank_ids": [
+ ["base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_bank-adaWadsadasdDJzGbp","Ladies bank: 032465789-156113"]
+ ],
+ },
+ "partner_id": ["purchase:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_test20", "jones white"],
+ "partner_address": {
+ "__id": "base:5af1272e-dd26-11e0-b65e-701a04e25543.res_partner_address_7wdsjasdjh",
+ "__module": "base",
+ "__model": "res.partner.address",
+ "phone": "(+32).81.81.37.00",
+ "street": "Chaussee de Namur 40",
+ "city": "Gerompont",
+ "zip": "1367",
+ "country_id": ["base:5af1272e-dd26-11e0-b65e-701a04e25543.be", "Belgium"],
+ },
+ "order_line": [{
+ "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-AlhsVDZGoKvJ",
+ "__module": "purchase",
+ "__model": "purchase.order.line",
+ "__import_module": "sale",
+ "__import_model": "sale.order.line",
+ "name": "Basic PC",
+ "date_planned": "2011-09-30",
+ "price_unit": 150.0,
+ "product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_pc1", "[PC1] Basic PC"],
+ "product_qty": 1.0,
+ "product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "PCE"],
+ },
+ {
+ "__id": "purchase:5af1272e-dd26-11e0-b65e-701a04e25543.purchase_order_line-Alsads33e",
+ "__module": "purchase",
+ "__model": "purchase.order.line",
+ "__import_module": "sale",
+ "__import_model": "sale.order.line",
+ "name": "Medium PC",
+ "date_planned": "2011-09-15",
+ "price_unit": 100.0,
+ "product_id": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_product_pc3", "[PC3] Medium PC"],
+ "product_qty": 2.0,
+ "product_uom": ["product:5af1272e-dd26-11e0-b65e-701a04e25543.product_uom_unit", "PCE"],
+ }],
+ }
+ new_sale_order_id = sale_order_pool.edi_import(cr, uid, edi_document, context=context)
+ assert new_sale_order_id, 'Sale order import failed'
+ order_new = sale_order_pool.browse(cr, uid, new_sale_order_id)
+
+ # check bank info on partner
+ assert order_new.partner_id.customer, "Imported partner should be a customer, as we just imported the document as a sale order"
+ assert len(order_new.partner_id.bank_ids) == 1, "Expected 1 bank entry related to partner"
+ bank_info = order_new.partner_id.bank_ids[0]
+ assert bank_info.acc_number == "Ladies bank: 032465789-156113", 'Expected "Ladies bank: 032465789-156113", got %s' % bank_info.acc_number
+
+ assert order_new.pricelist_id.name == 'Public Pricelist' , "Public Price list was not automatically assigned"
+ assert order_new.amount_total == 350, "Amount total is wrong"
+ assert order_new.amount_untaxed == 350, "Untaxed amount is wrong"
+ assert len(order_new.order_line) == 2, "Sale order lines mismatch"
+ for sale_line in order_new.order_line:
+ if sale_line.name == 'Basic PC':
+ assert sale_line.product_uom.name == "PCE" , "uom is not same"
+ assert sale_line.price_unit == 150 , "unit price is not same, got %s, expected 150"%(sale_line.price_unit,)
+ assert sale_line.product_uom_qty == 1 , "product qty is not same"
+ elif sale_line.name == 'Medium PC':
+ assert sale_line.product_uom.name == "PCE" , "uom is not same"
+ assert sale_line.price_unit == 100 , "unit price is not same, got %s, expected 100"%(sale_line.price_unit,)
+ assert sale_line.product_uom_qty == 2 , "product qty is not same"
+ else:
+ raise AssertionError('unknown order line: %s' % sale_line)
\ No newline at end of file