← Back to team overview

openerp-community-reviewer team mailing list archive

lp:~akretion-team/stock-logistic-flows/70-product_serial-plus-plus into lp:stock-logistic-flows/7.0

 

Alexis de Lattre has proposed merging lp:~akretion-team/stock-logistic-flows/70-product_serial-plus-plus into lp:stock-logistic-flows/7.0.

Requested reviews:
  Stock and Logistic Core Editors (stock-logistic-core-editors)

For more details, see:
https://code.launchpad.net/~akretion-team/stock-logistic-flows/70-product_serial-plus-plus/+merge/195144

This merge proposal is for the module product_serial.

The main new feature of this merge proposal is the ability to select or create prodlots from a text file (simple texte file, 1 line per prodlot)

Other changes :
- Keep the native "split in serial number" button, because it can be usefull for products that need manual split in non-predictable lot size.
- Code cleaning : reduce flake8 warnings in the wizard, remove old code

I hope you will like this merge proposal :-)
-- 
https://code.launchpad.net/~akretion-team/stock-logistic-flows/70-product_serial-plus-plus/+merge/195144
Your team Stock and Logistic Core Editors is requested to review the proposed merge of lp:~akretion-team/stock-logistic-flows/70-product_serial-plus-plus into lp:stock-logistic-flows/7.0.
=== modified file 'product_serial/__init__.py'
--- product_serial/__init__.py	2013-08-01 12:40:27 +0000
+++ product_serial/__init__.py	2013-11-13 21:39:32 +0000
@@ -19,8 +19,7 @@
 #
 ##############################################################################
 
-import product
-import stock
-import company
-import wizard
-
+from . import product
+from . import stock
+from . import company
+from . import wizard

=== modified file 'product_serial/stock_view.xml'
--- product_serial/stock_view.xml	2013-05-20 19:39:45 +0000
+++ product_serial/stock_view.xml	2013-11-13 21:39:32 +0000
@@ -32,11 +32,11 @@
             <field name="product_packaging" groups="product.group_stock_packaging" domain="[('product_id','=',product_id)]" invisible="context.get('picking_type') != 'out' or False"/>
             <field name="location_id" groups="stock.group_locations"/>
         </xpath>
-        <xpath expr="//button[@name='%(stock.track_line)d']" position="replace">
-            <button name="split_move" string="Manual Split"
+        <xpath expr="//button[@name='%(stock.track_line)d']" position="after">
+            <button name="split_move" string="Auto-Split"
                     groups="stock.group_production_lot"
                     states="draft,waiting,confirmed,assigned"
-                    type="object" icon="gtk-justify-fill" />
+                    type="object" icon="STOCK_COPY" />
         </xpath>
         <!-- In the form view of Incoming shipments, add the "new_prodlot_code" field -->
         <xpath expr="//field[@name='prodlot_id']" position="before">
@@ -75,83 +75,5 @@
     </field>
 </record>
 
