[PATCH] Upstream patch - 01102022

This commit is contained in:
Parthiv Patel
2022-10-01 08:35:00 +00:00
parent 1139d6e6ef
commit fd59bc318d
24 changed files with 366 additions and 32 deletions

View File

@@ -68,7 +68,7 @@ class AccountPaymentTerm(models.Model):
if dist:
last_date = result and result[-1][0] or fields.Date.context_today(self)
result.append((last_date, dist))
return result
return sorted(result, key=lambda k: k[0])
def unlink(self):
for terms in self:

View File

@@ -135,6 +135,7 @@ class ReSequenceWizard(models.TransientModel):
if self.move_ids.journal_id and self.move_ids.journal_id.restrict_mode_hash_table:
if self.ordering == 'date':
raise UserError(_('You can not reorder sequence by date when the journal is locked with a hash.'))
self.move_ids._check_fiscalyear_lock_date()
self.env['account.move'].browse(int(k) for k in new_values.keys()).name = False
for move_id in self.move_ids:
if str(move_id.id) in new_values:

View File

@@ -96,10 +96,8 @@ class AccountMove(models.Model):
for num, line in enumerate(lines):
sign = -1 if line.move_id.is_inbound() else 1
price_subtotal = (line.balance * sign) if convert_to_euros else line.price_subtotal
# The price_subtotal should be inverted when:
# The line has downpayment lines, but is not a downpayment (i.e. the final invoice, from which downpayment lines are subtracted) or,
# the line is a reverse charge refund.
if (line._get_downpayment_lines() and not is_downpayment) or reverse_charge_refund:
# The price_subtotal should be inverted when the line is a reverse charge refund.
if reverse_charge_refund:
price_subtotal = -price_subtotal
# Unit price

View File

@@ -477,7 +477,10 @@ class MrpProduction(models.Model):
production.state = 'draft'
elif all(move.state == 'cancel' for move in production.move_raw_ids):
production.state = 'cancel'
elif all(move.state in ('cancel', 'done') for move in production.move_raw_ids):
elif (
all(move.state in ('cancel', 'done') for move in production.move_raw_ids)
and all(move.state in ('cancel', 'done') for move in production.move_finished_ids)
):
production.state = 'done'
elif production.workorder_ids and all(wo_state in ('done', 'cancel') for wo_state in production.workorder_ids.mapped('state')):
production.state = 'to_close'

View File

@@ -424,13 +424,13 @@ class MrpWorkorder(models.Model):
raise UserError(_('The planned end date of the work order cannot be prior to the planned start date, please correct this to save the work order.'))
if 'duration_expected' not in values and not self.env.context.get('bypass_duration_calculation'):
if values.get('date_planned_start') and values.get('date_planned_finished'):
computed_finished_time = self._calculate_date_planned_finished(start_date)
computed_finished_time = workorder._calculate_date_planned_finished(start_date)
values['date_planned_finished'] = computed_finished_time
elif values.get('date_planned_start'):
computed_duration = self._calculate_duration_expected(date_planned_start=start_date)
computed_duration = workorder._calculate_duration_expected(date_planned_start=start_date)
values['duration_expected'] = computed_duration
elif values.get('date_planned_finished'):
computed_duration = self._calculate_duration_expected(date_planned_finished=end_date)
computed_duration = workorder._calculate_duration_expected(date_planned_finished=end_date)
values['duration_expected'] = computed_duration
# Update MO dates if the start date of the first WO or the
# finished date of the last WO is update.

View File

