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
2012-12-06 14:56:32 +00:00
from openerp . tools import DEFAULT_SERVER_DATE_FORMAT , DEFAULT_SERVER_DATETIME_FORMAT , DATETIME_FORMATS_MAP , float_compare
2012-07-26 20:56:59 +00:00
from dateutil . relativedelta import relativedelta
2012-12-06 14:56:32 +00:00
from openerp . osv import fields , osv
from openerp . tools . translate import _
2013-03-26 12:14:37 +00:00
import pytz
from openerp import SUPERUSER_ID
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 ,
} )
return super ( sale_order , self ) . copy ( cr , uid , id , default , context = context )
2013-06-14 14:16:43 +00:00
2013-07-10 15:02:07 +00:00
#Might have been deleted before for a reason
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 } }
2013-01-22 11:08:57 +00:00
def _get_default_warehouse ( self , cr , uid , context = None ) :
2013-06-14 14:33:17 +00:00
company_id = self . pool . get ( ' res.users ' ) . _get_company ( cr , uid , context = context )
2013-06-14 14:16:43 +00:00
warehouse_ids = self . pool . get ( ' stock.warehouse ' ) . search ( cr , uid , [ ( ' company_id ' , ' = ' , company_id ) ] , context = context )
2013-01-22 11:08:57 +00:00
if not warehouse_ids :
2013-06-11 08:38:59 +00:00
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' There is no warehouse defined for current company. ' ) )
2013-01-22 11:08:57 +00:00
return warehouse_ids [ 0 ]
2012-07-26 20:56:59 +00:00
_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 ' ) ,
2012-12-20 12:56:54 +00:00
( ' progress ' , ' Sales Order ' ) ,
2012-07-26 20:56:59 +00:00
( ' manual ' , ' Sale to Invoice ' ) ,
( ' shipping_except ' , ' Shipping Exception ' ) ,
( ' invoice_except ' , ' Invoice Exception ' ) ,
( ' done ' , ' Done ' ) ,
2013-07-10 15:00:42 +00:00
] , ' Status ' , readonly = True , help = " Gives the status of the quotation or sales order. \
2012-10-12 11:42:58 +00:00
\nThe exception status 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 ' status is set when the invoice is confirmed \
2012-07-26 20:56:59 +00:00
but waiting for the scheduler to run on the order date . " , select=True),
2012-10-23 12:04:15 +00:00
' incoterm ' : fields . many2one ( ' stock.incoterms ' , ' Incoterm ' , help = " International Commercial Terms are a series of predefined commercial terms used in international transactions. " ) ,
2012-07-26 20:56:59 +00:00
' 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 ) ] } ,
2012-10-31 11:03:26 +00:00
help = """ Pick ' Deliver each product when available ' if you allow partial delivery. """ ) ,
2012-07-26 20:56:59 +00:00
' 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 ) ] } ,
2012-10-31 11:03:26 +00:00
help = """ On demand: A draft invoice can be created from the sales order when needed. \n On delivery order: A draft invoice can be created from the delivery order when the products have been delivered. \n Before delivery: A draft invoice is created from the sales order and must be paid before the products can be delivered. """ ) ,
2012-07-26 20:56:59 +00:00
' 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. " ) ,
2013-06-14 14:16:43 +00:00
' warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' , required = True ) ,
2012-07-26 20:56:59 +00:00
}
_defaults = {
2013-06-29 22:17:03 +00:00
' warehouse_id ' : _get_default_warehouse ,
' picking_policy ' : ' direct ' ,
' order_policy ' : ' manual ' ,
}
2013-01-31 06:26:13 +00:00
def onchange_warehouse_id ( self , cr , uid , ids , warehouse_id , context = None ) :
val = { }
if warehouse_id :
warehouse = self . pool . get ( ' stock.warehouse ' ) . browse ( cr , uid , warehouse_id , context = context )
2013-06-14 14:16:43 +00:00
if warehouse . company_id :
2013-01-31 06:26:13 +00:00
val [ ' company_id ' ] = warehouse . company_id . id
return { ' value ' : val }
2012-07-26 20:56:59 +00:00
2013-06-29 22:17:03 +00:00
# FP Note: to change, take the picking related to the moves related to the
# procurements related to SO lines
2012-07-26 20:56:59 +00:00
def action_view_delivery ( self , cr , uid , ids , context = None ) :
'''
2013-06-29 22:17:03 +00:00
This function returns an action that display existing delivery orders
of given sales order ids . It can either be a in a list or in a form
view , if there is only one delivery order to show .
2012-07-26 20:56:59 +00:00
'''
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 ]
2013-06-29 22:17:03 +00:00
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 ]
2013-06-29 22:17:03 +00:00
2012-07-26 20:56:59 +00:00
#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-12-15 17:50:33 +00:00
def action_invoice_create ( self , cr , uid , ids , grouped = False , states = [ ' confirmed ' , ' done ' , ' exception ' ] , date_invoice = False , context = None ) :
2012-07-26 20:56:59 +00:00
picking_obj = self . pool . get ( ' stock.picking ' )
2012-12-15 17:50:33 +00:00
res = super ( sale_order , self ) . action_invoice_create ( cr , uid , ids , grouped = grouped , states = states , date_invoice = date_invoice , 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 ) :
if context is None :
context = { }
sale_order_line_obj = self . pool . get ( ' sale.order.line ' )
proc_obj = self . pool . get ( ' procurement.order ' )
[IMP] Workflow api changes: [account_payment, account_voucher, hr_holidays, hr_payroll, hr_timesheet_sheet, l10n_in_hr_payroll, point_of_sale, procurement, purchase, sale, sale_stock].
bzr revid: tta@openerp.com-20130204082943-vfevxeb7za6r4sy7
bzr revid: tta@openerp.com-20130204112949-07l1v3ru215iglut
2013-02-04 11:29:49 +00:00
stock_obj = self . pool . get ( ' stock.picking ' )
2012-07-26 20:56:59 +00:00
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 :
2013-07-10 15:02:07 +00:00
proc_obj . signal_button_check ( cr , uid , proc_ids )
2012-07-26 20:56:59 +00:00
for r in self . read ( cr , uid , ids , [ ' picking_ids ' ] ) :
2013-02-01 13:34:40 +00:00
stock_obj . signal_button_cancel ( cr , uid , r [ ' picking_ids ' ] )
2012-07-26 20:56:59 +00:00
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
2013-03-26 12:14:37 +00:00
def date_to_datetime ( self , cr , uid , userdate , context = None ) :
2013-04-04 09:21:34 +00:00
""" Convert date values expressed in user ' s timezone to
server - side UTC timestamp , assuming a default arbitrary
time of 12 : 00 AM - because a time is needed .
: param str userdate : date string in in user time zone
: return : UTC datetime string for server - side use
2013-03-26 12:14:37 +00:00
"""
2013-04-04 09:21:34 +00:00
# TODO: move to fields.datetime in server after 7.0
2013-04-09 09:14:33 +00:00
user_date = datetime . strptime ( userdate , DEFAULT_SERVER_DATE_FORMAT )
2013-03-26 12:14:37 +00:00
if context and context . get ( ' tz ' ) :
2013-04-04 09:21:34 +00:00
tz_name = context [ ' tz ' ]
2013-03-26 12:14:37 +00:00
else :
tz_name = self . pool . get ( ' res.users ' ) . read ( cr , SUPERUSER_ID , uid , [ ' tz ' ] ) [ ' tz ' ]
2013-04-04 09:21:34 +00:00
if tz_name :
utc = pytz . timezone ( ' UTC ' )
context_tz = pytz . timezone ( tz_name )
2013-04-09 12:25:30 +00:00
user_datetime = user_date + relativedelta ( hours = 12.0 )
2013-04-04 09:21:34 +00:00
local_timestamp = context_tz . localize ( user_datetime , is_dst = False )
user_datetime = local_timestamp . astimezone ( utc )
return user_datetime . strftime ( DEFAULT_SERVER_DATETIME_FORMAT )
2013-04-09 09:14:33 +00:00
return user_date . strftime ( DEFAULT_SERVER_DATETIME_FORMAT )
2013-03-25 09:20:41 +00:00
2012-07-26 20:56:59 +00:00
# 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
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 )
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
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 )
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 :
2012-12-18 22:50:15 +00:00
if order_line . product_id and order_line . product_id . type in ( ' product ' , ' consu ' ) :
2012-07-26 20:56:59 +00:00
return True
return False
2013-07-10 15:02:07 +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
2012-09-17 05:07:38 +00:00
2013-07-09 15:00:27 +00:00
class stock_move ( osv . osv ) :
_inherit = ' stock.move '
_columns = {
' sale_line_id ' : fields . many2one ( ' sale.order.line ' , ' Sale Line ' ) ,
}
2012-07-26 20:56:59 +00:00
class sale_order_line ( osv . osv ) :
2012-09-21 08:13:39 +00:00
2013-07-10 15:02:07 +00:00
2012-09-21 08:13:39 +00:00
2012-07-26 20:56:59 +00:00
_inherit = ' sale.order.line '
_columns = {
' move_ids ' : fields . one2many ( ' stock.move ' , ' sale_line_id ' , ' Inventory Moves ' , readonly = True ) ,
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-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
2012-10-22 12:22:03 +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-10-15 10:03:03 +00:00
sale_line_obj = self . pool . get ( ' sale.order.line ' )
2012-10-22 12:22:03 +00:00
wizard = self . browse ( cr , uid , [ result ] , context )
sale = sale_obj . browse ( cr , uid , sale_id , context = context )
# 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 ' ) [ 0 ] [ 2 ] . get ( ' name ' ) or ' '
line_tax = inv_values . get ( ' invoice_line ' ) and inv_values . get ( ' invoice_line ' ) [ 0 ] [ 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 )
return result
2013-07-10 15:00:42 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: