[PATCH] Upstream patch - 27042022

This commit is contained in:
Parthiv Patel
2022-04-27 08:35:23 +00:00
parent 867a7edc1f
commit 5bc5715027
16 changed files with 159 additions and 27 deletions

View File

@@ -1,4 +1,4 @@
<flectra>
<flectra noupdate="1">
<record id="ir_cron_edi_network" model="ir.cron">
<field name="name">EDI : Perform web services operations</field>
<field name="model_id" ref="model_account_edi_document"/>

View File

@@ -3,10 +3,12 @@
import logging
import poplib
from ssl import SSLError
from socket import gaierror, timeout
import socket
from imaplib import IMAP4, IMAP4_SSL
from poplib import POP3, POP3_SSL
from socket import gaierror, timeout
from ssl import SSLError
from flectra import api, fields, models, tools, _
from flectra.exceptions import UserError
@@ -19,6 +21,11 @@ MAIL_TIMEOUT = 60
# Workaround for Python 2.7.8 bug https://bugs.python.org/issue23906
poplib._MAXLINE = 65536
# Add timeout to IMAP connections
# HACK https://bugs.python.org/issue38615
# TODO: clean in Python 3.9
IMAP4._create_socket = lambda self, timeout=MAIL_TIMEOUT: socket.create_connection((self.host or None, self.port), timeout)
class FetchmailServer(models.Model):
"""Incoming POP/IMAP mail server account"""
@@ -108,15 +115,13 @@ flectra_mailgate: "|/path/to/flectra-mailgate.py --host=localhost -u %(uid)d -p
self._imap_login(connection)
elif self.server_type == 'pop':
if self.is_ssl:
connection = POP3_SSL(self.server, int(self.port))
connection = POP3_SSL(self.server, int(self.port), timeout=MAIL_TIMEOUT)
else:
connection = POP3(self.server, int(self.port))
connection = POP3(self.server, int(self.port), timeout=MAIL_TIMEOUT)
#TODO: use this to remove only unread messages
#connection.user("recent:"+server.user)
connection.user(self.user)
connection.pass_(self.password)
# Add timeout on socket
connection.sock.settimeout(MAIL_TIMEOUT)
return connection
def _imap_login(self, connection):

View File