@@ -84,6 +84,35 @@ class TestMrpByProduct(common.TransactionCase):
# I see that stock moves of External Hard Disk including Headset USB are done now.
self.assertFalse(any(move.state != 'done' for move in moves), 'Moves are not done!')
def test_01_mrp_byproduct(self):
self.env["stock.quant"].create({
"product_id": self.product_c_id,
"location_id": self.warehouse.lot_stock_id.id,
"quantity": 4,
})
bom_product_a = self.MrpBom.create({
'product_tmpl_id': self.product_a.product_tmpl_id.id,
'product_qty': 1.0,
'type': 'normal',
'product_uom_id': self.uom_unit_id,
'bom_line_ids': [(0, 0, {'product_id': self.product_c_id, 'product_uom_id': self.uom_unit_id, 'product_qty': 2})]
})
mnf_product_a_form = Form(self.env['mrp.production'])
mnf_product_a_form.product_id = self.product_a
mnf_product_a_form.bom_id = bom_product_a
mnf_product_a_form.product_qty = 2.0
mnf_product_a = mnf_product_a_form.save()
mnf_product_a.action_confirm()
self.assertEqual(mnf_product_a.state, "confirmed")
mnf_product_a.move_raw_ids._action_assign()
mnf_product_a.move_raw_ids.quantity_done = mnf_product_a.move_raw_ids.product_uom_qty
mnf_product_a.move_raw_ids._action_done()
self.assertEqual(mnf_product_a.state, "progress")
mnf_product_a.qty_producing = 2
mnf_product_a.button_mark_done()
self.assertTrue(mnf_product_a.move_finished_ids)
self.assertEqual(mnf_product_a.state, "done")
def test_change_product(self):
""" Create a production order for a specific product with a BoM. Then change the BoM and the finished product for
other ones and check the finished product of the first mo did not became a byproduct of the second one."""

View File

@@ -807,3 +807,116 @@ class TestSubcontractingTracking(TransactionCase):
wizard.process()
self.assertEqual(picking_receipt.state, 'done')
@tagged('post_install', '-at_install')
class TestSubcontractingPurchaseFlows(TransactionCase):
def setUp(self):
super().setUp()
# todo 15.0: move in `mrp_subcontracting_purchase`
if 'purchase.order' not in self.env:
self.skipTest('`purchase` is not installed')
self.subcontractor = self.env['res.partner'].create({'name': 'SuperSubcontractor'})
self.finished, self.compo = self.env['product.product'].create([{
'name': 'SuperProduct',
'type': 'product',
}, {
'name': 'Component',
'type': 'consu',
}])
self.bom = self.env['mrp.bom'].create({
'product_tmpl_id': self.finished.product_tmpl_id.id,
'type': 'subcontract',
'subcontractor_ids': [(6, 0, self.subcontractor.ids)],
'bom_line_ids': [(0, 0, {
'product_id': self.compo.id,
'product_qty': 1,
})],
})
def test_purchase_and_return01(self):
"""
The user buys 10 x a subcontracted product P. He receives the 10
products and then does a return with 3 x P. The test ensures that the
final received quantity is correctly computed
"""
po = self.env['purchase.order'].create({
'partner_id': self.subcontractor.id,
'order_line': [(0, 0, {
'name': self.finished.name,
'product_id': self.finished.id,
'product_uom_qty': 10,
'product_uom': self.finished.uom_id.id,
'price_unit': 1,
})],
})
po.button_confirm()
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
self.assertTrue(mo)
receipt = po.picking_ids
receipt.move_lines.quantity_done = 10
receipt.button_validate()
return_form = Form(self.env['stock.return.picking'].with_context(active_id=receipt.id, active_model='stock.picking'))
with return_form.product_return_moves.edit(0) as line:
line.quantity = 3
line.to_refund = True
return_wizard = return_form.save()
return_id, _ = return_wizard._create_returns()
return_picking = self.env['stock.picking'].browse(return_id)
return_picking.move_lines.quantity_done = 3
return_picking.button_validate()
self.assertEqual(self.finished.qty_available, 7.0)
self.assertEqual(po.order_line.qty_received, 7.0)
def test_purchase_and_return02(self):
"""
The user buys 10 x a subcontracted product P. He receives the 10
products and then does a return with 3 x P (with the flag to_refund
disabled and the subcontracting location as return location). The test
ensures that the final received quantity is correctly computed
"""
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
self.env.user.write({'groups_id': [(4, grp_multi_loc.id)]})
po = self.env['purchase.order'].create({
'partner_id': self.subcontractor.id,
'order_line': [(0, 0, {
'name': self.finished.name,
'product_id': self.finished.id,
'product_uom_qty': 10,
'product_uom': self.finished.uom_id.id,
'price_unit': 1,
})],
})
po.button_confirm()
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
self.assertTrue(mo)
receipt = po.picking_ids
receipt.move_lines.quantity_done = 10
receipt.button_validate()
return_form = Form(self.env['stock.return.picking'].with_context(active_id=receipt.id, active_model='stock.picking'))
return_form.location_id = self.env.company.subcontracting_location_id
with return_form.product_return_moves.edit(0) as line:
line.quantity = 3
line.to_refund = False
return_wizard = return_form.save()
return_id, _ = return_wizard._create_returns()
return_picking = self.env['stock.picking'].browse(return_id)
return_picking.move_lines.quantity_done = 3
return_picking.button_validate()
self.assertEqual(self.finished.qty_available, 7.0)
self.assertEqual(po.order_line.qty_received, 10.0)

