Skip to content

Commit

Permalink
[MERGE] forward port branch saas-14 up to b780e4a
Browse files Browse the repository at this point in the history
  • Loading branch information
KangOl committed Nov 30, 2017
2 parents 92f7ecc + b780e4a commit 9853933
Show file tree
Hide file tree
Showing 55 changed files with 361 additions and 104 deletions.
2 changes: 1 addition & 1 deletion addons/account/models/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ class AccountTaxGroup(models.Model):
class AccountTax(models.Model):
_name = 'account.tax'
_description = 'Tax'
_order = 'sequence'
_order = 'sequence,id'

@api.model
def _default_tax_group(self):
Expand Down
15 changes: 8 additions & 7 deletions addons/account/models/account_journal_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,19 @@ def get_journal_dashboard_datas(self):
number_late += 1
sum_late += cur.compute(result.get('amount_total'), currency) * factor

difference = currency.round(last_balance-account_sum) + 0.0
return {
'number_to_reconcile': number_to_reconcile,
'account_balance': formatLang(self.env, account_sum, currency_obj=self.currency_id or self.company_id.currency_id),
'last_balance': formatLang(self.env, last_balance, currency_obj=self.currency_id or self.company_id.currency_id),
'difference': (last_balance-account_sum) and formatLang(self.env, last_balance-account_sum, currency_obj=self.currency_id or self.company_id.currency_id) or False,
'account_balance': formatLang(self.env, currency.round(account_sum) + 0.0, currency_obj=currency),
'last_balance': formatLang(self.env, currency.round(last_balance) + 0.0, currency_obj=currency),
'difference': formatLang(self.env, difference, currency_obj=currency) if difference else False,
'number_draft': number_draft,
'number_waiting': number_waiting,
'number_late': number_late,
'sum_draft': formatLang(self.env, sum_draft or 0.0, currency_obj=self.currency_id or self.company_id.currency_id),
'sum_waiting': formatLang(self.env, sum_waiting or 0.0, currency_obj=self.currency_id or self.company_id.currency_id),
'sum_late': formatLang(self.env, sum_late or 0.0, currency_obj=self.currency_id or self.company_id.currency_id),
'currency_id': self.currency_id and self.currency_id.id or self.company_id.currency_id.id,
'sum_draft': formatLang(self.env, currency.round(sum_draft) + 0.0, currency_obj=currency),
'sum_waiting': formatLang(self.env, currency.round(sum_waiting) + 0.0, currency_obj=currency),
'sum_late': formatLang(self.env, currency.round(sum_late) + 0.0, currency_obj=currency),
'currency_id': currency.id,
'bank_statements_source': self.bank_statements_source,
'title': title,
}
Expand Down
3 changes: 1 addition & 2 deletions addons/account/models/chart_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ def migrate_tags_on_taxes(cr, registry):
('type_tax_use', '=', tax_template.type_tax_use),
('description', '=', tax_template.description)
])
if len(tax_id.ids) == 1:
tax_id.sudo().write({'tag_ids': [(6, 0, tax_template.tag_ids.ids)]})
tax_id.sudo().write({'tag_ids': [(6, 0, tax_template.tag_ids.ids)]})

def preserve_existing_tags_on_taxes(cr, registry, module):
''' This is a utility function used to preserve existing previous tags during upgrade of the module.'''
Expand Down
17 changes: 11 additions & 6 deletions addons/account/models/partner.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,16 @@ def _invoice_total(self):
partner.total_invoiced = sum(price['total'] for price in price_totals if price['partner_id'] in child_ids)

@api.multi
def _journal_item_count(self):
def _compute_journal_item_count(self):
AccountMoveLine = self.env['account.move.line']
for partner in self:
partner.journal_item_count = self.env['account.move.line'].search_count([('partner_id', '=', partner.id)])
partner.contracts_count = self.env['account.analytic.account'].search_count([('partner_id', '=', partner.id)])
partner.journal_item_count = AccountMoveLine.search_count([('partner_id', '=', partner.id)])

@api.multi
def _compute_contracts_count(self):
AccountAnalyticAccount = self.env['account.analytic.account']
for partner in self:
partner.contracts_count = AccountAnalyticAccount.search_count([('partner_id', '=', partner.id)])

def get_followup_lines_domain(self, date, overdue_only=False, only_unblocked=False):
domain = [('reconciled', '=', False), ('account_id.deprecated', '=', False), ('account_id.internal_type', '=', 'receivable'), '|', ('debit', '!=', 0), ('credit', '!=', 0), ('company_id', '=', self.env.user.company_id.id)]
Expand Down Expand Up @@ -382,9 +388,8 @@ def _get_company_currency(self):
groups='account.group_account_invoice')
currency_id = fields.Many2one('res.currency', compute='_get_company_currency', readonly=True,
string="Currency", help='Utility field to express amount currency')

contracts_count = fields.Integer(compute='_journal_item_count', string="Contracts", type='integer')
journal_item_count = fields.Integer(compute='_journal_item_count', string="Journal Items", type="integer")
contracts_count = fields.Integer(compute='_compute_contracts_count', string="Contracts", type='integer')
journal_item_count = fields.Integer(compute='_compute_journal_item_count', string="Journal Items", type="integer")
issued_total = fields.Monetary(compute='_compute_issued_total', string="Journal Items")
property_account_payable_id = fields.Many2one('account.account', company_dependent=True,
string="Account Payable", oldname="property_account_payable",
Expand Down
2 changes: 1 addition & 1 deletion addons/event_sale/models/sale_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def _update_registrations(self, confirm=True, cancel_to_draft=False, registratio
registrations linked to this line. This method update existing registrations
and create new one for missing one. """
Registration = self.env['event.registration']
registrations = Registration.search([('sale_order_line_id', 'in', self.ids)])
registrations = Registration.search([('sale_order_line_id', 'in', self.ids), ('state', '!=', 'cancel')])
for so_line in self.filtered('event_id'):
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)
if confirm:
Expand Down
9 changes: 8 additions & 1 deletion addons/hr_expense/models/hr_expense.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,4 +592,11 @@ def action_open_journal_entries(self):
def _check_employee(self):
employee_ids = self.expense_line_ids.mapped('employee_id')
if len(employee_ids) > 1 or (len(employee_ids) == 1 and employee_ids != self.employee_id):
raise ValidationError(_('You cannot add expense lines of another employee.'))
raise ValidationError(_('You cannot add expense lines of another employee.'))

@api.one
@api.constrains('expense_line_ids')
def _check_payment_mode(self):
payment_mode = set(self.expense_line_ids.mapped('payment_mode'))
if len(payment_mode) > 1:
raise ValidationError(_('You cannot report expenses with different payment modes.'))
7 changes: 6 additions & 1 deletion addons/hr_expense/views/hr_expense_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -465,9 +465,14 @@
<field name="attachment_number" string=" "/>
<button name="action_get_attachment_view" string="View Attachments" type="object" icon="fa-paperclip"/>
<field name="tax_ids" widget="many2many_tags"/>
<field name="total_amount" sum="Total Amount" widget="monetary"/>
<field name="total_amount" widget="monetary"/>
</tree>
</field>
<group>
<group class="oe_subtotal_footer oe_right" colspan="2" name="expense_total">
<field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}"/>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
Expand Down
5 changes: 3 additions & 2 deletions addons/l10n_fr_certification/models/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,9 @@ class AccountJournal(models.Model):
@api.multi
def write(self, vals):
# restrict the operation in case we are trying to write a forbidden field
if self.company_id.country_id.code == 'FR' and vals.get('update_posted'):
raise UserError(ERR_MSG % (self._name, 'update_posted'))
for rec in self:
if rec.company_id.country_id.code == 'FR' and vals.get('update_posted'):
raise UserError(ERR_MSG % (self._name, 'update_posted'))
return super(AccountJournal, self).write(vals)

@api.model
Expand Down
4 changes: 3 additions & 1 deletion addons/mail/models/mail_thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,8 @@ def _message_extract_payload_postprocess(self, message, body, attachments):
mail module, and should not contain security or generic html cleaning.
Indeed those aspects should be covered by the html_sanitize method
located in tools. """
if not body:
return body, attachments
root = lxml.html.fromstring(body)
postprocessed = False
to_remove = []
Expand Down Expand Up @@ -1400,7 +1402,7 @@ def _message_extract_payload(self, message, save_original=False):
# Content-Type: multipart/related;
# boundary="_004_3f1e4da175f349248b8d43cdeb9866f1AMSPR06MB343eurprd06pro_";
# type="text/html"
if not message.is_multipart() or message.get('content-type', '').startswith("text/"):
if message.get_content_maintype() == 'text':
encoding = message.get_content_charset()
body = message.get_payload(decode=True)
body = tools.ustr(body, encoding, errors='replace')
Expand Down
4 changes: 4 additions & 0 deletions addons/mail/models/res_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import _, api, exceptions, fields, models
from odoo.addons.base.res.res_users import is_selection_groups


class Users(models.Model):
Expand Down Expand Up @@ -61,11 +62,14 @@ def create(self, values):
@api.multi
def write(self, vals):
write_res = super(Users, self).write(vals)
sel_groups = [vals[k] for k in vals if is_selection_groups(k) and vals[k]]
if vals.get('groups_id'):
# form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
user_group_ids = [command[1] for command in vals['groups_id'] if command[0] == 4]
user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
self.env['mail.channel'].search([('group_ids', 'in', user_group_ids)])._subscribe_users()
elif sel_groups:
self.env['mail.channel'].search([('group_ids', 'in', sel_groups)])._subscribe_users()
return write_res

def _create_welcome_message(self):
Expand Down
2 changes: 1 addition & 1 deletion addons/mail/security/ir.model.access.csv
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,1
access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
access_mail_message_subtype_user,mail.message.subtype.user,model_mail_message_subtype,,1,1,1,1
access_mail_message_subtype_user,mail.message.subtype.user,model_mail_message_subtype,base.group_user,1,1,1,1
access_mail_tracking_value_all,mail.tracking.value.all,model_mail_tracking_value,,0,0,0,0
access_mail_tracking_value_portal,mail.tracking.value.portal,model_mail_tracking_value,base.group_portal,0,0,0,0
access_mail_tracking_value_user,mail.tracking.value.user,model_mail_tracking_value,base.group_user,0,0,0,0
Expand Down
17 changes: 17 additions & 0 deletions addons/mail/tests/test_mail_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,19 @@
--Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227--
"""

MAIL_SINGLE_BINARY = """X-Original-To: [email protected]
Delivered-To: [email protected]
Received: by mail1.grosbedon.com (Postfix, from userid 10002)
id E8166BFACA; Fri, 23 Aug 2013 13:18:01 +0200 (CEST)
From: "Bruce Wayne" <[email protected]>
Content-Type: application/pdf;
Content-Disposition: filename=thetruth.pdf
Content-Transfer-Encoding: base64
Message-Id: <[email protected]>
Mime-Version: 1.0 (Mac OS X Mail 7.3 \(1878.6\))
SSBhbSB0aGUgQmF0TWFuCg=="""


MAIL_MULTIPART_IMAGE = """X-Original-To: [email protected]
Delivered-To: [email protected]
Expand Down Expand Up @@ -349,6 +362,10 @@ def test_message_parse(self):
res.get('body', ''),
'message_parse: second part of the html version should be in body after parsing multipart/mixed')

res = self.env['mail.thread'].message_parse(MAIL_SINGLE_BINARY)
self.assertEqual(res['body'], '')
self.assertEqual(res['attachments'][0][0], 'thetruth.pdf')

@mute_logger('odoo.addons.mail.models.mail_thread')
def test_message_process_cid(self):
new_groups = self.format_and_process(MAIL_MULTIPART_IMAGE, subject='My Frogs', to='[email protected]')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
<strong t-if="o.address_id != o.partner_invoice_id">Invoice address: </strong>
<div t-field="o.partner_invoice_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": True, "phone_icons": True}'/>
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
<p t-if="o.partner_invoice_id.vat">VAT: <span t-field="o.partner_invoice_id.vat"/></p>
</div>
<div t-if="o.address_id != o.partner_invoice_id">
<strong>Shipping address :</strong>
<div t-field="o.address_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": True, "phone_icons": True}'/>
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
<p t-if="o.address_id.vat">VAT: <span t-field="o.address_id.vat"/></p>
</div>
</div>
<div class="col-xs-5 col-xs-offset-1">
Expand Down
54 changes: 51 additions & 3 deletions addons/payment_authorize/models/authorize_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,34 @@ def strip_ns(xml, ns):
return it.root


def error_check(elem):
"""Check if the response sent by Authorize.net contains an error.
Errors can be a failure to try the transaction (in that case, the transasctionResponse
is empty, and the meaningful error message will be in message/code) or a failure to process
the transaction (in that case, the message/code content will be generic and the actual error
message is in transactionResponse/errors/error/errorText).
:param etree._Element elem: the root element of the response that will be parsed
:rtype: tuple (bool, str)
:return: tuple containnig a boolean indicating if the response should be considered
as an error and the most meaningful error message found in it.
"""
result_code = elem.find('messages/resultCode')
msg = 'No meaningful error message found, please check logs or the Authorize.net backend'
has_error = result_code is not None and result_code.text == 'Error'
if has_error:
# accumulate the most meangingful error
error = elem.find('transactionResponse/errors/error')
error = error if error is not None else elem.find('messages/message')
if error is not None:
code = error[0].text
text = error[1].text
msg = '%s: %s' % (code, text)
return (has_error, msg)


class AuthorizeAPI():
"""Authorize.net Gateway API integration.
Expand All @@ -38,6 +66,9 @@ class AuthorizeAPI():
- Customer Profile/Payment Profile creation
- Transaction authorization/capture/voiding
"""

AUTH_ERROR_STATUS = 3

def __init__(self, acquirer):
"""Initiate the environment with the acquirer data.
Expand All @@ -63,9 +94,6 @@ def _authorize_request(self, data):
request.add_header('Content-Type', 'text/xml')
response = urlopen(request).read()
response = strip_ns(response, XMLNS)
if response.find('messages/resultCode').text == 'Error':
messages = map(lambda m: m.text, response.findall('messages/message/text'))
raise ValidationError(_('Authorize.net Error Message(s):\n %s') % '\n'.join(messages))
return response

def _base_tree(self, requestType):
Expand Down Expand Up @@ -190,6 +218,11 @@ def auth_and_capture(self, token, amount, reference):
etree.SubElement(order, "invoiceNumber").text = reference
response = self._authorize_request(root)
res = dict()
(has_error, error_msg) = error_check(response)
if has_error:
res['x_response_code'] = self.AUTH_ERROR_STATUS
res['x_response_reason_text'] = error_msg
return res
res['x_response_code'] = response.find('transactionResponse/responseCode').text
res['x_trans_id'] = response.find('transactionResponse/transId').text
res['x_type'] = 'auth_capture'
Expand Down Expand Up @@ -220,6 +253,11 @@ def authorize(self, token, amount, reference):
etree.SubElement(order, "invoiceNumber").text = reference
response = self._authorize_request(root)
res = dict()
(has_error, error_msg) = error_check(response)
if has_error:
res['x_response_code'] = self.AUTH_ERROR_STATUS
res['x_response_reason_text'] = error_msg
return res
res['x_response_code'] = response.find('transactionResponse/responseCode').text
res['x_trans_id'] = response.find('transactionResponse/transId').text
res['x_type'] = 'auth_only'
Expand All @@ -245,6 +283,11 @@ def capture(self, transaction_id, amount):
etree.SubElement(tx, "refTransId").text = transaction_id
response = self._authorize_request(root)
res = dict()
(has_error, error_msg) = error_check(response)
if has_error:
res['x_response_code'] = self.AUTH_ERROR_STATUS
res['x_response_reason_text'] = error_msg
return res
res['x_response_code'] = response.find('transactionResponse/responseCode').text
res['x_trans_id'] = response.find('transactionResponse/transId').text
res['x_type'] = 'prior_auth_capture'
Expand All @@ -265,6 +308,11 @@ def void(self, transaction_id):
etree.SubElement(tx, "refTransId").text = transaction_id
response = self._authorize_request(root)
res = dict()
(has_error, error_msg) = error_check(response)
if has_error:
res['x_response_code'] = self.AUTH_ERROR_STATUS
res['x_response_reason_text'] = error_msg
return res
res['x_response_code'] = response.find('transactionResponse/responseCode').text
res['x_trans_id'] = response.find('transactionResponse/transId').text
res['x_type'] = 'void'
Expand Down
19 changes: 18 additions & 1 deletion addons/payment_authorize/tests/test_authorize.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ def test_30_authorize_s2s(self):
'acquirer_id': authorize.id,
'type': 'server2server',
'currency_id': self.currency_usd.id,
'reference': 'test_ref_%s' % odoo.fields.Date.today(),
'reference': 'test_ref_%s' % int(time.time()),
'payment_token_id': payment_token.id,
'partner_id': self.buyer_id,

Expand Down Expand Up @@ -251,3 +251,20 @@ def test_30_authorize_s2s(self):
self.assertEqual(transaction.state, 'authorized')
transaction.action_void()
self.assertEqual(transaction.state, 'cancel')

# try charging an unexisting profile
ghost_payment_token = payment_token.copy()
ghost_payment_token.authorize_profile = '99999999999'
# create normal s2s transaction
transaction = self.env['payment.transaction'].create({
'amount': 500,
'acquirer_id': authorize.id,
'type': 'server2server',
'currency_id': self.currency_usd.id,
'reference': 'test_ref_%s' % int(time.time()),
'payment_token_id': ghost_payment_token.id,
'partner_id': self.buyer_id,

})
transaction.authorize_s2s_do_transaction()
self.assertEqual(transaction.state, 'error')
Loading

0 comments on commit 9853933

Please sign in to comment.