diff --git a/.gitignore b/.gitignore
index 6148a29a95d..4b38dc02ac7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,4 +25,4 @@ install/win32/meta.py
/lib/
/man/
/share/
-/src/
+/src/
\ No newline at end of file
diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml
index 61c397364b9..fe701a85661 100644
--- a/addons/account/account_invoice_view.xml
+++ b/addons/account/account_invoice_view.xml
@@ -60,7 +60,7 @@
-
+
diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py
index 3fd83c19207..2e3c9b7c374 100644
--- a/addons/account/report/account_aged_partner_balance.py
+++ b/addons/account/report/account_aged_partner_balance.py
@@ -161,7 +161,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
dates_query += ' < %s)'
args_list += (form[str(i)]['stop'],)
args_list += (self.date_from,)
- self.cr.execute('''SELECT l.partner_id, SUM(l.debit-l.credit)
+ self.cr.execute('''SELECT l.partner_id, SUM(l.debit-l.credit), l.reconcile_partial_id
FROM account_move_line AS l, account_account, account_move am
WHERE (l.account_id = account_account.id) AND (l.move_id=am.id)
AND (am.state IN %s)
@@ -173,12 +173,24 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header):
AND account_account.active
AND ''' + dates_query + '''
AND (l.date <= %s)
- GROUP BY l.partner_id''', args_list)
- t = self.cr.fetchall()
- d = {}
- for i in t:
- d[i[0]] = i[1]
- history.append(d)
+ GROUP BY l.partner_id, l.reconcile_partial_id''', args_list)
+ partners_partial = self.cr.fetchall()
+ partners_amount = dict((i[0],0) for i in partners_partial)
+ for partner_info in partners_partial:
+ if partner_info[2]:
+ # in case of partial reconciliation, we want to keep the left amount in the oldest period
+ self.cr.execute('''SELECT MIN(COALESCE(date_maturity,date)) FROM account_move_line WHERE reconcile_partial_id = %s''', (partner_info[2],))
+ date = self.cr.fetchall()
+ if date and args_list[-3] <= date[0][0] <= args_list[-2]:
+ # partial reconcilation
+ self.cr.execute('''SELECT SUM(l.debit-l.credit)
+ FROM account_move_line AS l
+ WHERE l.reconcile_partial_id = %s''', (partner_info[2],))
+ unreconciled_amount = self.cr.fetchall()
+ partners_amount[partner_info[0]] += unreconciled_amount[0][0]
+ else:
+ partners_amount[partner_info[0]] += partner_info[1]
+ history.append(partners_amount)
for partner in partners:
values = {}
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml
index 6c4b701c6cf..a250eb9ebf7 100644
--- a/addons/account/views/report_invoice.xml
+++ b/addons/account/views/report_invoice.xml
@@ -5,7 +5,7 @@
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index acb1793b7bd..11f93e25632 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -669,9 +669,10 @@ class mail_thread(osv.AbstractModel):
def message_get_default_recipients(self, cr, uid, ids, context=None):
if context and context.get('thread_model') and context['thread_model'] in self.pool and context['thread_model'] != self._name:
- sub_ctx = dict(context)
- sub_ctx.pop('thread_model')
- return self.pool[context['thread_model']].message_get_default_recipients(cr, uid, ids, context=sub_ctx)
+ if hasattr(self.pool[context['thread_model']], 'message_get_default_recipients'):
+ sub_ctx = dict(context)
+ sub_ctx.pop('thread_model')
+ return self.pool[context['thread_model']].message_get_default_recipients(cr, uid, ids, context=sub_ctx)
res = {}
for record in self.browse(cr, SUPERUSER_ID, ids, context=context):
recipient_ids, email_to, email_cc = set(), False, False
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index 99a7751e15e..e5eca724659 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -919,15 +919,45 @@ openerp.mail = function (session) {
this.$('.oe_mail_expand').on('click', this.on_expand);
this.$('.oe_mail_reduce').on('click', this.on_expand);
this.$('.oe_mail_action_model').on('click', this.on_record_clicked);
+ this.$('.oe_mail_action_author').on('click', this.on_record_author_clicked);
},
on_record_clicked: function (event) {
+ event.preventDefault();
+ var self = this;
var state = {
'model': this.model,
'id': this.res_id,
'title': this.record_name
};
session.webclient.action_manager.do_push_state(state);
+ this.context.params = {
+ model: this.model,
+ res_id: this.res_id,
+ };
+ this.thread.ds_thread.call("message_redirect_action", {context: this.context}).then(function(action){
+ self.do_action(action);
+ });
+ },
+
+ on_record_author_clicked: function (event) {
+ event.preventDefault();
+ var partner_id = $(event.target).data('partner');
+ var state = {
+ 'model': 'res.partner',
+ 'id': partner_id,
+ 'title': this.record_name
+ };
+ session.webclient.action_manager.do_push_state(state);
+ var action = {
+ type:'ir.actions.act_window',
+ view_type: 'form',
+ view_mode: 'form',
+ res_model: 'res.partner',
+ views: [[false, 'form']],
+ res_id: partner_id,
+ }
+ this.do_action(action);
},
/* Call the on_compose_message on the thread of this message. */
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index ec7b0fa2911..3aea4a19555 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -273,7 +273,7 @@
-
+
updated document
@@ -293,7 +293,7 @@
-
+ ,
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index 2e8a82a8f4d..f721ed2f385 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -4,10 +4,9 @@ from datetime import datetime
from dateutil import relativedelta
import json
import random
-import urllib
-import urlparse
from openerp import tools
+from openerp.exceptions import Warning
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
from openerp.osv import osv, fields
@@ -62,6 +61,12 @@ class MassMailingContact(osv.Model):
rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context)
return self.name_get(cr, uid, [rec_id], context)[0]
+ def message_get_default_recipients(self, cr, uid, ids, context=None):
+ res = {}
+ for record in self.browse(cr, uid, ids, context=context):
+ res[record.id] = {'partner_ids': [], 'email_to': record.email, 'email_cc': False}
+ return res
+
class MassMailingList(osv.Model):
"""Model of a contact list. """
@@ -549,6 +554,8 @@ class MassMailing(osv.Model):
for mailing in self.browse(cr, uid, ids, context=context):
# instantiate an email composer + send emails
res_ids = self.get_recipients(cr, uid, mailing, context=context)
+ if not res_ids:
+ raise Warning('Please select recipients.')
comp_ctx = dict(context, active_ids=res_ids)
composer_values = {
'author_id': author_id,
diff --git a/addons/mrp_repair/views/report_mrprepairorder.xml b/addons/mrp_repair/views/report_mrprepairorder.xml
index 0ea3f22167c..2897fee1fd9 100644
--- a/addons/mrp_repair/views/report_mrprepairorder.xml
+++ b/addons/mrp_repair/views/report_mrprepairorder.xml
@@ -22,7 +22,7 @@
VAT:
-
+
diff --git a/addons/pad/static/src/img/pad_link_companies.jpeg b/addons/pad/static/src/img/pad_link_companies.jpeg
new file mode 100644
index 00000000000..1bce3a56186
Binary files /dev/null and b/addons/pad/static/src/img/pad_link_companies.jpeg differ
diff --git a/addons/payment/models/res_config.py b/addons/payment/models/res_config.py
index 70668a296da..a74e595b52e 100644
--- a/addons/payment/models/res_config.py
+++ b/addons/payment/models/res_config.py
@@ -16,4 +16,7 @@ class AccountPaymentConfig(osv.TransientModel):
'module_payment_adyen': fields.boolean(
'Manage Payments Using Adyen',
help='-It installs the module payment_adyen.'),
+ 'module_payment_buckaroo': fields.boolean(
+ 'Manage Payments Using Buckaroo',
+ help='-It installs the module payment_buckaroo.'),
}
diff --git a/addons/payment/views/res_config_view.xml b/addons/payment/views/res_config_view.xml
index 101acb7c9ee..45c0a3d168e 100644
--- a/addons/payment/views/res_config_view.xml
+++ b/addons/payment/views/res_config_view.xml
@@ -20,6 +20,10 @@
+
+
+
+
diff --git a/addons/payment_buckaroo/__init__.py b/addons/payment_buckaroo/__init__.py
new file mode 100644
index 00000000000..1f4b1b74e57
--- /dev/null
+++ b/addons/payment_buckaroo/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2014-Today OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import models
+import controllers
diff --git a/addons/payment_buckaroo/__openerp__.py b/addons/payment_buckaroo/__openerp__.py
new file mode 100644
index 00000000000..526f7c38612
--- /dev/null
+++ b/addons/payment_buckaroo/__openerp__.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+{
+ 'name': 'Buckaroo Payment Acquirer',
+ 'category': 'Hidden',
+ 'summary': 'Payment Acquirer: Buckaroo Implementation',
+ 'version': '1.0',
+ 'description': """Buckaroo Payment Acquirer""",
+ 'author': 'OpenERP SA',
+ 'depends': ['payment'],
+ 'data': [
+ 'views/buckaroo.xml',
+ 'views/payment_acquirer.xml',
+ 'data/buckaroo.xml',
+ ],
+ 'installable': True,
+}
diff --git a/addons/payment_buckaroo/controllers/__init__.py b/addons/payment_buckaroo/controllers/__init__.py
new file mode 100644
index 00000000000..bbd183e955b
--- /dev/null
+++ b/addons/payment_buckaroo/controllers/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+import main
diff --git a/addons/payment_buckaroo/controllers/main.py b/addons/payment_buckaroo/controllers/main.py
new file mode 100644
index 00000000000..d3fe5b196fc
--- /dev/null
+++ b/addons/payment_buckaroo/controllers/main.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+try:
+ import simplejson as json
+except ImportError:
+ import json
+
+import logging
+import pprint
+import werkzeug
+
+from openerp import http, SUPERUSER_ID
+from openerp.http import request
+
+_logger = logging.getLogger(__name__)
+
+
+class BuckarooController(http.Controller):
+ _return_url = '/payment/buckaroo/return'
+ _cancel_url = '/payment/buckaroo/cancel'
+ _exception_url = '/payment/buckaroo/error'
+ _reject_url = '/payment/buckaroo/reject'
+
+ @http.route([
+ '/payment/buckaroo/return',
+ '/payment/buckaroo/cancel',
+ '/payment/buckaroo/error',
+ '/payment/buckaroo/reject',
+ ], type='http', auth='none')
+ def buckaroo_return(self, **post):
+ """ Buckaroo."""
+ _logger.info('Buckaroo: entering form_feedback with post data %s', pprint.pformat(post)) # debug
+ request.registry['payment.transaction'].form_feedback(request.cr, SUPERUSER_ID, post, 'buckaroo', context=request.context)
+ return_url = post.pop('return_url', '')
+ if not return_url:
+ data ='' + post.pop('ADD_RETURNDATA', '{}').replace("'", "\"")
+ custom = json.loads(data)
+ return_url = custom.pop('return_url', '/')
+ return werkzeug.utils.redirect(return_url)
diff --git a/addons/payment_buckaroo/data/buckaroo.xml b/addons/payment_buckaroo/data/buckaroo.xml
new file mode 100644
index 00000000000..9d100a59191
--- /dev/null
+++ b/addons/payment_buckaroo/data/buckaroo.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ Buckaroo
+ buckaroo
+
+
+ test
+ You will be redirected to the Buckaroo website after cliking on the payment button.]]>
+ dummy
+ dummy
+
+
+
+
diff --git a/addons/payment_buckaroo/models/__init__.py b/addons/payment_buckaroo/models/__init__.py
new file mode 100644
index 00000000000..7e1780a0059
--- /dev/null
+++ b/addons/payment_buckaroo/models/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+import buckaroo
diff --git a/addons/payment_buckaroo/models/buckaroo.py b/addons/payment_buckaroo/models/buckaroo.py
new file mode 100644
index 00000000000..5b576a24c3b
--- /dev/null
+++ b/addons/payment_buckaroo/models/buckaroo.py
@@ -0,0 +1,191 @@
+# -*- coding: utf-'8' "-*-"
+from hashlib import sha1
+import logging
+import urlparse
+
+from openerp.addons.payment.models.payment_acquirer import ValidationError
+from openerp.addons.payment_buckaroo.controllers.main import BuckarooController
+from openerp.osv import osv, fields
+from openerp.tools.float_utils import float_compare
+
+_logger = logging.getLogger(__name__)
+
+
+class AcquirerBuckaroo(osv.Model):
+ _inherit = 'payment.acquirer'
+
+ def _get_buckaroo_urls(self, cr, uid, environment, context=None):
+ """ Buckaroo URLs
+ """
+ if environment == 'prod':
+ return {
+ 'buckaroo_form_url': 'https://checkout.buckaroo.nl/html/',
+ }
+ else:
+ return {
+ 'buckaroo_form_url': 'https://testcheckout.buckaroo.nl/html/',
+ }
+
+ def _get_providers(self, cr, uid, context=None):
+ providers = super(AcquirerBuckaroo, self)._get_providers(cr, uid, context=context)
+ providers.append(['buckaroo', 'Buckaroo'])
+ return providers
+
+ _columns = {
+ 'brq_websitekey': fields.char('WebsiteKey', required_if_provider='buckaroo'),
+ 'brq_secretkey': fields.char('SecretKey', required_if_provider='buckaroo'),
+ }
+
+ def _buckaroo_generate_digital_sign(self, acquirer, inout, values):
+ """ Generate the shasign for incoming or outgoing communications.
+
+ :param browse acquirer: the payment.acquirer browse record. It should
+ have a shakey in shaky out
+ :param string inout: 'in' (openerp contacting buckaroo) or 'out' (buckaroo
+ contacting openerp).
+ :param dict values: transaction values
+
+ :return string: shasign
+ """
+ assert inout in ('in', 'out')
+ assert acquirer.provider == 'buckaroo'
+
+ keys = "add_returndata Brq_amount Brq_culture Brq_currency Brq_invoicenumber Brq_return Brq_returncancel Brq_returnerror Brq_returnreject brq_test Brq_websitekey".split()
+
+ def get_value(key):
+ if values.get(key):
+ return values[key]
+ return ''
+
+ if inout == 'out':
+ if 'BRQ_SIGNATURE' in values:
+ del values['BRQ_SIGNATURE']
+ items = sorted((k.upper(), v) for k, v in values.items())
+ sign = ''.join('%s=%s' % (k, v) for k, v in items)
+ else:
+ sign = ''.join('%s=%s' % (k,get_value(k)) for k in keys)
+ #Add the pre-shared secret key at the end of the signature
+ sign = sign + acquirer.brq_secretkey
+ if isinstance(sign, str):
+ sign = urlparse.parse_qsl(sign)
+ shasign = sha1(sign).hexdigest()
+ return shasign
+
+
+ def buckaroo_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None):
+ base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
+ acquirer = self.browse(cr, uid, id, context=context)
+ buckaroo_tx_values = dict(tx_values)
+ buckaroo_tx_values.update({
+ 'Brq_websitekey': acquirer.brq_websitekey,
+ 'Brq_amount': tx_values['amount'],
+ 'Brq_currency': tx_values['currency'] and tx_values['currency'].name or '',
+ 'Brq_invoicenumber': tx_values['reference'],
+ 'brq_test' : True,
+ 'Brq_return': '%s' % urlparse.urljoin(base_url, BuckarooController._return_url),
+ 'Brq_returncancel': '%s' % urlparse.urljoin(base_url, BuckarooController._cancel_url),
+ 'Brq_returnerror': '%s' % urlparse.urljoin(base_url, BuckarooController._exception_url),
+ 'Brq_returnreject': '%s' % urlparse.urljoin(base_url, BuckarooController._reject_url),
+ 'Brq_culture': 'en-US',
+ })
+ if buckaroo_tx_values.get('return_url'):
+ buckaroo_tx_values['add_returndata'] = {'return_url': '%s' % buckaroo_tx_values.pop('return_url')}
+ else:
+ buckaroo_tx_values['add_returndata'] = ''
+ buckaroo_tx_values['Brq_signature'] = self._buckaroo_generate_digital_sign(acquirer, 'in', buckaroo_tx_values)
+ return partner_values, buckaroo_tx_values
+
+ def buckaroo_get_form_action_url(self, cr, uid, id, context=None):
+ acquirer = self.browse(cr, uid, id, context=context)
+ return self._get_buckaroo_urls(cr, uid, acquirer.environment, context=context)['buckaroo_form_url']
+
+class TxBuckaroo(osv.Model):
+ _inherit = 'payment.transaction'
+
+ # buckaroo status
+ _buckaroo_valid_tx_status = [190]
+ _buckaroo_pending_tx_status = [790, 791, 792, 793]
+ _buckaroo_cancel_tx_status = [890, 891]
+ _buckaroo_error_tx_status = [490, 491, 492]
+ _buckaroo_reject_tx_status = [690]
+
+ _columns = {
+ 'buckaroo_txnid': fields.char('Transaction ID'),
+ }
+
+
+ # --------------------------------------------------
+ # FORM RELATED METHODS
+ # --------------------------------------------------
+
+ def _buckaroo_form_get_tx_from_data(self, cr, uid, data, context=None):
+ """ Given a data dict coming from buckaroo, verify it and find the related
+ transaction record. """
+ reference, pay_id, shasign = data.get('BRQ_INVOICENUMBER'), data.get('BRQ_PAYMENT'), data.get('BRQ_SIGNATURE')
+ if not reference or not pay_id or not shasign:
+ error_msg = 'Buckaroo: received data with missing reference (%s) or pay_id (%s) or shashign (%s)' % (reference, pay_id, shasign)
+ _logger.error(error_msg)
+ raise ValidationError(error_msg)
+
+ tx_ids = self.search(cr, uid, [('reference', '=', reference)], context=context)
+ if not tx_ids or len(tx_ids) > 1:
+ error_msg = 'Buckaroo: received data for reference %s' % (reference)
+ if not tx_ids:
+ error_msg += '; no order found'
+ else:
+ error_msg += '; multiple order found'
+ _logger.error(error_msg)
+ raise ValidationError(error_msg)
+ tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
+
+ #verify shasign
+ shasign_check = self.pool['payment.acquirer']._buckaroo_generate_digital_sign(tx.acquirer_id, 'out' ,data)
+ if shasign_check.upper() != shasign.upper():
+ error_msg = 'Buckaroo: invalid shasign, received %s, computed %s, for data %s' % (shasign, shasign_check, data)
+ _logger.error(error_msg)
+ raise ValidationError(error_msg)
+
+ return tx
+
+ def _buckaroo_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
+ invalid_parameters = []
+
+ if tx.acquirer_reference and data.get('BRQ_TRANSACTIONS') != tx.acquirer_reference:
+ invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), tx.acquirer_reference))
+ # check what is buyed
+ if float_compare(float(data.get('BRQ_AMOUNT', '0.0')), tx.amount, 2) != 0:
+ invalid_parameters.append(('Amount', data.get('BRQ_AMOUNT'), '%.2f' % tx.amount))
+ if data.get('BRQ_CURRENCY') != tx.currency_id.name:
+ invalid_parameters.append(('Currency', data.get('BRQ_CURRENCY'), tx.currency_id.name))
+
+ return invalid_parameters
+
+ def _buckaroo_form_validate(self, cr, uid, tx, data, context=None):
+ status_code = int(data.get('BRQ_STATUSCODE','0'))
+ if status_code in self._buckaroo_valid_tx_status:
+ tx.write({
+ 'state': 'done',
+ 'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
+ })
+ return True
+ elif status_code in self._buckaroo_pending_tx_status:
+ tx.write({
+ 'state': 'pending',
+ 'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
+ })
+ return True
+ elif status_code in self._buckaroo_cancel_tx_status:
+ tx.write({
+ 'state': 'cancel',
+ 'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
+ })
+ return True
+ else:
+ error = 'Buckaroo: feedback error'
+ _logger.info(error)
+ tx.write({
+ 'state': 'error',
+ 'state_message': error,
+ 'buckaroo_txnid': data.get('BRQ_TRANSACTIONS'),
+ })
+ return False
diff --git a/addons/payment_buckaroo/static/description/icon.png b/addons/payment_buckaroo/static/description/icon.png
new file mode 100644
index 00000000000..663fcad1d6d
Binary files /dev/null and b/addons/payment_buckaroo/static/description/icon.png differ
diff --git a/addons/payment_buckaroo/static/src/img/buckaroo_icon.png b/addons/payment_buckaroo/static/src/img/buckaroo_icon.png
new file mode 100644
index 00000000000..819606db730
Binary files /dev/null and b/addons/payment_buckaroo/static/src/img/buckaroo_icon.png differ
diff --git a/addons/payment_buckaroo/static/src/img/logo.png b/addons/payment_buckaroo/static/src/img/logo.png
new file mode 100644
index 00000000000..663fcad1d6d
Binary files /dev/null and b/addons/payment_buckaroo/static/src/img/logo.png differ
diff --git a/addons/payment_buckaroo/tests/__init__.py b/addons/payment_buckaroo/tests/__init__.py
new file mode 100644
index 00000000000..d245ab8339d
--- /dev/null
+++ b/addons/payment_buckaroo/tests/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+from openerp.addons.payment_buckaroo.tests import test_buckaroo
+
+checks = [
+ test_buckaroo,
+]
diff --git a/addons/payment_buckaroo/tests/test_buckaroo.py b/addons/payment_buckaroo/tests/test_buckaroo.py
new file mode 100644
index 00000000000..b826f3b920e
--- /dev/null
+++ b/addons/payment_buckaroo/tests/test_buckaroo.py
@@ -0,0 +1,178 @@
+# -*- coding: utf-8 -*-
+
+from lxml import objectify
+import urlparse
+
+import openerp
+from openerp.addons.payment.models.payment_acquirer import ValidationError
+from openerp.addons.payment.tests.common import PaymentAcquirerCommon
+from openerp.addons.payment_buckaroo.controllers.main import BuckarooController
+from openerp.tools import mute_logger
+
+
+@openerp.tests.common.at_install(False)
+@openerp.tests.common.post_install(False)
+class BuckarooCommon(PaymentAcquirerCommon):
+
+ def setUp(self):
+ super(BuckarooCommon, self).setUp()
+ cr, uid = self.cr, self.uid
+ self.base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url')
+
+ # get the buckaroo account
+ model, self.buckaroo_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_buckaroo', 'payment_acquirer_buckaroo')
+
+
+@openerp.tests.common.at_install(False)
+@openerp.tests.common.post_install(False)
+class BuckarooForm(BuckarooCommon):
+
+ def test_10_Buckaroo_form_render(self):
+ cr, uid, context = self.cr, self.uid, {}
+ # be sure not to do stupid things
+ buckaroo = self.payment_acquirer.browse(self.cr, self.uid, self.buckaroo_id, None)
+ self.assertEqual(buckaroo.environment, 'test', 'test without test environment')
+
+ # ----------------------------------------
+ # Test: button direct rendering
+ # ----------------------------------------
+
+ form_values = {
+ 'add_returndata': None,
+ 'Brq_websitekey': buckaroo.brq_websitekey,
+ 'Brq_amount': '2240.0',
+ 'Brq_currency': 'EUR',
+ 'Brq_invoicenumber': 'SO004',
+ 'Brq_signature': '1b8c10074c622d965272a91a9e88b5b3777d2474', # update me
+ 'brq_test': 'True',
+ 'Brq_return': '%s' % urlparse.urljoin(self.base_url, BuckarooController._return_url),
+ 'Brq_returncancel': '%s' % urlparse.urljoin(self.base_url, BuckarooController._cancel_url),
+ 'Brq_returnerror': '%s' % urlparse.urljoin(self.base_url, BuckarooController._exception_url),
+ 'Brq_returnreject': '%s' % urlparse.urljoin(self.base_url, BuckarooController._reject_url),
+ 'Brq_culture': 'en-US',
+ }
+
+ # render the button
+ res = self.payment_acquirer.render(
+ cr, uid, self.buckaroo_id,
+ 'SO004', 2240.0, self.currency_euro_id,
+ partner_id=None,
+ partner_values=self.buyer_values,
+ context=context)
+
+ # check form result
+ tree = objectify.fromstring(res)
+ self.assertEqual(tree.get('action'), 'https://testcheckout.buckaroo.nl/html/', 'Buckaroo: wrong form POST url')
+ for form_input in tree.input:
+ if form_input.get('name') in ['submit']:
+ continue
+ self.assertEqual(
+ form_input.get('value'),
+ form_values[form_input.get('name')],
+ 'Buckaroo: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
+ )
+
+ # ----------------------------------------
+ # Test2: button using tx + validation
+ # ----------------------------------------
+
+ # create a new draft tx
+ tx_id = self.payment_transaction.create(
+ cr, uid, {
+ 'amount': 2240.0,
+ 'acquirer_id': self.buckaroo_id,
+ 'currency_id': self.currency_euro_id,
+ 'reference': 'SO004',
+ 'partner_id': self.buyer_id,
+ }, context=context
+ )
+
+ # render the button
+ res = self.payment_acquirer.render(
+ cr, uid, self.buckaroo_id,
+ 'should_be_erased', 2240.0, self.currency_euro,
+ tx_id=tx_id,
+ partner_id=None,
+ partner_values=self.buyer_values,
+ context=context)
+
+ # check form result
+ tree = objectify.fromstring(res)
+ self.assertEqual(tree.get('action'), 'https://testcheckout.buckaroo.nl/html/', 'Buckaroo: wrong form POST url')
+ for form_input in tree.input:
+ if form_input.get('name') in ['submit']:
+ continue
+ self.assertEqual(
+ form_input.get('value'),
+ form_values[form_input.get('name')],
+ 'Buckaroo: wrong value for form input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
+ )
+
+ @mute_logger('openerp.addons.payment_buckaroo.models.buckaroo', 'ValidationError')
+ def test_20_buckaroo_form_management(self):
+ cr, uid, context = self.cr, self.uid, {}
+ # be sure not to do stupid thing
+ buckaroo = self.payment_acquirer.browse(self.cr, self.uid, self.buckaroo_id, None)
+ self.assertEqual(buckaroo.environment, 'test', 'test without test environment')
+
+ # typical data posted by buckaroo after client has successfully paid
+ buckaroo_post_data = {
+ 'BRQ_RETURNDATA': u'',
+ 'BRQ_AMOUNT': u'2240.00',
+ 'BRQ_CURRENCY': u'EUR',
+ 'BRQ_CUSTOMER_NAME': u'Jan de Tester',
+ 'BRQ_INVOICENUMBER': u'SO004',
+ 'BRQ_PAYMENT': u'573311D081B04069BD6336001611DBD4',
+ 'BRQ_PAYMENT_METHOD': u'paypal',
+ 'BRQ_SERVICE_PAYPAL_PAYERCOUNTRY': u'NL',
+ 'BRQ_SERVICE_PAYPAL_PAYEREMAIL': u'fhe@openerp.com',
+ 'BRQ_SERVICE_PAYPAL_PAYERFIRSTNAME': u'Jan',
+ 'BRQ_SERVICE_PAYPAL_PAYERLASTNAME': u'Tester',
+ 'BRQ_SERVICE_PAYPAL_PAYERMIDDLENAME': u'de',
+ 'BRQ_SERVICE_PAYPAL_PAYERSTATUS': u'verified',
+ 'BRQ_SIGNATURE': u'175d82dd53a02bad393fee32cb1eafa3b6fbbd91',
+ 'BRQ_STATUSCODE': u'190',
+ 'BRQ_STATUSCODE_DETAIL': u'S001',
+ 'BRQ_STATUSMESSAGE': u'Transaction successfully processed',
+ 'BRQ_TEST': u'true',
+ 'BRQ_TIMESTAMP': u'2014-05-08 12:41:21',
+ 'BRQ_TRANSACTIONS': u'D6106678E1D54EEB8093F5B3AC42EA7B',
+ 'BRQ_WEBSITEKEY': u'5xTGyGyPyl',
+ }
+
+ # should raise error about unknown tx
+ with self.assertRaises(ValidationError):
+ self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
+
+ tx_id = self.payment_transaction.create(
+ cr, uid, {
+ 'amount': 2240.0,
+ 'acquirer_id': self.buckaroo_id,
+ 'currency_id': self.currency_euro_id,
+ 'reference': 'SO004',
+ 'partner_name': 'Norbert Buyer',
+ 'partner_country_id': self.country_france_id,
+ }, context=context
+ )
+ # validate it
+ self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
+ # check state
+ tx = self.payment_transaction.browse(cr, uid, tx_id, context=context)
+ self.assertEqual(tx.state, 'done', 'Buckaroo: validation did not put tx into done state')
+ self.assertEqual(tx.buckaroo_txnid, buckaroo_post_data.get('BRQ_TRANSACTIONS'), 'Buckaroo: validation did not update tx payid')
+
+ # reset tx
+ tx.write({'state': 'draft', 'date_validate': False, 'buckaroo_txnid': False})
+
+ # now buckaroo post is ok: try to modify the SHASIGN
+ buckaroo_post_data['BRQ_SIGNATURE'] = '54d928810e343acf5fb0c3ee75fd747ff159ef7a'
+ with self.assertRaises(ValidationError):
+ self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
+
+ # simulate an error
+ buckaroo_post_data['BRQ_STATUSCODE'] = 2
+ buckaroo_post_data['BRQ_SIGNATURE'] = '4164b52adb1e6a2221d3d8a39d8c3e18a9ecb90b'
+ self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context)
+ # check state
+ tx = self.payment_transaction.browse(cr, uid, tx_id, context=context)
+ self.assertEqual(tx.state, 'error', 'Buckaroo: erroneous validation did not put tx into error state')
diff --git a/addons/payment_buckaroo/views/buckaroo.xml b/addons/payment_buckaroo/views/buckaroo.xml
new file mode 100644
index 00000000000..567a2691b00
--- /dev/null
+++ b/addons/payment_buckaroo/views/buckaroo.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/payment_buckaroo/views/payment_acquirer.xml b/addons/payment_buckaroo/views/payment_acquirer.xml
new file mode 100644
index 00000000000..4eb294e49c3
--- /dev/null
+++ b/addons/payment_buckaroo/views/payment_acquirer.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+ acquirer.form.buckaroo
+ payment.acquirer
+
+
+
+
+
+
+
+
+
+
+
+
+ acquirer.transaction.form.buckaroo
+ payment.transaction
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/payment_paypal/models/paypal.py b/addons/payment_paypal/models/paypal.py
index d71e3d0620b..5dcbd975a4a 100644
--- a/addons/payment_paypal/models/paypal.py
+++ b/addons/payment_paypal/models/paypal.py
@@ -95,9 +95,12 @@ class AcquirerPaypal(osv.Model):
return 0.0
country = self.pool['res.country'].browse(cr, uid, country_id, context=context)
if country and acquirer.company_id.country_id.id == country.id:
- fees = amount * (1 + acquirer.fees_dom_var / 100.0) + acquirer.fees_dom_fixed - amount
+ percentage = acquirer.fees_dom_var
+ fixed = acquirer.fees_dom_fixed
else:
- fees = amount * (1 + acquirer.fees_int_var / 100.0) + acquirer.fees_int_fixed - amount
+ percentage = acquirer.fees_int_var
+ fixed = acquirer.fees_int_fixed
+ fees = (percentage / 100.0 * amount + fixed ) / (1 - percentage / 100.0)
return fees
def paypal_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None):
diff --git a/addons/payment_paypal/tests/test_paypal.py b/addons/payment_paypal/tests/test_paypal.py
index ccac9b6fdef..a66b90cf0fb 100644
--- a/addons/payment_paypal/tests/test_paypal.py
+++ b/addons/payment_paypal/tests/test_paypal.py
@@ -148,7 +148,7 @@ class PaypalForm(PaypalCommon):
for form_input in tree.input:
if form_input.get('name') in ['handling']:
handling_found = True
- self.assertEqual(form_input.get('value'), '1.56', 'paypal: wrong computed fees')
+ self.assertEqual(form_input.get('value'), '1.57', 'paypal: wrong computed fees')
self.assertTrue(handling_found, 'paypal: fees_active did not add handling input in rendered form')
@mute_logger('openerp.addons.payment_paypal.models.paypal', 'ValidationError')
diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml
index fc195f86ee4..391c61a8e94 100644
--- a/addons/purchase/edi/purchase_order_action_data.xml
+++ b/addons/purchase/edi/purchase_order_action_data.xml
@@ -111,7 +111,7 @@
REFERENCES
Order number: ${object.name}
- Order total: ${object.amount_total} ${object.pricelist_id.currency_id.name}
+ Order total: ${object.amount_total} ${object.currency_id.name}
Order date: ${object.date_order}
% if object.origin:
Order reference: ${object.origin}
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 6ff6feae376..67b07644dc4 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -220,7 +220,7 @@ class purchase_order(osv.osv):
'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of reception operations that have been generated for this purchase order."),
'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'),
- 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
+ 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been validated"),
'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Based on incoming shipments')], 'Invoicing Control', required=True,
readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]},
diff --git a/addons/purchase/views/report_purchaseorder.xml b/addons/purchase/views/report_purchaseorder.xml
index 300149e4f1d..91486c035aa 100644
--- a/addons/purchase/views/report_purchaseorder.xml
+++ b/addons/purchase/views/report_purchaseorder.xml
@@ -23,7 +23,7 @@
diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py
index de3bbd63db6..c6bc13d38f8 100644
--- a/addons/report/controllers/main.py
+++ b/addons/report/controllers/main.py
@@ -66,7 +66,7 @@ class ReportController(Controller):
# Misc. route utils
#------------------------------------------------------
@route(['/report/barcode', '/report/barcode//'], type='http', auth="user")
- def report_barcode(self, type, value, width=300, height=50):
+ def report_barcode(self, type, value, width=600, height=100):
"""Contoller able to render barcode images thanks to reportlab.
Samples:
diff --git a/addons/report/data/report_paperformat.xml b/addons/report/data/report_paperformat.xml
index 275d65edd13..f05e0afb6f0 100644
--- a/addons/report/data/report_paperformat.xml
+++ b/addons/report/data/report_paperformat.xml
@@ -9,7 +9,7 @@
0Portrait40
- 20
+ 2377
diff --git a/addons/report/models/report.py b/addons/report/models/report.py
index d8a51387cb7..cbbee7fcebd 100644
--- a/addons/report/models/report.py
+++ b/addons/report/models/report.py
@@ -25,10 +25,8 @@ from openerp.tools.translate import _
from openerp.addons.web.http import request
from openerp.tools.safe_eval import safe_eval as eval
-import os
+import re
import time
-import psutil
-import signal
import base64
import logging
import tempfile
@@ -52,15 +50,19 @@ try:
['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
except OSError:
- _logger.error('You need wkhtmltopdf to print a pdf version of the reports.')
+ _logger.info('You need wkhtmltopdf to print a pdf version of the reports.')
else:
out, err = process.communicate()
- version = out.splitlines()[1].strip()
- version = version.split(' ')[1]
+ version = re.search('([0-9.]+)', out).group(0)
if LooseVersion(version) < LooseVersion('0.12.0'):
- _logger.warning('Upgrade wkhtmltopdf to (at least) 0.12.0')
+ _logger.info('Upgrade wkhtmltopdf to (at least) 0.12.0')
wkhtmltopdf_state = 'upgrade'
- wkhtmltopdf_state = 'ok'
+ else:
+ wkhtmltopdf_state = 'ok'
+
+ if config['workers'] == 1:
+ _logger.info('You need to start OpenERP with at least two workers to print a pdf version of the reports.')
+ wkhtmltopdf_state = 'workers'
class Report(osv.Model):
@@ -343,19 +345,20 @@ class Report(osv.Model):
command_args = []
tmp_dir = tempfile.gettempdir()
- # Passing the cookie to wkhtmltopdf in order to resolve URL.
+ # Passing the cookie to wkhtmltopdf in order to resolve internal links.
try:
if request:
command_args.extend(['--cookie', 'session_id', request.session.sid])
except AttributeError:
pass
- # Display arguments
+ # Wkhtmltopdf arguments
+ command_args.extend(['--quiet']) # Less verbose error messages
if paperformat:
+ # Convert the paperformat record into arguments
command_args.extend(self._build_wkhtmltopdf_args(paperformat, spec_paperformat_args))
- command_args.extend(['--load-error-handling', 'ignore'])
-
+ # Force the landscape orientation if necessary
if landscape and '--orientation' in command_args:
command_args_copy = list(command_args)
for index, elem in enumerate(command_args_copy):
@@ -366,61 +369,46 @@ class Report(osv.Model):
elif landscape and not '--orientation' in command_args:
command_args.extend(['--orientation', 'landscape'])
+ # Execute WKhtmltopdf
pdfdocuments = []
- # HTML to PDF thanks to WKhtmltopdf
for index, reporthtml in enumerate(bodies):
- command_arg_local = []
- pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.',
- mode='w+b')
- # Directly load the document if we have it
+ local_command_args = []
+ pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.', mode='w+b')
+
+ # Directly load the document if we already have it
if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]):
pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0]))
pdfreport.seek(0)
pdfdocuments.append(pdfreport)
continue
- # Header stuff
+ # Wkhtmltopdf handles header/footer as separate pages. Create them if necessary.
if headers:
- head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.',
- dir=tmp_dir, mode='w+')
+ head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', dir=tmp_dir, mode='w+')
head_file.write(headers[index])
head_file.seek(0)
- command_arg_local.extend(['--header-html', head_file.name])
-
- # Footer stuff
+ local_command_args.extend(['--header-html', head_file.name])
if footers:
- foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.',
- dir=tmp_dir, mode='w+')
+ foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', dir=tmp_dir, mode='w+')
foot_file.write(footers[index])
foot_file.seek(0)
- command_arg_local.extend(['--footer-html', foot_file.name])
+ local_command_args.extend(['--footer-html', foot_file.name])
# Body stuff
- content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.',
- dir=tmp_dir, mode='w+')
+ content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', dir=tmp_dir, mode='w+')
content_file.write(reporthtml[1])
content_file.seek(0)
try:
- # If the server is running with only one worker, ask to create a secund to be able
- # to serve the http request of wkhtmltopdf subprocess.
- if config['workers'] == 1:
- ppid = psutil.Process(os.getpid()).ppid
- os.kill(ppid, signal.SIGTTIN)
-
- wkhtmltopdf = command + command_args + command_arg_local
+ wkhtmltopdf = command + command_args + local_command_args
wkhtmltopdf += [content_file.name] + [pdfreport.name]
- process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = process.communicate()
- if config['workers'] == 1:
- os.kill(ppid, signal.SIGTTOU)
-
- if process.returncode != 0:
+ if process.returncode not in [0, 1]:
raise osv.except_osv(_('Report (PDF)'),
- _('wkhtmltopdf failed with error code = %s. '
+ _('Wkhtmltopdf failed (error code: %s). '
'Message: %s') % (str(process.returncode), err))
# Save the pdf in attachment if marked
@@ -446,7 +434,7 @@ class Report(osv.Model):
except:
raise
- # Get and return the full pdf
+ # Return the entire document
if len(pdfdocuments) == 1:
content = pdfdocuments[0].read()
pdfdocuments[0].close()
diff --git a/addons/report/static/src/js/qwebactionmanager.js b/addons/report/static/src/js/qwebactionmanager.js
index 37a0fae6ea6..cb0f3921482 100644
--- a/addons/report/static/src/js/qwebactionmanager.js
+++ b/addons/report/static/src/js/qwebactionmanager.js
@@ -54,33 +54,35 @@ openerp.report = function(instance) {
if (action.report_type == 'qweb-html') {
window.open(report_url, '_blank', 'height=900,width=1280');
instance.web.unblockUI();
- } else {
+ } else if (action.report_type === 'qweb-pdf') {
// Trigger the download of the pdf/controller report
-
- if (action.report_type == 'qweb-pdf') {
- (wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) {
- // Fallback of qweb-pdf if wkhtmltopdf is not installed
- if (presence == 'install' && action.report_type == 'qweb-pdf') {
- self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \
- system. The report will be shown in html.
\
- wkhtmltopdf.org'), true);
- report_url = report_url.substring(12)
- window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
- instance.web.unblockUI();
- return;
- } else {
- if (presence == 'upgrade') {
- self.do_notify(_t('Report'), _t('You should upgrade your version of\
- Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\
- support for table-breaking between pages.
wkhtmltopdf.org'), true);
- }
- }
- return trigger_download(self.session, response, c);
- });
- } else if (action.report_type == 'controller') {
+ (wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) {
+ // Fallback on html if wkhtmltopdf is not installed or if OpenERP is started with one worker
+ if (presence === 'install') {
+ self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \
+system. The report will be shown in html.
\
+wkhtmltopdf.org'), true);
+ report_url = report_url.substring(12)
+ window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
+ instance.web.unblockUI();
+ return;
+ } else if (presence === 'workers') {
+ self.do_notify(_t('Report'), _t('You need to start OpenERP with at least two \
+workers to print a pdf version of the reports.'), true);
+ report_url = report_url.substring(12)
+ window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024');
+ instance.web.unblockUI();
+ return;
+ } else if (presence === 'upgrade') {
+ self.do_notify(_t('Report'), _t('You should upgrade your version of\
+ Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\
+ support for table-breaking between pages.