@@ -467,6 +467,12 @@ var MassMailingFieldHtml = FieldHtml.extend({
$dropdown.find('.dropdown-item:eq(' + themesParams.indexOf(selectedTheme) + ')').addClass('selected');
});
// Prevent expansion of drop-down while clicking on empty area during theme selection
$dropdown.on("click", ".dropdown-menu", function (ev) {
ev.preventDefault();
ev.stopPropagation();
});
/**
* If the user opens the theme selection screen, indicates which one is active and
* saves the information...

View File

@@ -77,6 +77,10 @@
}
}
&:focus {
background-color: $o-we-sidebar-bg;
}
&.selected .o_thumb {
border: 2px solid $o-brand-odoo;
background-color: $o-we-sidebar-bg;

View File

@@ -16,8 +16,10 @@ class MailMessage(models.Model):
def _portal_message_format(self, fields_list):
vals_list = self._message_format(fields_list)
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
for attachment in vals.get('attachment_ids', []):
if not attachment.get('access_token'):
attachment['access_token'] = IrAttachmentSudo.browse(attachment['id']).generate_access_token()[0]

View File

@@ -114,7 +114,10 @@
<!-- Chatter: internal toggle widget -->
<t t-name="portal.chatter_internal_toggle">
<div t-attf-class="float-right o_portal_chatter_js_is_internal #{message.is_internal and 'o_portal_message_internal_on' or 'o_portal_message_internal_off'}"
<div t-if="message.is_message_subtype_note" class="float-right">
<button class="btn btn-secondary" title="Internal notes are only displayed to internal users." disabled="true">Internal Note</button>
</div>
<div t-else="" t-attf-class="float-right o_portal_chatter_js_is_internal #{message.is_internal and 'o_portal_message_internal_on' or 'o_portal_message_internal_off'}"
t-att-data-message-id="message.id"
t-att-data-is-internal="message.is_internal">
<button class="btn btn-danger"

View File

@@ -141,7 +141,7 @@
<div class="clearfix" name="so_total_summary">
<div id="total" class="row" name="total">
<div t-attf-class="#{'col-4' if report_type != 'html' else 'col-sm-7 col-md-5'} ml-auto">
<div t-attf-class="#{'col-6' if report_type != 'html' else 'col-sm-7 col-md-6'} ml-auto">
<table class="table table-sm">
<tr class="border-black o_subtotal" style="">
<td name="td_amount_untaxed_label"><strong>Subtotal</strong></td>

View File

@@ -516,7 +516,7 @@
</table>
<div id="total" class="row" name="total" style="page-break-inside: avoid;">
<div t-attf-class="#{'col-4' if report_type != 'html' else 'col-sm-7 col-md-5'} ml-auto">
<div t-attf-class="#{'col-6' if report_type != 'html' else 'col-sm-7 col-md-6'} ml-auto">
<t t-call="sale.sale_order_portal_content_totals_table"/>
</div>
</div>

View File

@@ -343,3 +343,39 @@ class TestProgramWithCodeOperations(TestSaleCouponCommon):
}).process_coupon()
self.assertEqual(len(order.order_line), 2, "You should get a discount line")
def test_apply_program_no_reward_link(self):
# Tests that applying a promo code that does not generate reward lines
# does not link on the order
self.env['coupon.program'].create({
'name': 'Code for 10% on orders',
'promo_code_usage': 'code_needed',
'promo_code': 'test_10pc',
'discount_type': 'percentage',
'discount_percentage': 10.0,
'program_type': 'promotion_program',
})
self.empty_order.write({'order_line': [
(0, False, {
'product_id': self.product_C.id,
'name': '1 Product C',
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
'price_unit': 0,
})
]})
self.env['sale.coupon.apply.code'].with_context(active_id=self.empty_order.id).create({
'coupon_code': 'test_10pc',
}).process_coupon()
self.assertFalse(self.empty_order.code_promo_program_id, 'The program should not be linked to the order')
# Same for a coupon's code
self.env['coupon.generate.wizard'].with_context(active_id=self.code_promotion_program_with_discount.id).create({
'generation_type': 'nbr_coupon',
'nbr_coupons': 1,
}).generate_coupon()
coupon = self.code_promotion_program_with_discount.coupon_ids
self.env['sale.coupon.apply.code'].with_context(active_id=self.empty_order.id).create({
'coupon_code': coupon.code,
}).process_coupon()
self.assertFalse(self.empty_order.applied_coupon_ids, 'No coupon should be linked to the order')
self.assertEqual(coupon.state, 'new', 'Coupon should be in a new state')

View File

@@ -39,16 +39,22 @@ class SaleCouponApplyCode(models.TransientModel):
}
}
else: # The program is applied on this order
# Only link the promo program if reward lines were created
order_line_count = len(order.order_line)
order._create_reward_line(program)
order.code_promo_program_id = program
if order_line_count < len(order.order_line):
order.code_promo_program_id = program
else:
coupon = self.env['coupon.coupon'].search([('code', '=', coupon_code)], limit=1)
if coupon:
error_status = coupon._check_coupon_code(order)
if not error_status:
# Consume coupon only if reward lines were created
order_line_count = len(order.order_line)
order._create_reward_line(coupon.program_id)
order.applied_coupon_ids += coupon
coupon.write({'state': 'used'})
if order_line_count < len(order.order_line):
order.applied_coupon_ids += coupon
coupon.write({'state': 'used'})
else:
error_status = {'not_found': _('This coupon is invalid (%s).') % (coupon_code)}
return error_status

View File

@@ -408,8 +408,9 @@ class StockWarehouseOrderpoint(models.Model):
orderpoints = self.env['stock.warehouse.orderpoint'].with_user(SUPERUSER_ID).create(orderpoint_values_list)
for orderpoint in orderpoints:
orderpoint.route_id = orderpoint.product_id.route_ids[:1]
orderpoints.filtered(lambda o: not o.route_id)._set_default_route_id()
orderpoint_wh = orderpoint.location_id.get_warehouse()
orderpoint.route_id = next((r for r in orderpoint.product_id.route_ids if not r.supplied_wh_id or r.supplied_wh_id == orderpoint_wh), None) \
or orderpoint._set_default_route_id()
return action
@api.model

View File

@@ -321,6 +321,60 @@ class TestProcRule(TransactionCase):
self.assertAlmostEqual(picking_pick.move_lines.product_uom_qty, 5.0)
self.assertAlmostEqual(picking_ship.move_lines.product_uom_qty, 3.0)
def test_orderpoint_replenishment_view(self):
""" Create two warehouses + two moves
verify that the replenishment view is consistent"""
warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
warehouse_2, warehouse_3 = self.env['stock.warehouse'].create([{
'name': 'Warehouse Two',
'code': 'WH2',
'resupply_wh_ids': [warehouse_1.id],
}, {
'name': 'Warehouse Three',
'code': 'WH3',
'resupply_wh_ids': [warehouse_1.id],
}])
route_2 = self.env['stock.location.route'].search([
('supplied_wh_id', '=', warehouse_2.id),
('supplier_wh_id', '=', warehouse_1.id),
])
route_3 = self.env['stock.location.route'].search([
('supplied_wh_id', '=', warehouse_3.id),
('supplier_wh_id', '=', warehouse_1.id),
])
product = self.env['product.product'].create({
'name': 'Super Product',
'type': 'product',
'route_ids': [route_2.id, route_3.id]
})
moves = self.env['stock.move'].create([{
'name': 'Move WH2',
'location_id': warehouse_2.lot_stock_id.id,
'location_dest_id': self.partner.property_stock_customer.id,
'product_id': product.id,
'product_uom': product.uom_id.id,
'product_uom_qty': 1,
}, {
'name': 'Move WH3',
'location_id': warehouse_3.lot_stock_id.id,
'location_dest_id': self.partner.property_stock_customer.id,
'product_id': product.id,
'product_uom': product.uom_id.id,
'product_uom_qty': 1,
}])
moves._action_confirm()
# activate action of opening the replenishment view
self.env['report.stock.quantity'].flush()
self.env['stock.warehouse.orderpoint'].action_open_orderpoints()
replenishments = self.env['stock.warehouse.orderpoint'].search([
('product_id', '=', product.id),
])
# Verify that the location and the route make sense
self.assertRecordValues(replenishments, [
{'location_id': warehouse_2.lot_stock_id.id, 'route_id': route_2.id},
{'location_id': warehouse_3.lot_stock_id.id, 'route_id': route_3.id},
])
class TestProcRuleLoad(TransactionCase):
def setUp(cls):

View File

@@ -768,7 +768,7 @@ class Website(models.Model):
endpoint.routing['auth'] in ('none', 'public') and
endpoint.routing.get('website', False) and
all(hasattr(converter, 'generate') for converter in converters)):
return False
return False
# dont't list routes without argument having no default value or converter
sign = inspect.signature(endpoint.method.original_func)
@@ -966,6 +966,17 @@ class Website(models.Model):
self.ensure_one()
return self._get_http_domain() or super(BaseModel, self).get_base_url()
@tools.ormcache('path', 'lang')
def _get_canonical_url_localized_cached(self, path, args, lang):
router = http.root.get_db_router(request.db).bind_to_environ(request.httprequest.environ)
for key, val in list(args.items()):
if isinstance(val, models.BaseModel):
if val.env.context.get('lang') != lang:
args[key] = val.with_context(lang=lang)
endpoint = router.match(path_info=path, return_rule=True)[0].endpoint
return router.build(endpoint, args)
def _get_canonical_url_localized(self, lang, canonical_params):
"""Returns the canonical URL for the current request with translatable
elements appropriately translated in `lang`.
@@ -976,13 +987,11 @@ class Website(models.Model):
"""
self.ensure_one()
if request.endpoint:
router = http.root.get_db_router(request.db).bind('')
arguments = dict(request.endpoint_arguments)
for key, val in list(arguments.items()):
if isinstance(val, models.BaseModel):
if val.env.context.get('lang') != lang.code:
arguments[key] = val.with_context(lang=lang.code)
path = router.build(request.endpoint, arguments)
path = self._get_canonical_url_localized_cached(
request.httprequest.path,
dict(request.endpoint_arguments),
lang.code
)
else:
# The build method returns a quoted URL so convert in this case for consistency.
path = urls.url_quote_plus(request.httprequest.path, safe='/')

View File

@@ -712,11 +712,13 @@ registry.mediaVideo = publicWidget.Widget.extend(MobileYoutubeAutoplayMixin, {
// Unsupported domain, don't inject iframe
return;
}
return this.$target.append($('<iframe/>', {
const iframeEl = $('<iframe/>', {
src: src,
frameborder: '0',
allowfullscreen: 'allowfullscreen',
}))[0];
})[0];
this.$target.append(iframeEl);
return iframeEl;
},
});

View File

@@ -103,13 +103,13 @@
</t>
</t>
<t t-if="request and request.is_frontend_multilang and website">
<t t-if="request and request.is_frontend_multilang and website and website.is_public_user()">
<t t-set="alternate_languages" t-value="website._get_alternate_languages(canonical_params=canonical_params)"/>
<t t-foreach="alternate_languages" t-as="lg">
<link rel="alternate" t-att-hreflang="lg['hreflang']" t-att-href="lg['href']"/>
</t>
</t>
<link t-if="request and website" rel="canonical" t-att-href="website._get_canonical_url(canonical_params=canonical_params)"/>
<link t-if="request and website and website.is_public_user()" rel="canonical" t-att-href="website._get_canonical_url(canonical_params=canonical_params)"/>
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin=""/>
</xpath>

View File

@@ -720,6 +720,10 @@ class WebsiteSale(http.Controller):
values = kw
else:
partner_id = self._checkout_form_save(mode, post, kw)
# We need to validate _checkout_form_save return, because when partner_id not in shippings
# it returns Forbidden() instead the partner_id
if isinstance(partner_id, Forbidden):
return partner_id
if mode[1] == 'billing':
order.partner_id = partner_id
order.with_context(not_self_saleperson=True).onchange_partner_id()