[PATCH] Upstream patch - 20102022

This commit is contained in:
Parthiv Patel
2022-10-20 08:34:45 +00:00
parent c72fddc652
commit bfc0294fb0
25 changed files with 291 additions and 96 deletions
+2 -2
View File
@@ -5,7 +5,7 @@ from flectra.tools.translate import _
class AccountTaxTemplate(models.Model):
_inherit = 'account.tax.template'
l10n_de_datev_code = fields.Char(size=2)
l10n_de_datev_code = fields.Char(size=4)
def _get_tax_vals(self, company, tax_template_to_tax):
vals = super(AccountTaxTemplate, self)._get_tax_vals(company, tax_template_to_tax)
@@ -15,7 +15,7 @@ class AccountTaxTemplate(models.Model):
class AccountTax(models.Model):
_inherit = "account.tax"
l10n_de_datev_code = fields.Char(size=2, help="2 digits code use by Datev")
l10n_de_datev_code = fields.Char(size=4, help="4 digits code use by Datev")
class AccountMove(models.Model):
@@ -594,7 +594,7 @@
<field name="description">19% USt</field>
<field name="amount_type">percent</field>
<field name="amount">19</field>
<field name="l10n_de_datev_code">13</field>
<field name="l10n_de_datev_code">3</field>
<field name="type_tax_use">sale</field>
<field name="price_include">False</field>
<field name="active">True</field>
@@ -636,7 +636,7 @@
<field name="description">7% USt</field>
<field name="amount_type">percent</field>
<field name="amount">7</field>
<field name="l10n_de_datev_code">12</field>
<field name="l10n_de_datev_code">2</field>
<field name="type_tax_use">sale</field>
<field name="price_include">False</field>
<field name="active">True</field>
@@ -951,7 +951,7 @@
<field name="description">19% VSt</field>
<field name="amount_type">percent</field>
<field name="amount">19</field>
<field name="l10n_de_datev_code">19</field>
<field name="l10n_de_datev_code">9</field>
<field name="type_tax_use">purchase</field>
<field name="price_include">False</field>
<field name="active">True</field>
@@ -991,7 +991,7 @@
<field name="description">7% VSt</field>
<field name="amount_type">percent</field>
<field name="amount">7</field>
<field name="l10n_de_datev_code">18</field>
<field name="l10n_de_datev_code">8</field>
<field name="type_tax_use">purchase</field>
<field name="price_include">False</field>
<field name="active">True</field>
@@ -615,7 +615,7 @@
<field name="description">19% USt</field>
<field name="amount_type">percent</field>
<field name="amount">19</field>
<field name="l10n_de_datev_code">13</field>
<field name="l10n_de_datev_code">3</field>
<field name="type_tax_use">sale</field>
<field name="price_include">False</field>
<field name="active">True</field>
@@ -659,7 +659,7 @@
<field name="description">7% USt</field>
<field name="amount_type">percent</field>
<field name="amount">7</field>
<field name="l10n_de_datev_code">12</field>
<field name="l10n_de_datev_code">2</field>
<field name="type_tax_use">sale</field>
<field name="price_include">False</field>
<field name="active">True</field>
@@ -990,7 +990,7 @@
<field name="description">19% VSt</field>
<field name="amount_type">percent</field>
<field name="amount">19</field>
<field name="l10n_de_datev_code">19</field>
<field name="l10n_de_datev_code">9</field>
<field name="type_tax_use">purchase</field>
<field name="price_include">False</field>
<field name="active">True</field>
@@ -1032,7 +1032,7 @@
<field name="description">7% VSt</field>
<field name="amount_type">percent</field>
<field name="amount">7</field>
<field name="l10n_de_datev_code">18</field>
<field name="l10n_de_datev_code">8</field>
<field name="type_tax_use">purchase</field>
<field name="price_include">False</field>
<field name="active">True</field>
@@ -36,14 +36,10 @@ class AccountMove(models.Model):
('08', '08 Penyerahan yang PPN-nya Dibebaskan (Impor Barang Tertentu)'),
('09', '09 Penyerahan Aktiva ( Pasal 16D UU PPN )'),
], string='Kode Transaksi', help='Dua digit pertama nomor pajak',
readonly=True, states={'draft': [('readonly', False)]}, copy=False)
readonly=False, states={'posted': [('readonly', True)], 'cancel': [('readonly', True)]}, copy=False,
compute="_compute_kode_transaksi", store=True)
l10n_id_need_kode_transaksi = fields.Boolean(compute='_compute_need_kode_transaksi')
@api.onchange('partner_id')
def _onchange_partner_id(self):
self.l10n_id_kode_transaksi = self.partner_id.l10n_id_kode_transaksi
return super(AccountMove, self)._onchange_partner_id()
@api.onchange('l10n_id_tax_number')
def _onchange_l10n_id_tax_number(self):
for record in self:
@@ -55,6 +51,11 @@ class AccountMove(models.Model):
for record in self:
record.l10n_id_csv_created = bool(record.l10n_id_attachment_id)
@api.depends('partner_id')
def _compute_kode_transaksi(self):
for move in self:
move.l10n_id_kode_transaksi = move.partner_id.l10n_id_kode_transaksi
@api.depends('partner_id')
def _compute_need_kode_transaksi(self):
for move in self:
-4
View File
@@ -24,10 +24,6 @@
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_4d" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_5" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
+4 -1
View File
@@ -230,7 +230,10 @@ class MailController(http.Controller):
# ==============================================================================================
if res_id and isinstance(res_id, str):
res_id = int(res_id)
try:
res_id = int(res_id)
except ValueError:
res_id = False
return self._redirect_to_record(model, res_id, access_token, **kwargs)
@http.route('/mail/assign', type='http', auth='user', methods=['GET'])
+5 -5
View File
@@ -191,11 +191,11 @@ class ProductProduct(models.Model):
ratios_free_qty.append(component_res["free_qty"] / qty_per_kit)
if bom_sub_lines and ratios_virtual_available: # Guard against all cnsumable bom: at least one ratio should be present.
res[product.id] = {
'virtual_available': min(ratios_virtual_available) // 1,
'qty_available': min(ratios_qty_available) // 1,
'incoming_qty': min(ratios_incoming_qty) // 1,
'outgoing_qty': min(ratios_outgoing_qty) // 1,
'free_qty': min(ratios_free_qty) // 1,
'virtual_available': min(ratios_virtual_available) * bom_kits[product].product_qty // 1,
'qty_available': min(ratios_qty_available) * bom_kits[product].product_qty // 1,
'incoming_qty': min(ratios_incoming_qty) * bom_kits[product].product_qty // 1,
'outgoing_qty': min(ratios_outgoing_qty) * bom_kits[product].product_qty // 1,
'free_qty': min(ratios_free_qty) * bom_kits[product].product_qty // 1,
}
else:
res[product.id] = {
+1 -1
View File
@@ -292,7 +292,7 @@ class TestBoM(TestMrpCommon):
# ending the recurse call to not call the compute method and just left the Falsy value `0.0`
# for the components available qty.
kit_product_qty, _, _ = (self.product_7_3 + self.product_2 + self.product_3).mapped("qty_available")
self.assertEqual(kit_product_qty, 2)
self.assertEqual(kit_product_qty, 8)
def test_14_bom_kit_qty_multi_uom(self):
uom_dozens = self.env.ref('uom.product_uom_dozen')
+1 -1
View File
@@ -361,7 +361,7 @@ class PaymentTransactionStripe(models.Model):
_logger.info('_create_stripe_refund: Sending values to stripe URL, values:\n%s', pprint.pformat(refund_params))
# Create an idempotency key using the hash of the transaction reference and the database UUID
database_uuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
idempotency_key = sha1((database_uuid + self.reference).encode("utf-8")).hexdigest()
idempotency_key = sha1((database_uuid + self.reference + 'refunds').encode("utf-8")).hexdigest()
res = self.acquirer_id._stripe_request('refunds', refund_params, idempotency_key=idempotency_key)
_logger.info('_create_stripe_refund: Values received:\n%s', pprint.pformat(res))
+7 -3
View File
@@ -1213,9 +1213,6 @@ td {
width:100%;
overflow: hidden;
text-overflow: ellipsis;
background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
/* troublesome in latest webkit
background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
*/
@@ -1224,6 +1221,13 @@ td {
padding-top:15px;
}
/*rtl:ignore*/
.pos .product .product-name {
background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
}
/* ********* The Screens ********* */
+5 -1
View File
@@ -230,6 +230,10 @@ class MailController(MailController):
If so, those two parameters are used to authentify the recipient in the chatter, if any.
:return:
"""
# no model / res_id, meaning no possible record -> direct skip to super
if not model or not res_id or model not in request.env:
return super(MailController, cls)._redirect_to_record(model, res_id, access_token=access_token, **kwargs)
if issubclass(type(request.env[model]), request.env.registry['portal.mixin']):
uid = request.session.uid or request.env.ref('base.public_user').id
record_sudo = request.env[model].sudo().browse(res_id).exists()
@@ -249,4 +253,4 @@ class MailController(MailController):
url_params.update([("pid", pid), ("hash", hash)])
url = url.replace(query=urls.url_encode(url_params)).to_url()
return werkzeug.utils.redirect(url)
return super(MailController, cls)._redirect_to_record(model, res_id, access_token=access_token)
return super(MailController, cls)._redirect_to_record(model, res_id, access_token=access_token, **kwargs)
@@ -165,6 +165,7 @@ var PaymentAdyen = PaymentInterface.extend({
// represented by true.
if (! ignore_error && data !== true) {
self._show_error(_t('Cancelling the payment failed. Please cancel it manually on the payment terminal.'));
self.was_cancelled = !!self.polling;
}
});
},
-11
View File
@@ -234,10 +234,6 @@
'xml_id': 'product.product_product_4c',
'record': obj().env.ref('product.product_product_4_product_template')._get_variant_for_combination(obj().env.ref('product.product_4_attribute_1_value_2') + obj().env.ref('product.product_4_attribute_2_value_1')),
'noupdate': True,
}, {
'xml_id': 'product.product_product_4d',
'record': obj().env.ref('product.product_product_4_product_template')._get_variant_for_combination(obj().env.ref('product.product_4_attribute_1_value_2') + obj().env.ref('product.product_4_attribute_2_value_2')),
'noupdate': True,
},]"/>
</function>
@@ -259,13 +255,6 @@
<field name="standard_price">500.0</field>
<field name="image_1920" type="base64" file="product/static/img/table03.png"/>
</record>
<record id="product_product_4d" model="product.product">
<field name="default_code">DESK0004</field>
<field name="weight">0.01</field>
<field name="standard_price">500.0</field>
<field name="image_1920" type="base64" file="product/static/img/table01.png"/>
</record>
<record id="product_product_5" model="product.product">
<field name="name">Corner Desk Right Sit</field>
<field name="categ_id" ref="product_category_5"/>
+15 -3
View File
@@ -2,12 +2,12 @@
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
import ast
from datetime import timedelta, datetime
from collections import defaultdict
from datetime import timedelta
from random import randint
from flectra import api, fields, models, tools, SUPERUSER_ID, _
from flectra.exceptions import UserError, AccessError, ValidationError, RedirectWarning
from flectra.tools.misc import format_date, get_lang
from flectra.exceptions import UserError, ValidationError
from flectra.osv.expression import OR
from .project_task_recurrence import DAYS, WEEKS
@@ -1096,6 +1096,18 @@ class Task(models.Model):
task._portal_ensure_token()
return tasks
def _load_records_create(self, values):
tasks = super()._load_records_create(values)
stage_ids_per_project = defaultdict(list)
for task in tasks:
if task.stage_id and task.stage_id not in task.project_id.type_ids and task.stage_id.id not in stage_ids_per_project[task.project_id]:
stage_ids_per_project[task.project_id].append(task.stage_id.id)
for project, stage_ids in stage_ids_per_project.items():
project.write({'type_ids': [(4, stage_id) for stage_id in stage_ids]})
return tasks
def write(self, vals):
now = fields.Datetime.now()
if 'parent_id' in vals and vals['parent_id'] in self.ids:
+20
View File
@@ -104,3 +104,23 @@ class TestProjectCommon(SavepointCase):
with self.assertRaises(UserError):
self.project_pigs.unlink()
def test_auto_assign_stages_when_importing_tasks(self):
self.assertFalse(self.project_pigs.type_ids)
self.assertEqual(len(self.project_goats.type_ids), 2)
first_stage = self.project_goats.type_ids[0]
self.env['project.task']._load_records_create({
'name': 'First Task',
'user_id': self.user_projectuser.id,
'project_id': self.project_pigs.id,
'stage_id': first_stage.id,
})
self.assertEqual(self.project_pigs.type_ids, first_stage)
self.env['project.task']._load_records_create([
{'name': 'task',
'user_id': self.user_projectuser.id,
'project_id': self.project_pigs.id,
'stage_id': stage.id,
} for stage in self.project_goats.type_ids
])
self.assertEqual(self.project_pigs.type_ids, self.project_goats.type_ids)
@@ -101,14 +101,6 @@
<field name="invoice_policy">delivery</field>
</record>
<record id="product.product_product_4d" model="product.product">
<field name="invoice_policy">delivery</field>
</record>
<record id="product.product_product_4d" model="product.product">
<field name="invoice_policy">delivery</field>
</record>
<record id="product.product_product_4c" model="product.product">
<field name="invoice_policy">delivery</field>
</record>
-7
View File
@@ -91,13 +91,6 @@
<field name="product_qty">55.0</field>
<field name="location_id" model="stock.location" eval="obj().env.ref('stock.warehouse0').lot_stock_id.id"/>
</record>
<record id="stock_inventory_line_7d" model="stock.inventory.line">
<field name="product_id" ref="product.product_product_4d"/>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
<field name="inventory_id" ref="stock_inventory_0"/>
<field name="product_qty">60.0</field>
<field name="location_id" model="stock.location" eval="obj().env.ref('stock.warehouse0').lot_stock_id.id"/>
</record>
<record id="stock_inventory_line_11" model="stock.inventory.line">
<field name="product_id" ref="product.product_product_12"/>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
+16 -16
View File
@@ -69,28 +69,28 @@
<!-- Matrix -->
<div attrs="{'invisible': [('question_type', '!=', 'matrix')]}">
<div class="row o_matrix_head">
<div class="col-lg-3"></div>
<div class="col-lg-3">ans</div>
<div class="col-lg-3">ans</div>
<div class="col-lg-3">ans</div>
<div class="col-3"></div>
<div class="col-3">ans</div>
<div class="col-3">ans</div>
<div class="col-3">ans</div>
</div>
<div class="row o_matrix_row">
<div class="col-lg-3">Row1</div>
<div class="col-lg-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
<div class="col-lg-3"><i class="fa fa-dot-circle-o fa-lg" role="img" aria-label="Checked" title="Checked"/></div>
<div class="col-lg-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
<div class="col-3">Row1</div>
<div class="col-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
<div class="col-3"><i class="fa fa-dot-circle-o fa-lg" role="img" aria-label="Checked" title="Checked"/></div>
<div class="col-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
</div>
<div class="row o_matrix_row">
<div class="col-lg-3">Row2</div>
<div class="col-lg-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
<div class="col-lg-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
<div class="col-lg-3"><i class="fa fa-dot-circle-o fa-lg" role="img" aria-label="Checked" title="Checked"/></div>
<div class="col-3">Row2</div>
<div class="col-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
<div class="col-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
<div class="col-3"><i class="fa fa-dot-circle-o fa-lg" role="img" aria-label="Checked" title="Checked"/></div>
</div>
<div class="row o_matrix_row">
<div class="col-lg-3">Row3</div>
<div class="col-lg-3"><i class="fa fa-dot-circle-o fa-lg" role="img" aria-label="Checked" title="Checked"/></div>
<div class="col-lg-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
<div class="col-lg-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
<div class="col-3">Row3</div>
<div class="col-3"><i class="fa fa-dot-circle-o fa-lg" role="img" aria-label="Checked" title="Checked"/></div>
<div class="col-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
<div class="col-3"><i class="fa fa-circle-o fa-lg" role="img" aria-label="Not checked" title="Not checked"/></div>
</div>
</div>
</div>
+72 -15
View File
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from werkzeug.urls import url_parse, url_decode
import json
from flectra import http
@@ -15,6 +17,7 @@ class TestPortal(HttpCase):
def setUp(self):
super(TestPortal, self).setUp()
self.user_admin = self.env.ref('base.user_admin')
self.user_employee = mail_new_test_user(
self.env,
groups='base.group_user',
@@ -40,6 +43,56 @@ class TestPortal(HttpCase):
self.record_portal._portal_ensure_token()
@tagged('-at_install', 'post_install', 'portal')
class TestPortalControllers(TestPortal):
def test_redirect_to_records(self):
""" Test redirection of portal-enabled records """
# Test Case 0: as anonymous, cannot access, redirect to web/login
response = self.url_open('/mail/view?model=%s&res_id=%s' % (
self.record_portal._name,
self.record_portal.id), timeout=15)
path = url_parse(response.url).path
self.assertEqual(path, '/web/login')
# Test Case 1: as admin, can access record
self.authenticate(self.user_admin.login, self.user_admin.login)
response = self.url_open('/mail/view?model=%s&res_id=%s' % (
self.record_portal._name,
self.record_portal.id), timeout=15)
self.assertEqual(response.status_code, 200)
fragment = url_parse(response.url).fragment
params = url_decode(fragment)
self.assertEqual(params['cids'], '%s' % self.user_admin.company_id.id)
self.assertEqual(params['id'], '%s' % self.record_portal.id)
self.assertEqual(params['model'], self.record_portal._name)
def test_redirect_to_records_norecord(self):
""" Check specific use case of missing model, should directly redirect
to login page. """
for model, res_id in [
(False, self.record_portal.id),
('', self.record_portal.id),
(self.record_portal._name, False),
(self.record_portal._name, ''),
(False, False),
('wrong.model', self.record_portal.id),
(self.record_portal._name, -4),
]:
response = self.url_open(
'/mail/view?model=%s&res_id=%s' % (model, res_id),
timeout=15
)
path = url_parse(response.url).path
self.assertEqual(
path, '/web/login',
'Failed with %s - %s' % (model, res_id)
)
def test_portal_message_fetch(self):
"""Test retrieving chatter messages through the portal controller"""
self.authenticate(None, None)
@@ -82,21 +135,6 @@ class TestPortal(HttpCase):
# Empty messages should be ignored
self.assertEqual(get_chatter_message_count(), 5)
@users('employee')
def test_portal_mixin(self):
""" Test internals of portal mixin """
customer = self.partner_1.with_env(self.env)
record_portal = self.env['mail.test.portal'].create({
'partner_id': customer.id,
'name': 'Test Portal Record',
})
self.assertFalse(record_portal.access_token)
self.assertEqual(record_portal.access_url, '/my/test_portal/%s' % record_portal.id)
record_portal._portal_ensure_token()
self.assertTrue(record_portal.access_token)
def test_portal_share_comment(self):
""" Test posting through portal controller allowing to use a hash to
post wihtout access rights. """
@@ -119,3 +157,22 @@ class TestPortal(HttpCase):
self.assertIn('Test', message.body)
self.assertEqual(message.author_id, self.partner_2)
@tagged('portal')
class TestPortalMixin(TestPortal):
@users('employee')
def test_portal_mixin(self):
""" Test internals of portal mixin """
customer = self.partner_1.with_env(self.env)
record_portal = self.env['mail.test.portal'].create({
'partner_id': customer.id,
'name': 'Test Portal Record',
})
self.assertFalse(record_portal.access_token)
self.assertEqual(record_portal.access_url, '/my/test_portal/%s' % record_portal.id)
record_portal._portal_ensure_token()
self.assertTrue(record_portal.access_token)
+1 -1
View File
@@ -556,7 +556,7 @@ datetime.datetime = py.type('datetime', null, {
}));
},
to_utc: function () {
var d = new Date(this.year, this.month, this.day, this.hour, this.minute, this.second);
var d = new Date(this.year, this.month - 1, this.day, this.hour, this.minute, this.second);
var offset = d.getTimezoneOffset();
var kwargs = {minutes: py.float.fromJSON(offset)};
var timedelta = py.PY_call(py.extras.datetime.timedelta,[],kwargs);
+1 -1
View File
@@ -152,7 +152,7 @@ var Apps = AbstractAction.extend({
route: '/web/action/load',
params: {action_id: self.failback_action_id},
}).then(function(action) {
return self.do_action(action);
return self.do_action(action, {clear_breadcrumbs: true});
}).then(reject, reject);
});
});
+9 -2
View File
@@ -406,17 +406,24 @@ Dialog.alert = function (owner, message, options) {
// static method to open simple confirm dialog
Dialog.confirm = function (owner, message, options) {
let clickProm;
var buttons = [
{
text: _t("Ok"),
classes: 'btn-primary',
close: true,
click: options && options.confirm_callback,
click: options && options.confirm_callback && (() => {
clickProm = clickProm || options.confirm_callback() || Promise.resolve();
return clickProm;
}),
},
{
text: _t("Cancel"),
close: true,
click: options && options.cancel_callback
click: options && options.cancel_callback && (() => {
clickProm = clickProm || options.cancel_callback() || Promise.resolve();
return clickProm;
}),
}
];
return new Dialog(owner, _.extend({
@@ -103,6 +103,53 @@ QUnit.module('core', {}, function () {
parent.destroy();
});
QUnit.test("click twice on 'Ok' button of a confirm dialog", async function (assert) {
assert.expect(3);
var testPromise = testUtils.makeTestPromise();
var parent = await createEmptyParent();
var options = {
confirm_callback: () => {
assert.step("confirm");
return testPromise;
},
};
Dialog.confirm(parent, "", options);
await testUtils.nextTick();
assert.verifySteps([]);
await testUtils.dom.click($('.modal[role="dialog"] .btn-primary'));
await testUtils.dom.click($('.modal[role="dialog"] .btn-primary'));
assert.verifySteps(['confirm']);
parent.destroy();
});
QUnit.test("click on 'Cancel' and then 'Ok' in a confirm dialog", async function (assert) {
assert.expect(3);
var parent = await createEmptyParent();
var options = {
confirm_callback: () => {
throw new Error("should not be called");
},
cancel_callback: () => {
assert.step("cancel");
}
};
Dialog.confirm(parent, "", options);
await testUtils.nextTick();
assert.verifySteps([]);
testUtils.dom.click($('.modal[role="dialog"] footer button:not(.btn-primary)'));
testUtils.dom.click($('.modal[role="dialog"] footer .btn-primary'));
assert.verifySteps(['cancel']);
parent.destroy();
});
QUnit.test("Closing alert dialog without using buttons calls confirm callback", async function (assert) {
assert.expect(3);
@@ -4,6 +4,7 @@ flectra.define('web.py_utils_tests', function(require) {
var Context = require('web.Context');
var pyUtils = require('web.py_utils');
var time = require('web.time');
var testUtils = require('web.test_utils');
const r = String.raw;
@@ -500,6 +501,36 @@ QUnit.module('core', function () {
assert.strictEqual(result.getSeconds(), 31);
});
QUnit.test('to_utc in october with winter/summer change', function (assert) {
assert.expect(7);
const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset;
Date.prototype.getTimezoneOffset = function () {
const month = this.getMonth() // starts at 0;
if (10 <= month || month <= 2) {
//rough approximation
return -60;
} else {
return -120;
}
}
var result = py.eval(
"datetime.datetime(2022, 10, 17).to_utc()",
pyUtils.context());
assert.ok(result instanceof Date);
assert.strictEqual(result.getFullYear(), 2022);
assert.strictEqual(result.getMonth(), 9);
assert.strictEqual(result.getDate(), 16);
assert.strictEqual(result.getHours(), 22);
assert.strictEqual(result.getMinutes(), 0);
assert.strictEqual(result.getSeconds(), 0);
Date.prototype.getTimezoneOffset = originalGetTimezoneOffset;
});
QUnit.test('datetime.combine', function (assert) {
assert.expect(2);
@@ -7284,6 +7284,44 @@ QUnit.module('Views', {
form.destroy();
});
QUnit.test('buttons with "confirm" attribute: click twice on "Ok"', async function (assert) {
assert.expect(7);
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form>
<header>
<button name="post" class="p" string="Confirm" type="object" confirm="U sure?"/>
</header>
</form>`,
mockRPC: function (route, args) {
assert.step(args.method);
return this._super.apply(this, arguments);
},
intercepts: {
execute_action: function (event) {
assert.step('execute_action'); // should be called only once
event.data.on_success();
},
},
});
assert.verifySteps(["onchange"]);
await testUtils.dom.click(form.$('.o_statusbar_buttons button'));
assert.verifySteps([]);
testUtils.dom.click($('.modal-footer button.btn-primary'));
await Promise.resolve();
await testUtils.dom.click($('.modal-footer button.btn-primary'));
assert.verifySteps(['create', 'read', 'execute_action']);
form.destroy();
});
QUnit.test('buttons are disabled until action is resolved (in dialogs)', async function (assert) {
assert.expect(3);