[MERGE] forward port branch 11.0 up to bab5ccfdd7
This commit is contained in:
@@ -267,7 +267,7 @@ class AccountMove(models.Model):
|
||||
aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile or x.account_id.internal_type == 'liquidity')
|
||||
aml.remove_move_reconcile()
|
||||
#reconcile together the reconcilable (or the liquidity aml) and their newly created counterpart
|
||||
for account in list(set([x.account_id for x in aml])):
|
||||
for account in set([x.account_id for x in aml]):
|
||||
to_rec = aml.filtered(lambda y: y.account_id == account)
|
||||
to_rec |= reversed_move.line_ids.filtered(lambda y: y.account_id == account)
|
||||
#reconciliation will be full, so speed up the computation by using skip_full_reconcile_check in the context
|
||||
@@ -952,6 +952,8 @@ class AccountMoveLine(models.Model):
|
||||
ctx = {}
|
||||
if 'date' in vals:
|
||||
ctx['date'] = vals['date']
|
||||
elif 'date_maturity' in vals:
|
||||
ctx['date'] = vals['date_maturity']
|
||||
temp['currency_id'] = bank.currency_id.id
|
||||
temp['amount_currency'] = bank.company_id.currency_id.with_context(ctx).compute(tax_vals['amount'], bank.currency_id, round=True)
|
||||
if vals.get('tax_exigible'):
|
||||
|
||||
@@ -90,6 +90,7 @@ class HrEmployee(models.Model):
|
||||
action_message = self.env.ref('hr_attendance.hr_attendance_action_greeting_message').read()[0]
|
||||
action_message['previous_attendance_change_date'] = self.last_attendance_id and (self.last_attendance_id.check_out or self.last_attendance_id.check_in) or False
|
||||
action_message['employee_name'] = self.name
|
||||
action_message['barcode'] = self.barcode
|
||||
action_message['next_action'] = next_action
|
||||
|
||||
if self.user_id:
|
||||
|
||||
@@ -51,6 +51,7 @@ var GreetingMessage = AbstractAction.extend({
|
||||
this.attendance.check_in_time = this.attendance.check_in && this.attendance.check_in.format(this.format_time);
|
||||
this.attendance.check_out_time = this.attendance.check_out && this.attendance.check_out.format(this.format_time);
|
||||
this.employee_name = action.employee_name;
|
||||
this.attendanceBarcode = action.barcode;
|
||||
},
|
||||
|
||||
start: function() {
|
||||
@@ -136,21 +137,27 @@ var GreetingMessage = AbstractAction.extend({
|
||||
|
||||
_onBarcodeScanned: function(barcode) {
|
||||
var self = this;
|
||||
if (this.return_to_main_menu) { // in case of multiple scans in the greeting message view, delete the timer, a new one will be created.
|
||||
clearTimeout(this.return_to_main_menu);
|
||||
if (this.attendanceBarcode !== barcode){
|
||||
if (this.return_to_main_menu) { // in case of multiple scans in the greeting message view, delete the timer, a new one will be created.
|
||||
clearTimeout(this.return_to_main_menu);
|
||||
}
|
||||
core.bus.off('barcode_scanned', this, this._onBarcodeScanned);
|
||||
this._rpc({
|
||||
model: 'hr.employee',
|
||||
method: 'attendance_scan',
|
||||
args: [barcode, ],
|
||||
})
|
||||
.then(function (result) {
|
||||
if (result.action) {
|
||||
self.do_action(result.action);
|
||||
} else if (result.warning) {
|
||||
self.do_warn(result.warning);
|
||||
setTimeout( function() { self.do_action(self.next_action, {clear_breadcrumbs: true}); }, 5000);
|
||||
}
|
||||
}, function () {
|
||||
setTimeout( function() { self.do_action(self.next_action, {clear_breadcrumbs: true}); }, 5000);
|
||||
});
|
||||
}
|
||||
this._rpc({
|
||||
model: 'hr.employee',
|
||||
method: 'attendance_scan',
|
||||
args: [barcode, ],
|
||||
})
|
||||
.then(function (result) {
|
||||
if (result.action) {
|
||||
self.do_action(result.action);
|
||||
} else if (result.warning) {
|
||||
self.do_warn(result.warning);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
|
||||
@@ -36,6 +36,7 @@ var KioskMode = AbstractAction.extend({
|
||||
|
||||
_onBarcodeScanned: function(barcode) {
|
||||
var self = this;
|
||||
core.bus.off('barcode_scanned', this, this._onBarcodeScanned);
|
||||
this._rpc({
|
||||
model: 'hr.employee',
|
||||
method: 'attendance_scan',
|
||||
@@ -46,7 +47,10 @@ var KioskMode = AbstractAction.extend({
|
||||
self.do_action(result.action);
|
||||
} else if (result.warning) {
|
||||
self.do_warn(result.warning);
|
||||
core.bus.on('barcode_scanned', self, self._onBarcodeScanned);
|
||||
}
|
||||
}, function () {
|
||||
core.bus.on('barcode_scanned', self, self._onBarcodeScanned);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@ odoo.define('hr_attendance.tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
var core = require('web.core');
|
||||
|
||||
var MyAttendances = require('hr_attendance.my_attendances');
|
||||
var KioskMode = require('hr_attendance.kiosk_mode');
|
||||
var GreetingMessage = require('hr_attendance.greeting_message');
|
||||
|
||||
|
||||
QUnit.module('HR Attendance', {
|
||||
@@ -19,12 +22,30 @@ QUnit.module('HR Attendance', {
|
||||
default: 1,
|
||||
},
|
||||
user_id: {string: 'user ID', type: 'integer'},
|
||||
barcode: {string:'barcode', type: 'integer'},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
name: "Employee A",
|
||||
attendance_state: 1,
|
||||
user_id: 1,
|
||||
barcode: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Employee B",
|
||||
attendance_state: 'checked_out',
|
||||
user_id: 2,
|
||||
barcode: 2,
|
||||
}],
|
||||
},
|
||||
'res.company': {
|
||||
fields: {
|
||||
name: {string: 'Name', type: 'char'},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
name: "Company A",
|
||||
}],
|
||||
},
|
||||
};
|
||||
@@ -51,6 +72,112 @@ QUnit.module('HR Attendance', {
|
||||
clientAction.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Attendance Kiosk Mode Test', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var $target = $('#qunit-fixture');
|
||||
var self = this;
|
||||
var rpcCount = 0;
|
||||
var clientAction = new KioskMode(null);
|
||||
testUtils.addMockEnvironment(clientAction, {
|
||||
data: this.data,
|
||||
session: {
|
||||
uid: 1,
|
||||
company_id: 1,
|
||||
},
|
||||
mockRPC: function(route, args) {
|
||||
if (args.method === 'attendance_scan' && args.model === 'hr.employee') {
|
||||
|
||||
rpcCount++;
|
||||
return $.when(self.data['hr.employee'].records[0]);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
clientAction.appendTo($target);
|
||||
core.bus.trigger('barcode_scanned', 1);
|
||||
core.bus.trigger('barcode_scanned', 1);
|
||||
assert.strictEqual(rpcCount, 1, 'RPC call should have been done only once.');
|
||||
|
||||
core.bus.trigger('barcode_scanned', 2);
|
||||
assert.strictEqual(rpcCount, 1, 'RPC call should have been done only once.');
|
||||
|
||||
clientAction.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Attendance Greeting Message Test', function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
var $target = $('#qunit-fixture');
|
||||
var self = this;
|
||||
var rpcCount = 0;
|
||||
|
||||
var clientActions = [];
|
||||
function createGreetingMessage (target, barcode){
|
||||
var action = {
|
||||
attendance: {
|
||||
check_in: "2018-09-20 13:41:13",
|
||||
employee_id: [barcode],
|
||||
},
|
||||
next_action: "hr_attendance.hr_attendance_action_kiosk_mode",
|
||||
barcode: barcode,
|
||||
}
|
||||
var clientAction = new GreetingMessage(null, action);
|
||||
testUtils.addMockEnvironment(clientAction, {
|
||||
data: self.data,
|
||||
session: {
|
||||
uid: 1,
|
||||
company_id: 1,
|
||||
},
|
||||
mockRPC: function(route, args) {
|
||||
if (args.method === 'attendance_scan' && args.model === 'hr.employee') {
|
||||
rpcCount++;
|
||||
action.attendance.employee_id = [args.args[0], 'Employee'];
|
||||
/*
|
||||
if rpc have been made, a new instance is created to simulate the same behaviour
|
||||
as functional flow.
|
||||
*/
|
||||
createGreetingMessage (target, args.args[0]);
|
||||
return $.when({action: action});
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
clientAction.appendTo(target);
|
||||
|
||||
clientActions.push(clientAction);
|
||||
};
|
||||
|
||||
// init - mock coming from kiosk
|
||||
createGreetingMessage ($target, 1);
|
||||
assert.strictEqual(clientActions.length, 1, 'Number of clientAction must = 1.');
|
||||
|
||||
core.bus.trigger('barcode_scanned', 1);
|
||||
/*
|
||||
As action is given when instantiate GreetingMessage, we simulate that we come from the KioskMode
|
||||
So rescanning the same barcode won't lead to another RPC.
|
||||
*/
|
||||
assert.strictEqual(clientActions.length, 1, 'Number of clientActions must = 1.');
|
||||
assert.strictEqual(rpcCount, 0, 'RPC call should not have been done.');
|
||||
|
||||
core.bus.trigger('barcode_scanned', 2);
|
||||
assert.strictEqual(clientActions.length, 2, 'Number of clientActions must = 2.');
|
||||
assert.strictEqual(rpcCount, 1, 'RPC call should have been done only once.');
|
||||
core.bus.trigger('barcode_scanned', 2);
|
||||
assert.strictEqual(clientActions.length, 2, 'Number of clientActions must = 2.');
|
||||
assert.strictEqual(rpcCount, 1, 'RPC call should have been done only once.');
|
||||
|
||||
core.bus.trigger('barcode_scanned', 1);
|
||||
assert.strictEqual(clientActions.length, 3, 'Number of clientActions must = 3.');
|
||||
core.bus.trigger('barcode_scanned', 1);
|
||||
assert.strictEqual(clientActions.length, 3, 'Number of clientActions must = 3.');
|
||||
assert.strictEqual(rpcCount, 2, 'RPC call should have been done only twice.');
|
||||
|
||||
_.each(clientActions, function(clientAction) {
|
||||
clientAction.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.osv import expression
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons import decimal_precision as dp
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
@@ -108,6 +109,7 @@ class ProductAttributeLine(models.Model):
|
||||
# search on a m2o and one on a m2m, probably this will quickly become
|
||||
# difficult to compute - check if performance optimization is required
|
||||
if name and operator in ('=', 'ilike', '=ilike', 'like', '=like'):
|
||||
args = expression.AND([['|', ('attribute_id', operator, name), ('value_ids', operator, name)], args])
|
||||
return self.search(args, limit=limit).name_get()
|
||||
args = args or []
|
||||
domain = ['|', ('attribute_id', operator, name), ('value_ids', operator, name)]
|
||||
return self.search(expression.AND([domain, args]), limit=limit).name_get()
|
||||
return super(ProductAttributeLine, self).name_search(name=name, args=args, operator=operator, limit=limit)
|
||||
|
||||
@@ -310,7 +310,7 @@ class PurchaseOrder(models.Model):
|
||||
|
||||
self.partner_id = partner.id
|
||||
self.fiscal_position_id = fpos.id
|
||||
self.payment_term_id = payment_term.id,
|
||||
self.payment_term_id = payment_term.id
|
||||
self.company_id = requisition.company_id.id
|
||||
self.currency_id = requisition.currency_id.id
|
||||
if not self.origin or requisition.name not in self.origin.split(', '):
|
||||
|
||||
@@ -356,6 +356,9 @@ class WebsiteSale(http.Controller):
|
||||
revive: Revival method when abandoned cart. Can be 'merge' or 'squash'
|
||||
"""
|
||||
order = request.website.sale_get_order()
|
||||
if order and order.state != 'draft':
|
||||
request.session['sale_order_id'] = None
|
||||
order = request.website.sale_get_order()
|
||||
values = {}
|
||||
if access_token:
|
||||
abandoned_order = request.env['sale.order'].sudo().search([('access_token', '=', access_token)], limit=1)
|
||||
@@ -398,7 +401,7 @@ class WebsiteSale(http.Controller):
|
||||
|
||||
@http.route(['/shop/cart/update'], type='http', auth="public", methods=['POST'], website=True, csrf=False)
|
||||
def cart_update(self, product_id, add_qty=1, set_qty=0, **kw):
|
||||
sale_order = request.website.sale_get_order(force_create=1)
|
||||
sale_order = request.website.sale_get_order(force_create=True)
|
||||
if sale_order.state != 'draft':
|
||||
request.session['sale_order_id'] = None
|
||||
sale_order = request.website.sale_get_order(force_create=True)
|
||||
|
||||
@@ -58,9 +58,14 @@ class WebsiteSaleDelivery(WebsiteSale):
|
||||
def _update_website_sale_delivery(self, **post):
|
||||
order = request.website.sale_get_order()
|
||||
carrier_id = int(post['carrier_id'])
|
||||
currency = order.currency_id
|
||||
if order:
|
||||
order._check_carrier_quotation(force_carrier_id=carrier_id)
|
||||
return self._update_website_sale_delivery_return(order, **post)
|
||||
|
||||
def _update_website_sale_delivery_return(self, order, **post):
|
||||
carrier_id = int(post['carrier_id'])
|
||||
currency = order.currency_id
|
||||
if order:
|
||||
return {'status': order.delivery_rating_success,
|
||||
'error_message': order.delivery_message,
|
||||
'carrier_id': carrier_id,
|
||||
|
||||
@@ -19,7 +19,10 @@ class WebsiteSaleOptions(WebsiteSale):
|
||||
if lang:
|
||||
request.website = request.website.with_context(lang=lang)
|
||||
|
||||
order = request.website.sale_get_order(force_create=1)
|
||||
order = request.website.sale_get_order(force_create=True)
|
||||
if order.state != 'draft':
|
||||
request.session['sale_order_id'] = None
|
||||
order = request.website.sale_get_order(force_create=True)
|
||||
product = request.env['product.product'].browse(int(product_id))
|
||||
|
||||
option_ids = product.optional_product_ids.mapped('product_variant_ids').ids
|
||||
|
||||
+17
-12
@@ -59,6 +59,14 @@ def memory_info(process):
|
||||
pmem = (getattr(process, 'memory_info', None) or process.get_memory_info)()
|
||||
return (pmem.rss, pmem.vms)
|
||||
|
||||
def empty_pipe(fd):
|
||||
try:
|
||||
while os.read(fd, 1):
|
||||
pass
|
||||
except OSError as e:
|
||||
if e.errno not in [errno.EAGAIN]:
|
||||
raise
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Werkzeug WSGI servers patched
|
||||
#----------------------------------------------------------
|
||||
@@ -562,13 +570,7 @@ class PreforkServer(CommonServer):
|
||||
for fd in ready[0]:
|
||||
if fd in fds:
|
||||
fds[fd].watchdog_time = time.time()
|
||||
try:
|
||||
# empty pipe
|
||||
while os.read(fd, 1):
|
||||
pass
|
||||
except OSError as e:
|
||||
if e.errno not in [errno.EAGAIN]:
|
||||
raise
|
||||
empty_pipe(fd)
|
||||
except select.error as e:
|
||||
if e.args[0] not in [errno.EINTR]:
|
||||
raise
|
||||
@@ -659,6 +661,7 @@ class Worker(object):
|
||||
self.watchdog_time = time.time()
|
||||
self.watchdog_pipe = multi.pipe_new()
|
||||
self.eintr_pipe = multi.pipe_new()
|
||||
self.wakeup_fd_r, self.wakeup_fd_w = self.eintr_pipe
|
||||
# Can be set to None if no watchdog is desired.
|
||||
self.watchdog_timeout = multi.timeout
|
||||
self.ppid = os.getpid()
|
||||
@@ -682,8 +685,9 @@ class Worker(object):
|
||||
|
||||
def sleep(self):
|
||||
try:
|
||||
wakeup_fd = self.eintr_pipe[0]
|
||||
select.select([self.multi.socket, wakeup_fd], [], [], self.multi.beat)
|
||||
select.select([self.multi.socket, self.wakeup_fd_r], [], [], self.multi.beat)
|
||||
# clear wakeup pipe if we were interrupted
|
||||
empty_pipe(self.wakeup_fd_r)
|
||||
except select.error as e:
|
||||
if e.args[0] not in [errno.EINTR]:
|
||||
raise
|
||||
@@ -737,7 +741,7 @@ class Worker(object):
|
||||
signal.signal(signal.SIGINT, self.signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
||||
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
||||
signal.set_wakeup_fd(self.eintr_pipe[1])
|
||||
signal.set_wakeup_fd(self.wakeup_fd_w)
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
@@ -809,8 +813,9 @@ class WorkerCron(Worker):
|
||||
|
||||
# simulate interruptible sleep with select(wakeup_fd, timeout)
|
||||
try:
|
||||
wakeup_fd = self.eintr_pipe[0]
|
||||
select.select([wakeup_fd], [], [], interval)
|
||||
select.select([self.wakeup_fd_r], [], [], interval)
|
||||
# clear wakeup pipe if we were interrupted
|
||||
empty_pipe(self.wakeup_fd_r)
|
||||
except select.error as e:
|
||||
if e.args[0] != errno.EINTR:
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user