[PATCH] Upstream patch - 23102022

This commit is contained in:
Parthiv Patel
2022-10-23 08:35:55 +00:00
parent 60440c47b7
commit ca2bc06c61
31 changed files with 406 additions and 33 deletions

View File

@@ -1069,7 +1069,7 @@ class AccountBankStatementLine(models.Model):
reconciliation_overview = []
total_balance = liquidity_lines.balance
total_amount_currency = liquidity_lines.amount_currency
total_amount_currency = -self._prepare_move_line_default_vals()[1]['amount_currency']
# Step 1: Split 'lines_vals_list' into two batches:
# - The existing account.move.lines that need to be reconciled with the statement line.

View File

@@ -4855,6 +4855,7 @@ class AccountMoveLine(models.Model):
exchange_diff_move_vals['date'] = max(exchange_diff_move_vals['date'], company._get_user_fiscal_lock_date())
exchange_move = self.env['account.move'].create(exchange_diff_move_vals)
exchange_move.line_ids.write({'tax_exigible': True}) # Enforce exigibility in case some cash basis adjustments were made in this exchange difference
else:
return None

View File

@@ -169,6 +169,7 @@ class AccountPartialReconcile(models.Model):
partial_amount_currency = 0.0
rate_amount = 0.0
rate_amount_currency = 0.0
if partial.debit_move_id.move_id == move:
partial_amount += partial.amount
partial_amount_currency += partial.debit_amount_currency
@@ -176,6 +177,7 @@ class AccountPartialReconcile(models.Model):
rate_amount_currency -= partial.credit_move_id.amount_currency
source_line = partial.debit_move_id
counterpart_line = partial.credit_move_id
if partial.credit_move_id.move_id == move:
partial_amount += partial.amount
partial_amount_currency += partial.credit_amount_currency
@@ -184,6 +186,16 @@ class AccountPartialReconcile(models.Model):
source_line = partial.credit_move_id
counterpart_line = partial.debit_move_id
if partial.debit_move_id.move_id.is_invoice(include_receipts=True) and partial.credit_move_id.move_id.is_invoice(include_receipts=True):
# Will match when reconciling a refund with an invoice.
# In this case, we want to use the rate of each businness document to compute its cash basis entry,
# not the rate of what it's reconciled with.
rate_amount = source_line.balance
rate_amount_currency = source_line.amount_currency
payment_date = move.date
else:
payment_date = counterpart_line.date
if move_values['currency'] == move.company_id.currency_id:
# Percentage made on company's currency.
percentage = partial_amount / move_values['total_balance']
@@ -198,7 +210,7 @@ class AccountPartialReconcile(models.Model):
counterpart_line.company_currency_id,
source_line.currency_id,
counterpart_line.company_id,
counterpart_line.date,
payment_date,
)
elif rate_amount:
payment_rate = rate_amount_currency / rate_amount

View File

@@ -1422,6 +1422,51 @@ class TestAccountBankStatementLine(TestAccountBankStatementCommon):
{'name': 'whatever', 'account_id': random_acc_1.id, 'balance': -100.0},
])
def test_reconciliation_statement_line_foreign_currency(self):
statement = self.env['account.bank.statement'].create({
'name': 'test_statement',
'date': '2019-01-01',
'journal_id': self.bank_journal_1.id,
'line_ids': [
(0, 0, {
'date': '2019-01-01',
'payment_ref': 'line_1',
'partner_id': self.partner_a.id,
'foreign_currency_id': self.currency_2.id,
'amount': -80.0,
'amount_currency': -120.0,
}),
],
})
statement.button_post()
statement_line = statement.line_ids
invoice = self.env['account.move'].create({
'move_type': 'in_invoice',
'invoice_date': '2019-01-01',
'date': '2019-01-01',
'partner_id': self.partner_a.id,
'currency_id': self.currency_2.id,
'invoice_line_ids': [
(0, None, {
'name': 'counterpart line, same amount',
'account_id': self.company_data['default_account_revenue'].id,
'quantity': 1,
'price_unit': 120.0,
}),
],
})
invoice.action_post()
invoice_line = invoice.line_ids.filtered(lambda line: line.account_internal_type == 'payable')
statement_line.reconcile([{'id': invoice_line.id}])
self.assertRecordValues(statement_line.line_ids, [
# pylint: disable=bad-whitespace
{'amount_currency': -80.0, 'currency_id': self.currency_1.id, 'balance': -80.0, 'reconciled': False},
{'amount_currency': 120.0, 'currency_id': self.currency_2.id, 'balance': 80.0, 'reconciled': True},
])
def test_reconciliation_statement_line_with_generated_payments(self):
self.statement.button_post()

