Bugfix, BUG #714
[odoo/odoo.git] / addons / purchase / purchase.py
index 4d46e25..b91a2e8 100644 (file)
@@ -48,14 +48,15 @@ class purchase_order(osv.osv):
                return res
 
        def _amount_untaxed(self, cr, uid, ids, field_name, arg, context):
-               id_set = ",".join(map(str, ids))
-               cr.execute("SELECT s.id,COALESCE(SUM(l.price_unit*l.product_qty),0) AS amount FROM purchase_order s LEFT OUTER JOIN purchase_order_line l ON (s.id=l.order_id) WHERE s.id IN ("+id_set+") GROUP BY s.id ")
-               res = dict(cr.fetchall())
+               res = {}
                cur_obj=self.pool.get('res.currency')
-               for id in res.keys():
-                       order=self.browse(cr, uid, [id])[0]
-                       cur=order.pricelist_id.currency_id
-                       res[id]=cur_obj.round(cr, uid, cur, res[id])
+               for purchase in self.browse(cr, uid, ids):
+                       res[purchase.id] = 0.0
+                       for line in purchase.order_line:
+                               res[purchase.id] += line.price_subtotal
+                       cur = purchase.pricelist_id.currency_id
+                       res[purchase.id] = cur_obj.round(cr, uid, cur, res[purchase.id])
+
                return res
 
        def _amount_tax(self, cr, uid, ids, field_name, arg, context):
@@ -65,7 +66,7 @@ class purchase_order(osv.osv):
                        val = 0.0
                        cur=order.pricelist_id.currency_id
                        for line in order.order_line:
-                               for c in self.pool.get('account.tax').compute(cr, uid, line.taxes_id, line.price_unit, line.product_qty, order.partner_address_id.id, line.product_id):
+                               for c in self.pool.get('account.tax').compute(cr, uid, line.taxes_id, line.price_unit, line.product_qty, order.partner_address_id.id, line.product_id, order.partner_id):
                                        val+= cur_obj.round(cr, uid, cur, c['amount'])
                        res[order.id]=cur_obj.round(cr, uid, cur, val)
                return res
@@ -85,20 +86,19 @@ class purchase_order(osv.osv):
                'name': fields.char('Order Description', size=64, required=True, select=True),
                'origin': fields.char('Origin', size=64),
                'ref': fields.char('Order Reference', size=64),
-               'partner_ref': fields.char('Partner Reference', size=64),
+               'partner_ref': fields.char('Partner Ref.', size=64),
                'date_order':fields.date('Date Ordered', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}),
                'date_approve':fields.date('Date Approved'),
-               'partner_id':fields.many2one('res.partner', 'Partner', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, change_default=True, relate=True),
+               'partner_id':fields.many2one('res.partner', 'Partner', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, change_default=True),
                'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True, states={'posted':[('readonly',True)]}),
 
                'dest_address_id':fields.many2one('res.partner.address', 'Destination Address', states={'posted':[('readonly',True)]}),
-               'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', states={'posted':[('readonly',True)]}, relate=True),
+               'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', states={'posted':[('readonly',True)]}),
                'location_id': fields.many2one('stock.location', 'Delivery destination', required=True),
-               'project_id':fields.many2one('account.analytic.account', 'Analytic Account', states={'posted':[('readonly',True)]}),
 
                'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}),
 
-               'state': fields.selection([('draft', 'Request for Quotation'), ('wait', 'Waiting'), ('confirmed', 'Confirmed'), ('approved', 'Approved'),('except_ship', 'Shipping Exception'), ('except_invoice', 'Invoice Exception'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Order State', readonly=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Done'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
+               'state': fields.selection([('draft', 'Request for Quotation'), ('wait', 'Waiting'), ('confirmed', 'Confirmed'), ('approved', 'Approved'),('except_picking', 'Shipping Exception'), ('except_invoice', 'Invoice Exception'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Order State', readonly=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Done'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
                'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order State', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}),
                'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
                'notes': fields.text('Notes'),
@@ -106,7 +106,7 @@ class purchase_order(osv.osv):
                'picking_ids': fields.one2many('stock.picking', 'purchase_id', 'Picking List', readonly=True, help="This is the list of picking list that have been generated for this purchase"),
                'shipped':fields.boolean('Received', readonly=True, select=True),
                'invoiced':fields.boolean('Invoiced & Paid', readonly=True, select=True),
-               'invoice_method': fields.selection([('manual','Manual'),('order','From order'),('picking','From picking')], 'Invoicing method', required=True),
+               'invoice_method': fields.selection([('manual','Manual'),('order','From order'),('picking','From picking')], 'Invoicing Control', required=True),
 
                'amount_untaxed': fields.function(_amount_untaxed, method=True, string='Untaxed Amount'),
                'amount_tax': fields.function(_amount_tax, method=True, string='Taxes'),
@@ -118,10 +118,13 @@ class purchase_order(osv.osv):
                'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
                'shipped': lambda *a: 0,
                'invoice_method': lambda *a: 'order',
-               'invoiced': lambda *a: 0
+               'invoiced': lambda *a: 0,
+               'partner_address_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['default'])['default'],
+               'pricelist_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').browse(cr, uid, context['partner_id']).property_product_pricelist_purchase.id,
        }
        _name = "purchase.order"
        _description = "Purchase order"
+       _order = "name desc"
 
        def button_dummy(self, cr, uid, ids, context={}):
                return True
@@ -130,7 +133,7 @@ class purchase_order(osv.osv):
                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[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}}
 
        def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
@@ -143,7 +146,7 @@ class purchase_order(osv.osv):
                if not part:
                        return {'value':{'partner_address_id': False}}
                addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['default'])