-	<!-- Wizard to help users input production lots in batch -->
-<!-- TODO Nan-TIc : port to v6
-	<record id="view_stock_picking_prodlot_selection" model="ir.ui.view">
-		<field name="name">stock.picking.prodlot.selection</field>
-		<field name="model">stock.picking.prodlot.selection</field>
-		<field name="type">form</field>
-		<field name="arch" type="xml">
-			<form string="Select Production Lots">
-				<field name="product_id" colspan="4"/>
-				<field name="first_lot"/>
-				<field name="last_lot"/>
-				<button type="object" name="action_cancel" string="Cancel" icon="gtk-cancel" special="cancel" colspan="2"/>
-				<button type="object" name="action_accept" string="Accept" icon="gtk-ok" colspan="2"/>
-			</form>
-		</field>
-	</record>
-
-	<record model="ir.actions.act_window" id="action_prodlot_selection">
-		<field name="name">Select Production Lots</field>
-		<field name="res_model">stock.picking.prodlot.selection</field>
-		<field name="view_type">form</field>
-		<field name="view_mode">form</field>
-		<field name="target">new</field>
-	</record>
-
-	<record id="view_picking_form" model="ir.ui.view">
-		<field name="name">stock.picking.form.prodlot.selection</field>
-		<field name="model">stock.picking</field>
-		<field name="type">form</field>
-		<field name="inherit_id" ref="stock.view_picking_form"/>
-		<field name="arch" type="xml">
-			<xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
-				<label colspan="5"/>
-				<button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
-			</xpath>
-		</field>
-	</record>
-
-	<record id="view_picking_in_form" model="ir.ui.view">
-		<field name="name">stock.picking.in.form.prodlot.selection</field>
-		<field name="model">stock.picking</field>
-		<field name="type">form</field>
-		<field name="inherit_id" ref="stock.view_picking_in_form"/>
-		<field name="arch" type="xml">
-			<xpath expr="/form/notebook/page/group/label[@colspan='5']" position="replace">
-				<label colspan="4"/>
-				<button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
-			</xpath>
-		</field>
-	</record>
-
-	<record id="view_picking_out_form" model="ir.ui.view">
-		<field name="name">stock.picking.out.form</field>
-		<field name="model">stock.picking</field>
-		<field name="type">form</field>
-		<field name="inherit_id" ref="stock.view_picking_out_form"/>
-		<field name="arch" type="xml">
-			<xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
-				<label colspan="5"/>
-				<button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
-			</xpath>
-		</field>
-	</record>
-
-	<record id="view_picking_delivery_form" model="ir.ui.view">
-		<field name="name">stock.picking.delivery.form</field>
-		<field name="model">stock.picking</field>
-		<field name="type">form</field>
-		<field name="inherit_id" ref="stock.view_picking_delivery_form"/>
-		<field name="arch" type="xml">
-			<xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
-				<label colspan="5"/>
-				<button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
-			</xpath>
-		</field>
-	</record>
--->
-
 </data>
 </openerp>

=== modified file 'product_serial/wizard/__init__.py'
--- product_serial/wizard/__init__.py	2013-08-01 12:40:27 +0000
+++ product_serial/wizard/__init__.py	2013-11-13 21:39:32 +0000
@@ -20,5 +20,4 @@
 #
 ##############################################################################
 
-import prodlot_wizard
-
+from . import prodlot_wizard

=== modified file 'product_serial/wizard/prodlot_wizard.py'
--- product_serial/wizard/prodlot_wizard.py	2013-08-01 12:40:27 +0000
+++ product_serial/wizard/prodlot_wizard.py	2013-11-13 21:39:32 +0000
@@ -24,95 +24,163 @@
 
 from openerp.osv import orm, fields
 from openerp.tools.translate import _
+import base64
 
 
 class stock_picking_prodlot_selection_wizard(orm.TransientModel):
     _name = 'stock.picking.prodlot.selection'
+    _description = "Select or Create Production Lots"
 
     _columns = {
-        'product_id': fields.many2one('product.product', 'Product', required=True),
+        'product_id': fields.many2one(
+            'product.product', 'Product', required=True),
         'prefix': fields.char('Prefix', size=256),
         'suffix': fields.char('Suffix', size=256),
-        'first_number': fields.char('First Number', size=256, required=True),
-        'last_number': fields.char('Last Number', size=256, required=True),
+        'first_number': fields.char('First Number', size=256),
+        'last_number': fields.char('Last Number', size=256),
+        'prodlot_file': fields.binary(
+            'Prodlot File',
+            help="The Prodlot file should be a text file with one line per prodlot (all for the same product)."),
         'create_prodlots': fields.boolean('Create New Serial Numbers'),
     }
 
+    def _default_create_prodlots(self, cr, uid, context=None):
+        if context is None:
+            context = {}
+        return context.get('default_type') == 'in'
+
     _defaults = {
-        'create_prodlots': False,
+        'create_prodlots': _default_create_prodlots,
         }
 
