[MERGE] forward report branch 11.0 up to daf9763e6e
This commit is contained in:
@@ -371,7 +371,7 @@ class AccountMoveLine(models.Model):
|
||||
else:
|
||||
date = partial_line.credit_move_id.date if partial_line.debit_move_id == line else partial_line.debit_move_id.date
|
||||
rate = line.currency_id.with_context(date=date).rate
|
||||
amount_residual_currency += sign_partial_line * line.currency_id.round(partial_line.amount * rate)
|
||||
amount_residual_currency += sign_partial_line * partial_line.amount * rate
|
||||
|
||||
#computing the `reconciled` field.
|
||||
reconciled = False
|
||||
|
||||
@@ -888,14 +888,23 @@ class Escpos:
|
||||
self._raw(PAPER_FULL_CUT)
|
||||
|
||||
|
||||
def cashdraw(self, pin):
|
||||
""" Send pulse to kick the cash drawer """
|
||||
if pin == 2:
|
||||
self._raw(CD_KICK_2)
|
||||
elif pin == 5:
|
||||
self._raw(CD_KICK_5)
|
||||
else:
|
||||
raise CashDrawerError()
|
||||
def cashdraw(self, pin, tries=5):
|
||||
""" Send pulse to kick the cash drawer
|
||||
|
||||
With some printers the drawer will not open after one pulse, for this reason we will check
|
||||
the drawer status up to 'tries' times and send a new pulse if the drawer is still closed. If the
|
||||
drawer status is open, we will stop sending pulses.
|
||||
"""
|
||||
for i in range(tries):
|
||||
if pin == 2:
|
||||
self._raw(CD_KICK_2)
|
||||
elif pin == 5:
|
||||
self._raw(CD_KICK_5)
|
||||
else:
|
||||
raise CashDrawerError()
|
||||
|
||||
if i != tries - 1 and not self.get_printer_status()['printer']['drawer_pin_high']:
|
||||
break
|
||||
|
||||
|
||||
def hw(self, hw):
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
'views/fleet_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/hr_contract_views.xml',
|
||||
'security/security.xml',
|
||||
],
|
||||
'auto_install': True,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Note for later versions, the group Employee Manager should become contract manager -->
|
||||
<record id="hr.group_hr_manager" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('fleet.fleet_group_manager'))]"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -479,25 +479,25 @@
|
||||
<field name='name'>119.01 IVA pendiente de pago</field>
|
||||
<field name='color'>4</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>A</field>
|
||||
<field name='nature'>D</field>
|
||||
</record>
|
||||
<record id='account_tag_119_02' model='account.account.tag'>
|
||||
<field name='name'>119.02 IVA de importación pendiente de pago</field>
|
||||
<field name='color'>4</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>A</field>
|
||||
<field name='nature'>D</field>
|
||||
</record>
|
||||
<record id='account_tag_119_03' model='account.account.tag'>
|
||||
<field name='name'>119.03 IEPS pendiente de pago</field>
|
||||
<field name='color'>4</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>A</field>
|
||||
<field name='nature'>D</field>
|
||||
</record>
|
||||
<record id='account_tag_119_04' model='account.account.tag'>
|
||||
<field name='name'>119.04 IEPS pendiente de pago en importación</field>
|
||||
<field name='color'>4</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>A</field>
|
||||
<field name='nature'>D</field>
|
||||
</record>
|
||||
<record id='account_tag_120_01' model='account.account.tag'>
|
||||
<field name='name'>120.01 Anticipo a proveedores nacional</field>
|
||||
@@ -917,7 +917,7 @@
|
||||
<field name='name'>180.01 Crédito mercantil</field>
|
||||
<field name='color'>4</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>A</field>
|
||||
<field name='nature'>D</field>
|
||||
</record>
|
||||
<record id='account_tag_181_01' model='account.account.tag'>
|
||||
<field name='name'>181.01 Gastos de instalación</field>
|
||||
@@ -2423,7 +2423,7 @@
|
||||
<field name='name'>503 Devoluciones, descuentos o bonificaciones sobre compras</field>
|
||||
<field name='color'>1</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>D</field>
|
||||
<field name='nature'>A</field>
|
||||
</record>
|
||||
<record id='account_tag_503_01' model='account.account.tag'>
|
||||
<field name='name'>503.01 Devoluciones, descuentos o bonificaciones sobre compras</field>
|
||||
@@ -2585,13 +2585,13 @@
|
||||
<field name='name'>505.01 Costo por venta de activo fijo</field>
|
||||
<field name='color'>4</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>A</field>
|
||||
<field name='nature'>D</field>
|
||||
</record>
|
||||
<record id='account_tag_505_02' model='account.account.tag'>
|
||||
<field name='name'>505.02 Costo por baja de activo fijo</field>
|
||||
<field name='color'>4</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>A</field>
|
||||
<field name='nature'>D</field>
|
||||
</record>
|
||||
<record id='account_tag_601_01' model='account.account.tag'>
|
||||
<field name='name'>601.01 Sueldos y salarios</field>
|
||||
@@ -4781,7 +4781,7 @@
|
||||
<field name='name'>607.01 Participación de los trabajadores en las utilidades</field>
|
||||
<field name='color'>4</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>A</field>
|
||||
<field name='nature'>D</field>
|
||||
</record>
|
||||
<record id='account_tag_608_01' model='account.account.tag'>
|
||||
<field name='name'>608.01 Participación en resultados de subsidiarias</field>
|
||||
@@ -4799,7 +4799,7 @@
|
||||
<field name='name'>610.01 Participación de los trabajadores en las utilidades diferida</field>
|
||||
<field name='color'>4</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>A</field>
|
||||
<field name='nature'>D</field>
|
||||
</record>
|
||||
<record id='account_tag_611_01' model='account.account.tag'>
|
||||
<field name='name'>611.01 Impuesto Sobre la renta</field>
|
||||
@@ -4817,7 +4817,7 @@
|
||||
<field name='name'>612.01 Gastos no deducibles para CUFIN</field>
|
||||
<field name='color'>4</field>
|
||||
<field name='applicability'>accounts</field>
|
||||
<field name='nature'>A</field>
|
||||
<field name='nature'>D</field>
|
||||
</record>
|
||||
<record id='account_tag_613_01' model='account.account.tag'>
|
||||
<field name='name'>613.01 Depreciación de edificios</field>
|
||||
|
||||
@@ -103,7 +103,7 @@ class MailActivity(models.Model):
|
||||
('today', 'Today'),
|
||||
('planned', 'Planned')], 'State',
|
||||
compute='_compute_state')
|
||||
recommended_activity_type_id = fields.Many2one('mail.activity.type', string="Recommended Activity Type", readonly=True)
|
||||
recommended_activity_type_id = fields.Many2one('mail.activity.type', string="Recommended Activity Type")
|
||||
previous_activity_type_id = fields.Many2one('mail.activity.type', string='Previous Activity Type', readonly=True)
|
||||
has_recommended_activities = fields.Boolean(
|
||||
'Next activities available',
|
||||
|
||||
@@ -5,6 +5,7 @@ import base64
|
||||
import datetime
|
||||
import logging
|
||||
import psycopg2
|
||||
import smtplib
|
||||
import threading
|
||||
|
||||
from collections import defaultdict
|
||||
@@ -150,6 +151,7 @@ class MailMail(models.Model):
|
||||
if notif_emails:
|
||||
notifications = self.env['mail.notification'].search([
|
||||
('mail_message_id', 'in', notif_emails.mapped('mail_message_id').ids),
|
||||
('res_partner_id', 'in', notif_emails.mapped('recipient_ids').ids),
|
||||
('is_email', '=', True)])
|
||||
if mail_sent:
|
||||
notifications.write({
|
||||
@@ -301,6 +303,20 @@ class MailMail(models.Model):
|
||||
})
|
||||
mail_sent = False
|
||||
|
||||
# Update notification in a transient exception state to avoid concurrent
|
||||
# update in case an email bounces while sending all emails related to current
|
||||
# mail record.
|
||||
notifs = self.env['mail.notification'].search([
|
||||
('is_email', '=', True),
|
||||
('mail_message_id', 'in', mail.mapped('mail_message_id').ids),
|
||||
('res_partner_id', 'in', mail.mapped('recipient_ids').ids),
|
||||
('email_status', 'not in', ('sent', 'canceled'))
|
||||
])
|
||||
if notifs:
|
||||
notifs.sudo().write({
|
||||
'email_status': 'exception',
|
||||
})
|
||||
|
||||
# build an RFC2822 email.message.Message object and send it without queuing
|
||||
res = None
|
||||
for email in email_list:
|
||||
@@ -348,11 +364,12 @@ class MailMail(models.Model):
|
||||
'MemoryError while processing mail with ID %r and Msg-Id %r. Consider raising the --limit-memory-hard startup option',
|
||||
mail.id, mail.message_id)
|
||||
raise
|
||||
except psycopg2.Error:
|
||||
# If an error with the database occurs, chances are that the cursor is unusable.
|
||||
# This will lead to an `psycopg2.InternalError` being raised when trying to write
|
||||
# `state`, shadowing the original exception and forbid a retry on concurrent
|
||||
# update. Let's bubble it.
|
||||
except (psycopg2.Error, smtplib.SMTPServerDisconnected):
|
||||
# If an error with the database or SMTP session occurs, chances are that the cursor
|
||||
# or SMTP session are unusable, causing further errors when trying to save the state.
|
||||
_logger.exception(
|
||||
'Exception while processing mail with ID %r and Msg-Id %r.',
|
||||
mail.id, mail.message_id)
|
||||
raise
|
||||
except Exception as e:
|
||||
failure_reason = tools.ustr(e)
|
||||
|
||||
@@ -353,6 +353,7 @@ class MailComposer(models.TransientModel):
|
||||
- normal mode: return rendered values
|
||||
/!\ for x2many field, this onchange return command instead of ids
|
||||
"""
|
||||
attachment_ids = []
|
||||
if template_id and composition_mode == 'mass_mail':
|
||||
template = self.env['mail.template'].browse(template_id)
|
||||
fields = ['subject', 'body_html', 'email_from', 'reply_to', 'mail_server_id']
|
||||
@@ -378,7 +379,7 @@ class MailComposer(models.TransientModel):
|
||||
'res_id': 0,
|
||||
'type': 'binary', # override default_type from context, possibly meant for another model!
|
||||
}
|
||||
values.setdefault('attachment_ids', list()).append(Attachment.create(data_attach).id)
|
||||
attachment_ids.append(Attachment.create(data_attach).id)
|
||||
else:
|
||||
default_values = self.with_context(default_composition_mode=composition_mode, default_model=model, default_res_id=res_id).default_get(['composition_mode', 'model', 'res_id', 'parent_id', 'partner_ids', 'subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'])
|
||||
values = dict((key, default_values[key]) for key in ['subject', 'body', 'partner_ids', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'] if key in default_values)
|
||||
@@ -391,6 +392,8 @@ class MailComposer(models.TransientModel):
|
||||
# this force the complete replacement of x2many field with
|
||||
# command and is compatible with onchange api.v7
|
||||
values = self._convert_to_write(values)
|
||||
if attachment_ids:
|
||||
values.update(attachment_ids=[(6, 0, attachment_ids)])
|
||||
|
||||
return {'value': values}
|
||||
|
||||
|
||||
@@ -458,7 +458,7 @@ var Gui = core.Class.extend({
|
||||
}
|
||||
} else if (input === '-') {
|
||||
if (options.firstinput) {
|
||||
newbuf = '-';
|
||||
newbuf = '-0';
|
||||
} else if ( newbuf[0] === '-' ) {
|
||||
newbuf = newbuf.substring(1,newbuf.length);
|
||||
} else {
|
||||
@@ -473,6 +473,9 @@ var Gui = core.Class.extend({
|
||||
newbuf += input;
|
||||
}
|
||||
}
|
||||
if (newbuf === "-") {
|
||||
newbuf = "";
|
||||
}
|
||||
|
||||
// End of input buffer at 12 characters.
|
||||
if (newbuf.length > buffer.length && newbuf.length > 12) {
|
||||
|
||||
@@ -1040,7 +1040,7 @@ class SaleOrderLine(models.Model):
|
||||
result.setdefault(so_line_id, 0.0)
|
||||
uom = product_uom_map.get(item['product_uom_id'][0])
|
||||
if so_line.product_uom.category_id == uom.category_id:
|
||||
qty = uom._compute_quantity(item['unit_amount'], so_line.product_uom)
|
||||
qty = uom._compute_quantity(item['unit_amount'], so_line.product_uom, rounding_method='HALF-UP')
|
||||
else:
|
||||
qty = item['unit_amount']
|
||||
result[so_line_id] += qty
|
||||
|
||||
@@ -825,6 +825,10 @@ registry.gallerySlider = Animation.extend({
|
||||
destroy: function () {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
if (!this.$indicator) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$prev.prependTo(this.$indicator);
|
||||
this.$next.appendTo(this.$indicator);
|
||||
this.$carousel.off('.gallery_slider');
|
||||
|
||||
@@ -170,18 +170,12 @@
|
||||
<template id="references_block" name="Partner References Block">
|
||||
<t t-if="any([p.website_published for p in partner.implemented_partner_ids])">
|
||||
<h3 id="references">References</h3>
|
||||
<div t-foreach="partner.implemented_partner_ids" t-as="reference" class="media">
|
||||
<t t-if="reference.website_published">
|
||||
<a class="pull-left" t-attf-href="/customers/#{slug(reference)}">
|
||||
<span t-field="reference.image_small" t-options='{"widget": "image", "class": "center-block"}'/>
|
||||
</a>
|
||||
<div t-foreach="partner.implemented_partner_ids" t-if="reference.website_published" t-as="reference" class="media">
|
||||
<span t-field="reference.image_small" t-options='{"widget": "image", "class": "center-block pull-left"}'/>
|
||||
<div class="media-body" style="min-height: 64px;">
|
||||
<a class="media-heading" t-attf-href="/customers/#{slug(reference)}">
|
||||
<span t-field="reference.self"/>
|
||||
</a>
|
||||
<span class="media-heading" t-field="reference.self"/>
|
||||
<div t-field='reference.website_short_description'/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
@@ -239,4 +239,13 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="references_block_href" inherit_id="website_crm_partner_assign.references_block" name="Partner References Block">
|
||||
<xpath expr="//div/span" position="replace">
|
||||
<a class="pull-left" t-attf-href="/customers/#{slug(reference)}">$0</a>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('media-body')]/span" position="replace">
|
||||
<a class="media-heading" t-attf-href="/customers/#{slug(reference)}">$0</a>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
Germany, 2019-05-14
|
||||
|
||||
ecoservice GbR agrees to the terms of the Odoo Corporate Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Falk Neubert neubert@ecoservice.de https://github.com/FNeu75
|
||||
|
||||
List of contributors:
|
||||
|
||||
* Christian Engelhardt c.engelhardt@ecoservice.de https://github.com/c-engelhardt-ecoservice
|
||||
* Christian Schöttke c.schoettke@ecoservice.de https://github.com/cschoettke
|
||||
* Falk Neubert neubert@ecoservice.de https://github.com/FNeu75
|
||||
* Gülhan Celik g.celik@ecoservice.de https://github.com/guelhancelik
|
||||
* Jan Brodersen 4rmitxes@gmail.com https://github.com/Armitxes
|
||||
* Kamal Prajapati k.prajapati@ecoservice.de https://github.com/kamalprajapati
|
||||
* Marcel Much m.much@ecoservice.de https://github.com/mmuch
|
||||
@@ -12,17 +12,24 @@ João Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joa
|
||||
|
||||
List of contributors:
|
||||
|
||||
Agathe Mollé agathe.molle@savoirfairelinux.com https://github.com/Ehtaga
|
||||
Bruno Joliveau bruno.joliveau@savoirfairelinux.com https://github.com/bjoliveau
|
||||
David Dufresne david.dufresne@savoirfairelinux.com https://github.com/dufresnedavid
|
||||
Istvan SZALAÏ istvan.szalai@savoirfairelinux.com https://github.com/ventilooo
|
||||
João Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joaoalf
|
||||
Julien Jezequel-Breard julien.jezequel-breard@savoirfairelinux.com https://github.com/jjbreard
|
||||
Quentin Lavallée-Bourdeau quentin.lavallee@savoirfairelinux.com https://github.com/qtiplb
|
||||
|
||||
Rim Ben Dhaou rim.bendhaou@savoirfairelinux.com https://github.com/rimbendhaou
|
||||
Yasmine El Mrini yasmine.elmrini@savoirfairelinux.com https://github.com/yasmineelmrini
|
||||
Luis Garcia Ruiz luis.garcia@savoirfairelinux.com https://github.com/eilst
|
||||
Dimitar Apostolov dimitar.apostolov@savoirfairelinux.com https://github.com/dapostolov-sfl
|
||||
Pierre Gault pierre.gault@savoirfairelinux.com https://github.com/gaultp
|
||||
Maroua Romdhane maroua.romdhane@savoirfairelinux.com https://github.com/mromdhane
|
||||
Jean-François Bolduc jean-francois.bolduc@savoirfairelinux.com https://github.com/fjdrake
|
||||
Blaise Frison blaise.frison@savoirfairelinux.com https://github.com/bfrison-sfl
|
||||
Larbi Gharib larbi.gharib@savoirfairelinux.com https://github.com/lgharib
|
||||
Jananjoy Rajkumar jananjoy.rajkumar@savoirfairelinux.com https://github.com/jananjoy
|
||||
Yasmine El Mrini yasmine.elmrini@savoirfairelinux.com https://github.com/yasmineelmrini (up to 2019-05-13)
|
||||
Agathe Mollé agathe.molle@savoirfairelinux.com https://github.com/Ehtaga (up to 2019-05-13)
|
||||
Bruno Joliveau bruno.joliveau@savoirfairelinux.com https://github.com/bjoliveau (up to 2019-05-13)
|
||||
David Dufresne david.dufresne@savoirfairelinux.com https://github.com/dufresnedavid (up to 2019-05-13)
|
||||
Istvan SZALAÏ istvan.szalai@savoirfairelinux.com https://github.com/ventilooo (up to 2019-05-13)
|
||||
João Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joaoalf (up to 2019-05-13)
|
||||
Julien Jezequel-Breard julien.jezequel-breard@savoirfairelinux.com https://github.com/jjbreard (up to 2019-05-13)
|
||||
Quentin Lavallée-Bourdeau quentin.lavallee@savoirfairelinux.com https://github.com/qtiplb (up to 2019-05-13)
|
||||
Jérome Boisvert-Chouinard jerome.boisvertchouinard@savoirfairelinux.com https://github.com/jbchouinard (up to 2017-10-17)
|
||||
Julie Moussu julie.mousse@savoirfairelinux.com https://github.com/JulieSFL (up to 2017-10-17)
|
||||
Pierre Lamarche pierre.lamarche@savoirfairelinux.com https://github.com/plamarche (up to 2017-10-17)
|
||||
@@ -34,6 +41,5 @@ Jordi Riera jordi.riera@savoirfairelinux.com https://github.com/foutoucour (up t
|
||||
Julien Roux julien.roux@savoirfairelinux.com https://github.com/jrouxsfl (up to 2017-05-12)
|
||||
Loïc Faure-Lacroix loic.lacroix@savoirfairelinux.com https://github.com/llacroix (up to 2017-05-12)
|
||||
Maxime Chambreuil maxime.chambreuil@savoirfairelinux.com https://github.com/max3903 (up to 2017-05-12)
|
||||
Pierre Gault pierre.gault@savoirfairelinux.com https://github.com/gaultp (up to 2017-05-12)
|
||||
Sandy Carter sandy.carter@savoirfairelinux.com https://github.com/bwrsandman (up to 2017-05-12)
|
||||
Vincent Vinet vincent.vinet@savoirfairelinux.com https://github.com/veloutin (up to 2017-05-12)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Belgium, 2019-05-09
|
||||
|
||||
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Geert Janssens geert@kobaltwit.be https://github.com/gjanssens
|
||||
@@ -119,10 +119,18 @@ def encode_rfc2822_address_header(header_text):
|
||||
# Header as a string, using an unlimited line length.", the old one
|
||||
# was "A synonym for Header.encode()." so call encode() directly?
|
||||
name = Header(pycompat.to_text(name)).encode()
|
||||
return formataddr((name, email))
|
||||
# if the from does not follow the (name <addr>),* convention, we might
|
||||
# try to encode meaningless strings as address, as getaddresses is naive
|
||||
# note it would also fail on real addresses with non-ascii characters
|
||||
try:
|
||||
return formataddr((name, email))
|
||||
except UnicodeEncodeError:
|
||||
_logger.warning(_('Failed to encode the address %s\n'
|
||||
'from mail header:\n%s') % addr, header_text)
|
||||
return ""
|
||||
|
||||
addresses = getaddresses([pycompat.to_native(ustr(header_text))])
|
||||
return COMMASPACE.join(encode_addr(a) for a in addresses)
|
||||
return COMMASPACE.join(a for a in (encode_addr(addr) for addr in addresses) if a)
|
||||
|
||||
|
||||
class IrMailServer(models.Model):
|
||||
@@ -481,6 +489,8 @@ class IrMailServer(models.Model):
|
||||
# do not quit() a pre-established smtp_session
|
||||
if not smtp_session:
|
||||
smtp.quit()
|
||||
except smtplib.SMTPServerDisconnected:
|
||||
raise
|
||||
except Exception as e:
|
||||
params = (ustr(smtp_server), e.__class__.__name__, ustr(e))
|
||||
msg = _("Mail delivery failed via SMTP server '%s'.\n%s: %s") % params
|
||||
|
||||
@@ -874,9 +874,9 @@ class UsersView(models.Model):
|
||||
group_multi_company = self.env.ref('base.group_multi_company', False)
|
||||
if group_multi_company and 'company_ids' in values:
|
||||
if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids:
|
||||
group_multi_company.write({'users': [(3, user.id)]})
|
||||
user.write({'groups_id': [(3, group_multi_company.id)]})
|
||||
elif len(user.company_ids) > 1 and user.id not in group_multi_company.users.ids:
|
||||
group_multi_company.write({'users': [(4, user.id)]})
|
||||
user.write({'groups_id': [(4, group_multi_company.id)]})
|
||||
return user
|
||||
|
||||
@api.multi
|
||||
@@ -887,9 +887,9 @@ class UsersView(models.Model):
|
||||
if group_multi_company and 'company_ids' in values:
|
||||
for user in self:
|
||||
if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids:
|
||||
group_multi_company.write({'users': [(3, user.id)]})
|
||||
user.write({'groups_id': [(3, group_multi_company.id)]})
|
||||
elif len(user.company_ids) > 1 and user.id not in group_multi_company.users.ids:
|
||||
group_multi_company.write({'users': [(4, user.id)]})
|
||||
user.write({'groups_id': [(4, group_multi_company.id)]})
|
||||
return res
|
||||
|
||||
def _remove_reified_groups(self, values):
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
|
||||
@@ -5,4 +5,5 @@
|
||||
'name': 'test_convert',
|
||||
'description': "Data for xml conversion tests",
|
||||
'version': '0.0.1',
|
||||
'data': ['ir.model.access.csv'],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_test_convert_test_model,access_test_convert_test_model,model_test_convert_test_model,,1,1,1,1
|
||||
|
@@ -0,0 +1,16 @@
|
||||
from odoo import api, models
|
||||
|
||||
class TestModel(models.Model):
|
||||
_name = 'test_convert.test_model'
|
||||
|
||||
@api.model
|
||||
def action_test_date(self, today_date):
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def action_test_time(self, cur_time):
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def action_test_timezone(self, timezone):
|
||||
return True
|
||||
@@ -4,8 +4,10 @@
|
||||
import collections
|
||||
import unittest
|
||||
|
||||
from lxml import etree as ET
|
||||
from lxml.builder import E
|
||||
|
||||
import odoo
|
||||
from odoo.tests import common
|
||||
from odoo.tools.convert import _eval_xml
|
||||
|
||||
@@ -16,6 +18,25 @@ class TestEvalXML(common.TransactionCase):
|
||||
def eval_xml(self, node, obj=None):
|
||||
return _eval_xml(obj, node, self.env)
|
||||
|
||||
def test_function_eval(self):
|
||||
def id_get(): pass
|
||||
Obj = collections.namedtuple('Obj', ['module', 'idref', 'id_get'])
|
||||
obj = Obj('test_convert', {}, id_get)
|
||||
|
||||
try:
|
||||
test_datetime = ET.XML("<function name='action_test_date' model='test_convert.test_model' eval='[datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")]'/>")
|
||||
self.eval_xml(node=test_datetime, obj=obj)
|
||||
test_time = ET.XML("<function name='action_test_time' model='test_convert.test_model' eval='[time.strftime(\"%Y-%m-%d %H:%M:%S\")]'/>")
|
||||
self.eval_xml(node=test_time, obj=obj)
|
||||
test_timedelta = ET.XML("<function name='action_test_date' model='test_convert.test_model' eval='[(datetime.today()-timedelta(days=365)).strftime(\"%Y-%m-%d %H:%M:%S\")]'/>")
|
||||
self.eval_xml(node=test_timedelta, obj=obj)
|
||||
test_relativedelta = ET.XML("<function name='action_test_date' model='test_convert.test_model' eval='[(datetime.today()+relativedelta(months=3)).strftime(\"%Y-%m-%d %H:%M:%S\")]'/>")
|
||||
self.eval_xml(node=test_relativedelta, obj=obj)
|
||||
test_timezone = ET.XML("<function name='action_test_timezone' model='test_convert.test_model' eval='[pytz.timezone(\"Asia/Calcutta\")]'/>")
|
||||
self.eval_xml(node=test_timezone, obj=obj)
|
||||
except ValueError as e:
|
||||
self.fail(e.message)
|
||||
|
||||
def test_char(self):
|
||||
self.assertEqual(
|
||||
self.eval_xml(Field("foo")),
|
||||
|
||||
@@ -179,15 +179,16 @@ def _eval_xml(self, node, env):
|
||||
elif node.tag == "function":
|
||||
args = []
|
||||
a_eval = node.get('eval')
|
||||
model_str = node.get('model')
|
||||
# FIXME: should probably be exclusive
|
||||
if a_eval:
|
||||
self.idref['ref'] = self.id_get
|
||||
args = safe_eval(a_eval, self.idref)
|
||||
idref2 = _get_idref(self, env, model_str, self.idref)
|
||||
args = safe_eval(a_eval, idref2)
|
||||
for n in node:
|
||||
return_val = _eval_xml(self, n, env)
|
||||
if return_val is not None:
|
||||
args.append(return_val)
|
||||
model = env[node.get('model')]
|
||||
model = env[model_str]
|
||||
method = node.get('name')
|
||||
# this one still depends on the old API
|
||||
return odoo.api.call_kw(model, method, args, {})
|
||||
|
||||
Reference in New Issue
Block a user