-               pricelist = self.pool.get('res.partner').browse(cr, uid, part).property_product_pricelist_purchase[0]
+               pricelist = self.pool.get('res.partner').browse(cr, uid, part).property_product_pricelist_purchase.id
                return {'value':{'partner_address_id': addr['default'], 'pricelist_id': pricelist}}
 
        def wkf_approve_order(self, cr, uid, ids):
@@ -177,6 +180,17 @@ class purchase_order(osv.osv):
                                           'ref_partner_id': po.partner_id.id,
                                           'ref_doc1': 'purchase.order,%d' % (po.id,),
                                           })
+       def inv_line_create(self,a,ol):
+               return (0, False, {
+                                       'name': ol.name,
+                                       'account_id': a,
+                                       'price_unit': ol.price_unit or 0.0,
+                                       'quantity': ol.product_qty,
+                                       'product_id': ol.product_id.id or False,
+                                       'uos_id': ol.product_uom.id or False,
+                                       'invoice_line_tax_id': [(6, 0, [x.id for x in ol.taxes_id])],
+                                       'account_analytic_id': ol.account_analytic_id.id,
+                               })
 
        def action_invoice_create(self, cr, uid, ids, *args):
                res = False
@@ -185,38 +199,40 @@ class purchase_order(osv.osv):
                        for ol in o.order_line:
 
                                if ol.product_id:
-                                       a = ol.product_id.product_tmpl_id.property_account_expense
+                                       a = ol.product_id.product_tmpl_id.property_account_expense.id
                                        if not a:
-                                               a = ol.product_id.categ_id.property_account_expense_categ[0]
-                                       else:
-                                               a = a[0]
+                                               a = ol.product_id.categ_id.property_account_expense_categ.id
+                                       if not a:
+                                               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')
-                               il.append((0, False, {
-                                       'name': ol.name,
-                                       'account_id': a,
-                                       'price_unit': ol.price_unit or 0.0,
-                                       'quantity': ol.product_qty,
-                                       'product_id': ol.product_id.id or False,
-                                       'uos_id': ol.product_uom.id or False,
-                                       'invoice_line_tax_id': [(6, 0, [x.id for x in ol.taxes_id])]
-                               }))
+                               il.append(self.inv_line_create(a,ol))
+#                              il.append((0, False, {
+#                                      'name': ol.name,
+#                                      'account_id': a,
+#                                      'price_unit': ol.price_unit or 0.0,
+#                                      'quantity': ol.product_qty,
+#                                      'product_id': ol.product_id.id or False,
+#                                      'uos_id': ol.product_uom.id or False,
+#                                      'invoice_line_tax_id': [(6, 0, [x.id for x in ol.taxes_id])],
+#                                      'account_analytic_id': ol.account_analytic_id.id,
+#                              }))
 
-                       a = o.partner_id.property_account_payable[0]
+                       a = o.partner_id.property_account_payable.id
                        inv = {
-                               'name': o.name,
+                               'name': o.partner_ref or o.name,
                                'reference': "P%dPO%d" % (o.partner_id.id, o.id),
                                'account_id': a,
                                'type': 'in_invoice',
                                'partner_id': o.partner_id.id,
                                'currency_id': o.pricelist_id.currency_id.id,
-                               'project_id': o.project_id.id,
                                'address_invoice_id': o.partner_address_id.id,
                                'address_contact_id': o.partner_address_id.id,
                                'origin': o.name,
                                'invoice_line': il,
                        }
-                       inv_id = self.pool.get('account.invoice').create(cr, uid, inv)
+                       inv_id = self.pool.get('account.invoice').create(cr, uid, inv, {'type':'in_invoice'})
+                       self.pool.get('account.invoice').button_compute(cr, uid, [inv_id], {'type':'in_invoice'}, set_total=True)
 
                        self.write(cr, uid, [o.id], {'invoice_id': inv_id})
                        res = inv_id
