From dda9de67b6e6622621e57c1fbb07d49e7e2a8336 Mon Sep 17 00:00:00 2001 From: Parthiv Patel Date: Fri, 26 Aug 2022 08:34:34 +0000 Subject: [PATCH] [PATCH] Upstream patch - 26082022 --- addons/account/wizard/account_resequence.py | 4 +- .../account_edi/models/account_edi_format.py | 3 + .../data/facturx_templates.xml | 4 ++ .../models/account_edi_format.py | 5 +- .../models/stock_picking.py | 12 ++++ .../tests/test_purchase_subcontracting.py | 58 +++++++++++++++++++ addons/payment_stripe/controllers/main.py | 7 +++ .../static/src/js/payment_processing.js | 7 ++- .../static/lib/tempusdominus/tempusdominus.js | 9 ++- addons/web/static/src/js/public/lazyloader.js | 6 +- .../src/snippets/s_image_gallery/000.js | 18 +++--- .../static/src/scss/website_event.scss | 5 ++ 12 files changed, 124 insertions(+), 14 deletions(-) diff --git a/addons/account/wizard/account_resequence.py b/addons/account/wizard/account_resequence.py index 842e32018f..b96d6a2989 100644 --- a/addons/account/wizard/account_resequence.py +++ b/addons/account/wizard/account_resequence.py @@ -100,7 +100,7 @@ class ReSequenceWizard(models.TransientModel): for move in record.move_ids._origin: # Sort the moves by period depending on the sequence number reset moves_by_period[_get_move_key(move)] += move - format, format_values = self.env['account.move']._get_sequence_format_param(record.first_name) + seq_format, format_values = record.move_ids[0]._get_sequence_format_param(record.first_name) new_values = {} for j, period_recs in enumerate(moves_by_period.values()): @@ -114,7 +114,7 @@ class ReSequenceWizard(models.TransientModel): 'server-date': str(move.date), } - new_name_list = [format.format(**{ + new_name_list = [seq_format.format(**{ **format_values, 'year': period_recs[0].date.year % (10 ** format_values['year_length']), 'month': period_recs[0].date.month, diff --git a/addons/account_edi/models/account_edi_format.py b/addons/account_edi/models/account_edi_format.py index 82a498a160..5e1e357ded 100644 --- a/addons/account_edi/models/account_edi_format.py +++ b/addons/account_edi/models/account_edi_format.py @@ -625,6 +625,9 @@ class AccountEdiFormat(models.Model): :param barcode: The barcode of the product. :returns: A product or an empty recordset if not found. ''' + if name and '\n' in name: + # cut Sales Description from the name + name = name.split('\n')[0] domains = [] for value, domain in ( (name, ('name', 'ilike', name)), diff --git a/addons/account_edi_facturx/data/facturx_templates.xml b/addons/account_edi_facturx/data/facturx_templates.xml index cc0dd3d967..ba0ba3ab12 100644 --- a/addons/account_edi_facturx/data/facturx_templates.xml +++ b/addons/account_edi_facturx/data/facturx_templates.xml @@ -21,6 +21,10 @@ + diff --git a/addons/account_edi_facturx/models/account_edi_format.py b/addons/account_edi_facturx/models/account_edi_format.py index dce594e40e..bea5d8b62d 100644 --- a/addons/account_edi_facturx/models/account_edi_format.py +++ b/addons/account_edi_facturx/models/account_edi_format.py @@ -324,13 +324,14 @@ class AccountEdiFormat(models.Model): # Product. name = _find_value('.//ram:SpecifiedTradeProduct/ram:Name', element) - if name: - invoice_line_form.name = name invoice_line_form.product_id = self_ctx._retrieve_product( default_code=_find_value('.//ram:SpecifiedTradeProduct/ram:SellerAssignedID', element), name=_find_value('.//ram:SpecifiedTradeProduct/ram:Name', element), barcode=_find_value('.//ram:SpecifiedTradeProduct/ram:GlobalID', element) ) + # force original line description instead of the one copied from product's Sales Description + if name: + invoice_line_form.name = name # Quantity. line_elements = element.xpath('.//ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', namespaces=tree.nsmap) diff --git a/addons/mrp_subcontracting_dropshipping/models/stock_picking.py b/addons/mrp_subcontracting_dropshipping/models/stock_picking.py index 09f9029b86..47333ef481 100644 --- a/addons/mrp_subcontracting_dropshipping/models/stock_picking.py +++ b/addons/mrp_subcontracting_dropshipping/models/stock_picking.py @@ -11,3 +11,15 @@ class StockPicking(models.Model): if subcontract_move.sale_line_id: return subcontract_move.sale_line_id.order_id.warehouse_id return super(StockPicking, self)._get_warehouse(subcontract_move) + + def _prepare_subcontract_mo_vals(self, subcontract_move, bom): + res = super()._prepare_subcontract_mo_vals(subcontract_move, bom) + if not res.get('picking_type_id') and subcontract_move.location_dest_id.usage == 'customer': + # If the if-condition is respected, it means that `subcontract_move` is not + # related to a specific warehouse. This can happen if, for instance, the user + # confirms a PO with a subcontracted product that should be delivered to a + # customer (dropshipping). In that case, we can use a default warehouse to + # get the picking type + default_warehouse = self.env['stock.warehouse'].search([('company_id', '=', subcontract_move.company_id.id)], limit=1) + res['picking_type_id'] = default_warehouse.subcontracting_type_id.id, + return res diff --git a/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py b/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py index f58ae1d475..40de5c3e84 100644 --- a/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py +++ b/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py @@ -134,3 +134,61 @@ class TestSubcontractingDropshippingFlows(TestMrpSubcontractingCommon): self.assertEqual(move1.product_uom_qty, 1) self.assertEqual(move2.product_id, self.comp1) self.assertEqual(move2.product_uom_qty, 1) + + def test_po_to_customer(self): + """ + Create and confirm a PO with a subcontracted move. The picking type of + the PO is 'Dropship' and the delivery address a customer. + """ + subcontractor, client = self.env['res.partner'].create([ + {'name': 'SuperSubcontractor'}, + {'name': 'SuperClient'}, + ]) + + p_finished, p_compo = self.env['product.product'].create([{ + 'name': 'Finished Product', + 'type': 'product', + 'seller_ids': [(0, 0, {'name': subcontractor.id})], + }, { + 'name': 'Component', + 'type': 'consu', + }]) + + bom = self.env['mrp.bom'].create({ + 'product_tmpl_id': p_finished.product_tmpl_id.id, + 'product_qty': 1, + 'type': 'subcontract', + 'subcontractor_ids': [(6, 0, subcontractor.ids)], + 'bom_line_ids': [ + (0, 0, {'product_id': p_compo.id, 'product_qty': 1}), + ], + }) + + dropship_picking_type = self.env['stock.picking.type'].search([ + ('company_id', '=', self.env.company.id), + ('default_location_src_id.usage', '=', 'supplier'), + ('default_location_dest_id.usage', '=', 'customer'), + ], limit=1, order='sequence') + + po = self.env['purchase.order'].create({ + "partner_id": subcontractor.id, + "picking_type_id": dropship_picking_type.id, + "dest_address_id": client.id, + "order_line": [(0, 0, { + 'product_id': p_finished.id, + 'name': p_finished.name, + 'product_qty': 1.0, + })], + }) + po.button_confirm() + + mo = self.env['mrp.production'].search([('bom_id', '=', bom.id)]) + self.assertEqual(mo.picking_type_id, self.warehouse.subcontracting_type_id) + + delivery = po.picking_ids + delivery.move_line_ids.qty_done = 1.0 + delivery.button_validate() + + self.assertEqual(delivery.state, 'done') + self.assertEqual(mo.state, 'done') + self.assertEqual(po.order_line.qty_received, 1) diff --git a/addons/payment_stripe/controllers/main.py b/addons/payment_stripe/controllers/main.py index a95de27fe3..4a268f1196 100644 --- a/addons/payment_stripe/controllers/main.py +++ b/addons/payment_stripe/controllers/main.py @@ -55,6 +55,13 @@ class StripeController(http.Controller): def stripe_s2s_process_payment_intent(self, **post): return request.env['payment.transaction'].sudo().form_feedback(post, 'stripe') + @http.route('/payment/stripe/s2s/process_payment_error', type='json', auth='public', csrf=False) + def stripe_s2s_process_payment_error(self, **post): + transaction_sudo = request.env['payment.transaction'].sudo().search([('reference', '=', post['reference']), + ('provider', '=', 'stripe'), + ('stripe_payment_intent_secret', '=', post['stripe_payment_intent_secret'])]) + transaction_sudo.write({'state': 'error', 'state_message': post['error']}) + @http.route('/payment/stripe/webhook', type='json', auth='public', csrf=False) def stripe_webhook(self, **kwargs): data = json.loads(request.httprequest.data) diff --git a/addons/payment_stripe/static/src/js/payment_processing.js b/addons/payment_stripe/static/src/js/payment_processing.js index 70ad3c6010..0bcb9b1a97 100644 --- a/addons/payment_stripe/static/src/js/payment_processing.js +++ b/addons/payment_stripe/static/src/js/payment_processing.js @@ -22,7 +22,12 @@ return PaymentProcessing.include({ return stripe.handleCardPayment(tx.stripe_payment_intent_secret) .then(function(result) { if (result.error) { - return Promise.reject({"message": {"data": { "message": result.error.message}}}); + return rpc.query({ + route: '/payment/stripe/s2s/process_payment_error', + params: _.extend({}, {reference: tx.reference, + stripe_payment_intent_secret: tx.stripe_payment_intent_secret, + error: result.error.message}) + }).then(()=> Promise.reject({"message": {"data": { "message": result.error.message}}})); } return rpc.query({ route: '/payment/stripe/s2s/process_payment_intent', diff --git a/addons/web/static/lib/tempusdominus/tempusdominus.js b/addons/web/static/lib/tempusdominus/tempusdominus.js index 3ba81a4ea4..71aa9c1975 100644 --- a/addons/web/static/lib/tempusdominus/tempusdominus.js +++ b/addons/web/static/lib/tempusdominus/tempusdominus.js @@ -45,7 +45,14 @@ var DateTimePicker = function ($, moment) { EVENT_KEY = '.' + DATA_KEY, DATA_API_KEY = '.data-api', Selector = { - DATA_TOGGLE: '[data-toggle="' + DATA_KEY + '"]' + // /!\ FLECTRA PATCH: ensure the datetimepickers can be toggled only after + // the Flectra lazy loader finished loading all lazy scripts. Another + // solution could have been to temporarily removing the data-toggle + // attributes during lazyloading but that would not have been stable as + // custom code could search for data-toggle elements while the lazy + // loading is being performed. Without this, clicking too soon on a + // datetimepicker would not use the right format and UI options. + DATA_TOGGLE: 'body:not(.o_lazy_js_waiting) [data-toggle="' + DATA_KEY + '"]' }, ClassName = { INPUT: NAME + '-input' diff --git a/addons/web/static/src/js/public/lazyloader.js b/addons/web/static/src/js/public/lazyloader.js index c132d760d2..03c6f22c5e 100644 --- a/addons/web/static/src/js/public/lazyloader.js +++ b/addons/web/static/src/js/public/lazyloader.js @@ -10,7 +10,7 @@ var blockFunction = function (ev) { var waitingLazy = false; /** - * Blocks the DOM sections which explicitely require the lazy loaded JS to be + * Blocks the DOM sections which explicitly require the lazy loaded JS to be * working (those sections should be marked with the 'o_wait_lazy_js' class). * * @see stopWaitingLazy @@ -28,6 +28,8 @@ function waitLazy() { element.addEventListener(evType, blockFunction); }); } + + document.body.classList.add('o_lazy_js_waiting'); } /** * Unblocks the DOM sections blocked by @see waitLazy and removes the related @@ -47,6 +49,8 @@ function stopWaitingLazy() { }); element.classList.remove('o_wait_lazy_js'); } + + document.body.classList.remove('o_lazy_js_waiting'); } // Start waiting for lazy loading as soon as the DOM is available diff --git a/addons/website/static/src/snippets/s_image_gallery/000.js b/addons/website/static/src/snippets/s_image_gallery/000.js index bc521add9a..e2adc2d05a 100644 --- a/addons/website/static/src/snippets/s_image_gallery/000.js +++ b/addons/website/static/src/snippets/s_image_gallery/000.js @@ -26,6 +26,9 @@ const GalleryWidget = publicWidget.Widget.extend({ * @param {Event} ev */ _onClickImg: function (ev) { + if (this.$modal) { + return; + } var self = this; var $cur = $(ev.currentTarget); @@ -43,29 +46,30 @@ const GalleryWidget = publicWidget.Widget.extend({ var $img = ($cur.is('img') === true) ? $cur : $cur.closest('img'); const milliseconds = $cur.closest('.s_image_gallery').data('interval') || false; - var $modal = $(qweb.render('website.gallery.slideshow.lightbox', { + this.$modal = $(qweb.render('website.gallery.slideshow.lightbox', { images: $images.get(), index: $images.index($img), dim: dimensions, interval: milliseconds || 0, id: _.uniqueId('slideshow_'), })); - $modal.modal({ + this.$modal.modal({ keyboard: true, backdrop: true, }); - $modal.on('hidden.bs.modal', function () { + this.$modal.on('hidden.bs.modal', function () { $(this).hide(); $(this).siblings().filter('.modal-backdrop').remove(); // bootstrap leaves a modal-backdrop $(this).remove(); + self.$modal = undefined; }); - $modal.find('.modal-content, .modal-body.o_slideshow').css('height', '100%'); - $modal.appendTo(document.body); + this.$modal.find('.modal-content, .modal-body.o_slideshow').css('height', '100%'); + this.$modal.appendTo(document.body); - $modal.one('shown.bs.modal', function () { + this.$modal.one('shown.bs.modal', function () { self.trigger_up('widgets_start_request', { editableMode: false, - $target: $modal.find('.modal-body.o_slideshow'), + $target: self.$modal.find('.modal-body.o_slideshow'), }); }); }, diff --git a/addons/website_event/static/src/scss/website_event.scss b/addons/website_event/static/src/scss/website_event.scss index 69cdfc6380..426fa56d82 100644 --- a/addons/website_event/static/src/scss/website_event.scss +++ b/addons/website_event/static/src/scss/website_event.scss @@ -63,6 +63,11 @@ } } } + @include media-breakpoint-up(sm) { + #o_wevent_index_main_col article div.col { + min-width: 0; + } + } &.opt_event_list_cards_bg { @if (color('body') == $o-portal-default-body-bg) { @extend .bg-200;