mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2025-02-25 18:55:21 -06:00
[PATCH] Upstream patch - 04112022
This commit is contained in:
@@ -221,7 +221,7 @@ class AccountPayment(models.Model):
|
||||
def prepare_vals(invoice, partials):
|
||||
number = ' - '.join([invoice.name, invoice.ref] if invoice.ref else [invoice.name])
|
||||
|
||||
if invoice.is_outbound():
|
||||
if invoice.is_outbound() or invoice.move_type == 'entry':
|
||||
invoice_sign = 1
|
||||
partial_field = 'debit_amount_currency'
|
||||
else:
|
||||
@@ -242,10 +242,11 @@ class AccountPayment(models.Model):
|
||||
'currency': invoice.currency_id,
|
||||
}
|
||||
|
||||
# Decode the reconciliation to keep only invoices.
|
||||
# Decode the reconciliation to keep only bills.
|
||||
term_lines = self.line_ids.filtered(lambda line: line.account_id.internal_type in ('receivable', 'payable'))
|
||||
invoices = (term_lines.matched_debit_ids.debit_move_id.move_id + term_lines.matched_credit_ids.credit_move_id.move_id)\
|
||||
.filtered(lambda x: x.is_outbound())
|
||||
invoices = (term_lines.matched_debit_ids.debit_move_id.move_id + term_lines.matched_credit_ids.credit_move_id.move_id) \
|
||||
.filtered(lambda move: move.is_outbound() or move.move_type == 'entry')
|
||||
|
||||
invoices = invoices.sorted(lambda x: x.invoice_date_due or x.date)
|
||||
|
||||
# Group partials by invoices.
|
||||
@@ -272,7 +273,7 @@ class AccountPayment(models.Model):
|
||||
else:
|
||||
stub_lines = [prepare_vals(invoice, partials)
|
||||
for invoice, partials in invoice_map.items()
|
||||
if invoice.move_type == 'in_invoice']
|
||||
if invoice.move_type in ('in_invoice', 'entry')]
|
||||
|
||||
# Crop the stub lines or split them on multiple pages
|
||||
if not self.company_id.account_check_printing_multi_stub:
|
||||
|
||||
@@ -490,9 +490,13 @@ class AccountEdiFormat(models.Model):
|
||||
res = False
|
||||
try:
|
||||
if file_data['type'] == 'xml':
|
||||
res = edi_format._update_invoice_from_xml_tree(file_data['filename'], file_data['xml_tree'], invoice)
|
||||
res = edi_format\
|
||||
.with_context(default_move_type=invoice.move_type)\
|
||||
._update_invoice_from_xml_tree(file_data['filename'], file_data['xml_tree'], invoice)
|
||||
elif file_data['type'] == 'pdf':
|
||||
res = edi_format._update_invoice_from_pdf_reader(file_data['filename'], file_data['pdf_reader'], invoice)
|
||||
res = edi_format\
|
||||
.with_context(default_move_type=invoice.move_type)\
|
||||
._update_invoice_from_pdf_reader(file_data['filename'], file_data['pdf_reader'], invoice)
|
||||
file_data['pdf_reader'].stream.close()
|
||||
else: # file_data['type'] == 'binary'
|
||||
res = edi_format._update_invoice_from_binary(file_data['filename'], file_data['content'], file_data['extension'], invoice)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
from flectra.addons.hr_expense.tests.common import TestExpenseCommon
|
||||
from flectra.tests import tagged, Form
|
||||
from flectra.tools.misc import formatLang
|
||||
from flectra import fields
|
||||
from flectra.exceptions import UserError
|
||||
|
||||
@@ -296,3 +297,56 @@ class TestExpenses(TestExpenseCommon):
|
||||
move = self.env['account.payment'].browse(action['res_id']).move_id
|
||||
move.button_cancel()
|
||||
self.assertEqual(sheet.state, 'cancel', 'Sheet state must be cancel when the payment linked to that sheet is canceled')
|
||||
|
||||
def test_print_expense_check(self):
|
||||
"""
|
||||
Test the check content when printing a check
|
||||
that comes from an expense
|
||||
"""
|
||||
sheet = self.env['hr.expense.sheet'].create({
|
||||
'company_id': self.env.company.id,
|
||||
'employee_id': self.expense_employee.id,
|
||||
'name': 'test sheet',
|
||||
'expense_line_ids': [
|
||||
(0, 0, {
|
||||
'name': 'expense_1',
|
||||
'date': '2016-01-01',
|
||||
'product_id': self.product_a.id,
|
||||
'unit_amount': 10.0,
|
||||
'employee_id': self.expense_employee.id,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'expense_2',
|
||||
'date': '2016-01-01',
|
||||
'product_id': self.product_a.id,
|
||||
'unit_amount': 1.0,
|
||||
'employee_id': self.expense_employee.id,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
#actions
|
||||
sheet.action_submit_sheet()
|
||||
sheet.approve_expense_sheets()
|
||||
sheet.action_sheet_move_create()
|
||||
action_data = sheet.action_register_payment()
|
||||
payment_method = self.env.company.bank_journal_ids.outbound_payment_method_ids.filtered(lambda m: m.code == 'check_printing')
|
||||
with Form(self.env[action_data['res_model']].with_context(action_data['context'])) as wiz_form:
|
||||
wiz_form.payment_method_id = payment_method
|
||||
wizard = wiz_form.save()
|
||||
action = wizard.action_create_payments()
|
||||
self.assertEqual(sheet.state, 'done', 'all account.move.line linked to expenses must be reconciled after payment')
|
||||
|
||||
payment = self.env[action['res_model']].browse(action['res_id'])
|
||||
pages = payment._check_get_pages()
|
||||
stub_line = pages[0]['stub_lines'][:1]
|
||||
self.assertTrue(stub_line)
|
||||
move = self.env[action_data['context']['active_model']].browse(action_data['context']['active_ids'])
|
||||
self.assertDictEqual(stub_line[0], {
|
||||
'due_date': '',
|
||||
'number': ' - '.join([move.name, move.ref] if move.ref else [move.name]),
|
||||
'amount_total': formatLang(self.env, 11.0, currency_obj=self.env.company.currency_id),
|
||||
'amount_residual': '-',
|
||||
'amount_paid': formatLang(self.env, 11.0, currency_obj=self.env.company.currency_id),
|
||||
'currency': self.env.company.currency_id
|
||||
})
|
||||
|
||||
@@ -229,6 +229,9 @@ class AccountEdiFormat(models.Model):
|
||||
if (not partner.country_id or partner.country_id.code == 'ES') and partner.vat:
|
||||
# ES partner with VAT.
|
||||
partner_info['NIF'] = partner.vat[2:] if partner.vat.startswith('ES') else partner.vat
|
||||
if self.env.context.get('error_1117'):
|
||||
partner_info['IDOtro'] = {'IDType': '07', 'ID': IDOtro_ID}
|
||||
|
||||
elif partner.country_id.code in eu_country_codes and partner.vat:
|
||||
# European partner.
|
||||
partner_info['IDOtro'] = {'IDType': '02', 'ID': IDOtro_ID}
|
||||
@@ -562,6 +565,8 @@ class AccountEdiFormat(models.Model):
|
||||
results[inv] = {'success': True}
|
||||
inv.message_post(body=_("We saw that this invoice was sent correctly before, but we did not treat "
|
||||
"the response. Make sure it is not because of a wrong configuration."))
|
||||
elif respl.CodigoErrorRegistro == 1117 and not self.env.context.get('error_1117'):
|
||||
return self.with_context(error_1117=True)._post_invoice_edi(invoices)
|
||||
else:
|
||||
results[inv] = {
|
||||
'error': _("[%s] %s", respl.CodigoErrorRegistro, respl.DescripcionErrorRegistro),
|
||||
|
||||
@@ -54,7 +54,7 @@ class TestAccountFrFec(AccountTestInvoicingCommon):
|
||||
"INV|Customer Invoices|INV/2021/05/0001|20210502|701100|Ventes de produits finis (ou groupe) A|||-|20210502|Hello Darkness|0,00| 000000000001437,12|||20210502|-000000000001437,12|EUR\r\n"
|
||||
"INV|Customer Invoices|INV/2021/05/0001|20210502|701100|Ventes de produits finis (ou groupe) A|||-|20210502|my old friend|0,00| 000000000001676,64|||20210502|-000000000001676,64|EUR\r\n"
|
||||
"INV|Customer Invoices|INV/2021/05/0001|20210502|701100|Ventes de produits finis (ou groupe) A|||-|20210502|/|0,00| 000000000003353,28|||20210502|-000000000003353,28|EUR\r\n"
|
||||
"INV|Customer Invoices|INV/2021/05/0001|20210502|445710|TVA collectée|||-|20210502|TVA collectée (vente) 20,0%|0,00| 000000000001293,41|||20210502|-000000000001293,41|EUR\r\n"
|
||||
"INV|Customer Invoices|INV/2021/05/0001|20210502|445710|TVA collectée|||-|20210502|TVA 20,0%|0,00| 000000000001293,41|||20210502|-000000000001293,41|EUR\r\n"
|
||||
f"INV|Customer Invoices|INV/2021/05/0001|20210502|411100|Clients - Ventes de biens ou de prestations de services|{self.partner_a.id}|partner_a|-|20210502|INV/2021/05/0001| 000000000007760,45|0,00|||20210502| 000000000007760,45|EUR"
|
||||
)
|
||||
content = base64.b64decode(self.wizard.fec_data).decode()
|
||||
|
||||
@@ -279,7 +279,7 @@ class AccountMove(models.Model):
|
||||
'document_total': document_total,
|
||||
'representative': company.l10n_it_tax_representative_partner_id,
|
||||
'codice_destinatario': codice_destinatario,
|
||||
'regime_fiscale': company.l10n_it_tax_system if not is_self_invoice else 'RF01',
|
||||
'regime_fiscale': company.l10n_it_tax_system if not is_self_invoice else 'RF18',
|
||||
'is_self_invoice': is_self_invoice,
|
||||
'partner_bank': self.partner_bank_id,
|
||||
'format_date': format_date,
|
||||
|
||||
@@ -2,4 +2,4 @@ id,name
|
||||
tax_group_0,TAX 0%
|
||||
tax_group_gst_15,GST 15%
|
||||
tax_group_15,TAX 15%
|
||||
tax_group_100000000,GST 100000000%
|
||||
tax_group_100000000,GST 100%
|
||||
|
||||
|
@@ -259,21 +259,12 @@
|
||||
</record>
|
||||
<record id="nz_tax_purchase_gst_only" model="account.tax.template">
|
||||
<field name="chart_template_id" ref="l10n_nz_chart_template"/>
|
||||
<field name="name">GST Only – Imports</field>
|
||||
<field name="name">GST Only - Imports</field>
|
||||
<field name="sequence">5</field>
|
||||
<field name="description">GST Only on Imports</field>
|
||||
<field name="type_tax_use">purchase</field>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="amount">100000000000</field>
|
||||
<!--
|
||||
The tax percentage is so high because on imported goods we
|
||||
needed to link the tax line acknowledgment (not to be paid)
|
||||
on the customer invoice and what need to actually be
|
||||
paid from another invoice given by a clearance house
|
||||
(i.e. customs)
|
||||
For more info see the complete discussion below
|
||||
https://github.com/flectra/flectra/pull/48700#issuecomment-607586417
|
||||
-->
|
||||
<field name="amount_type">division</field>
|
||||
<field name="amount">100</field>
|
||||
<field name="price_include">TRUE</field>
|
||||
<field name="tax_group_id" ref="tax_group_100000000"/>
|
||||
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
|
||||
|
||||
@@ -12,6 +12,7 @@ from flectra.osv import expression
|
||||
from flectra.tools import ormcache, formataddr
|
||||
from flectra.exceptions import AccessError
|
||||
from flectra.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
|
||||
from flectra.tools import html_escape
|
||||
|
||||
MODERATION_FIELDS = ['moderation', 'moderator_ids', 'moderation_ids', 'moderation_notify', 'moderation_notify_msg', 'moderation_guidelines', 'moderation_guidelines_msg']
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -1114,13 +1115,13 @@ class Channel(models.Model):
|
||||
def _execute_command_help(self, **kwargs):
|
||||
partner = self.env.user.partner_id
|
||||
if self.channel_type == 'channel':
|
||||
msg = _("You are in channel <b>#%s</b>.", self.name)
|
||||
msg = _("You are in channel <b>#%s</b>.", html_escape(self.name))
|
||||
if self.public == 'private':
|
||||
msg += _(" This channel is private. People must be invited to join it.")
|
||||
else:
|
||||
all_channel_partners = self.env['mail.channel.partner'].with_context(active_test=False)
|
||||
channel_partners = all_channel_partners.search([('partner_id', '!=', partner.id), ('channel_id', '=', self.id)])
|
||||
msg = _("You are in a private conversation with <b>@%s</b>.", channel_partners[0].partner_id.name if channel_partners else _('Anonymous'))
|
||||
msg = _("You are in a private conversation with <b>@%s</b>.", _(" @").join(html_escape(member.partner_id.name) for member in channel_partners) if channel_partners else _('Anonymous'))
|
||||
msg += _("""<br><br>
|
||||
Type <b>@username</b> to mention someone, and grab his attention.<br>
|
||||
Type <b>#channel</b> to mention a channel.<br>
|
||||
|
||||
@@ -138,26 +138,13 @@ class ComposerSuggestedRecipient extends Component {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} record the newly-created record
|
||||
*/
|
||||
_onDialogSaved(record) {
|
||||
_onDialogSaved() {
|
||||
const thread = this.suggestedRecipientInfo && this.suggestedRecipientInfo.thread;
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
thread.fetchAndUpdateSuggestedRecipients();
|
||||
if (!this.suggestedRecipientInfo.partner) {
|
||||
this.env.services.notification.notify({
|
||||
title: this.env._t('Invalid Partner'),
|
||||
message: this.env._t('The information you have entered does not match the existing contact information for this record. The partner was not created.'),
|
||||
type: 'warning'
|
||||
});
|
||||
this.env.services.rpc({
|
||||
args: [record.res_id],
|
||||
model: 'res.partner',
|
||||
method: 'unlink',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ class MrpBom(models.Model):
|
||||
if not products:
|
||||
return bom_by_product
|
||||
product_templates = products.mapped('product_tmpl_id')
|
||||
domain = ['|', ('product_id', 'in', products.ids), '&', ('product_id', '=', False), ('product_tmpl_id', 'in', product_templates.ids)]
|
||||
domain = ['|', ('product_id', 'in', products.ids), '&', ('product_id', '=', False), ('product_tmpl_id', 'in', product_templates.ids), ('active', '=', True)]
|
||||
if picking_type:
|
||||
domain += ['|', ('picking_type_id', '=', picking_type.id), ('picking_type_id', '=', False)]
|
||||
if company_id or self.env.context.get('company_id'):
|
||||
@@ -453,11 +453,11 @@ class MrpBomLine(models.Model):
|
||||
self.ensure_one()
|
||||
if product._name == 'product.template':
|
||||
return False
|
||||
if self.bom_product_template_attribute_value_ids:
|
||||
for ptal, iter_ptav in groupby(self.bom_product_template_attribute_value_ids.sorted('attribute_line_id'), lambda ptav: ptav.attribute_line_id):
|
||||
if not any(ptav in product.product_template_attribute_value_ids for ptav in iter_ptav):
|
||||
return True
|
||||
return False
|
||||
# The intersection of the values of the product and those of the line satisfy:
|
||||
# * the number of items equals the number of attributes (since a product cannot
|
||||
# have multiple values for the same attribute),
|
||||
# * the attributes are a subset of the attributes of the line.
|
||||
return len(product.product_template_attribute_value_ids & self.bom_product_template_attribute_value_ids) != len(self.bom_product_template_attribute_value_ids.attribute_id)
|
||||
|
||||
def action_see_attachments(self):
|
||||
domain = [
|
||||
|
||||
@@ -593,6 +593,8 @@ class MrpWorkorder(models.Model):
|
||||
else:
|
||||
if self.date_planned_start > start_date:
|
||||
vals['date_planned_start'] = start_date
|
||||
if self.duration_expected:
|
||||
vals['date_planned_finished'] = self._calculate_date_planned_finished(start_date)
|
||||
if self.date_planned_finished and self.date_planned_finished < start_date:
|
||||
vals['date_planned_finished'] = start_date
|
||||
return self.with_context(bypass_duration_calculation=True).write(vals)
|
||||
|
||||
@@ -25,6 +25,8 @@ class TestStockValuation(TransactionCase):
|
||||
'name': 'Large Desk',
|
||||
'standard_price': 1299.0,
|
||||
'list_price': 1799.0,
|
||||
# Ignore tax calculations for these tests.
|
||||
'supplier_taxes_id': False,
|
||||
'type': 'product',
|
||||
})
|
||||
Account = self.env['account.account']
|
||||
|
||||
@@ -406,24 +406,43 @@ Dialog.alert = function (owner, message, options) {
|
||||
|
||||
// static method to open simple confirm dialog
|
||||
Dialog.confirm = function (owner, message, options) {
|
||||
let clickProm;
|
||||
/**
|
||||
* Creates an improved callback from the given callback value at the given
|
||||
* key from the parent function's options parameter. This is improved to:
|
||||
*
|
||||
* - Prevent calling given callbacks once one has been called.
|
||||
*
|
||||
* - Re-allow calling callbacks once a previous callback call's returned
|
||||
* Promise is rejected.
|
||||
*/
|
||||
let isBlocked = false;
|
||||
function makeCallback(key) {
|
||||
const callback = options && options[key];
|
||||
return function () {
|
||||
if (isBlocked) {
|
||||
// Do not (re)call any callback and return a rejected Promise
|
||||
// to prevent closing the Dialog.
|
||||
return Promise.reject();
|
||||
}
|
||||
isBlocked = true;
|
||||
const callbackRes = callback && callback.apply(this, arguments);
|
||||
Promise.resolve(callbackRes).guardedCatch(() => {
|
||||
isBlocked = false;
|
||||
});
|
||||
return callbackRes;
|
||||
};
|
||||
}
|
||||
var buttons = [
|
||||
{
|
||||
text: _t("Ok"),
|
||||
classes: 'btn-primary',
|
||||
close: true,
|
||||
click: options && options.confirm_callback && (() => {
|
||||
clickProm = clickProm || options.confirm_callback() || Promise.resolve();
|
||||
return clickProm;
|
||||
}),
|
||||
click: makeCallback('confirm_callback'),
|
||||
},
|
||||
{
|
||||
text: _t("Cancel"),
|
||||
close: true,
|
||||
click: options && options.cancel_callback && (() => {
|
||||
clickProm = clickProm || options.cancel_callback() || Promise.resolve();
|
||||
return clickProm;
|
||||
}),
|
||||
click: makeCallback('cancel_callback'),
|
||||
}
|
||||
];
|
||||
return new Dialog(owner, _.extend({
|
||||
|
||||
@@ -104,7 +104,7 @@ QUnit.module('core', {}, function () {
|
||||
});
|
||||
|
||||
QUnit.test("click twice on 'Ok' button of a confirm dialog", async function (assert) {
|
||||
assert.expect(3);
|
||||
assert.expect(5);
|
||||
|
||||
var testPromise = testUtils.makeTestPromise();
|
||||
var parent = await createEmptyParent();
|
||||
@@ -121,7 +121,12 @@ QUnit.module('core', {}, function () {
|
||||
|
||||
await testUtils.dom.click($('.modal[role="dialog"] .btn-primary'));
|
||||
await testUtils.dom.click($('.modal[role="dialog"] .btn-primary'));
|
||||
await testUtils.nextTick();
|
||||
assert.verifySteps(['confirm']);
|
||||
assert.ok($('.modal[role="dialog"]').hasClass('show'), "Should still be opened");
|
||||
testPromise.resolve();
|
||||
await testUtils.nextTick();
|
||||
assert.notOk($('.modal[role="dialog"]').hasClass('show'), "Should now be closed");
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
@@ -150,6 +155,107 @@ QUnit.module('core', {}, function () {
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("click on 'Cancel' and then 'Ok' in a confirm dialog (no cancel callback)", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var parent = await createEmptyParent();
|
||||
var options = {
|
||||
confirm_callback: () => {
|
||||
throw new Error("should not be called");
|
||||
},
|
||||
// Cannot add a step in cancel_callback, that's the point of this
|
||||
// test, we'll rely on checking the Dialog is opened then closed
|
||||
// without a crash.
|
||||
};
|
||||
Dialog.confirm(parent, "", options);
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.ok($('.modal[role="dialog"]').hasClass('show'));
|
||||
testUtils.dom.click($('.modal[role="dialog"] footer button:not(.btn-primary)'));
|
||||
testUtils.dom.click($('.modal[role="dialog"] footer .btn-primary'));
|
||||
await testUtils.nextTick();
|
||||
assert.notOk($('.modal[role="dialog"]').hasClass('show'));
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("Confirm dialog callbacks properly handle rejections", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
var parent = await createEmptyParent();
|
||||
var options = {
|
||||
confirm_callback: () => {
|
||||
assert.step("confirm");
|
||||
return Promise.reject();
|
||||
},
|
||||
cancel_callback: () => {
|
||||
assert.step("cancel");
|
||||
return $.Deferred().reject(); // Test jquery deferred too
|
||||
}
|
||||
};
|
||||
Dialog.confirm(parent, "", options);
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.verifySteps([]);
|
||||
testUtils.dom.click($('.modal[role="dialog"] footer button:not(.btn-primary)'));
|
||||
await testUtils.nextTick();
|
||||
testUtils.dom.click($('.modal[role="dialog"] footer .btn-primary'));
|
||||
await testUtils.nextTick();
|
||||
testUtils.dom.click($('.modal[role="dialog"] footer button:not(.btn-primary)'));
|
||||
assert.verifySteps(['cancel', 'confirm', 'cancel']);
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("Properly can rely on the this in confirm and cancel callbacks of confirm dialog", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
let dialogInstance = null;
|
||||
var parent = await createEmptyParent();
|
||||
var options = {
|
||||
confirm_callback: function () {
|
||||
assert.equal(this, dialogInstance, "'this' is properly a reference to the dialog instance");
|
||||
return Promise.reject();
|
||||
},
|
||||
cancel_callback: function () {
|
||||
assert.equal(this, dialogInstance, "'this' is properly a reference to the dialog instance");
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
dialogInstance = Dialog.confirm(parent, "", options);
|
||||
await testUtils.nextTick();
|
||||
|
||||
testUtils.dom.click($('.modal[role="dialog"] footer button:not(.btn-primary)'));
|
||||
await testUtils.nextTick();
|
||||
testUtils.dom.click($('.modal[role="dialog"] footer .btn-primary'));
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("Confirm dialog callbacks can return anything without crash", async function (assert) {
|
||||
assert.expect(3);
|
||||
// Note that this test could be removed in master if the related code
|
||||
// is reworked. This only prevents a stable fix to break this again by
|
||||
// relying on the fact what is returned by those callbacks are undefined
|
||||
// or promises.
|
||||
|
||||
var parent = await createEmptyParent();
|
||||
var options = {
|
||||
confirm_callback: () => {
|
||||
assert.step("confirm");
|
||||
return 5;
|
||||
},
|
||||
};
|
||||
Dialog.confirm(parent, "", options);
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.verifySteps([]);
|
||||
testUtils.dom.click($('.modal[role="dialog"] footer .btn-primary'));
|
||||
assert.verifySteps(['confirm']);
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("Closing alert dialog without using buttons calls confirm callback", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
|
||||
@@ -1119,7 +1119,7 @@ function _clonePage(pageId) {
|
||||
method: 'clone_page',
|
||||
args: [
|
||||
pageId,
|
||||
this.$content.find('#page_name').val(),
|
||||
this.$('#page_name').val(),
|
||||
],
|
||||
}).then(function (path) {
|
||||
window.location.href = path;
|
||||
|
||||
@@ -420,9 +420,10 @@ var MetaTitleDescription = Widget.extend({
|
||||
_seoNameChanged: function () {
|
||||
var self = this;
|
||||
// don't use _, because we need to keep trailing whitespace during edition
|
||||
const slugified = this.$seoName.val().toString().toLowerCase()
|
||||
const slugified = this.$seoName.val().toString().trim().normalize('NFKD').toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(/[^\w\-]+/g, '-') // Remove all non-word chars
|
||||
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
|
||||
.replace(/\-$/g, '') // Remove trailing -
|
||||
.replace(/\-\-+/g, '-'); // Replace multiple - with single -
|
||||
this.$seoName.val(slugified);
|
||||
self._renderPreview();
|
||||
|
||||
Reference in New Issue
Block a user