1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
22 from datetime import datetime, timedelta
23 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP, float_compare
24 from dateutil.relativedelta import relativedelta
25 from openerp.osv import fields, osv
26 from openerp.tools.translate import _
28 class sale_shop(osv.osv):
29 _inherit = "sale.shop"
31 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
36 class sale_order(osv.osv):
37 _inherit = "sale.order"
39 def copy(self, cr, uid, id, default=None, context=None):
46 return super(sale_order, self).copy(cr, uid, id, default, context=context)
48 def shipping_policy_change(self, cr, uid, ids, policy, context=None):
52 if policy == 'prepaid':
54 elif policy == 'picking':
55 inv_qty = 'procurement'
56 return {'value': {'invoice_quantity': inv_qty}}
58 def write(self, cr, uid, ids, vals, context=None):
59 if vals.get('order_policy', False):
60 if vals['order_policy'] == 'prepaid':
61 vals.update({'invoice_quantity': 'order'})
62 elif vals['order_policy'] == 'picking':
63 vals.update({'invoice_quantity': 'procurement'})
64 return super(sale_order, self).write(cr, uid, ids, vals, context=context)
66 def create(self, cr, uid, vals, context=None):
67 if vals.get('order_policy', False):
68 if vals['order_policy'] == 'prepaid':
69 vals.update({'invoice_quantity': 'order'})
70 if vals['order_policy'] == 'picking':
71 vals.update({'invoice_quantity': 'procurement'})
72 order = super(sale_order, self).create(cr, uid, vals, context=context)
76 def _picked_rate(self, cr, uid, ids, name, arg, context=None):
82 tmp[id] = {'picked': 0.0, 'total': 0.0}
84 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
88 stock_picking p on (p.id=m.picking_id)
90 procurement_order mp on (mp.move_id=m.id)
92 p.sale_id IN %s GROUP BY m.state, mp.state, p.sale_id, p.type''', (tuple(ids),))
94 for item in cr.dictfetchall():
95 if item['move_state'] == 'cancel':
98 if item['picking_type'] == 'in':#this is a returned picking
99 tmp[item['sale_order_id']]['total'] -= item['nbr'] or 0.0 # Deducting the return picking qty
100 if item['procurement_state'] == 'done' or item['move_state'] == 'done':
101 tmp[item['sale_order_id']]['picked'] -= item['nbr'] or 0.0
103 tmp[item['sale_order_id']]['total'] += item['nbr'] or 0.0
104 if item['procurement_state'] == 'done' or item['move_state'] == 'done':
105 tmp[item['sale_order_id']]['picked'] += item['nbr'] or 0.0
107 for order in self.browse(cr, uid, ids, context=context):
109 res[order.id] = 100.0
111 res[order.id] = tmp[order.id]['total'] and (100.0 * tmp[order.id]['picked'] / tmp[order.id]['total']) or 0.0
115 'state': fields.selection([
116 ('draft', 'Draft Quotation'),
117 ('sent', 'Quotation Sent'),
118 ('cancel', 'Cancelled'),
119 ('waiting_date', 'Waiting Schedule'),
120 ('progress', 'Sales Order'),
121 ('manual', 'Sale to Invoice'),
122 ('shipping_except', 'Shipping Exception'),
123 ('invoice_except', 'Invoice Exception'),
125 ], 'Status', readonly=True,help="Gives the status of the quotation or sales order.\
126 \nThe exception status is automatically set when a cancel operation occurs \
127 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\
128 but waiting for the scheduler to run on the order date.", select=True),
129 'incoterm': fields.many2one('stock.incoterms', 'Incoterm', help="International Commercial Terms are a series of predefined commercial terms used in international transactions."),
130 'picking_policy': fields.selection([('direct', 'Deliver each product when available'), ('one', 'Deliver all products at once')],
131 'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
132 help="""Pick 'Deliver each product when available' if you allow partial delivery."""),
133 'order_policy': fields.selection([
134 ('manual', 'On Demand'),
135 ('picking', 'On Delivery Order'),
136 ('prepaid', 'Before Delivery'),
137 ], 'Create Invoice', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
138 help="""On demand: A draft invoice can be created from the sales order when needed. \nOn delivery order: A draft invoice can be created from the delivery order when the products have been delivered. \nBefore delivery: A draft invoice is created from the sales order and must be paid before the products can be delivered."""),
139 '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."),
140 '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."),
141 'picked_rate': fields.function(_picked_rate, string='Picked', type='float'),
142 'invoice_quantity': fields.selection([('order', 'Ordered Quantities'), ('procurement', 'Shipped Quantities')], 'Invoice on',
143 help="The sales order will automatically create the invoice proposition (draft invoice).\
144 You have to choose if you want your invoice based on ordered ", required=True, readonly=True, states={'draft': [('readonly', False)]}),
147 'picking_policy': 'direct',
148 'order_policy': 'manual',
149 'invoice_quantity': 'order',
153 def unlink(self, cr, uid, ids, context=None):
154 sale_orders = self.read(cr, uid, ids, ['state'], context=context)
156 for s in sale_orders:
157 if s['state'] in ['draft', 'cancel']:
158 unlink_ids.append(s['id'])
160 raise osv.except_osv(_('Invalid Action!'), _('In order to delete a confirmed sales order, you must cancel it.\nTo do so, you must first cancel related picking for delivery orders.'))
162 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
164 def action_view_delivery(self, cr, uid, ids, context=None):
166 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.
168 mod_obj = self.pool.get('ir.model.data')
169 act_obj = self.pool.get('ir.actions.act_window')
171 result = mod_obj.get_object_reference(cr, uid, 'stock', 'action_picking_tree')
172 id = result and result[1] or False
173 result = act_obj.read(cr, uid, [id], context=context)[0]
174 #compute the number of delivery orders to display
176 for so in self.browse(cr, uid, ids, context=context):
177 pick_ids += [picking.id for picking in so.picking_ids]
178 #choose the view_mode accordingly
179 if len(pick_ids) > 1:
180 result['domain'] = "[('id','in',["+','.join(map(str, pick_ids))+"])]"
182 res = mod_obj.get_object_reference(cr, uid, 'stock', 'view_picking_out_form')
183 result['views'] = [(res and res[1] or False, 'form')]
184 result['res_id'] = pick_ids and pick_ids[0] or False
187 def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception'], date_invoice = False, context=None):
188 picking_obj = self.pool.get('stock.picking')
189 res = super(sale_order,self).action_invoice_create( cr, uid, ids, grouped=grouped, states=states, date_invoice = date_invoice, context=context)
190 for order in self.browse(cr, uid, ids, context=context):
191 if order.order_policy == 'picking':
192 picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
195 def action_cancel(self, cr, uid, ids, context=None):
198 sale_order_line_obj = self.pool.get('sale.order.line')
199 proc_obj = self.pool.get('procurement.order')
200 stock_obj = self.pool.get('stock.picking')
201 for sale in self.browse(cr, uid, ids, context=context):
202 for pick in sale.picking_ids:
203 if pick.state not in ('draft', 'cancel'):
204 raise osv.except_osv(
205 _('Cannot cancel sales order!'),
206 _('You must first cancel all delivery order(s) attached to this sales order.'))
207 if pick.state == 'cancel':
208 for mov in pick.move_lines:
209 proc_ids = proc_obj.search(cr, uid, [('move_id', '=', mov.id)])
211 proc_obj.signal_button_check(cr, uid, proc_ids)
212 for r in self.read(cr, uid, ids, ['picking_ids']):
213 stock_obj.signal_button_cancel(cr, uid, r['picking_ids'])
214 return super(sale_order, self).action_cancel(cr, uid, ids, context=context)
216 def action_wait(self, cr, uid, ids, context=None):
217 res = super(sale_order, self).action_wait(cr, uid, ids, context=context)
218 for o in self.browse(cr, uid, ids):
219 noprod = self.test_no_product(cr, uid, o, context)
220 if noprod and o.order_policy=='picking':
221 self.write(cr, uid, [o.id], {'order_policy': 'manual'}, context=context)
224 def procurement_lines_get(self, cr, uid, ids, *args):
226 for order in self.browse(cr, uid, ids, context={}):
227 for line in order.order_line:
228 if line.procurement_id:
229 res.append(line.procurement_id.id)
232 # if mode == 'finished':
233 # returns True if all lines are done, False otherwise
234 # if mode == 'canceled':
235 # returns True if there is at least one canceled line, False otherwise
236 def test_state(self, cr, uid, ids, mode, *args):
237 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
241 write_cancel_ids = []
242 for order in self.browse(cr, uid, ids, context={}):
243 for line in order.order_line:
244 if (not line.procurement_id) or (line.procurement_id.state=='done'):
245 if line.state != 'done':
246 write_done_ids.append(line.id)
249 if line.procurement_id:
250 if (line.procurement_id.state == 'cancel'):
252 if line.state != 'exception':
253 write_cancel_ids.append(line.id)
255 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
257 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
259 if mode == 'finished':
261 elif mode == 'canceled':
264 def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, context=None):
267 'origin': order.name,
268 'date_planned': date_planned,
269 'product_id': line.product_id.id,
270 'product_qty': line.product_uom_qty,
271 'product_uom': line.product_uom.id,
272 'product_uos_qty': (line.product_uos and line.product_uos_qty)\
273 or line.product_uom_qty,
274 'product_uos': (line.product_uos and line.product_uos.id)\
275 or line.product_uom.id,
276 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
277 'procure_method': line.type,
279 'company_id': order.company_id.id,
283 def _prepare_order_line_move(self, cr, uid, order, line, picking_id, date_planned, context=None):
284 location_id = order.shop_id.warehouse_id.lot_stock_id.id
285 output_id = order.shop_id.warehouse_id.lot_output_id.id
288 'picking_id': picking_id,
289 'product_id': line.product_id.id,
290 'date': date_planned,
291 'date_expected': date_planned,
292 'product_qty': line.product_uom_qty,
293 'product_uom': line.product_uom.id,
294 'product_uos_qty': (line.product_uos and line.product_uos_qty) or line.product_uom_qty,
295 'product_uos': (line.product_uos and line.product_uos.id)\
296 or line.product_uom.id,
297 'product_packaging': line.product_packaging.id,
298 'partner_id': line.address_allotment_id.id or order.partner_shipping_id.id,
299 'location_id': location_id,
300 'location_dest_id': output_id,
301 'sale_line_id': line.id,
302 'tracking_id': False,
305 'company_id': order.company_id.id,
306 'price_unit': line.product_id.standard_price or 0.0
309 def _prepare_order_picking(self, cr, uid, order, context=None):
310 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
313 'origin': order.name,
314 'date': order.date_order,
317 'move_type': order.picking_policy,
319 'partner_id': order.partner_shipping_id.id,
321 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
322 'company_id': order.company_id.id,
325 def ship_recreate(self, cr, uid, order, line, move_id, proc_id):
326 # FIXME: deals with potentially cancelled shipments, seems broken (specially if shipment has production lot)
328 Define ship_recreate for process after shipping exception
329 param order: sales order to which the order lines belong
330 param line: sales order line records to procure
331 param move_id: the ID of stock move
332 param proc_id: the ID of procurement
334 move_obj = self.pool.get('stock.move')
335 if order.state == 'shipping_except':
336 for pick in order.picking_ids:
337 for move in pick.move_lines:
338 if move.state == 'cancel':
339 mov_ids = move_obj.search(cr, uid, [('state', '=', 'cancel'),('sale_line_id', '=', line.id),('picking_id', '=', pick.id)])
341 for mov in move_obj.browse(cr, uid, mov_ids):
342 # 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?
343 move_obj.write(cr, uid, [move_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
344 self.pool.get('procurement.order').write(cr, uid, [proc_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
347 def _get_date_planned(self, cr, uid, order, line, start_date, context=None):
348 date_planned = datetime.strptime(start_date, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=line.delay or 0.0)
349 date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
352 def _create_pickings_and_procurements(self, cr, uid, order, order_lines, picking_id=False, context=None):
353 """Create the required procurements to supply sales order lines, also connecting
354 the procurements to appropriate stock moves in order to bring the goods to the
355 sales order's requested location.
357 If ``picking_id`` is provided, the stock moves will be added to it, otherwise
358 a standard outgoing picking will be created to wrap the stock moves, as returned
359 by :meth:`~._prepare_order_picking`.
361 Modules that wish to customize the procurements or partition the stock moves over
362 multiple stock pickings may override this method and call ``super()`` with
363 different subsets of ``order_lines`` and/or preset ``picking_id`` values.
365 :param browse_record order: sales order to which the order lines belong
366 :param list(browse_record) order_lines: sales order line records to procure
367 :param int picking_id: optional ID of a stock picking to which the created stock moves
368 will be added. A new picking will be created if ommitted.
371 move_obj = self.pool.get('stock.move')
372 picking_obj = self.pool.get('stock.picking')
373 procurement_obj = self.pool.get('procurement.order')
376 for line in order_lines:
377 if line.state == 'done':
380 date_planned = self._get_date_planned(cr, uid, order, line, order.date_order, context=context)
383 if line.product_id.type in ('product', 'consu'):
385 picking_id = picking_obj.create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
386 move_id = move_obj.create(cr, uid, self._prepare_order_line_move(cr, uid, order, line, picking_id, date_planned, context=context))
388 # a service has no stock move
391 proc_id = procurement_obj.create(cr, uid, self._prepare_order_line_procurement(cr, uid, order, line, move_id, date_planned, context=context))
392 proc_ids.append(proc_id)
393 line.write({'procurement_id': proc_id})
394 self.ship_recreate(cr, uid, order, line, move_id, proc_id)
397 picking_obj.signal_button_confirm(cr, uid, [picking_id])
398 procurement_obj.signal_button_confirm(cr, uid, proc_ids)
401 if order.state == 'shipping_except':
402 val['state'] = 'progress'
403 val['shipped'] = False
405 if (order.order_policy == 'manual'):
406 for line in order.order_line:
407 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
408 val['state'] = 'manual'
413 def action_ship_create(self, cr, uid, ids, context=None):
414 for order in self.browse(cr, uid, ids, context=context):
415 self._create_pickings_and_procurements(cr, uid, order, order.order_line, None, context=context)
418 def action_ship_end(self, cr, uid, ids, context=None):
419 for order in self.browse(cr, uid, ids, context=context):
420 val = {'shipped': True}
421 if order.state == 'shipping_except':
422 val['state'] = 'progress'
423 if (order.order_policy == 'manual'):
424 for line in order.order_line:
425 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
426 val['state'] = 'manual'
428 for line in order.order_line:
430 if line.state == 'exception':
431 towrite.append(line.id)
433 self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
434 res = self.write(cr, uid, [order.id], val)
437 def has_stockable_products(self, cr, uid, ids, *args):
438 for order in self.browse(cr, uid, ids):
439 for order_line in order.order_line:
440 if order_line.product_id and order_line.product_id.type in ('product', 'consu'):
445 class sale_order_line(osv.osv):
447 def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
449 for line in self.browse(cr, uid, ids, context=context):
451 res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
456 _inherit = 'sale.order.line'
458 '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)]}),
459 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
460 'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft': [('readonly', False)]}),
461 'product_packaging': fields.many2one('product.packaging', 'Packaging'),
462 'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
463 'number_packages': fields.function(_number_packages, type='integer', string='Number Packages'),
467 'product_packaging': False,
470 def _get_line_qty(self, cr, uid, line, context=None):
471 if line.procurement_id and not (line.order_id.invoice_quantity=='order'):
472 return self.pool.get('procurement.order').quantity_get(cr, uid,
473 line.procurement_id.id, context=context)
475 return super(sale_order_line, self)._get_line_qty(cr, uid, line, context=context)
478 def _get_line_uom(self, cr, uid, line, context=None):
479 if line.procurement_id and not (line.order_id.invoice_quantity=='order'):
480 return self.pool.get('procurement.order').uom_get(cr, uid,
481 line.procurement_id.id, context=context)
483 return super(sale_order_line, self)._get_line_uom(cr, uid, line, context=context)
485 def button_cancel(self, cr, uid, ids, context=None):
486 res = super(sale_order_line, self).button_cancel(cr, uid, ids, context=context)
487 for line in self.browse(cr, uid, ids, context=context):
488 for move_line in line.move_ids:
489 if move_line.state != 'cancel':
490 raise osv.except_osv(
491 _('Cannot cancel sales order line!'),
492 _('You must first cancel stock moves attached to this sales order line.'))
495 def copy_data(self, cr, uid, id, default=None, context=None):
498 default.update({'move_ids': []})
499 return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
501 def product_packaging_change(self, cr, uid, ids, pricelist, product, qty=0, uom=False,
502 partner_id=False, packaging=False, flag=False, context=None):
504 return {'value': {'product_packaging': False}}
505 product_obj = self.pool.get('product.product')
506 product_uom_obj = self.pool.get('product.uom')
507 pack_obj = self.pool.get('product.packaging')
512 res = self.product_id_change(cr, uid, ids, pricelist=pricelist,
513 product=product, qty=qty, uom=uom, partner_id=partner_id,
514 packaging=packaging, flag=False, context=context)
515 warning_msgs = res.get('warning') and res['warning']['message']
517 products = product_obj.browse(cr, uid, product, context=context)
518 if not products.packaging:
519 packaging = result['product_packaging'] = False
520 elif not packaging and products.packaging and not flag:
521 packaging = products.packaging[0].id
522 result['product_packaging'] = packaging
525 default_uom = products.uom_id and products.uom_id.id
526 pack = pack_obj.browse(cr, uid, packaging, context=context)
527 q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
528 # qty = qty - qty % q + q
529 if qty and (q and not (qty % q) == 0):
530 ean = pack.ean or _('(n/a)')
534 warn_msg = _("You selected a quantity of %d Units.\n"
535 "But it's not compatible with the selected packaging.\n"
536 "Here is a proposition of quantities according to the packaging:\n"
537 "EAN: %s Quantity: %s Type of ul: %s") % \
538 (qty, ean, qty_pack, type_ul.name)
539 warning_msgs += _("Picking Information ! : ") + warn_msg + "\n\n"
541 'title': _('Configuration Error!'),
542 'message': warning_msgs
544 result['product_uom_qty'] = qty
546 return {'value': result, 'warning': warning}
548 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
549 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
550 lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False, context=None):
551 context = context or {}
552 product_uom_obj = self.pool.get('product.uom')
553 partner_obj = self.pool.get('res.partner')
554 product_obj = self.pool.get('product.product')
556 res = super(sale_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty=qty,
557 uom=uom, qty_uos=qty_uos, uos=uos, name=name, partner_id=partner_id,
558 lang=lang, update_tax=update_tax, date_order=date_order, packaging=packaging, fiscal_position=fiscal_position, flag=flag, context=context)
561 res['value'].update({'product_packaging': False})
564 #update of result obtained in super function
565 res_packing = self.product_packaging_change(cr, uid, ids, pricelist, product, qty, uom, partner_id, packaging, context=context)
566 res['value'].update(res_packing.get('value', {}))
567 warning_msgs = res_packing.get('warning') and res_packing['warning']['message'] or ''
568 product_obj = product_obj.browse(cr, uid, product, context=context)
569 res['value']['delay'] = (product_obj.sale_delay or 0.0)
570 res['value']['type'] = product_obj.procure_method
572 #check if product is available, and if not: raise an error
575 uom2 = product_uom_obj.browse(cr, uid, uom)
576 if product_obj.uom_id.category_id.id != uom2.category_id.id:
579 uom2 = product_obj.uom_id
580 compare_qty = float_compare(product_obj.virtual_available * uom2.factor, qty * product_obj.uom_id.factor, precision_rounding=product_obj.uom_id.rounding)
581 if (product_obj.type=='product') and int(compare_qty) == -1 \
582 and (product_obj.procure_method=='make_to_stock'):
583 warn_msg = _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') % \
584 (qty, uom2 and uom2.name or product_obj.uom_id.name,
585 max(0,product_obj.virtual_available), product_obj.uom_id.name,
586 max(0,product_obj.qty_available), product_obj.uom_id.name)
587 warning_msgs += _("Not enough stock ! : ") + warn_msg + "\n\n"
589 #update of warning messages
592 'title': _('Configuration Error!'),
593 'message' : warning_msgs
595 res.update({'warning': warning})
599 class sale_advance_payment_inv(osv.osv_memory):
600 _inherit = "sale.advance.payment.inv"
602 def _create_invoices(self, cr, uid, inv_values, sale_id, context=None):
603 result = super(sale_advance_payment_inv, self)._create_invoices(cr, uid, inv_values, sale_id, context=context)
604 sale_obj = self.pool.get('sale.order')
605 sale_line_obj = self.pool.get('sale.order.line')
606 wizard = self.browse(cr, uid, [result], context)
607 sale = sale_obj.browse(cr, uid, sale_id, context=context)
608 if sale.order_policy == 'postpaid':
609 raise osv.except_osv(
611 _("You cannot make an advance on a sales order \
612 that is defined as 'Automatic Invoice after delivery'."))
614 # If invoice on picking: add the cost on the SO
615 # If not, the advance will be deduced when generating the final invoice
616 line_name = inv_values.get('invoice_line') and inv_values.get('invoice_line')[0][2].get('name') or ''
617 line_tax = inv_values.get('invoice_line') and inv_values.get('invoice_line')[0][2].get('invoice_line_tax_id') or False
618 if sale.order_policy == 'picking':
622 'price_unit': -inv_amount,
623 'product_uom_qty': wizard.qtty or 1.0,
624 'product_uos_qty': wizard.qtty or 1.0,
625 'product_uos': res.get('uos_id', False),
626 'product_uom': res.get('uom_id', False),
627 'product_id': wizard.product_id.id or False,
631 sale_line_obj.create(cr, uid, vals, context=context)