@@ -232,7 +248,7 @@ 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):
-                       loc_id = order.partner_id.property_stock_supplier[0]
+                       loc_id = order.partner_id.property_stock_supplier.id
                        istate = 'none'
                        if order.invoice_method=='picking':
                                istate = '2binvoiced'
@@ -295,16 +311,17 @@ class purchase_order_line(osv.osv):
        _columns = {
                'name': fields.char('Description', size=64, required=True),
                'product_qty': fields.float('Quantity', required=True, digits=(16,2)),
-               'date_planned': fields.date('Date Promised', required=True),
+               'date_planned': fields.date('Scheduled date', required=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, relate=True),
+               'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
                'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
                'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
                'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
                'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal'),
                'notes': fields.text('Notes'),
-               'order_id': fields.many2one('purchase.order', 'Order Ref', select=True)
+               'order_id': fields.many2one('purchase.order', 'Order Ref', select=True, required=True, ondelete='cascade'),
+               'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
        }
        _defaults = {
                'product_qty': lambda *a: 1.0
@@ -318,27 +335,50 @@ class purchase_order_line(osv.osv):
                default.update({'state':'draft', 'move_id':False})
                return super(purchase_order_line, self).copy(cr, uid, id, default, context)
 
-       def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom, partner_id):
+       def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
+                       partner_id, date_order=False):
                if not pricelist:
-                       raise osv.except_osv('No Pricelist !', 'You have to select a pricelist in the sale form !\n Please set one before choosing a product.')
+                       raise osv.except_osv('No Pricelist !', 'You have to select a pricelist in the purchase form !\n Please set one before choosing a product.')
                if not product:
                        return {'value': {'price_unit': 0.0, 'name':'','notes':''}, 'domain':{'product_uom':[]}}
                lang=False
                if partner_id:
                        lang=self.pool.get('res.partner').read(cr, uid, [partner_id])[0]['lang']
                context={'lang':lang}
-               price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist], product, qty or 1.0, partner_id, {'uom': uom})[pricelist]
-               prod = self.pool.get('product.product').read(cr, uid, [product], ['taxes_id','name','seller_delay','uom_po_id','description_purchase'])[0]
+
+               prod = self.pool.get('product.product').read(cr, uid, [product], ['supplier_taxes_id','name','seller_delay','uom_po_id','description_purchase'])[0]
+               prod_uom_po = prod['uom_po_id'][0]
+               if not uom:
+                       uom = prod_uom_po
+               if not date_order:
+                       date_order = time.strftime('%Y-%m-%d')
+               price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist],
+                               product, qty or 1.0, partner_id, {
+                                       'uom': uom,
+                                       'date': date_order,
+                                       })[pricelist]
                dt = (DateTime.now() + DateTime.RelativeDateTime(days=prod['seller_delay'] or 0.0)).strftime('%Y-%m-%d')
                prod_name = self.pool.get('product.product').name_get(cr, uid, [product], context=context)[0][1]
-               res = {'value': {'price_unit': price, 'name':prod_name, 'taxes_id':prod['taxes_id'], 'date_planned': dt,'notes':prod['description_purchase']}}
+
+               res = {'value': {'price_unit': price, 'name':prod_name, 'taxes_id':prod['supplier_taxes_id'], 'date_planned': dt,'notes':prod['description_purchase'], 'product_uom': uom}}
                domain = {}
-               if not uom:
-                       res['value']['product_uom'] = prod['uom_po_id'][0]
-                       if res['value']['product_uom']:
-                               res2 = self.pool.get('product.uom').read(cr, uid, [res['value']['product_uom']], ['category_id'])
-                               if res2 and res2[0]['category_id']:
-                                       domain = {'product_uom':[('category_id','=',res2[0]['category_id'][0])]}
+
+               res2 = self.pool.get('product.uom').read(cr, uid, [uom], ['category_id'])
+               res3 = self.pool.get('product.uom').read(cr, uid, [prod_uom_po], ['category_id'])
+               domain = {'product_uom':[('category_id','=',res2[0]['category_id'][0])]}
+               if res2[0]['category_id'] != res3[0]['category_id']:
+                       raise osv.except_osv('Wrong Product UOM !', 'You have to select a product UOM in the same category than the purchase UOM of the product')
+
                res['domain'] = domain
                return res
+
+       def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
+                       partner_id, date_order=False):
+               res = self.product_id_change(cr, uid, ids, pricelist, product, qty, uom,
+                               partner_id, date_order=date_order)
+               if 'product_uom' in res['value']:
+                       del res['value']['product_uom']
+               if not uom:
+                       res['value']['price_unit'] = 0.0
+               return res
 purchase_order_line()