diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000000..6877871d313a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+*.pyc
+*.py~
+.cache
+.idea
+.project
+.pydevproject
+.sync
+.settings
+/docs/_build
+/_bin
+Thumbs.db
+*/ghostdriver.log
+*.csv
+/output
+.venv
+/build
+/dist
+/log
+*.spec
+*.json
+*.xml
+/node_modules
diff --git a/product_unique_serial_picking/README.md b/product_unique_serial_picking/README.md
new file mode 100644
index 000000000000..88fb1f160f15
--- /dev/null
+++ b/product_unique_serial_picking/README.md
@@ -0,0 +1,4 @@
+Product Unique Serial Number
+==================
+Add a field to product to activate check if is a product unique serial number.
+Add a check constraint to deny stock moves with quantity different to 1 if has unique serial number as True.
diff --git a/product_unique_serial_picking/__init__.py b/product_unique_serial_picking/__init__.py
new file mode 100644
index 000000000000..e62f96769b88
--- /dev/null
+++ b/product_unique_serial_picking/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+import model
+import wizard
diff --git a/product_unique_serial_picking/__openerp__.py b/product_unique_serial_picking/__openerp__.py
new file mode 100644
index 000000000000..c2182c3fb184
--- /dev/null
+++ b/product_unique_serial_picking/__openerp__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': "Product Serial Unique Number Picking",
+ 'author': "vauxoo",
+ 'website': "http://www.vauxoo.com",
+ 'category': 'stock',
+ 'version': '1.0',
+ 'depends': ['stock_no_negative'],
+ 'data': [
+ 'wizard/product_serial_wizard.xml',
+ "views/product_view.xml",
+ "views/stock_view.xml",
+ ],
+ 'demo': [
+ "demo/test_demo.xml",
+ ],
+ 'installable': True,
+ 'auto_install': False,
+}
diff --git a/product_unique_serial_picking/demo/test_demo.xml b/product_unique_serial_picking/demo/test_demo.xml
new file mode 100644
index 000000000000..cd21d04cb980
--- /dev/null
+++ b/product_unique_serial_picking/demo/test_demo.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+ Huawei Ascend
+
+
+
+
+ 75.50
+ 82.25
+
+
+
+ Nokia 2630
+
+
+
+
+ 35.50
+ 42.28
+
+
+
+
+
+ 86137801852514
+
+
+
+
+ 86137801852515
+
+
+
+
+ 86137801852516
+
+
+
+
+
diff --git a/product_unique_serial_picking/model/__init__.py b/product_unique_serial_picking/model/__init__.py
new file mode 100644
index 000000000000..8e24da8c5595
--- /dev/null
+++ b/product_unique_serial_picking/model/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+import product
+import stock
diff --git a/product_unique_serial_picking/model/product.py b/product_unique_serial_picking/model/product.py
new file mode 100644
index 000000000000..cd5d72048f9e
--- /dev/null
+++ b/product_unique_serial_picking/model/product.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+from openerp import fields, models
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ lot_unique_ok = fields.Boolean('Unique lot',
+ help='Forces set qty=1 '
+ 'to specify a Unique '
+ 'Serial Number for '
+ 'all moves')
diff --git a/product_unique_serial_picking/model/stock.py b/product_unique_serial_picking/model/stock.py
new file mode 100644
index 000000000000..ea06c9b1d1ae
--- /dev/null
+++ b/product_unique_serial_picking/model/stock.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+
+from openerp import _, api, fields, exceptions, models
+
+
+class StockProductionLot(models.Model):
+ _inherit = 'stock.production.lot'
+
+ @api.one
+ @api.depends('quant_ids')
+ def _get_last_location_id(self):
+ last_quant_data = self.env['stock.quant'].search_read(
+ [('id', 'in', self.quant_ids.ids)],
+ ['location_id'],
+ order='in_date DESC, id DESC',
+ limit=1)
+ if last_quant_data:
+ self.last_location_id = last_quant_data[0][
+ 'location_id'][0]
+ else:
+ self.last_location_id = False
+
+ last_location_id = fields.Many2one(
+ 'stock.location',
+ string="Last location",
+ compute='_get_last_location_id',
+ store=True) # TODO: Fix fails recomputed
+
+ # Overwrite field to deny create serial number duplicated
+ ref = fields.Char('Internal Reference',
+ help="Internal reference number"
+ " in this case it"
+ " is same of manufacturer's"
+ " serial number",
+ related="name", store=True, readonly=True)
+
+
+class StockMove(models.Model):
+ _inherit = 'stock.move'
+
+ def check_after_action_done(self, cr, uid, operation_or_move,
+ lot_id=None, context=None):
+ super(StockMove, self).check_after_action_done(
+ cr, uid, operation_or_move,
+ lot_id, context=context)
+ return self.check_unicity_qty_available(
+ cr, uid, operation_or_move,
+ lot_id, context=context)
+
+ def check_unicity_move_qty(self, cr, uid, ids, context=None):
+ """
+ Check move quantity to verify that has qty = 1
+ if 'lot unique' is ok on product
+ """
+ if not isinstance(ids, list):
+ ids = [ids]
+ for move in self.browse(cr, uid, ids, context=context):
+ if move.product_id.lot_unique_ok:
+ for move_operation in \
+ move.linked_move_operation_ids:
+ if abs(move_operation.qty) > 1:
+ raise exceptions.ValidationError(_(
+ "Product '%s' has active"
+ " 'unique lot' "
+ "but has qty > 1"
+ ) % (move.product_id.name))
+
+ def check_unicity_qty_available(self, cr, uid, operation_or_move,
+ lot_id,
+ context=None):
+ """
+ Check quantity on hand to verify that has qty = 1
+ if 'lot unique' is ok on product
+ """
+ if operation_or_move.product_id.lot_unique_ok and lot_id:
+ ctx = context.copy()
+ ctx.update({'lot_id': lot_id})
+ product_ctx = self.pool.get('product.product').browse(
+ cr, uid, [operation_or_move.product_id.id],
+ context=ctx)[0]
+ qty = product_ctx.qty_available
+ if not 0 <= qty <= 1:
+ lot = self.pool.get('stock.production.lot').browse(
+ cr, uid, [lot_id])[0]
+ raise exceptions.ValidationError(_(
+ "Product '%s' has active "
+ "'unique lot'\n"
+ "but with this move "
+ "you will have a quantity of "
+ "'%s' in lot '%s'"
+ ) % (operation_or_move.product_id.name, qty, lot.name))
+ return True
+
+ def check_tracking(self, cr, uid, move, lot_id, context=None):
+ res = super(StockMove, self).check_tracking(
+ cr, uid, move, lot_id, context=context)
+ self.check_unicity_move_qty(cr, uid, [move.id], context=context)
+ return res
diff --git a/product_unique_serial_picking/tests/test_for_unicity.py b/product_unique_serial_picking/tests/test_for_unicity.py
new file mode 100644
index 000000000000..dcb02582fb2a
--- /dev/null
+++ b/product_unique_serial_picking/tests/test_for_unicity.py
@@ -0,0 +1,393 @@
+# -*- encoding: utf-8 -*-
+###############################################################################
+# Module Writen to OpenERP, Open Source Management Solution
+#
+# Copyright (c) 2010 Vauxoo - http://www.vauxoo.com/
+# All Rights Reserved.
+# info Vauxoo (info@vauxoo.com)
+###############################################################################
+# Coded by: Sergio Ernesto Tostado Sánchez (sergio@vauxoo.com)
+###############################################################################
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+###############################################################################
+
+from copy import deepcopy
+
+from openerp.tests.common import TransactionCase
+from openerp.exceptions import except_orm
+from openerp.tools import mute_logger
+from psycopg2 import IntegrityError
+
+
+class TestUnicity(TransactionCase):
+
+ """
+ This test will prove the next cases to procure the
+ module unicity:
+ - Test 1: Can't be created two Serial Numbers with the same name
+ """
+
+ def setUp(self):
+ super(TestUnicity, self).setUp()
+ self.stock_production_lot_obj = self.env['stock.production.lot']
+ self.stock_picking_type_obj = self.env['stock.picking.type']
+ self.stock_picking_obj = self.env['stock.picking']
+ self.product_obj = self.env['product.product']
+ self.stock_move_obj = self.env['stock.move']
+ self.product_uom_obj = self.env['product.uom']
+ self.stock_location_obj = self.env['stock.location']
+
+ def create_stock_picking(self, moves_data, picking_data, picking_type):
+ """ Returns the stock.picking object, with his respective created
+ created stock.move lines """
+ # Require deepcopy for clone dict into list items
+ moves_data_copy = deepcopy(moves_data)
+ picking_data_copy = picking_data.copy()
+ stock_move_ids = []
+ default_product_data = None
+ for move_n in moves_data_copy:
+ # Getting the qty for the stock.move
+ qty = move_n.get('qty')
+ del move_n['qty']
+ # Getting default data through product_id onchange
+ if 'source_loc' in move_n.keys()\
+ and 'destination_loc' in move_n.keys():
+ default_product_data = self.stock_move_obj.onchange_product_id(
+ prod_id=move_n.get('product_id'),
+ loc_id=move_n.get('source_loc'),
+ loc_dest_id=move_n.get('destination_loc'))
+ del move_n['source_loc']
+ del move_n['destination_loc']
+ else:
+ default_product_data = self.stock_move_obj.onchange_product_id(
+ move_n.get('product_id'))
+ move_n.update(default_product_data.get('value'))
+ # Getting default data through product_uom_qty onchange
+ default_qty_data = self.stock_move_obj.onchange_quantity(
+ move_n.get('product_id'),
+ qty,
+ default_product_data.get('product_uom'),
+ default_product_data.get('product_uos'))
+ default_qty_data['value'].update({'product_uom_qty': qty})
+ move_n.update(default_qty_data.get('value'))
+ # Getting the new stock.move id
+ move_created = self.stock_move_obj.with_context(
+ default_picking_type_id=picking_type.id).create(move_n)
+ stock_move_ids.append(move_created.id)
+ # Creating picking
+ picking_data_copy.update({
+ 'move_lines': [(6, 0, stock_move_ids)],
+ # TODO: Uncomment when fix current context bug
+ # 'move_lines': [(0, 0, moves_data_copy)],
+ # and remove move_created line
+ 'picking_type_id': picking_type.id})
+ return self.stock_picking_obj.create(picking_data_copy)
+
+ def transfer_picking(self, picking_instance, serial_number=None):
+ """ Creates a wizard to transfer the picking given like parameter """
+ # Marking the picking as Todo
+ picking_instance.action_confirm()
+ if picking_instance.state == 'confirmed':
+ # Checking availability
+ picking_instance.action_assign()
+ if picking_instance.state == 'assigned':
+ # Transfering picking
+ transfer_details = picking_instance.do_enter_transfer_details()
+ wizard_for_transfer = self.env[transfer_details.get('res_model')].\
+ browse(transfer_details.get('res_id'))
+ if serial_number:
+ if len(serial_number) == 1:
+ for transfer_item in wizard_for_transfer.item_ids:
+ transfer_item.lot_id = serial_number[0].id
+ else:
+ # Case for the A, B & C serial numbers: That are 3 like
+ # value for the quantity field in the wizard, so we should
+ # split this record 2 times.
+ # Splitting the record and executing the transfer
+ wizard_split = wizard_for_transfer.item_ids[0].\
+ split_quantities()
+ wizard_for_transfer = self.env['stock.transfer_details'].\
+ browse(wizard_split.get('res_id'))
+ wizard_split = wizard_for_transfer.item_ids[0].\
+ split_quantities()
+ wizard_for_transfer = self.env['stock.transfer_details'].\
+ browse(wizard_split.get('res_id'))
+ index = 0
+ for transfer_item in wizard_for_transfer.item_ids:
+ transfer_item.lot_id = serial_number[index].id
+ index += 1
+ # Executing the picking transfering
+ wizard_for_transfer.do_detailed_transfer()
+
+ def test_1_1product_1serialnumber_2p_in(self):
+ """
+ Test 1. Creating 2 pickings with 1 product for the same serial number,
+ in the receipts scope, with the next form:
+ - Picking 1
+ =============================================
+ || Product || Quantity || Serial Number ||
+ =============================================
+ || A || 1 || 001 ||
+ =============================================
+ - Picking 2
+ =============================================
+ || Product || Quantity || Serial Number ||
+ =============================================
+ || A || 1 || 001 ||
+ =============================================
+ Warehouse: Your Company
+ """
+ # Creating move line for picking
+ product = self.env.ref('product_unique_serial.product_demo_1')
+ stock_move_datas = [{
+ 'product_id': product.id,
+ 'qty': 1.0
+ }]
+ # Creating the pickings
+ picking_data_1 = {
+ 'name': 'Test Picking IN 1',
+ }
+ picking_data_2 = {
+ 'name': 'Test Picking IN 2',
+ }
+ picking_1 = self.create_stock_picking(
+ stock_move_datas, picking_data_1,
+ self.env.ref('stock.picking_type_in'))
+ picking_2 = self.create_stock_picking(
+ stock_move_datas, picking_data_2,
+ self.env.ref('stock.picking_type_in'))
+ # Executing the wizard for pickings transfering
+ self.transfer_picking(
+ picking_1,
+ self.env.ref('product_unique_serial.serial_number_demo_1'))
+ with self.assertRaisesRegexp(
+ except_orm,
+ r"you will have a quantity of '2.0' "
+ r"in lot '86137801852514'"):
+ self.transfer_picking(
+ picking_2,
+ [self.env.ref('product_unique_serial.serial_number_demo_1')])
+
+ def test_2_1product_1serialnumber_2p_out(self):
+ """
+ Test 2. Creating 2 pickings with 1 product for the same serial number,
+ in the delivery orders scope, with the next form:
+ - Picking 1
+ =============================================
+ || Product || Quantity || Serial Number ||
+ =============================================
+ || A || 1 || 001 ||
+ =============================================
+ - Picking 2
+ =============================================
+ || Product || Quantity || Serial Number ||
+ =============================================
+ || A || 1 || 001 ||
+ =============================================
+ NOTE: To can operate this case, we need an IN PICKING
+ Warehouse: Your Company
+ """
+ # Creating move line for picking
+ product = self.env.ref('product_unique_serial.product_demo_1')
+ stock_move_datas = [{
+ 'product_id': product.id,
+ 'qty': 1.0
+ }]
+ # Creating the pickings
+ picking_data_in = {
+ 'name': 'Test Picking IN 1',
+ }
+ picking_data_out_1 = {
+ 'name': 'Test Picking OUT 1',
+ }
+ picking_data_out_2 = {
+ 'name': 'Test Picking OUT 2',
+ }
+ # IN PROCESS
+ picking_in = self.create_stock_picking(
+ stock_move_datas, picking_data_in,
+ self.env.ref('stock.picking_type_in'))
+ self.transfer_picking(
+ picking_in,
+ self.env.ref('product_unique_serial.serial_number_demo_1'))
+ # OUT PROCESS
+ picking_out_1 = self.create_stock_picking(
+ stock_move_datas, picking_data_out_1,
+ self.env.ref('stock.picking_type_out'))
+ picking_out_2 = self.create_stock_picking(
+ stock_move_datas, picking_data_out_2,
+ self.env.ref('stock.picking_type_out'))
+ # Executing the wizard for pickings transfering
+ self.transfer_picking(
+ picking_out_1,
+ self.env.ref('product_unique_serial.serial_number_demo_1'))
+ with self.assertRaisesRegexp(
+ except_orm,
+ r"you will have a quantity of '-1.0' "
+ r"in lot '86137801852514'"):
+ self.transfer_picking(
+ picking_out_2,
+ [self.env.ref('product_unique_serial.serial_number_demo_1')])
+
+ def test_3_1product_qtyno1_1serialnumber_1p_in(self):
+ """
+ Test 3. Creating a picking with 1 product for the same serial number,
+ in the delivery orders scope, with the next form:
+ - Picking 1
+ =============================================
+ || Product || Quantity || Serial Number ||
+ =============================================
+ || A || >1 || 001 ||
+ =============================================
+ Warehouse: Your Company
+ """
+ # Creating move line for picking
+ product = self.env.ref('product_unique_serial.product_demo_1')
+ stock_move_datas = [{
+ 'product_id': product.id,
+ 'qty': 2.0
+ }]
+ # Creating the pickings
+ picking_data_1 = {
+ 'name': 'Test Picking IN 1',
+ }
+ picking_1 = self.create_stock_picking(
+ stock_move_datas, picking_data_1,
+ self.env.ref('stock.picking_type_in'))
+ # Executing the wizard for pickings transfering
+ with self.assertRaisesRegexp(except_orm, r'but has qty > 1'):
+ self.transfer_picking(
+ picking_1,
+ [self.env.ref('product_unique_serial.serial_number_demo_2')])
+
+ def test_4_1product_qty3_3serialnumber_1p_in(self):
+ """
+ Test 4. Creating a picking with 1 product for three serial numbers,
+ in the receipts scope, with the next form:
+ - Picking 1
+ =============================================
+ || Product || Quantity || Serial Number ||
+ =============================================
+ || A || 1 || 001 ||
+ =============================================
+ || A || 1 || 002 ||
+ =============================================
+ || A || 1 || 003 ||
+ =============================================
+ Warehouse: Your Company
+ """
+ # Creating move line for picking
+ product = self.env.ref('product_unique_serial.product_demo_1')
+ stock_move_datas = [
+ {'product_id': product.id, 'qty': 1.0},
+ {'product_id': product.id, 'qty': 1.0},
+ {'product_id': product.id, 'qty': 1.0}
+ ]
+ # Creating the picking
+ picking_data_in = {
+ 'name': 'Test Picking IN 1',
+ }
+ picking_in = self.create_stock_picking(
+ stock_move_datas, picking_data_in,
+ self.env.ref('stock.picking_type_in'))
+ # Executing the wizard for pickings transfering: this should be correct
+ # 'cause is the ideal case
+ self.transfer_picking(
+ picking_in,
+ [self.env.ref('product_unique_serial.serial_number_demo_1'),
+ self.env.ref('product_unique_serial.serial_number_demo_2'),
+ self.env.ref('product_unique_serial.serial_number_demo_3')]
+ )
+
+ def test_5_1product_1serialnumber_2p_internal(self):
+ """
+ Test 5. Creating 2 pickings with 1 product for the same serial number,
+ in the internal scope, with the next form:
+ - Picking 1
+ =============================================
+ || Product || Quantity || Serial Number ||
+ =============================================
+ || A || 1 || 001 ||
+ =============================================
+ - Picking 2
+ =============================================
+ || Product || Quantity || Serial Number ||
+ =============================================
+ || A || 1 || 001 ||
+ =============================================
+ NOTE: To can operate this case, we need an IN PICKING
+ Warehouse: Your Company
+ """
+ product = self.env.ref('product_unique_serial.product_demo_2')
+ stock_move_in_datas = [
+ {'product_id': product.id,
+ 'qty': 1.0,
+ 'source_loc': self.env.ref('stock.stock_location_suppliers').id,
+ 'destination_loc': self.env.ref(
+ 'stock.stock_location_components').id}]
+ stock_move_internal_datas = [
+ {'product_id': product.id,
+ 'qty': 1.0,
+ 'source_loc': self.env.ref('stock.stock_location_components').id,
+ 'destination_loc': self.env.ref('stock.stock_location_14').id}]
+ picking_data_in = {
+ 'name': 'Test Picking IN 1',
+ }
+ picking_data_internal_1 = {
+ 'name': 'Test Picking INTERNAL 1',
+ }
+ picking_data_internal_2 = {
+ 'name': 'Test Picking INTERNAL 2',
+ }
+ # IN PROCESS
+ picking_in = self.create_stock_picking(
+ stock_move_in_datas, picking_data_in,
+ self.env.ref('stock.picking_type_in'))
+ self.transfer_picking(
+ picking_in,
+ [self.env.ref('product_unique_serial.serial_number_demo_1')])
+ # INTERNAL PROCESS
+ picking_internal_1 = self.create_stock_picking(
+ stock_move_internal_datas, picking_data_internal_1,
+ self.env.ref('stock.picking_type_internal'))
+ picking_internal_2 = self.create_stock_picking(
+ stock_move_internal_datas, picking_data_internal_2,
+ self.env.ref('stock.picking_type_internal'))
+ self.transfer_picking(
+ picking_internal_1,
+ [self.env.ref('product_unique_serial.serial_number_demo_1')])
+ with self.assertRaisesRegexp(
+ except_orm,
+ r"Product 'Nokia 2630' has active 'check no negative'"):
+ self.transfer_picking(
+ picking_internal_2,
+ [self.env.ref('product_unique_serial.serial_number_demo_1')])
+
+ @mute_logger('openerp.sql_db')
+ def test_6_1serialnumber_1product_2records(self):
+ """
+ Test 7. Creating 2 identical serial numbers
+ """
+ product_id = self.env.ref('product_unique_serial.product_demo_1')
+ lot_data = {
+ 'name': '86137801852520',
+ 'ref': '86137801852520',
+ 'product_id': product_id.id
+ }
+ self.stock_production_lot_obj.create(lot_data)
+ with self.assertRaisesRegexp(
+ IntegrityError, r'"stock_production_lot_name_ref_uniq"'):
+ self.stock_production_lot_obj.create(lot_data)
diff --git a/product_unique_serial_picking/views/product_view.xml b/product_unique_serial_picking/views/product_view.xml
new file mode 100644
index 000000000000..0774282ba496
--- /dev/null
+++ b/product_unique_serial_picking/views/product_view.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ view.product.unique.serial.form
+ product.template
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/product_unique_serial_picking/views/stock_view.xml b/product_unique_serial_picking/views/stock_view.xml
new file mode 100644
index 000000000000..0be9bbbfb0c7
--- /dev/null
+++ b/product_unique_serial_picking/views/stock_view.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+ stock.production.lot.form.inh.pus
+ stock.production.lot
+
+
+
+
+
+
+
+
+
+ Product Set Products Serial
+ product.serial.wizard
+ form
+ form
+
+ new
+
+
+
+
+ stock.move.tree
+ stock.move
+
+
+
+
+
+
+
+
+
+
diff --git a/product_unique_serial_picking/wizard/__init__.py b/product_unique_serial_picking/wizard/__init__.py
new file mode 100644
index 000000000000..882d2e8c940a
--- /dev/null
+++ b/product_unique_serial_picking/wizard/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+import stock_transfer_details
+import product_serial_wizard
diff --git a/product_unique_serial_picking/wizard/product_serial_wizard.py b/product_unique_serial_picking/wizard/product_serial_wizard.py
new file mode 100644
index 000000000000..1f0ce5557506
--- /dev/null
+++ b/product_unique_serial_picking/wizard/product_serial_wizard.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from openerp import models, fields, api
+
+
+class ProductSerialWizard(models.TransientModel):
+
+ _name = "product.serial.wizard"
+
+ product_id = fields.Many2one('product.product', string='Product')
+ lot_id = fields.Many2one('stock.production.lot', 'Lot', readonly=True, select=True, ondelete="restrict")
+ picking_source_location_id = fields.Many2one('stock.location', string="Head source location", readonly=True)
+ picking_destination_location_id = fields.Many2one('stock.location', string="Head destination location",
+ readonly=True)
+
+ @api.multi
+ def set_serials(self):
+ return True
diff --git a/product_unique_serial_picking/wizard/product_serial_wizard.xml b/product_unique_serial_picking/wizard/product_serial_wizard.xml
new file mode 100644
index 000000000000..dee336afe7f5
--- /dev/null
+++ b/product_unique_serial_picking/wizard/product_serial_wizard.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ Enter transfer details
+ product.serial.wizard
+
+
+
+
+
+
+
diff --git a/product_unique_serial_picking/wizard/stock_transfer_details.py b/product_unique_serial_picking/wizard/stock_transfer_details.py
new file mode 100644
index 000000000000..de91d1b62337
--- /dev/null
+++ b/product_unique_serial_picking/wizard/stock_transfer_details.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+from lxml import etree
+
+from openerp import models, api
+
+
+# TODO: Looking for how append domain string
+def domain_str_append(old_domain_str, subdomain_str):
+ return old_domain_str.replace(
+ "]",
+ ", " + subdomain_str + "]")
+
+
+class StockTransferDetails(models.TransientModel):
+ _inherit = 'stock.transfer_details'
+
+ @api.model
+ def fields_view_get(self, view_id=None, view_type='form',
+ toolbar=False, submenu=False):
+ """
+ Allow create serial only with incoming picking
+ Set option "no_create = True"
+ when picking type is different to incoming.
+ """
+ res = super(StockTransferDetails, self).fields_view_get(
+ view_id=view_id, view_type=view_type,
+ toolbar=toolbar, submenu=submenu)
+ context = self._context
+ if 'item_ids' in res['fields']:
+ arch = res['fields']['item_ids'][
+ 'views']['tree']['arch']
+ doc = etree.XML(arch)
+ if context.get('active_model') == 'stock.picking' \
+ and context.get('active_id'):
+ picking = self.env['stock.picking'].\
+ browse(context['active_id'])
+ for node in doc.xpath("//field[@name='lot_id']"):
+ if picking.picking_type_id.code != 'incoming':
+ node.set('options', "{'no_create': True}")
+ # Don't show unused serial.
+ # allow to select a serial with moves.
+ # TODO: Disable this option when
+ # fields.function last_location_id
+ # was fix it
+ sub_domain = "('quant_ids', '!=', [])"
+ # Set domain to show only serial number
+ # that exists in source location
+ # TODO: Enable when fields.function
+ # last_location_id was fix it
+ # sub_domain = "('last_location_id', '=', " + \
+ # "sourceloc_id)"
+ else:
+ # Don't show old serial number
+ # just allow to create new one or
+ # allow to select a serial without moves
+ sub_domain = "('quant_ids', '=', [])"
+ new_domain = domain_str_append(
+ node.get('domain'), sub_domain)
+ node.set('domain', new_domain)
+ res['fields']['item_ids']['views'][
+ 'tree']['arch'] = etree.tostring(doc)
+ return res