-
-    def select_or_create_prodlots(self, cr, uid, ids, context=None):
-        if context is None:
-            context = {}
-        if not ids:
-            return {}
-        if not 'active_id' in context:
-            return {}
-
-        prodlot_obj = self.pool['stock.production.lot']
+    def select_or_create_prodlots_from_file(
+            self, cr, uid, ids, context=None):
+        assert len(ids) == 1, "Only one ID"
+        if context is None:
+            context = {}
+        if not ids:
+            return {}
+        if not 'active_id' in context:
+            return {}
+        picking_id = context['active_id']
+
+        record = self.browse(cr, uid, ids[0], context)
+        if not record.prodlot_file:
+            raise orm.except_orm(
+                _('Error'),
+                _("You should upload a text file containing the production lots."))
+        full_prodlot_str = base64.decodestring(record.prodlot_file)
+        full_prodlot_seq = full_prodlot_str.split('\n')
+        # Remove empty lines
+        prodlot_seq = [prodlot for prodlot in full_prodlot_seq if prodlot]
+        return self._select_or_create_prodlots(
+            cr, uid, picking_id, record.product_id, prodlot_seq,
+            record.create_prodlots, context=context)
+
+    def select_or_create_prodlots_from_interval(
+            self, cr, uid, ids, context=None):
+        assert len(ids) == 1, "Only one ID"
+        if context is None:
+            context = {}
+        if not ids:
+            return {}
+        if not 'active_id' in context:
+            return {}
+        picking_id = context['active_id']
+
         record = self.browse(cr, uid, ids[0], context)
         prefix = record.prefix or ''
         suffix = record.suffix or ''
+        if not record.first_number or not record.last_number:
+            raise orm.except_orm(
+                _('Missing parameters'),
+                _('You should enter a value for the First Number and the Last Number'))
         try:
             first_number = int(record.first_number)
         except:
-            raise orm.except_orm(_('Invalid First Number'), _("The field 'First Number' should only contain digits."))
+            raise orm.except_orm(
+                _('Invalid First Number'),
+                _("The field 'First Number' should only contain digits."))
 
         try:
             last_number = int(record.last_number)
         except:
-            raise orm.except_orm(_('Invalid Last Number'), _("The field 'Last Number' should only contain digits."))
+            raise orm.except_orm(
+                _('Invalid Last Number'),
+                _("The field 'Last Number' should only contain digits."))
 
         if last_number < first_number:
-            raise orm.except_orm(_('Invalid Numbers'), _('The First Number must be lower than the Last Number.'))
+            raise orm.except_orm(
+                _('Invalid Numbers'),
+                _('The First Number must be lower than the Last Number.'))
 
         if len(record.first_number) != len(record.last_number):
-            raise orm.except_orm(_('Invalid Lot Numbers'), _('First and Last Numbers must have the same length.'))
+            raise orm.except_orm(
+                _('Invalid Lot Numbers'),
+                _('First and Last Numbers must have the same length.'))
 
         number_length = len(record.first_number)
+        prodlot_seq = ['%%s%%0%dd%%s' % number_length % (
+            prefix, current_number, suffix) for current_number in
+            range(first_number, last_number+1)
+            ]
+        return self._select_or_create_prodlots(
+            cr, uid, picking_id, record.product_id, prodlot_seq,
+            record.create_prodlots, context=context)
 
-        picking_id = context['active_id']
-        current_number = first_number
-        picking = self.pool['stock.picking'].browse(cr, uid, picking_id, context=context)
+    def _select_or_create_prodlots(
+            self, cr, uid, picking_id, product, prodlot_seq,
+            create_prodlots, context=None):
+        assert prodlot_seq and isinstance(prodlot_seq, list)
+        prodlot_obj = self.pool['stock.production.lot']
+        picking = self.pool['stock.picking'].browse(
+            cr, uid, picking_id, context=context)
         company_id = picking.company_id.id
         for move in picking.move_lines:
-            if move.prodlot_id or move.product_id != record.product_id:
+            if move.prodlot_id or move.product_id != product:
                 continue
-
-            current_lot = '%%s%%0%dd%%s' % number_length % (prefix, current_number, suffix)
-            if record.create_prodlots:
+            try:
+                current_lot = prodlot_seq.pop(0)
+            except:
+                break
+            if create_prodlots:
                 # Create new prodlot
