diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index fcab29082bd..9354e4731bd 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -468,7 +468,10 @@ class AccountJournal(models.Model):
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
args = args or []
- recs = self.search(['|', ('code', operator, name), ('name', operator, name)] + args, limit=limit)
+ connector = '|'
+ if operator in expression.NEGATIVE_TERM_OPERATORS:
+ connector = '&'
+ recs = self.search([connector, ('code', operator, name), ('name', operator, name)] + args, limit=limit)
return recs.name_get()
@api.multi
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 6aff8481161..0fc05b549bc 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -248,7 +248,7 @@ class AccountBankStatement(models.Model):
for aml in st_line.journal_entry_ids:
moves |= aml.move_id
if moves:
- moves.post()
+ moves.filtered(lambda m: m.state != 'posted').post()
statement.message_post(body=_('Statement %s confirmed, journal items were created.') % (statement.name,))
statements.link_bank_to_partner()
statements.write({'state': 'confirm', 'date_done': time.strftime("%Y-%m-%d %H:%M:%S")})
diff --git a/addons/account/tests/account_test_classes.py b/addons/account/tests/account_test_classes.py
index cf37e2493ef..8477f4109d9 100644
--- a/addons/account/tests/account_test_classes.py
+++ b/addons/account/tests/account_test_classes.py
@@ -28,3 +28,30 @@ class AccountingTestCase(HttpCase):
if theorical_lines:
raise ValidationError('Remaining theorical line (not found). %s)' % ([(aml[0], aml[1], aml[2]) for aml in theorical_lines]))
return True
+
+ def ensure_account_property(self, property_name):
+ '''Ensure the ir.property targetting an account.account passed as parameter exists.
+ In case it's not: create it with a random account. This is useful when testing with
+ partially defined localization (missing stock properties for example)
+
+ :param property_name: The name of the property.
+ '''
+ company_id = self.env.user.company_id
+ field_id = self.env['ir.model.fields'].search(
+ [('model', '=', 'product.template'), ('name', '=', property_name)], limit=1)
+ property_id = self.env['ir.property'].search([
+ ('company_id', '=', company_id.id),
+ ('name', '=', property_name),
+ ('res_id', '=', None),
+ ('fields_id', '=', field_id.id)], limit=1)
+ account_id = self.env['account.account'].search([('company_id', '=', company_id.id)], limit=1)
+ value_reference = 'account.account,%d' % account_id.id
+ if property_id and not property_id.value_reference:
+ property_id.value_reference = value_reference
+ else:
+ self.env['ir.property'].create({
+ 'name': property_name,
+ 'company_id': company_id.id,
+ 'fields_id': field_id.id,
+ 'value_reference': value_reference,
+ })
diff --git a/addons/base_geolocalize/models/res_partner.py b/addons/base_geolocalize/models/res_partner.py
index 778a844ad83..44fa8d9692f 100644
--- a/addons/base_geolocalize/models/res_partner.py
+++ b/addons/base_geolocalize/models/res_partner.py
@@ -8,6 +8,8 @@ from odoo.exceptions import UserError
def geo_find(addr):
+ if not addr:
+ return None
url = 'https://maps.googleapis.com/maps/api/geocode/json?sensor=false&address='
url += urllib2.quote(addr.encode('utf8'))
diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py
index 43eb8f08d6e..889ddf7bb7b 100644
--- a/addons/base_import_module/models/ir_module.py
+++ b/addons/base_import_module/models/ir_module.py
@@ -100,18 +100,23 @@ class IrModule(models.Model):
raise UserError(_("File '%s' exceed maximum allowed file size") % zf.filename)
with tempdir() as module_dir:
- z.extractall(module_dir)
- dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))]
- for mod_name in dirs:
- module_names.append(mod_name)
- try:
- # assert mod_name.startswith('theme_')
- path = opj(module_dir, mod_name)
- self._import_module(mod_name, path, force=force)
- success.append(mod_name)
- except Exception as e:
- _logger.exception('Error while importing module')
- errors[mod_name] = exception_to_unicode(e)
+ import odoo.modules as addons
+ try:
+ addons.module.ad_paths.append(module_dir)
+ z.extractall(module_dir)
+ dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))]
+ for mod_name in dirs:
+ module_names.append(mod_name)
+ try:
+ # assert mod_name.startswith('theme_')
+ path = opj(module_dir, mod_name)
+ self._import_module(mod_name, path, force=force)
+ success.append(mod_name)
+ except Exception as e:
+ _logger.exception('Error while importing module')
+ errors[mod_name] = exception_to_unicode(e)
+ finally:
+ addons.module.ad_paths.remove(module_dir)
r = ["Successfully imported module '%s'" % mod for mod in success]
for mod, error in errors.items():
r.append("Error while importing module '%s': %r" % (mod, error))
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index 98f5e5c95d9..0782ae3384a 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -567,15 +567,18 @@ class Meeting(models.Model):
if not event_date:
event_date = datetime.now()
- # Convert the event date to saved timezone (or context tz) as it'll
- # define the correct hour/day asked by the user to repeat for recurrence.
- event_date = event_date.astimezone(timezone) # transform "+hh:mm" timezone
- rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date, forceset=True)
+ if self.allday and self.rrule and 'UNTIL' in self.rrule and 'Z' not in self.rrule:
+ rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date.replace(tzinfo=None), forceset=True, ignoretz=True)
+ else:
+ # Convert the event date to saved timezone (or context tz) as it'll
+ # define the correct hour/day asked by the user to repeat for recurrence.
+ event_date = event_date.astimezone(timezone) # transform "+hh:mm" timezone
+ rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date, forceset=True, tzinfos={})
recurring_meetings = self.search([('recurrent_id', '=', self.id), '|', ('active', '=', False), ('active', '=', True)])
for meeting in recurring_meetings:
rset1._exdate.append(todate(meeting.recurrent_id_date))
- return [d.astimezone(pytz.UTC) for d in rset1]
+ return [d.astimezone(pytz.UTC) if d.tzinfo else d for d in rset1]
@api.multi
def _get_recurrency_end_date(self):
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index fb4472f43e8..5ea52c874e2 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -247,6 +247,7 @@ class Lead(models.Model):
team_id = self.env['crm.team']._get_default_team_id(user_id=user_id)
return {'team_id': team_id}
+ @api.constrains('user_id')
@api.onchange('user_id')
def _onchange_user_id(self):
""" When changing the user, also set a team_id or restrict team id to the ones user_id is member of. """
diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py
index 543c0feb0b7..dc91bf4e5e6 100644
--- a/addons/delivery/models/delivery_carrier.py
+++ b/addons/delivery/models/delivery_carrier.py
@@ -226,26 +226,26 @@ class DeliveryCarrier(models.Model):
if record.delivery_type == 'fixed':
PriceRule.search([('carrier_id', '=', record.id)]).unlink()
- line_data = {
- 'carrier_id': record.id,
- 'variable': 'price',
- 'operator': '>=',
- }
- # Create the delivery price rules
- if record.free_if_more_than:
- line_data.update({
- 'max_value': record.amount,
- 'standard_price': 0.0,
- 'list_base_price': 0.0,
- })
- PriceRule.create(line_data)
- if record.fixed_price is not False:
- line_data.update({
- 'max_value': 0.0,
- 'standard_price': record.fixed_price,
- 'list_base_price': record.fixed_price,
- })
- PriceRule.create(line_data)
+ line_data = {
+ 'carrier_id': record.id,
+ 'variable': 'price',
+ 'operator': '>=',
+ }
+ # Create the delivery price rules
+ if record.free_if_more_than:
+ line_data.update({
+ 'max_value': record.amount,
+ 'standard_price': 0.0,
+ 'list_base_price': 0.0,
+ })
+ PriceRule.create(line_data)
+ if record.fixed_price is not False:
+ line_data.update({
+ 'max_value': 0.0,
+ 'standard_price': record.fixed_price,
+ 'list_base_price': record.fixed_price,
+ })
+ PriceRule.create(line_data)
return True
@api.model
diff --git a/addons/hr_holidays/models/hr_holidays.py b/addons/hr_holidays/models/hr_holidays.py
index eff33ce5af3..2ad8de7130e 100644
--- a/addons/hr_holidays/models/hr_holidays.py
+++ b/addons/hr_holidays/models/hr_holidays.py
@@ -329,9 +329,12 @@ class Holidays(models.Model):
res = []
for leave in self:
if leave.type == 'remove':
- res.append((leave.id, _("%s on %s : %.2f day(s)") % (leave.employee_id.name or leave.category_id.name, leave.holiday_status_id.name, leave.number_of_days_temp)))
+ if self.env.context.get('short_name'):
+ res.append((leave.id, _("%s : %.2f day(s)") % (leave.name or leave.holiday_status_id.name, leave.number_of_days_temp)))
+ else:
+ res.append((leave.id, _("%s on %s : %.2f day(s)") % (leave.employee_id.name or leave.category_id.name, leave.holiday_status_id.name, leave.number_of_days_temp)))
else:
- res.append((leave.id, _("Allocation of %s : %.2f day(s) To %s") % (leave.holiday_status_id.name, leave.number_of_days_temp,leave.employee_id.name)))
+ res.append((leave.id, _("Allocation of %s : %.2f day(s) To %s") % (leave.holiday_status_id.name, leave.number_of_days_temp, leave.employee_id.name)))
return res
def _check_state_access_right(self, vals):
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index a797892a4c2..4f728d48698 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -208,8 +208,11 @@ class MrpProduction(models.Model):
@api.depends('move_raw_ids.quantity_done', 'move_finished_ids.quantity_done')
def _compute_post_visible(self):
for order in self:
- order.post_visible = any(order.move_raw_ids.filtered(lambda x: (x.quantity_done) > 0 and (x.state not in ['done', 'cancel']))) or \
- any(order.move_finished_ids.filtered(lambda x: (x.quantity_done) > 0 and (x.state not in ['done', 'cancel'])))
+ if order.product_tmpl_id._is_cost_method_standard():
+ order.post_visible = any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_raw_ids) or \
+ any((x.quantity_done > 0 and x.state not in ['done' 'cancel']) for x in order.move_finished_ids)
+ else:
+ order.post_visible = any((x.quantity_done > 0 and x.state not in ['done' 'cancel']) for x in order.move_finished_ids)
@api.multi
@api.depends('workorder_ids.state', 'move_finished_ids')
diff --git a/addons/sale/tests/test_sale_order.py b/addons/sale/tests/test_sale_order.py
index 4022c85c179..248cb435ff2 100644
--- a/addons/sale/tests/test_sale_order.py
+++ b/addons/sale/tests/test_sale_order.py
@@ -104,6 +104,9 @@ class TestSaleOrder(TestSale):
def test_cost_invoicing(self):
""" Test confirming a vendor invoice to reinvoice cost on the so """
+ # force the pricelist to have the same currency as the company
+ self.env.ref('product.list0').currency_id = self.env.ref('base.main_company').currency_id
+
serv_cost = self.env.ref('product.service_cost_01')
prod_gap = self.env.ref('product.product_product_1')
so = self.env['sale.order'].create({
diff --git a/addons/sale_expense/tests/test_sale_expense.py b/addons/sale_expense/tests/test_sale_expense.py
index 2dd9dba7726..9b3fa6cd79a 100644
--- a/addons/sale_expense/tests/test_sale_expense.py
+++ b/addons/sale_expense/tests/test_sale_expense.py
@@ -7,6 +7,9 @@ from odoo.addons.sale.tests.test_sale_common import TestSale
class TestSaleExpense(TestSale):
def test_sale_expense(self):
""" Test the behaviour of sales orders when managing expenses """
+ # force the pricelist to have the same currency as the company
+ self.env.ref('product.list0').currency_id = self.env.ref('base.main_company').currency_id
+
# create a so with a product invoiced on delivery
prod = self.env.ref('product.product_product_1')
so = self.env['sale.order'].create({
diff --git a/addons/stock/i18n/stock.pot b/addons/stock/i18n/stock.pot
index 1296f083384..8e86883b4aa 100644
--- a/addons/stock/i18n/stock.pot
+++ b/addons/stock/i18n/stock.pot
@@ -6383,6 +6383,12 @@ msgid "You cannot set a negative product quantity in an inventory line:\n"
" %s - qty: %s"
msgstr ""
+#. module: stock
+#: code:addons/stock/models/stock_production_lot.py:37
+#, python-format
+msgid "You are not allowed to create a lot for this picking type"
+msgstr ""
+
#. module: stock
#: code:addons/stock/models/stock_move.py:927
#, python-format
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py
index 54b4a877fd7..6e26cf6dc77 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -419,6 +419,9 @@ class ProductTemplate(models.Model):
relation="stock.location.route", string="Category Routes",
related='categ_id.total_route_ids')
+ def _is_cost_method_standard(self):
+ return True
+
def _compute_quantities(self):
res = self._compute_quantities_dict()
for template in self:
diff --git a/addons/stock/models/stock_production_lot.py b/addons/stock/models/stock_production_lot.py
index 60f370fff51..a568cd80fe3 100644
--- a/addons/stock/models/stock_production_lot.py
+++ b/addons/stock/models/stock_production_lot.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
+from odoo.exceptions import UserError
class ProductionLot(models.Model):
@@ -27,6 +28,15 @@ class ProductionLot(models.Model):
('name_ref_uniq', 'unique (name, product_id)', 'The combination of serial number and product must be unique !'),
]
+ @api.model
+ def create(self, vals):
+ pack_id = self.env.context.get('active_pack_operation', False)
+ if pack_id:
+ pack = self.env['stock.pack.operation'].browse(pack_id)
+ if pack.picking_id and not pack.picking_id.picking_type_id.use_create_lots:
+ raise UserError(_("You are not allowed to create a lot for this picking type"))
+ return super(ProductionLot, self).create(vals)
+
@api.one
@api.depends('quant_ids.qty')
def _product_qty(self):
diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py
index 20de2d25775..8453a49b4e3 100644
--- a/addons/stock/models/stock_scrap.py
+++ b/addons/stock/models/stock_scrap.py
@@ -66,6 +66,12 @@ class StockScrap(models.Model):
scrap.do_scrap()
return scrap
+ @api.multi
+ def unlink(self):
+ if 'done' in self.mapped('state'):
+ raise UserError(_('You cannot delete a scrap which is done.'))
+ return super(StockScrap, self).unlink()
+
def _get_origin_moves(self):
return self.picking_id and self.picking_id.move_lines.filtered(lambda x: x.product_id == self.product_id)
diff --git a/addons/stock/security/ir.model.access.csv b/addons/stock/security/ir.model.access.csv
index dc8f1756134..6b13597199d 100644
--- a/addons/stock/security/ir.model.access.csv
+++ b/addons/stock/security/ir.model.access.csv
@@ -78,4 +78,8 @@ access_barcode_rule_stock_manager,barcode.rule.stock.manager,barcodes.model_barc
access_stock_forecast_user,report.stock.forecast.user,model_report_stock_forecast,stock.group_stock_user,1,0,0,0
access_stock_forecast_manager,report.stock.forecast.manager,model_report_stock_forecast,stock.group_stock_manager,1,1,1,1
access_stock_scrap_user,stock.scrap.user,model_stock_scrap,stock.group_stock_user,1,1,1,0
-access_procurement_order,procurement.order,stock.model_procurement_order,base.group_portal,1,0,0,0
\ No newline at end of file
+access_procurement_order,procurement.order,stock.model_procurement_order,base.group_portal,1,0,0,0
+access_product_attribute_manager,product.attribute manager,product.model_product_attribute,stock.group_stock_manager,1,1,1,1
+access_product_attribute_value_manager,product.attribute manager value,product.model_product_attribute_value,stock.group_stock_manager,1,1,1,1
+access_product_attribute_price_manager,product.attribute manager price,product.model_product_attribute_price,stock.group_stock_manager,1,1,1,1
+access_product_attribute_line_manager,product.attribute manager line,product.model_product_attribute_line,stock.group_stock_manager,1,1,1,1
\ No newline at end of file
diff --git a/addons/stock/views/stock_pack_operation_views.xml b/addons/stock/views/stock_pack_operation_views.xml
index 5b6919dab83..cd8469d4672 100644
--- a/addons/stock/views/stock_pack_operation_views.xml
+++ b/addons/stock/views/stock_pack_operation_views.xml
@@ -85,7 +85,7 @@
By sharing you answer, you will get additional karma points if your answer is selected as the right one. See what you can do with karma - here.
+ here.Share this content to increase your chances to be featured on the front page and attract more visitors.
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml index 74df06b10cb..3608b9b47c7 100644 --- a/addons/website_forum/views/website_forum.xml +++ b/addons/website_forum/views/website_forum.xml @@ -793,9 +793,10 @@