[IMP] Add stock_location_sale for changing procurements with sale orders and grouping...
authorJosse Colpaert <jco@openerp.com>
Thu, 27 Jun 2013 14:46:29 +0000 (16:46 +0200)
committerJosse Colpaert <jco@openerp.com>
Thu, 27 Jun 2013 14:46:29 +0000 (16:46 +0200)
bzr revid: jco@openerp.com-20130627144629-1zegnhelwrfmgdm5

addons/procurement/procurement.py
addons/sale_stock/sale_stock.py
addons/stock/product.py
addons/stock/product_view.xml
addons/stock/report/report_stock_move.py
addons/stock/stock.py
addons/stock_location/procurement_pull.py
addons/stock_location/stock_location.py
addons/stock_location_sale/__init__.py [new file with mode: 0644]
addons/stock_location_sale/__openerp__.py [new file with mode: 0644]
addons/stock_location_sale/stock_location_sale.py [new file with mode: 0644]

index 93b9417..c016c4a 100644 (file)
@@ -65,6 +65,7 @@ class StockMove(osv.osv):
     _inherit = 'stock.move'
     _columns= {
         'procurements': fields.one2many('procurement.order', 'move_id', 'Procurements'),
+        'group_id':fields.many2one('stock.move.group', 'Move Group'), 
     }
 
     def copy_data(self, cr, uid, id, default=None, context=None):
@@ -74,6 +75,28 @@ class StockMove(osv.osv):
         return super(StockMove, self).copy_data(cr, uid, id, default, context=context)
 
 
+class move_group(osv.osv):
+    '''
+    The procurement requirement class is used to group procurement orders. 
+    The goal is that when you have one delivery order of several products
+    and the products are pulled from the same or several location(s), to keep having
+    the moves grouped into pickings.  
+    
+    As the pulled moves are created by the procurement orders who are created by moves/SO/..., 
+    the procurement requisition will bundle these procurement orders according to the same original picking
+    
+    Suppose you have 4 lines on a picking from Output where 2 lines will need to come from Input and 2 lines coming from Stock -> Output
+    As the four procurement orders will have the same group ids from the SO, the move from input will have a stock.picking with 2 grouped lines
+    and the move from stock will have 2 grouped lines also.  
+    '''
+    _name = 'stock.move.group'
+    _columns = {
+        'name': fields.char('Name'), 
+        'sequence_id': fields.many2one('ir.sequence', 'Group Sequence', help="Move group sequence"), 
+        }
+
+
+
 class procurement_order(osv.osv):
     """
     Procurement Orders
@@ -96,8 +119,8 @@ class procurement_order(osv.osv):
         'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, states={'draft':[('readonly',False)]}, readonly=True),
         'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
         'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
-        'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
         'close_move': fields.boolean('Close Move at end'),
+        'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
         'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True),
         'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
             readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
@@ -117,6 +140,7 @@ class procurement_order(osv.osv):
             \nAfter confirming the status is set to \'Running\'.\n If any exception arises in the order then the status is set to \'Exception\'.\n Once the exception is removed the status becomes \'Ready\'.\n It is in \'Waiting\'. status when the procurement is waiting for another one to finish.'),
         'note': fields.text('Note'),
         'company_id': fields.many2one('res.company','Company',required=True),
+        'group_id':fields.many2one('stock.move.group', 'Move Group'), 
     }
     _defaults = {
         'state': 'draft',
@@ -305,6 +329,8 @@ class procurement_order(osv.osv):
         @return: True
         """
         move_obj = self.pool.get('stock.move')
