mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2026-08-18 17:24:42 -05:00
[PATCH] Upstream patch - 20102022
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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] = {
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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 ********* */
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user