[MERGE] forward port branch saas-15 up to d44cd77884
This commit is contained in:
@@ -1507,6 +1507,13 @@ class AccountInvoiceLine(models.Model):
|
||||
return accounts['income']
|
||||
return accounts['expense']
|
||||
|
||||
def _set_currency(self):
|
||||
company = self.invoice_id.company_id
|
||||
currency = self.invoice_id.currency_id
|
||||
if company and currency:
|
||||
if company.currency_id != currency:
|
||||
self.price_unit = self.price_unit * currency.with_context(dict(self._context or {}, date=self.invoice_id.date_invoice)).rate
|
||||
|
||||
def _set_taxes(self):
|
||||
""" Used in on_change to set taxes and price."""
|
||||
if self.invoice_id.type in ('out_invoice', 'out_refund'):
|
||||
@@ -1525,8 +1532,10 @@ class AccountInvoiceLine(models.Model):
|
||||
prec = self.env['decimal.precision'].precision_get('Product Price')
|
||||
if not self.price_unit or float_compare(self.price_unit, self.product_id.standard_price, precision_digits=prec) == 0:
|
||||
self.price_unit = fix_price(self.product_id.standard_price, taxes, fp_taxes)
|
||||
self._set_currency()
|
||||
else:
|
||||
self.price_unit = fix_price(self.product_id.lst_price, taxes, fp_taxes)
|
||||
self._set_currency()
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id(self):
|
||||
@@ -1575,8 +1584,6 @@ class AccountInvoiceLine(models.Model):
|
||||
domain['uom_id'] = [('category_id', '=', product.uom_id.category_id.id)]
|
||||
|
||||
if company and currency:
|
||||
if company.currency_id != currency:
|
||||
self.price_unit = self.price_unit * currency.with_context(dict(self._context or {}, date=self.invoice_id.date_invoice)).rate
|
||||
|
||||
if self.uom_id and self.uom_id.id != product.uom_id.id:
|
||||
self.price_unit = product.uom_id._compute_price(self.price_unit, self.uom_id)
|
||||
|
||||
@@ -14,7 +14,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
'account.journal',
|
||||
related='company_id.currency_exchange_journal_id',
|
||||
string="Exchange Gain or Loss Journal",
|
||||
domain=[('type', '=', 'general')],
|
||||
domain="[('company_id', '=', company_id), ('type', '=', 'general')]",
|
||||
help='The accounting journal where automatic exchange differences will be registered')
|
||||
has_chart_of_accounts = fields.Boolean(compute='_compute_has_chart_of_accounts', string='Company has a chart of accounts')
|
||||
chart_template_id = fields.Many2one('account.chart.template', string='Template',
|
||||
|
||||
@@ -248,14 +248,16 @@
|
||||
<span><t t-esc="dashboard.account_balance"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" name="latest_statement" t-if="dashboard.last_balance != dashboard.account_balance">
|
||||
<div class="col-xs-6">
|
||||
<span title="Latest Statement">Latest Statement</span>
|
||||
<t t-if="dashboard.last_balance != dashboard.account_balance">
|
||||
<div class="row" name="latest_statement">
|
||||
<div class="col-xs-6">
|
||||
<span title="Latest Statement">Latest Statement</span>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<span><t t-esc="dashboard.last_balance"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<span><t t-esc="dashboard.last_balance"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="JournalBodySalePurchase" id="account.JournalBodySalePurchase">
|
||||
|
||||
@@ -50,7 +50,11 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
# Create the bank statements
|
||||
statement_ids, notifications = self._create_bank_statements(stmts_vals)
|
||||
# Now that the import worked out, set it as the bank_statements_source of the journal
|
||||
journal.bank_statements_source = 'file_import'
|
||||
if journal.bank_statements_source != 'file_import':
|
||||
# Use sudo() because only 'account.group_account_manager'
|
||||
# has write access on 'account.journal', but 'account.group_account_user'
|
||||
# must be able to import bank statement files
|
||||
journal.sudo().bank_statements_source = 'file_import'
|
||||
# Finally dispatch to reconciliation interface
|
||||
action = self.env.ref('account.action_bank_reconcile_bank_statements')
|
||||
return {
|
||||
|
||||
@@ -10,7 +10,7 @@ class ResPartner(models.Model):
|
||||
|
||||
def _compute_event_count(self):
|
||||
for partner in self:
|
||||
partner.event_count = self.env['event.event'].search_count([('registration_ids.partner_id', 'child_of', partner.ids)])
|
||||
partner.event_count = self.env['event.event'].sudo().search_count([('registration_ids.partner_id', 'child_of', partner.ids)])
|
||||
|
||||
@api.multi
|
||||
def action_event_view(self):
|
||||
|
||||
@@ -80,6 +80,8 @@ class MrpBom(models.Model):
|
||||
def onchange_product_tmpl_id(self):
|
||||
if self.product_tmpl_id:
|
||||
self.product_uom_id = self.product_tmpl_id.uom_id.id
|
||||
if self.product_id.product_tmpl_id != self.product_tmpl_id:
|
||||
self.product_id = False
|
||||
|
||||
@api.onchange('routing_id')
|
||||
def onchange_routing_id(self):
|
||||
|
||||
@@ -358,6 +358,7 @@ class MrpWorkorder(models.Model):
|
||||
move_line = production_move.move_line_ids.filtered(lambda x: x.lot_id.id == self.final_lot_id.id)
|
||||
if move_line:
|
||||
move_line.product_uom_qty += self.qty_producing
|
||||
move_line.qty_done += self.qty_producing
|
||||
else:
|
||||
move_line.create({'move_id': production_move.id,
|
||||
'product_id': production_move.product_id.id,
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
<label for="duration"/>
|
||||
<div>
|
||||
<button style="pointer-events: none;" class="oe_inline label label-default">
|
||||
<field name="duration" widget="mrp_time_counter"/>
|
||||
<field name="duration" widget="mrp_time_counter" help="Time the currently logged user spent on this workorder."/>
|
||||
</button>
|
||||
</div>
|
||||
</group>
|
||||
|
||||
@@ -38,8 +38,8 @@ class PosSession(models.Model):
|
||||
paid=order.amount_paid,
|
||||
))
|
||||
order.action_pos_order_done()
|
||||
orders = session.order_ids.filtered(lambda order: order.state in ['invoiced', 'done'])
|
||||
orders.sudo()._reconcile_payments()
|
||||
orders_to_reconcile = session.order_ids.filtered(lambda order: order.state in ['invoiced', 'done'] and order.partner_id)
|
||||
orders_to_reconcile.sudo()._reconcile_payments()
|
||||
|
||||
config_id = fields.Many2one(
|
||||
'pos.config', string='Point of Sale',
|
||||
|
||||
@@ -140,7 +140,7 @@ class PortalWizardUser(models.TransientModel):
|
||||
if wizard_user.partner_id.company_id:
|
||||
company_id = wizard_user.partner_id.company_id.id
|
||||
else:
|
||||
company_id = self.env['res.company']._company_default_get('res.users')
|
||||
company_id = self.env['res.company']._company_default_get('res.users').id
|
||||
user_portal = wizard_user.sudo().with_context(company_id=company_id)._create_user()
|
||||
else:
|
||||
user_portal = user
|
||||
|
||||
@@ -222,6 +222,7 @@ class ProductProduct(models.Model):
|
||||
for supplier_info in self.seller_ids:
|
||||
if supplier_info.name.id == self._context.get('partner_id'):
|
||||
self.code = supplier_info.product_code or self.default_code
|
||||
break
|
||||
else:
|
||||
self.code = self.default_code
|
||||
|
||||
@@ -230,6 +231,7 @@ class ProductProduct(models.Model):
|
||||
for supplier_info in self.seller_ids:
|
||||
if supplier_info.name.id == self._context.get('partner_id'):
|
||||
product_name = supplier_info.product_name or self.default_code
|
||||
break
|
||||
self.partner_ref = '%s%s' % (self.code and '[%s] ' % self.code or '', product_name)
|
||||
else:
|
||||
self.partner_ref = self.name_get()[0][1]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_variants, test_uom, test_pricelist, test_product_pricelist
|
||||
from . import test_seller, test_variants, test_uom, test_pricelist, test_product_pricelist
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestSeller(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSeller, self).setUp()
|
||||
self.product_service = self.env.ref('product.product_product_2')
|
||||
self.product_service.default_code = 'DEFCODE'
|
||||
self.asustec = self.env.ref('base.res_partner_1')
|
||||
self.camptocamp = self.env.ref('base.res_partner_12')
|
||||
|
||||
def test_10_sellers(self):
|
||||
self.product_service.write({'seller_ids': [
|
||||
(0, 0, {'name': self.asustec.id, 'product_code': 'ASUCODE'}),
|
||||
(0, 0, {'name': self.camptocamp.id, 'product_code': 'C2CCODE'}),
|
||||
]})
|
||||
|
||||
default_code = self.product_service.code
|
||||
self.assertEqual("DEFCODE", default_code, "Default code not used in product name")
|
||||
|
||||
context_code = self.product_service\
|
||||
.with_context(partner_id=self.camptocamp.id)\
|
||||
.code
|
||||
self.assertEqual('C2CCODE', context_code, "Partner's code not used in product name with context set")
|
||||
@@ -753,7 +753,7 @@ class Task(models.Model):
|
||||
|
||||
groups = [new_group] + groups
|
||||
for group_name, group_method, group_data in groups:
|
||||
if group_name in ['customer', 'portal']:
|
||||
if group_name == 'customer':
|
||||
continue
|
||||
group_data['has_button_access'] = True
|
||||
|
||||
|
||||
@@ -59,12 +59,16 @@ var EventRegistrationForm = Widget.extend({
|
||||
$button.attr('disabled', true);
|
||||
return ajax.jsonRpc($form.attr('action'), 'call', post).then(function (modal) {
|
||||
var $modal = $(modal);
|
||||
$modal.modal({backdrop: 'static', keyboard: false});
|
||||
$modal.find('.modal-body > div').removeClass('container'); // retrocompatibility - REMOVE ME in master / saas-19
|
||||
$modal.insertAfter($form).modal();
|
||||
$modal.on('click', '.js_goto_event', function () {
|
||||
$modal.modal('hide');
|
||||
$button.prop('disabled', false);
|
||||
});
|
||||
$modal.on('click', '.close', function () {
|
||||
$button.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -422,6 +422,11 @@ class Post(models.Model):
|
||||
raise KarmaError('User karma not sufficient to post an image or link.')
|
||||
return content
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
if not self._check_recursion():
|
||||
raise ValidationError(_('You cannot create recursive forum posts.'))
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if 'content' in vals and vals.get('forum_id'):
|
||||
|
||||
@@ -20,13 +20,17 @@ class WebsiteLivechat(http.Controller):
|
||||
@http.route('/livechat/channel/<model("im_livechat.channel"):channel>', type='http', auth='public', website=True)
|
||||
def channel_rating(self, channel, **kw):
|
||||
# get the last 100 ratings and the repartition per grade
|
||||
ratings = request.env['rating.rating'].search([('res_model', '=', 'mail.channel'), ('res_id', 'in', channel.sudo().channel_ids.ids)], order='create_date desc', limit=100)
|
||||
repartition = channel.sudo().channel_ids.rating_get_grades()
|
||||
domain = [
|
||||
('res_model', '=', 'mail.channel'), ('res_id', 'in', channel.sudo().channel_ids.ids),
|
||||
('consumed', '=', True), ('rating', '>=', 1),
|
||||
]
|
||||
ratings = request.env['rating.rating'].search(domain, order='create_date desc', limit=100)
|
||||
repartition = channel.sudo().channel_ids.rating_get_grades(domain=domain)
|
||||
|
||||
# compute percentage
|
||||
percentage = dict.fromkeys(['great', 'okay', 'bad'], 0)
|
||||
for grade in repartition:
|
||||
percentage[grade] = repartition[grade] * 100 / sum(repartition.values()) if sum(repartition.values()) else 0
|
||||
percentage[grade] = round(repartition[grade] * 100.0 / sum(repartition.values()), 1) if sum(repartition.values()) else 0
|
||||
|
||||
# the value dict to render the template
|
||||
values = {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
China, 2018-04-28
|
||||
|
||||
Shine IT 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,
|
||||
|
||||
Tony Gu tony@openerp.cn https://github.com/digitalsatori
|
||||
|
||||
List of contributors:
|
||||
Tony Gu tony@openerp.cn https://github.com/digitalsatori
|
||||
Joshua Jan joshua@openerp.cn https://github.com/joshuajan
|
||||
Xiao Guo xerxes@openerp.cn https://github.com/xerxesnoPT
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
France, 2018-06-07
|
||||
|
||||
I here by 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,
|
||||
|
||||
Flavien CHÊNE me@flavienchene.fr https://github.com/Zelwak
|
||||
@@ -0,0 +1,11 @@
|
||||
France, 2018-05-24
|
||||
|
||||
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,
|
||||
|
||||
Martin Duvergey martin.duvergey@gpsante.fr https://github.com/mduvergey
|
||||
@@ -0,0 +1,11 @@
|
||||
Belgium, 2018-05-17
|
||||
|
||||
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,
|
||||
|
||||
Rod Schouteden rod@schout-it.be https://github.com/schout-it
|
||||
@@ -46,7 +46,7 @@ On Linux, using an installer
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python3 /usr/bin/odoo.py -d <database_name> -i web_enterprise --stop-after-init
|
||||
$ python3 /usr/bin/odoo-bin -d <database_name> -i web_enterprise --stop-after-init
|
||||
|
||||
* You should be able to connect to your Odoo Enterprise instance using your usual mean of identification.
|
||||
You can then link your database with your Odoo Enterprise Subscription by entering the code you received
|
||||
|
||||
@@ -22,6 +22,8 @@ from odoo.tools import ustr, pycompat
|
||||
_logger = logging.getLogger(__name__)
|
||||
_test_logger = logging.getLogger('odoo.tests')
|
||||
|
||||
SMTP_TIMEOUT = 60
|
||||
|
||||
|
||||
class MailDeliveryException(except_orm):
|
||||
"""Specific exception subclass for mail delivery errors"""
|
||||
@@ -225,9 +227,9 @@ class IrMailServer(models.Model):
|
||||
"You could use STARTTLS instead. "
|
||||
"If SSL is needed, an upgrade to Python 2.6 on the server-side "
|
||||
"should do the trick."))
|
||||
connection = smtplib.SMTP_SSL(smtp_server, smtp_port)
|
||||
connection = smtplib.SMTP_SSL(smtp_server, smtp_port, timeout=SMTP_TIMEOUT)
|
||||
else:
|
||||
connection = smtplib.SMTP(smtp_server, smtp_port)
|
||||
connection = smtplib.SMTP(smtp_server, smtp_port, timeout=SMTP_TIMEOUT)
|
||||
connection.set_debuglevel(smtp_debug)
|
||||
if smtp_encryption == 'starttls':
|
||||
# starttls() will perform ehlo() if needed first
|
||||
|
||||
@@ -790,6 +790,11 @@ class GroupsView(models.Model):
|
||||
xml = E.field(E.group(*(xml1), col="2"), E.group(*(xml2), col="4"), name="groups_id", position="replace")
|
||||
xml.addprevious(etree.Comment("GENERATED AUTOMATICALLY BY GROUPS"))
|
||||
xml_content = etree.tostring(xml, pretty_print=True, encoding="unicode")
|
||||
if not view.check_access_rights('write', raise_exception=False):
|
||||
# erp manager has the rights to update groups/users but not
|
||||
# to modify ir.ui.view
|
||||
if self.env.user.has_group('base.group_erp_manager'):
|
||||
view = view.sudo()
|
||||
|
||||
new_context = dict(view._context)
|
||||
new_context.pop('install_mode_data', None) # don't set arch_fs for this computed view
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
<field name="name">res.partner.rule.private.employee</field>
|
||||
<field name="model_id" ref="base.model_res_partner"/>
|
||||
<field name="domain_force">
|
||||
[('type', '!=', 'private')]
|
||||
['|', ('type', '!=', 'private'), ('type', '=', False)]
|
||||
</field>
|
||||
<field name="groups" eval="[
|
||||
(4, ref('base.group_user')),
|
||||
|
||||
+1
-1
@@ -3940,7 +3940,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
vals = self.copy_data(default)[0]
|
||||
# To avoid to create a translation in the lang of the user, copy_translation will do it
|
||||
new = self.with_context(lang=None).create(vals)
|
||||
self.copy_translations(new)
|
||||
self.with_context(from_copy_translation=True).copy_translations(new)
|
||||
return new
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -360,7 +360,10 @@ class GeventServer(CommonServer):
|
||||
|
||||
def start(self):
|
||||
import gevent
|
||||
from gevent.wsgi import WSGIServer
|
||||
try:
|
||||
from gevent.pywsgi import WSGIServer
|
||||
except ImportError:
|
||||
from gevent.wsgi import WSGIServer
|
||||
|
||||
|
||||
if os.name == 'posix':
|
||||
|
||||
Reference in New Issue
Block a user