mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2025-02-25 18:55:21 -06:00
[PATCH] Upstream patch - 11062023
This commit is contained in:
@@ -40,9 +40,13 @@ class IrActionsReport(models.Model):
|
||||
def _render_qweb_pdf(self, res_ids=None, data=None):
|
||||
# Overridden so that the print > invoices actions raises an error
|
||||
# when trying to print a miscellaneous operation instead of an invoice.
|
||||
# + append context data with the display_name_in_footer parameter
|
||||
if self.model == 'account.move' and res_ids:
|
||||
invoice_reports = (self.env.ref('account.account_invoices_without_payment'), self.env.ref('account.account_invoices'))
|
||||
if self in invoice_reports:
|
||||
if self.env['ir.config_parameter'].sudo().get_param('account.display_name_in_footer'):
|
||||
data = data and dict(data) or {}
|
||||
data.update({'display_name_in_footer': True})
|
||||
moves = self.env['account.move'].browse(res_ids)
|
||||
if any(not move.is_invoice(include_receipts=True) for move in moves):
|
||||
raise UserError(_("Only invoices could be printed."))
|
||||
|
||||
@@ -58,6 +58,10 @@ class IrModule(models.Model):
|
||||
terp.update(ast.literal_eval(f.read().decode()))
|
||||
if not terp:
|
||||
return False
|
||||
if not terp.get('icon'):
|
||||
icon_path = 'static/description/icon.png'
|
||||
module_icon = module if os.path.exists(opj(path, icon_path)) else 'base'
|
||||
terp['icon'] = opj('/', module_icon, icon_path)
|
||||
values = self.get_values_from_terp(terp)
|
||||
if 'version' in terp:
|
||||
values['latest_version'] = terp['version']
|
||||
|
||||
@@ -10,7 +10,7 @@ from unittest.mock import patch
|
||||
|
||||
from flectra.addons import __path__ as __addons_path__
|
||||
from flectra.tools import mute_logger
|
||||
from flectra.tests.common import TransactionCase
|
||||
from flectra.tests.common import TransactionCase, HttpCase
|
||||
|
||||
class TestImportModule(TransactionCase):
|
||||
def import_zipfile(self, files):
|
||||
@@ -189,3 +189,20 @@ class TestImportModule(TransactionCase):
|
||||
static_attachment = self.env['ir.attachment'].search([('url', '=', '/%s' % static_path)])
|
||||
self.assertEqual(static_attachment.name, os.path.basename(static_path))
|
||||
self.assertEqual(static_attachment.datas, base64.b64encode(static_data))
|
||||
|
||||
|
||||
class TestImportModuleHttp(TestImportModule, HttpCase):
|
||||
def test_import_module_icon(self):
|
||||
"""Assert import a module with an icon result in the module displaying the icon in the apps menu,
|
||||
and with the base module icon if module without icon"""
|
||||
files = [
|
||||
('foo/__manifest__.py', b"{'name': 'foo'}"),
|
||||
('foo/static/description/icon.png', b"foo_icon"),
|
||||
('bar/__manifest__.py', b"{'name': 'bar'}"),
|
||||
]
|
||||
self.import_zipfile(files)
|
||||
foo_icon_path, foo_icon_data = files[1]
|
||||
# Assert icon of module foo, which must be the icon provided in the zip
|
||||
self.assertEqual(self.url_open('/' + foo_icon_path).content, foo_icon_data)
|
||||
# Assert icon of module bar, which must be the icon of the base module as none was provided
|
||||
self.assertEqual(self.env.ref('base.module_bar').icon_image, self.env.ref('base.module_base').icon_image)
|
||||
|
||||
@@ -45,12 +45,12 @@ class AccountMove(models.Model):
|
||||
domain += [('code', 'in', ['70', '71', '56', '61'])]
|
||||
elif self.partner_id.l10n_cl_sii_taxpayer_type == '3':
|
||||
domain += [('code', 'in', ['35', '38', '39', '41', '56', '61'])]
|
||||
elif not self.partner_id.l10n_cl_sii_taxpayer_type or self.partner_id.country_id != self.env.ref(
|
||||
'base.cl') or self.partner_id.l10n_cl_sii_taxpayer_type == '4':
|
||||
elif self.partner_id.country_id.code != 'CL' or self.partner_id.l10n_cl_sii_taxpayer_type == '4':
|
||||
domain += [('code', '=', '46')]
|
||||
else:
|
||||
domain += [('code', 'in', [])]
|
||||
return domain
|
||||
|
||||
|
||||
def _check_document_types_post(self):
|
||||
for rec in self.filtered(
|
||||
lambda r: r.company_id.country_id.code == "CL" and
|
||||
@@ -89,7 +89,7 @@ class AccountMove(models.Model):
|
||||
if latam_document_type_code in ['110', '111', '112']:
|
||||
raise ValidationError(_('The tax payer type of this supplier is not entitled to deliver '
|
||||
'imports documents'))
|
||||
if tax_payer_type == '4' or country_id.code != "CL":
|
||||
if (tax_payer_type == '4' or country_id.code != "CL") and latam_document_type_code != '46':
|
||||
raise ValidationError(_('You need a journal without the use of documents for foreign '
|
||||
'suppliers'))
|
||||
|
||||
|
||||
@@ -28,4 +28,11 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
||||
<data noupdate="0">
|
||||
<record id="display_name_in_footer_param" model="ir.config_parameter">
|
||||
<field name="key">account.display_name_in_footer</field>
|
||||
<field name="value" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</flectra>
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
<field name="type_tax_use">purchase</field>
|
||||
<field name="name">Standard rated purchases from EU (IE)</field>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="amount">17.5</field>
|
||||
<field name="amount">23</field>
|
||||
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
|
||||
(0,0, {
|
||||
'factor_percent': 100,
|
||||
|
||||
@@ -13,6 +13,7 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
||||
def setUpClass(cls):
|
||||
super(TestSaleStockMargin, cls).setUpClass()
|
||||
cls.pricelist = cls.env['product.pricelist'].create({'name': 'Simple Pricelist'})
|
||||
cls.env['res.currency.rate'].search([]).unlink()
|
||||
|
||||
#########
|
||||
# UTILS #
|
||||
|
||||
@@ -371,6 +371,9 @@
|
||||
of
|
||||
<span class="topage"/>
|
||||
</div>
|
||||
<div t-if="report_type == 'pdf' and display_name_in_footer" class="text-muted">
|
||||
<span t-field="o.name"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -413,6 +416,9 @@
|
||||
<div t-if="report_type == 'pdf'">
|
||||
Page: <span class="page"/> / <span class="topage"/>
|
||||
</div>
|
||||
<div t-if="report_type == 'pdf' and display_name_in_footer" class="text-muted">
|
||||
<span t-field="o.name"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -511,6 +517,9 @@
|
||||
<div t-if="report_type == 'pdf'" class="text-muted">
|
||||
Page: <span class="page"/> / <span class="topage"/>
|
||||
</div>
|
||||
<div t-if="report_type == 'pdf' and display_name_in_footer" class="text-muted">
|
||||
<span t-field="o.name"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2274,7 +2274,6 @@ const SnippetOptionWidget = Widget.extend({
|
||||
*
|
||||
* @param {string} name - an identifier for a type of update
|
||||
* @param {*} data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
notify: function (name, data) {
|
||||
if (name === 'target') {
|
||||
|
||||
@@ -87,6 +87,8 @@ class WebsiteRewrite(models.Model):
|
||||
raise ValidationError(_('"URL to" can not be empty.'))
|
||||
elif not rewrite.url_to.startswith('/'):
|
||||
raise ValidationError(_('"URL to" must start with a leading slash.'))
|
||||
if not rewrite.url_from:
|
||||
raise ValidationError(_('"URL from" can not be empty.'))
|
||||
for param in re.findall('/<.*?>', rewrite.url_from):
|
||||
if param not in rewrite.url_to:
|
||||
raise ValidationError(_('"URL to" must contain parameter %s used in "URL from".') % param)
|
||||
|
||||
@@ -4,6 +4,7 @@ flectra.define('website.s_image_gallery_options', function (require) {
|
||||
var core = require('web.core');
|
||||
var weWidgets = require('wysiwyg.widgets');
|
||||
var options = require('web_editor.snippets.options');
|
||||
const wUtils = require("website.utils");
|
||||
|
||||
var _t = core._t;
|
||||
var qweb = core.qweb;
|
||||
@@ -44,11 +45,14 @@ options.registry.gallery = options.Class.extend({
|
||||
});
|
||||
|
||||
const $container = this.$('> .container, > .container-fluid, > .o_container_small');
|
||||
let layoutPromise;
|
||||
if ($container.find('> *:not(div)').length) {
|
||||
self.mode(null, self.getMode());
|
||||
layoutPromise = self._modeWithImageWait(null, self.getMode());
|
||||
} else {
|
||||
layoutPromise = Promise.resolve();
|
||||
}
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
return layoutPromise.then(this._super.apply(this, arguments));
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -103,6 +107,7 @@ options.registry.gallery = options.Class.extend({
|
||||
var lastImage = _.last(this._getImages());
|
||||
var index = lastImage ? this._getIndex(lastImage) : -1;
|
||||
return new Promise(resolve => {
|
||||
let savedPromise = Promise.resolve();
|
||||
dialog.on('save', this, function (attachments) {
|
||||
for (var i = 0; i < attachments.length; i++) {
|
||||
$('<img/>', {
|
||||
@@ -115,13 +120,14 @@ options.registry.gallery = options.Class.extend({
|
||||
}).appendTo($container);
|
||||
}
|
||||
if (attachments.length > 0) {
|
||||
this.mode('reset', this.getMode());
|
||||
this.trigger_up('cover_update');
|
||||
savedPromise = this._modeWithImageWait('reset', this.getMode()).then(() => {
|
||||
this.trigger_up('cover_update');
|
||||
});
|
||||
}
|
||||
});
|
||||
dialog.on('closed', this, () => {
|
||||
this.__imageDialogOpened = false;
|
||||
return resolve();
|
||||
return savedPromise.then(resolve);
|
||||
});
|
||||
dialog.open();
|
||||
});
|
||||
@@ -136,6 +142,7 @@ options.registry.gallery = options.Class.extend({
|
||||
const nbColumns = parseInt(widgetValue || '1');
|
||||
this.$target.attr('data-columns', nbColumns);
|
||||
|
||||
// TODO In master return mode's result.
|
||||
this.mode(previewMode, this.getMode(), {}); // TODO improve
|
||||
},
|
||||
/**
|
||||
@@ -199,12 +206,38 @@ options.registry.gallery = options.Class.extend({
|
||||
|
||||
// Dispatch images in columns by always putting the next one in the
|
||||
// smallest-height column
|
||||
if (this._masonryAwaitImages) {
|
||||
// TODO In master return promise.
|
||||
this._masonryAwaitImagesPromise = new Promise(async resolve => {
|
||||
for (const imgEl of imgs) {
|
||||
let min = Infinity;
|
||||
let smallestColEl;
|
||||
for (const colEl of cols) {
|
||||
const imgEls = colEl.querySelectorAll("img");
|
||||
const lastImgRect = imgEls.length && imgEls[imgEls.length - 1].getBoundingClientRect();
|
||||
const height = lastImgRect ? Math.round(lastImgRect.top + lastImgRect.height) : 0;
|
||||
if (height < min) {
|
||||
min = height;
|
||||
smallestColEl = colEl;
|
||||
}
|
||||
}
|
||||
smallestColEl.append(imgEl);
|
||||
await wUtils.onceAllImagesLoaded(this.$target);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
return;
|
||||
}
|
||||
// TODO Remove in master.
|
||||
// Order might be wrong if images were not loaded yet.
|
||||
while (imgs.length) {
|
||||
var min = Infinity;
|
||||
var $lowest;
|
||||
_.each(cols, function (col) {
|
||||
var $col = $(col);
|
||||
var height = $col.is(':empty') ? 0 : $col.find('img').last().offset().top + $col.find('img').last().height() - self.$target.offset().top;
|
||||
// Neutralize invisible sub-pixel height differences.
|
||||
height = Math.round(height);
|
||||
if (height < min) {
|
||||
min = height;
|
||||
$lowest = $col;
|
||||
@@ -310,12 +343,19 @@ options.registry.gallery = options.Class.extend({
|
||||
*/
|
||||
notify: function (name, data) {
|
||||
this._super(...arguments);
|
||||
// TODO In master: nest in a snippet_edition_request to await mode.
|
||||
if (name === 'image_removed') {
|
||||
data.$image.remove(); // Force the removal of the image before reset
|
||||
// TODO In 16.0: use _modeWithImageWait.
|
||||
this.mode('reset', this.getMode());
|
||||
} else if (name === 'image_index_request') {
|
||||
var imgs = this._getImages();
|
||||
var position = _.indexOf(imgs, data.$image[0]);
|
||||
if (position === 0 && data.position === "prev") {
|
||||
data.position = "last";
|
||||
} else if (position === imgs.length - 1 && data.position === "next") {
|
||||
data.position = "first";
|
||||
}
|
||||
imgs.splice(position, 1);
|
||||
switch (data.position) {
|
||||
case 'first':
|
||||
@@ -339,6 +379,7 @@ options.registry.gallery = options.Class.extend({
|
||||
$(img).attr('data-index', index);
|
||||
});
|
||||
const currentMode = this.getMode();
|
||||
// TODO In 16.0: use _modeWithImageWait.
|
||||
this.mode('reset', currentMode);
|
||||
if (currentMode === 'slideshow') {
|
||||
const $carousel = this.$target.find('.carousel');
|
||||
@@ -455,6 +496,25 @@ options.registry.gallery = options.Class.extend({
|
||||
$container.empty().append($content);
|
||||
return $container;
|
||||
},
|
||||
/**
|
||||
* Call mode while ensuring that all images are loaded.
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_modeWithImageWait(previewMode, widgetValue, params) {
|
||||
// TODO Remove in master.
|
||||
let promise;
|
||||
this._masonryAwaitImages = true;
|
||||
try {
|
||||
this.mode(previewMode, widgetValue, params);
|
||||
promise = this._masonryAwaitImagesPromise;
|
||||
} finally {
|
||||
this._masonryAwaitImages = false;
|
||||
this._masonryAwaitImagesPromise = undefined;
|
||||
}
|
||||
return promise || Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
options.registry.gallery_img = options.Class.extend({
|
||||
|
||||
@@ -150,13 +150,21 @@ flectra.define('website_form.s_website_form', function (require) {
|
||||
// force server date format usage for existing fields
|
||||
this.$target.find('.s_website_form_field:not(.s_website_form_custom)')
|
||||
.find('.s_website_form_date, .s_website_form_datetime').each(function () {
|
||||
const $input = $(this).find('input');
|
||||
|
||||
// Datetimepicker('viewDate') will return `new Date()` if the
|
||||
// input is empty but we want to keep the empty value
|
||||
if (!$input.val()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var date = $(this).datetimepicker('viewDate').clone().locale('en');
|
||||
var format = 'YYYY-MM-DD';
|
||||
if ($(this).hasClass('s_website_form_datetime')) {
|
||||
date = date.utc();
|
||||
format = 'YYYY-MM-DD HH:mm:ss';
|
||||
}
|
||||
form_values[$(this).find('input').attr('name')] = date.format(format);
|
||||
form_values[$input.attr('name')] = date.format(format);
|
||||
});
|
||||
|
||||
if (this._recaptchaLoaded) {
|
||||
|
||||
Reference in New Issue
Block a user