[PATCH] Upstream patch - 16122022

This commit is contained in:
Parthiv Patel
2022-12-16 08:35:39 +00:00
parent 689018f85d
commit d14ecb41a2
17 changed files with 214 additions and 57 deletions
+7
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from flectra import api, fields, models, _
from flectra.exceptions import ValidationError
ACCOUNT_DOMAIN = "[('deprecated', '=', False), ('internal_type','=','other'), ('company_id', '=', current_company_id), ('is_off_balance', '=', False)]"
@@ -53,6 +54,12 @@ class ProductTemplate(models.Model):
fiscal_pos = self.env['account.fiscal.position']
return fiscal_pos.map_accounts(accounts)
@api.constrains('uom_id')
def _check_uom_not_in_invoice(self):
for template in self:
invoices = self.env['account.move.line'].sudo().search([('product_id.product_tmpl_id.id', '=', template.id)], limit=1)
if invoices:
raise ValidationError(_('The product "%s" is used in invoices. You cannot change its Unit of Measure.', self.display_name))
class ProductProduct(models.Model):
_inherit = "product.product"
+14 -7
View File
@@ -706,20 +706,27 @@
<label for="journal_id"
groups="account.group_account_readonly"
invisible="context.get('default_journal_id') and context.get('move_type', 'entry') != 'entry'"/>
<div name="journal_div" class="d-flex" invisible="context.get('default_journal_id') and context.get('move_type', 'entry') != 'entry'">
<div name="journal_div"
class="d-flex"
invisible="context.get('default_journal_id') and context.get('move_type', 'entry') != 'entry'"
groups="account.group_account_readonly">
<field name="journal_id"
groups="account.group_account_readonly"
options="{'no_create': True}"
attrs="{'readonly': [('posted_before', '=', True)]}"/>
options="{'no_create': True}"
attrs="{'readonly': [('posted_before', '=', True)]}"/>
<span class="oe_inline o_form_label mx-3 oe_read_only"
groups="base.group_multi_currency"> in </span>
<!-- Bigger margin on the left because in edit mode the external link button covers the text -->
<span class="oe_inline o_form_label mr-3 ml-5 oe_edit_only"
groups="base.group_multi_currency"> in </span>
groups="base.group_multi_currency"> in </span>
<field name="currency_id"
groups="base.group_multi_currency"
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
groups="base.group_multi_currency"
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
</div>
<field name="currency_id"
attrs="{'readonly': [('state', '!=', 'draft')]}"
groups="!account.group_account_readonly,base.group_multi_currency"/>
</group>
</group>
<notebook>
+5 -5
View File
@@ -16,7 +16,7 @@ from flectra import api, fields, models, SUPERUSER_ID, tools
from flectra.addons.base.models.res_partner import _tz_get
from flectra.addons.resource.models.resource import float_to_time, HOURS_PER_DAY
from flectra.exceptions import AccessError, UserError, ValidationError
from flectra.tools import float_compare
from flectra.tools import float_compare, format_date
from flectra.tools.float_utils import float_round
from flectra.tools.translate import _
from flectra.osv import expression
@@ -654,6 +654,7 @@ class HolidaysRequest(models.Model):
target = leave.category_id.name
else:
target = leave.employee_id.name
display_date = format_date(self.env, leave.date_from)
if leave.leave_type_request_unit == 'hour':
if self.env.context.get('hide_employee_name') and 'employee_id' in self.env.context.get('group_by', []):
res.append((
@@ -662,7 +663,7 @@ class HolidaysRequest(models.Model):
person=target,
leave_type=leave.holiday_status_id.name,
duration=leave.number_of_hours_display,
date=fields.Date.to_string(leave.date_from),
date=display_date,
)
))
else:
@@ -672,13 +673,12 @@ class HolidaysRequest(models.Model):
person=target,
leave_type=leave.holiday_status_id.name,
duration=leave.number_of_hours_display,
date=fields.Date.to_string(leave.date_from),
date=display_date,
)
))
else:
display_date = fields.Date.to_string(leave.date_from)
if leave.number_of_days > 1:
display_date += '%s' % fields.Date.to_string(leave.date_to)
display_date += '%s' % format_date(self.env, leave.date_to)
if self.env.context.get('hide_employee_name') and 'employee_id' in self.env.context.get('group_by', []):
res.append((
leave.id,
@@ -55,7 +55,7 @@
</group>
<group>
<field name="date_start" attrs="{'readonly': [('state', '!=', 'draft')]}" />
<field name="date_stop" attrs="{'readonly': [('state', '!=', 'draft')]}" />
<field name="date_stop" attrs="{'readonly': [('state', '!=', 'draft')]}" required="1"/>
<label for="duration" string="Period"/>
<div class="o_row">
<field name="duration" nolabel="1" attrs="{'readonly': [('state', '!=', 'draft')]}" /><span class="ml8">Hours</span>
+7
View File
@@ -1825,3 +1825,10 @@ class StockMove(models.Model):
moves_to_reserve = self.env['stock.move'].search(expression.AND([static_domain, expression.OR(domains)]),
order='priority desc, date asc, id asc')
moves_to_reserve._action_assign()
def _get_orig_reserved_availability(self):
self.ensure_one()
reserved = self.reserved_availability if self.show_reserved_availability else 0.0
if not reserved and self.move_orig_ids:
reserved = sum([m._get_orig_reserved_availability() for m in self.move_orig_ids])
return reserved
@@ -163,8 +163,10 @@ class ReplenishmentReport(models.AbstractModel):
)
outs = self.env['stock.move'].search(out_domain, order='priority desc, date, id')
outs_per_product = defaultdict(lambda: [])
outs_reservation = {}
for out in outs:
outs_per_product[out.product_id.id].append(out)
outs_reservation[out.id] = out._get_orig_reserved_availability()
ins = self.env['stock.move'].search(in_domain, order='priority desc, date, id')
ins_per_product = defaultdict(lambda: [])
for in_ in ins:
@@ -178,20 +180,22 @@ class ReplenishmentReport(models.AbstractModel):
lines = []
for product in (ins | outs).product_id:
for out in outs_per_product[product.id]:
if out.state not in ('partially_available', 'assigned'):
reserved_availability = outs_reservation[out.id]
if float_is_zero(reserved_availability, precision_rounding=product.uom_id.rounding):
continue
current = currents[out.product_id.id]
reserved = out.product_uom._compute_quantity(out.reserved_availability, product.uom_id)
reserved = out.product_uom._compute_quantity(reserved_availability, product.uom_id)
currents[product.id] -= reserved
lines.append(self._prepare_report_line(reserved, move_out=out, reservation=True))
unreconciled_outs = []
for out in outs_per_product[product.id]:
reserved_availability = outs_reservation[out.id]
# Reconcile with the current stock.
current = currents[out.product_id.id]
reserved = 0.0
if out.state in ('partially_available', 'assigned'):
reserved = out.product_uom._compute_quantity(out.reserved_availability, product.uom_id)
if not float_is_zero(reserved_availability, precision_rounding=product.uom_id.rounding):
reserved = out.product_uom._compute_quantity(reserved_availability, product.uom_id)
demand = out.product_qty - reserved
taken_from_stock = min(demand, current)
if not float_is_zero(taken_from_stock, precision_rounding=product.uom_id.rounding):
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1400 570" xml:space="preserve">
viewBox="0 0 1400 1400" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{opacity:0.44;fill:#383E45;enable-background:new;}

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1400 570" xml:space="preserve">
viewBox="0 -830 1400 1400" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{opacity:0.13;fill:#3AADAA;enable-background:new;}

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

+1 -1
View File
@@ -24,7 +24,7 @@
<linearGradient id="linear-gradient-13" x1="9.16" y1="352.59" x2="140.03" y2="410.41" gradientTransform="matrix(1, 0, 0, 1, 0, 0)" xlink:href="#linear-gradient-3"/>
</defs>
<g>
<polygon points="424.92 566.63 -1.3 566.63 -1.3 853.69 424.92 566.63" style="fill: url(#linear-gradient)"/>
<polygon points="424.92 566.6 -1.3 566.6 -1.3 853.69" style="fill: url(#linear-gradient)"/>
<g>
<path d="M728.32-4.39-1.3,488.16v78.43H424.92L960.5,205c68.75-46.41,87.32-140.36,41.68-209.42Z" style="fill: url(#linear-gradient-2)"/>
<g>

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

@@ -4058,7 +4058,7 @@ registry.BackgroundShape = SnippetOptionWidget.extend({
const insertShapeContainer = newContainer => {
const shapeContainer = target.querySelector(':scope > .o_we_shape');
if (shapeContainer) {
shapeContainer.remove();
this._removeShapeEl(shapeContainer);
}
if (newContainer) {
const preShapeLayerElement = this._getLastPreShapeLayerElement();
@@ -4141,6 +4141,13 @@ registry.BackgroundShape = SnippetOptionWidget.extend({
this.prevShape = target.dataset.oeShapeData;
}
},
/**
* @private
* @param {HTMLElement} shapeEl
*/
_removeShapeEl(shapeEl) {
shapeEl.remove();
},
/**
* Overwrites shape properties with the specified data.
*
@@ -4248,7 +4255,12 @@ registry.BackgroundShape = SnippetOptionWidget.extend({
shapeToSelect = possibleShapes.find((shape, i) => {
return possibleShapes[i - 1] === previousShape;
});
} else {
}
// If there is no previous sibling, if the previous sibling had the
// last shape selected or if the previous shape could not be found
// in the possible shapes, default to the first shape. ([0] being no
// shapes selected.)
if (!shapeToSelect) {
shapeToSelect = possibleShapes[1];
}
return this._handlePreviewState(false, () => ({shape: shapeToSelect}));
@@ -4880,6 +4892,20 @@ registry.SnippetSave = SnippetOptionWidget.extend({
dialog.on('closed', this, () => resolve());
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* TODO adapt in master, this option should only be instantiated for real
* snippets in the first place.
*
* @override
*/
_computeVisibility() {
return this.$target[0].hasAttribute('data-snippet');
},
});
/**
@@ -1240,6 +1240,89 @@ registry.ImagesLazyLoading = publicWidget.Widget.extend({
},
});
/**
* @todo while this solution mitigates the issue, it is not fixing it entirely
* but mainly, we should find a better solution than a JS solution as soon as
* one is available and ideally without having to make ugly patches to the SVGs.
*
* Due to a bug on Chrome when using browser zoom, there is sometimes a gap
* between sections with shapes. This gap is due to a rounding issue when
* positioning the SVG background images. This code reduces the rounding error
* by ensuring that shape elements always have a width value as close to an
* integer as possible.
*
* Note: a gap also appears between some shapes without zoom. This is likely
* due to error in the shapes themselves. Many things were done to try and fix
* this, but the remaining errors will likely be fixed with a review of the
* shapes in future Flectra versions.
*
* /!\
* If a better solution for stable comes up, this widget behavior may be
* disabled, avoid depending on it if possible.
* /!\
*/
registry.ZoomedBackgroundShape = publicWidget.Widget.extend({
selector: '.o_we_shape',
disabledInEditableMode: false,
/**
* @override
*/
start() {
this._onBackgroundShapeResize();
this.throttledShapeResize = _.throttle(() => this._onBackgroundShapeResize(), 25);
window.addEventListener('resize', this.throttledShapeResize);
return this._super(...arguments);
},
/**
* @override
*/
destroy() {
this._updateShapePosition();
window.removeEventListener('resize', this.throttledShapeResize);
this._super(...arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Updates the left and right offset of the shape.
*
* @private
* @param {string} offset
*/
_updateShapePosition(offset = '') {
this.el.style.left = offset;
this.el.style.right = offset;
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onBackgroundShapeResize() {
this._updateShapePosition();
// Get the decimal part of the shape element width.
let decimalPart = this.el.getBoundingClientRect().width % 1;
// Round to two decimal places.
decimalPart = Math.round((decimalPart + Number.EPSILON) * 100) / 100;
// If there is a decimal part. (e.g. Chrome + browser zoom enabled)
if (decimalPart > 0) {
// Compensate for the gap by giving an integer width value to the
// shape by changing its "right" and "left" positions.
let offset = (decimalPart < 0.5 ? decimalPart : decimalPart - 1) / 2;
// This never causes the horizontal scrollbar to appear because it
// only appears if the overflow to the right exceeds 0.333px.
this._updateShapePosition(offset + 'px');
}
},
});
return {
Widget: publicWidget.Widget,
Animation: Animation,
@@ -867,7 +867,16 @@ options.registry.BackgroundShape.include({
return el;
}
return _getLastPreFilterLayerElement(this.$target);
}
},
/**
* @override
*/
_removeShapeEl(shapeEl) {
this.trigger_up('widgets_stop_request', {
$target: $(shapeEl),
});
return this._super(...arguments);
},
});
options.registry.BackgroundVideo = options.Class.extend({
@@ -1540,24 +1549,35 @@ options.registry.sizing_x = options.registry.sizing.extend({
* @override
*/
_onResize: function (compass, beginClass, current) {
if (compass === 'w') {
// don't change the right border position when we change the offset (replace col size)
var beginCol = Number(beginClass.match(/col-lg-([0-9]+)|$/)[1] || 0);
var beginOffset = Number(beginClass.match(/offset-lg-([0-9-]+)|$/)[1] || beginClass.match(/offset-xl-([0-9-]+)|$/)[1] || 0);
var offset = Number(this.grid.w[0][current].match(/offset-lg-([0-9-]+)|$/)[1] || 0);
if (offset < 0) {
offset = 0;
}
var colSize = beginCol - (offset - beginOffset);
if (colSize <= 0) {
colSize = 1;
offset = beginOffset + beginCol - 1;
}
this.$target.attr('class', this.$target.attr('class').replace(/\s*(offset-xl-|offset-lg-|col-lg-)([0-9-]+)/g, ''));
if (compass === 'w' || compass === 'e') {
const beginOffset = Number(beginClass.match(/offset-lg-([0-9-]+)|$/)[1] || beginClass.match(/offset-xl-([0-9-]+)|$/)[1] || 0);
this.$target.addClass('col-lg-' + (colSize > 12 ? 12 : colSize));
if (offset > 0) {
this.$target.addClass('offset-lg-' + offset);
if (compass === 'w') {
// don't change the right border position when we change the offset (replace col size)
var beginCol = Number(beginClass.match(/col-lg-([0-9]+)|$/)[1] || 0);
var offset = Number(this.grid.w[0][current].match(/offset-lg-([0-9-]+)|$/)[1] || 0);
if (offset < 0) {
offset = 0;
}
var colSize = beginCol - (offset - beginOffset);
if (colSize <= 0) {
colSize = 1;
offset = beginOffset + beginCol - 1;
}
this.$target.attr('class', this.$target.attr('class').replace(/\s*(offset-xl-|offset-lg-|col-lg-)([0-9-]+)/g, ''));
this.$target.addClass('col-lg-' + (colSize > 12 ? 12 : colSize));
if (offset > 0) {
this.$target.addClass('offset-lg-' + offset);
}
} else if (beginOffset > 0) {
const endCol = Number(this.grid.e[0][current].match(/col-lg-([0-9]+)|$/)[1] || 0);
// Avoids overflowing the grid to the right if the
// column size + the offset exceeds 12.
if ((endCol + beginOffset) > 12) {
this.$target[0].className = this.$target[0].className.replace(/\s*(col-lg-)([0-9-]+)/g, '');
this.$target[0].classList.add('col-lg-' + (12 - beginOffset));
}
}
}
this._super.apply(this, arguments);
@@ -197,16 +197,6 @@ body.editor_enable.editor_has_snippets {
}
}
.editor_enable {
@if o-website-value('header-template') == 'sidebar' {
#wrapwrap > header {
@if o-website-value('hamburger-position') != 'right' {
right: $o-we-sidebar-width;
}
}
}
}
// s_countdown preview classes
body.editor_enable {
.s_countdown {
@@ -13,6 +13,8 @@ const TableOfContent = publicWidget.Widget.extend({
*/
async start() {
await this._super(...arguments);
this.$scrollingElement = $().getScrollingElement();
this.previousPosition = -1;
this._updateTableOfContentNavbarPosition();
extraMenuUpdateCallbacks.push(this._updateTableOfContentNavbarPosition.bind(this));
},
@@ -42,7 +44,18 @@ const TableOfContent = publicWidget.Widget.extend({
const $mainNavBar = $('#oe_main_menu_navbar');
position += $mainNavBar.length ? $mainNavBar.outerHeight() : 0;
position += isHorizontalNavbar ? this.$target.outerHeight() : 0;
$().getScrollingElement().scrollspy({target: '.s_table_of_content_navbar', method: 'offset', offset: position + 100, alwaysKeepFirstActive: true});
if (this.previousPosition !== position) {
// The scrollSpy must be destroyed before calling it again.
// Otherwise the call has no effect.
this.$scrollingElement.scrollspy('dispose');
this.$scrollingElement.scrollspy({
target: '.s_table_of_content_navbar',
method: 'offset',
offset: position + 100,
alwaysKeepFirstActive: true,
});
this.previousPosition = position;
}
},
});
@@ -106,11 +106,11 @@ var TagCourseDialog = Dialog.extend({
formatNoMatches: false,
selection_data: false,
fetch_rpc_fnc: fetchFNC,
formatSelection: function (data) {
formatSelection: function (data, container, fmt) {
if (data.tag) {
data.text = data.tag;
}
return data.text;
return fmt(data.text);
},
createSearchChoice: function (term, data) {
var addedTags = $(this.opts.element).select2('data');
@@ -287,11 +287,11 @@ var SlideUploadDialog = Dialog.extend({
formatNoMatches: false,
selection_data: false,
fetch_rpc_fnc: fetchFNC,
formatSelection: function (data) {
formatSelection: function (data, container, fmt) {
if (data.tag) {
data.text = data.tag;
}
return data.text;
return fmt(data.text);
},
createSearchChoice: function (term, data) {
var addedTags = $(this.opts.element).select2('data');
@@ -54,11 +54,11 @@ var ChannelCreateDialog = Dialog.extend({
formatNoMatches: false,
multiple: true,
selection_data: false,
formatSelection: function (data) {
formatSelection: function (data, container, fmt) {
if (data.tag) {
data.text = data.tag;
}
return data.text;
return fmt(data.text);
},
createSearchChoice: function(term, data) {
var addedTags = $(this.opts.element).select2('data');