From 195bd0a9dec040a6789007d5ac9ecff996476783 Mon Sep 17 00:00:00 2001 From: Parthiv Patel Date: Mon, 6 Jun 2022 08:34:46 +0000 Subject: [PATCH] [PATCH] Upstream patch - 06062022 --- .../account/models/account_bank_statement.py | 2 +- addons/account/models/account_move.py | 17 +++++++- .../account/models/account_reconcile_model.py | 3 +- .../tests/test_account_bank_statement.py | 26 ++++++++++++ .../tests/test_account_move_in_invoice.py | 40 ++++++++++++++++++ addons/l10n_ch/models/account_invoice.py | 8 ++++ addons/l10n_ch/models/res_bank.py | 15 +++++++ addons/l10n_ch/report/swissqr_report.xml | 25 +++++++++-- .../l10n_it_edi/models/account_edi_format.py | 9 ++-- .../models/account_edi_format.py | 3 +- .../composer_suggested_recipient.js | 7 +++- .../static/src/widgets/discuss/discuss.js | 1 + addons/mail/wizard/mail_compose_message.py | 1 + addons/mrp/models/mrp_production.py | 3 +- addons/payment_alipay/controllers/main.py | 13 +++++- addons/point_of_sale/models/pos_order.py | 2 +- addons/point_of_sale/static/src/js/models.js | 1 + addons/portal/models/mail_message.py | 2 +- addons/portal/tests/__init__.py | 1 + .../tests/test_message_format_portal.py | 41 +++++++++++++++++++ addons/product/models/product_attribute.py | 3 +- addons/product/views/product_views.xml | 5 ++- addons/stock_account/models/stock_move.py | 2 +- .../tests/test_stockvaluation.py | 31 ++++++++++++++ 24 files changed, 238 insertions(+), 23 deletions(-) create mode 100644 addons/portal/tests/test_message_format_portal.py diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 7f88a85db5..534d81b232 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -901,7 +901,7 @@ class AccountBankStatementLine(models.Model): st_line_vals_to_write = {} if 'state' in changed_fields: - if (st_line.state == 'open' and move.state != 'draft') or (st_line.state == 'posted' and move.state != 'posted'): + if (st_line.state == 'open' and move.state != 'draft') or (st_line.state in ('posted', 'confirm') and move.state != 'posted'): raise UserError(_( "You can't manually change the state of journal entry %s, as it has been created by bank " "statement %s." diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index bcb9e8c2e7..f99fcc520d 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -2,7 +2,7 @@ from flectra import api, fields, models, _ from flectra.exceptions import RedirectWarning, UserError, ValidationError, AccessError -from flectra.tools import float_compare, date_utils, email_split, email_re +from flectra.tools import float_compare, date_utils, email_split, email_re, float_is_zero from flectra.tools.misc import formatLang, format_date, get_lang from datetime import date, timedelta @@ -1086,10 +1086,23 @@ class AccountMove(models.Model): ''' for invoice in self: # Dispatch lines and pre-compute some aggregated values like taxes. + + # determine required tax_lines by finding all non-zero tax amounts + lines_with_taxes = invoice.line_ids.filtered( + lambda l: l.tax_ids and l.move_id.currency_id.compare_amounts(l.price_subtotal, l.price_total)) + + needed_taxes = lines_with_taxes.tax_ids._origin.flatten_taxes_hierarchy().filtered( + lambda tax: ( + tax.amount_type == 'fixed' and not invoice.company_id.currency_id.is_zero(tax.amount) + or not float_is_zero(tax.amount, precision_digits=4) + ) + ) + current_taxes = invoice.line_ids.tax_line_id._origin + if ( recompute_all_taxes or any(line.recompute_tax_line for line in invoice.line_ids) - or invoice.line_ids.tax_ids.flatten_taxes_hierarchy()._origin > invoice.line_ids.tax_line_id._origin + or needed_taxes > current_taxes ): invoice.line_ids.recompute_tax_line = False invoice._recompute_tax_lines() diff --git a/addons/account/models/account_reconcile_model.py b/addons/account/models/account_reconcile_model.py index 4561258a21..f2c2610249 100644 --- a/addons/account/models/account_reconcile_model.py +++ b/addons/account/models/account_reconcile_model.py @@ -350,7 +350,8 @@ class AccountReconcileModel(models.Model): if match: sign = 1 if residual_balance > 0.0 else -1 try: - extracted_balance = float(re.sub(r'\D' + self.decimal_separator, '', match.group(1)).replace(self.decimal_separator, '.')) + extracted_match_group = re.sub(r'[^\d' + self.decimal_separator + ']', '', match.group(1)) + extracted_balance = float(extracted_match_group.replace(self.decimal_separator, '.')) balance = copysign(extracted_balance * sign, residual_balance) except ValueError: balance = 0 diff --git a/addons/account/tests/test_account_bank_statement.py b/addons/account/tests/test_account_bank_statement.py index e975c6fffa..542889e939 100644 --- a/addons/account/tests/test_account_bank_statement.py +++ b/addons/account/tests/test_account_bank_statement.py @@ -1135,6 +1135,32 @@ class TestAccountBankStatementLine(TestAccountBankStatementCommon): {'debit': 0.0, 'credit': 80.0, 'amount_currency': -80.0, 'currency_id': self.currency_1.id}, ) + def test_reconciliation_move_draft(self): + ''' Ensures that the account move of a reconciled statement cannot be set to draft + ''' + statement = self.env['account.bank.statement'].create({ + 'name': 'test_statement', + 'date': '2019-01-01', + 'journal_id': self.bank_journal_1.id, + 'line_ids': [ + (0, 0, { + 'date': '2019-01-01', + 'payment_ref': 'line_1', + 'partner_id': self.partner_a.id, + 'amount': 100.0, + }), + ], + }) + + statement.button_post() + statement.line_ids[0].reconcile([{'name': 'test', 'account_id': self.company_data['default_account_revenue'].id, 'balance': -100}]) + + statement.button_validate_or_action() + account_move = statement.line_ids.move_id + + with self.assertRaises(UserError): + account_move.button_draft() + def test_reconciliation_statement_line_state(self): ''' Test the reconciliation on the bank statement line with a foreign currency on the journal: - Ensure the statement line is_reconciled field is well computed. diff --git a/addons/account/tests/test_account_move_in_invoice.py b/addons/account/tests/test_account_move_in_invoice.py index 05b2295ae1..127d6f1512 100644 --- a/addons/account/tests/test_account_move_in_invoice.py +++ b/addons/account/tests/test_account_move_in_invoice.py @@ -1984,3 +1984,43 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon): # Assert the tax line is recreated for the tax self.assertIn(tax, self.invoice.line_ids.tax_line_id) + + def test_bill_amount_should_be_editable(self): + tax0 = self.env['account.tax'].create({ + 'name': 'test_tax_0', + 'amount_type': 'percent', + 'amount': 0.0, + 'type_tax_use': 'purchase', + }) + invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) + invoice_form.partner_id = self.partner_a + # Both 15% default tax and 0% tax, will have only one tax line for the 15% + with invoice_form.invoice_line_ids.new() as line: + line.name = 'test_line_1' + line.account_id = self.company_data['default_account_expense'] + line.tax_ids.add(tax0) + line.price_unit = 200.0 + # only the default 15% tax is set + with invoice_form.invoice_line_ids.new() as line: + line.name = 'test_line_2' + line.account_id = self.company_data['default_account_expense'] + line.price_unit = 100.0 + # no tax because of zero price + with invoice_form.invoice_line_ids.new() as line: + line.name = 'test_line_3' + line.account_id = self.company_data['default_account_expense'] + line.price_unit = 0.0 + + invoice = invoice_form.save() + + # mimic the behavior editing taxes with `tax_group_widget` + with invoice_form: + with invoice_form.line_ids.edit(0) as tax_line_form: + tax_line_form.debit = 500.0 + + # make sure taxes are not recompute + self.assertRecordValues(invoice.line_ids.filtered(lambda line: line.tax_line_id), + [{ + 'debit': 500, + 'tax_line_id': self.company_data['default_tax_purchase'].id, + }]) diff --git a/addons/l10n_ch/models/account_invoice.py b/addons/l10n_ch/models/account_invoice.py index 9b12cece20..389ef4c038 100644 --- a/addons/l10n_ch/models/account_invoice.py +++ b/addons/l10n_ch/models/account_invoice.py @@ -350,3 +350,11 @@ class AccountMove(models.Model): i -= 5 return spaced_qrr_ref + + @api.model + def space_scor_reference(self, iso11649_ref): + """ Makes the provided SCOR reference human-friendly, spacing its elements + by blocks of 5 from right to left. + """ + + return ' '.join(iso11649_ref[i:i + 4] for i in range(0, len(iso11649_ref), 4)) diff --git a/addons/l10n_ch/models/res_bank.py b/addons/l10n_ch/models/res_bank.py index 94d3e9e825..56a02d1c34 100644 --- a/addons/l10n_ch/models/res_bank.py +++ b/addons/l10n_ch/models/res_bank.py @@ -2,6 +2,7 @@ # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. import re +from stdnum.util import clean from flectra import api, fields, models, _ from flectra.exceptions import ValidationError @@ -191,6 +192,9 @@ class ResPartnerBank(models.Model): # _check_for_qr_code_errors ensures we can't have a QR-IBAN without a QR-reference here reference_type = 'QRR' reference = structured_communication + elif self._is_iso11649_reference(structured_communication): + reference_type = 'SCOR' + reference = structured_communication.replace(' ', '') currency = currency or self.currency_id or self.company_id.currency_id @@ -288,6 +292,17 @@ class ResPartnerBank(models.Model): and re.match('\d+$', reference) \ and reference == mod10r(reference[:-1]) + @api.model + def _is_iso11649_reference(self, reference): + """ Checks whether the given reference is a ISO11649 (SCOR) reference. + """ + return reference \ + and len(reference) >= 5 \ + and len(reference) <= 25 \ + and reference.startswith('RF') \ + and int(''.join(str(int(x, 36)) for x in clean(reference[4:] + reference[:4], ' -.,/:').upper().strip())) % 97 == 1 + # see https://github.com/arthurdejong/python-stdnum/blob/master/stdnum/iso11649.py + def _eligible_for_qr_code(self, qr_method, debtor_partner, currency): if qr_method == 'ch_qr': diff --git a/addons/l10n_ch/report/swissqr_report.xml b/addons/l10n_ch/report/swissqr_report.xml index 15022fe9fb..1f269be2ca 100644 --- a/addons/l10n_ch/report/swissqr_report.xml +++ b/addons/l10n_ch/report/swissqr_report.xml @@ -29,6 +29,9 @@ + + +

QR-bill for invoice

@@ -58,15 +61,23 @@
- +
Reference
+
+


+ +
+
+
+
+
Payable by @@ -160,17 +171,25 @@
- +
Reference
+
+


+ +
+
+
+
+
- +
Additional information diff --git a/addons/l10n_it_edi/models/account_edi_format.py b/addons/l10n_it_edi/models/account_edi_format.py index 241395128d..089e7d2e46 100644 --- a/addons/l10n_it_edi/models/account_edi_format.py +++ b/addons/l10n_it_edi/models/account_edi_format.py @@ -442,10 +442,13 @@ class AccountEdiFormat(models.Model): if invoice_form.partner_id and invoice_form.partner_id.commercial_partner_id: bank = self.env['res.partner.bank'].search([ ('acc_number', '=', elements[0].text), - ('partner_id.id', '=', invoice_form.partner_id.commercial_partner_id.id) - ]) + ('partner_id', '=', invoice_form.partner_id.commercial_partner_id.id), + ('company_id', 'in', [invoice_form.company_id.id, False]) + ], order='company_id', limit=1) else: - bank = self.env['res.partner.bank'].search([('acc_number', '=', elements[0].text)]) + bank = self.env['res.partner.bank'].search([ + ('acc_number', '=', elements[0].text), ('company_id', 'in', [invoice_form.company_id.id, False]) + ], order='company_id', limit=1) if bank: invoice_form.partner_bank_id = bank else: diff --git a/addons/l10n_it_edi_sdicoop/models/account_edi_format.py b/addons/l10n_it_edi_sdicoop/models/account_edi_format.py index e1328347d2..3cec9c5431 100644 --- a/addons/l10n_it_edi_sdicoop/models/account_edi_format.py +++ b/addons/l10n_it_edi_sdicoop/models/account_edi_format.py @@ -51,8 +51,7 @@ class AccountEdiFormat(models.Model): # should not happen as the file has been checked by SdiCoop _logger.info('Received file badly formatted, skipping: \n %s', file) continue - - invoice = self.env['account.move'].create({'move_type': 'in_invoice'}) + invoice = self.env['account.move'].with_company(company).create({'move_type': 'in_invoice'}) attachment = self.env['ir.attachment'].create({ 'name': fattura['filename'], 'raw': file, diff --git a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js index 3f00f541c5..eaac4ddde8 100644 --- a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js +++ b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js @@ -7,6 +7,7 @@ const useUpdate = require('mail/static/src/component_hooks/use_update/use_update const { FormViewDialog } = require('web.view_dialogs'); const { ComponentAdapter } = require('web.OwlCompatibility'); +const session = require('web.session'); const { Component } = owl; const { useRef } = owl.hooks; @@ -124,11 +125,13 @@ class ComposerSuggestedRecipient extends Component { // recipient that does not have a partner, the partner creation form // should be opened. if (isChecked && this._dialogRef && !this._isDialogOpen) { + const widget = this._dialogRef.comp.widget; this._isDialogOpen = true; - this._dialogRef.comp.widget.on('closed', this, () => { + widget.on('closed', this, () => { this._isDialogOpen = false; }); - this._dialogRef.comp.widget.open(); + widget.context = Object.assign({}, widget.context, session.user_context) + widget.open(); } } } diff --git a/addons/mail/static/src/widgets/discuss/discuss.js b/addons/mail/static/src/widgets/discuss/discuss.js index aec1f29b98..5db6ecf3ce 100644 --- a/addons/mail/static/src/widgets/discuss/discuss.js +++ b/addons/mail/static/src/widgets/discuss/discuss.js @@ -147,6 +147,7 @@ const DiscussWidget = AbstractAction.extend({ 'o-update-control-panel', this._updateControlPanelEventListener ); + this._lastPushStateActiveThread = null; }, //-------------------------------------------------------------------------- diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index f166f92a6c..b427b0641e 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -423,6 +423,7 @@ class MailComposer(models.TransientModel): 'body_html': record.body or False, 'model_id': model.id or False, 'attachment_ids': [(6, 0, [att.id for att in record.attachment_ids])], + 'use_default_to': True, } template = self.env['mail.template'].create(values) # generate the saved template diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 8ad4c45658..74b7d3af94 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -116,7 +116,8 @@ class MrpProduction(models.Model): picking_type_id = fields.Many2one( 'stock.picking.type', 'Operation Type', domain="[('code', '=', 'mrp_operation'), ('company_id', '=', company_id)]", - default=_get_default_picking_type, required=True, check_company=True) + default=_get_default_picking_type, required=True, check_company=True, + readonly=True, states={'draft': [('readonly', False)]}) use_create_components_lots = fields.Boolean(related='picking_type_id.use_create_components_lots') location_src_id = fields.Many2one( 'stock.location', 'Components Location', diff --git a/addons/payment_alipay/controllers/main.py b/addons/payment_alipay/controllers/main.py index 0c101275f2..3dce95a095 100644 --- a/addons/payment_alipay/controllers/main.py +++ b/addons/payment_alipay/controllers/main.py @@ -49,9 +49,18 @@ class AlipayController(http.Controller): return 'success' return "" - @http.route('/payment/alipay/return', type='http', auth="public", methods=['GET', 'POST']) + @http.route('/payment/alipay/return', type='http', auth="public", methods=['GET', 'POST'], save_session=False) def alipay_return(self, **post): - """ Alipay return """ + """ Alipay return + + The route is flagged with `save_session=False` to prevent Flectra from assigning a new session + to the user if they are redirected to this route with a POST request. Indeed, as the session + cookie is created without a `SameSite` attribute, some browsers that don't implement the + recommended default `SameSite=Lax` behavior will not include the cookie in the redirection + request from the payment provider to Flectra. As the redirection to the '/payment/status' page + will satisfy any specification of the `SameSite` attribute, the session of the user will be + retrieved and with it the transaction which will be immediately post-processed. + """ _logger.info('Beginning Alipay form_feedback with post data %s', pprint.pformat(post)) self._alipay_validate_data(**post) return werkzeug.utils.redirect('/payment/process') diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 70b1e5173d..0a46a555b6 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -764,7 +764,7 @@ class PosOrderLine(models.Model): price_subtotal_incl = fields.Float(string='Subtotal', digits=0, readonly=True, required=True) discount = fields.Float(string='Discount (%)', digits=0, default=0.0) - order_id = fields.Many2one('pos.order', string='Order Ref', ondelete='cascade', required=True) + order_id = fields.Many2one('pos.order', string='Order Ref', ondelete='cascade', required=True, index=True) tax_ids = fields.Many2many('account.tax', string='Taxes', readonly=True) tax_ids_after_fiscal_position = fields.Many2many('account.tax', compute='_get_tax_ids_after_fiscal_position', string='Taxes to Apply') pack_lot_ids = fields.One2many('pos.pack.operation.lot', 'pos_order_line_id', string='Lot/serial Number') diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index e3c717fccc..c67f98ba16 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1884,6 +1884,7 @@ exports.Orderline = Backbone.Model.extend({ getPackLotLinesToEdit: function(isAllowOnlyOneLot) { const currentPackLotLines = this.pack_lot_lines.models; let nExtraLines = Math.abs(this.quantity) - currentPackLotLines.length; + nExtraLines = Math.ceil(nExtraLines); nExtraLines = nExtraLines > 0 ? nExtraLines : 1; const tempLines = currentPackLotLines .map(lotLine => ({ diff --git a/addons/portal/models/mail_message.py b/addons/portal/models/mail_message.py index 1e7cbec777..855c90cc62 100644 --- a/addons/portal/models/mail_message.py +++ b/addons/portal/models/mail_message.py @@ -19,7 +19,7 @@ class MailMessage(models.Model): message_subtype_note_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note') IrAttachmentSudo = self.env['ir.attachment'].sudo() for vals in vals_list: - vals['is_message_subtype_note'] = message_subtype_note_id and vals.get('subtype_id', [False])[0] == message_subtype_note_id + vals['is_message_subtype_note'] = message_subtype_note_id and (vals.get('subtype_id') or [False])[0] == message_subtype_note_id for attachment in vals.get('attachment_ids', []): if not attachment.get('access_token'): attachment['access_token'] = IrAttachmentSudo.browse(attachment['id']).generate_access_token()[0] diff --git a/addons/portal/tests/__init__.py b/addons/portal/tests/__init__.py index 9fc1e95759..37dcc42ede 100644 --- a/addons/portal/tests/__init__.py +++ b/addons/portal/tests/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. +from . import test_message_format_portal from . import test_tours diff --git a/addons/portal/tests/test_message_format_portal.py b/addons/portal/tests/test_message_format_portal.py new file mode 100644 index 0000000000..334eb2579a --- /dev/null +++ b/addons/portal/tests/test_message_format_portal.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. + +from flectra.tools import mute_logger +from flectra.tests import common, tagged + + +@tagged('mail_message') +class TestMessageFormatPortal(common.SavepointCase): + + @mute_logger('flectra.models.unlink') + def test_mail_message_format(self): + """ Test the specific message formatting for the portal. + Notably the flag that tells if the message is of subtype 'note'. """ + + partner = self.env['res.partner'].create({'name': 'Partner'}) + message_no_subtype = self.env['mail.message'].create([{ + 'model': 'res.partner', + 'res_id': partner.id, + }]) + formatted_result = message_no_subtype.portal_message_format() + # no defined subtype -> should return False + self.assertFalse(formatted_result[0].get('is_message_subtype_note')) + + message_comment = self.env['mail.message'].create([{ + 'model': 'res.partner', + 'res_id': partner.id, + 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment'), + }]) + formatted_result = message_comment.portal_message_format() + # subtype is a comment -> should return False + self.assertFalse(formatted_result[0].get('is_message_subtype_note')) + + message_note = self.env['mail.message'].create([{ + 'model': 'res.partner', + 'res_id': partner.id, + 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'), + }]) + formatted_result = message_note.portal_message_format() + # subtype is note -> should return True + self.assertTrue(formatted_result[0].get('is_message_subtype_note')) diff --git a/addons/product/models/product_attribute.py b/addons/product/models/product_attribute.py index 703468a3f2..1f83a5c26a 100644 --- a/addons/product/models/product_attribute.py +++ b/addons/product/models/product_attribute.py @@ -364,7 +364,8 @@ class ProductTemplateAttributeLine(models.Model): # re-use a value that was archived at a previous step. ptav_to_activate.write({'ptav_active': True}) ptav_to_unlink.write({'ptav_active': False}) - ptav_to_unlink.unlink() + if ptav_to_unlink: + ptav_to_unlink.unlink() ProductTemplateAttributeValue.create(ptav_to_create) self.product_tmpl_id._create_variant_ids() diff --git a/addons/product/views/product_views.xml b/addons/product/views/product_views.xml index c19ee042ab..ba251447c6 100644 --- a/addons/product/views/product_views.xml +++ b/addons/product/views/product_views.xml @@ -111,7 +111,7 @@ colspan="4" attrs="{'invisible':['|', ('type', 'not in', ['product', 'consu']), ('product_variant_count', '>', 1), ('is_product_variant', '=', False)]}" groups="product.group_stock_packaging"> - + @@ -219,6 +219,7 @@ +