summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2016-11-15 13:58:55 +0100
committerChristophe Simonis <chs@odoo.com>2016-11-15 13:58:55 +0100
commitdece72d010e23c88bdf5b2a1ace6ff0df2b2732f (patch)
treef9fc12d1f2f5f51acca168854702f75e316e1f0b
parent22d7be5b7397f9d9a811e071601a9ad07762dc55 (diff)
parentca2003839a9abc3f76f48d2382d512a4546aeab1 (diff)
[MERGE] forward port branch saas-12 up to ca20038
-rw-r--r--addons/account/models/account_move.py9
-rw-r--r--addons/account_asset/models/account_invoice.py2
-rw-r--r--addons/account_asset/views/account_asset_views.xml4
-rw-r--r--addons/account_bank_statement_import/account_bank_statement_import.py6
-rw-r--r--addons/crm/models/crm_lead.py4
-rw-r--r--addons/delivery/models/stock_picking.py6
-rw-r--r--addons/hr_expense/views/hr_expense_views.xml2
-rw-r--r--addons/hr_timesheet/hr_timesheet.py2
-rw-r--r--addons/mail/static/src/js/chat_manager.js10
-rw-r--r--addons/mail/static/src/js/utils.js20
-rw-r--r--addons/mrp/models/mrp_workorder.py9
-rw-r--r--addons/point_of_sale/security/ir.model.access.csv2
-rw-r--r--addons/purchase/models/purchase.py2
-rw-r--r--addons/rating/controllers/main.py3
-rw-r--r--addons/stock/models/stock_move.py4
-rw-r--r--addons/stock/tests/common.py21
-rw-r--r--addons/stock/tests/test_stock_flow.py161
-rw-r--r--addons/stock/views/product_views.xml2
-rw-r--r--addons/web_editor/static/src/js/summernote.js17
-rw-r--r--addons/web_editor/static/src/js/widgets.js6
-rw-r--r--doc/cla/individual/fmdl.md11
-rw-r--r--odoo/addons/base/ir/ir_http.py2
-rw-r--r--odoo/addons/base/module/module.py2
-rw-r--r--odoo/addons/base/module/wizard/base_language_install.py9
24 files changed, 254 insertions, 62 deletions
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index f27f5e4d42c..8a61dcc1704 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -70,6 +70,13 @@ class AccountMove(models.Model):
if self.env.context.get('default_journal_type'):
return self.env['account.journal'].search([('type', '=', self.env.context['default_journal_type'])], limit=1).id
+ @api.multi
+ @api.depends('line_ids.partner_id')
+ def _compute_partner_id(self):
+ for move in self:
+ partner = move.line_ids.mapped('partner_id')
+ move.partner_id = partner.id if len(partner) == 1 else False
+
name = fields.Char(string='Number', required=True, copy=False, default='/')
ref = fields.Char(string='Reference', copy=False)
date = fields.Date(required=True, states={'posted': [('readonly', True)]}, index=True, default=fields.Date.context_today)
@@ -84,7 +91,7 @@ class AccountMove(models.Model):
'in \'Posted\' status.')
line_ids = fields.One2many('account.move.line', 'move_id', string='Journal Items',
states={'posted': [('readonly', True)]}, copy=True)
- partner_id = fields.Many2one('res.partner', related='line_ids.partner_id', string="Partner", store=True, readonly=True)
+ partner_id = fields.Many2one('res.partner', compute='_compute_partner_id', string="Partner", store=True, readonly=True)
amount = fields.Monetary(compute='_amount_compute', store=True)
narration = fields.Text(string='Internal Note')
company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', store=True, readonly=True,
diff --git a/addons/account_asset/models/account_invoice.py b/addons/account_asset/models/account_invoice.py
index 105354f6509..3e45b722976 100644
--- a/addons/account_asset/models/account_invoice.py
+++ b/addons/account_asset/models/account_invoice.py
@@ -66,7 +66,7 @@ class AccountInvoiceLine(models.Model):
'partner_id': self.invoice_id.partner_id.id,
'company_id': self.invoice_id.company_id.id,
'currency_id': self.invoice_id.company_currency_id.id,
- 'date': self.asset_start_date or self.invoice_id.date_invoice,
+ 'date': self.invoice_id.date_invoice,
'invoice_id': self.invoice_id.id,
}
changed_vals = self.env['account.asset.asset'].onchange_category_id_values(vals['category_id'])
diff --git a/addons/account_asset/views/account_asset_views.xml b/addons/account_asset/views/account_asset_views.xml
index 0b5e7b0de1f..f569a6b60b1 100644
--- a/addons/account_asset/views/account_asset_views.xml
+++ b/addons/account_asset/views/account_asset_views.xml
@@ -31,12 +31,12 @@
<field name="account_asset_id" nolabel="1" attrs="{'invisible': [('type','=', False)]}"/>
<div>
<label for="account_depreciation_id" attrs="{'invisible': [('type','!=','purchase')]}"/>
- <label for="account_depreciation_id" string="Recognition Account" attrs="{'invisible': [('type','!=','sale')]}"/>
+ <label for="account_depreciation_id" string="Recognition Income Account" attrs="{'invisible': [('type','!=','sale')]}"/>
</div>
<field name="account_depreciation_id" nolabel="1"/>
<div>
<label for="account_depreciation_expense_id" attrs="{'invisible': [('type','!=','purchase')]}"/>
- <label for="account_depreciation_expense_id" string="Recognition Income Account" attrs="{'invisible': [('type','!=','sale')]}"/>
+ <label for="account_depreciation_expense_id" string="Recognition Account" attrs="{'invisible': [('type','!=','sale')]}"/>
</div>
<field name="account_depreciation_expense_id" nolabel="1"/>
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
diff --git a/addons/account_bank_statement_import/account_bank_statement_import.py b/addons/account_bank_statement_import/account_bank_statement_import.py
index 32e1e9ce5d5..2d45a95612d 100644
--- a/addons/account_bank_statement_import/account_bank_statement_import.py
+++ b/addons/account_bank_statement_import/account_bank_statement_import.py
@@ -213,7 +213,11 @@ class AccountBankStatementImport(models.TransientModel):
if 'unique_import_id' not in line_vals \
or not line_vals['unique_import_id'] \
or not bool(BankStatementLine.sudo().search([('unique_import_id', '=', line_vals['unique_import_id'])], limit=1)):
- filtered_st_lines.append(line_vals)
+ if line_vals['amount'] != 0:
+ # Some banks, like ING, create a line for free charges.
+ # We just skip those lines as there's a 'non-zero' constraint
+ # on the amount of account.bank.statement.line
+ filtered_st_lines.append(line_vals)
else:
ignored_statement_lines_import_ids.append(line_vals['unique_import_id'])
if len(filtered_st_lines) > 0:
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index de94d7cdeb6..a6e08be6bc7 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -520,7 +520,7 @@ class Lead(FormatAddress, models.Model):
merge_message = _('Merged leads') if result_type == 'lead' else _('Merged opportunities')
subject = merge_message + ": " + ", ".join(opportunities.mapped('name'))
# message bodies
- message_bodies = opportunities._mail_body(CRM_LEAD_FIELDS_TO_MERGE)
+ message_bodies = opportunities._mail_body(list(CRM_LEAD_FIELDS_TO_MERGE))
message_body = "\n\n".join(message_bodies)
return self.message_post(body=message_body, subject=subject)
@@ -607,7 +607,7 @@ class Lead(FormatAddress, models.Model):
# merge all the sorted opportunity. This means the value of
# the first (head opp) will be a priority.
- merged_data = opportunities._merge_data(CRM_LEAD_FIELDS_TO_MERGE)
+ merged_data = opportunities._merge_data(list(CRM_LEAD_FIELDS_TO_MERGE))
# force value for saleperson and sales team
if user_id:
diff --git a/addons/delivery/models/stock_picking.py b/addons/delivery/models/stock_picking.py
index 4ce72cefef6..916c00c1f30 100644
--- a/addons/delivery/models/stock_picking.py
+++ b/addons/delivery/models/stock_picking.py
@@ -93,7 +93,11 @@ class StockPicking(models.Model):
@api.onchange('carrier_id')
def onchange_carrier(self):
if self.carrier_id.delivery_type in ['fixed', 'base_on_rule']:
- self.carrier_price = self.carrier_id.price
+ order = self.sale_id
+ if order:
+ self.carrier_price = self.carrier_id.get_price_available(order)
+ else:
+ self.carrier_price = self.carrier_id.price
@api.depends('product_id', 'move_lines')
def _cal_weight(self):
diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml
index 5088ff3b571..ad601c6e57b 100644
--- a/addons/hr_expense/views/hr_expense_views.xml
+++ b/addons/hr_expense/views/hr_expense_views.xml
@@ -162,7 +162,7 @@
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<separator />
<filter domain="[('state', '=', 'draft')]" string="To Submit" name="to_report" help="New Expense"/>
- <filter domain="[('state', '=', 'report')]" string="Reported" name="submitted" help="Confirmed Expenses"/>
+ <filter domain="[('state', '=', 'reported')]" string="Reported" name="submitted" help="Confirmed Expenses"/>
<filter domain="[('state', '=', 'refused')]" string="Refused" name="refused" help="Refused Expenses"/>
<filter domain="[('state', '!=', 'cancel')]" string="Not Refused" name="uncancelled" help="Actual expense sheets, not the refused ones"/>
<separator />
diff --git a/addons/hr_timesheet/hr_timesheet.py b/addons/hr_timesheet/hr_timesheet.py
index 32152b8f0e7..c8b7241e5c5 100644
--- a/addons/hr_timesheet/hr_timesheet.py
+++ b/addons/hr_timesheet/hr_timesheet.py
@@ -9,7 +9,7 @@ class AccountAnalyticLine(models.Model):
task_id = fields.Many2one('project.task', 'Task')
project_id = fields.Many2one('project.project', 'Project', domain=[('allow_timesheets', '=', True)])
- department_id = fields.Many2one('hr.department', "Department", related='user_id.employee_ids.department_id', store=True)
+ department_id = fields.Many2one('hr.department', "Department", related='user_id.employee_ids.department_id', store=True, readonly=True)
@api.onchange('project_id')
def onchange_project_id(self):
diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js
index 0b7b37ec49f..2f63dd61b5a 100644
--- a/addons/mail/static/src/js/chat_manager.js
+++ b/addons/mail/static/src/js/chat_manager.js
@@ -661,9 +661,17 @@ var chat_manager = {
post_message: function (data, options) {
options = options || {};
+
+ // This message will be received from the mail composer as html content subtype
+ // but the urls will not be linkified. If the mail composer takes the responsibility
+ // to linkify the urls we end up with double linkification a bit everywhere.
+ // Ideally we want to keep the content as text internally and only make html
+ // enrichment at display time but the current design makes this quite hard to do.
+ var body = utils.linkify(_.str.trim(data.content));
+
var msg = {
partner_ids: data.partner_ids,
- body: _.str.trim(data.content),
+ body: body,
attachment_ids: data.attachment_ids,
};
if ('subject' in data) {
diff --git a/addons/mail/static/src/js/utils.js b/addons/mail/static/src/js/utils.js
index 7c64ba48df0..db82b976945 100644
--- a/addons/mail/static/src/js/utils.js
+++ b/addons/mail/static/src/js/utils.js
@@ -61,12 +61,23 @@ function _parse_and_transform(nodes, transform_function) {
// suggested regexp (gruber url matching regexp, adapted to js, see https://gist.github.com/gruber/8891611)
var url_regexp = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/gi;
+function linkify(text, attrs) {
+ attrs = attrs || {};
+ if (attrs.target === undefined) {
+ attrs.target = '_blank';
+ }
+ attrs = _.map(attrs, function (value, key) {
+ return key + '="' + _.escape(value) + '"';
+ }).join(' ');
+ return text.replace(url_regexp, function (url) {
+ var href = (!/^(f|ht)tps?:\/\//i.test(url)) ? "http://" + url : url;
+ return '<a ' + attrs + ' href="' + href + '">' + url + '</a>';
+ });
+}
+
function add_link (node, transform_children) {
if (node.nodeType === 3) { // text node
- return node.data.replace(url_regexp, function (url) {
- var href = (!/^(f|ht)tps?:\/\//i.test(url)) ? "http://" + url : url;
- return '<a target="_blank" href="' + href + '">' + url + '</a>';
- });
+ return linkify(node.data);
}
if (node.tagName === "A") return node.outerHTML;
node.innerHTML = transform_children();
@@ -132,6 +143,7 @@ return {
send_notification: send_notification,
parse_and_transform: parse_and_transform,
add_link: add_link,
+ linkify: linkify,
strip_html: strip_html,
inline: inline,
parse_email: parse_email,
diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py
index 920d8b0bc4c..9d2062b62c9 100644
--- a/addons/mrp/models/mrp_workorder.py
+++ b/addons/mrp/models/mrp_workorder.py
@@ -301,9 +301,12 @@ class MrpWorkorder(models.Model):
if self.next_work_order_id and self.final_lot_id and not self.next_work_order_id.final_lot_id:
self.next_work_order_id.final_lot_id = self.final_lot_id.id
- # TODO: add filter for those that have not been done yet --> need to check as it can have different filters
- self.move_lot_ids.filtered(lambda x: not x.done_move and not x.lot_produced_id).write({'lot_produced_id': self.final_lot_id.id,
- 'lot_produced_qty': self.qty_producing,})
+ self.move_lot_ids.filtered(
+ lambda move_lot: not move_lot.done_move and not move_lot.lot_produced_id and move_lot.quantity_done > 0
+ ).write({
+ 'lot_produced_id': self.final_lot_id.id,
+ 'lot_produced_qty': self.qty_producing
+ })
# If last work order, then post lots used
# TODO: should be same as checking if for every workorder something has been done?
diff --git a/addons/point_of_sale/security/ir.model.access.csv b/addons/point_of_sale/security/ir.model.access.csv
index d24b36d76a7..2de182a4835 100644
--- a/addons/point_of_sale/security/ir.model.access.csv
+++ b/addons/point_of_sale/security/ir.model.access.csv
@@ -33,3 +33,5 @@ access_barcode_nomenclature_pos_user,barcode.nomenclature.pos.user,barcodes.mode
access_barcode_nomenclature_pos_manager,barcode.nomenclature.pos.manager,barcodes.model_barcode_nomenclature,group_pos_manager,1,1,1,1
access_barcode_rule_pos_user,barcode.rule.pos.user,barcodes.model_barcode_rule,group_pos_user,1,0,0,0
access_barcode_rule_pos_manager,barcode.rule.pos.manager,barcodes.model_barcode_rule,group_pos_manager,1,1,1,1
+access_bank_statement_cashbox_user,account.bank.statement.cashbox user,account.model_account_bank_statement_cashbox,group_pos_user,1,1,1,0
+access_account_cashbox_line_user,account.cashbox.line user,account.model_account_cashbox_line,group_pos_user,1,1,1,0
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index 299ffa6d91c..8d1c5ae56c5 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -405,7 +405,7 @@ class PurchaseOrder(models.Model):
else:
picking = pickings[0]
moves = order.order_line._create_stock_moves(picking)
- moves = moves.action_confirm()
+ moves = moves.filtered(lambda x: x.state not in ('done', 'cancel')).action_confirm()
moves.force_assign()
picking.message_post_with_view('mail.message_origin_link',
values={'self': picking, 'origin': order},
diff --git a/addons/rating/controllers/main.py b/addons/rating/controllers/main.py
index e4535131bf2..2782ffc3350 100644
--- a/addons/rating/controllers/main.py
+++ b/addons/rating/controllers/main.py
@@ -38,5 +38,6 @@ class Rating(http.Controller):
if request.session.uid:
return werkzeug.utils.redirect('/web#model=%s&id=%s&view_type=form' % (record_sudo._name, record_sudo.id))
return request.render('rating.rating_external_page_view', {
- 'web_base_url': request.env['ir.config_parameter'].sudo().get_param('web.base.url')
+ 'web_base_url': request.env['ir.config_parameter'].sudo().get_param('web.base.url'),
+ 'rating': rating,
})
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 0ddd9de24d2..42c4e3b80bb 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -750,8 +750,8 @@ class StockMove(models.Model):
owner_id=pack_operation.owner_id.id, src_package_id=pack_operation.package_id.id,
dest_package_id=dest_package_id)
if redo_false_quants:
- false_quants_move = [x for x in move_rec_updateme.reserved_quant_ids if (not x.lot_id) and (x.owner_id.id == pack_operation.owner_id.id) \
- and (x.location_id.id == pack_operation.location_id.id) and (x.package_id.id != pack_operation.package_id.id)]
+ false_quants_move = [x for x in move_rec_updateme.reserved_quant_ids if (not x.lot_id) and (x.owner_id.id == pack_operation.owner_id.id) and
+ (x.location_id.id == pack_operation.location_id.id) and (x.package_id.id == pack_operation.package_id.id)]
return True
@api.multi
diff --git a/addons/stock/tests/common.py b/addons/stock/tests/common.py
index 767470368f0..e836c93a0d6 100644
--- a/addons/stock/tests/common.py
+++ b/addons/stock/tests/common.py
@@ -27,15 +27,16 @@ class TestStockCommon(common.TransactionCase):
self.picking_type_out = self.ModelDataObj.xmlid_to_res_id('stock.picking_type_out')
self.supplier_location = self.ModelDataObj.xmlid_to_res_id('stock.stock_location_suppliers')
self.stock_location = self.ModelDataObj.xmlid_to_res_id('stock.stock_location_stock')
+ self.pack_location = self.ModelDataObj.xmlid_to_res_id('stock.location_pack_zone')
self.customer_location = self.ModelDataObj.xmlid_to_res_id('stock.stock_location_customers')
self.categ_unit = self.ModelDataObj.xmlid_to_res_id('product.product_uom_categ_unit')
self.categ_kgm = self.ModelDataObj.xmlid_to_res_id('product.product_uom_categ_kgm')
# Product Created A, B, C, D
- self.productA = self.ProductObj.create({'name': 'Product A'})
- self.productB = self.ProductObj.create({'name': 'Product B'})
- self.productC = self.ProductObj.create({'name': 'Product C'})
- self.productD = self.ProductObj.create({'name': 'Product D'})
+ self.productA = self.ProductObj.create({'name': 'Product A', 'type': 'product'})
+ self.productB = self.ProductObj.create({'name': 'Product B', 'type': 'product'})
+ self.productC = self.ProductObj.create({'name': 'Product C', 'type': 'product'})
+ self.productD = self.ProductObj.create({'name': 'Product D', 'type': 'product'})
# Configure unit of measure.
self.uom_kg = self.UomObj.create({
@@ -90,9 +91,9 @@ class TestStockCommon(common.TransactionCase):
'rounding': 1.0})
# Product for different unit of measure.
- self.DozA = self.ProductObj.create({'name': 'Dozon-A', 'uom_id': self.uom_dozen.id, 'uom_po_id': self.uom_dozen.id})
- self.SDozA = self.ProductObj.create({'name': 'SuperDozon-A', 'uom_id': self.uom_sdozen.id, 'uom_po_id': self.uom_sdozen.id})
- self.SDozARound = self.ProductObj.create({'name': 'SuperDozenRound-A', 'uom_id': self.uom_sdozen_round.id, 'uom_po_id': self.uom_sdozen_round.id})
- self.UnitA = self.ProductObj.create({'name': 'Unit-A'})
- self.kgB = self.ProductObj.create({'name': 'kg-B', 'uom_id': self.uom_kg.id, 'uom_po_id': self.uom_kg.id})
- self.gB = self.ProductObj.create({'name': 'g-B', 'uom_id': self.uom_gm.id, 'uom_po_id': self.uom_gm.id})
+ self.DozA = self.ProductObj.create({'name': 'Dozon-A', 'type': 'product', 'uom_id': self.uom_dozen.id, 'uom_po_id': self.uom_dozen.id})
+ self.SDozA = self.ProductObj.create({'name': 'SuperDozon-A', 'type': 'product', 'uom_id': self.uom_sdozen.id, 'uom_po_id': self.uom_sdozen.id})
+ self.SDozARound = self.ProductObj.create({'name': 'SuperDozenRound-A', 'type': 'product', 'uom_id': self.uom_sdozen_round.id, 'uom_po_id': self.uom_sdozen_round.id})
+ self.UnitA = self.ProductObj.create({'name': 'Unit-A', 'type': 'product'})
+ self.kgB = self.ProductObj.create({'name': 'kg-B', 'type': 'product', 'uom_id': self.uom_kg.id, 'uom_po_id': self.uom_kg.id})
+ self.gB = self.ProductObj.create({'name': 'g-B', 'type': 'product', 'uom_id': self.uom_gm.id, 'uom_po_id': self.uom_gm.id})
diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py
index 238bab1170e..12f6c323c69 100644
--- a/addons/stock/tests/test_stock_flow.py
+++ b/addons/stock/tests/test_stock_flow.py
@@ -205,19 +205,21 @@ class TestStockFlow(TestStockCommon):
self.assertEqual(move.state, 'confirmed', 'Wrong state of move line.')
# Product assign to outgoing shipments
picking_out.action_assign()
- for move in picking_out.move_lines:
- self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
+ self.assertEqual(picking_out.move_lines[0].state, 'confirmed', 'Wrong state of move line.')
+ self.assertEqual(picking_out.move_lines[1].state, 'assigned', 'Wrong state of move line.')
+ self.assertEqual(picking_out.move_lines[2].state, 'assigned', 'Wrong state of move line.')
+ self.assertEqual(picking_out.move_lines[3].state, 'confirmed', 'Wrong state of move line.')
# Check availability for product A
- aval_a_qty = self.MoveObj.search([('product_id', '=', self.productA.id), ('picking_id', '=', picking_out.id)], limit=1).availability
+ aval_a_qty = self.MoveObj.search([('product_id', '=', self.productA.id), ('picking_id', '=', picking_out.id)], limit=1).reserved_availability
self.assertEqual(aval_a_qty, 4.0, 'Wrong move quantity availability of product A (%s found instead of 4)' % (aval_a_qty))
# Check availability for product B
- aval_b_qty = self.MoveObj.search([('product_id', '=', self.productB.id), ('picking_id', '=', picking_out.id)], limit=1).availability
+ aval_b_qty = self.MoveObj.search([('product_id', '=', self.productB.id), ('picking_id', '=', picking_out.id)], limit=1).reserved_availability
self.assertEqual(aval_b_qty, 5.0, 'Wrong move quantity availability of product B (%s found instead of 5)' % (aval_b_qty))
# Check availability for product C
- aval_c_qty = self.MoveObj.search([('product_id', '=', self.productC.id), ('picking_id', '=', picking_out.id)], limit=1).availability
+ aval_c_qty = self.MoveObj.search([('product_id', '=', self.productC.id), ('picking_id', '=', picking_out.id)], limit=1).reserved_availability
self.assertEqual(aval_c_qty, 3.0, 'Wrong move quantity availability of product C (%s found instead of 3)' % (aval_c_qty))
# Check availability for product D
- aval_d_qty = self.MoveObj.search([('product_id', '=', self.productD.id), ('picking_id', '=', picking_out.id)], limit=1).availability
+ aval_d_qty = self.MoveObj.search([('product_id', '=', self.productD.id), ('picking_id', '=', picking_out.id)], limit=1).reserved_availability
self.assertEqual(aval_d_qty, 7.0, 'Wrong move quantity availability of product D (%s found instead of 7)' % (aval_d_qty))
# ----------------------------------------------------------------------
@@ -815,23 +817,21 @@ class TestStockFlow(TestStockCommon):
for move in picking_out.move_lines:
self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
# Check product A available quantity
- DozA_qty = self.MoveObj.search([('product_id', '=', self.DozA.id), ('picking_id', '=', picking_out.id)], limit=1).availability
+ DozA_qty = self.MoveObj.search([('product_id', '=', self.DozA.id), ('picking_id', '=', picking_out.id)], limit=1).reserved_availability
self.assertEqual(DozA_qty, 4.5, 'Wrong move quantity availability (%s found instead of 4.5)' % (DozA_qty))
# Check product B available quantity
- SDozA_qty = self.MoveObj.search([('product_id', '=', self.SDozA.id), ('picking_id', '=', picking_out.id)], limit=1).availability
+ SDozA_qty = self.MoveObj.search([('product_id', '=', self.SDozA.id), ('picking_id', '=', picking_out.id)], limit=1).reserved_availability
self.assertEqual(SDozA_qty, 2, 'Wrong move quantity availability (%s found instead of 2)' % (SDozA_qty))
# Check product C available quantity
- SDozARound_qty = self.MoveObj.search([('product_id', '=', self.SDozARound.id), ('picking_id', '=', picking_out.id)], limit=1).availability
+ SDozARound_qty = self.MoveObj.search([('product_id', '=', self.SDozARound.id), ('picking_id', '=', picking_out.id)], limit=1).reserved_availability
self.assertEqual(SDozARound_qty, 3, 'Wrong move quantity availability (%s found instead of 3)' % (SDozARound_qty))
# Check product D available quantity
- gB_qty = self.MoveObj.search([('product_id', '=', self.gB.id), ('picking_id', '=', picking_out.id)], limit=1).availability
+ gB_qty = self.MoveObj.search([('product_id', '=', self.gB.id), ('picking_id', '=', picking_out.id)], limit=1).reserved_availability
self.assertEqual(gB_qty, 503, 'Wrong move quantity availability (%s found instead of 503)' % (gB_qty))
# Check product D available quantity
- kgB_qty = self.MoveObj.search([('product_id', '=', self.kgB.id), ('picking_id', '=', picking_out.id)], limit=1).availability
+ kgB_qty = self.MoveObj.search([('product_id', '=', self.kgB.id), ('picking_id', '=', picking_out.id)], limit=1).reserved_availability
self.assertEqual(kgB_qty, 0.020, 'Wrong move quantity availability (%s found instead of 0.020)' % (kgB_qty))
- picking_out.action_confirm()
- picking_out.action_assign()
picking_out.do_prepare_partial()
picking_out.do_transfer()
@@ -1276,4 +1276,137 @@ class TestStockFlow(TestStockCommon):
self.assertEqual(sum([x.qty for x in quants if not x.lot_id]), 0.0, 'Wrong sum of quants with no lot')
self.assertEqual(sum([x.qty for x in quants if x.lot_id.id == lot1.id]), 1.0, 'Wrong sum of quants with lot 1')
self.assertEqual(sum([x.qty for x in quants if x.lot_id.id == lot2.id]), 1.0, 'Wrong sum of quants with lot 2')
- self.assertEqual(sum([x.qty for x in quants if x.lot_id.id == lot3.id]), 2.0, 'Wrong sum of quants with lot 3') \ No newline at end of file
+ self.assertEqual(sum([x.qty for x in quants if x.lot_id.id == lot3.id]), 2.0, 'Wrong sum of quants with lot 3')
+
+
+ def test_40_pack_in_pack(self):
+ """ Put a pack in pack"""
+ picking_out = self.PickingObj.create({
+ 'partner_id': self.partner_agrolite_id,
+ 'picking_type_id': self.picking_type_out,
+ 'location_id': self.pack_location,
+ 'location_dest_id': self.customer_location})
+ move_out = self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 3,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': picking_out.id,
+ 'location_id': self.pack_location,
+ 'location_dest_id': self.customer_location})
+ picking_pack = self.PickingObj.create({
+ 'partner_id': self.partner_agrolite_id,
+ 'picking_type_id': self.picking_type_out,
+ 'location_id': self.stock_location ,
+ 'location_dest_id': self.pack_location})
+ move_pack = self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 3,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': picking_pack.id,
+ 'location_id': self.stock_location,
+ 'location_dest_id': self.pack_location,
+ 'move_dest_id': move_out.id})
+ picking_in = self.PickingObj.create({
+ 'partner_id': self.partner_delta_id,
+ 'picking_type_id': self.picking_type_in,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location})
+ move_in = self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 3,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': picking_in.id,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location,
+ 'move_dest_id': move_pack.id})
+
+ # Check incoming shipment move lines state.
+ for move in picking_in.move_lines:
+ self.assertEqual(move.state, 'draft', 'Wrong state of move line.')
+ # Confirm incoming shipment.
+ picking_in.action_confirm()
+ # Check incoming shipment move lines state.
+ for move in picking_in.move_lines:
+ self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
+
+ # Check incoming shipment move lines state.
+ for move in picking_pack.move_lines:
+ self.assertEqual(move.state, 'draft', 'Wrong state of move line.')
+ # Confirm incoming shipment.
+ picking_pack.action_confirm()
+ # Check incoming shipment move lines state.
+ for move in picking_pack.move_lines:
+ self.assertEqual(move.state, 'waiting', 'Wrong state of move line.')
+
+ # Check incoming shipment move lines state.
+ for move in picking_out.move_lines:
+ self.assertEqual(move.state, 'draft', 'Wrong state of move line.')
+ # Confirm incoming shipment.
+ picking_out.action_confirm()
+ # Check incoming shipment move lines state.
+ for move in picking_out.move_lines:
+ self.assertEqual(move.state, 'waiting', 'Wrong state of move line.')
+
+ # Set the quantity done on the pack operation
+ picking_in.pack_operation_product_ids.qty_done = 3.0
+ # Put in a pack
+ picking_in.put_in_pack()
+ # Get the new package
+ picking_in_package = picking_in.pack_operation_ids.result_package_id
+ # Validate picking
+ picking_in.do_new_transfer()
+
+ # Check first picking state changed to done
+ for move in picking_in.move_lines:
+ self.assertEqual(move.state, 'done', 'Wrong state of move line.')
+ # Check next picking state changed to 'assigned'
+ for move in picking_pack.move_lines:
+ self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
+
+ # set the pack in pack operation to 'done'
+ for pack in picking_pack.pack_operation_pack_ids:
+ pack.qty_done = 1.0
+
+ # Put in a pack
+ picking_pack.put_in_pack()
+ # Get the new package
+ picking_pack_package = picking_pack.pack_operation_ids.result_package_id
+ # Validate picking
+ picking_pack.do_new_transfer()
+
+ # Check second picking state changed to done
+ for move in picking_pack.move_lines:
+ self.assertEqual(move.state, 'done', 'Wrong state of move line.')
+ # Check next picking state changed to 'assigned'
+ for move in picking_out.move_lines:
+ self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
+
+ # set the pack in pack operation to 'done'
+ for pack in picking_out.pack_operation_pack_ids:
+ pack.qty_done = 1.0
+
+ # Validate picking
+ picking_out.do_new_transfer()
+
+ # check all pickings are done
+ for move in picking_in.move_lines:
+ self.assertEqual(move.state, 'done', 'Wrong state of move line.')
+ for move in picking_pack.move_lines:
+ self.assertEqual(move.state, 'done', 'Wrong state of move line.')
+ for move in picking_out.move_lines:
+ self.assertEqual(move.state, 'done', 'Wrong state of move line.')
+
+ # Check picking_in_package is in picking_pack_package
+ self.assertEqual(picking_in_package.parent_id.id, picking_pack_package.id, 'The package created in the picking in is not in the one created in picking pack')
+ # Check that both packages are in the customer location
+ self.assertEqual(picking_pack_package.location_id.id, self.customer_location, 'The package created in picking pack is not in the customer location')
+ self.assertEqual(picking_in_package.location_id.id, self.customer_location, 'The package created in picking in is not in the customer location')
+ # Check that we have a quant in customer location, for the productA with qty 3
+ quant = self.StockQuantObj.search([('location_id', '=', self.customer_location), ('product_id', '=', self.productA.id)])
+ self.assertTrue(quant.id, 'There is no quant in customer location for productA')
+ self.assertEqual(quant.qty, 3.0, 'The quant in customer location for productA has not a quantity of 3.0')
+ # Check that the parent package of the quant is the picking_in_package
+ self.assertEqual(quant.package_id.id, picking_in_package.id, 'The quant in customer location is not in its package created in picking in')
diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml
index f7276259634..6b8fbb71937 100644
--- a/addons/stock/views/product_views.xml
+++ b/addons/stock/views/product_views.xml
@@ -289,7 +289,7 @@
<field name="nbr_reordering_rules" widget="statinfo"/>
</button>
<button name="%(product_open_orderpoint)d" type="action"
- attrs="{'invisible':['|',('type', '!=', ['product', 'consu']),('nbr_reordering_rules', '!=', 1)]}"
+ attrs="{'invisible':['|',('type', 'not in', ['product', 'consu']),('nbr_reordering_rules', '!=', 1)]}"
class="oe_stat_button" icon="fa-refresh">
<div class="o_form_field o_stat_info mr4">
<span class="o_stat_text">Min :</span>
diff --git a/addons/web_editor/static/src/js/summernote.js b/addons/web_editor/static/src/js/summernote.js
index 28bfe83a345..5c344fbbcd1 100644
--- a/addons/web_editor/static/src/js/summernote.js
+++ b/addons/web_editor/static/src/js/summernote.js
@@ -938,12 +938,19 @@ $.summernote.pluginEvents.insertTable = function (event, editor, layoutInfo, sDi
var dimension = sDim.split('x');
var r = range.create();
if (!r) return;
- r = r.deleteContents();
+ r = r.deleteContents(true);
- var isBodyContainer = dom.isBodyContainer;
- dom.isBodyContainer = dom.isNotBreakable;
- r.insertNode(editor.table.createTable(dimension[0], dimension[1]));
- dom.isBodyContainer = isBodyContainer;
+ var table = editor.table.createTable(dimension[0], dimension[1]);
+ var parent = r.sc;
+ while (dom.isText(parent.parentNode) || dom.isRemovableEmptyNode(parent.parentNode)) {
+ parent = parent.parentNode;
+ }
+ var node = dom.splitTree(parent, {'node': r.sc, 'offset': r.so}) || r.sc;
+ node.parentNode.insertBefore(table, node);
+
+ if ($(node).text() === '' || node.textContent === '\u00A0') {
+ node.parentNode.removeChild(node);
+ }
editor.afterCommand($editable);
event.preventDefault();
diff --git a/addons/web_editor/static/src/js/widgets.js b/addons/web_editor/static/src/js/widgets.js
index 61ec1564ac5..d3ad1e6fbc8 100644
--- a/addons/web_editor/static/src/js/widgets.js
+++ b/addons/web_editor/static/src/js/widgets.js
@@ -812,7 +812,7 @@ function createVideoNode(url, options) {
class: 'vine-embed'
});
} else if (vimMatch && vimMatch[3].length) {
- $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>', {
+ $video = $('<iframe>', {
src: '//player.vimeo.com/video/' + vimMatch[3],
width: '640',
height: '360'
@@ -824,14 +824,14 @@ function createVideoNode(url, options) {
height: '360'
});
} else if (youkuMatch && youkuMatch[1].length) {
- $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>', {
+ $video = $('<iframe>', {
height: '498',
width: '510',
src: '//player.youku.com/embed/' + youkuMatch[1]
});
} else {
// this is not a known video link. Now what, Cat? Now what?
- $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>', {
+ $video = $('<iframe>', {
width: '640',
height: '360',
src: url
diff --git a/doc/cla/individual/fmdl.md b/doc/cla/individual/fmdl.md
new file mode 100644
index 00000000000..45c3464ce3f
--- /dev/null
+++ b/doc/cla/individual/fmdl.md
@@ -0,0 +1,11 @@
+France, 12-11-2016
+
+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,
+
+Florent de Labarre florent.mirieu@gmail.com https://github.com/fmdl
diff --git a/odoo/addons/base/ir/ir_http.py b/odoo/addons/base/ir/ir_http.py
index 6bca1edee58..1d942e75a86 100644
--- a/odoo/addons/base/ir/ir_http.py
+++ b/odoo/addons/base/ir/ir_http.py
@@ -286,7 +286,7 @@ class IrHttp(models.AbstractModel):
module_path = os.path.join(os.path.normpath(module_path), '') # join ensures the path ends with '/'
module_resource_path = os.path.normpath(module_resource_path)
if module_resource_path.startswith(module_path):
- with open(module_resource_path, 'r') as f:
+ with open(module_resource_path, 'rb') as f:
content = base64.b64encode(f.read())
last_update = str(os.path.getmtime(module_resource_path))
diff --git a/odoo/addons/base/module/module.py b/odoo/addons/base/module/module.py
index 89d49a4b74c..233d12cb370 100644
--- a/odoo/addons/base/module/module.py
+++ b/odoo/addons/base/module/module.py
@@ -708,7 +708,7 @@ class Module(models.Model):
to_install = self.search([('name', 'in', urls.keys()), ('state', '=', 'uninstalled')])
post_install_action = to_install.button_immediate_install()
- if installed:
+ if installed or to_install:
# in this case, force server restart to reload python code...
self._cr.commit()
odoo.service.server.restart()
diff --git a/odoo/addons/base/module/wizard/base_language_install.py b/odoo/addons/base/module/wizard/base_language_install.py
index e8dba882fc8..10fb90311fa 100644
--- a/odoo/addons/base/module/wizard/base_language_install.py
+++ b/odoo/addons/base/module/wizard/base_language_install.py
@@ -20,11 +20,10 @@ class BaseLanguageInstall(models.TransientModel):
@api.multi
def lang_install(self):
- this = self[0]
- this = this.with_context(overwrite=this.overwrite)
+ self.ensure_one()
mods = self.env['ir.module.module'].search([('state', '=', 'installed')])
- mods.update_translations(this.lang)
- this.state = 'done'
+ mods.with_context(overwrite=self.overwrite).update_translations(self.lang)
+ self.state = 'done'
return {
'name': _('Language Pack'),
'view_type': 'form',
@@ -35,5 +34,5 @@ class BaseLanguageInstall(models.TransientModel):
'context': dict(self._context, active_ids=self.ids),
'type': 'ir.actions.act_window',
'target': 'new',
- 'res_id': this.id,
+ 'res_id': self.id,
}