+        mod_obj = self.pool.get('ir.model.data')
+        location_model, location_id = mod_obj.get_object_reference(cr, uid, 'stock', 'stock_location_customers')
         for procurement in self.browse(cr, uid, ids, context=context):
             if procurement.product_qty <= 0.00:
                 raise osv.except_osv(_('Data Insufficient!'),
@@ -312,7 +338,7 @@ class procurement_order(osv.osv):
             if procurement.product_id.type in ('product', 'consu'):
                 if not procurement.move_id:
                     source = procurement.location_id.id
-                    if procurement.procure_method == 'make_to_order':
+                    if procurement.procure_method == 'make_to_order':#and source != location_id: Last statement is not good
                         source = procurement.product_id.property_stock_procurement.id
                     id = move_obj.create(cr, uid, {
                         'name': procurement.name,
@@ -328,7 +354,23 @@ class procurement_order(osv.osv):
                     })
                     move_obj.action_confirm(cr, uid, [id], context=context)
                     self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move': 1})
-        self.write(cr, uid, ids, {'state': 'confirmed', 'message': ''})
+                self.write(cr, uid, [procurement.id], {'state': 'confirmed', 'message': ''})
+                    #Now check if all the moves of group TODO
+
+#                 if procurement.group_id: 
+#                     procs = move_obj.search(cr, uid, [('group_id', '=', procurement.group_id.id), ('state', '=', 'draft')], context=context)
+#                     
+#                     #If can not find any => confirm pickings which have moves from this group
+#                     if not procs: 
+#                         print "CONFIRM REST"
+#                         # Find pickings from this group that need to be confirmed
+#                         moves = move_obj.search(cr, uid, [('group_id', '=', procurement.group_id.id), ('state', '=', 'draft')], context=context)
+#                         pickings = []
+#                         for move in move_obj.browse(cr, uid, moves, context=context): 
+#                             pickings.append(move.picking_id.id)
+#                         pickings = list(set(pickings))
+#                         if pickings:
+#                             self.pool.get('stock.picking').signal_button_confirm(cr, uid, pickings)
         return True
 
     def action_move_assigned(self, cr, uid, ids, context=None):
index 3f7ac28..e282e63 100644 (file)
@@ -27,6 +27,16 @@ from openerp.tools.translate import _
 import pytz
 from openerp import SUPERUSER_ID
 
+
+#COULD BE INTERESTING WHEN PROCUREMENT TO CUSTOMER AND PULL RULE
+class procurement_order(osv.osv):
+    _inherit = 'procurement.order'
+    _columns = {
+        'sale_line_id': fields.many2one('sale.order.line', 'Sale order line'), 
+        }
+
+
+
 class sale_order(osv.osv):
     _inherit = "sale.order"
     
@@ -295,7 +305,10 @@ class sale_order(osv.osv):
         elif mode == 'canceled':
             return canceled
 
-    def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, context=None):
+    def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, group_id = False, context=None):
+        mod_obj = self.pool.get('ir.model.data')
+        location_model, location_id = mod_obj.get_object_reference(cr, uid, 'stock', 'stock_location_customers')
+        output_id = order.warehouse_id.lot_output_id.id
         return {
             'name': line.name,
             'origin': order.name,
@@ -307,16 +320,19 @@ class sale_order(osv.osv):
                     or line.product_uom_qty,
             'product_uos': (line.product_uos and line.product_uos.id)\
                     or line.product_uom.id,
-            'location_id': order.warehouse_id.lot_stock_id.id,
+            'location_id': order.warehouse_id.lot_stock_id.id, #TODO Procurement should be generated towards customers instead
             'procure_method': line.type,
             'move_id': move_id,
             'company_id': order.company_id.id,
             'note': line.name,
+            'group_id': group_id, 
         }
-
-    def _prepare_order_line_move(self, cr, uid, order, line, picking_id, date_planned, context=None):
+    def _prepare_order_line_move(self, cr, uid, order, line, picking_id, date_planned, group_id = False, context=None):
         location_id = order.warehouse_id.lot_stock_id.id
         output_id = order.warehouse_id.lot_output_id.id
+        #mod_obj = self.pool.get('ir.model.data')
+        #location_model, location_id = mod_obj.get_object_reference(cr, uid, 'stock', 'stock_location_customers')
         return {
             'name': line.name,
             'picking_id': picking_id,
@@ -331,15 +347,16 @@ class sale_order(osv.osv):
             'product_packaging': line.product_packaging.id,
             'partner_id': line.address_allotment_id.id or order.partner_shipping_id.id,
             'location_id': location_id,
-            'location_dest_id': output_id,
+            'location_dest_id': output_id, 
             'sale_line_id': line.id,
             'tracking_id': False,
             'state': 'draft',
             #'state': 'waiting',
             'company_id': order.company_id.id,
-            'price_unit': line.product_id.standard_price or 0.0
+            'price_unit': line.product_id.standard_price or 0.0, 
+            'group_id': group_id, 
         }