-                lot_id_on_move = prodlot_obj.create(cr, uid, {
-                    'product_id': record.product_id.id,
-                    'name': current_lot,
-                    'company_id': company_id,
-                        }, context=context)
+                lot_id_on_move = prodlot_obj.create(
+                    cr, uid, {
+                        'product_id': product.id,
+                        'name': current_lot,
+                        'company_id': company_id,
+                    }, context=context)
             else:
                 # Search existing prodlots
-                lot_ids = prodlot_obj.search(cr, uid, [('name','=',current_lot)], limit=1, context=context)
+                lot_ids = prodlot_obj.search(
+                    cr, uid, [('name', '=', current_lot)], limit=1,
+                    context=context)
                 if not lot_ids:
-                    raise orm.except_orm(_('Invalid lot numbers'), _('Production lot %s not found.') % current_lot)
+                    raise orm.except_orm(
+                        _('Invalid lot numbers'),
+                        _('Production lot %s not found.') % current_lot)
 
                 ctx = context.copy()
                 ctx['location_id'] = move.location_id.id
-                prodlot = self.pool.get('stock.production.lot').browse(cr, uid, lot_ids[0], ctx)
+                prodlot = self.pool.get('stock.production.lot').browse(
+                    cr, uid, lot_ids[0], ctx)
 
-                if prodlot.product_id != record.product_id:
-                    raise orm.except_orm(_('Invalid lot numbers'), _('Production lot %s exists but not for product %s.') % (current_lot, record.product_id.name))
+                if prodlot.product_id != product:
+                    raise orm.except_orm(
+                        _('Invalid lot numbers'),
+                        _('Production lot %s exists but not for product %s.')
+                        % (current_lot, product.name))
 
                 if prodlot.stock_available < move.product_qty:
-                    raise orm.except_orm(_('Invalid lot numbers'), _('Not enough stock available of production lot %s.') % current_lot)
+                    raise orm.except_orm(
+                        _('Invalid lot numbers'),
+                        _('Not enough stock available of production lot %s.')
+                        % current_lot)
                 lot_id_on_move = lot_ids[0]
 
-            self.pool.get('stock.move').write(cr, uid, [move.id], {
-                'prodlot_id': lot_id_on_move,
-            }, context=context)
-
-            current_number += 1
-            if current_number > last_number:
-                break
+            self.pool.get('stock.move').write(
+                cr, uid, [move.id], {'prodlot_id': lot_id_on_move},
+                context=context)
 
         return True
-

=== modified file 'product_serial/wizard/prodlot_wizard_view.xml'
--- product_serial/wizard/prodlot_wizard_view.xml	2013-08-01 12:40:27 +0000
+++ product_serial/wizard/prodlot_wizard_view.xml	2013-11-13 21:39:32 +0000
@@ -7,17 +7,23 @@
     <field name="name">stock.picking.prodlot.selection</field>
     <field name="model">stock.picking.prodlot.selection</field>
     <field name="arch" type="xml">
-        <form string="Select Production Lots" version="7.0">
-            <group>
+        <form string="Select or Create Production Lots" version="7.0">
+            <group name="common">
                 <field name="product_id" />
+                <field name="create_prodlots"/>
+            </group>
+            <group name="from_interval" string="From Interval">
                 <field name="prefix"/>
                 <field name="first_number"/>
                 <field name="last_number"/>
                 <field name="suffix"/>
-                <field name="create_prodlots"/>
+                <button type="object" name="select_or_create_prodlots_from_interval" string="Run From Interval" class="oe_highlight"/>
+            </group>
+            <group name="from_file" string="From File">
+                <field name="prodlot_file"/>
+                <button type="object" name="select_or_create_prodlots_from_file" string="Run From File" class="oe_highlight"/>
             </group>
             <footer>
-                <button type="object" name="select_or_create_prodlots" string="Validate" class="oe_highlight"/>
                 <button string="Cancel" special="cancel" class="oe_link"/>
             </footer>
         </form>
@@ -26,7 +32,7 @@
 
 
 <record id="action_prodlot_selection" model="ir.actions.act_window">
-    <field name="name">Select Production Lots</field>
+    <field name="name">Select or Create Production Lots</field>
     <field name="res_model">stock.picking.prodlot.selection</field>
     <field name="view_type">form</field>
     <field name="view_mode">form</field>


Follow ups