diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 877ad3cd0d2..da1641854ac 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -244,6 +244,9 @@ class AccountMove(models.Model): date = date or fields.Date.today() reversed_moves = self.env['account.move'] for ac_move in self: + #unreconcile all lines reversed + aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile or x.account_id.internal_type == 'liquidity') + aml.remove_move_reconcile() reversed_move = ac_move._reverse_move(date=date, journal_id=journal_id) reversed_moves |= reversed_move @@ -290,7 +293,7 @@ class AccountMoveLine(models.Model): for unreconciled lines, and something in-between for partially reconciled lines. """ for line in self: - if not line.account_id.reconcile: + if not line.account_id.reconcile and line.account_id.internal_type != 'liquidity': line.reconciled = False line.amount_residual = 0 line.amount_residual_currency = 0 @@ -1760,7 +1763,6 @@ class AccountPartialReconcile(models.Model): """ When removing a partial reconciliation, also unlink its full reconciliation if it exists """ full_to_unlink = self.env['account.full.reconcile'] for rec in self: - #without the deleted partial reconciliations, the full reconciliation won't be full anymore if rec.full_reconcile_id: full_to_unlink |= rec.full_reconcile_id #reverse the tax basis move created at the reconciliation time diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index deeba898ce9..d4dfac560a5 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -811,3 +811,71 @@ class TestReconciliation(AccountingTestCase): self.assertEqual(positive_line[0]['amount'], 50.0, 'The amount of the amls should be 50') self.assertEqual(negative_line[0]['amount'], -50.0, 'The amount of the amls should be -50') + + def test_revert_payment_and_reconcile_exchange(self): + + # A reversal of a reconciled payment which created a currency exchange entry, should create reversal moves + # which move lines should be reconciled two by two with the original move's lines + + def _determine_debit_credit_line(move): + line_ids_reconciliable = move.line_ids.filtered(lambda l: l.account_id.reconcile or l.account_id.internal_type == 'liquidity') + return line_ids_reconciliable.filtered(lambda l: l.debit), line_ids_reconciliable.filtered(lambda l: l.credit) + + def _move_revert_test_pair(move, revert): + self.assertTrue(move.line_ids) + self.assertTrue(revert.line_ids) + + move_lines = _determine_debit_credit_line(move) + revert_lines = _determine_debit_credit_line(revert) + + # in the case of the exchange entry, only one pair of lines will be found + if move_lines[0] and revert_lines[1]: + self.assertTrue(move_lines[0].full_reconcile_id.exists()) + self.assertEqual(move_lines[0].full_reconcile_id.id, revert_lines[1].full_reconcile_id.id) + + if move_lines[1] and revert_lines[0]: + self.assertTrue(move_lines[1].full_reconcile_id.exists()) + self.assertEqual(move_lines[1].full_reconcile_id.id, revert_lines[0].full_reconcile_id.id) + + self.env['res.currency.rate'].create({ + 'name': time.strftime('%Y') + '-07-01', + 'rate': 1.0, + 'currency_id': self.currency_usd_id, + 'company_id': self.env.ref('base.main_company').id + }) + self.env['res.currency.rate'].create({ + 'name': time.strftime('%Y') + '-08-01', + 'rate': 0.5, + 'currency_id': self.currency_usd_id, + 'company_id': self.env.ref('base.main_company').id + }) + inv = self.create_invoice(invoice_amount=111, 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': 111, + 'currency_id': self.currency_usd_id, + 'journal_id': self.bank_journal_usd.id, + 'payment_date': time.strftime('%Y') + '-08-01', + }) + payment.post() + + credit_aml = payment.move_line_ids.filtered('credit') + inv.assign_outstanding_credit(credit_aml.id) + self.assertTrue(inv.state == 'paid', 'The invoice should be paid') + + exchange_reconcile = payment.move_line_ids.mapped('full_reconcile_id') + exchange_move = exchange_reconcile.exchange_move_id + payment_move = payment.move_line_ids[0].move_id + + reverted_payment_move = self.env['account.move'].browse(payment_move.reverse_moves(time.strftime('%Y') + '-08-01')) + + # After reversal of payment, the invoice should be open + self.assertTrue(inv.state == 'open', 'The invoice should be open again') + self.assertFalse(exchange_reconcile.exists()) + + reverted_exchange_move = self.env['account.move'].search([('journal_id', '=', exchange_move.journal_id.id), ('ref', 'ilike', exchange_move.name)], limit=1) + _move_revert_test_pair(payment_move, reverted_payment_move) + _move_revert_test_pair(exchange_move, reverted_exchange_move) diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index a5a888f51a2..0e8270d7eed 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -131,6 +131,8 @@ class OAuthController(http.Controller): def signin(self, **kw): state = json.loads(kw['state']) dbname = state['d'] + if not http.db_filter([dbname]): + return BadRequest() provider = state['p'] context = state.get('c', {}) registry = registry_get(dbname) @@ -180,6 +182,8 @@ class OAuthController(http.Controller): dbname = db_monodb() if not dbname: return BadRequest() + if not http.db_filter([dbname]): + return BadRequest() registry = registry_get(dbname) with registry.cursor() as cr: diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index a8b51aec442..bdf4c2dd329 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -872,6 +872,14 @@ class Meeting(models.Model): self.start = self.start_datetime self.stop = fields.Datetime.to_string(start + timedelta(hours=self.duration)) + @api.onchange('start_date') + def _onchange_start_date(self): + self.start = self.start_date + + @api.onchange('stop_date') + def _onchange_stop_date(self): + self.stop = self.stop_date + #################################################### # Calendar Business, Reccurency, ... #################################################### diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 7cbef6c99dc..8b938738e85 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -221,7 +221,7 @@ exports.PosModel = Backbone.Model.extend({ fields: ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at','sequence_number','login_number'], domain: function(self){ return [['state','=','opened'],['user_id','=',session.uid]]; }, loaded: function(self,pos_sessions){ - self.pos_session = pos_sessions[0]; + self.pos_session = pos_sessions[0]; }, },{ model: 'pos.config', @@ -241,6 +241,8 @@ exports.PosModel = Backbone.Model.extend({ self.db.set_uuid(self.config.uuid); self.cashier = self.get_cashier(); + // We need to do it here, since only then the local storage has the correct uuid + self.db.save('pos_session_id', self.pos_session.id); var orders = self.db.get_orders(); for (var i = 0; i < orders.length; i++) { @@ -577,6 +579,10 @@ exports.PosModel = Backbone.Model.extend({ // returns the user who is currently the cashier for this point of sale get_cashier: function(){ + // reset the cashier to the current user if session is new + if (this.db.load('pos_session_id') !== this.pos_session.id) { + this.set_cashier(this.user); + } return this.db.get_cashier() || this.cashier || this.user; }, // changes the current cashier diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py index 1544c78288c..ab39a0d2129 100644 --- a/addons/sale_stock/tests/test_sale_stock.py +++ b/addons/sale_stock/tests/test_sale_stock.py @@ -52,7 +52,7 @@ class TestSaleStock(TestSale): pick_2 = self.so.picking_ids[0] pick_2.force_assign() pick_2.pack_operation_product_ids.write({'qty_done': 1}) - self.assertTrue(pick_2.do_new_transfer(), 'Sale Stock: second picking should be final without need for a backorder') + self.assertIsNone(pick_2.do_new_transfer(), 'Sale Stock: second picking should be final without need for a backorder') self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" after complete delivery') del_qties = [sol.qty_delivered for sol in self.so.order_line] del_qties_truth = [2.0 if sol.product_id.type in ['product', 'consu'] else 0.0 for sol in self.so.order_line] @@ -100,7 +100,7 @@ class TestSaleStock(TestSale): pick = self.so.picking_ids pick.force_assign() pick.pack_operation_product_ids.write({'qty_done': 2}) - self.assertTrue(pick.do_new_transfer(), 'Sale Stock: complete delivery should not need a backorder') + self.assertIsNone(pick.do_new_transfer(), 'Sale Stock: complete delivery should not need a backorder') del_qties = [sol.qty_delivered for sol in self.so.order_line] del_qties_truth = [2.0 if sol.product_id.type in ['product', 'consu'] else 0.0 for sol in self.so.order_line] self.assertEqual(del_qties, del_qties_truth, 'Sale Stock: delivered quantities are wrong after partial delivery') diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index 2661915b303..0fae5076d7c 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -842,7 +842,7 @@ class Picking(models.Model): if pack_operations_delete: pack_operations_delete.unlink() self.do_transfer() - return True + return def check_backorder(self): need_rereserve, all_op_processed = self.picking_recompute_remaining_quantities(done_qtys=True) diff --git a/addons/web/models/ir_http.py b/addons/web/models/ir_http.py index a1fa58ef81d..8d73db777ec 100644 --- a/addons/web/models/ir_http.py +++ b/addons/web/models/ir_http.py @@ -26,7 +26,7 @@ class Http(models.AbstractModel): "session_id": request.session.sid, "uid": request.session.uid, "is_system": request.env.user._is_system(), - "is_superuser": request.env.user._is_superuser(), + "is_superuser": request.env.user._is_superuser() if request.session.uid else False, "user_context": request.session.get_context() if request.session.uid else {}, "db": request.session.db, "server_version": version_info.get('server_version'), @@ -36,7 +36,7 @@ class Http(models.AbstractModel): "company_id": request.env.user.company_id.id if request.session.uid else None, "partner_id": request.env.user.partner_id.id if request.session.uid and request.env.user.partner_id else None, "user_companies": {'current_company': (user.company_id.id, user.company_id.name), 'allowed_companies': [(comp.id, comp.name) for comp in user.company_ids]} if display_switch_company_menu else False, - "currencies": self.get_currencies(), + "currencies": self.get_currencies() if request.session.uid else {}, } def get_currencies(self): diff --git a/odoo/addons/base/ir/ir_autovacuum.py b/odoo/addons/base/ir/ir_autovacuum.py index a85f82af6a9..67880e72e23 100644 --- a/odoo/addons/base/ir/ir_autovacuum.py +++ b/odoo/addons/base/ir/ir_autovacuum.py @@ -4,6 +4,7 @@ import logging from odoo import api, models +from odoo.exceptions import AccessDenied _logger = logging.getLogger(__name__) @@ -36,6 +37,8 @@ class AutoVacuum(models.AbstractModel): @api.model def power_on(self): + if not self.env.user._is_admin(): + raise AccessDenied() self.env['ir.attachment']._file_gc() self._gc_transient_models() self._gc_user_logs()