[MERGE] merge with latest stable
[odoo/odoo.git] / addons / purchase / purchase.py
index b2127cf..0767e20 100644 (file)
@@ -68,18 +68,21 @@ class purchase_order(osv.osv):
         if type(ids)!=type([]):
             ids=[ids]
         for po in self.browse(cr, uid, ids, context=context):
-            cr.execute("""update purchase_order_line set
-                    date_planned=%s
-                where
-                    order_id=%s and
-                    (date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
+            if po.order_line:
+                cr.execute("""update purchase_order_line set
+                        date_planned=%s
+                    where
+                        order_id=%s and
+                        (date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
+            cr.execute("""update purchase_order set
+                    minimum_planned_date=%s where id=%s""", (value, po.id))
         return True
 
     def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context=None):
         res={}
         purchase_obj=self.browse(cr, uid, ids, context=context)
         for purchase in purchase_obj:
-            res[purchase.id] = time.strftime('%Y-%m-%d %H:%M:%S')
+            res[purchase.id] = False
             if purchase.order_line:
                 min_date=purchase.order_line[0].date_planned
                 for line in purchase.order_line:
@@ -96,8 +99,9 @@ class purchase_order(osv.osv):
             for invoice in purchase.invoice_ids:
                 if invoice.state not in ('draft','cancel'):
                     tot += invoice.amount_untaxed
+            
             if purchase.amount_untaxed:
-                res[purchase.id] = tot * 100.0 / purchase.amount_untaxed
+                res[purchase.id] = min(100.0, tot * 100.0 / (purchase.amount_untaxed))
             else:
                 res[purchase.id] = 0.0
         return res
@@ -148,7 +152,7 @@ class purchase_order(osv.osv):
     STATE_SELECTION = [
         ('draft', 'Request for Quotation'),
         ('wait', 'Waiting'),
-        ('confirmed', 'Waiting Supplier Ack'),
+        ('confirmed', 'Waiting Approval'),
         ('approved', 'Approved'),
         ('except_picking', 'Shipping Exception'),
         ('except_invoice', 'Invoice Exception'),
@@ -162,8 +166,8 @@ class purchase_order(osv.osv):
             help="Reference of the document that generated this purchase order request."
         ),
         'partner_ref': fields.char('Supplier Reference', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, size=64),
-        'date_order':fields.date('Date Ordered', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, help="Date on which this document has been created."),
-        'date_approve':fields.date('Date Approved', readonly=1, help="Date on which purchase order has been approved"),
+        'date_order':fields.date('Date Ordered', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, select=True, help="Date on which this document has been created."),
+        'date_approve':fields.date('Date Approved', readonly=1, select=True, help="Date on which purchase order has been approved"),
         'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, change_default=True),
         'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True,
             states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},domain="[('partner_id', '=', partner_id)]"),
@@ -191,7 +195,11 @@ class purchase_order(osv.osv):
                 "From Picking: a draft invoice will be pre-generated based on validated receptions.\n" \
                 "Manual: allows you to generate suppliers invoices by chosing in the uninvoiced lines of all manual purchase orders."
         ),
