diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 3e25df9127..6a61c52c39 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -293,7 +293,7 @@ class AccountMove(models.Model):
invoice_incoterm_id = fields.Many2one('account.incoterms', string='Incoterm',
default=_get_default_invoice_incoterm,
help='International Commercial Terms are a series of predefined commercial terms used in international transactions.')
- display_qr_code = fields.Boolean(string="Display QR-code", related='company_id.qr_code')
+ display_qr_code = fields.Boolean(string="Display QR-code", compute='_compute_display_qr_code')
qr_code_method = fields.Selection(string="Payment QR-code", copy=False,
selection=lambda self: self.env['res.partner.bank'].get_available_qr_methods_in_sequence(),
help="Type of QR-code to be generated for the payment of this invoice, when printing it. If left blank, the first available and usable method will be used.")
@@ -2999,6 +2999,14 @@ class AccountMove(models.Model):
for move in self:
move.has_reconciled_entries = len(move.line_ids._reconciled_lines()) > 1
+ @api.depends('company_id')
+ def _compute_display_qr_code(self):
+ for record in self:
+ record.display_qr_code = (
+ record.move_type in ('out_invoice', 'out_receipt')
+ and record.company_id.qr_code
+ )
+
def action_view_reverse_entry(self):
self.ensure_one()
@@ -3074,8 +3082,8 @@ class AccountMove(models.Model):
"""
self.ensure_one()
- if not self.is_invoice():
- raise UserError(_("QR-codes can only be generated for invoice entries."))
+ if not self.display_qr_code:
+ return None
qr_code_method = self.qr_code_method
if qr_code_method:
diff --git a/addons/account/views/account_move_views.xml b/addons/account/views/account_move_views.xml
index 1b4725c6bb..80f9903bf0 100644
--- a/addons/account/views/account_move_views.xml
+++ b/addons/account/views/account_move_views.xml
@@ -996,7 +996,7 @@
sum="Total Credit"
attrs="{'invisible': [('display_type', 'in', ('line_section', 'line_note'))]}"/>
-
+
Calendar Event: Reminder
- Event reminder: ${object.name}, ${object.display_time}
+ Event reminder: ${object.name}, ${object.get_display_time_tz(object.partner_id.tz)}
diff --git a/addons/l10n_it_edi/data/invoice_it_template.xml b/addons/l10n_it_edi/data/invoice_it_template.xml
index 3ad3a64357..d98c49be93 100644
--- a/addons/l10n_it_edi/data/invoice_it_template.xml
+++ b/addons/l10n_it_edi/data/invoice_it_template.xml
@@ -166,7 +166,7 @@
-
+
diff --git a/addons/l10n_it_edi/views/l10n_it_view.xml b/addons/l10n_it_edi/views/l10n_it_view.xml
index 915a13d349..6975712e21 100644
--- a/addons/l10n_it_edi/views/l10n_it_view.xml
+++ b/addons/l10n_it_edi/views/l10n_it_view.xml
@@ -26,7 +26,7 @@
-
+
diff --git a/addons/l10n_it_edi_sdicoop/tests/expected_xmls/reverse_charge_bill.xml b/addons/l10n_it_edi_sdicoop/tests/expected_xmls/reverse_charge_bill.xml
index 9b5fc4e1c2..07d142e03c 100644
--- a/addons/l10n_it_edi_sdicoop/tests/expected_xmls/reverse_charge_bill.xml
+++ b/addons/l10n_it_edi_sdicoop/tests/expected_xmls/reverse_charge_bill.xml
@@ -20,7 +20,7 @@
Alessi
- RF01
+ RF18
Avenue Test rue
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index 14060491eb..60a0537b72 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -435,7 +435,7 @@ class StockMove(models.Model):
def _update_quantity_done(self, mo):
self.ensure_one()
- new_qty = mo.product_uom_id._compute_quantity((mo.qty_producing - mo.qty_produced) * self.unit_factor, mo.product_uom_id, rounding_method='HALF-UP')
+ new_qty = float_round((mo.qty_producing - mo.qty_produced) * self.unit_factor, precision_rounding=self.product_uom.rounding)
if not self.is_quantity_done_editable:
self.move_line_ids.filtered(lambda ml: ml.state not in ('done', 'cancel')).qty_done = 0
self.move_line_ids = self._set_quantity_done_prepare_vals(new_qty)
diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py
index e83cbacadb..4c35df07a9 100644
--- a/addons/mrp/tests/test_order.py
+++ b/addons/mrp/tests/test_order.py
@@ -2395,3 +2395,27 @@ class TestMrpOrder(TestMrpCommon):
('raw_material_production_id', '=', mo_backorder.id)])
self.assertEqual(sum(move_prod_1_bo.mapped('product_uom_qty')), 60.0)
self.assertEqual(sum(move_prod_2_bo.mapped('product_uom_qty')), 40.0)
+
+ def test_update_qty_to_consume_of_component(self):
+ """
+ The UoM of the finished product has a rounding precision equal to 1.0
+ and the UoM of the component has a decimal one. When the producing qty
+ is set, an onchange autocomplete the consumed quantity of the component.
+ Then, when updating the 'to consume' quantity of the components, their
+ consumed quantity is updated again. The test ensures that this update
+ respects the rounding precisions
+ """
+ self.uom_dozen.rounding = 1
+ self.bom_4.product_uom_id = self.uom_dozen
+
+ mo_form = Form(self.env['mrp.production'])
+ mo_form.bom_id = self.bom_4
+ mo = mo_form.save()
+ mo.action_confirm()
+
+ with Form(mo) as mo_form:
+ mo_form.qty_producing = 1
+ with mo_form.move_raw_ids.edit(0) as raw:
+ raw.product_uom_qty = 1.25
+
+ self.assertEqual(mo.move_raw_ids.quantity_done, 1.25)
diff --git a/addons/sale_coupon/models/sale_order.py b/addons/sale_coupon/models/sale_order.py
index 9c75c992ad..56dc3043ec 100644
--- a/addons/sale_coupon/models/sale_order.py
+++ b/addons/sale_coupon/models/sale_order.py
@@ -393,6 +393,10 @@ class SaleOrder(models.Model):
def update_line(order, lines, values):
'''Update the lines and return them if they should be deleted'''
lines_to_remove = self.env['sale.order.line']
+ # move global coupons to the end of the SO
+ if program.discount_apply_on == 'on_order':
+ values['sequence'] = max(order.order_line.mapped('sequence')) + 1
+
# Check commit 6bb42904a03 for next if/else
# Remove reward line if price or qty equal to 0
if values['product_uom_qty'] and values['price_unit']:
diff --git a/addons/sale_coupon/tests/test_sale_invoicing.py b/addons/sale_coupon/tests/test_sale_invoicing.py
index 2d267361c2..1adc8bead0 100644
--- a/addons/sale_coupon/tests/test_sale_invoicing.py
+++ b/addons/sale_coupon/tests/test_sale_invoicing.py
@@ -52,3 +52,38 @@ class TestSaleInvoicing(TestSaleCouponCommon):
self.assertEqual(order.order_line, invoiceable_lines)
account_move = order._create_invoices()
self.assertEqual(len(account_move.invoice_line_ids), 2)
+
+ def test_coupon_on_order_sequence(self):
+ # discount_coupon_program
+ self.env['coupon.program'].create({
+ 'name': '10% Discount', # Default behavior
+ 'program_type': 'coupon_program',
+ 'reward_type': 'discount',
+ 'discount_apply_on': 'on_order',
+ 'promo_code_usage': 'no_code_needed',
+ })
+ order = self.empty_order
+
+ # orderline1
+ self.env['sale.order.line'].create({
+ 'product_id': self.env.ref('product.product_product_6').id,
+ 'name': 'largeCabinet',
+ 'product_uom_qty': 1.0,
+ 'order_id': order.id,
+ })
+
+ order.recompute_coupon_lines()
+ self.assertEqual(len(order.order_line), 2, 'Coupon correctly applied')
+
+ # orderline2
+ self.env['sale.order.line'].create({
+ 'product_id': self.env.ref('product.product_product_11').id,
+ 'name': 'conferenceChair',
+ 'product_uom_qty': 1.0,
+ 'order_id': order.id,
+ })
+
+ order.recompute_coupon_lines()
+ self.assertEqual(len(order.order_line), 3, 'Coupon correctly applied')
+
+ self.assertTrue(order.order_line.sorted(lambda x: x.sequence)[-1].is_reward_line, 'Global coupons appear on the last line')
diff --git a/addons/snailmail/models/snailmail_letter.py b/addons/snailmail/models/snailmail_letter.py
index bdc47a3bbc..cbea84581f 100644
--- a/addons/snailmail/models/snailmail_letter.py
+++ b/addons/snailmail/models/snailmail_letter.py
@@ -13,7 +13,7 @@ from reportlab.pdfgen.canvas import Canvas
from flectra import fields, models, api, _
from flectra.addons.iap.tools import iap_tools
-from flectra.exceptions import AccessError
+from flectra.exceptions import AccessError, UserError
from flectra.tools.safe_eval import safe_eval
DEFAULT_ENDPOINT = 'https://iap-snailmail.flectrahq.com'
@@ -140,7 +140,10 @@ class SnailmailLetter(models.Model):
else:
report_name = 'Document'
filename = "%s.%s" % (report_name, "pdf")
- pdf_bin, _ = report.with_context(snailmail_layout=not self.cover)._render_qweb_pdf(self.res_id)
+ paperformat = report.get_paperformat()
+ if (paperformat.format == 'custom' and paperformat.page_width != 210 and paperformat.page_height != 297) or paperformat.format != 'A4':
+ raise UserError(_("Please use an A4 Paper format."))
+ pdf_bin, unused_filetype = report.with_context(snailmail_layout=not self.cover, lang='en_US').render_qweb_pdf(self.res_id)
if self.cover:
pdf_bin = self._append_cover_page(pdf_bin)
attachment = self.env['ir.attachment'].create({
@@ -426,7 +429,7 @@ class SnailmailLetter(models.Model):
return all(record[key] for key in required_keys)
def _append_cover_page(self, invoice_bin: bytes):
- address = self.partner_id.with_context(show_address=True)._get_name().replace('\n', ' ')
+ address = self.partner_id.with_context(show_address=True, lang='en_US')._get_name().replace('\n', ' ')
address_x = 118 * mm
address_y = 60 * mm
frame_width = 85.5 * mm
diff --git a/addons/stock_account/models/stock_move.py b/addons/stock_account/models/stock_move.py
index d21b031128..b4d8c9b77e 100644
--- a/addons/stock_account/models/stock_move.py
+++ b/addons/stock_account/models/stock_move.py
@@ -272,11 +272,11 @@ class StockMove(models.Model):
for svl in stock_valuation_layers:
- if not svl.product_id.valuation == 'real_time':
+ if not svl.with_company(svl.company_id).product_id.valuation == 'real_time':
continue
if svl.currency_id.is_zero(svl.value):
continue
- svl.stock_move_id._account_entry_move(svl.quantity, svl.description, svl.id, svl.value)
+ svl.stock_move_id.with_company(svl.company_id)._account_entry_move(svl.quantity, svl.description, svl.id, svl.value)
stock_valuation_layers._check_company()
diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js
index b3ff69756d..bee245e9e8 100644
--- a/addons/web/static/src/js/core/ajax.js
+++ b/addons/web/static/src/js/core/ajax.js
@@ -97,11 +97,22 @@ function _genericJsonRpc (fct_name, params, settings, fct) {
});
// FIXME: jsonp?
- promise.abort = function () {
- rejection({
- message: "XmlHttpRequestError abort",
- event: $.Event('abort')
- });
+ /**
+ * @param {Boolean} rejectError Returns an error if true. Allows you to cancel
+ * ignored rpc's in order to unblock the ui and not display an error.
+ */
+ promise.abort = function (rejectError = true) {
+ if (rejectError) {
+ rejection({
+ message: "XmlHttpRequestError abort",
+ event: $.Event('abort')
+ });
+ }
+
+ if (!shadow) {
+ core.bus.trigger('rpc_response');
+ }
+
if (xhr.abort) {
xhr.abort();
}
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 3ef431e0ac..60dbfe517b 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -576,7 +576,10 @@ var FieldMany2One = AbstractField.extend({
domain.push(['id', 'not in', blackListedIds]);
}
- const nameSearch = this._rpc({
+ if (this.lastNameSearch) {
+ this.lastNameSearch.abort(false)
+ }
+ this.lastNameSearch = this._rpc({
model: this.field.relation,
method: "name_search",
kwargs: {
@@ -587,7 +590,7 @@ var FieldMany2One = AbstractField.extend({
context,
}
});
- const results = await this.orderer.add(nameSearch);
+ const results = await this.orderer.add(this.lastNameSearch);
// Format results to fit the options dropdown
let values = results.map((result) => {
diff --git a/addons/web/static/src/scss/daterangepicker.scss b/addons/web/static/src/scss/daterangepicker.scss
index aece2a9903..d6c28ecea1 100644
--- a/addons/web/static/src/scss/daterangepicker.scss
+++ b/addons/web/static/src/scss/daterangepicker.scss
@@ -51,6 +51,8 @@
}
}
.calendar-time {
+ /*rtl:ignore*/
+ direction:ltr;
select {
&.hourselect, &.minuteselect, &.secondselect, &.ampmselect {
display: initial;
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index c9f56b6b28..ced144a7d1 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -151,8 +151,10 @@ var MockServer = Class.extend({
if (abort) {
abort = abort.bind(def);
} else {
- abort = function () {
- throw new Error("Can't abort this request");
+ abort = function (rejectError = true) {
+ if (rejectError) {
+ throw new ConnectionAbortedError("XmlHttpRequestError abort");
+ }
};
}