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 ,
' 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 ' } )
2013-06-14 14:16:43 +00:00
order = super ( sale_order , self ) . create ( cr , uid , vals , context = context )
2012-07-26 20:56:59 +00:00
return order
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-12-03 09:55:44 +00:00
return False
2013-01-22 11:08:57 +00:00
return warehouse_ids [ 0 ]
2012-07-26 20:56:59 +00:00
# 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 ' ) ,
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 ' ) ,
2014-06-12 10:37:11 +00:00
] , ' Status ' , readonly = True , track_visibility = ' onchange ' ,
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
' 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 ' ) ,
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
' invoice_quantity ' : fields . selection ( [ ( ' order ' , ' Ordered Quantities ' ) , ( ' procurement ' , ' Shipped Quantities ' ) ] , ' Invoice on ' ,
2012-12-21 16:48:08 +00:00
help = " The sales 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 = {
2013-01-22 11:08:57 +00:00
' warehouse_id ' : _get_default_warehouse ,
2012-07-26 20:56:59 +00:00
' 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
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
def action_view_delivery ( self , cr , uid , ids , context = None ) :
'''
2012-12-21 16:48:08 +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 ]
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-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 :
[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
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
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
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 _prepare_order_line_procurement ( self , cr , uid , order , line , move_id , date_planned , context = None ) :
return {
2012-12-17 22:28:27 +00:00
' name ' : line . name ,
2012-07-26 20:56:59 +00:00
' 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 ,
2013-01-22 11:08:57 +00:00
' location_id ' : order . warehouse_id . lot_stock_id . id ,
2012-07-26 20:56:59 +00:00
' procure_method ' : line . type ,
' move_id ' : move_id ,
' company_id ' : order . company_id . id ,
2012-12-17 22:28:27 +00:00
' note ' : line . name ,
2012-07-26 20:56:59 +00:00
}
def _prepare_order_line_move ( self , cr , uid , order , line , picking_id , date_planned , context = None ) :
2013-01-22 11:08:57 +00:00
location_id = order . warehouse_id . lot_stock_id . id
output_id = order . warehouse_id . lot_output_id . id
2012-07-26 20:56:59 +00:00
return {
2012-12-17 22:28:27 +00:00
' name ' : line . name ,
2012-07-26 20:56:59 +00:00
' 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',
' 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 ,
2013-03-26 12:14:37 +00:00
' date ' : self . date_to_datetime ( cr , uid , order . date_order , context ) ,
2012-07-26 20:56:59 +00:00
' 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 ) :
"""
Define ship_recreate for process after shipping exception
2012-12-21 16:48:08 +00:00
param order : sales order to which the order lines belong
param line : sales order line records to procure
2012-07-26 20:56:59 +00:00
param move_id : the ID of stock move
param proc_id : the ID of procurement
"""
move_obj = self . pool . get ( ' stock.move ' )
2014-04-24 15:51:41 +00:00
proc_obj = self . pool . get ( ' procurement.order ' )
if move_id and order . state == ' shipping_except ' :
current_move = move_obj . browse ( cr , uid , move_id )
moves = [ ]
for picking in order . picking_ids :
if picking . id != current_move . picking_id . id and picking . state != ' cancel ' :
moves . extend ( move for move in picking . move_lines if move . state != ' cancel ' and move . sale_line_id . id == line . id )
if moves :
product_qty = current_move . product_qty
product_uos_qty = current_move . product_uos_qty
for move in moves :
product_qty - = move . product_qty
product_uos_qty - = move . product_uos_qty
if product_qty > 0 or product_uos_qty > 0 :
move_obj . write ( cr , uid , [ move_id ] , { ' product_qty ' : product_qty , ' product_uos_qty ' : product_uos_qty } )
proc_obj . write ( cr , uid , [ proc_id ] , { ' product_qty ' : product_qty , ' product_uos_qty ' : product_uos_qty } )
else :
current_move . unlink ( )
proc_obj . unlink ( cr , uid , [ proc_id ] )
2012-07-26 20:56:59 +00:00
return True
def _get_date_planned ( self , cr , uid , order , line , start_date , context = None ) :
2013-03-26 12:14:37 +00:00
start_date = self . date_to_datetime ( cr , uid , start_date , context )
2013-03-25 09:20:41 +00:00
date_planned = datetime . strptime ( start_date , DEFAULT_SERVER_DATETIME_FORMAT ) + relativedelta ( days = line . delay or 0.0 )
2012-07-26 20:56:59 +00:00
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 ) :
2012-12-21 16:48:08 +00:00
""" Create the required procurements to supply sales order lines, also connecting
2012-07-26 20:56:59 +00:00
the procurements to appropriate stock moves in order to bring the goods to the
2012-12-21 16:48:08 +00:00
sales order ' s requested location.
2012-07-26 20:56:59 +00:00
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 .
2012-12-21 16:48:08 +00:00
: param browse_record order : sales order to which the order lines belong
: param list ( browse_record ) order_lines : sales order line records to procure
2012-07-26 20:56:59 +00:00
: 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 :
2012-12-18 22:50:15 +00:00
if line . product_id . type in ( ' product ' , ' consu ' ) :
2012-07-26 20:56:59 +00:00
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 )
if picking_id :
2013-02-04 07:15:26 +00:00
picking_obj . signal_button_confirm ( cr , uid , [ picking_id ] )
procurement_obj . signal_button_confirm ( cr , uid , proc_ids )
2012-07-26 20:56:59 +00:00
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 )
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
2012-09-17 05:07:38 +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-10-23 12:04:15 +00:00
' delay ' : fields . float ( ' Delivery Lead Time ' , required = True , help = " Number of days between the order confirmation and the shipping of the products to the customer " , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2012-09-21 08:13:39 +00:00
' procurement_id ' : fields . many2one ( ' procurement.order ' , ' Procurement ' ) ,
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 ,
' 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 :
2012-10-01 17:09:20 +00:00
res [ ' value ' ] . update ( { ' product_packaging ' : False } )
return res
2014-09-19 14:38:31 +00:00
# set product uom in context to get virtual stock in current uom
if res . get ( ' value ' , { } ) . get ( ' product_uom ' ) :
# use the uom changed by super call
context . update ( { ' uom ' : res [ ' value ' ] [ ' product_uom ' ] } )
elif uom :
# fallback on selected
context . update ( { ' uom ' : uom } )
2012-10-01 17:09:20 +00:00
#update of result obtained in super function
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-10-01 17:13:41 +00:00
res [ ' value ' ] [ ' type ' ] = product_obj . procure_method
2012-10-01 17:09:20 +00:00
#check if product is available, and if not: raise an error
2012-08-16 12:09:47 +00:00
uom2 = False
if uom :
2013-07-01 11:15:43 +00:00
uom2 = product_uom_obj . browse ( cr , uid , uom , context = context )
2012-08-16 12:09:47 +00:00
if product_obj . uom_id . category_id . id != uom2 . category_id . id :
uom = False
if not uom2 :
uom2 = product_obj . uom_id
2013-06-13 16:08:12 +00:00
# Calling product_packaging_change function after updating UoM
2013-06-13 08:44:41 +00:00
res_packing = self . product_packaging_change ( cr , uid , ids , pricelist , product , qty , uom , partner_id , packaging , context = context )
res [ ' value ' ] . update ( res_packing . get ( ' value ' , { } ) )
warning_msgs = res_packing . get ( ' warning ' ) and res_packing [ ' warning ' ] [ ' message ' ] or ' '
2014-05-13 12:00:06 +00:00
compare_qty = float_compare ( product_obj . virtual_available , qty , precision_rounding = uom2 . rounding )
2012-08-16 12:09:47 +00:00
if ( product_obj . type == ' product ' ) and int ( compare_qty ) == - 1 \
2014-05-13 12:19:45 +00:00
and ( product_obj . procure_method == ' make_to_stock ' ) :
2012-08-16 12:09:47 +00:00
warn_msg = _ ( ' You plan to sell %.2f %s but you only have %.2f %s available ! \n The real stock is %.2f %s . (without reservations) ' ) % \
2014-05-13 12:19:45 +00:00
( qty , uom2 . name ,
max ( 0 , product_obj . virtual_available ) , uom2 . name ,
max ( 0 , product_obj . qty_available ) , uom2 . name )
2012-08-16 12:09:47 +00:00
warning_msgs + = _ ( " Not enough stock ! : " ) + warn_msg + " \n \n "
2012-10-01 17:09:20 +00:00
#update of warning messages
2012-08-16 12:09:47 +00:00
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
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