View File

@@ -2153,6 +2153,165 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon):
{'debit': 0.0, 'credit': 50.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id},
])
def test_reconcile_cash_basis_refund_multicurrency(self):
self.env.company.tax_exigibility = True
rates_data = self.setup_multi_currency_data(default_values={
'name': 'Playmock',
'symbol': '🦌',
'rounding': 0.01,
'currency_unit_label': 'Playmock',
'currency_subunit_label': 'Cent',
}, rate2016=0.5, rate2017=0.33333333333333333)
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner_a.id,
'currency_id': rates_data['currency'].id,
'invoice_date': '2016-01-01',
'invoice_line_ids': [(0, 0, {
'name': 'dudu',
'account_id': self.company_data['default_account_revenue'].id,
'price_unit': 100.0,
'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)],
})],
})
refund = self.env['account.move'].create({
'move_type': 'out_refund',
'partner_id': self.partner_a.id,
'currency_id': rates_data['currency'].id,
'invoice_date': '2017-01-01',
'invoice_line_ids': [(0, 0, {
'name': 'dudu',
'account_id': self.company_data['default_account_revenue'].id,
'price_unit': 100.0,
'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)],
})],
})
invoice.action_post()
refund.action_post()
(refund + invoice).line_ids.filtered(lambda x: x.account_id.user_type_id.type == 'receivable').reconcile()
# Check the cash basis moves
self.assertRecordValues(
self.env['account.move'].search([('tax_cash_basis_move_id', '=', invoice.id)]).line_ids,
[
{
'debit': 200,
'credit': 0,
'amount_currency': 100,
'currency_id': rates_data['currency'].id,
'tax_ids': [],
'tax_repartition_line_id': None,
'tax_tag_ids': [],
'tax_exigible': True,
},
{
'debit': 0,
'credit': 200,
'amount_currency': -100,
'currency_id': rates_data['currency'].id,
'tax_ids': self.cash_basis_tax_a_third_amount.ids,
'tax_repartition_line_id': None,
'tax_tag_ids': self.tax_tags[0].ids,
'tax_exigible': True,
},
{
'debit': 66.66,
'credit': 0,
'amount_currency': 33.33,
'currency_id': rates_data['currency'].id,
'tax_ids': [],
'tax_repartition_line_id': None,
'tax_tag_ids': [],
'tax_exigible': True,
},
{
'debit': 0,
'credit': 66.66,
'amount_currency': -33.33,
'currency_id': rates_data['currency'].id,
'tax_ids': [],
'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_tag_ids': self.tax_tags[1].ids,
'tax_exigible': True,
},
]
)
self.assertRecordValues(
self.env['account.move'].search([('tax_cash_basis_move_id', '=', refund.id)]).line_ids,
[
{
'debit': 0,
'credit': 300,
'amount_currency': -100,
'currency_id': rates_data['currency'].id,
'tax_ids': [],
'tax_repartition_line_id': None,
'tax_tag_ids': [],
'tax_exigible': True,
},
{
'debit': 300,
'credit': 0,
'amount_currency': 100,
'currency_id': rates_data['currency'].id,
'tax_ids': self.cash_basis_tax_a_third_amount.ids,
'tax_repartition_line_id': None,
'tax_tag_ids': self.tax_tags[2].ids,
'tax_exigible': True,
},
{
'debit': 0,
'credit': 99.99,
'amount_currency': -33.33,
'currency_id': rates_data['currency'].id,
'tax_ids': [],
'tax_repartition_line_id': None,
'tax_tag_ids': [],
'tax_exigible': True,
},
{
'debit': 99.99,
'credit': 0,
'amount_currency': 33.33,
'currency_id': rates_data['currency'].id,
'tax_ids': [],
'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.refund_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_tag_ids': self.tax_tags[3].ids,
'tax_exigible': True,
},
]
)
# Check the exchange difference move, to be sure no cash basis rounding data was added into it
self.assertRecordValues(
invoice.line_ids.full_reconcile_id.exchange_move_id.line_ids,
[
{
'debit': 133.33,
'credit': 0,
'amount_currency': 0,
'currency_id': rates_data['currency'].id,
'tax_ids': [],
'tax_repartition_line_id': None,
'tax_tag_ids': [],
},
{
'debit': 0,
'credit': 133.33,
'amount_currency': 0,
'currency_id': rates_data['currency'].id,
'tax_ids': [],
'tax_repartition_line_id': None,
'tax_tag_ids': [],
},
]
)
def test_reconcile_cash_basis_revert(self):
''' Ensure the cash basis journal entry can be reverted. '''
self.env.company.tax_exigibility = True

View File

@@ -91,8 +91,11 @@ class SaleOrder(models.Model):
def _create_delivery_line(self, carrier, price_unit):
SaleOrderLine = self.env['sale.order.line']
context = {}
if self.partner_id:
# set delivery detail in the customer language
# used in local scope translation process
context['lang'] = self.partner_id.lang
carrier = carrier.with_context(lang=self.partner_id.lang)
# Apply fiscal position
@@ -102,12 +105,12 @@ class SaleOrder(models.Model):
taxes_ids = self.fiscal_position_id.map_tax(taxes, carrier.product_id, self.partner_id).ids
# Create the sales order line
carrier_with_partner_lang = carrier.with_context(lang=self.partner_id.lang)
if carrier_with_partner_lang.product_id.description_sale:
so_description = '%s: %s' % (carrier_with_partner_lang.name,
carrier_with_partner_lang.product_id.description_sale)
if carrier.product_id.description_sale:
so_description = '%s: %s' % (carrier.name,
carrier.product_id.description_sale)
else:
so_description = carrier_with_partner_lang.name
so_description = carrier.name
values = {
'order_id': self.id,
'name': so_description,
@@ -127,6 +130,7 @@ class SaleOrder(models.Model):
if self.order_line:
values['sequence'] = self.order_line[-1].sequence + 1
sol = SaleOrderLine.sudo().create(values)
del context
return sol
def _format_currency_amount(self, amount):

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import models

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
{
"name": "Fetchmail Outlook",
"version": "1.0.1.0.0",
"version": "1.0",
"category": "Hidden",
"description": "OAuth authentication for incoming Outlook mail server",
"depends": [

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import fetchmail_server

View File

@@ -1,4 +1,5 @@
# Part of Flectra. See LICENSE file for full copyright and licensing details.
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import _, api, models
from flectra.exceptions import UserError

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import test_fetchmail_outlook

View File

@@ -1,4 +1,5 @@
# Part of Flectra. See LICENSE file for full copyright and licensing details.
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
import time

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import controllers

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
{
"name": "Microsoft Outlook",
"version": "1.0.1.0.0",
"version": "1.0",
"category": "Hidden",
"description": "Outlook support for outgoing mail servers",
"depends": [

View File

@@ -1,4 +1,5 @@
# Part of Flectra. See LICENSE file for full copyright and licensing details.
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
import json
import logging

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import microsoft_outlook_mixin

View File

@@ -1,4 +1,5 @@
# Part of Flectra. See LICENSE file for full copyright and licensing details.
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
import base64

View File

@@ -1,4 +1,5 @@
# Part of Flectra. See LICENSE file for full copyright and licensing details.
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
import json
import logging

View File

@@ -1,4 +1,5 @@
# Part of Flectra. See LICENSE file for full copyright and licensing details.
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import fields, models

View File

@@ -776,7 +776,7 @@ class MrpProduction(models.Model):
# Remove from `move_finished_ids` the by-product moves and then move `move_byproduct_ids`
# into `move_finished_ids` to avoid duplicate and inconsistency.
if values.get('move_finished_ids', False):
values['move_finished_ids'] = list(filter(lambda move: move[2]['byproduct_id'] is False, values['move_finished_ids']))
values['move_finished_ids'] = list(filter(lambda move: move[2].get('byproduct_id', False) is False, values['move_finished_ids']))
if values.get('move_byproduct_ids', False):
values['move_finished_ids'] = values.get('move_finished_ids', []) + values['move_byproduct_ids']
del values['move_byproduct_ids']

View File

@@ -282,6 +282,8 @@ class PosOrder(models.Model):
@api.onchange('payment_ids', 'lines')
def _onchange_amount_all(self):
for order in self:
if not order.pricelist_id.currency_id:
raise UserError(_("You can't: create a pos order from the backend interface, or unset the pricelist, or create a pos.order in a python test with Form tool, or edit the form view in studio if no PoS order exist"))
currency = order.pricelist_id.currency_id
order.amount_paid = sum(payment.amount for payment in order.payment_ids)
order.amount_return = sum(payment.amount < 0 and payment.amount or 0 for payment in order.payment_ids)

View File

@@ -66,7 +66,6 @@ class PurchaseReport(models.Model):
def _select(self):
select_str = """
WITH currency_rate as (%s)
SELECT
po.id as order_id,
min(l.id) as id,
@@ -101,7 +100,7 @@ class PurchaseReport(models.Model):
then sum(l.product_qty / line_uom.factor * product_uom.factor) - sum(l.qty_invoiced / line_uom.factor * product_uom.factor)
else sum(l.qty_received / line_uom.factor * product_uom.factor) - sum(l.qty_invoiced / line_uom.factor * product_uom.factor)
end as qty_to_be_billed
""" % self.env['res.currency']._select_companies_rates()
"""
return select_str
def _from(self):
@@ -115,10 +114,6 @@ class PurchaseReport(models.Model):
left join uom_uom line_uom on (line_uom.id=l.product_uom)
left join uom_uom product_uom on (product_uom.id=t.uom_id)
left join account_analytic_account analytic_account on (l.account_analytic_id = analytic_account.id)
left join currency_rate cr on (cr.currency_id = po.currency_id and
cr.company_id = po.company_id and
cr.date_start <= coalesce(po.date_order, now()) and
(cr.date_end is null or cr.date_end > coalesce(po.date_order, now())))
left join {currency_table} ON currency_table.company_id = po.company_id
""".format(
currency_table=self.env['res.currency']._get_query_currency_table({'multi_company': True, 'date': {'date_to': fields.Date.today()}}),

View File

@@ -403,6 +403,8 @@ class SnailmailLetter(models.Model):
])
for letter in letters_send:
letter._snailmail_print()
if letter.error_code == 'CREDIT_ERROR':
break # avoid spam
# Commit after every letter sent to avoid to send it again in case of a rollback
if autocommit:
self.env.cr.commit()

View File

@@ -388,8 +388,12 @@ var AbstractField = Widget.extend({
* @returns {string}
*/
_formatValue: function (value, formatType) {
formatType = formatType || this.formatType;
if (!formatType) {
throw new Error(`Missing format type for '${this.name}' value from the '${this.model}' model`);
}
var options = _.extend({}, this.nodeOptions, { data: this.recordData }, this.formatOptions);
return field_utils.format[formatType || this.formatType](value, this.field, options);
return field_utils.format[formatType](value, this.field, options);
},
/**
* Returns the className corresponding to a given decoration. A

View File

@@ -351,8 +351,14 @@ var KanbanModel = BasicModel.extend({
var data = results[1];
_.each(list.data, function (groupID) {
var group = self.localData[groupID];
var value = group.value;
if (value === true) {
value = "True";
} else if (value === false) {
value = "False";
}
group.progressBarValues = _.extend({
counts: data[group.value] || {},
counts: data[value] || {},
}, list.progressBar);
});
return list;

View File

@@ -1593,6 +1593,15 @@ var MockServer = Class.extend({
_.each(records, function (record) {
var groupByValue = record[groupBy]; // always technical value here
// special case for bool values: rpc call response with capitalized strings
if (!(groupByValue in data)) {
if (groupByValue === true) {
groupByValue = "True";
} else if (groupByValue === false) {
groupByValue = "False";
}
}
if (!(groupByValue in data)) {
data[groupByValue] = {};
_.each(progress_bar.colors, function (val, key) {

View File

@@ -1389,7 +1389,23 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
} else if (weUtils.isColorCombinationName(this._value)) {
this.colorPreviewEl.classList.add('o_cc', `o_cc${this._value}`);
} else {
this.colorPreviewEl.classList.add(`bg-${this._value}`);
// Checking if the className actually exists seems overkill but
// it is actually needed to prevent a crash. As an example, if a
// colorpicker widget is linked to a SnippetOption instance's
// `selectStyle` method designed to handle the "border-color"
// property of an element, the value received can be split if
// the item uses different colors for its top/right/bottom/left
// borders. For instance, you could receive "red blue" if the
// item as red top and bottom borders and blue left and right
// borders, in which case you would reach this `else` and try to
// add the class "bg-red blue" which would crash because of the
// space inside). In that case, we simply do not show any color.
// We could choose to handle this split-value case specifically
// but it was decided that this is enough for the moment.
const className = `bg-${this._value}`;
if (classes.includes(className)) {
this.colorPreviewEl.classList.add(className);
}
}
}
},
@@ -2085,6 +2101,13 @@ const SnippetOptionWidget = Widget.extend({
* @param {boolean} previewMode - @see this.selectClass
* @param {string} widgetValue
* @param {Object} params
* @param {string} [params.forceStyle] if undefined, the method will not
* set the inline style (and thus even remove it) if the item would
* already have the given style without it (thanks to a CSS rule for
* example). If defined (as a string), it acts as the "priority" param
* of @see CSSStyleDeclaration.setProperty: it should be 'important' to
* set the style as important or '' otherwise. Note that if forceStyle
* is undefined, the style is always set as important when applied.
* @returns {Promise|undefined}
*/
selectStyle: function (previewMode, widgetValue, params) {
@@ -2179,8 +2202,11 @@ const SnippetOptionWidget = Widget.extend({
hasUserValue = applyCSS.call(this, cssProps[0], values.join(' '), styles) || hasUserValue;
function applyCSS(cssProp, cssValue, styles) {
if (!weUtils.areCssValuesEqual(styles[cssProp], cssValue, cssProp, this.$target[0])) {
this.$target[0].style.setProperty(cssProp, cssValue, 'important');
const forceStyle = (typeof params.forceStyle !== 'undefined');
if (forceStyle
|| !weUtils.areCssValuesEqual(styles[cssProp], cssValue, cssProp, this.$target[0])) {
const priority = forceStyle ? params.forceStyle : 'important';
this.$target[0].style.setProperty(cssProp, cssValue, priority);
return true;
}
return false;

View File

@@ -17,8 +17,12 @@ class Tour(models.Model):
def consume(self, tour_names):
""" Sets given tours as consumed, meaning that
these tours won't be active anymore for that user """
if not self.env.user.has_group('base.group_user'):
# Only internal users can use this method.
# TODO master: update ir.model.access records instead of using sudo()
return
for name in tour_names:
self.create({'name': name, 'user_id': self.env.uid})
self.sudo().create({'name': name, 'user_id': self.env.uid})
@api.model
def get_consumed_tours(self):

View File

@@ -27,6 +27,7 @@ const BaseAnimatedHeader = animations.Animation.extend({
this.fixedHeader = false;
this.scrolledPoint = 0;
this.hasScrolled = false;
this.scrollHeightTooShort = false;
},
/**
* @override
@@ -155,6 +156,33 @@ const BaseAnimatedHeader = animations.Animation.extend({
}
this.$main.css('padding-top', this.fixedHeader ? this.headerHeight : '');
},
/**
* Checks if the size of the header will decrease by adding the
* 'o_header_is_scrolled' class. If so, we do not add this class if the
* remaining scroll height is not enough to stay above 'this.scrolledPoint'
* after the transition, otherwise it causes the scroll position to move up
* again below 'this.scrolledPoint' and trigger an infinite loop.
*
* @todo header effects should be improved in the future to not ever change
* the page scroll-height during their animation. The code would probably be
* simpler but also prevent having weird scroll "jumps" during animations
* (= depending on the logo height after/before scroll, a scroll step (one
* mousewheel event for example) can be bigger than other ones).
*
* @private
* @returns {boolean}
*/
_scrollHeightTooShort() {
const scrollEl = $().getScrollingElement()[0];
const remainingScroll = (scrollEl.scrollHeight - scrollEl.clientHeight) - this.scrolledPoint;
const clonedHeader = this.el.cloneNode(true);
scrollEl.append(clonedHeader);
clonedHeader.classList.add('o_header_is_scrolled', 'o_header_affixed', 'o_header_no_transition');
const endHeaderHeight = clonedHeader.offsetHeight;
clonedHeader.remove();
const heightDiff = this.headerHeight - endHeaderHeight;
return heightDiff > 0 ? remainingScroll <= heightDiff : false;
},
//--------------------------------------------------------------------------
// Handlers
@@ -180,9 +208,12 @@ const BaseAnimatedHeader = animations.Animation.extend({
// Indicates the page is scrolled, the logo size is changed.
const headerIsScrolled = (scroll > this.scrolledPoint);
if (this.headerIsScrolled !== headerIsScrolled) {
this.el.classList.toggle('o_header_is_scrolled', headerIsScrolled);
this.$el.trigger('flectra-transitionstart');
this.headerIsScrolled = headerIsScrolled;
this.scrollHeightTooShort = headerIsScrolled && this._scrollHeightTooShort();
if (!this.scrollHeightTooShort) {
this.el.classList.toggle('o_header_is_scrolled', headerIsScrolled);
this.$el.trigger('flectra-transitionstart');
this.headerIsScrolled = headerIsScrolled;
}
}
// Close opened menus
@@ -222,6 +253,13 @@ publicWidget.registry.StandardAffixedHeader = BaseAnimatedHeader.extend({
this.headerHeight = this.$el.outerHeight();
return this._super.apply(this, arguments);
},
/**
* @override
*/
destroy() {
this.$el.css('transform', '');
this._super(...arguments);
},
//--------------------------------------------------------------------------
// Handlers
@@ -236,14 +274,14 @@ publicWidget.registry.StandardAffixedHeader = BaseAnimatedHeader.extend({
/**
* Called when the window is scrolled
*
* @private
* @override
* @param {integer} scroll
*/
_updateHeaderOnScroll: function (scroll) {
this._super(...arguments);
const mainPosScrolled = (scroll > this.headerHeight + this.topGap);
const reachPosScrolled = (scroll > this.scrolledPoint + this.topGap);
const reachPosScrolled = (scroll > this.scrolledPoint + this.topGap) && !this.scrollHeightTooShort;
const fixedUpdate = (this.fixedHeader !== mainPosScrolled);
const showUpdate = (this.fixedHeaderShow !== reachPosScrolled);

View File

@@ -2687,6 +2687,58 @@ options.registry.ScrollButton = options.Class.extend({
},
});
// TODO there is no data-js associated to this but a data-option-name, somehow
// it acts as data-js... it will be reviewed in master.
options.registry.minHeight = options.Class.extend({
/**
* @override
*/
_renderCustomXML(uiFragment) {
// TODO adapt in master. This sets up a different UI for the image
// gallery snippet: for this one, we allow to force a specific height
// in auto mode. It was done in stable as without it, the default height
// is difficult to understand for the user as it depends on screen
// height of the one who edited the website and not on the added images.
// It was also a regression as in <= 11.0, this was a possibility.
if (this.$target[0].dataset.snippet !== 's_image_gallery') {
return;
}
const minHeightEl = uiFragment.querySelector('we-button-group');
if (!minHeightEl) {
return;
}
minHeightEl.setAttribute('string', _t("Min-Height"));
const heightEl = document.createElement('we-input');
heightEl.setAttribute('string', _t("└ Height"));
heightEl.dataset.name = 'image_gallery_height_opt';
heightEl.dataset.unit = 'px';
heightEl.dataset.selectStyle = '';
heightEl.dataset.cssProperty = 'height';
// For this setting, we need to always force the style (= if the block
// is naturally 800px tall and the user enters 800px for this setting,
// we set 800px as inline style anyway). Indeed, this snippet's style
// is based on the height that is forced but once the related public
// widgets are started, the inner carousel items receive a min-height
// which makes it so the snippet "natural" height is equal to the
// initially forced height... so if the style is not forced, it would
// ultimately be removed by mistake thinking it is not necessary.
// Note: this is forced as not important as we still need the height to
// be reset to 'auto' in mobile (generic css rules).
heightEl.dataset.forceStyle = '';
uiFragment.appendChild(heightEl);
},
/**
* @override
*/
_computeWidgetVisibility(widgetName, params) {
if (widgetName === 'image_gallery_height_opt') {
return !this.$target[0].classList.contains('o_half_screen_height')
&& !this.$target[0].classList.contains('o_full_screen_height');
}
return this._super(...arguments);
},
});
return {
UrlPickerUserValueWidget: UrlPickerUserValueWidget,
FontFamilyPickerUserValueWidget: FontFamilyPickerUserValueWidget,

View File

@@ -1001,6 +1001,7 @@
</div>
<!-- Min height of section -->
<!-- TODO adapt in master, this is patched in JS -->
<div data-option-name="minHeight" data-selector="section">
<we-button-group string="Height">
<we-button data-select-class="" title="Fit content">Auto</we-button>