[MERGE]
[odoo/odoo.git] / addons / sale / sale.py
index 49a96fe..e36d1f1 100644 (file)
@@ -2,7 +2,7 @@
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
+#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
 #    $Id$
 #
 #    This program is free software: you can redistribute it and/or modify
@@ -32,9 +32,9 @@ class sale_shop(osv.osv):
     _name = "sale.shop"
     _description = "Sale Shop"
     _columns = {
-        'name': fields.char('Shop name',size=64, required=True),
+        'name': fields.char('Shop Name',size=64, required=True),
         'payment_default_id': fields.many2one('account.payment.term','Default Payment Term',required=True),
-        'payment_account_id': fields.many2many('account.account','sale_shop_account','shop_id','account_id','Payment accounts'),
+        'payment_account_id': fields.many2many('account.account','sale_shop_account','shop_id','account_id','Payment Accounts'),
         'warehouse_id': fields.many2one('stock.warehouse','Warehouse'),
         'pricelist_id': fields.many2one('product.pricelist', 'Pricelist'),
         'project_id': fields.many2one('account.analytic.account', 'Analytic Account'),
@@ -60,38 +60,29 @@ class sale_order(osv.osv):
         })
         return super(sale_order, self).copy(cr, uid, id, default, context)
 
-    def _amount_untaxed(self, cr, uid, ids, field_name, arg, context):
-        res = {}
-        cur_obj=self.pool.get('res.currency')
-        for sale in self.browse(cr, uid, ids):
-            res[sale.id] = 0.0
-            for line in sale.order_line:
-                res[sale.id] += line.price_subtotal
-            cur = sale.pricelist_id.currency_id
-            res[sale.id] = cur_obj.round(cr, uid, cur, res[sale.id])
-        return res
+    def _amount_line_tax(self, cr, uid, line, context={}):
+        val = 0.0
+        for c in self.pool.get('account.tax').compute(cr, uid, line.tax_id, line.price_unit * (1-(line.discount or 0.0)/100.0), line.product_uom_qty, line.order_id.partner_invoice_id.id, line.product_id, line.order_id.partner_id):
+            val+= c['amount']
+        return val
 
-    def _amount_tax(self, cr, uid, ids, field_name, arg, context):
+    def _amount_all(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
+            res[order.id] = {
+                'amount_untaxed': 0.0,
+                'amount_tax': 0.0,
+                'amount_total': 0.0,
+            }
+            val = val1 = 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.tax_id, line.price_unit * (1-(line.discount or 0.0)/100.0), line.product_uom_qty, order.partner_invoice_id.id, line.product_id, order.partner_id):
-                    val+= 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:
-            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))
+                val1 += line.price_subtotal
+                val += self._amount_line_tax(cr, uid, line, context)
+            res[order.id]['amount_tax']=cur_obj.round(cr, uid, cur, val)
+            res[order.id]['amount_untaxed']=cur_obj.round(cr, uid, cur, val1)
+            res[order.id]['amount_total']=res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
         return res
 
     def _picked_rate(self, cr, uid, ids, name, arg, context=None):
@@ -136,8 +127,9 @@ class sale_order(osv.osv):
             for invoice in sale.invoice_ids:
                 if invoice.state not in ('draft','cancel'):
                     tot += invoice.amount_untaxed
+
             if tot:
-                res[sale.id] = tot * 100.0 / sale.amount_untaxed
+                res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
             else:
                 res[sale.id] = 0.0
         return res
@@ -181,22 +173,28 @@ class sale_order(osv.osv):
             return [('id', '=', 0)]
         return [('id', 'in', [x[0] for x in res])]
 
+    def _get_order(self, cr, uid, ids, context={}):
+        result = {}
+        for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):
+            result[line.order_id.id] = True
+        return result.keys()
+
     _columns = {
         'name': fields.char('Order Reference', size=64, required=True, select=True),
         'shop_id':fields.many2one('sale.shop', 'Shop', required=True, readonly=True, states={'draft':[('readonly',False)]}),
         'origin': fields.char('Origin', size=64),
-        'client_order_ref': fields.char('Customer Ref.',size=64),
+        'client_order_ref': fields.char('Customer Ref',size=64),
 
         'state': fields.selection([
             ('draft','Quotation'),
             ('waiting_date','Waiting Schedule'),
-            ('manual','Manual in progress'),
-            ('progress','In progress'),
+            ('manual','Manual In Progress'),
+            ('progress','In Progress'),
             ('shipping_except','Shipping Exception'),
             ('invoice_except','Invoice Exception'),
             ('done','Done'),
-            ('cancel','Cancel')
-            ], 'Order State', readonly=True, help="Gives the state of the quotation or sale order. The exception state is automatically set when a cancel operation occurs in the invoice validation (Invoice Exception) or in the packing list process (Shipping Exception). The 'Waiting Schedule' state is set when the invoice is confirmed but waiting for the scheduler to be on the date 'Date Ordered'.", select=True),
+            ('cancel','Cancelled')
+            ], 'Order State', readonly=True, help="Gives the state of the quotation or sale order. The exception state is automatically set when a cancel operation occurs in the invoice validation (Invoice Exception) or in the packing list process (Shipping Exception). The 'Waiting Schedule' state is set when the invoice is confirmed but waiting for the scheduler to run on the date 'Date Ordered'.", select=True),
         'date_order':fields.date('Date Ordered', required=True, readonly=True, states={'draft':[('readonly',False)]}),
 
         'user_id':fields.many2one('res.users', 'Salesman', states={'draft':[('readonly',False)]}, select=True),
