[MERGE] forward port branch 11.0 up to f6d36d81f7

This commit is contained in:
Christophe Simonis
2018-06-28 10:32:03 +02:00
14 changed files with 79 additions and 55 deletions
+12
View File
@@ -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):
"""
+1 -1
View File
@@ -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
+14 -10
View File
@@ -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)
+3 -3
View File
@@ -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),
-5
View File
@@ -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>
+1 -1
View File
@@ -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():
+1
View File
@@ -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
+18 -14
View File
@@ -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.
+5 -17
View File
@@ -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
+2 -2
View File
@@ -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):