View File

@@ -14,7 +14,10 @@ class StockPicking(models.Model):
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 not res.get('picking_type_id') and (
subcontract_move.location_dest_id.usage == 'customer'
or subcontract_move.partner_id.property_stock_subcontractor.parent_path in subcontract_move.location_dest_id.parent_path
):
# 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

View File

@@ -192,3 +192,73 @@ class TestSubcontractingDropshippingFlows(TestMrpSubcontractingCommon):
self.assertEqual(delivery.state, 'done')
self.assertEqual(mo.state, 'done')
self.assertEqual(po.order_line.qty_received, 1)
def test_po_to_subcontractor(self):
"""
Create and confirm a PO with a subcontracted move. The bought product is
also a component of another subcontracted product. The picking type of
the PO is 'Dropship' and the delivery address is the other subcontractor
"""
subcontractor, super_subcontractor = self.env['res.partner'].create([
{'name': 'Subcontractor'},
{'name': 'SuperSubcontractor'},
])
super_subcontractor.property_stock_customer = super_subcontractor.property_stock_subcontractor
super_product, product, component = self.env['product.product'].create([{
'name': 'Super Product',
'type': 'product',
'seller_ids': [(0, 0, {'name': super_subcontractor.id})],
}, {
'name': 'Product',
'type': 'product',
'seller_ids': [(0, 0, {'name': subcontractor.id})],
}, {
'name': 'Component',
'type': 'consu',
}])
_, bom_product = self.env['mrp.bom'].create([{
'product_tmpl_id': super_product.product_tmpl_id.id,
'product_qty': 1,
'type': 'subcontract',
'subcontractor_ids': [(6, 0, super_subcontractor.ids)],
'bom_line_ids': [
(0, 0, {'product_id': product.id, 'product_qty': 1}),
],
}, {
'product_tmpl_id': product.product_tmpl_id.id,
'product_qty': 1,
'type': 'subcontract',
'subcontractor_ids': [(6, 0, subcontractor.ids)],
'bom_line_ids': [
(0, 0, {'product_id': component.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": super_subcontractor.id,
"order_line": [(0, 0, {
'product_id': product.id,
'name': product.name,
'product_qty': 1.0,
})],
})
po.button_confirm()
mo = self.env['mrp.production'].search([('bom_id', '=', bom_product.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(po.order_line.qty_received, 1.0)

View File

@@ -109,6 +109,16 @@ class PurchaseOrder(models.Model):
class PurchaseOrderLine(models.Model):
_inherit = 'purchase.order.line'
def _compute_account_analytic_id(self):
for rec in self:
if not rec.order_id.requisition_id:
super(PurchaseOrderLine, self)._compute_account_analytic_id()
def _compute_analytic_tag_ids(self):
for rec in self:
if not rec.order_id.requisition_id:
super(PurchaseOrderLine, self)._compute_analytic_tag_ids()
@api.onchange('product_qty', 'product_uom')
def _onchange_quantity(self):
res = super(PurchaseOrderLine, self)._onchange_quantity()

View File

@@ -174,8 +174,8 @@ class PurchaseRequisitionLine(models.Model):
qty_ordered = fields.Float(compute='_compute_ordered_qty', string='Ordered Quantities')
requisition_id = fields.Many2one('purchase.requisition', required=True, string='Purchase Agreement', ondelete='cascade')
company_id = fields.Many2one('res.company', related='requisition_id.company_id', string='Company', store=True, readonly=True, default= lambda self: self.env.company)
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account', store=True, compute='_compute_account_analytic_id', readonly=False)
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags', store=True, compute='_compute_analytic_tag_ids', readonly=False)
schedule_date = fields.Date(string='Scheduled Date')
supplier_info_ids = fields.One2many('product.supplierinfo', 'purchase_requisition_line_id')
@@ -239,6 +239,30 @@ class PurchaseRequisitionLine(models.Model):
else:
line.qty_ordered = 0
@api.depends('product_id', 'schedule_date')
def _compute_account_analytic_id(self):
for line in self:
default_analytic_account = line.env['account.analytic.default'].sudo().account_get(
product_id=line.product_id.id,
partner_id=line.requisition_id.vendor_id.id,
user_id=line.env.uid,
date=line.schedule_date,
company_id=line.company_id.id,
)
line.account_analytic_id = default_analytic_account.analytic_id
@api.depends('product_id', 'schedule_date')
def _compute_analytic_tag_ids(self):
for line in self:
default_analytic_account = line.env['account.analytic.default'].sudo().account_get(
product_id=line.product_id.id,
partner_id=line.requisition_id.vendor_id.id,
user_id=line.env.uid,
date=line.schedule_date,
company_id=line.company_id.id,
)
line.analytic_tag_ids = default_analytic_account.analytic_tag_ids
@api.onchange('product_id')
def _onchange_product_id(self):
if self.product_id:

View File

@@ -2,6 +2,7 @@
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra.addons.purchase_requisition.tests.common import TestPurchaseRequisitionCommon
from flectra.tests import Form
class TestPurchaseRequisition(TestPurchaseRequisitionCommon):
@@ -113,3 +114,22 @@ class TestPurchaseRequisition(TestPurchaseRequisitionCommon):
('name', '=', vendor.id)
]) - supplier_info
self.assertEqual(new_si.purchase_requisition_id, requisition_blanket, 'the blanket order is not linked to the supplier info')
def test_07_purchase_requisition(self):
"""
Check that the analytic account and the account tag defined in the purchase requisition line
is used in the purchase order line when creating a PO.
"""
analytic_account = self.env['account.analytic.account'].create({'name': 'test_analytic_account'})
analytic_tag = self.env['account.analytic.tag'].create({'name': 'test_analytic_tag'})
self.assertEqual(len(self.requisition1.line_ids), 1)
self.requisition1.line_ids[0].write({
'account_analytic_id': analytic_account,
'analytic_tag_ids': analytic_tag,
})
# Create purchase order from purchase requisition
po_form = Form(self.env['purchase.order'].with_context(default_requisition_id=self.requisition1.id))
po_form.partner_id = self.res_partner_1
po = po_form.save()
self.assertEqual(po.order_line.account_analytic_id.id, analytic_account.id, 'The analytic account defined in the purchase requisition line must be the same as the one from the purchase order line.')
self.assertEqual(po.order_line.analytic_tag_ids.id, analytic_tag.id, 'The analytic account tag defined in the purchase requisition line must be the same as the one from the purchase order line.')

View File

@@ -306,6 +306,7 @@ class PurchaseOrderLine(models.Model):
elif (
move.location_dest_id.usage == "internal"
and move.location_id.usage != "supplier"
and move.warehouse_id
and move.location_dest_id
not in self.env["stock.location"].search(
[("id", "child_of", move.warehouse_id.view_location_id.id)]

View File

@@ -188,14 +188,12 @@ class SaleOrder(models.Model):
discount_amount -= discount_line_amount_price * lines_total / lines_price
tax_name = ""
if len(tax_ids) == 1:
tax_name = " - " + _("On product with following tax: ") + ', '.join(tax_ids.mapped('name'))
elif len(tax_ids) > 1:
tax_name = " - " + _("On product with following taxes: ") + ', '.join(tax_ids.mapped('name'))
reward_lines[tax_ids] = {
'name': _("Discount: ") + program.name + tax_name,
'name': _(
"Discount: %(program)s - On product with following taxes: %(taxes)s",
program=program.name,
taxes=", ".join(tax_ids.mapped('name')),
),
'product_id': program.discount_line_product_id.id,
'price_unit': -discount_line_amount_price,
'product_uom_qty': 1.0,

View File

@@ -9,12 +9,13 @@
</div>
</xpath>
<xpath expr="//div[@id='informations']" position="inside">
<t t-if="sale_order.picking_ids">
<t t-set="delivery_orders" t-value="sale_order.picking_ids.filtered(lambda picking: picking.picking_type_id.code == 'outgoing')"/>
<t t-if="delivery_orders">
<div>
<strong>Delivery Orders</strong>
</div>
<div>
<t t-foreach="sale_order.picking_ids.filtered(lambda picking: picking.picking_type_id.code != 'internal')" t-as="i">
<t t-foreach="delivery_orders" t-as="i">
<t t-set="delivery_report_url" t-value="'/my/picking/pdf/%s?%s' % (i.id, keep_query())"/>
<div class="d-flex flex-wrap align-items-center justify-content-between o_sale_stock_picking">
<div>

View File

@@ -537,7 +537,10 @@ var dom = {
return size;
},
/**
* @param {HTMLElement} el - the element to stroll to
* @param {HTMLElement} el - the element to stroll to (limitation: if the
* element is using a fixed position, this function cannot work except
* if is the header (with the "top" id) or the footer (with the
* "bottom" id) for which exceptions have been made)
* @param {number} [options] - same as animate of jQuery
* @param {number} [options.extraOffset=0]
* extra offset to add on top of the automatic one (the automatic one
@@ -554,13 +557,22 @@ var dom = {
const isTopScroll = $scrollable.is($topLevelScrollable);
function _computeScrollTop() {
if (el.id === 'top') {
return 0;
}
if (el.id === 'bottom') {
return $scrollable[0].scrollHeight - $scrollable[0].clientHeight;
}
let offsetTop = $el.offset().top;
if (el.classList.contains('d-none')) {
el.classList.remove('d-none');
offsetTop = $el.offset().top;
el.classList.add('d-none');
}
const elPosition = $scrollable[0].scrollTop + (offsetTop - $scrollable.offset().top);
const isDocScrollingEl = $scrollable.is(el.ownerDocument.scrollingElement);
const elPosition = offsetTop
- ($scrollable.offset().top - (isDocScrollingEl ? 0 : $scrollable[0].scrollTop));
let offset = options.forcedOffset;
if (offset === undefined) {
offset = (isTopScroll ? dom.scrollFixedOffset() : 0) + (options.extraOffset || 0);

View File

@@ -123,11 +123,23 @@ $.fn.extend({
});
},
/**
* @todo Should really be converted to no jQuery and probably even removed
* from jQuery utilities in master
* @return {jQuery}
*/
closestScrollable() {
const document = this.length ? this[0].ownerDocument : window.document;
let $el = this;
while ($el[0] !== document.scrollingElement) {
if (!$el.length || $el[0] instanceof Document) {
// Ensure that $().closestScrollable() -> $() and handle the
// case of elements not attached to the DOM.
// Also, .parent() used to loop through ancestors can
// theoretically reach the document if nothing up to the HTML
// included is not scrollable.
return $();
}
if ($el.isScrollable()) {
return $el;
}
@@ -194,9 +206,13 @@ $.fn.extend({
* @returns {boolean}
*/
isScrollable() {
if (!this.length) {
return false;
}
const overflow = this.css('overflow-y');
const el = this[0];
return overflow === 'auto' || overflow === 'scroll'
|| (overflow === 'visible' && this === document.scrollingElement);
|| (overflow === 'visible' && el === el.ownerDocument.scrollingElement);
},
});

View File

@@ -4624,9 +4624,13 @@ var BasicModel = AbstractModel.extend({
});
value = choice ? choice[1] : false;
}
// When group_by_no_leaf key is present FIELD_ID_count doesn't exist
// we have to get the count from `__count` instead
// see _read_group_raw in models.py
const countKey = rawGroupBy + '_count';
var newGroup = self._makeDataPoint({
modelName: list.model,
count: group[rawGroupBy + '_count'],
count: countKey in group ? group[countKey] : group.__count,
domain: group.__domain,
context: list.context,
fields: list.fields,

View File

@@ -1547,7 +1547,8 @@ var MockServer = Class.extend({
// compute count key to match dumb server logic...
var countKey;
if (kwargs.lazy) {
const groupByNoLeaf = kwargs.context ? 'group_by_no_leaf' in kwargs.context : false;
if (kwargs.lazy && (groupBy.length >= 2 || !groupByNoLeaf)) {
countKey = groupBy[0].split(':')[0] + "_count";
} else {
countKey = "__count";

View File

@@ -4453,6 +4453,30 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('list with group_by_no_leaf and group by', async function (assert) {
assert.expect(4);
const list = await createView({
View: ListView,
model: 'foo',
data: this.data,
arch: '<tree expand="1"><field name="foo"/></tree>',
groupBy: ['currency_id'],
context: { group_by_no_leaf: true },
});
const groups = list.el.querySelectorAll(".o_group_name");
const groupsRecords = [...list.el.querySelectorAll(".o_data_row .o_data_cell")];
assert.strictEqual(groups.length, 2, "There should be 2 groups");
assert.strictEqual(groups[0].textContent, "EUR (1)", "First group should have 1 record");
assert.strictEqual(groups[1].textContent, "USD (3)", "Second group should have 3 records");
assert.deepEqual(
groupsRecords.map(groupEl => groupEl.textContent),
["yop", "blip", "gnap", "blip"],
"Groups should contains correct records");
list.destroy();
});
QUnit.test("non empty list with sample data", async function (assert) {
assert.expect(6);

View File

@@ -1175,6 +1175,9 @@ var SnippetsMenu = Widget.extend({
// Hide the active overlay when scrolling.
// Show it again and recompute all the overlays after the scroll.
this.$scrollingElement = $().getScrollingElement();
this.$scrollingTarget = this.$scrollingElement.is(this.ownerDocument.scrollingElement)
? $(this.ownerDocument.defaultView)
: this.$scrollingElement;
this._onScrollingElementScroll = _.throttle(() => {
for (const editor of this.snippetEditors) {
editor.toggleOverlayVisibility(false);
@@ -1192,7 +1195,7 @@ var SnippetsMenu = Widget.extend({
// Setting capture to true allows to take advantage of event bubbling
// for events that otherwise dont support it. (e.g. useful when
// scrolling a modal)
this.$scrollingElement[0].addEventListener('scroll', this._onScrollingElementScroll, {capture: true});
this.$scrollingTarget[0].addEventListener('scroll', this._onScrollingElementScroll, {capture: true});
// Auto-selects text elements with a specific class and remove this
// on text changes
@@ -1257,7 +1260,10 @@ var SnippetsMenu = Widget.extend({
this.$snippetEditorArea.remove();
this.$window.off('.snippets_menu');
this.$document.off('.snippets_menu');
this.$scrollingElement[0].removeEventListener('scroll', this._onScrollingElementScroll, {capture: true});
if (this.$scrollingTarget) {
this.$scrollingTarget[0].removeEventListener('scroll', this._onScrollingElementScroll, {capture: true});
}
}
core.bus.off('deactivate_snippet', this, this._onDeactivateSnippet);
delete this.cacheSnippetTemplate[this.options.snippets];

View File

@@ -284,7 +284,7 @@ class Website(Home):
for name, url, mod in current_website.get_suggested_controllers():
if needle.lower() in name.lower() or needle.lower() in url.lower():
module_sudo = mod and request.env.ref('base.module_%s' % mod, False).sudo()
icon = mod and "<img src='%s' width='24px' class='mr-2 rounded' /> " % (module_sudo and module_sudo.icon or mod) or ''
icon = mod and "<img src='%s' width='24px' height='24px' class='mr-2 rounded' /> " % (module_sudo and module_sudo.icon or mod) or ''
suggested_controllers.append({
'value': url,
'label': '%s%s (%s)' % (icon, url, name),

View File

@@ -41,12 +41,12 @@ const UrlPickerUserValueWidget = InputUserValueWidget.extend({
this.inputEl.classList.add('text-left');
const options = {
position: {
collision: 'flip fit',
collision: 'flip flipfit',
},
classes: {
"ui-autocomplete": 'o_website_ui_autocomplete'
},
}
};
wUtils.autocompleteWithPages(this, $(this.inputEl), options);
},

View File

@@ -579,7 +579,7 @@ class WebsiteSale(http.Controller):
# prevent name change if invoices exist
if data.get('partner_id'):
partner = request.env['res.partner'].browse(int(data['partner_id']))
if partner.exists() and not partner.sudo().can_edit_vat() and 'name' in data and (data['name'] or False) != (partner.name or False):
if partner.exists() and partner.name and not partner.sudo().can_edit_vat() and 'name' in data and (data['name'] or False) != (partner.name or False):
error['name'] = 'error'
error_message.append(_('Changing your name is not allowed once invoices have been issued for your account. Please contact us directly for this operation.'))