@@ -207,35 +205,53 @@ class sale_order(osv.osv):
 
         'incoterm': fields.selection(_incoterm_get, 'Incoterm',size=3),
         'picking_policy': fields.selection([('direct','Partial Delivery'),('one','Complete Delivery')],
-            'Packing Policy', required=True, help="""If you don't have enough stock available to deliver all at once, do you accept partial shippings or not."""),
+            'Packing Policy', required=True, help="""If you don't have enough stock available to deliver all at once, do you accept partial shipments or not?"""),
         'order_policy': fields.selection([
-            ('prepaid','Payment before delivery'),
+            ('prepaid','Payment Before Delivery'),
             ('manual','Shipping & Manual Invoice'),
-            ('postpaid','Automatic Invoice after delivery'),
-            ('picking','Invoice from the packings'),
+            ('postpaid','Invoice on Order After Delivery'),
+            ('picking','Invoice from the Packing'),
         ], 'Shipping Policy', required=True, readonly=True, states={'draft':[('readonly',False)]},
                     help="""The Shipping Policy is used to synchronise invoice and delivery operations.
   - The 'Pay before delivery' choice will first generate the invoice and then generate the packing order after the payment of this invoice.
   - The 'Shipping & Manual Invoice' will create the packing order directly and wait for the user to manually click on the 'Invoice' button to generate the draft invoice.
-  - The 'Invoice after delivery' choice will generate the draft invoice after the packing list have been finished.
-  - The 'Invoice from the packings' choice is used to create an invoice during the packing process."""),
+  - The 'Invoice on Order Ater Delivery' choice will generate the draft invoice based on sale order after all packing lists have been finished.
+  - The 'Invoice from the packing' choice is used to create an invoice during the packing process."""),
         'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft':[('readonly',False)]}),
         'project_id':fields.many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft':[('readonly', False)]}),
 
         'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft':[('readonly',False)]}),
         'invoice_ids': fields.many2many('account.invoice', 'sale_order_invoice_rel', 'order_id', 'invoice_id', 'Invoice', help="This is the list of invoices that have been generated for this sale order. The same sale order may have been invoiced in several times (by line for example)."),
-        'picking_ids': fields.one2many('stock.picking', 'sale_id', 'Related Packings', readonly=True, help="This is the list of picking list that have been generated for this invoice"),
+        'picking_ids': fields.one2many('stock.picking', 'sale_id', 'Related Packing', readonly=True, help="This is the list of picking list that have been generated for this invoice"),
         'shipped':fields.boolean('Picked', readonly=True),
         'picked_rate': fields.function(_picked_rate, method=True, string='Picked', type='float'),
         'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
         'invoiced': fields.function(_invoiced, method=True, string='Paid',
             fnct_search=_invoiced_search, type='boolean'),
         'note': fields.text('Notes'),
-        'amount_untaxed': fields.function(_amount_untaxed, method=True, string='Untaxed Amount'),
-        'amount_tax': fields.function(_amount_tax, method=True, string='Taxes'),
-        'amount_total': fields.function(_amount_total, method=True, string='Total'),
+
+        'amount_untaxed': fields.function(_amount_all, method=True, string='Untaxed Amount',
+            store={
+                'sale.order': (lambda self, cr, uid, ids, c={}: ids, None, 10),
+                'sale.order.line': (_get_order, None, 10),
+            },
+            multi='sums'),
+        'amount_tax': fields.function(_amount_all, method=True, string='Taxes',
+            store={
+                'sale.order': (lambda self, cr, uid, ids, c={}: ids, None, 10),
+                'sale.order.line': (_get_order, None, 10),
+            },
+            multi='sums'),
+        'amount_total': fields.function(_amount_all, method=True, string='Total',
+            store={
+                'sale.order': (lambda self, cr, uid, ids, c={}: ids, None, 10),
+                'sale.order.line': (_get_order, None, 10),
+            },
+            multi='sums'),
+
         'invoice_quantity': fields.selection([('order','Ordered Quantities'),('procurement','Shipped Quantities')], 'Invoice on', help="The sale order will automatically create the invoice proposition (draft invoice). Ordered and delivered quantities may not be the same. You have to choose if you invoice based on ordered or shipped quantities. If the product is a service, shipped quantities means hours spent on the associated tasks.",required=True),
         'payment_term' : fields.many2one('account.payment.term', 'Payment Term'),
+        'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
     }
     _defaults = {
         'picking_policy': lambda *a: 'direct',
@@ -253,7 +269,7 @@ class sale_order(osv.osv):
     _order = 'name desc'
 
     # Form filling
-    def unlink(self, cr, uid, ids):
+    def unlink(self, cr, uid, ids, context=None):
         sale_orders = self.read(cr, uid, ids, ['state'])
         unlink_ids = []
         for s in sale_orders:
@@ -261,7 +277,7 @@ class sale_order(osv.osv):
                 unlink_ids.append(s['id'])
             else:
                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sale Order(s) which are already confirmed !'))
-        return osv.osv.unlink(self, cr, uid, unlink_ids)
+        return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
 
 
     def onchange_shop_id(self, cr, uid, ids, shop_id):
@@ -289,12 +305,16 @@ class sale_order(osv.osv):
 
     def onchange_partner_id(self, cr, uid, ids, part):
         if not part:
-            return {'value':{'partner_invoice_id': False, 'partner_shipping_id':False, 'partner_order_id':False, 'payment_term' : False}}
+            return {'value':{'partner_invoice_id': False, 'partner_shipping_id':False, 'partner_order_id':False, 'payment_term' : False, 'fiscal_position': False}}
         addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['delivery','invoice','contact'])
         part = self.pool.get('res.partner').browse(cr, uid, part)
         pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
         payment_term = part.property_payment_term and part.property_payment_term.id or False
-        return {'value':{'partner_invoice_id': addr['invoice'], 'partner_order_id':addr['contact'], 'partner_shipping_id':addr['delivery'], 'pricelist_id': pricelist, 'payment_term' : payment_term}}
+        fiscal_position = part.property_account_position and part.property_account_position.id or False
+        val = {'partner_invoice_id': addr['invoice'], 'partner_order_id':addr['contact'], 'partner_shipping_id':addr['delivery'], 'payment_term' : payment_term, 'fiscal_position': fiscal_position}
+        if pricelist:
+            val['pricelist_id'] = pricelist
+        return {'value':val}
 
     def shipping_policy_change(self, cr, uid, ids, policy, context={}):
         if not policy:
@@ -339,7 +359,7 @@ class sale_order(osv.osv):
         else:
             pay_term = False
         for preinv in order.invoice_ids:
-            if preinv.state in ('open','paid','proforma'):
+            if preinv.state not in ('cancel',):
                 for preline in preinv.invoice_line:
                     inv_line_id = self.pool.get('account.invoice.line').copy(cr, uid, preline.id, {'invoice_id':False, 'price_unit':-preline.price_unit})
                     lines.append(inv_line_id)
@@ -356,6 +376,7 @@ class sale_order(osv.osv):
             'currency_id' : order.pricelist_id.currency_id.id,
             'comment': order.note,
             'payment_term': pay_term,
+            'fiscal_position': order.partner_id.property_account_position.id
         }
         inv_obj = self.pool.get('account.invoice')
         inv.update(self._inv_get(cr, uid, order))
@@ -363,12 +384,12 @@ class sale_order(osv.osv):
         data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id],
             pay_term,time.strftime('%Y-%m-%d'))
         if data.get('value',False):
-            inv_obj.write(cr, uid, [inv_id], inv.update(data['value']), context=context)
+            inv_obj.write(cr, uid, [inv_id], data['value'], context=context)
         inv_obj.button_compute(cr, uid, [inv_id])
         return inv_id
 
 
-    def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed','done']):
+    def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed','done','exception']):
         res = False
         invoices = {}
         invoice_ids = []
@@ -387,7 +408,7 @@ class sale_order(osv.osv):
                 for i in o.invoice_ids:
                     if i.state == 'draft':
                         return i.id
-               picking_obj=self.pool.get('stock.picking')
+        picking_obj=self.pool.get('stock.picking')
         for val in invoices.values():
             if grouped:
                 res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x,y: x + y, [l for o,l in val], []))
@@ -395,7 +416,7 @@ class sale_order(osv.osv):
                     self.write(cr, uid, [o.id], {'state' : 'progress'})
                     if o.order_policy=='picking':
                         picking_obj.write(cr,uid,map(lambda x:x.id,o.picking_ids),{'invoice_state':'invoiced'})
-                    cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%d,%d)', (o.id, res))
+                    cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
             else:
                 for order, il in val:
                     res = self._make_invoice(cr, uid, order, il)
@@ -403,7 +424,7 @@ class sale_order(osv.osv):
                     self.write(cr, uid, [order.id], {'state' : 'progress'})
                     if order.order_policy=='picking':
                         picking_obj.write(cr,uid,map(lambda x:x.id,order.picking_ids),{'invoice_state':'invoiced'})
-                    cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%d,%d)', (order.id, res))
+                    cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
         return res
 
     def action_invoice_cancel(self, cr, uid, ids, context={}):
@@ -428,7 +449,7 @@ class sale_order(osv.osv):
                 if pick.state not in ('draft','cancel'):
                     raise osv.except_osv(
                         _('Could not cancel sale order !'),
-                        _('You must first cancel all packings attached to this sale order.'))
+                        _('You must first cancel all packing attached to this sale order.'))
             for r in self.read(cr,uid,ids,['picking_ids']):
                 for pick in r['picking_ids']:
                     wf_service = netsvc.LocalService("workflow")
@@ -485,26 +506,24 @@ class sale_order(osv.osv):
         write_cancel_ids = []
         for order in self.browse(cr, uid, ids, context={}):
             for line in order.order_line:
-                if (not line.procurement_id) or (line.procurement_id.state=='done') or (line.procurement_id.move_id and line.procurement_id.move_id.state=='done'):
+                if (not line.procurement_id) or (line.procurement_id.state=='done'):
                     finished = True
+                    if line.state != 'done':
+                        write_done_ids.append(line.id)
                 else:
                     finished = False
                 if line.procurement_id:
-                    if (line.procurement_id.state == 'cancel') or (line.procurement_id.move_id and line.procurement_id.move_id.state=='cancel'):
+                    if (line.procurement_id.state == 'cancel'):
                         canceled = True
-                        if line.state != 'cancel':
+                        if line.state != 'exception':
                             write_cancel_ids.append(line.id)
                     else:
                         notcanceled = True
-                # if a line is finished (ie its procuremnt is done or it has not procuremernt and it
-                # is not already marked as done, mark it as being so...
-                if ((not line.procurement_id) or line.procurement_id.state == 'done') and line.state != 'done':
-                    write_done_ids.append(line.id)
 
         if write_done_ids:
             self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
         if write_cancel_ids:
-            self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'cancel'})
+            self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
 
         if mode=='finished':
             return finished
@@ -569,7 +588,8 @@ class sale_order(osv.osv):
                         'product_id': line.product_id.id,
                         'product_qty': line.product_uom_qty,
                         'product_uom': line.product_uom.id,
-                        'product_uos_qty': line.product_uos_qty,
+                        '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': order.shop_id.warehouse_id.lot_stock_id.id,
@@ -608,8 +628,12 @@ class sale_order(osv.osv):
 
             if order.state=='shipping_except':
                 val['state'] = 'progress'
-                if (order.order_policy == 'manual') and order.invoice_ids:
-                    val['state'] = 'manual'
+
+                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
             self.write(cr, uid, [order.id], val)
 
         return True
@@ -618,10 +642,18 @@ class sale_order(osv.osv):
         for order in self.browse(cr, uid, ids):
             val = {'shipped':True}
             if order.state=='shipping_except':
-                if (order.order_policy=='manual') and not order.invoice_ids:
-                    val['state'] = 'manual'
-                else:
-                    val['state'] = 'progress'
+                val['state'] = 'progress'
+                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
+            for line in order.order_line:
+                towrite = []
+                if line.state=='exception':
+                    towrite.append(line.id)
+                if towrite:
+                    self.pool.get('sale.order.line').write(cr, uid, towrite, {'state':'done'},context=context)
             self.write(cr, uid, [order.id], val)
         return True
 
@@ -646,11 +678,6 @@ sale_order()
 # - update it on change product and unit price
 # - use it in report if there is a uos
 class sale_order_line(osv.osv):
-    def copy(self, cr, uid, id, default=None, context={}):
-        if not default: default = {}
-        default.update( {'invoice_lines':[]})
-        return super(sale_order_line, self).copy(cr, uid, id, default, context)
-
     def _amount_line_net(self, cr, uid, ids, field_name, arg, context):
         res = {}
         for line in self.browse(cr, uid, ids):
@@ -695,15 +722,15 @@ class sale_order_line(osv.osv):
         'address_allotment_id' : fields.many2one('res.partner.address', 'Allotment Partner'),
         'product_uom_qty': fields.float('Quantity (UoM)', digits=(16,2), required=True),
         'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
-        'product_uos_qty': fields.float('Quantity (UOS)'),
-        'product_uos': fields.many2one('product.uom', 'Product UOS'),
+        'product_uos_qty': fields.float('Quantity (UoS)'),
+        'product_uos': fields.many2one('product.uom', 'Product UoS'),
         'product_packaging': fields.many2one('product.packaging', 'Packaging'),
         'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
         'discount': fields.float('Discount (%)', digits=(16,2)),
-        'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number packages'),
+        'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
         'notes': fields.text('Notes'),
         'th_weight' : fields.float('Weight'),
-        'state': fields.selection([('draft','Draft'),('confirmed','Confirmed'),('done','Done'),('cancel','Canceled')], 'Status', required=True, readonly=True),
+        'state': fields.selection([('draft','Draft'),('confirmed','Confirmed'),('done','Done'),('cancel','Canceled'),('exception','Exception')], 'Status', required=True, readonly=True),
         'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', string='Customer')
     }
     _order = 'sequence, id'
@@ -738,6 +765,7 @@ class sale_order_line(osv.osv):
                         line.procurement_id.id, context)
 
         create_ids = []
+        sales = {}
         for line in self.browse(cr, uid, ids, context):
             if not line.invoiced:
                 if line.product_id:
@@ -759,7 +787,8 @@ class sale_order_line(osv.osv):
                 if uosqty:
                     pu = round(line.price_unit * line.product_uom_qty / uosqty,
                             int(config['price_accuracy']))
-                a = self.pool.get('account.fiscal.position').map_account(cr, uid, line.order_id.partner_id, a)
+                fpos = line.order_id.fiscal_position or False
+                a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
                 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
                     'name': line.name,
                     'origin':line.order_id.name,
@@ -773,11 +802,24 @@ class sale_order_line(osv.osv):
                     'note': line.notes,
                     'account_analytic_id': line.order_id.project_id.id,
                 })
-                cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%d,%d)', (line.id, inv_id))
+                cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
                 self.write(cr, uid, [line.id], {'invoiced':True})
+
+                sales[line.order_id.id] = True
                 create_ids.append(inv_id)
+
+        # Trigger workflow events
+        wf_service = netsvc.LocalService("workflow")
+        for sid in sales.keys():
+            wf_service.trg_write(uid, 'sale.order', sid, cr)
         return create_ids
 
+    def button_cancel(self, cr, uid, ids, context={}):
+        for line in self.browse(cr, uid, ids, context=context):
+            if line.invoiced:
+                raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sale order line that has already been invoiced !'))
+        return self.write(cr, uid, ids, {'state':'cancel'})
+
     def button_confirm(self, cr, uid, ids, context={}):
         return self.write(cr, uid, ids, {'state':'confirmed'})
 
@@ -809,15 +851,17 @@ class sale_order_line(osv.osv):
             pass
         return {'value' : value}
 
-    def copy(self, cr, uid, id, default=None,context={}):
+    def copy_data(self, cr, uid, id, default=None,context={}):
         if not default:
             default = {}
         default.update({'state':'draft', 'move_ids':[], 'invoiced':False, 'invoice_lines':[]})
-        return super(sale_order_line, self).copy(cr, uid, id, default, context)
+        return super(sale_order_line, self).copy_data(cr, uid, id, default, context)
 
     def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
             uom=False, qty_uos=0, uos=False, name='', partner_id=False,
-            lang=False, update_tax=True, date_order=False, packaging=False):
+            lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
+        if not  partner_id:
+            raise osv.except_osv(_('No Customer Defined !'), _('You have to select a customer in the sale form !\nPlease set one customer before choosing a product.'))
         warning={}
         product_uom_obj = self.pool.get('product.uom')
         partner_obj = self.pool.get('res.partner')
@@ -836,19 +880,23 @@ class sale_order_line(osv.osv):
 
         result = {}
         product_obj = product_obj.browse(cr, uid, product, context=context)
+        if not packaging and product_obj.packaging:
+            packaging = product_obj.packaging[0].id
+            result['product_packaging'] = packaging
+
         if packaging:
             default_uom = product_obj.uom_id and product_obj.uom_id.id
             pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context)
             q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
 #            qty = qty - qty % q + q
-            if not (qty % q) == 0 :
+            if qty and (q and not (qty % q) == 0):
                 ean = pack.ean
                 qty_pack = pack.qty
                 type_ul = pack.ul
-                warn_msg = "You selected a quantity of %d Units.\nBut it's not compatible with the selected packaging.\nHere is a proposition of quantities according to the packaging: " % (qty)
-                warn_msg = warn_msg + "\n\nEAN: " + str(ean) + " Quantiny: " + str(qty_pack) + " Type of ul: " + str(type_ul.name)
+                warn_msg = _("You selected a quantity of %d Units.\nBut it's not compatible with the selected packaging.\nHere is a proposition of quantities according to the packaging: ") % (qty)
+                warn_msg = warn_msg + "\n\n"+_("EAN: ") + str(ean) + _(" Quantity: ") + str(qty_pack) + _(" Type of ul: ") + str(type_ul.name)
                 warning={
-                    'title':'Packing Information !',
+                    'title':_('Packing Information !'),
                     'message': warn_msg
                     }
             result['product_uom_qty'] = qty
@@ -869,15 +917,15 @@ class sale_order_line(osv.osv):
         result .update({'type': product_obj.procure_method})
         if product_obj.description_sale:
             result['notes'] = product_obj.description_sale
-
+        fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
         if update_tax: #The quantity only have changed
             result['delay'] = (product_obj.sale_delay or 0.0)
             partner = partner_obj.browse(cr, uid, partner_id)
-            result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner, product_obj.taxes_id)
-
-        result['name'] = product_obj.partner_ref
+            result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
+        if not flag:    
+            result['name'] = product_obj.partner_ref
         domain = {}
-        if not uom and not uos:
+        if (not uom) and (not uos):
             result['product_uom'] = product_obj.uom_id.id
             if product_obj.uos_id:
                 result['product_uos'] = product_obj.uos_id.id
@@ -892,21 +940,21 @@ class sale_order_line(osv.osv):
                         [('category_id', '=', product_obj.uom_id.category_id.id)],
                         'product_uos':
                         [('category_id', '=', uos_category_id)]}
+
+        elif uos: # only happens if uom is False
+            result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
+            result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
+            result['th_weight'] = result['product_uom_qty'] * product_obj.weight
         elif uom: # whether uos is set or not
             default_uom = product_obj.uom_id and product_obj.uom_id.id
             q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
             if product_obj.uos_id:
                 result['product_uos'] = product_obj.uos_id.id
-                result['product_uos_qty'] = q * product_obj.uos_coeff
+                result['product_uos_qty'] = qty * product_obj.uos_coeff
             else:
                 result['product_uos'] = False
-                result['product_uos_qty'] = q
-            result['th_weight'] = q * product_obj.weight
-        elif uos: # only happens if uom is False
-            result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
-            result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
-            result['th_weight'] = result['product_uom_qty'] * product_obj.weight
-        # Round the quantity up
+                result['product_uos_qty'] = qty
+            result['th_weight'] = q * product_obj.weight        # Round the quantity up
 
         # get unit price
 
@@ -950,15 +998,6 @@ class sale_order_line(osv.osv):
 sale_order_line()
 
 
-
-_policy_form = '''<?xml version="1.0"?>
-<form string="Select Bank Account">
-    <field name="picking_policy" colspan="4"/>
-</form>'''
-
-_policy_fields = {
-    'picking_policy': {'string': 'Packing Policy', 'type': 'selection','selection': [('direct','Direct Delivery'),('one','All at once')],'required': True,}
-}
 class sale_config_picking_policy(osv.osv_memory):
     _name='sale.config.picking_policy'
     _columns = {
@@ -1021,3 +1060,4 @@ class sale_config_picking_policy(osv.osv_memory):
 sale_config_picking_policy()
 
 
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: