Bugfix, BUG #714
[odoo/odoo.git] / addons / purchase / purchase.py
index 822508b..b91a2e8 100644 (file)
@@ -33,6 +33,7 @@ import netsvc
 import ir
 from mx import DateTime
 import pooler
+from tools import config
 
 #
 # Model definition
@@ -47,56 +48,65 @@ 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)::decimal(16,2) 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 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):
                res = {}
+               cur_obj=self.pool.get('res.currency')
                for order in self.browse(cr, uid, ids):
                        val = 0.0
+                       cur=order.pricelist_id.currency_id
                        for line in order.order_line:
-                               for tax in line.taxes_id:
-                                       for c in self.pool.get('account.tax').compute(cr, uid, [tax.id], line.price_unit, line.product_qty, order.partner_address_id.id):
-                                               val+=c['amount']
-                       res[order.id]=round(val,2)
+                               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
 
        def _amount_total(self, cr, uid, ids, field_name, arg, context):
                res = {}
                untax = self._amount_untaxed(cr, uid, ids, field_name, arg, context) 
                tax = self._amount_tax(cr, uid, ids, field_name, arg, context)
+               cur_obj=self.pool.get('res.currency')
                for id in ids:
-                       res[id] = untax.get(id, 0.0) + tax.get(id, 0.0)
+                       order=self.browse(cr, uid, [id])[0]
+                       cur=order.pricelist_id.currency_id
+                       res[id] = cur_obj.round(cr, uid, cur, untax.get(id, 0.0) + tax.get(id, 0.0))
                return res
 
        _columns = {
                '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'),
                'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True),
-               '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 invoice"),
+               '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'),
@@ -108,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
@@ -120,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):
@@ -133,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):
@@ -167,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
@@ -175,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.id
                                        if not a:
-                                               a = ol.product_id.categ_id.property_account_expense_categ[0]
-                                       else:
-                                               a = a[0]
+                                               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
@@ -222,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'
@@ -231,7 +257,7 @@ class purchase_order(osv.osv):
                                'type': 'in',
                                'address_id': order.dest_address_id.id or order.partner_address_id.id,
                                'invoice_state': istate,
-                               'purchase_id': order.id
+                               'purchase_id': order.id,
                        })
                        for order_line in order.order_line:
                                if not order_line.product_id:
@@ -250,7 +276,8 @@ class purchase_order(osv.osv):
                                                'location_dest_id': dest,
                                                'picking_id': picking_id,
                                                'move_dest_id': order_line.move_dest_id.id,
-                                               'state': 'assigned'
+                                               'state': 'assigned',
+                                               'purchase_line_id': order_line.id,
                                        })
                                        if order_line.move_dest_id:
                                                self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
@@ -275,23 +302,26 @@ purchase_order()
 class purchase_order_line(osv.osv):
        def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
                res = {}
+               cur_obj=self.pool.get('res.currency')
                for line in self.browse(cr, uid, ids):
-                       res[line.id] = line.price_unit * line.product_qty
+                       cur = line.order_id.pricelist_id.currency_id
+                       res[line.id] = cur_obj.round(cr, uid, cur, line.price_unit * line.product_qty)
                return res
        
        _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),
+               '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
@@ -305,23 +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':[]}}
-               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]
+               lang=False
+               if partner_id:
+                       lang=self.pool.get('res.partner').read(cr, uid, [partner_id])[0]['lang']
+               context={'lang':lang}
+
+               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])[0][1]
-               res = {'value': {'price_unit': price, 'name':prod_name, 'taxes_id':prod['taxes_id'], 'date_planned': dt,'notes':prod['description_purchase']}}
+               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['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()