[MERGE] forward port branch 11.0 up to f6d36d81f7
This commit is contained in:
@@ -595,6 +595,18 @@ msgstr ""
|
||||
msgid "Error ! You cannot create a recursive Salary Structure."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_payroll
|
||||
#: code:addons/hr_payroll/models/hr_salary_rule.py:91
|
||||
#, python-format
|
||||
msgid "Error! You cannot create recursive hierarchy of Salary Rule Category."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_payroll
|
||||
#: code:addons/hr_payroll/models/hr_salary_rule.py:176
|
||||
#, python-format
|
||||
msgid "Error! You cannot create recursive hierarchy of Salary Rules."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_payroll
|
||||
#: model:ir.model.fields,help:hr_payroll.field_hr_payslip_line__register_id
|
||||
#: model:ir.model.fields,help:hr_payroll.field_hr_salary_rule__register_id
|
||||
|
||||
@@ -85,6 +85,11 @@ class HrSalaryRuleCategory(models.Model):
|
||||
company_id = fields.Many2one('res.company', string='Company',
|
||||
default=lambda self: self.env['res.company']._company_default_get())
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
if not self._check_recursion():
|
||||
raise ValidationError(_('Error! You cannot create recursive hierarchy of Salary Rule Category.'))
|
||||
|
||||
|
||||
class HrSalaryRule(models.Model):
|
||||
_name = 'hr.salary.rule'
|
||||
@@ -165,6 +170,11 @@ class HrSalaryRule(models.Model):
|
||||
input_ids = fields.One2many('hr.rule.input', 'input_id', string='Inputs', copy=True)
|
||||
note = fields.Text(string='Description')
|
||||
|
||||
@api.constrains('parent_rule_id')
|
||||
def _check_parent_rule_id(self):
|
||||
if not self._check_recursion(parent='parent_rule_id'):
|
||||
raise ValidationError(_('Error! You cannot create recursive hierarchy of Salary Rules.'))
|
||||
|
||||
@api.multi
|
||||
def _recursive_search_of_rules(self):
|
||||
"""
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
|
||||
&.o_chat_inline_composer {
|
||||
.o_composer_container {
|
||||
.o-flex(1, 0, auto);
|
||||
.o-flex(1, 1, auto);
|
||||
}
|
||||
.o_composer {
|
||||
padding: @o-mail-chatter-gap @o-mail-chatter-gap 0 @o-mail-chatter-gap;
|
||||
|
||||
@@ -45,6 +45,11 @@
|
||||
[<span t-esc="product.code"/>]
|
||||
</t>
|
||||
<span t-esc="product.name"/>
|
||||
<span t-foreach="product.attribute_value_ids" t-as="attribute_value">
|
||||
<span t-if="attribute_value_first">-</span>
|
||||
<span t-if="not attribute_value_last" t-esc="attribute_value.name+','"/>
|
||||
<span t-else="" t-esc="attribute_value.name"/>
|
||||
</span>
|
||||
</td>
|
||||
<t t-foreach="data['quantities']" t-as="quantity">
|
||||
<td><strong t-esc="categ_data['prices'][product.id][quantity]"
|
||||
|
||||
@@ -313,8 +313,12 @@ class PurchaseOrder(models.Model):
|
||||
self.payment_term_id = payment_term.id,
|
||||
self.company_id = requisition.company_id.id
|
||||
self.currency_id = requisition.currency_id.id
|
||||
self.origin = requisition.name
|
||||
self.partner_ref = requisition.name # to control vendor bill based on agreement reference
|
||||
if not self.origin or requisition.name not in self.origin.split(', '):
|
||||
if self.origin:
|
||||
if requisition.name:
|
||||
self.origin = self.origin + ', ' + requisition.name
|
||||
else:
|
||||
self.origin = requisition.name
|
||||
self.notes = requisition.description
|
||||
self.date_order = requisition.date_end or fields.Datetime.now()
|
||||
self.picking_type_id = requisition.picking_type_id.id
|
||||
|
||||
@@ -593,15 +593,19 @@ class ProductUoM(models.Model):
|
||||
def write(self, values):
|
||||
# Users can not update the factor if open stock moves are based on it
|
||||
if 'factor' in values or 'factor_inv' in values or 'category_id' in values:
|
||||
stock_move_lines = self.env['stock.move.line'].search_count([
|
||||
('product_uom_id.category_id', '=', self.category_id.id),
|
||||
('state', '!=', 'cancel'),
|
||||
])
|
||||
changed = self.filtered(
|
||||
lambda u: any(u[f] != values[f] if f in values else False
|
||||
for f in {'factor', 'factor_inv', 'category_id'}))
|
||||
if changed:
|
||||
stock_move_lines = self.env['stock.move.line'].search_count([
|
||||
('product_uom_id.category_id', 'in', changed.mapped('category_id.id')),
|
||||
('state', '!=', 'cancel'),
|
||||
])
|
||||
|
||||
if stock_move_lines:
|
||||
raise UserError(_(
|
||||
"You cannot change the ratio of this unit of mesure as some"
|
||||
" products with this UoM have already been moved or are "
|
||||
"currently reserved."
|
||||
))
|
||||
if stock_move_lines:
|
||||
raise UserError(_(
|
||||
"You cannot change the ratio of this unit of mesure as some"
|
||||
" products with this UoM have already been moved or are "
|
||||
"currently reserved."
|
||||
))
|
||||
return super(ProductUoM, self).write(values)
|
||||
|
||||
@@ -84,10 +84,10 @@ class Location(models.Model):
|
||||
if self.mapped('quant_ids'):
|
||||
raise UserError(_("This location's usage cannot be changed to view as it contains products."))
|
||||
if 'usage' in values or 'scrap_location' in values:
|
||||
|
||||
modified_locations = self.filtered(
|
||||
lambda l: l.usage != values.get('usage') or
|
||||
l.scrap_location != values.get('scrap_location')
|
||||
)
|
||||
lambda l: any(l[f] != values[f] if f in values else False
|
||||
for f in {'usage', 'scrap_location'}))
|
||||
reserved_quantities = self.env['stock.move.line'].search_count([
|
||||
('location_id', 'in', modified_locations.ids),
|
||||
('product_qty', '>', 0),
|
||||
|
||||
@@ -44,9 +44,6 @@ class TestStockCommon(common.TransactionCase):
|
||||
self.uom_kg = self.UomObj.create({
|
||||
'name': 'Test-KG',
|
||||
'category_id': self.categ_kgm,
|
||||
'factor_inv': 1,
|
||||
'factor': 1,
|
||||
'uom_type': 'reference',
|
||||
'rounding': 0.000001})
|
||||
self.uom_tone = self.UomObj.create({
|
||||
'name': 'Test-Tone',
|
||||
@@ -70,8 +67,6 @@ class TestStockCommon(common.TransactionCase):
|
||||
self.uom_unit = self.UomObj.create({
|
||||
'name': 'Test-Unit',
|
||||
'category_id': self.categ_unit,
|
||||
'factor': 1,
|
||||
'uom_type': 'reference',
|
||||
'rounding': 1.0})
|
||||
self.uom_dozen = self.UomObj.create({
|
||||
'name': 'Test-DozenA',
|
||||
|
||||
@@ -283,6 +283,7 @@
|
||||
<field name="model">stock.location.route</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Route">
|
||||
<field name="name"/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
|
||||
@@ -65,7 +65,7 @@ class Image(models.AbstractModel):
|
||||
atts["data-zoom"] = src_zoom and u'1' or None
|
||||
atts["data-zoom-image"] = src_zoom
|
||||
|
||||
atts = self.env['ir.qweb']._post_processing_att('img', atts, options)
|
||||
atts = self.env['ir.qweb']._post_processing_att('img', atts, options.get('template_options'))
|
||||
|
||||
img = ['<img']
|
||||
for name, value in atts.items():
|
||||
|
||||
@@ -173,6 +173,7 @@ class Contact(models.AbstractModel):
|
||||
@api.model
|
||||
def attributes(self, record, field_name, options, values):
|
||||
attrs = super(Contact, self).attributes(record, field_name, options, values)
|
||||
options.pop('template_options') # remove options not specific to this widget
|
||||
attrs['data-oe-contact-options'] = json.dumps(options)
|
||||
return attrs
|
||||
|
||||
|
||||
@@ -519,20 +519,24 @@ class IrActionsReport(models.Model):
|
||||
# we look on the pdf structure using pypdf to compute the outlines_pages that is
|
||||
# an array like [0, 3, 5] that means a new document start at page 0, 3 and 5.
|
||||
reader = PdfFileReader(pdf_content_stream)
|
||||
outlines_pages = sorted(
|
||||
[outline.getObject()[0] for outline in reader.trailer['/Root']['/Dests'].values()])
|
||||
assert len(outlines_pages) == len(res_ids)
|
||||
for i, num in enumerate(outlines_pages):
|
||||
to = outlines_pages[i + 1] if i + 1 < len(outlines_pages) else reader.numPages
|
||||
attachment_writer = PdfFileWriter()
|
||||
for j in range(num, to):
|
||||
attachment_writer.addPage(reader.getPage(j))
|
||||
stream = io.BytesIO()
|
||||
attachment_writer.write(stream)
|
||||
if res_ids[i] and res_ids[i] not in save_in_attachment:
|
||||
self.postprocess_pdf_report(record_map[res_ids[i]], stream)
|
||||
streams.append(stream)
|
||||
close_streams([pdf_content_stream])
|
||||
if reader.trailer['/Root'].get('/Dests'):
|
||||
outlines_pages = sorted(
|
||||
[outline.getObject()[0] for outline in reader.trailer['/Root']['/Dests'].values()])
|
||||
assert len(outlines_pages) == len(res_ids)
|
||||
for i, num in enumerate(outlines_pages):
|
||||
to = outlines_pages[i + 1] if i + 1 < len(outlines_pages) else reader.numPages
|
||||
attachment_writer = PdfFileWriter()
|
||||
for j in range(num, to):
|
||||
attachment_writer.addPage(reader.getPage(j))
|
||||
stream = io.BytesIO()
|
||||
attachment_writer.write(stream)
|
||||
if res_ids[i] and res_ids[i] not in save_in_attachment:
|
||||
self.postprocess_pdf_report(record_map[res_ids[i]], stream)
|
||||
streams.append(stream)
|
||||
close_streams([pdf_content_stream])
|
||||
else:
|
||||
# If no outlines available, do not save each record
|
||||
streams.append(pdf_content_stream)
|
||||
|
||||
# If attachment_use is checked, the records already having an existing attachment
|
||||
# are not been rendered by wkhtmltopdf. So, create a new stream for each of them.
|
||||
|
||||
@@ -51,21 +51,7 @@ class IrQWeb(models.AbstractModel, QWeb):
|
||||
if method.startswith('render_'):
|
||||
_logger.warning("Unused method '%s' is found in ir.qweb." % method)
|
||||
|
||||
def jsonable(val):
|
||||
try:
|
||||
json.dumps(val)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
context = {
|
||||
key: val
|
||||
for key, val in self.env.context.items()
|
||||
if isinstance(key, pycompat.string_types) and not key.startswith('_')
|
||||
if jsonable(val)
|
||||
}
|
||||
context['dev_mode'] = 'qweb' in tools.config['dev_mode']
|
||||
# context = dict(self.env.context, dev_mode='qweb' in tools.config['dev_mode'])
|
||||
context = dict(self.env.context, dev_mode='qweb' in tools.config['dev_mode'])
|
||||
context.update(options)
|
||||
|
||||
return super(IrQWeb, self).render(id_or_xml_id, values=values, **context)
|
||||
@@ -369,8 +355,7 @@ class IrQWeb(models.AbstractModel, QWeb):
|
||||
field = record._fields[field_name]
|
||||
|
||||
# adds template compile options for rendering fields
|
||||
for k, v in options.items():
|
||||
field_options.setdefault(k, v)
|
||||
field_options['template_options'] = options
|
||||
|
||||
# adds generic field options
|
||||
field_options['tagName'] = tagName
|
||||
@@ -392,6 +377,9 @@ class IrQWeb(models.AbstractModel, QWeb):
|
||||
return (attributes, content, inherit_branding or translate)
|
||||
|
||||
def _get_widget(self, value, expression, tagName, field_options, options, values):
|
||||
# adds template compile options for rendering fields
|
||||
field_options['template_options'] = options
|
||||
|
||||
field_options['type'] = field_options['widget']
|
||||
field_options['tagName'] = tagName
|
||||
field_options['expression'] = expression
|
||||
|
||||
@@ -261,7 +261,7 @@ class HTMLConverter(models.AbstractModel):
|
||||
for element in body.iter():
|
||||
if element.attrib:
|
||||
attrib = OrderedDict(element.attrib)
|
||||
attrib = irQweb._post_processing_att(element.tag, attrib, options)
|
||||
attrib = irQweb._post_processing_att(element.tag, attrib, options.get('template_options'))
|
||||
element.attrib.clear()
|
||||
element.attrib.update(attrib)
|
||||
return etree.tostring(body, encoding='unicode', method='html')[6:-7]
|
||||
@@ -488,7 +488,7 @@ class Contact(models.AbstractModel):
|
||||
'object': value,
|
||||
'options': options
|
||||
}
|
||||
return self.env['ir.qweb'].render('base.contact', val)
|
||||
return self.env['ir.qweb'].render('base.contact', val, **options.get('template_options'))
|
||||
|
||||
|
||||
class QwebView(models.AbstractModel):
|
||||
|
||||
Reference in New Issue
Block a user