2010-01-18 10:43:33 +00:00
# -*- encoding: utf-8 -*-
##############################################################################
#
2010-04-09 00:32:46 +00:00
# OpenERP, Open Source Management Solution
2010-08-05 11:23:26 +00:00
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). All Rights Reserved
2010-01-18 10:43:33 +00:00
# $Id$
#
# This program is free software: you can redistribute it and/or modify
2010-10-28 06:54:18 +00:00
# it under the terms of the GNU Affero General Public License as published by
2010-01-18 10:43:33 +00:00
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2010-10-28 06:54:18 +00:00
# GNU Affero General Public License for more details.
2010-01-18 10:43:33 +00:00
#
2010-10-28 06:54:18 +00:00
# You should have received a copy of the GNU Affero General Public License
2010-01-18 10:43:33 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
2011-12-02 07:27:55 +00:00
from datetime import datetime
from dateutil . relativedelta import relativedelta
2010-06-23 11:53:20 +00:00
import time
2012-12-06 14:56:32 +00:00
from openerp . osv import fields , osv
from openerp . tools . translate import _
2012-12-17 15:23:03 +00:00
import openerp . addons . decimal_precision as dp
2010-01-18 10:43:33 +00:00
2010-04-09 07:18:17 +00:00
class purchase_requisition ( osv . osv ) :
_name = " purchase.requisition "
2010-05-19 18:32:32 +00:00
_description = " Purchase Requisition "
2012-08-22 15:31:45 +00:00
_inherit = [ ' mail.thread ' , ' ir.needaction_mixin ' ]
2010-01-18 10:43:33 +00:00
_columns = {
2010-04-09 07:18:17 +00:00
' name ' : fields . char ( ' Requisition Reference ' , size = 32 , required = True ) ,
2012-10-08 10:41:51 +00:00
' origin ' : fields . char ( ' Source Document ' , size = 32 ) ,
2010-04-09 07:47:07 +00:00
' date_start ' : fields . datetime ( ' Requisition Date ' ) ,
' date_end ' : fields . datetime ( ' Requisition Deadline ' ) ,
2010-01-18 10:43:33 +00:00
' user_id ' : fields . many2one ( ' res.users ' , ' Responsible ' ) ,
2013-06-10 11:41:41 +00:00
' exclusive ' : fields . selection ( [ ( ' exclusive ' , ' Purchase Requisition (exclusive) ' ) , ( ' multiple ' , ' Multiple Requisitions ' ) ] , ' Requisition Type ' , required = True , help = " Purchase Requisition (exclusive): On the confirmation of a purchase order, it cancels the remaining purchase order. \n Multiple Requisitions: It allows to have multiple purchase orders.On confirmation of a purchase order it does not cancel the remaining orders " " " ) ,
2010-01-18 10:43:33 +00:00
' description ' : fields . text ( ' Description ' ) ,
2010-04-23 07:24:58 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True ) ,
2010-07-08 14:21:02 +00:00
' purchase_ids ' : fields . one2many ( ' purchase.order ' , ' requisition_id ' , ' Purchase Orders ' , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
2010-06-21 13:59:11 +00:00
' line_ids ' : fields . one2many ( ' purchase.requisition.line ' , ' requisition_id ' , ' Products to Purchase ' , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
2010-08-25 06:24:52 +00:00
' warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' ) ,
2012-12-18 17:03:06 +00:00
' state ' : fields . selection ( [ ( ' draft ' , ' New ' ) , ( ' in_progress ' , ' Sent to Suppliers ' ) , ( ' cancel ' , ' Cancelled ' ) , ( ' done ' , ' Purchase Done ' ) ] ,
2012-12-20 11:47:30 +00:00
' Status ' , track_visibility = ' onchange ' , required = True )
2010-01-18 10:43:33 +00:00
}
_defaults = {
2012-06-27 14:48:36 +00:00
' date_start ' : lambda * args : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
2010-06-23 11:53:20 +00:00
' state ' : ' draft ' ,
' exclusive ' : ' multiple ' ,
2010-07-01 06:59:57 +00:00
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' purchase.requisition ' , context = c ) ,
' user_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , c ) . id ,
2010-04-09 07:18:17 +00:00
' name ' : lambda obj , cr , uid , context : obj . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' purchase.order.requisition ' ) ,
2010-01-18 10:43:33 +00:00
}
2010-04-16 04:42:36 +00:00
2010-11-22 10:37:53 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
2010-08-25 11:39:56 +00:00
if not default :
default = { }
default . update ( {
' state ' : ' draft ' ,
' purchase_ids ' : [ ] ,
' name ' : self . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' purchase.order.requisition ' ) ,
} )
return super ( purchase_requisition , self ) . copy ( cr , uid , id , default , context )
2012-06-04 13:26:53 +00:00
2010-06-23 11:53:20 +00:00
def tender_cancel ( self , cr , uid , ids , context = None ) :
2010-04-12 06:58:25 +00:00
purchase_order_obj = self . pool . get ( ' purchase.order ' )
2010-11-22 10:37:53 +00:00
for purchase in self . browse ( cr , uid , ids , context = context ) :
2010-04-12 06:58:25 +00:00
for purchase_id in purchase . purchase_ids :
2012-02-15 12:03:31 +00:00
if str ( purchase_id . state ) in ( ' draft ' ) :
2010-04-23 07:24:58 +00:00
purchase_order_obj . action_cancel ( cr , uid , [ purchase_id . id ] )
2014-06-24 10:12:27 +00:00
procurement_ids = self . pool [ ' procurement.order ' ] . search ( cr , uid , [ ( ' requisition_id ' , ' in ' , ids ) ] , context = context )
[FIX] purchase_requisition: duplicated stock moves
When a purchase requisition is created from a procurement order, a first stock move is created, not associated to any purchase orders
Then, on purchase order creation and confirmation, in the purchase requisition, new stock moves are created, associated to the purchase order.
The existing stock move issued from the procurement order which created the purchase requisition remained untouched, leading to wrong inventory values
To fix this, the destination location of the stock move of the procurement order is written on the source location
A proper fix should be to use a dedicated workflow for puchase requisition, but this can't be done in 7.0, it has to be done in master/trunk
2014-06-25 10:00:14 +00:00
self . pool [ ' procurement.order ' ] . action_done ( cr , uid , procurement_ids )
2012-12-18 17:03:06 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' cancel ' } )
2010-07-01 06:59:57 +00:00
2010-06-23 11:53:20 +00:00
def tender_in_progress ( self , cr , uid , ids , context = None ) :
2012-12-18 17:03:06 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' in_progress ' } , context = context )
2010-07-01 06:59:57 +00:00
2010-06-23 11:53:20 +00:00
def tender_reset ( self , cr , uid , ids , context = None ) :
2012-12-18 17:03:06 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' draft ' } )
2010-07-01 06:59:57 +00:00
2010-06-23 11:53:20 +00:00
def tender_done ( self , cr , uid , ids , context = None ) :
2014-06-24 10:12:27 +00:00
procurement_ids = self . pool [ ' procurement.order ' ] . search ( cr , uid , [ ( ' requisition_id ' , ' in ' , ids ) ] , context = context )
[FIX] purchase_requisition: duplicated stock moves
When a purchase requisition is created from a procurement order, a first stock move is created, not associated to any purchase orders
Then, on purchase order creation and confirmation, in the purchase requisition, new stock moves are created, associated to the purchase order.
The existing stock move issued from the procurement order which created the purchase requisition remained untouched, leading to wrong inventory values
To fix this, the destination location of the stock move of the procurement order is written on the source location
A proper fix should be to use a dedicated workflow for puchase requisition, but this can't be done in 7.0, it has to be done in master/trunk
2014-06-25 10:00:14 +00:00
self . pool [ ' procurement.order ' ] . action_done ( cr , uid , procurement_ids )
2012-12-18 17:03:06 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' done ' , ' date_end ' : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) } , context = context )
2012-06-04 13:26:53 +00:00
2011-12-02 07:27:55 +00:00
def _planned_date ( self , requisition , delay = 0.0 ) :
company = requisition . company_id
date_planned = False
if requisition . date_start :
date_planned = datetime . strptime ( requisition . date_start , ' % Y- % m- %d % H: % M: % S ' ) - relativedelta ( days = company . po_lead )
else :
date_planned = datetime . today ( ) - relativedelta ( days = company . po_lead )
if delay :
date_planned - = relativedelta ( days = delay )
return date_planned and date_planned . strftime ( ' % Y- % m- %d % H: % M: % S ' ) or False
def _seller_details ( self , cr , uid , requisition_line , supplier , context = None ) :
product_uom = self . pool . get ( ' product.uom ' )
pricelist = self . pool . get ( ' product.pricelist ' )
product = requisition_line . product_id
default_uom_po_id = product . uom_po_id . id
qty = product_uom . _compute_qty ( cr , uid , requisition_line . product_uom_id . id , requisition_line . product_qty , default_uom_po_id )
seller_delay = 0.0
seller_price = False
seller_qty = False
for product_supplier in product . seller_ids :
if supplier . id == product_supplier . name and qty > = product_supplier . qty :
seller_delay = product_supplier . delay
seller_qty = product_supplier . qty
supplier_pricelist = supplier . property_product_pricelist_purchase or False
2013-12-12 05:25:44 +00:00
seller_price = pricelist . price_get ( cr , uid , [ supplier_pricelist . id ] , product . id , qty , supplier . id , { ' uom ' : default_uom_po_id } ) [ supplier_pricelist . id ]
2011-12-02 07:27:55 +00:00
if seller_qty :
qty = max ( qty , seller_qty )
date_planned = self . _planned_date ( requisition_line . requisition_id , seller_delay )
return seller_price , qty , default_uom_po_id , date_planned
def make_purchase_order ( self , cr , uid , ids , partner_id , context = None ) :
"""
Create New RFQ for Supplier
"""
if context is None :
context = { }
assert partner_id , ' Supplier should be specified '
purchase_order = self . pool . get ( ' purchase.order ' )
purchase_order_line = self . pool . get ( ' purchase.order.line ' )
res_partner = self . pool . get ( ' res.partner ' )
fiscal_position = self . pool . get ( ' account.fiscal.position ' )
supplier = res_partner . browse ( cr , uid , partner_id , context = context )
supplier_pricelist = supplier . property_product_pricelist_purchase or False
res = { }
for requisition in self . browse ( cr , uid , ids , context = context ) :
if supplier . id in filter ( lambda x : x , [ rfq . state < > ' cancel ' and rfq . partner_id . id or None for rfq in requisition . purchase_ids ] ) :
2012-08-07 11:34:14 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' You have already one %s purchase order for this partner, you must cancel this purchase order to create a new quotation. ' ) % rfq . state )
2011-12-02 07:27:55 +00:00
location_id = requisition . warehouse_id . lot_input_id . id
2013-03-05 10:48:01 +00:00
context . update ( { ' mail_create_nolog ' : True } )
2011-12-02 07:27:55 +00:00
purchase_id = purchase_order . create ( cr , uid , {
' origin ' : requisition . name ,
' partner_id ' : supplier . id ,
' pricelist_id ' : supplier_pricelist . id ,
' location_id ' : location_id ,
' company_id ' : requisition . company_id . id ,
' fiscal_position ' : supplier . property_account_position and supplier . property_account_position . id or False ,
' requisition_id ' : requisition . id ,
' notes ' : requisition . description ,
' warehouse_id ' : requisition . warehouse_id . id ,
} )
2013-02-20 05:42:49 +00:00
purchase_order . message_post ( cr , uid , [ purchase_id ] , body = _ ( " RFQ created " ) , context = context )
2011-12-02 07:27:55 +00:00
res [ requisition . id ] = purchase_id
for line in requisition . line_ids :
product = line . product_id
seller_price , qty , default_uom_po_id , date_planned = self . _seller_details ( cr , uid , line , supplier , context = context )
taxes_ids = product . supplier_taxes_id
taxes = fiscal_position . map_tax ( cr , uid , supplier . property_account_position , taxes_ids )
purchase_order_line . create ( cr , uid , {
' order_id ' : purchase_id ,
' name ' : product . partner_ref ,
' product_qty ' : qty ,
' product_id ' : product . id ,
' product_uom ' : default_uom_po_id ,
' price_unit ' : seller_price ,
' date_planned ' : date_planned ,
' taxes_id ' : [ ( 6 , 0 , taxes ) ] ,
} , context = context )
return res
2010-01-18 10:43:33 +00:00
2010-04-09 07:18:17 +00:00
class purchase_requisition_line ( osv . osv ) :
2010-07-01 06:59:57 +00:00
2010-04-09 07:18:17 +00:00
_name = " purchase.requisition.line "
_description = " Purchase Requisition Line "
2010-04-09 00:32:46 +00:00
_rec_name = ' product_id '
2010-07-01 06:59:57 +00:00
2010-04-09 00:32:46 +00:00
_columns = {
2010-05-04 12:54:03 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' ) ,
2012-04-25 12:09:08 +00:00
' product_uom_id ' : fields . many2one ( ' product.uom ' , ' Product Unit of Measure ' ) ,
' product_qty ' : fields . float ( ' Quantity ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) ) ,
2010-04-16 06:17:50 +00:00
' requisition_id ' : fields . many2one ( ' purchase.requisition ' , ' Purchase Requisition ' , ondelete = ' cascade ' ) ,
2011-12-02 07:27:55 +00:00
' company_id ' : fields . related ( ' requisition_id ' , ' company_id ' , type = ' many2one ' , relation = ' res.company ' , string = ' Company ' , store = True , readonly = True ) ,
2010-04-09 00:32:46 +00:00
}
2010-04-12 10:23:28 +00:00
2012-03-05 18:40:03 +00:00
def onchange_product_id ( self , cr , uid , ids , product_id , product_uom_id , context = None ) :
2010-04-09 13:57:28 +00:00
""" Changes UoM and name if product_id changes.
@param name : Name of the field
@param product_id : Changed product_id
@return : Dictionary of changed values
"""
2010-05-20 11:10:36 +00:00
value = { ' product_uom_id ' : ' ' }
2010-04-09 13:57:28 +00:00
if product_id :
2010-11-23 11:31:52 +00:00
prod = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id , context = context )
2010-06-21 13:59:11 +00:00
value = { ' product_uom_id ' : prod . uom_id . id , ' product_qty ' : 1.0 }
2010-04-09 13:57:28 +00:00
return { ' value ' : value }
2010-05-05 12:48:59 +00:00
2010-04-23 07:24:58 +00:00
_defaults = {
2010-07-01 06:59:57 +00:00
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' purchase.requisition.line ' , context = c ) ,
2010-08-13 12:20:05 +00:00
}
2010-04-09 00:32:46 +00:00
2010-01-18 10:43:33 +00:00
class purchase_order ( osv . osv ) :
_inherit = " purchase.order "
_columns = {
2010-04-09 07:18:17 +00:00
' requisition_id ' : fields . many2one ( ' purchase.requisition ' , ' Purchase Requisition ' )
2010-01-18 10:43:33 +00:00
}
2012-06-04 13:26:53 +00:00
2010-11-22 10:37:53 +00:00
def wkf_confirm_order ( self , cr , uid , ids , context = None ) :
res = super ( purchase_order , self ) . wkf_confirm_order ( cr , uid , ids , context = context )
2011-03-08 14:52:52 +00:00
proc_obj = self . pool . get ( ' procurement.order ' )
2010-11-22 10:37:53 +00:00
for po in self . browse ( cr , uid , ids , context = context ) :
2010-04-09 07:47:07 +00:00
if po . requisition_id and ( po . requisition_id . exclusive == ' exclusive ' ) :
2010-04-09 07:18:17 +00:00
for order in po . requisition_id . purchase_ids :
2012-06-27 14:48:36 +00:00
if order . id != po . id :
2010-07-09 11:35:54 +00:00
proc_ids = proc_obj . search ( cr , uid , [ ( ' purchase_id ' , ' = ' , order . id ) ] )
if proc_ids and po . state == ' confirmed ' :
2011-02-25 11:40:03 +00:00
proc_obj . write ( cr , uid , proc_ids , { ' purchase_id ' : po . id } )
2013-01-31 10:30:15 +00:00
self . signal_purchase_cancel ( cr , uid , [ order . id ] )
2011-12-02 05:15:06 +00:00
po . requisition_id . tender_done ( context = context )
[FIX] purchase_requisition: duplicated stock moves
When a purchase requisition is created from a procurement order, a first stock move is created, not associated to any purchase orders
Then, on purchase order creation and confirmation, in the purchase requisition, new stock moves are created, associated to the purchase order.
The existing stock move issued from the procurement order which created the purchase requisition remained untouched, leading to wrong inventory values
To fix this, the destination location of the stock move of the procurement order is written on the source location
A proper fix should be to use a dedicated workflow for puchase requisition, but this can't be done in 7.0, it has to be done in master/trunk
2014-06-25 10:00:14 +00:00
if po . requisition_id and all ( purchase_id . state in [ ' draft ' , ' cancel ' ] for purchase_id in po . requisition_id . purchase_ids if purchase_id . id != po . id ) :
procurement_ids = self . pool [ ' procurement.order ' ] . search ( cr , uid , [ ( ' requisition_id ' , ' = ' , po . requisition_id . id ) ] , context = context )
for procurement in proc_obj . browse ( cr , uid , procurement_ids , context = context ) :
procurement . move_id . write ( { ' location_id ' : procurement . move_id . location_dest_id . id } )
2010-01-18 10:43:33 +00:00
return res
2010-04-09 00:32:46 +00:00
2010-04-23 07:24:58 +00:00
2010-04-09 00:32:46 +00:00
class product_product ( osv . osv ) :
_inherit = ' product.product '
2010-07-01 06:59:57 +00:00
2010-04-09 00:32:46 +00:00
_columns = {
2012-06-25 12:08:24 +00:00
' purchase_requisition ' : fields . boolean ( ' Purchase Requisition ' , help = " Check this box to generates purchase requisition instead of generating requests for quotation from procurement. " )
2010-04-09 00:32:46 +00:00
}
_defaults = {
2010-06-23 11:53:20 +00:00
' purchase_requisition ' : False
2010-04-09 00:32:46 +00:00
}
2010-05-27 12:47:06 +00:00
class procurement_order ( osv . osv ) :
_inherit = ' procurement.order '
2010-07-09 09:26:48 +00:00
_columns = {
2013-11-26 13:52:54 +00:00
' requisition_id ' : fields . many2one ( ' purchase.requisition ' , ' Latest Requisition ' )
2010-07-09 09:26:48 +00:00
}
2013-11-26 13:52:54 +00:00
2013-11-26 18:07:26 +00:00
def _get_warehouse ( self , procurement , user_company ) :
"""
Return the warehouse containing the procurment stock location ( or one of it ancestors )
If none match , returns then first warehouse of the company
"""
# NOTE This method is a copy of the one on the procurement.order defined in purchase
# module. It's been copied to ensure it been always available, even if module
# purchase is not up to date.
# Do not forget to update both version in case of modification.
company_id = ( procurement . company_id or user_company ) . id
domains = [
[
' & ' , ( ' company_id ' , ' = ' , company_id ) ,
' | ' , ' & ' , ( ' lot_stock_id.parent_left ' , ' < ' , procurement . location_id . parent_left ) ,
( ' lot_stock_id.parent_right ' , ' > ' , procurement . location_id . parent_right ) ,
( ' lot_stock_id ' , ' = ' , procurement . location_id . id )
] ,
[ ( ' company_id ' , ' = ' , company_id ) ]
]
cr , uid = procurement . _cr , procurement . _uid
context = procurement . _context
Warehouse = self . pool [ ' stock.warehouse ' ]
for domain in domains :
ids = Warehouse . search ( cr , uid , domain , context = context )
if ids :
return ids [ 0 ]
return False
2010-07-01 06:59:57 +00:00
def make_po ( self , cr , uid , ids , context = None ) :
2012-06-21 06:00:34 +00:00
res = { }
2012-06-19 13:33:00 +00:00
requisition_obj = self . pool . get ( ' purchase.requisition ' )
2013-11-26 13:52:54 +00:00
non_requisition = [ ]
for procurement in self . browse ( cr , uid , ids , context = context ) :
if procurement . product_id . purchase_requisition :
user_company = self . pool [ ' res.users ' ] . browse ( cr , uid , uid , context = context ) . company_id
2014-06-24 10:12:27 +00:00
req = requisition_obj . create ( cr , uid , {
2011-10-13 13:07:31 +00:00
' origin ' : procurement . origin ,
2010-04-09 07:47:07 +00:00
' date_end ' : procurement . date_planned ,
2013-11-26 13:52:54 +00:00
' warehouse_id ' : self . _get_warehouse ( procurement , user_company ) ,
' company_id ' : procurement . company_id . id ,
' line_ids ' : [ ( 0 , 0 , {
2010-04-09 00:32:46 +00:00
' product_id ' : procurement . product_id . id ,
' product_uom_id ' : procurement . product_uom . id ,
' product_qty ' : procurement . product_qty
2013-11-26 13:52:54 +00:00
} ) ] ,
2010-04-09 00:32:46 +00:00
} )
2013-11-26 13:52:54 +00:00
procurement . write ( {
' state ' : ' running ' ,
' requisition_id ' : req
} )
2014-06-24 10:12:27 +00:00
res [ procurement . id ] = 0
2013-11-26 13:52:54 +00:00
else :
non_requisition . append ( procurement . id )
if non_requisition :
res . update ( super ( procurement_order , self ) . make_po ( cr , uid , non_requisition , context = context ) )
2010-04-09 07:47:07 +00:00
return res
2010-07-01 06:59:57 +00:00
2010-06-23 11:53:20 +00:00
2010-08-24 12:37:11 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: