diff --git a/addons/account/account_pre_install.yml b/addons/account/account_pre_install.yml
index e295989e698..798c0d954a5 100644
--- a/addons/account/account_pre_install.yml
+++ b/addons/account/account_pre_install.yml
@@ -7,7 +7,7 @@
wiz = wizards.browse(cr, uid, ref('account.account_configuration_installer_todo'))
part = self.pool.get('res.partner').browse(cr, uid, ref('base.main_partner'))
# if we know the country and the wizard has not yet been executed, we do it
- if (part.country_id.id) and (wiz.state=='open'):
+ if (part.country_id.id) and (part.country_id.code) and (wiz.state=='open'):
mod = 'l10n_'+part.country_id.code.lower()
ids = modules.search(cr, uid, [ ('name','=',mod) ], context=context)
if ids:
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml
index 1e244d5a40c..79174cd77dd 100644
--- a/addons/account/views/report_invoice.xml
+++ b/addons/account/views/report_invoice.xml
@@ -8,7 +8,7 @@
diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py
index e3f69d79e26..d1dfa90901f 100644
--- a/addons/account_asset/account_asset.py
+++ b/addons/account_asset/account_asset.py
@@ -171,10 +171,6 @@ class account_asset_asset(osv.osv):
for x in range(len(posted_depreciation_line_ids), undone_dotation_number):
i = x + 1
amount = self._compute_board_amount(cr, uid, asset, i, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date, context=context)
- company_currency = asset.company_id.currency_id.id
- current_currency = asset.currency_id.id
- # compute amount into company currency
- amount = currency_obj.compute(cr, uid, current_currency, company_currency, amount, context=context)
residual_amount -= amount
vals = {
'amount': amount,
@@ -215,7 +211,10 @@ class account_asset_asset(osv.osv):
l.asset_id IN %s GROUP BY l.asset_id """, (tuple(ids),))
res=dict(cr.fetchall())
for asset in self.browse(cr, uid, ids, context):
- res[asset.id] = asset.purchase_value - res.get(asset.id, 0.0) - asset.salvage_value
+ company_currency = asset.company_id.currency_id.id
+ current_currency = asset.currency_id.id
+ amount = self.pool['res.currency'].compute(cr, uid, company_currency, current_currency, res.get(asset.id, 0.0), context=context)
+ res[asset.id] = asset.purchase_value - amount - asset.salvage_value
for id in ids:
res.setdefault(id, 0.0)
return res
diff --git a/addons/account_payment/account_move_line.py b/addons/account_payment/account_move_line.py
index 8bfe07573a0..d13eba17c35 100644
--- a/addons/account_payment/account_move_line.py
+++ b/addons/account_payment/account_move_line.py
@@ -27,8 +27,10 @@ from operator import itemgetter
class account_move_line(osv.osv):
_inherit = "account.move.line"
+ # delegate to parent, used for local fields.function redefinition
def _amount_residual(self, cr, uid, ids, field_names, args, context=None):
- return self._amount_residual(self, cr, uid, ids, field_names, args, context=context)
+ return super(account_move_line, self)._amount_residual(
+ cr, uid, ids, field_names, args, context=context)
def _to_pay_search(self, cr, uid, obj, name, args, context=None):
if not args:
diff --git a/addons/mail/res_users_view.xml b/addons/mail/res_users_view.xml
index eef2924134f..7bbe369c4d7 100644
--- a/addons/mail/res_users_view.xml
+++ b/addons/mail/res_users_view.xml
@@ -16,6 +16,18 @@
+
+
+
res.users.form.mail
diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py
index acfcb9a1ab9..6532b32e167 100644
--- a/addons/mrp/mrp.py
+++ b/addons/mrp/mrp.py
@@ -1094,14 +1094,27 @@ class mrp_production(osv.osv):
#is 1 element long, so we can take the first.
return stock_move.action_confirm(cr, uid, [move_id], context=context)[0]
- def _get_raw_material_procure_method(self, cr, uid, product, context=None):
- '''This method returns the procure_method to use when creating the stock move for the production raw materials'''
+ def _get_raw_material_procure_method(self, cr, uid, product, location_id=False, location_dest_id=False, context=None):
+ '''This method returns the procure_method to use when creating the stock move for the production raw materials
+ Besides the standard configuration of looking if the product or product category has the MTO route,
+ you can also define a rule e.g. from Stock to Production (which might be used in the future like the sale orders)
+ '''
warehouse_obj = self.pool['stock.warehouse']
+ routes = product.route_ids + product.categ_id.total_route_ids
+
+ if location_id and location_dest_id:
+ pull_obj = self.pool['procurement.rule']
+ pulls = pull_obj.search(cr, uid, [('route_id', 'in', [x.id for x in routes]),
+ ('location_id', '=', location_dest_id),
+ ('location_src_id', '=', location_id)], limit=1, context=context)
+ if pulls:
+ return pull_obj.browse(cr, uid, pulls[0], context=context).procure_method
+
try:
mto_route = warehouse_obj._get_mto_route(cr, uid, context=context)
except:
return "make_to_stock"
- routes = product.route_ids + product.categ_id.total_route_ids
+
if mto_route in [x.id for x in routes]:
return "make_to_order"
return "make_to_stock"
@@ -1133,7 +1146,8 @@ class mrp_production(osv.osv):
move = stock_move.copy(cr, uid, move_id, default = {
'location_id': source_location_id,
'location_dest_id': dest_location_id,
- 'procure_method': self._get_raw_material_procure_method(cr, uid, product, context=context),
+ 'procure_method': self._get_raw_material_procure_method(cr, uid, product, location_id=source_location_id,
+ location_dest_id=dest_location_id, context=context),
'raw_material_production_id': False,
'move_dest_id': move_id,
'picking_type_id': types and types[0] or False,
@@ -1166,7 +1180,8 @@ class mrp_production(osv.osv):
'location_id': source_location_id,
'location_dest_id': destination_location_id,
'company_id': production.company_id.id,
- 'procure_method': prev_move and 'make_to_stock' or self._get_raw_material_procure_method(cr, uid, product, context=context), #Make_to_stock avoids creating procurement
+ 'procure_method': prev_move and 'make_to_stock' or self._get_raw_material_procure_method(cr, uid, product, location_id=source_location_id,
+ location_dest_id=destination_location_id, context=context), #Make_to_stock avoids creating procurement
'raw_material_production_id': production.id,
#this saves us a browse in create()
'price_unit': product.standard_price,
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index 19dc7f09b69..bd05695fbe6 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -1128,11 +1128,16 @@ class pos_order(osv.osv):
if not grouped_data[key]:
grouped_data[key].append(values)
else:
- current_value = grouped_data[key][0]
- current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0)
- current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
- current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
- current_value['tax_amount'] = current_value.get('tax_amount', 0.0) + values.get('tax_amount', 0.0)
+ for line in grouped_data[key]:
+ if line.get('tax_code_id') == values.get('tax_code_id'):
+ current_value = line
+ current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0)
+ current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
+ current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
+ current_value['tax_amount'] = current_value.get('tax_amount', 0.0) + values.get('tax_amount', 0.0)
+ break
+ else:
+ grouped_data[key].append(values)
else:
grouped_data[key].append(values)
diff --git a/addons/point_of_sale/views/templates.xml b/addons/point_of_sale/views/templates.xml
index 9a9f7f1d5c1..d45d124e8ea 100644
--- a/addons/point_of_sale/views/templates.xml
+++ b/addons/point_of_sale/views/templates.xml
@@ -35,6 +35,7 @@
var s = new openerp.init();
var wc = new s.web.WebClient();
+ wc._title_changed = function() {};
wc.show_application = function() {
wc.action_manager.do_action("pos.ui");
};
diff --git a/addons/product/product.py b/addons/product/product.py
index 0d442bb36fa..83dd371bfb6 100644
--- a/addons/product/product.py
+++ b/addons/product/product.py
@@ -791,6 +791,12 @@ class product_template(osv.osv):
return super(product_template, self).name_get(cr, user, ids, context)
def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
+ # Only use the product.product heuristics if there is a search term and the domain
+ # does not specify a match on `product.template` IDs.
+ if not name or any(term[0] == 'id' for term in (args or [])):
+ return super(product_template, self).name_search(
+ cr, user, name=name, args=args, operator=operator, context=context, limit=limit)
+
product_product = self.pool['product.product']
results = product_product.name_search(
cr, user, name, args, operator=operator, context=context, limit=limit)
@@ -798,8 +804,11 @@ class product_template(osv.osv):
template_ids = [p.product_tmpl_id.id
for p in product_product.browse(
cr, user, product_ids, context=context)]
- uniq_ids = OrderedDict.fromkeys(template_ids).keys()
- return self.name_get(cr, user, uniq_ids, context=context)
+
+ # re-apply product.template order + name_get
+ return super(product_template, self).name_search(
+ cr, user, '', args=[('id', 'in', template_ids)],
+ operator=operator, context=context, limit=limit)
class product_product(osv.osv):
_name = "product.product"
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 80c8b370101..c8ba0ce6ee9 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -1019,9 +1019,9 @@ class purchase_order_line(osv.osv):
if line.state not in ['draft', 'cancel']:
raise UserError(_('Cannot delete a purchase order line which is in state \'%s\'.') %(line.state,))
procurement_obj = self.pool.get('procurement.order')
- procurement_ids_to_cancel = procurement_obj.search(cr, uid, [('purchase_line_id', 'in', ids)], context=context)
- if procurement_ids_to_cancel:
- self.pool['procurement.order'].cancel(cr, uid, procurement_ids_to_cancel)
+ procurement_ids_to_except = procurement_obj.search(cr, uid, [('purchase_line_id', 'in', ids)], context=context)
+ if procurement_ids_to_except:
+ self.pool['procurement.order'].write(cr, uid, procurement_ids_to_except, {'state': 'exception'}, context=context)
return super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index ff5ca396609..ade148a5e71 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -550,7 +550,7 @@
On Purchase Order Lines
ir.actions.act_window
purchase.order.line
- [('order_id.invoice_method','<>','picking'), ('state','in',('confirmed','done')), ('invoiced', '=', False)]
+ [('order_id.invoice_method','=','manual'), ('state','in',('confirmed','done')), ('invoiced', '=', False)]
form
tree,form
diff --git a/addons/purchase/test/process/run_scheduler.yml b/addons/purchase/test/process/run_scheduler.yml
index 01aa497311d..83c5c6a099b 100644
--- a/addons/purchase/test/process/run_scheduler.yml
+++ b/addons/purchase/test/process/run_scheduler.yml
@@ -39,4 +39,4 @@
procurement = self.browse(cr, uid, ref('procurement_order_testcase0'))
procurement.purchase_line_id.unlink()
procurement.refresh()
- assert procurement.state == 'cancel', 'Procurement should be cancelled'
+ assert procurement.state == 'exception', 'Procurement should be in exception'
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 2e181782775..7a7fd777e49 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -578,11 +578,13 @@ class sale_order(osv.osv):
context = {}
sale_order_line_obj = self.pool.get('sale.order.line')
account_invoice_obj = self.pool.get('account.invoice')
+ procurement_obj = self.pool.get('procurement.order')
for sale in self.browse(cr, uid, ids, context=context):
for inv in sale.invoice_ids:
if inv.state not in ('draft', 'cancel'):
raise UserError(_('Cannot cancel this sales order!') + ':' + _('First cancel all invoices attached to this sales order.'))
inv.signal_workflow('invoice_cancel')
+ procurement_obj.cancel(cr, uid, sum([l.procurement_ids.ids for l in sale.order_line],[]))
sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
{'state': 'cancel'})
self.write(cr, uid, ids, {'state': 'cancel'})
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py
index 35827cbc04d..0f99767a317 100644
--- a/addons/sale_stock/sale_stock.py
+++ b/addons/sale_stock/sale_stock.py
@@ -155,19 +155,6 @@ class sale_order(osv.osv):
move_obj.write(cr, uid, [x.id for x in picking.move_lines], {'invoice_state': 'invoiced'}, context=context)
return res
- def action_cancel(self, cr, uid, ids, context=None):
- if context is None:
- context = {}
- sale_order_line_obj = self.pool.get('sale.order.line')
- proc_obj = self.pool.get('procurement.order')
- stock_obj = self.pool.get('stock.picking')
- for sale in self.browse(cr, uid, ids, context=context):
- for pick in sale.picking_ids:
- if pick.state not in ('draft', 'cancel'):
- raise UserError(_('Cannot cancel sales order!') + '\n' + _('You must first cancel all delivery order(s) attached to this sales order.'))
- stock_obj.signal_workflow(cr, uid, [p.id for p in sale.picking_ids], 'button_cancel')
- return super(sale_order, self).action_cancel(cr, uid, ids, context=context)
-
def action_wait(self, cr, uid, ids, context=None):
res = super(sale_order, self).action_wait(cr, uid, ids, context=context)
for o in self.browse(cr, uid, ids):
diff --git a/addons/share/security/share_security.xml b/addons/share/security/share_security.xml
index 8619cfa4ca3..38edcdf2e89 100644
--- a/addons/share/security/share_security.xml
+++ b/addons/share/security/share_security.xml
@@ -11,7 +11,7 @@
Shared Group
This group is a template group for shared users, giving basic access needs, the menus for instance.
-
+
1
diff --git a/addons/stock/product.py b/addons/stock/product.py
index 42fecb062f0..c0a0b5d1a5b 100644
--- a/addons/stock/product.py
+++ b/addons/stock/product.py
@@ -186,7 +186,7 @@ class product_product(osv.osv):
operator = '=='
ids = []
- if name == 'qty_available':
+ if name == 'qty_available' and (value != 0.0 or operator not in ('==', '>=', '<=')):
res.append(('id', 'in', self._search_qty_available(cr, uid, operator, value, context)))
else:
product_ids = self.search(cr, uid, [], context=context)
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index d73edc3adcd..829be277793 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -2660,14 +2660,6 @@ class stock_inventory(osv.osv):
self.pool.get('stock.inventory.line').write(cr, uid, line_ids, {'product_qty': 0})
return True
- def _inventory_line_hook(self, cr, uid, inventory_line, move_vals):
- """ Creates a stock move from an inventory line
- @param inventory_line:
- @param move_vals:
- @return:
- """
- return self.pool.get('stock.move').create(cr, uid, move_vals)
-
def action_done(self, cr, uid, ids, context=None):
""" Finish the inventory
@return: True
@@ -2686,35 +2678,7 @@ class stock_inventory(osv.osv):
#as they will be moved to inventory loss, and other quants will be created to the encoded quant location. This is a normal behavior
#as quants cannot be reuse from inventory location (users can still manually move the products before/after the inventory if they want).
move_obj = self.pool.get('stock.move')
- move_obj.action_done(cr, uid, [x.id for x in inv.move_ids], context=context)
-
- def _create_stock_move(self, cr, uid, inventory, todo_line, context=None):
- stock_move_obj = self.pool.get('stock.move')
- product_obj = self.pool.get('product.product')
- inventory_location_id = product_obj.browse(cr, uid, todo_line['product_id'], context=context).property_stock_inventory.id
- vals = {
- 'name': _('INV:') + (inventory.name or ''),
- 'product_id': todo_line['product_id'],
- 'product_uom': todo_line['product_uom_id'],
- 'date': inventory.date,
- 'company_id': inventory.company_id.id,
- 'inventory_id': inventory.id,
- 'state': 'assigned',
- 'restrict_lot_id': todo_line.get('prod_lot_id'),
- 'restrict_partner_id': todo_line.get('partner_id'),
- }
-
- if todo_line['product_qty'] < 0:
- #found more than expected
- vals['location_id'] = inventory_location_id
- vals['location_dest_id'] = todo_line['location_id']
- vals['product_uom_qty'] = -todo_line['product_qty']
- else:
- #found less than expected
- vals['location_id'] = todo_line['location_id']
- vals['location_dest_id'] = inventory_location_id
- vals['product_uom_qty'] = todo_line['product_qty']
- return stock_move_obj.create(cr, uid, vals, context=context)
+ move_obj.action_done(cr, uid, [x.id for x in inv.move_ids if x.state != 'done'], context=context)
def action_check(self, cr, uid, ids, context=None):
""" Checks the inventory and computes the stock move to do
@@ -2728,13 +2692,14 @@ class stock_inventory(osv.osv):
stock_move_obj.unlink(cr, uid, move_ids, context=context)
for line in inventory.line_ids:
#compare the checked quantities on inventory lines to the theorical one
- inventory_line_obj._resolve_inventory_line(cr, uid, line, context=context)
+ stock_move = inventory_line_obj._resolve_inventory_line(cr, uid, line, context=context)
def action_cancel_draft(self, cr, uid, ids, context=None):
""" Cancels the stock move and change inventory state to draft.
@return: True
"""
for inv in self.browse(cr, uid, ids, context=context):
+ self.write(cr, uid, [inv.id], {'line_ids': [(5,)]}, context=context)
self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in inv.move_ids], context=context)
self.write(cr, uid, [inv.id], {'state': 'draft'}, context=context)
return True
@@ -2862,7 +2827,7 @@ class stock_inventory_line(osv.osv):
def _get_quants(self, cr, uid, line, context=None):
quant_obj = self.pool["stock.quant"]
- dom = [('company_id', '=', line.company_id.id), ('location_id', 'child_of', line.location_id.id), ('lot_id', '=', line.prod_lot_id.id),
+ dom = [('company_id', '=', line.company_id.id), ('location_id', '=', line.location_id.id), ('lot_id', '=', line.prod_lot_id.id),
('product_id','=', line.product_id.id), ('owner_id', '=', line.partner_id.id), ('package_id', '=', line.package_id.id)]
quants = quant_obj.search(cr, uid, dom, context=context)
return quants
@@ -2882,7 +2847,7 @@ class stock_inventory_line(osv.osv):
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
if not company_id:
company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
- dom = [('company_id', '=', company_id), ('location_id', 'child_of', location_id), ('lot_id', '=', prod_lot_id),
+ dom = [('company_id', '=', company_id), ('location_id', '=', location_id), ('lot_id', '=', prod_lot_id),
('product_id','=', product_id), ('owner_id', '=', partner_id), ('package_id', '=', package_id)]
quants = quant_obj.search(cr, uid, dom, context=context)
th_qty = sum([x.qty for x in quant_obj.browse(cr, uid, quants, context=context)])
@@ -2893,6 +2858,7 @@ class stock_inventory_line(osv.osv):
def _resolve_inventory_line(self, cr, uid, inventory_line, context=None):
stock_move_obj = self.pool.get('stock.move')
+ quant_obj = self.pool.get('stock.quant')
diff = inventory_line.theoretical_qty - inventory_line.product_qty
if not diff:
return
@@ -2919,7 +2885,24 @@ class stock_inventory_line(osv.osv):
vals['location_id'] = inventory_line.location_id.id
vals['location_dest_id'] = inventory_location_id
vals['product_uom_qty'] = diff
- return stock_move_obj.create(cr, uid, vals, context=context)
+ move_id = stock_move_obj.create(cr, uid, vals, context=context)
+ move = stock_move_obj.browse(cr, uid, move_id, context=context)
+ if diff > 0:
+ domain = [('qty', '>', 0.0), ('package_id', '=', inventory_line.package_id.id), ('lot_id', '=', inventory_line.prod_lot_id.id), ('location_id', '=', inventory_line.location_id.id)]
+ preferred_domain_list = [[('reservation_id', '=', False)], [('reservation_id.inventory_id', '!=', inventory_line.inventory_id.id)]]
+ quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, move.product_qty, domain=domain, prefered_domain_list=preferred_domain_list, restrict_partner_id=move.restrict_partner_id.id, context=context)
+ quant_obj.quants_reserve(cr, uid, quants, move, context=context)
+ elif inventory_line.package_id:
+ stock_move_obj.action_done(cr, uid, move_id, context=context)
+ quants = [x.id for x in move.quant_ids]
+ quant_obj.write(cr, uid, quants, {'package_id': inventory_line.package_id.id}, context=context)
+ res = quant_obj.search(cr, uid, [('qty', '<', 0.0), ('product_id', '=', move.product_id.id),
+ ('location_id', '=', move.location_dest_id.id), ('package_id', '!=', False)], limit=1, context=context)
+ if res:
+ for quant in move.quant_ids:
+ if quant.location_id.id == move.location_dest_id.id: #To avoid we take a quant that was reconcile already
+ quant_obj._quant_reconcile_negative(cr, uid, quant, move, context=context)
+ return move_id
# Should be left out in next version
def restrict_change(self, cr, uid, ids, theoretical_qty, context=None):
diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py
index 32c9e5e3eb0..bd3302b5be9 100644
--- a/addons/stock/tests/test_stock_flow.py
+++ b/addons/stock/tests/test_stock_flow.py
@@ -1095,3 +1095,55 @@ class TestStockFlow(TestStockCommon):
total_qty = [quant.qty for quant in quants]
self.assertEqual(sum(total_qty), 4000, 'Expecting 4000 kg , got %.4f on location stock!' % (sum(total_qty)))
self.assertEqual(productKG.qty_available, 4000, 'Expecting 4000 kg , got %.4f of quantity available!' % (productKG.qty_available))
+
+
+ #--------------------------------------------------------
+ # TEST PARTIAL INVENTORY WITH PACKS and LOTS
+ #---------------------------------------------------------
+
+ packproduct = self.ProductObj.create({'name': 'Pack Product', 'uom_id': self.uom_unit.id, 'uom_po_id': self.uom_unit.id})
+ lotproduct = self.ProductObj.create({'name': 'Lot Product', 'uom_id': self.uom_unit.id, 'uom_po_id': self.uom_unit.id})
+ inventory = self.InvObj.create({'name': 'Test Partial and Pack',
+ 'filter': 'partial',
+ 'location_id': self.stock_location})
+ inventory.prepare_inventory()
+ pack_obj = self.env['stock.quant.package']
+ lot_obj = self.env['stock.production.lot']
+ pack1 = pack_obj.create({'name': 'PACK00TEST1'})
+ pack2 = pack_obj.create({'name': 'PACK00TEST2'})
+ lot1 = lot_obj.create({'name': 'Lot001', 'product_id': lotproduct.id})
+ move = self.MoveObj.search([('product_id', '=', productKG.id), ('inventory_id', '=', inventory.id)], limit=1)
+ self.assertEqual(len(move), 0, "Partial filter should not create a lines upon prepare")
+
+ line_vals = []
+ line_vals += [{'location_id': self.stock_location, 'product_id': packproduct.id, 'product_qty': 10, 'product_uom_id': packproduct.uom_id.id}]
+ line_vals += [{'location_id': self.stock_location, 'product_id': packproduct.id, 'product_qty': 20, 'product_uom_id': packproduct.uom_id.id, 'package_id': pack1.id}]
+ line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 30, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': lot1.id}]
+ line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 25, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': False}]
+ inventory.write({'line_ids': [(0, 0, x) for x in line_vals]})
+ inventory.action_done()
+ self.assertEqual(packproduct.qty_available, 30, "Wrong qty available for packproduct")
+ self.assertEqual(lotproduct.qty_available, 55, "Wrong qty available for lotproduct")
+ quants = self.StockQuantObj.search([('product_id', '=', packproduct.id), ('location_id', '=', self.stock_location), ('package_id', '=', pack1.id)])
+ total_qty = sum([quant.qty for quant in quants])
+ self.assertEqual(total_qty, 20, 'Expecting 20 units on package 1 of packproduct, but we got %.4f on location stock!' % (total_qty))
+
+ #Create an inventory that will put the lots without lot to 0 and check that taking without pack will not take it from the pack
+ inventory2 = self.InvObj.create({'name': 'Test Partial Lot and Pack2',
+ 'filter': 'partial',
+ 'location_id': self.stock_location})
+ inventory2.prepare_inventory()
+ line_vals = []
+ line_vals += [{'location_id': self.stock_location, 'product_id': packproduct.id, 'product_qty': 20, 'product_uom_id': packproduct.uom_id.id}]
+ line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 0, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': False}]
+ line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 10, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': lot1.id}]
+ inventory2.write({'line_ids': [(0, 0, x) for x in line_vals]})
+ inventory2.action_done()
+ self.assertEqual(packproduct.qty_available, 40, "Wrong qty available for packproduct")
+ self.assertEqual(lotproduct.qty_available, 10, "Wrong qty available for lotproduct")
+ quants = self.StockQuantObj.search([('product_id', '=', lotproduct.id), ('location_id', '=', self.stock_location), ('lot_id', '=', lot1.id)])
+ total_qty = sum([quant.qty for quant in quants])
+ self.assertEqual(total_qty, 10, 'Expecting 0 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty))
+ quants = self.StockQuantObj.search([('product_id', '=', lotproduct.id), ('location_id', '=', self.stock_location), ('lot_id', '=', False)])
+ total_qty = sum([quant.qty for quant in quants])
+ self.assertEqual(total_qty, 0, 'Expecting 0 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty))
\ No newline at end of file
diff --git a/addons/stock/views/report_stockpicking.xml b/addons/stock/views/report_stockpicking.xml
index 8f42b94a3d3..fb7bff3f23d 100644
--- a/addons/stock/views/report_stockpicking.xml
+++ b/addons/stock/views/report_stockpicking.xml
@@ -83,15 +83,24 @@
- |
- |
- |
-
-
-
-
- |
- |
+
+ |
+
+
+
+
+
+
+
+ |
+ |
+
+
+
+
+ |
+ |
+
diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js
index 4ad9c893097..3bc5684486a 100644
--- a/addons/web_kanban/static/src/js/kanban.js
+++ b/addons/web_kanban/static/src/js/kanban.js
@@ -28,6 +28,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
this.group_by_field = {};
this.grouped_by_m2o = false;
this.many2manys = [];
+ this.m2m_context = {};
this.state = {
groups : {},
records : {}
@@ -107,6 +108,11 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
this.transform_qweb_template(child);
this.qweb.add_template(instance.web.json_node_to_xml(child));
break;
+ } else if (child.tag === 'field') {
+ var ftype = child.attrs.widget || this.fields_view.fields[child.attrs.name].type;
+ if(ftype == "many2many" && "context" in child.attrs) {
+ this.m2m_context[child.attrs.name] = child.attrs.context;
+ }
}
}
},
@@ -570,7 +576,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
return;
}
if (!relations[field.relation]) {
- relations[field.relation] = { ids: [], elements: {}};
+ relations[field.relation] = { ids: [], elements: {}, context: self.m2m_context[name]};
}
var rel = relations[field.relation];
field.raw_value.forEach(function(id) {
@@ -584,7 +590,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
});
});
_.each(relations, function(rel, rel_name) {
- var dataset = new instance.web.DataSetSearch(self, rel_name, self.dataset.get_context());
+ var dataset = new instance.web.DataSetSearch(self, rel_name, self.dataset.get_context(rel.context));
dataset.name_get(_.uniq(rel.ids)).done(function(result) {
result.forEach(function(nameget) {
$(rel.elements[nameget[0]]).append('' + _.str.escapeHTML(nameget[1]) + '');
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index d7262d54c6d..ba7166b45a8 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -480,7 +480,10 @@ class Website(openerp.addons.web.controllers.main.Home):
#------------------------------------------------------
# Server actions
#------------------------------------------------------
- @http.route('/website/action/', type='http', auth="public", website=True)
+ @http.route([
+ '/website/action/',
+ '/website/action//',
+ ], type='http', auth="public", website=True)
def actions_server(self, path_or_xml_id_or_id, **post):
cr, uid, context = request.cr, request.uid, request.context
res, action_id, action = None, None, None
diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js
index 8c8dfcc50c7..26a0ce0477e 100644
--- a/addons/website/static/src/js/website.js
+++ b/addons/website/static/src/js/website.js
@@ -323,7 +323,9 @@
function treat_node(node){
if(node.nodeType === 3) {
if(node.nodeValue.match(/\S/)){
- node.nodeValue = openerp._t($.trim(node.nodeValue));
+ var text_value = $.trim(node.nodeValue);
+ var spaces = node.nodeValue.split(text_value);
+ node.nodeValue = spaces[0] + openerp._t(text_value) + spaces[1];
}
}
else if(node.nodeType === 1 && node.hasChildNodes()) {
diff --git a/addons/website_report/controllers/main.py b/addons/website_report/controllers/main.py
index 5f79e259c60..5350bc4d2b0 100644
--- a/addons/website_report/controllers/main.py
+++ b/addons/website_report/controllers/main.py
@@ -28,6 +28,7 @@ class Website(Website):
@route()
def customize_template_get(self, key, full=False, bundles=False):
res = super(Website, self).customize_template_get(key, full=full, bundles=bundles)
- if full and request.session.get('report_view_ids'):
- res += request.session['report_view_ids']
+ if full:
+ for r in request.session.get('report_view_ids', []):
+ res += super(Website, self).customize_template_get(r.get('xml_id'), full=full)
return res
diff --git a/doc/cla/corporate/bloopark.md b/doc/cla/corporate/bloopark.md
new file mode 100644
index 00000000000..2664bbe3006
--- /dev/null
+++ b/doc/cla/corporate/bloopark.md
@@ -0,0 +1,20 @@
+Germany 2015-02-25
+
+bloopark systems GmbH & Co. KG 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,
+
+Jens Holze jholze@bloopark.de https://github.com/jholze
+
+List of contributors:
+
+Jens Holze jholze@bloopark.de https://github.com/jholze
+Christoph Giesel cgiesel@bloopark.de https://github.com/christophlsa
+Benjamin Bachmann bBachmann@bloopark.de https://github.com/benniphx
+Robert Rübner rruebner@bloopark.de https://github.com/rruebner
+Florian Fischer ffischer@bloopark.de https://github.com/florianfischer
+Mercerdes Scenna mscenna@bloopark.de https://github.com/mscenna
diff --git a/doc/cla/corporate/therp.md b/doc/cla/corporate/therp.md
new file mode 100644
index 00000000000..428ecb2b411
--- /dev/null
+++ b/doc/cla/corporate/therp.md
@@ -0,0 +1,21 @@
+Netherlands, 2015-03-09
+
+Therp BV agrees to the terms of the Odoo Corporate
+Contributor License Agreement v1.0.
+
+We declare that we are authorized and able to make this agreement and sign
+this declaration.
+
+Signed,
+
+* Ronald Portier rportier@therp.nl https://github.com/nl66278
+* Stefan Rijnhart stefan@therp.nl https://github.com/stefanrijnhart
+
+List of contributors:
+
+* Giovanni Capalbo giovanni@therp.nl https://github.com/gfcapalbo
+* Holger Brunn hbrunn@therp.nl https://github.com/hbrunn
+* Lara Freeke lfreeke@therp.nl https://github.com/lfreeke
+* Ronald Portier rportier@therp.nl https://github.com/nl66278
+* Stefan Rijnhart stefan@therp.nl https://github.com/stefanrijnhart
+
diff --git a/doc/cla/individual/eLBati.md b/doc/cla/individual/eLBati.md
new file mode 100644
index 00000000000..50104556d80
--- /dev/null
+++ b/doc/cla/individual/eLBati.md
@@ -0,0 +1,11 @@
+Italy, 2015-02-18
+
+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,
+
+Lorenzo Battistini lorenzo.battistini@agilebg.com https://github.com/eLBati
diff --git a/doc/cla/individual/matthieuchoplin.md b/doc/cla/individual/matthieuchoplin.md
new file mode 100644
index 00000000000..4533005bfd0
--- /dev/null
+++ b/doc/cla/individual/matthieuchoplin.md
@@ -0,0 +1,11 @@
+United Kingdom, 13.03.2015
+
+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,
+
+Matthieu Choplin choplin.mat@gmail.com https://github.com/matthieuchoplin
diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py
index 2b7e2616151..cb71de52b71 100644
--- a/openerp/addons/base/ir/ir_actions.py
+++ b/openerp/addons/base/ir/ir_actions.py
@@ -1086,7 +1086,7 @@ class ir_server_object_lines(osv.osv):
_sequence = 'ir_actions_id_seq'
_columns = {
- 'server_id': fields.many2one('ir.actions.server', 'Related Server Action'),
+ 'server_id': fields.many2one('ir.actions.server', 'Related Server Action', ondelete='cascade'),
'col1': fields.many2one('ir.model.fields', 'Field', required=True),
'value': fields.text('Value', required=True, help="Expression containing a value specification. \n"
"When Formula type is selected, this field may be a Python expression "
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index aeead6e664e..798389ecc28 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -382,13 +382,13 @@ class ir_model_fields(osv.osv):
if vals.get('relation',False) and not self.pool['ir.model'].search(cr, user, [('model','=',vals['relation'])]):
raise UserError(_("Model %s does not exist!") % vals['relation'])
+ self.pool.clear_manual_fields()
+
if vals['model'] in self.pool:
model = self.pool[vals['model']]
if vals['model'].startswith('x_') and vals['name'] == 'x_name':
model._rec_name = 'x_name'
- self.pool.clear_manual_fields()
-
# re-initialize model in registry
model.__init__(self.pool, cr)
self.pool.setup_models(cr, partial=(not self.pool.ready))
diff --git a/openerp/addons/base/ir/ir_sequence.py b/openerp/addons/base/ir/ir_sequence.py
index 32bde90e664..a9a2dcd073c 100644
--- a/openerp/addons/base/ir/ir_sequence.py
+++ b/openerp/addons/base/ir/ir_sequence.py
@@ -179,7 +179,7 @@ class ir_sequence(models.Model):
if new_implementation in ('standard', None):
# Implementation has NOT changed.
# Only change sequence if really requested.
- if seq.number_next != n:
+ if values.get('number_next'):
_alter_sequence(self.env.cr, "ir_sequence_%03d" % seq.id, number_next=n)
if seq.number_increment != i:
_alter_sequence(self.env.cr, "ir_sequence_%03d" % seq.id, number_increment=i)
diff --git a/openerp/addons/base/tests/test_ir_sequence.py b/openerp/addons/base/tests/test_ir_sequence.py
index b0d106600d2..04a3c256ff5 100644
--- a/openerp/addons/base/tests/test_ir_sequence.py
+++ b/openerp/addons/base/tests/test_ir_sequence.py
@@ -196,5 +196,33 @@ class test_ir_sequence_generate(unittest2.TestCase):
drop_sequence('test_sequence_type_6')
-if __name__ == '__main__':
+class Test_ir_sequence_init(common.TransactionCase):
+
+ def test_00(self):
+ registry, cr, uid = self.registry, self.cr, self.uid
+
+ # test if read statement return the good number_next value (from postgreSQL sequence and not ir_sequence value)
+ sequence = registry('ir.sequence')
+ # first creation of sequence (normal)
+ values = {'number_next': 1,
+ 'company_id': 1,
+ 'padding': 4,
+ 'number_increment': 1,
+ 'implementation': 'standard',
+ 'name': 'test-sequence-00'}
+ seq_id = sequence.create(cr, uid, values)
+ # Call get next 4 times
+ sequence.next_by_id(cr, uid, seq_id)
+ sequence.next_by_id(cr, uid, seq_id)
+ sequence.next_by_id(cr, uid, seq_id)
+ read_sequence = sequence.next_by_id(cr, uid, seq_id)
+ # Read the value of the current sequence
+ assert read_sequence == "0004", 'The actual sequence value must be 4. reading : %s' % read_sequence
+ # reset sequence to 1 by write method calling
+ sequence.write(cr, uid, [seq_id], {'number_next': 1})
+ # Read the value of the current sequence
+ read_sequence = sequence.next_by_id(cr, uid, seq_id)
+ assert read_sequence == "0001", 'The actual sequence value must be 1. reading : %s' % read_sequence
+
+if __name__ == "__main__":
unittest2.main()
diff --git a/openerp/addons/base/workflow/workflow.py b/openerp/addons/base/workflow/workflow.py
index 4d23ea1bb53..63a1abf5d10 100644
--- a/openerp/addons/base/workflow/workflow.py
+++ b/openerp/addons/base/workflow/workflow.py
@@ -177,7 +177,7 @@ class wkf_workitem(osv.osv):
_columns = {
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="cascade", select=True),
'wkf_id': fields.related('act_id','wkf_id', type='many2one', relation='workflow', string='Workflow'),
- 'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="cascade", select=True),
+ 'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="set null", select=True),
'inst_id': fields.many2one('workflow.instance', 'Instance', required=True, ondelete="cascade", select=True),
'state': fields.char('Status', select=True),
}
diff --git a/openerp/fields.py b/openerp/fields.py
index 7be703b3ae5..82ecce34d74 100644
--- a/openerp/fields.py
+++ b/openerp/fields.py
@@ -1267,20 +1267,18 @@ class Selection(Field):
def set_class_name(self, cls, name):
super(Selection, self).set_class_name(cls, name)
# determine selection (applying 'selection_add' extensions)
- selection = None
for field in resolve_all_mro(cls, name, reverse=True):
if isinstance(field, type(self)):
# We cannot use field.selection or field.selection_add here
# because those attributes are overridden by `set_class_name`.
if 'selection' in field._attrs:
- selection = field._attrs['selection']
+ self.selection = field._attrs['selection']
if 'selection_add' in field._attrs:
# use an OrderedDict to update existing values
selection_add = field._attrs['selection_add']
- selection = OrderedDict(selection + selection_add).items()
+ self.selection = OrderedDict(self.selection + selection_add).items()
else:
- selection = None
- self.selection = selection
+ self.selection = None
def _description_selection(self, env):
""" return the selection list (pairs (value, label)); labels are
diff --git a/openerp/models.py b/openerp/models.py
index 13e829c186b..b59c6374f47 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -1599,6 +1599,7 @@ class BaseModel(object):
'views': [(view_id, 'form')],
'target': 'current',
'res_id': id,
+ 'context': context,
}
def get_access_action(self, cr, uid, id, context=None):
@@ -2472,7 +2473,9 @@ class BaseModel(object):
self._o2m_raise_on_missing_reference(cr, f)
elif isinstance(f, fields.many2many):
- self._m2m_raise_or_create_relation(cr, f)
+ res = self._m2m_raise_or_create_relation(cr, f)
+ if res and self._fields[k].depends:
+ stored_fields.append(self._fields[k])
else:
res = column_data.get(k)
@@ -2772,6 +2775,9 @@ class BaseModel(object):
raise UserError(_("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj))
def _m2m_raise_or_create_relation(self, cr, f):
+ """ Create the table for the relation if necessary.
+ Return ``True`` if the relation had to be created.
+ """
m2m_tbl, col1, col2 = f._sql_names(self)
# do not create relations for custom fields as they do not belong to a module
# they will be automatically removed when dropping the corresponding ir.model.field
@@ -2798,6 +2804,7 @@ class BaseModel(object):
cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (m2m_tbl, self._table, ref))
cr.commit()
_schema.debug("Create table '%s': m2m relation between '%s' and '%s'", m2m_tbl, self._table, ref)
+ return True
def _add_sql_constraints(self, cr):
diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py
index f464d57ac72..16e552fc681 100644
--- a/openerp/tools/misc.py
+++ b/openerp/tools/misc.py
@@ -533,7 +533,7 @@ ALL_LANGUAGES = {
'mn_MN': u'Mongolian / монгол',
'nb_NO': u'Norwegian Bokmål / Norsk bokmål',
'nl_NL': u'Dutch / Nederlands',
- 'nl_BE': u'Flemish (BE) / Vlaams (BE)',
+ 'nl_BE': u'Dutch (BE) / Nederlands (BE)',
'oc_FR': u'Occitan (FR, post 1500) / Occitan',
'pl_PL': u'Polish / Język polski',
'pt_BR': u'Portuguese (BR) / Português (BR)',