-        'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, method=True,store=True, string='Expected Date', type='date', help="This is computed as the minimum scheduled date of all purchase order lines' products."),
+        'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, method=True, string='Expected Date', type='date', select=True, help="This is computed as the minimum scheduled date of all purchase order lines' products.",
+            store = {
+                'purchase.order.line': (_get_order, ['date_planned'], 10),
+            }
+        ),
         'amount_untaxed': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Purchase Price'), string='Untaxed Amount',
             store={
                 'purchase.order.line': (_get_order, None, 10),
@@ -210,7 +218,7 @@ class purchase_order(osv.osv):
         'company_id': fields.many2one('res.company','Company',required=True,select=1),
     }
     _defaults = {
-        'date_order': time.strftime('%Y-%m-%d'),
+        'date_order': lambda *a: time.strftime('%Y-%m-%d'),
         'state': 'draft',
         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
         'shipped': 0,
@@ -250,9 +258,12 @@ class purchase_order(osv.osv):
     def onchange_dest_address_id(self, cr, uid, ids, adr_id):
         if not adr_id:
             return {}
-        part_id = self.pool.get('res.partner.address').read(cr, uid, [adr_id], ['partner_id'])[0]['partner_id'][0]
-        loc_id = self.pool.get('res.partner').browse(cr, uid, part_id).property_stock_customer.id
-        return {'value':{'location_id': loc_id, 'warehouse_id': False}}
+        values = {'warehouse_id': False}
+        part_id = self.pool.get('res.partner.address').browse(cr, uid, adr_id).partner_id
+        if part_id:
+            loc_id = part_id.property_stock_customer.id
+            values.update({'location_id': loc_id})
+        return {'value':values}
 
     def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
         if not warehouse_id:
@@ -337,7 +348,8 @@ class purchase_order(osv.osv):
 
     def action_invoice_create(self, cr, uid, ids, *args):
         res = False
-
+        property_obj = self.pool.get('ir.property')
+        fp_obj =  self.pool.get('account.fiscal.position')
         journal_obj = self.pool.get('account.journal')
         for o in self.browse(cr, uid, ids):
             il = []
@@ -345,26 +357,30 @@ class purchase_order(osv.osv):
             for ol in o.order_line:
                 todo.append(ol.id)
                 if ol.product_id:
-                    a = ol.product_id.product_tmpl_id.property_account_expense.id
-                    if not a:
-                        a = ol.product_id.categ_id.property_account_expense_categ.id
-                    if not a:
+                    acc_id = ol.product_id.product_tmpl_id.property_account_expense.id
+                    if not acc_id:
+                        acc_id = ol.product_id.categ_id.property_account_expense_categ.id
+                    if not acc_id:
                         raise osv.except_osv(_('Error !'), _('There is no expense account defined for this product: "%s" (id:%d)') % (ol.product_id.name, ol.product_id.id,))
                 else:
-                    a = self.pool.get('ir.property').get(cr, uid, 'property_account_expense_categ', 'product.category').id
+                    prop = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category')
+                    acc_id = prop and prop.id or False
                 fpos = o.fiscal_position or False
-                a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
-                il.append(self.inv_line_create(cr, uid, a, ol))
+                acc_id = fp_obj.map_account(cr, uid, fpos, acc_id)
+                if not acc_id:
+                    raise osv.except_osv(_('Error !'),
+                        _('There is no expense account defined in default Properties for Product Category or Fiscal Position is not defined !'))
+                il.append(self.inv_line_create(cr, uid, acc_id, ol))
 
-            a = o.partner_id.property_account_payable.id
+            acc_id = o.partner_id.property_account_payable.id
             journal_ids = journal_obj.search(cr, uid, [('type', '=','purchase'),('company_id', '=', o.company_id.id)], limit=1)
             if not journal_ids:
                 raise osv.except_osv(_('Error !'),
                     _('There is no purchase journal defined for this company: "%s" (id:%d)') % (o.company_id.name, o.company_id.id))
             inv = {
                 'name': o.partner_ref or o.name,
-                'reference': "P%dPO%d" % (o.partner_id.id, o.id),
-                'account_id': a,
+                'reference': o.partner_ref or o.name,
+                'account_id': acc_id,
                 'type': 'in_invoice',
                 'partner_id': o.partner_id.id,
                 'currency_id': o.pricelist_id.currency_id.id,
@@ -377,7 +393,7 @@ class purchase_order(osv.osv):
                 'payment_term': o.partner_id.property_payment_term and o.partner_id.property_payment_term.id or False,
                 'company_id': o.company_id.id,
             }
-            inv_id = self.pool.get('account.invoice').create(cr, uid, inv, {'type':'in_invoice'})
+            inv_id = self.pool.get('account.invoice').create(cr, uid, inv, {'type':'in_invoice', 'journal_type': 'purchase'})
             self.pool.get('account.invoice').button_compute(cr, uid, [inv_id], {'type':'in_invoice'}, set_total=True)
             self.pool.get('purchase.order.line').write(cr, uid, todo, {'invoiced':True})
             self.write(cr, uid, [o.id], {'invoice_ids': [(4, inv_id)]})
@@ -418,6 +434,16 @@ class purchase_order(osv.osv):
     def action_picking_create(self,cr, uid, ids, *args):
         picking_id = False
         for order in self.browse(cr, uid, ids):
+            reception_address_id = False
+            if order.dest_address_id:
+                reception_address_id = order.dest_address_id.id
+            elif order.warehouse_id and order.warehouse_id.partner_address_id:
+                reception_address_id = order.warehouse_id.partner_address_id.id
+            else:
+                if order.company_id.partner_id.address:
+                    addresses_default = [address.id for address in order.company_id.partner_id.address if address.type == 'default']
+                    addresses_delivery = [address.id for address in order.company_id.partner_id.address if address.type == 'delivery']
+                    reception_address_id = (addresses_delivery and addresses_delivery[0]) or (addresses_default and addresses_default[0]) or False
             loc_id = order.partner_id.property_stock_supplier.id
             istate = 'none'
             if order.invoice_method=='picking':
@@ -427,7 +453,7 @@ class purchase_order(osv.osv):
                 'name': pick_name,
                 'origin': order.name+((order.origin and (':'+order.origin)) or ''),
                 'type': 'in',
-                'address_id': order.dest_address_id.id or order.partner_address_id.id,
+                'address_id': reception_address_id,
                 'invoice_state': istate,
                 'purchase_id': order.id,
                 'company_id': order.company_id.id,
@@ -440,7 +466,7 @@ class purchase_order(osv.osv):
                 if order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
                     dest = order.location_id.id
                     move = self.pool.get('stock.move').create(cr, uid, {
-                        'name': 'PO:'+order_line.name,
+                        'name': order.name + ': ' +(order_line.name or ''),
                         'product_id': order_line.product_id.id,
                         'product_qty': order_line.product_qty,
                         'product_uos_qty': order_line.product_qty,
@@ -605,7 +631,7 @@ class purchase_order_line(osv.osv):
     _columns = {
         'name': fields.char('Description', size=256, required=True),
         'product_qty': fields.float('Quantity', required=True, digits=(16,2)),
-        'date_planned': fields.date('Scheduled Date', required=True),
+        'date_planned': fields.date('Scheduled Date', required=True, select=True),
         'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
         'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
         'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
@@ -616,7 +642,7 @@ class purchase_order_line(osv.osv):
         'notes': fields.text('Notes'),
         'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
         'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
-        'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company'),
+        'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
         'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'State', required=True, readonly=True,
                                   help=' * The \'Draft\' state is set automatically when purchase order in draft state. \
                                        \n* The \'Confirmed\' state is set automatically as confirm when purchase order in confirm state. \
@@ -640,7 +666,7 @@ class purchase_order_line(osv.osv):
     def copy_data(self, cr, uid, id, default=None, context=None):
         if not default:
             default = {}
-        default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
+        default.update({'state':'draft', 'move_ids':[], 'move_dest_id':False, 'invoiced':0,'invoice_lines':[]})
         return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
 
     def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
@@ -660,9 +686,8 @@ class purchase_order_line(osv.osv):
         lang=False
         if partner_id:
             lang=self.pool.get('res.partner').read(cr, uid, partner_id, ['lang'])['lang']
-        context={'lang':lang}
-        context['partner_id'] = partner_id
-
+        context = self.pool.get('res.users').context_get(cr, uid)
+        context_partner = {'lang':lang, 'partner_id': partner_id}
         prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
         prod_uom_po = prod.uom_po_id.id
         if not uom:
@@ -672,7 +697,7 @@ class purchase_order_line(osv.osv):
         qty = qty or 1.0
         seller_delay = 0
 
-        prod_name = self.pool.get('product.product').name_get(cr, uid, [prod.id], context=context)[0][1]
+        prod_name = self.pool.get('product.product').name_get(cr, uid, [prod.id], context=context_partner)[0][1]
         res = {}
         for s in prod.seller_ids:
             if s.name.id == partner_id:
@@ -690,10 +715,18 @@ class purchase_order_line(osv.osv):
                         'uom': uom,
                         'date': date_order,
                         })[pricelist]
+        if price is False:
+            warning = {
+                'title': 'No valid pricelist line found !',
+                'message':
+                    "Couldn't find a pricelist line matching this product and quantity.\n"
+                    "You have to change either the product, the quantity or the pricelist."
+                }
+            res.update({'warning': warning})
         dt = (datetime.now() + relativedelta(days=int(seller_delay) or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
 
 
-        res.update({'value': {'price_unit': price, 'name': name or prod_name,
+        res.update({'value': {'price_unit': price, 'name': prod_name,
             'taxes_id':map(lambda x: x.id, prod.supplier_taxes_id),
             'date_planned': date_planned or dt,'notes': notes or prod.description_purchase,
             'product_qty': qty,
@@ -714,11 +747,13 @@ class purchase_order_line(osv.osv):
         return res
 
     def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
-            partner_id, date_order=False,fiscal_position=False):
+            partner_id, date_order=False, fiscal_position=False, date_planned=False,
+            name=False, price_unit=False, notes=False):
         res = self.product_id_change(cr, uid, ids, pricelist, product, qty, uom,
-                partner_id, date_order=date_order,fiscal_position=fiscal_position)
+                partner_id, date_order=date_order, fiscal_position=fiscal_position, date_planned=date_planned,
+            name=name, price_unit=price_unit, notes=notes)
         if 'product_uom' in res['value']:
-            if uom and uom != res['value']['product_uom']:
+            if uom and (uom != res['value']['product_uom']) and res['value']['product_uom']:
                 seller_uom_name = self.pool.get('product.uom').read(cr, uid, [res['value']['product_uom']], ['name'])[0]['name']
                 res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier only sells this product by %s') % seller_uom_name }})
             del res['value']['product_uom']
@@ -760,6 +795,7 @@ class procurement_order(osv.osv):
         prod_obj = self.pool.get('product.product')
         acc_pos_obj = self.pool.get('account.fiscal.position')
         po_obj = self.pool.get('purchase.order')
+        po_line_obj = self.pool.get('purchase.order.line')
         for procurement in self.browse(cr, uid, ids, context=context):
             res_id = procurement.move_id.id
             partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
@@ -768,6 +804,7 @@ class procurement_order(osv.osv):
             partner_id = partner.id
             address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
             pricelist_id = partner.property_product_pricelist_purchase.id
+            fiscal_position = partner.property_account_position and partner.property_account_position.id or False
 
             uom_id = procurement.product_id.uom_po_id.id
 
@@ -775,11 +812,15 @@ class procurement_order(osv.osv):
             if seller_qty:
                 qty = max(qty,seller_qty)
 
-            price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
+            price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, partner_id, {'uom': uom_id})[pricelist_id]
 
             newdate = datetime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
             newdate = (newdate - relativedelta(days=company.po_lead)) - relativedelta(days=seller_delay)
 
+            res_onchange = po_line_obj.product_id_change(cr, uid, ids, pricelist_id, procurement.product_id.id, qty, uom_id,
+                partner_id, time.strftime('%Y-%m-%d'), fiscal_position=fiscal_position, date_planned=datetime.now() + relativedelta(days=seller_delay or 0.0),
+            name=procurement.name, price_unit=procurement.product_id.list_price, notes=procurement.product_id.description_purchase)
+
             #Passing partner_id to context for purchase order line integrity of Line name
             context.update({'lang': partner.lang, 'partner_id': partner_id})
 
@@ -787,10 +828,10 @@ class procurement_order(osv.osv):
 
             line = {
                 'name': product.partner_ref,
-                'product_qty': qty,
+                'product_qty': res_onchange['value']['product_qty'],
                 'product_id': procurement.product_id.id,
-                'product_uom': uom_id,
-                'price_unit': price,
+                'product_uom': res_onchange['value']['product_uom'],
+                'price_unit': res_onchange['value']['price_unit'],
                 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
                 'move_dest_id': res_id,
                 'notes': product.description_purchase,
@@ -823,12 +864,14 @@ class stock_invoice_onshipping(osv.osv_memory):
     def create_invoice(self, cr, uid, ids, context=None):
         if context is None:
             context = {}
+        res = super(stock_invoice_onshipping,self).create_invoice(cr, uid, ids, context=context)
         purchase_obj = self.pool.get('purchase.order')
         picking_obj = self.pool.get('stock.picking')
-        res = super(stock_invoice_onshipping,self).create_invoice(cr, uid, ids, context=context)
-        purchase_id =  picking_obj.browse(cr, uid, res.keys()[0]).purchase_id.id
-        purchase_obj.write(cr, uid, [purchase_id], {
-            'invoice_ids': [(4, res.values()[0])]}, context=context)
+        for pick_id in res:
+            pick = picking_obj.browse(cr, uid, pick_id, context=context)
+            if pick.purchase_id:
+                purchase_obj.write(cr, uid, [pick.purchase_id.id], {
+                    'invoice_ids': [(4, res[pick_id])]}, context=context)
         return res
 
 stock_invoice_onshipping()