2012-07-26 20:56:59 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by 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,
2012-09-17 05:07:38 +00:00
2012-07-26 20:56:59 +00:00
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import datetime , timedelta
from tools import DEFAULT_SERVER_DATE_FORMAT , DEFAULT_SERVER_DATETIME_FORMAT , DATETIME_FORMATS_MAP , float_compare
from dateutil . relativedelta import relativedelta
from osv import fields , osv
import netsvc
from tools . translate import _
class sale_shop ( osv . osv ) :
_inherit = " sale.shop "
_columns = {
2012-09-09 10:11:56 +00:00
' warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' ) ,
2012-07-26 20:56:59 +00:00
}
2012-09-09 10:11:56 +00:00
sale_shop ( )
2012-07-26 20:56:59 +00:00
class sale_order ( osv . osv ) :
_inherit = " sale.order "
def copy ( self , cr , uid , id , default = None , context = None ) :
if not default :
default = { }
default . update ( {
' shipped ' : False ,
' picking_ids ' : [ ] ,
} )
return super ( sale_order , self ) . copy ( cr , uid , id , default , context = context )
def shipping_policy_change ( self , cr , uid , ids , policy , context = None ) :
if not policy :
return { }
inv_qty = ' order '
if policy == ' prepaid ' :
inv_qty = ' order '
elif policy == ' picking ' :
inv_qty = ' procurement '
return { ' value ' : { ' invoice_quantity ' : inv_qty } }
def write ( self , cr , uid , ids , vals , context = None ) :
if vals . get ( ' order_policy ' , False ) :
if vals [ ' order_policy ' ] == ' prepaid ' :
vals . update ( { ' invoice_quantity ' : ' order ' } )
elif vals [ ' order_policy ' ] == ' picking ' :
vals . update ( { ' invoice_quantity ' : ' procurement ' } )
return super ( sale_order , self ) . write ( cr , uid , ids , vals , context = context )
def create ( self , cr , uid , vals , context = None ) :
if vals . get ( ' order_policy ' , False ) :
if vals [ ' order_policy ' ] == ' prepaid ' :
vals . update ( { ' invoice_quantity ' : ' order ' } )
if vals [ ' order_policy ' ] == ' picking ' :
vals . update ( { ' invoice_quantity ' : ' procurement ' } )
order = super ( sale_order , self ) . create ( cr , uid , vals , context = context )
return order
# This is False
def _picked_rate ( self , cr , uid , ids , name , arg , context = None ) :
if not ids :
return { }
res = { }
tmp = { }
for id in ids :
tmp [ id ] = { ' picked ' : 0.0 , ' total ' : 0.0 }
cr . execute ( ''' SELECT
p . sale_id as sale_order_id , sum ( m . product_qty ) as nbr , mp . state as procurement_state , m . state as move_state , p . type as picking_type
FROM
stock_move m
LEFT JOIN
stock_picking p on ( p . id = m . picking_id )
LEFT JOIN
procurement_order mp on ( mp . move_id = m . id )
WHERE
p . sale_id IN % s GROUP BY m . state , mp . state , p . sale_id , p . type ''' , (tuple(ids),))
for item in cr . dictfetchall ( ) :
if item [ ' move_state ' ] == ' cancel ' :
continue
if item [ ' picking_type ' ] == ' in ' : #this is a returned picking
tmp [ item [ ' sale_order_id ' ] ] [ ' total ' ] - = item [ ' nbr ' ] or 0.0 # Deducting the return picking qty
if item [ ' procurement_state ' ] == ' done ' or item [ ' move_state ' ] == ' done ' :
tmp [ item [ ' sale_order_id ' ] ] [ ' picked ' ] - = item [ ' nbr ' ] or 0.0
else :
tmp [ item [ ' sale_order_id ' ] ] [ ' total ' ] + = item [ ' nbr ' ] or 0.0
if item [ ' procurement_state ' ] == ' done ' or item [ ' move_state ' ] == ' done ' :
tmp [ item [ ' sale_order_id ' ] ] [ ' picked ' ] + = item [ ' nbr ' ] or 0.0
for order in self . browse ( cr , uid , ids , context = context ) :
if order . shipped :
res [ order . id ] = 100.0
else :
res [ order . id ] = tmp [ order . id ] [ ' total ' ] and ( 100.0 * tmp [ order . id ] [ ' picked ' ] / tmp [ order . id ] [ ' total ' ] ) or 0.0
return res
_columns = {
2012-09-09 10:11:56 +00:00
' state ' : fields . selection ( [
2012-07-26 20:56:59 +00:00
( ' draft ' , ' Draft Quotation ' ) ,
( ' sent ' , ' Quotation Sent ' ) ,
( ' cancel ' , ' Cancelled ' ) ,
( ' waiting_date ' , ' Waiting Schedule ' ) ,
( ' progress ' , ' Sale Order ' ) ,
( ' manual ' , ' Sale to Invoice ' ) ,
( ' shipping_except ' , ' Shipping Exception ' ) ,
( ' invoice_except ' , ' Invoice Exception ' ) ,
( ' done ' , ' Done ' ) ,
] , ' Status ' , readonly = True , help = " Gives the state of the quotation or sales order. \
\nThe exception state is automatically set when a cancel operation occurs \
in the invoice validation ( Invoice Exception ) or in the picking list process ( Shipping Exception ) . \nThe ' Waiting Schedule ' state is set when the invoice is confirmed \
but waiting for the scheduler to run on the order date . " , select=True),
' incoterm ' : fields . many2one ( ' stock.incoterms ' , ' Incoterm ' , help = " Incoterm which stands for ' International Commercial terms ' implies its a series of sales terms which are used in the commercial transaction. " ) ,
' picking_policy ' : fields . selection ( [ ( ' direct ' , ' Deliver each product when available ' ) , ( ' one ' , ' Deliver all products at once ' ) ] ,
' Shipping Policy ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] , ' sent ' : [ ( ' readonly ' , False ) ] } ,
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 ( [
( ' manual ' , ' On Demand ' ) ,
( ' picking ' , ' On Delivery Order ' ) ,
( ' prepaid ' , ' Before Delivery ' ) ,
] , ' Create Invoice ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] , ' sent ' : [ ( ' readonly ' , False ) ] } ,
help = """ This field controls how invoice and delivery operations are synchronized.
2012-09-21 08:13:39 +00:00
- With ' On Demand ' , the invoice is created manually when needed .
- With ' On Delivery Order ' , a draft invoice is generated after all pickings have been processed .
- With ' Before Delivery ' , a draft invoice is created , and it must be paid before delivery . """ ),
2012-07-26 20:56:59 +00:00
' picking_ids ' : fields . one2many ( ' stock.picking.out ' , ' sale_id ' , ' Related Picking ' , readonly = True , help = " This is a list of delivery orders that has been generated for this sales order. " ) ,
' shipped ' : fields . boolean ( ' Delivered ' , readonly = True , help = " It indicates that the sales order has been delivered. This field is updated only after the scheduler(s) have been launched. " ) ,
' picked_rate ' : fields . function ( _picked_rate , string = ' Picked ' , type = ' float ' ) ,
' invoice_quantity ' : fields . selection ( [ ( ' order ' , ' Ordered Quantities ' ) , ( ' procurement ' , ' Shipped Quantities ' ) ] , ' Invoice on ' ,
help = " The sale order will automatically create the invoice proposition (draft invoice). \
2012-09-09 10:11:56 +00:00
You have to choose if you want your invoice based on ordered " , required=True, readonly=True, states= { ' draft ' : [( ' readonly ' , False)]}),
2012-07-26 20:56:59 +00:00
}
_defaults = {
' picking_policy ' : ' direct ' ,
' order_policy ' : ' manual ' ,
' invoice_quantity ' : ' order ' ,
}
# Form filling
def unlink ( self , cr , uid , ids , context = None ) :
sale_orders = self . read ( cr , uid , ids , [ ' state ' ] , context = context )
unlink_ids = [ ]
for s in sale_orders :
if s [ ' state ' ] in [ ' draft ' , ' cancel ' ] :
unlink_ids . append ( s [ ' id ' ] )
else :
2012-09-21 08:13:39 +00:00
raise osv . except_osv ( _ ( ' Invalid Action! ' ) , _ ( ' In order to delete a confirmed sales order, you must cancel it. \n To do so, you must first cancel related picking for delivery orders. ' ) )
return osv . osv . unlink ( self , cr , uid , unlink_ids , context = context )
2012-07-26 20:56:59 +00:00
2012-09-09 10:11:56 +00:00
def onchange_shop_id ( self , cr , uid , ids , shop_id ) :
v = { }
if shop_id :
shop = self . pool . get ( ' sale.shop ' ) . browse ( cr , uid , shop_id )
v [ ' project_id ' ] = shop . project_id . id
# Que faire si le client a une pricelist a lui ?
if shop . pricelist_id . id :
v [ ' pricelist_id ' ] = shop . pricelist_id . id
return { ' value ' : v }
2012-07-26 20:56:59 +00:00
def action_view_delivery ( self , cr , uid , ids , context = None ) :
'''
This function returns an action that display existing delivery orders of given sale order ids . It can either be a in a list or in a form view , if there is only one delivery order to show .
'''
mod_obj = self . pool . get ( ' ir.model.data ' )
2012-09-19 09:38:42 +00:00
act_obj = self . pool . get ( ' ir.actions.act_window ' )
result = mod_obj . get_object_reference ( cr , uid , ' stock ' , ' action_picking_tree ' )
id = result and result [ 1 ] or False
result = act_obj . read ( cr , uid , [ id ] , context = context ) [ 0 ]
2012-07-26 20:56:59 +00:00
#compute the number of delivery orders to display
pick_ids = [ ]
for so in self . browse ( cr , uid , ids , context = context ) :
pick_ids + = [ picking . id for picking in so . picking_ids ]
#choose the view_mode accordingly
if len ( pick_ids ) > 1 :
2012-09-19 09:38:42 +00:00
result [ ' domain ' ] = " [( ' id ' , ' in ' ,[ " + ' , ' . join ( map ( str , pick_ids ) ) + " ])] "
2012-07-26 20:56:59 +00:00
else :
res = mod_obj . get_object_reference ( cr , uid , ' stock ' , ' view_picking_out_form ' )
2012-09-19 09:38:42 +00:00
result [ ' views ' ] = [ ( res and res [ 1 ] or False , ' form ' ) ]
result [ ' res_id ' ] = pick_ids and pick_ids [ 0 ] or False
2012-07-26 20:56:59 +00:00
return result
2012-08-13 05:17:48 +00:00
2012-07-26 20:56:59 +00:00
def action_invoice_create ( self , cr , uid , ids , grouped = False , states = [ ' confirmed ' , ' done ' , ' exception ' ] , date_inv = False , context = None ) :
picking_obj = self . pool . get ( ' stock.picking ' )
2012-09-09 10:11:56 +00:00
res = super ( sale_order , self ) . action_invoice_create ( cr , uid , ids , grouped = grouped , states = states , date_inv = date_inv , context = context )
2012-07-29 16:07:56 +00:00
for order in self . browse ( cr , uid , ids , context = context ) :
if order . order_policy == ' picking ' :
picking_obj . write ( cr , uid , map ( lambda x : x . id , order . picking_ids ) , { ' invoice_state ' : ' invoiced ' } )
2012-07-26 20:56:59 +00:00
return res
def action_cancel ( self , cr , uid , ids , context = None ) :
wf_service = netsvc . LocalService ( " workflow " )
if context is None :
context = { }
sale_order_line_obj = self . pool . get ( ' sale.order.line ' )
proc_obj = self . pool . get ( ' procurement.order ' )
for sale in self . browse ( cr , uid , ids , context = context ) :
for pick in sale . picking_ids :
if pick . state not in ( ' draft ' , ' cancel ' ) :
raise osv . except_osv (
2012-09-21 08:13:39 +00:00
_ ( ' Cannot cancel sales order! ' ) ,
_ ( ' You must first cancel all delivery order(s) attached to this sales order. ' ) )
2012-07-26 20:56:59 +00:00
if pick . state == ' cancel ' :
for mov in pick . move_lines :
proc_ids = proc_obj . search ( cr , uid , [ ( ' move_id ' , ' = ' , mov . id ) ] )
if proc_ids :
for proc in proc_ids :
wf_service . trg_validate ( uid , ' procurement.order ' , proc , ' button_check ' , cr )
for r in self . read ( cr , uid , ids , [ ' picking_ids ' ] ) :
2012-09-21 08:13:39 +00:00
for pick in r [ ' picking_ids ' ] :
2012-07-26 20:56:59 +00:00
wf_service . trg_validate ( uid , ' stock.picking ' , pick , ' button_cancel ' , cr )
return super ( sale_order , self ) . action_cancel ( cr , uid , ids , context = context )
2012-09-21 08:13:39 +00:00
def action_wait ( self , cr , uid , ids , context = None ) :
res = super ( sale_order , self ) . action_wait ( cr , uid , ids , context = context )
for o in self . browse ( cr , uid , ids ) :
noprod = self . test_no_product ( cr , uid , o , context )
if noprod and o . order_policy == ' picking ' :
self . write ( cr , uid , [ o . id ] , { ' order_policy ' : ' manual ' } , context = context )
return res
2012-09-17 05:07:38 +00:00
2012-07-26 20:56:59 +00:00
def procurement_lines_get ( self , cr , uid , ids , * args ) :
res = [ ]
for order in self . browse ( cr , uid , ids , context = { } ) :
for line in order . order_line :
if line . procurement_id :
res . append ( line . procurement_id . id )
return res
# if mode == 'finished':
# returns True if all lines are done, False otherwise
# if mode == 'canceled':
# returns True if there is at least one canceled line, False otherwise
def test_state ( self , cr , uid , ids , mode , * args ) :
assert mode in ( ' finished ' , ' canceled ' ) , _ ( " invalid mode for test_state " )
finished = True
canceled = False
notcanceled = False
write_done_ids = [ ]
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 ' ) :
if line . state != ' done ' :
write_done_ids . append ( line . id )
else :
finished = False
if line . procurement_id :
if ( line . procurement_id . state == ' cancel ' ) :
canceled = True
if line . state != ' exception ' :
write_cancel_ids . append ( line . id )
else :
notcanceled = True
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 ' : ' exception ' } )
if mode == ' finished ' :
return finished
elif mode == ' canceled ' :
return canceled
if notcanceled :
return False
return canceled
def _prepare_order_line_procurement ( self , cr , uid , order , line , move_id , date_planned , context = None ) :
return {
' name ' : line . name . split ( ' \n ' ) [ 0 ] ,
' origin ' : order . name ,
' date_planned ' : date_planned ,
' product_id ' : line . product_id . id ,
' product_qty ' : line . product_uom_qty ,
' product_uom ' : line . product_uom . id ,
' 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 ,
' procure_method ' : line . type ,
' move_id ' : move_id ,
' company_id ' : order . company_id . id ,
2012-09-17 05:07:38 +00:00
' note ' : ' \n ' . join ( line . name . split ( ' \n ' ) [ 1 : ] )
2012-07-26 20:56:59 +00:00
}
def _prepare_order_line_move ( self , cr , uid , order , line , picking_id , date_planned , context = None ) :
location_id = order . shop_id . warehouse_id . lot_stock_id . id
output_id = order . shop_id . warehouse_id . lot_output_id . id
return {
' name ' : line . name . split ( ' \n ' ) [ 0 ] [ : 250 ] ,
' picking_id ' : picking_id ,
' product_id ' : line . product_id . id ,
' date ' : date_planned ,
' date_expected ' : date_planned ,
' product_qty ' : line . product_uom_qty ,
' product_uom ' : line . product_uom . id ,
' 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 ,
' product_packaging ' : line . product_packaging . id ,
' partner_id ' : line . address_allotment_id . id or order . partner_shipping_id . id ,
' location_id ' : location_id ,
' location_dest_id ' : output_id ,
' sale_line_id ' : line . id ,
' tracking_id ' : False ,
' state ' : ' draft ' ,
#'state': 'waiting',
' note ' : ' \n ' . join ( line . name . split ( ' \n ' ) [ 1 : ] ) ,
' company_id ' : order . company_id . id ,
' price_unit ' : line . product_id . standard_price or 0.0
}
def _prepare_order_picking ( self , cr , uid , order , context = None ) :
pick_name = self . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' stock.picking.out ' )
return {
' name ' : pick_name ,
' origin ' : order . name ,
' date ' : order . date_order ,
' type ' : ' out ' ,
' state ' : ' auto ' ,
' move_type ' : order . picking_policy ,
' sale_id ' : order . id ,
' partner_id ' : order . partner_shipping_id . id ,
' note ' : order . note ,
' invoice_state ' : ( order . order_policy == ' picking ' and ' 2binvoiced ' ) or ' none ' ,
' company_id ' : order . company_id . id ,
}
def ship_recreate ( self , cr , uid , order , line , move_id , proc_id ) :
# FIXME: deals with potentially cancelled shipments, seems broken (specially if shipment has production lot)
"""
Define ship_recreate for process after shipping exception
param order : sale order to which the order lines belong
param line : sale order line records to procure
param move_id : the ID of stock move
param proc_id : the ID of procurement
"""
move_obj = self . pool . get ( ' stock.move ' )
if order . state == ' shipping_except ' :
for pick in order . picking_ids :
for move in pick . move_lines :
if move . state == ' cancel ' :
mov_ids = move_obj . search ( cr , uid , [ ( ' state ' , ' = ' , ' cancel ' ) , ( ' sale_line_id ' , ' = ' , line . id ) , ( ' picking_id ' , ' = ' , pick . id ) ] )
if mov_ids :
for mov in move_obj . browse ( cr , uid , mov_ids ) :
# FIXME: the following seems broken: what if move_id doesn't exist? What if there are several mov_ids? Shouldn't that be a sum?
move_obj . write ( cr , uid , [ move_id ] , { ' product_qty ' : mov . product_qty , ' product_uos_qty ' : mov . product_uos_qty } )
self . pool . get ( ' procurement.order ' ) . write ( cr , uid , [ proc_id ] , { ' product_qty ' : mov . product_qty , ' product_uos_qty ' : mov . product_uos_qty } )
return True
def _get_date_planned ( self , cr , uid , order , line , start_date , context = None ) :
date_planned = datetime . strptime ( start_date , DEFAULT_SERVER_DATE_FORMAT ) + relativedelta ( days = line . delay or 0.0 )
date_planned = ( date_planned - timedelta ( days = order . company_id . security_lead ) ) . strftime ( DEFAULT_SERVER_DATETIME_FORMAT )
return date_planned
def _create_pickings_and_procurements ( self , cr , uid , order , order_lines , picking_id = False , context = None ) :
""" Create the required procurements to supply sale order lines, also connecting
the procurements to appropriate stock moves in order to bring the goods to the
sale order ' s requested location.
If ` ` picking_id ` ` is provided , the stock moves will be added to it , otherwise
a standard outgoing picking will be created to wrap the stock moves , as returned
by : meth : ` ~ . _prepare_order_picking ` .
Modules that wish to customize the procurements or partition the stock moves over
multiple stock pickings may override this method and call ` ` super ( ) ` ` with
different subsets of ` ` order_lines ` ` and / or preset ` ` picking_id ` ` values .
: param browse_record order : sale order to which the order lines belong
: param list ( browse_record ) order_lines : sale order line records to procure
: param int picking_id : optional ID of a stock picking to which the created stock moves
will be added . A new picking will be created if ommitted .
: return : True
"""
move_obj = self . pool . get ( ' stock.move ' )
picking_obj = self . pool . get ( ' stock.picking ' )
procurement_obj = self . pool . get ( ' procurement.order ' )
proc_ids = [ ]
for line in order_lines :
if line . state == ' done ' :
continue
date_planned = self . _get_date_planned ( cr , uid , order , line , order . date_order , context = context )
if line . product_id :
if line . product_id . product_tmpl_id . type in ( ' product ' , ' consu ' ) :
if not picking_id :
picking_id = picking_obj . create ( cr , uid , self . _prepare_order_picking ( cr , uid , order , context = context ) )
move_id = move_obj . create ( cr , uid , self . _prepare_order_line_move ( cr , uid , order , line , picking_id , date_planned , context = context ) )
else :
# a service has no stock move
move_id = False
proc_id = procurement_obj . create ( cr , uid , self . _prepare_order_line_procurement ( cr , uid , order , line , move_id , date_planned , context = context ) )
proc_ids . append ( proc_id )
line . write ( { ' procurement_id ' : proc_id } )
self . ship_recreate ( cr , uid , order , line , move_id , proc_id )
wf_service = netsvc . LocalService ( " workflow " )
if picking_id :
wf_service . trg_validate ( uid , ' stock.picking ' , picking_id , ' button_confirm ' , cr )
self . delivery_send_note ( cr , uid , [ order . id ] , picking_id , context )
for proc_id in proc_ids :
wf_service . trg_validate ( uid , ' procurement.order ' , proc_id , ' button_confirm ' , cr )
val = { }
if order . state == ' shipping_except ' :
val [ ' state ' ] = ' progress '
val [ ' shipped ' ] = False
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
order . write ( val )
return True
def action_ship_create ( self , cr , uid , ids , context = None ) :
for order in self . browse ( cr , uid , ids , context = context ) :
self . _create_pickings_and_procurements ( cr , uid , order , order . order_line , None , context = context )
return True
def action_ship_end ( self , cr , uid , ids , context = None ) :
for order in self . browse ( cr , uid , ids , context = context ) :
val = { ' shipped ' : True }
if order . state == ' shipping_except ' :
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 )
res = self . write ( cr , uid , [ order . id ] , val )
if res :
self . delivery_end_send_note ( cr , uid , [ order . id ] , context = context )
return True
def has_stockable_products ( self , cr , uid , ids , * args ) :
for order in self . browse ( cr , uid , ids ) :
for order_line in order . order_line :
if order_line . product_id and order_line . product_id . product_tmpl_id . type in ( ' product ' , ' consu ' ) :
return True
return False
2012-09-17 05:07:38 +00:00
2012-07-26 20:56:59 +00:00
# ------------------------------------------------
# OpenChatter methods and notifications
# ------------------------------------------------
def get_needaction_user_ids ( self , cr , uid , ids , context = None ) :
result = super ( sale_order , self ) . get_needaction_user_ids ( cr , uid , ids , context = context )
return result
def delivery_send_note ( self , cr , uid , ids , picking_id , context = None ) :
for order in self . browse ( cr , uid , ids , context = context ) :
for picking in ( pck for pck in order . picking_ids if pck . id == picking_id ) :
# convert datetime field to a datetime, using server format, then
# convert it to the user TZ and re-render it with %Z to add the timezone
picking_datetime = fields . DT . datetime . strptime ( picking . min_date , DEFAULT_SERVER_DATETIME_FORMAT )
picking_date_str = fields . datetime . context_timestamp ( cr , uid , picking_datetime , context = context ) . strftime ( DATETIME_FORMATS_MAP [ ' % + ' ] + " ( % Z) " )
2012-09-09 12:19:03 +00:00
self . message_post ( cr , uid , [ order . id ] , body = _ ( " Delivery Order <em> %s </em> <b>scheduled</b> for %s . " ) % ( picking . name , picking_date_str ) , context = context )
2012-09-21 08:13:39 +00:00
2012-07-26 20:56:59 +00:00
def delivery_end_send_note ( self , cr , uid , ids , context = None ) :
2012-09-09 12:19:03 +00:00
self . message_post ( cr , uid , ids , body = _ ( " Order <b>delivered</b>. " ) , context = context )
2012-09-21 08:13:39 +00:00
2012-07-26 20:56:59 +00:00
class sale_order_line ( osv . osv ) :
2012-09-21 08:13:39 +00:00
2012-07-26 20:56:59 +00:00
def _number_packages ( self , cr , uid , ids , field_name , arg , context = None ) :
res = { }
for line in self . browse ( cr , uid , ids , context = context ) :
try :
res [ line . id ] = int ( ( line . product_uom_qty + line . product_packaging . qty - 0.0001 ) / line . product_packaging . qty )
except :
res [ line . id ] = 1
return res
2012-09-21 08:13:39 +00:00
2012-07-26 20:56:59 +00:00
_inherit = ' sale.order.line '
_columns = {
2012-09-21 08:13:39 +00:00
' delay ' : fields . float ( ' Delivery Lead Time ' , required = True , help = " Number of days between the order confirmation the shipping of the products to the customer " , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
' procurement_id ' : fields . many2one ( ' procurement.order ' , ' Procurement ' ) ,
' type ' : fields . selection ( [ ( ' make_to_stock ' , ' from stock ' ) , ( ' make_to_order ' , ' on order ' ) ] , ' Procurement Method ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
2012-07-26 20:56:59 +00:00
help = " If ' on order ' , it triggers a procurement when the sale order is confirmed to create a task, purchase order or manufacturing order linked to this sale order line. " ) ,
2012-09-09 10:11:56 +00:00
' property_ids ' : fields . many2many ( ' mrp.property ' , ' sale_order_line_property_rel ' , ' order_id ' , ' property_id ' , ' Properties ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2012-07-26 20:56:59 +00:00
' product_packaging ' : fields . many2one ( ' product.packaging ' , ' Packaging ' ) ,
' move_ids ' : fields . one2many ( ' stock.move ' , ' sale_line_id ' , ' Inventory Moves ' , readonly = True ) ,
' number_packages ' : fields . function ( _number_packages , type = ' integer ' , string = ' Number Packages ' ) ,
}
_defaults = {
2012-09-21 08:13:39 +00:00
' delay ' : 0.0 ,
' type ' : ' make_to_stock ' ,
' product_packaging ' : False ,
}
2012-09-21 09:35:20 +00:00
def _get_line_qty ( self , cr , uid , line , context = None ) :
2012-09-21 08:13:39 +00:00
if line . procurement_id and not ( line . order_id . invoice_quantity == ' order ' ) :
return self . pool . get ( ' procurement.order ' ) . quantity_get ( cr , uid ,
line . procurement_id . id , context = context )
else :
2012-09-21 09:35:20 +00:00
return super ( sale_order_line , self ) . _get_line_qty ( cr , uid , line , context = context )
2012-09-21 08:13:39 +00:00
2012-09-21 09:35:20 +00:00
def _get_line_uom ( self , cr , uid , line , context = None ) :
2012-09-21 08:13:39 +00:00
if line . procurement_id and not ( line . order_id . invoice_quantity == ' order ' ) :
return self . pool . get ( ' procurement.order ' ) . uom_get ( cr , uid ,
line . procurement_id . id , context = context )
else :
2012-09-21 09:35:20 +00:00
return super ( sale_order_line , self ) . _get_line_uom ( cr , uid , line , context = context )
2012-09-21 08:13:39 +00:00
def button_cancel ( self , cr , uid , ids , context = None ) :
res = super ( sale_order_line , self ) . button_cancel ( cr , uid , ids , context = context )
for line in self . browse ( cr , uid , ids , context = context ) :
for move_line in line . move_ids :
if move_line . state != ' cancel ' :
raise osv . except_osv (
_ ( ' Cannot cancel sales order line! ' ) ,
_ ( ' You must first cancel stock moves attached to this sales order line. ' ) )
2012-07-29 16:07:56 +00:00
return res
2012-09-21 08:13:39 +00:00
def copy_data ( self , cr , uid , id , default = None , context = None ) :
if not default :
default = { }
default . update ( { ' move_ids ' : [ ] } )
return super ( sale_order_line , self ) . copy_data ( cr , uid , id , default , context = context )
2012-07-26 20:56:59 +00:00
def product_packaging_change ( self , cr , uid , ids , pricelist , product , qty = 0 , uom = False ,
partner_id = False , packaging = False , flag = False , context = None ) :
if not product :
return { ' value ' : { ' product_packaging ' : False } }
product_obj = self . pool . get ( ' product.product ' )
product_uom_obj = self . pool . get ( ' product.uom ' )
pack_obj = self . pool . get ( ' product.packaging ' )
warning = { }
result = { }
warning_msgs = ' '
if flag :
res = self . product_id_change ( cr , uid , ids , pricelist = pricelist ,
product = product , qty = qty , uom = uom , partner_id = partner_id ,
packaging = packaging , flag = False , context = context )
warning_msgs = res . get ( ' warning ' ) and res [ ' warning ' ] [ ' message ' ]
products = product_obj . browse ( cr , uid , product , context = context )
if not products . packaging :
packaging = result [ ' product_packaging ' ] = False
elif not packaging and products . packaging and not flag :
packaging = products . packaging [ 0 ] . id
result [ ' product_packaging ' ] = packaging
if packaging :
default_uom = products . uom_id and products . uom_id . id
pack = pack_obj . browse ( cr , uid , packaging , context = context )
q = product_uom_obj . _compute_qty ( cr , uid , uom , pack . qty , default_uom )
# qty = qty - qty % q + q
if qty and ( q and not ( qty % q ) == 0 ) :
ean = pack . ean or _ ( ' (n/a) ' )
qty_pack = pack . qty
type_ul = pack . ul
if not warning_msgs :
warn_msg = _ ( " You selected a quantity of %d Units. \n "
" But it ' s not compatible with the selected packaging. \n "
" Here is a proposition of quantities according to the packaging: \n "
" EAN: %s Quantity: %s Type of ul: %s " ) % \
( qty , ean , qty_pack , type_ul . name )
warning_msgs + = _ ( " Picking Information ! : " ) + warn_msg + " \n \n "
warning = {
2012-09-21 08:13:39 +00:00
' title ' : _ ( ' Configuration Error! ' ) ,
2012-07-26 20:56:59 +00:00
' message ' : warning_msgs
}
result [ ' product_uom_qty ' ] = qty
return { ' value ' : result , ' warning ' : warning }
2012-08-16 12:09:47 +00:00
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 , fiscal_position = False , flag = False , context = None ) :
context = context or { }
product_uom_obj = self . pool . get ( ' product.uom ' )
partner_obj = self . pool . get ( ' res.partner ' )
2012-09-21 08:13:39 +00:00
product_obj = self . pool . get ( ' product.product ' )
2012-08-16 12:09:47 +00:00
warning = { }
res = super ( sale_order_line , self ) . product_id_change ( cr , uid , ids , pricelist , product , qty = qty ,
uom = uom , qty_uos = qty_uos , uos = uos , name = name , partner_id = partner_id ,
lang = lang , update_tax = update_tax , date_order = date_order , packaging = packaging , fiscal_position = fiscal_position , flag = flag , context = context )
2012-09-21 08:13:39 +00:00
2012-08-16 12:09:47 +00:00
if not product :
return { ' value ' : { ' th_weight ' : 0 , ' product_packaging ' : False ,
' product_uos_qty ' : qty } , ' domain ' : { ' product_uom ' : [ ] ,
' product_uos ' : [ ] } }
2012-09-09 10:11:56 +00:00
res_packing = self . product_packaging_change ( cr , uid , ids , pricelist , product , qty , uom , partner_id , packaging , context = context )
2012-09-21 08:13:39 +00:00
res [ ' value ' ] . update ( res_packing . get ( ' value ' , { } ) )
2012-09-09 10:11:56 +00:00
warning_msgs = res_packing . get ( ' warning ' ) and res_packing [ ' warning ' ] [ ' message ' ] or ' '
2012-08-16 12:09:47 +00:00
product_obj = product_obj . browse ( cr , uid , product , context = context )
2012-09-21 08:13:39 +00:00
res [ ' value ' ] [ ' delay ' ] = ( product_obj . sale_delay or 0.0 )
2012-08-16 12:09:47 +00:00
uom2 = False
if uom :
uom2 = product_uom_obj . browse ( cr , uid , uom )
if product_obj . uom_id . category_id . id != uom2 . category_id . id :
uom = False
if not uom2 :
uom2 = product_obj . uom_id
compare_qty = float_compare ( product_obj . virtual_available * uom2 . factor , qty * product_obj . uom_id . factor , precision_rounding = product_obj . uom_id . rounding )
if ( product_obj . type == ' product ' ) and int ( compare_qty ) == - 1 \
and ( product_obj . procure_method == ' make_to_stock ' ) :
warn_msg = _ ( ' You plan to sell %.2f %s but you only have %.2f %s available ! \n The real stock is %.2f %s . (without reservations) ' ) % \
( qty , uom2 and uom2 . name or product_obj . uom_id . name ,
max ( 0 , product_obj . virtual_available ) , product_obj . uom_id . name ,
max ( 0 , product_obj . qty_available ) , product_obj . uom_id . name )
warning_msgs + = _ ( " Not enough stock ! : " ) + warn_msg + " \n \n "
# get unit price
if warning_msgs :
2012-09-09 10:11:56 +00:00
warning = {
' title ' : _ ( ' Configuration Error! ' ) ,
' message ' : warning_msgs
}
2012-08-16 12:09:47 +00:00
res . update ( { ' warning ' : warning } )
return res
2012-09-09 17:03:41 +00:00
class sale_advance_payment_inv ( osv . osv_memory ) :
_inherit = " sale.advance.payment.inv "
2012-09-21 08:59:08 +00:00
def _create_invoices ( self , cr , uid , inv_values , sale_id , context = None ) :
result = super ( sale_advance_payment_inv , self ) . _create_invoices ( cr , uid , inv_values , sale_id , context = context )
2012-09-09 17:03:41 +00:00
sale_obj = self . pool . get ( ' sale.order ' )
2012-09-09 17:30:20 +00:00
sale_line_obj = self . pool . get ( ' sale.order.line ' )
2012-09-09 17:03:41 +00:00
wizard = self . browse ( cr , uid , ids [ 0 ] , context )
2012-09-21 08:59:08 +00:00
sale = sale_obj . browse ( cr , uid , sale_id , context = context )
if sale . order_policy == ' postpaid ' :
raise osv . except_osv (
_ ( ' Error! ' ) ,
_ ( " You cannot make an advance on a sales order \
that is defined as ' Automatic Invoice after delivery ' . " ))
# If invoice on picking: add the cost on the SO
# If not, the advance will be deduced when generating the final invoice
line_name = inv_values . get ( ' invoice_line ' ) and inv_values . get ( ' invoice_line ' ) [ 2 ] . get ( ' name ' ) or ' '
line_tax = inv_values . get ( ' invoice_line ' ) and inv_values . get ( ' invoice_line ' ) [ 2 ] . get ( ' invoice_line_tax_id ' ) or False
if sale . order_policy == ' picking ' :
vals = {
' order_id ' : sale . id ,
' name ' : line_name ,
' price_unit ' : - inv_amount ,
' product_uom_qty ' : wizard . qtty or 1.0 ,
' product_uos_qty ' : wizard . qtty or 1.0 ,
' product_uos ' : res . get ( ' uos_id ' , False ) ,
' product_uom ' : res . get ( ' uom_id ' , False ) ,
' product_id ' : wizard . product_id . id or False ,
' discount ' : False ,
' tax_id ' : line_tax ,
}
sale_line_obj . create ( cr , uid , vals , context = context )
2012-09-09 17:03:41 +00:00
return result