define roles in process wherever necessary
[odoo/odoo.git] / addons / purchase / purchase.py
index 07ad5af..440bc38 100644 (file)
@@ -1,3 +1,4 @@
+# -*- encoding: utf-8 -*-
 ##############################################################################
 #
 # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
@@ -76,7 +77,7 @@ class purchase_order(osv.osv):
 
     def _amount_total(self, cr, uid, ids, field_name, arg, context):
         res = {}
-        untax = self._amount_untaxed(cr, uid, ids, field_name, arg, context) 
+        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:
@@ -85,31 +86,109 @@ class purchase_order(osv.osv):
             res[id] = cur_obj.round(cr, uid, cur, untax.get(id, 0.0) + tax.get(id, 0.0))
         return res
 
+    def _set_minimum_planned_date(self, cr, uid, ids, name, value, arg, context):
+        if not value: return False
+        if type(ids)!=type([]):
+            ids=[ids]
+        for po in self.browse(cr, uid, ids, context):
+            cr.execute("""update purchase_order_line set
+                    date_planned=%s
+                where
+                    order_id=%d and
+                    (date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
+        return True
+
+    def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context):
+        res={}
+        purchase_obj=self.browse(cr, uid, ids, context=context)
+        for purchase in purchase_obj:
+            res[purchase.id] = False
+            if purchase.order_line:
+                min_date=purchase.order_line[0].date_planned
+                for line in purchase.order_line:
+                    if line.date_planned < min_date:
+                        min_date=line.date_planned
+                res[purchase.id]=min_date
+        return res
+
+    def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
+        res = {}
+        for purchase in self.browse(cursor, user, ids, context=context):
+            tot = 0.0
+            if purchase.invoice_id and purchase.invoice_id.state not in ('draft','cancel'):
+                tot += purchase.invoice_id.amount_untaxed
+            if purchase.amount_untaxed:
+                res[purchase.id] = tot * 100.0 / purchase.amount_untaxed
+            else:
+                res[purchase.id] = 0.0
+        return res
+
+    def _shipped_rate(self, cr, uid, ids, name, arg, context=None):
+        if not ids: return {}
+        res = {}
+        for id in ids:
+            res[id] = [0.0,0.0]
+        cr.execute('''SELECT
+                p.purchase_id,sum(m.product_qty), m.state
+            FROM
+                stock_move m
+            LEFT JOIN
+                stock_picking p on (p.id=m.picking_id)
+            WHERE
+                p.purchase_id in ('''+','.join(map(str,ids))+''')
+            GROUP BY m.state, p.purchase_id''')
+        for oid,nbr,state in cr.fetchall():
+            if state=='cancel':
+                continue
+            if state=='done':
+                res[oid][0] += nbr or 0.0
+                res[oid][1] += nbr or 0.0
+            else:
+                res[oid][1] += nbr or 0.0
+        for r in res:
+            if not res[r][1]:
+                res[r] = 0.0
+            else:
+                res[r] = 100.0 * res[r][0] / res[r][1]
+        return res
+
     _columns = {
         'name': fields.char('Order Reference', size=64, required=True, select=True),
-        'origin': fields.char('Origin', size=64),
+        'origin': fields.char('Origin', size=64, 
+            help="Reference of the document that generated this purchase order request."
+        ),
         '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'),
+        'date_approve':fields.date('Date Approved', readonly=1),
         'partner_id':fields.many2one('res.partner', 'Supplier', 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)]}),
+        'dest_address_id':fields.many2one('res.partner.address', 'Destination Address', states={'posted':[('readonly',True)]},
+            help="Put an address if you want to deliver directly from the supplier to the customer." \
+                "In this case, it will remove the warehouse link and set the customer location."
+        ),
         'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', states={'posted':[('readonly',True)]}),
-        'location_id': fields.many2one('stock.location', 'Delivery destination', required=True),
+        'location_id': fields.many2one('stock.location', 'Destination', required=True),
 
-        'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}),
+        'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."),
 
-        '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)]}),
+        '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 Status', 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 Lines', 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 purchase"),
         'shipped':fields.boolean('Received', readonly=True, select=True),
+        'shipped_rate': fields.function(_shipped_rate, method=True, string='Received', type='float'),
         'invoiced':fields.boolean('Invoiced & Paid', readonly=True, select=True),
-        'invoice_method': fields.selection([('manual','Manual'),('order','From order'),('picking','From picking')], 'Invoicing Control', required=True),
-
+        'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
+        'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True,
+            help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
+                "will just have to validate this invoice for control.\n" \
+                "From Picking: a draft invoice will be pre-genearted based on validated receptions.\n" \
+                "Manual: no invoice will be pre-generated. The accountant will have to encode manually."
+        ),
+        'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, method=True,store=True, string='Planned Date', type='datetime', help="This is computed as the minimum scheduled date of all purchase order lines' products."),
         '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'),
@@ -148,10 +227,11 @@ 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.id
+        pricelist = self.pool.get('res.partner').property_get(cr, uid,
+                    part,property_pref=['property_product_pricelist_purchase']).get('property_product_pricelist_purchase',False)
         return {'value':{'partner_address_id': addr['default'], 'pricelist_id': pricelist}}
 
-    def wkf_approve_order(self, cr, uid, ids):
+    def wkf_approve_order(self, cr, uid, ids, context={}):
         self.write(cr, uid, ids, {'state': 'approved', 'date_approve': time.strftime('%Y-%m-%d')})
         return True
 
@@ -163,7 +243,7 @@ class purchase_order(osv.osv):
         for id in ids:
             self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
         return True
-    
+
     def wkf_warn_buyer(self, cr, uid, ids):
         self.write(cr, uid, ids, {'state' : 'wait', 'validator' : uid})
         request = pooler.get_pool(cr.dbname).get('res.request')
@@ -174,7 +254,7 @@ class purchase_order(osv.osv):
                 if manager and not (manager.id in managers):
                     managers.append(manager.id)
             for manager_id in managers:
-                request.create(cr, uid, 
+                request.create(cr, uid,
                       {'name' : "Purchase amount over the limit",
                        'act_from' : uid,
                        'act_to' : manager_id,
@@ -309,11 +389,11 @@ class purchase_order_line(osv.osv):
             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('Scheduled date', required=True),
+        'date_planned': fields.datetime('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),
@@ -330,7 +410,7 @@ class purchase_order_line(osv.osv):
     }
     _table = 'purchase_order_line'
     _name = 'purchase.order.line'
-    _description = 'Purchase Order line'
+    _description = 'Purchase Order lines'
     def copy(self, cr, uid, id, default=None,context={}):
         if not default:
             default = {}
@@ -342,13 +422,14 @@ class purchase_order_line(osv.osv):
         if not pricelist:
             raise osv.except_osv(_('No Pricelist !'), _('You have to select a pricelist in the purchase form !\nPlease set one before choosing a product.'))
         if not product:
-            return {'value': {'price_unit': 0.0, 'name':'','notes':''}, 'domain':{'product_uom':[]}}
+            return {'value': {'price_unit': 0.0, 'name':'','notes':'', 'product_uom' : False}, 'domain':{'product_uom':[]}}
+        prod= self.pool.get('product.product').browse(cr, uid,product)
         lang=False
         if partner_id:
-            lang=self.pool.get('res.partner').read(cr, uid, [partner_id])[0]['lang']
+            lang=self.pool.get('res.partner').read(cr, uid, partner_id)['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 = self.pool.get('product.product').read(cr, uid, product, ['supplier_taxes_id','name','seller_delay','uom_po_id','description_purchase'],context=context)
         prod_uom_po = prod['uom_po_id'][0]
         if not uom:
             uom = prod_uom_po
@@ -359,12 +440,28 @@ class purchase_order_line(osv.osv):
                     'uom': uom,
                     'date': date_order,
                     })[pricelist]
-        dt = (DateTime.now() + DateTime.RelativeDateTime(days=prod['seller_delay'] or 0.0)).strftime('%Y-%m-%d')
+        dt = (DateTime.now() + DateTime.RelativeDateTime(days=prod['seller_delay'] or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
         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 = {}
 
+        
+        taxes = self.pool.get('account.tax').browse(cr, uid,prod['supplier_taxes_id'])
+        taxep = None
+        if partner_id:
+            taxep_id = self.pool.get('res.partner').property_get(cr, uid,partner_id,property_pref=['property_account_supplier_tax']).get('property_account_supplier_tax',False)
+            if taxep_id:
+                taxep=self.pool.get('account.tax').browse(cr, uid,taxep_id)
+        if not taxep or not taxep.id:
+            res['value']['taxes_id'] = [x.id for x in prod['supplier_taxes_id']]
+        else:
+            res5 = [taxep.id]
+            for t in taxes:
+                if not t.tax_group==taxep.tax_group:
+                    res5.append(t.id)
+            res['value']['taxes_id'] = res5
+
         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])]}
@@ -384,3 +481,6 @@ class purchase_order_line(osv.osv):
             res['value']['price_unit'] = 0.0
         return res
 purchase_order_line()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+