Skip to content

Commit

Permalink
[MERGE] forward port branch 11.0 up to e8f630e
Browse files Browse the repository at this point in the history
  • Loading branch information
KangOl committed Mar 21, 2018
2 parents 9239ec7 + e8f630e commit 6773bdb
Show file tree
Hide file tree
Showing 31 changed files with 283 additions and 70 deletions.
5 changes: 5 additions & 0 deletions addons/account/models/account_move.py
Expand Up @@ -1212,6 +1212,11 @@ def remove_move_reconcile(self):
account_move_line.payment_id.write({'invoice_ids': [(3, invoice.id, None)]})
rec_move_ids += account_move_line.matched_debit_ids
rec_move_ids += account_move_line.matched_credit_ids
if self.env.context.get('invoice_id'):
current_invoice = self.env['account.invoice'].browse(self.env.context['invoice_id'])
rec_move_ids = rec_move_ids.filtered(
lambda r: (r.debit_move_id + r.credit_move_id) & current_invoice.move_id.line_ids
)
return rec_move_ids.unlink()

####################################################
Expand Down
Expand Up @@ -633,7 +633,7 @@ var StatementModel = BasicModel.extend({
handles = [handle];
} else {
_.each(this.lines, function (line, handle) {
if (!line.reconciled && !line.balance.amount && line.reconciliation_proposition.length) {
if (!line.reconciled && line.balance && !line.balance.amount && line.reconciliation_proposition.length) {
handles.push(handle);
}
});
Expand Down
36 changes: 36 additions & 0 deletions addons/account/tests/test_reconciliation.py
Expand Up @@ -625,6 +625,42 @@ def test_partial_reconcile_currencies_01(self):
full_rec_payable = full_rec_move.line_ids.filtered(lambda l: l.account_id == self.account_rsa)
self.assertEqual(full_rec_payable.balance, 18.75)

def test_unreconcile(self):
# Use case:
# 2 invoices paid with a single payment. Unreconcile the payment with one invoice, the
# other invoice should remain reconciled.
inv1 = self.create_invoice(invoice_amount=10, currency_id=self.currency_usd_id)
inv2 = self.create_invoice(invoice_amount=20, currency_id=self.currency_usd_id)
payment = self.env['account.payment'].create({
'payment_type': 'inbound',
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'amount': 100,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_usd.id,
})
payment.post()
credit_aml = payment.move_line_ids.filtered('credit')

# Check residual before assignation
self.assertAlmostEquals(inv1.residual, 10)
self.assertAlmostEquals(inv2.residual, 20)

# Assign credit and residual
inv1.assign_outstanding_credit(credit_aml.id)
inv2.assign_outstanding_credit(credit_aml.id)
self.assertAlmostEquals(inv1.residual, 0)
self.assertAlmostEquals(inv2.residual, 0)

# Unreconcile one invoice at a time and check residual
credit_aml.with_context(invoice_id=inv1.id).remove_move_reconcile()
self.assertAlmostEquals(inv1.residual, 10)
self.assertAlmostEquals(inv2.residual, 0)
credit_aml.with_context(invoice_id=inv2.id).remove_move_reconcile()
self.assertAlmostEquals(inv1.residual, 10)
self.assertAlmostEquals(inv2.residual, 20)

def test_partial_reconcile_currencies_02(self):
####
# Day 1: Invoice Cust/001 to customer (expressed in USD)
Expand Down
7 changes: 7 additions & 0 deletions addons/account_payment/models/payment.py
Expand Up @@ -175,3 +175,10 @@ def _check_or_create_invoice_tx(self, invoice, acquirer, payment_token=None, tx_
})

return tx

def _post_process_after_done(self, **kwargs):
# set invoice id in payment transaction when payment being done from sale order
res = super(PaymentTransaction, self)._post_process_after_done()
if kwargs.get('invoice_id'):
self.account_invoice_id = kwargs['invoice_id']
return res
110 changes: 108 additions & 2 deletions addons/barcodes/static/src/js/barcode_events.js
Expand Up @@ -28,6 +28,12 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
// Keys from a barcode scanner are usually processed as quick as possible,
// but some scanners can use an intercharacter delay (we support <= 50 ms)
max_time_between_keys_in_ms: session.max_time_between_keys_in_ms || 55,
// To be able to receive the barcode value, an input must be focused.
// On mobile devices, this causes the virtual keyboard to open.
// Unfortunately it is not possible to avoid this behavior...
// To avoid keyboard flickering at each detection of a barcode value,
// we want to keep it open for a while (800 ms).
inputTimeOut: 800,

init: function() {
mixins.PropertiesMixin.init.call(this);
Expand All @@ -38,6 +44,30 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
// Bind event handler once the DOM is loaded
// TODO: find a way to be active only when there are listeners on the bus
$(_.bind(this.start, this, false));

// Mobile device detection
var isMobile = navigator.userAgent.match(/Android/i) ||
navigator.userAgent.match(/webOS/i) ||
navigator.userAgent.match(/iPhone/i) ||
navigator.userAgent.match(/iPad/i) ||
navigator.userAgent.match(/iPod/i) ||
navigator.userAgent.match(/BlackBerry/i) ||
navigator.userAgent.match(/Windows Phone/i);
this.isChromeMobile = isMobile && window.chrome;

// Creates an input who will receive the barcode scanner value.
if (this.isChromeMobile) {
this.$barcodeInput = $('<input/>', {
name: 'barcode',
type: 'text',
css: {
'position': 'absolute',
'opacity': 0,
},
});
}

this.__removeBarcodeField = _.debounce(this._removeBarcodeField, this.inputTimeOut);
},

handle_buffered_keys: function() {
Expand Down Expand Up @@ -109,7 +139,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
e.key === "ArrowUp" || e.key === "ArrowDown" ||
e.key === "Escape" || e.key === "Tab" ||
e.key === "Backspace" || e.key === "Delete" ||
/F\d\d?/.test(e.key)) {
e.key === "Unidentified" || /F\d\d?/.test(e.key)) {
return true;
} else {
return false;
Expand Down Expand Up @@ -167,8 +197,84 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
}
},

/**
* Try to detect the barcode value by listening all keydown events:
* Checks if a dom element who may contains text value has the focus.
* If not, it's probably because these events are triggered by a barcode scanner.
* To be able to handle this value, a focused input will be created.
*
* This function also has the responsibility to detect the end of the barcode value.
* (1) In most of cases, an optional key (tab or enter) is sent to mark the end of the value.
* So, we direclty handle the value.
* (2) If no end key is configured, we have to calculate the delay between each keydowns.
* 'max_time_between_keys_in_ms' depends of the device and may be configured.
* Exceeded this timeout, we consider that the barcode value is entirely sent.
*
* @private
* @param {jQuery.Event} e keydown event
*/
_listenBarcodeScanner: function (e) {
if (!$('input:text:focus, textarea:focus, [contenteditable]:focus').length) {
$('body').append(this.$barcodeInput);
this.$barcodeInput.focus();
}
if (this.$barcodeInput.is(":focus")) {
// Handle buffered keys immediately if the keypress marks the end
// of a barcode or after x milliseconds without a new keypress.
clearTimeout(this.timeout);
// On chrome mobile, e.which only works for some special characters like ENTER or TAB.
if (String.fromCharCode(e.which).match(this.suffix)) {
this._handleBarcodeValue(e);
} else {
this.timeout = setTimeout(this._handleBarcodeValue.bind(this, e),
this.max_time_between_keys_in_ms);
}
// if the barcode input doesn't receive keydown for a while, remove it.
this.__removeBarcodeField();
}
},

/**
* Retrieves the barcode value from the temporary input element.
* This checks this value and trigger it on the bus.
*
* @private
* @param {jQuery.Event} keydown event
*/
_handleBarcodeValue: function (e) {
var barcodeValue = this.$barcodeInput.val();
if (barcodeValue.match(this.regexp)) {
core.bus.trigger('barcode_scanned', barcodeValue, $(e.target).parent()[0]);
this.$barcodeInput.val('');
}
},

/**
* Remove the temporary input created to store the barcode value.
* If nothing happens, this input will be removed, so the focus will be lost
* and the virtual keyboard on mobile devices will be closed.
*
* @private
*/
_removeBarcodeField: function () {
if (this.$barcodeInput) {
// Reset the value and remove from the DOM.
this.$barcodeInput.val('').remove();
}
},

start: function(prevent_key_repeat){
$('body').bind("keypress", this.__handler);
// Chrome Mobile isn't triggering keypress event.
// This is marked as Legacy in the DOM-Level-3 Standard.
// See: https://www.w3.org/TR/uievents/#legacy-keyboardevent-event-types
// This fix is only applied for Google Chrome Mobile but it should work for
// all other cases.
// In master, we could remove the behavior with keypress and only use keydown.
if (this.isChromeMobile) {
$('body').on("keydown", this._listenBarcodeScanner.bind(this));
} else {
$('body').bind("keypress", this.__handler);
}
if (prevent_key_repeat === true) {
$('body').bind("keydown", this.__keydown_handler);
$('body').bind('keyup', this.__keyup_handler);
Expand Down
4 changes: 3 additions & 1 deletion addons/base_import/models/base_import.py
Expand Up @@ -622,9 +622,11 @@ def _parse_import_data_recursive(self, model, prefix, data, import_fields, optio
# versions, for both data and pattern
user_format = pycompat.to_native(options.get('%s_format' % field['type']))
for num, line in enumerate(data):
if line[index]:
line[index] = line[index].strip()
if line[index]:
try:
line[index] = dt.strftime(dt.strptime(pycompat.to_native(line[index].strip()), user_format), server_format)
line[index] = dt.strftime(dt.strptime(pycompat.to_native(line[index]), user_format), server_format)
except ValueError as e:
raise ValueError(_("Column %s contains incorrect values. Error in line %d: %s") % (name, num + 1, e))
except Exception as e:
Expand Down
2 changes: 1 addition & 1 deletion addons/hr/models/mail_alias.py
Expand Up @@ -14,7 +14,7 @@ class MailAlias(models.AbstractModel):
_inherit = 'mail.alias.mixin'

def _alias_check_contact_on_record(self, record, message, message_dict, alias):
if alias.alias_contact == 'employees' and record.ids:
if alias.alias_contact == 'employees':
email_from = tools.decode_message_header(message, 'From')
email_address = tools.email_split(email_from)[0]
employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)
Expand Down

0 comments on commit 6773bdb

Please sign in to comment.