-
     def _prepare_order_picking(self, cr, uid, order, context=None):
         pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
         return {
@@ -384,6 +401,7 @@ class sale_order(osv.osv):
         date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
         return date_planned
 
+
     def _create_pickings_and_procurements(self, cr, uid, order, order_lines, picking_id=False, context=None):
         """Create the required procurements to supply sales order lines, also connecting
         the procurements to appropriate stock moves in order to bring the goods to the
@@ -403,11 +421,15 @@ class sale_order(osv.osv):
                                will be added. A new picking will be created if ommitted.
         :return: True
         """
+        print "CREATE OLD PICKINGS AND PROCUREMENTS"
         move_obj = self.pool.get('stock.move')
         picking_obj = self.pool.get('stock.picking')
         procurement_obj = self.pool.get('procurement.order')
         proc_ids = []
 
+        #Create group
+        group_id = self.pool.get("stock.move.group").create(cr, uid, {'name': order.name}, context=context)
+
         for line in order_lines:
             if line.state == 'done':
                 continue
@@ -418,12 +440,14 @@ class sale_order(osv.osv):
                 if line.product_id.type in ('product', 'consu'):
                     if not picking_id:
                         picking_id = picking_obj.create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
-                    move_id = move_obj.create(cr, uid, self._prepare_order_line_move(cr, uid, order, line, picking_id, date_planned, context=context))
+                    move_id = move_obj.create(cr, uid, self._prepare_order_line_move(cr, uid, order, line, picking_id, date_planned, group_id=group_id, context=context))
                 else:
                     # a service has no stock move
                     move_id = False
 
-                proc_id = procurement_obj.create(cr, uid, self._prepare_order_line_procurement(cr, uid, order, line, move_id, date_planned, context=context))
+            #TODO Need to do something instead of warehouse
+            if line.product_id:
+                proc_id = procurement_obj.create(cr, uid, self._prepare_order_line_procurement(cr, uid, order, line, move_id, date_planned, group_id = group_id, context=context))
                 proc_ids.append(proc_id)
                 line.write({'procurement_id': proc_id})
                 self.ship_recreate(cr, uid, order, line, move_id, proc_id)
index 3377d55..01d0dcd 100644 (file)
@@ -574,7 +574,7 @@ class product_category(osv.osv):
 
     _inherit = 'product.category'
     _columns = {
-        'removal_strategy': fields.selection([('fifo', 'FIFO'), ('lifo', 'LIFO'), ('nearest', 'Nearest Location')], "Picking Strategy"),
+        'removal_strategy': fields.selection([('fifo', 'FIFO'), ('lifo', 'LIFO'), ('nearest', 'Nearest Location')], "Standard Removal Strategy"),
         'property_stock_journal': fields.property(
             relation='account.journal',
             type='many2one',
index 6355bc0..edb8418 100644 (file)
                             <field name="property_stock_valuation_account_id" domain="[('type','&lt;&gt;','view'), ('type','&lt;&gt;','consolidation')]"/>
                             <field name="property_stock_journal"/>
                         </group>
+                        <group name="removal">
+                            <field name="removal_strategy"/>
+                        </group>
                     </group>
+                    
                 </data>
             </field>
         </record>
index fb64355..52b1b99 100644 (file)
@@ -237,7 +237,7 @@ class report_stock_inventory(osv.osv):
                 LEFT JOIN stock_location location ON (m.location_id = location.id)
                 LEFT JOIN stock_location location_dest ON (m.location_dest_id = location_dest.id)
                 LEFT JOIN product_template pt ON (sq.product_id=pt.id)
-                WHERE location_dest.usage = 'internal' and location.usage <> 'internal'
+                WHERE (location_dest.usage = 'internal' and location.usage <> 'internal') or (sq.qty < 0) or (sq.propagated_from_id IS NOT NULL)
             GROUP BY
                 sq.id, sq.product_id, pt.categ_id, m.partner_id, m.location_id, m.location_dest_id,
                 sq.prodlot_id, sq.in_date, m.state, location.usage, location_dest.usage, location.scrap_location, sq.company_id, to_char(sq.in_date, 'YYYY'), to_char(sq.in_date, 'MM'), p.name
index d96fd16..7b48c62 100644 (file)
@@ -347,7 +347,10 @@ class stock_location(osv.osv):
     
     def get_removal_strategy(self, cr, uid, id, product_id, context=None):
         product = self.pool.get("product.product").browse(cr, uid, product_id, context=context)
-        return product.categ_id.removal_strategy or 'fifo'
+        categ = product.categ_id
+        while (not categ.removal_strategy) and categ.parent_id:
+            categ = categ.parent_id
+        return categ.removal_strategy or None
 
     def _product_get(self, cr, uid, id, product_ids=False, context=None, states=None):
         """
@@ -690,7 +693,7 @@ class stock_quant(osv.osv):
 
 
     
-    def choose_quants(self, cr, uid, location_id,  product_id, qty, context=None):
+    def choose_quants(self, cr, uid, location_id,  product_id, qty, prodlot_id=False, context=None):
         """
         Use the removal strategies of product to search for the correct quants
         
@@ -700,14 +703,21 @@ class stock_quant(osv.osv):
         :TODOparam prodlot_id
         :returns: tuples of (quant_id, qty)
         """
-        #TODO Normally, you should check the removal strategy now
-        #But we will assume it is FIFO for the moment
-        #Will need to create search string beforehand
         if self.pool.get('stock.location').get_removal_strategy(cr, uid, location_id, product_id, context=context) == 'lifo':
-            possible_quants = self.search(cr, uid, [('location_id', 'child_of', location_id), ('product_id','=',product_id), 
+            if prodlot_id: 
+                possible_quants = self.search(cr, uid, [('location_id', 'child_of', location_id), ('product_id','=',product_id), 
+                                                    ('qty', '>', 0.0), ('reservation_id', '=', False), 
+                                                    ('prodlot_id', '=', prodlot_id)], order = 'in_date desc, id desc', context=context)
+            else:
+                possible_quants = self.search(cr, uid, [('location_id', 'child_of', location_id), ('product_id','=',product_id), 
                                                     ('qty', '>', 0.0), ('reservation_id', '=', False)], order = 'in_date desc, id desc', context=context)
         else:
-            possible_quants = self.search(cr, uid, [('location_id', 'child_of', location_id), ('product_id','=',product_id), 
+            if prodlot_id: 
+                possible_quants = self.search(cr, uid, [('location_id', 'child_of', location_id), ('product_id','=',product_id), 
+                                                    ('qty', '>', 0.0), ('reservation_id', '=', False), 
+                                                    ('prodlot_id', '=', prodlot_id)], order = 'in_date, id', context=context)
+            else:
+                possible_quants = self.search(cr, uid, [('location_id', 'child_of', location_id), ('product_id','=',product_id), 
                                                     ('qty', '>', 0.0), ('reservation_id', '=', False)], order = 'in_date, id', context=context)
         qty_todo = qty
         res = []
@@ -2497,7 +2507,7 @@ class stock_move(osv.osv):
                 
                 #Split for source locations
                 qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.product_id.uom_id.id)
-                res2 = quant_obj.choose_quants(cr, uid, move.location_id.id, move.product_id.id, qty, context=context)
+                res2 = quant_obj.choose_quants(cr, uid, move.location_id.id, move.product_id.id, qty, prodlot_id = move.prodlot_id.id, context=context)
                 print res2
                 #Should group quants by location:
                 quants = {}
@@ -2771,7 +2781,7 @@ class stock_move(osv.osv):
             qty_from_move = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.product_id.uom_id.id)
             #Check if the entire quantity has been transformed in to quants
             if qty_from_move > product_qty:
-                quant_tuples = quant_obj.choose_quants(cr, uid, move.location_id.id, move.product_id.id, qty_from_move - product_qty, context=context)
+                quant_tuples = quant_obj.choose_quants(cr, uid, move.location_id.id, move.product_id.id, qty_from_move - product_qty, prodlot_id = move.prodlot_id.id, context=context)
                 create_neg_quant = True
                 if quant_tuples: 
                     quant_obj.split_and_assign_quants(cr, uid, quant_tuples, move, context=context)
@@ -2833,7 +2843,6 @@ class stock_move(osv.osv):
                 self.check_total_qty(cr, uid, ids, context=context)
                 reconciled_quants = self.pool.get("stock.quant").move_quants(cr, uid, quants[move.id], move, context=context)
                 quants[move.id] += reconciled_quants
-                #Generate negative quants if necessary
         
                 
         #Do price calculation on move -> Should pass Quants here -> is a dictionary 
index 56a35d9..db7ba38 100644 (file)
 #
 ##############################################################################
 
-from openerp.osv import osv
+from openerp.osv import osv, fields
 from openerp.tools.translate import _
 
+
+
+
 class procurement_order(osv.osv):
     _inherit = 'procurement.order'
+    
+
+    
     def check_buy(self, cr, uid, ids, context=None):
         for procurement in self.browse(cr, uid, ids, context=context):
             for line in procurement.product_id.flow_pull_ids:
@@ -45,6 +51,7 @@ class procurement_order(osv.osv):
         return False
 
     def action_move_create(self, cr, uid, ids, context=None):
+        #Create moves from 
         proc_obj = self.pool.get('procurement.order')
         move_obj = self.pool.get('stock.move')
         picking_obj=self.pool.get('stock.picking')
@@ -55,17 +62,25 @@ class procurement_order(osv.osv):
                     break
             assert line, 'Line cannot be False if we are on this state of the workflow'
             origin = (proc.origin or proc.name or '').split(':')[0] +':'+line.name
-            picking_id = picking_obj.create(cr, uid, {
-                'origin': origin,
-                'company_id': line.company_id and line.company_id.id or False,
-                'type': line.picking_type,
-                'stock_journal_id': line.journal_id and line.journal_id.id or False,
-                'move_type': 'one',
-                'partner_id': line.partner_address_id.id,
-                'note': _('Picking for pulled procurement coming from original location %s, pull rule %s, via original Procurement %s (#%d)') % (proc.location_id.name, line.name, proc.name, proc.id),
-                'invoice_state': line.invoice_state,
-            })
             
+            #First do a search for moves which would not already have this picking:
+            moves = move_obj.search(cr, uid, [('group_id', '=', proc.group_id.id), ('location_id', '=', line.location_src_id.id), 
+                                              ('location_dest_id', '=', line.location_id.id), 
+                                              ], context=context)
+            print "moves, ", moves, proc.group_id.id
+            if moves and move_obj.browse(cr, uid, moves[0], context=context).picking_id:
+                picking_id = move_obj.browse(cr, uid, moves[0], context=context).picking_id.id
+            else:
+                picking_id = picking_obj.create(cr, uid, {
+                                                  'origin': origin,
+                                                  'company_id': line.company_id and line.company_id.id or False,
+                                                  'type': line.picking_type,
+                                                  'stock_journal_id': line.journal_id and line.journal_id.id or False,
+                                                  'move_type': 'one',
+                                                  'partner_id': line.partner_address_id.id,
+                                                  'note': _('Picking for pulled procurement coming from original location %s, pull rule %s, via original Procurement %s (#%d)') % (proc.location_id.name, line.name, proc.name, proc.id),
+                                                  'invoice_state': line.invoice_state,
+                                                          })
             move_id = move_obj.create(cr, uid, {
                 'name': line.name,
                 'picking_id': picking_id,
@@ -84,6 +99,7 @@ class procurement_order(osv.osv):
                 'move_dest_id': proc.move_id and proc.move_id.id or False, # to verif, about history ?
                 'tracking_id': False,
                 'cancel_cascade': line.cancel_cascade,
+                'group_id': proc.group_id.id, 
                 'state': 'confirmed',
                 'note': _('Move for pulled procurement coming from original location %s, pull rule %s, via original Procurement %s (#%d)') % (proc.location_id.name, line.name, proc.name, proc.id),
             })
@@ -106,9 +122,11 @@ class procurement_order(osv.osv):
                         or proc.product_uom.id,
                 'location_id': line.location_src_id.id,
                 'procure_method': line.procure_method,
-                'move_id': move_id,
+                'move_id': move_id, 
+                'group_id': proc.group_id.id, 
             })
             self.pool.get('stock.picking').signal_button_confirm(cr, uid, [picking_id])
+            
             self.signal_button_confirm(cr, uid, [proc_id])
             if proc.move_id:
                 move_obj.write(cr, uid, [proc.move_id.id],
index 46dd59b..e88ec4c 100644 (file)
@@ -98,10 +98,9 @@ class product_putaway_strategy(osv.osv):
     _description = 'Put Away Strategy'
     _columns = {
         'product_ids':fields.function(_calc_product_ids, "Products"), 
-        'product_categ_id':fields.many2one('product.category', 'Product Category'),
-        'location_id': fields.many2one('stock.location','Parent Location', help="Parent Destination Location from which a child bin location needs to be chosen"), #domain=[('type', '=', 'parent')], 
-        'method': fields.selection([('nearest_empty','Nearest Empty Location'), ('add_or_nearest_empty', 'Try to add on another location, otherwise nearest empty'), ('L2R', 'left to right'), ('R2L', 'right to left'), 
-                                    ('high2low', 'high to low'), ('low2high', 'low to high'), ('fixed', 'Fixed Location')], "Method"),
+        'product_categ_id':fields.many2one('product.category', 'Product Category', required=True),
+        'location_id': fields.many2one('stock.location','Parent Location', help="Parent Destination Location from which a child bin location needs to be chosen", required=True), #domain=[('type', '=', 'parent')], 
+        'method': fields.selection([('empty', 'Empty'), ('fixed', 'Fixed Location')], "Method", required = True),
                 }
 
 
@@ -117,9 +116,9 @@ class product_removal_strategy(osv.osv):
     _description = 'Removal Strategy'
     _columns = {
         'product_ids':fields.function(_calc_product_ids, "Products"),
-        'product_categ_id':fields.many2one('product.category', 'Product Category'),
-        'location_id': fields.many2one('stock.location', 'Parent Location', help="Parent Source Location from which a child bin location needs to be chosen"), #, domain=[('type', '=', 'parent')]
-        'method': fields.selection([('fifo', 'FIFO'), ('lifo', 'LIFO'), ('nearest', 'Nearest location')], "Method"), 
+        'product_categ_id':fields.many2one('product.category', 'Product Category', required=True),
+        'location_id': fields.many2one('stock.location', 'Parent Location', help="Parent Source Location from which a child bin location needs to be chosen", required=True), #, domain=[('type', '=', 'parent')]
+        'method': fields.selection([('fifo', 'FIFO'), ('lifo', 'LIFO')], "Method", required=True), 
         }
 
 
@@ -197,13 +196,17 @@ class stock_location(osv.osv):
         return strats and strats[0] or 'nearest'
 
     def get_removal_strategy(self, cr, uid, id, product_id, context=None):
+        #TODO improve code
         product = self.pool.get("product.product").browse(cr, uid, product_id, context=context)
-        strats = self.pool.get('product.removal').search(cr, uid, [('location_id','=',id), ('product_categ_id','child_of', product.categ_id.id)], context=context) #Also child_of for location???
+        strats = self.pool.get('product.removal').search(cr, uid, [('location_id','=',id), ('product_categ_id','=', product.categ_id.id)], context=context) #Also child_of for location???
         if not strats: 
             strat = product.categ_id.removal_strategy
         else: 
             strat = strats[0]
         return strat or product.categ_id.removal_strategy or 'fifo'
+
+        return super(stock_location, self).get_removal_strategy(cr, uid, id, product_id, context=context)
+
     
     def chained_location_get(self, cr, uid, location, partner=None, product=None, context=None):
         if product:
diff --git a/addons/stock_location_sale/__init__.py b/addons/stock_location_sale/__init__.py
new file mode 100644 (file)
index 0000000..d9d11c5
--- /dev/null
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+#    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 <http://www.gnu.org/licenses/>.     
+#
+##############################################################################
+
+import stock_location_sale
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock_location_sale/__openerp__.py b/addons/stock_location_sale/__openerp__.py
new file mode 100644 (file)
index 0000000..84fe0e7
--- /dev/null
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+{
+    'name': 'Advanced Routes with sales',
+    'version': '1.0',
+    'category': 'Manufacturing',
+    'description': """
+Cross-module for stock_location and sale such that a sale order generates a procurement in stock instead
+    """,
+    'author': 'OpenERP SA',
+    'images': [],
+    'depends': ['sale', 'stock_location'],
+    'data': [],
+    'demo': [
+     #   'stock_location_demo_cpu1.xml',
+     #   'stock_location_demo_cpu3.yml',
+    ],
+    'installable': True,
+    'test': [
+    #    'test/stock_location_pull_flow.yml',
+    #    'test/stock_location_push_flow.yml',
+    ],
+    'auto_install': False,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock_location_sale/stock_location_sale.py b/addons/stock_location_sale/stock_location_sale.py
new file mode 100644 (file)
index 0000000..4b06962
--- /dev/null
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+
+
+class sale_order(osv.osv):
+    
+    _inherit = "sale.order"
+    
+    
+    
+    
+    
+    def action_ship_create(self, cr, uid, ids, context=None):
+        print "acitonshipcreate"
+        for order in self.browse(cr, uid, ids, context=context):
+            self._create_pickings_and_procurements(cr, uid, order, order.order_line, None, context=context)
+        return True
+    
+    
+    def _create_pickings_and_procurements(self, cr, uid, order, order_lines, picking_id=False, context=None):
+        """Create the required procurements to supply sales order lines, also connecting
+        the procurements to appropriate stock moves in order to bring the goods to the
+        sales order's requested location.
+
+        If ``picking_id`` is provided, the stock moves will be added to it, otherwise
+        a standard outgoing picking will be created to wrap the stock moves, as returned
+        by :meth:`~._prepare_order_picking`.
+
+        Modules that wish to customize the procurements or partition the stock moves over
+        multiple stock pickings may override this method and call ``super()`` with
+        different subsets of ``order_lines`` and/or preset ``picking_id`` values.
+
+        :param browse_record order: sales order to which the order lines belong
+        :param list(browse_record) order_lines: sales order line records to procure
+        :param int picking_id: optional ID of a stock picking to which the created stock moves
+                               will be added. A new picking will be created if omitted.
+        :return: True
+        """
+        print "Create pickings and procurements!"
+        move_obj = self.pool.get('stock.move')
+        picking_obj = self.pool.get('stock.picking')
+        procurement_obj = self.pool.get('procurement.order')
+        proc_ids = []
+
+        #Create group
+        group_id = self.pool.get("stock.move.group").create(cr, uid, {'name': order.name}, context=context)
+
+        for line in order_lines:
+            if line.state == 'done':
+                continue
+
+            date_planned = self._get_date_planned(cr, uid, order, line, order.date_order, context=context)
+
+#             if line.product_id:
+#                 if line.product_id.type in ('product', 'consu'):
+#                     if not picking_id:
+#                         picking_id = picking_obj.create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
+#                     #move_id = move_obj.create(cr, uid, self._prepare_order_line_move(cr, uid, order, line, picking_id, date_planned, group_id=group_id, context=context))
+#                 else:
+#                     # a service has no stock move
+#                     move_id = False
+
+            #TODO Need to do something instead of warehouse
+            if line.product_id:
+                proc_id = procurement_obj.create(cr, uid, self._prepare_order_line_procurement(cr, uid, order, line, [], date_planned, group_id = group_id, context=context))
+                proc_ids.append(proc_id)
+                line.write({'procurement_id': proc_id})
+                #self.ship_recreate(cr, uid, order, line, move_id, proc_id)
+
+#         if picking_id:
+#             picking_obj.signal_button_confirm(cr, uid, [picking_id])
+        procurement_obj.signal_button_confirm(cr, uid, proc_ids)
+
+        val = {}
+        if order.state == 'shipping_except':
+            val['state'] = 'progress'
+            val['shipped'] = False
+
+            if (order.order_policy == 'manual'):
+                for line in order.order_line:
+                    if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
+                        val['state'] = 'manual'
+                        break
+        order.write(val)
+        return True
+
+
+
+    def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, group_id = False, context=None):
+        mod_obj = self.pool.get('ir.model.data')
+        location_model, location_id = mod_obj.get_object_reference(cr, uid, 'stock', 'stock_location_customers')
+        output_id = order.warehouse_id.lot_output_id.id
+        return {
+            'name': line.name,
+            'origin': order.name,
+            'date_planned': date_planned,
+            'product_id': line.product_id.id,
+            'product_qty': line.product_uom_qty,
+            'product_uom': line.product_uom.id,
+            'product_uos_qty': (line.product_uos and line.product_uos_qty)\
+                    or line.product_uom_qty,
+            'product_uos': (line.product_uos and line.product_uos.id)\
+                    or line.product_uom.id,
+            'location_id': location_id, 
+            'procure_method': line.type,
+            'move_id': move_id,
+            'company_id': order.company_id.id,
+            'note': line.name,
+            'group_id': group_id,
+            'state': 'draft',  
+        }
\ No newline at end of file