[MERGE] forward port of branch saas-3 up to 6c13c8d
authorChristophe Simonis <chs@odoo.com>
Thu, 20 Nov 2014 10:37:23 +0000 (11:37 +0100)
committerChristophe Simonis <chs@odoo.com>
Thu, 20 Nov 2014 10:37:23 +0000 (11:37 +0100)
1  2 
addons/account/project/project.py
addons/mrp/stock.py

@@@ -25,11 -25,11 +25,11 @@@ class account_analytic_journal(osv.osv)
      _name = 'account.analytic.journal'
      _description = 'Analytic Journal'
      _columns = {
 -        'name': fields.char('Journal Name', size=64, required=True),
 +        'name': fields.char('Journal Name', required=True),
          'code': fields.char('Journal Code', size=8),
          'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the analytic journal without removing it."),
 -        'type': fields.selection([('sale','Sale'), ('purchase','Purchase'), ('cash','Cash'), ('general','General'), ('situation','Situation')], 'Type', size=32, required=True, help="Gives the type of the analytic journal. When it needs for a document (eg: an invoice) to create analytic entries, OpenERP will look for a matching journal of the same type."),
 -        'line_ids': fields.one2many('account.analytic.line', 'journal_id', 'Lines'),
 +        'type': fields.selection([('sale','Sale'), ('purchase','Purchase'), ('cash','Cash'), ('general','General'), ('situation','Situation')], 'Type', required=True, help="Gives the type of the analytic journal. When it needs for a document (eg: an invoice) to create analytic entries, Odoo will look for a matching journal of the same type."),
-         'line_ids': fields.one2many('account.analytic.line', 'journal_id', 'Lines'),
++        'line_ids': fields.one2many('account.analytic.line', 'journal_id', 'Lines', copy=False),
          'company_id': fields.many2one('res.company', 'Company', required=True),
      }
      _defaults = {
@@@ -65,142 -42,90 +65,143 @@@ class StockMove(osv.osv)
          """
          bom_obj = self.pool.get('mrp.bom')
          move_obj = self.pool.get('stock.move')
 -        procurement_obj = self.pool.get('procurement.order')
 -        product_obj = self.pool.get('product.product')
 -        processed_ids = [move.id]
 -        if move.product_id.supply_method == 'produce':
 -            bis = bom_obj.search(cr, uid, [
 -                ('product_id','=',move.product_id.id),
 -                ('bom_id','=',False),
 -                ('type','=','phantom')])
 -            if bis:
 -                factor = move.product_qty
 -                bom_point = bom_obj.browse(cr, uid, bis[0], context=context)
 -                res = bom_obj._bom_explode(cr, uid, bom_point, factor, [])
 -                for line in res[0]: 
 +        prod_obj = self.pool.get("product.product")
 +        proc_obj = self.pool.get("procurement.order")
 +        uom_obj = self.pool.get("product.uom")
 +        to_explode_again_ids = []
 +        processed_ids = []
 +        bis = self._check_phantom_bom(cr, uid, move, context=context)
 +        if bis:
 +            bom_point = bom_obj.browse(cr, SUPERUSER_ID, bis[0], context=context)
 +            factor = uom_obj._compute_qty(cr, SUPERUSER_ID, move.product_uom.id, move.product_uom_qty, bom_point.product_uom.id) / bom_point.product_qty
 +            res = bom_obj._bom_explode(cr, SUPERUSER_ID, bom_point, move.product_id, factor, [], context=context)
 +            
-             state = 'confirmed'
-             if move.state == 'assigned':
-                 state = 'assigned'
 +            for line in res[0]:
 +                product = prod_obj.browse(cr, uid, line['product_id'], context=context)
 +                if product.type != 'service':
                      valdef = {
 -                        'picking_id': move.picking_id.id,
 +                        'picking_id': move.picking_id.id if move.picking_id else False,
                          'product_id': line['product_id'],
                          'product_uom': line['product_uom'],
 -                        'product_qty': line['product_qty'],
 +                        'product_uom_qty': line['product_qty'],
                          'product_uos': line['product_uos'],
                          'product_uos_qty': line['product_uos_qty'],
-                         'state': state,
 -                        'move_dest_id': move.id,
+                         'state': 'draft',  #will be confirmed below
                          'name': line['name'],
 -                        'move_history_ids': [(6,0,[move.id])],
 -                        'move_history_ids2': [(6,0,[])],
 -                        'procurements': [],
 +                        'procurement_id': move.procurement_id.id,
 +                        'split_from': move.id, #Needed in order to keep sale connection, but will be removed by unlink
                      }
 -                    mid = move_obj.copy(cr, uid, move.id, default=valdef)
 -                    processed_ids.append(mid)
 -                    prodobj = product_obj.browse(cr, uid, line['product_id'], context=context)
 -                    proc_id = procurement_obj.create(cr, uid, {
 -                        'name': (move.picking_id.origin or ''),
 -                        'origin': (move.picking_id.origin or ''),
 -                        'date_planned': move.date,
 -                        'product_id': line['product_id'],
 -                        'product_qty': line['product_qty'],
 -                        'product_uom': line['product_uom'],
 -                        'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
 -                        'product_uos':  line['product_uos'],
 -                        'location_id': move.location_id.id,
 -                        'procure_method': prodobj.procure_method,
 -                        'move_id': mid,
 -                    })
 -                    procurement_obj.signal_button_confirm(cr, uid, [proc_id])
 -                    
 -                move_obj.write(cr, uid, [move.id], {
 -                    'location_dest_id': move.location_id.id, # dummy move for the kit
 -                    'auto_validate': True,
 -                    'picking_id': False,
 -                    'state': 'confirmed'
 -                })
 -                procurement_ids = procurement_obj.search(cr, uid, [('move_id','=',move.id)], context)
 -                procurement_obj.signal_button_confirm(cr, uid, procurement_ids)
 -                procurement_obj.signal_button_wait_done(cr, uid, procurement_ids)
 -        if processed_ids and move.state == 'assigned':
 -            # Set the state of resulting moves according to 'assigned' as the original move is assigned
 -            move_obj.write(cr, uid, list(set(processed_ids) - set([move.id])), {'state': 'assigned'}, context=context)
 -        return processed_ids
 -    
 -    def action_consume(self, cr, uid, ids, product_qty, location_id=False, context=None):
 -        """ Consumed product with specific quatity from specific source location.
 -        @param product_qty: Consumed product quantity
 +                    mid = move_obj.copy(cr, uid, move.id, default=valdef, context=context)
 +                    to_explode_again_ids.append(mid)
 +                else:
 +                    if prod_obj.need_procurement(cr, uid, [product.id], context=context):
 +                        valdef = {
 +                            'name': move.rule_id and move.rule_id.name or "/",
 +                            'origin': move.origin,
 +                            'company_id': move.company_id and move.company_id.id or False,
 +                            'date_planned': move.date,
 +                            'product_id': line['product_id'],
 +                            'product_qty': line['product_qty'],
 +                            'product_uom': line['product_uom'],
 +                            'product_uos_qty': line['product_uos_qty'],
 +                            'product_uos': line['product_uos'],
 +                            'group_id': move.group_id.id,
 +                            'priority': move.priority,
 +                            'partner_dest_id': move.partner_id.id,
 +                            }
 +                        if move.procurement_id:
 +                            proc = proc_obj.copy(cr, uid, move.procurement_id.id, default=valdef, context=context)
 +                        else:
 +                            proc = proc_obj.create(cr, uid, valdef, context=context)
 +                        proc_obj.run(cr, uid, [proc], context=context) #could be omitted
 +
 +            
 +            #check if new moves needs to be exploded
 +            if to_explode_again_ids:
 +                for new_move in self.browse(cr, uid, to_explode_again_ids, context=context):
 +                    processed_ids.extend(self._action_explode(cr, uid, new_move, context=context))
 +            
 +            if not move.split_from and move.procurement_id:
 +                # Check if procurements have been made to wait for
 +                moves = move.procurement_id.move_ids
 +                if len(moves) == 1:
 +                    proc_obj.write(cr, uid, [move.procurement_id.id], {'state': 'done'}, context=context)
++
++            if processed_ids and move.state == 'assigned':
++                # Set the state of resulting moves according to 'assigned' as the original move is assigned
++                move_obj.write(cr, uid, list(set(processed_ids) - set([move.id])), {'state': 'assigned'}, context=context)
 +                
 +            #delete the move with original product which is not relevant anymore
 +            move_obj.unlink(cr, SUPERUSER_ID, [move.id], context=context)
 +        #return list of newly created move or the move id otherwise, unless there is no move anymore
 +        return processed_ids or (not bis and [move.id]) or []
 +
 +    def action_confirm(self, cr, uid, ids, context=None):
 +        move_ids = []
 +        for move in self.browse(cr, uid, ids, context=context):
 +            #in order to explode a move, we must have a picking_type_id on that move because otherwise the move
 +            #won't be assigned to a picking and it would be weird to explode a move into several if they aren't
 +            #all grouped in the same picking.
 +            if move.picking_type_id:
 +                move_ids.extend(self._action_explode(cr, uid, move, context=context))
 +            else:
 +                move_ids.append(move.id)
 +
 +        #we go further with the list of ids potentially changed by action_explode
 +        return super(StockMove, self).action_confirm(cr, uid, move_ids, context=context)
 +
 +    def action_consume(self, cr, uid, ids, product_qty, location_id=False, restrict_lot_id=False, restrict_partner_id=False,
 +                       consumed_for=False, context=None):
 +        """ Consumed product with specific quantity from specific source location.
 +        @param product_qty: Consumed/produced product quantity (= in quantity of UoM of product)
          @param location_id: Source location
 -        @return: Consumed lines
 -        """       
 +        @param restrict_lot_id: optionnal parameter that allows to restrict the choice of quants on this specific lot
 +        @param restrict_partner_id: optionnal parameter that allows to restrict the choice of quants to this specific partner
 +        @param consumed_for: optionnal parameter given to this function to make the link between raw material consumed and produced product, for a better traceability
 +        @return: New lines created if not everything was consumed for this line
 +        """
 +        if context is None:
 +            context = {}
          res = []
          production_obj = self.pool.get('mrp.production')
 -        for move in self.browse(cr, uid, ids):
 -            move.action_confirm(context)
 -            new_moves = super(StockMove, self).action_consume(cr, uid, [move.id], product_qty, location_id, context=context)
 -            production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
 -            for prod in production_obj.browse(cr, uid, production_ids, context=context):
 -                if prod.state == 'confirmed':
 -                    production_obj.force_production(cr, uid, [prod.id])
 -            production_obj.signal_button_produce(cr, uid, production_ids)                
 -            for new_move in new_moves:
 -                if new_move == move.id:
 -                    #This move is already there in move lines of production order
 -                    continue
 -                production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
 -                res.append(new_move)
 +
 +        if product_qty <= 0:
 +            raise osv.except_osv(_('Warning!'), _('Please provide proper quantity.'))
 +        #because of the action_confirm that can create extra moves in case of phantom bom, we need to make 2 loops
 +        ids2 = []
 +        for move in self.browse(cr, uid, ids, context=context):
 +            if move.state == 'draft':
 +                ids2.extend(self.action_confirm(cr, uid, [move.id], context=context))
 +            else:
 +                ids2.append(move.id)
 +
 +        prod_orders = set()
 +        for move in self.browse(cr, uid, ids2, context=context):
 +            prod_orders.add(move.raw_material_production_id.id or move.production_id.id)
 +            move_qty = move.product_qty
 +            if move_qty <= 0:
 +                raise osv.except_osv(_('Error!'), _('Cannot consume a move with negative or zero quantity.'))
 +            quantity_rest = move_qty - product_qty
 +            # Compare with numbers of move uom as we want to avoid a split with 0 qty
 +            quantity_rest_uom = move.product_uom_qty - self.pool.get("product.uom")._compute_qty_obj(cr, uid, move.product_id.uom_id, product_qty, move.product_uom)
 +            if float_compare(quantity_rest_uom, 0, precision_rounding=move.product_uom.rounding) != 0:
 +                new_mov = self.split(cr, uid, move, quantity_rest, context=context)
 +                res.append(new_mov)
 +            vals = {'restrict_lot_id': restrict_lot_id,
 +                    'restrict_partner_id': restrict_partner_id,
 +                    'consumed_for': consumed_for}
 +            if location_id:
 +                vals.update({'location_id': location_id})
 +            self.write(cr, uid, [move.id], vals, context=context)
 +        # Original moves will be the quantities consumed, so they need to be done
 +        self.action_done(cr, uid, ids2, context=context)
 +        if res:
 +            self.action_assign(cr, uid, res, context=context)
 +        if prod_orders:
 +            production_obj.signal_workflow(cr, uid, list(prod_orders), 'button_produce')
          return res
 -    
 -    def action_scrap(self, cr, uid, ids, product_qty, location_id, context=None):
 +
 +    def action_scrap(self, cr, uid, ids, product_qty, location_id, restrict_lot_id=False, restrict_partner_id=False, context=None):
          """ Move the scrap/damaged product into scrap location
          @param product_qty: Scraped product quantity
          @param location_id: Scrap location