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 import netsvc
27 from openerp.tools.translate import _
29 from openerp import SUPERUSER_ID
31 class sale_shop(osv.osv):
32 _inherit = "sale.shop"
34 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
39 class sale_order(osv.osv):
40 _inherit = "sale.order"
42 def copy(self, cr, uid, id, default=None, context=None):
49 return super(sale_order, self).copy(cr, uid, id, default, context=context)
51 def shipping_policy_change(self, cr, uid, ids, policy, context=None):
55 if policy == 'prepaid':
57 elif policy == 'picking':
58 inv_qty = 'procurement'
59 return {'value': {'invoice_quantity': inv_qty}}
61 def write(self, cr, uid, ids, vals, context=None):
62 if vals.get('order_policy', False):
63 if vals['order_policy'] == 'prepaid':
64 vals.update({'invoice_quantity': 'order'})
65 elif vals['order_policy'] == 'picking':
66 vals.update({'invoice_quantity': 'procurement'})
67 return super(sale_order, self).write(cr, uid, ids, vals, context=context)
69 def create(self, cr, uid, vals, context=None):
70 if vals.get('order_policy', False):
71 if vals['order_policy'] == 'prepaid':
72 vals.update({'invoice_quantity': 'order'})
73 if vals['order_policy'] == 'picking':
74 vals.update({'invoice_quantity': 'procurement'})
75 order = super(sale_order, self).create(cr, uid, vals, context=context)
79 def _picked_rate(self, cr, uid, ids, name, arg, context=None):
85 tmp[id] = {'picked': 0.0, 'total': 0.0}
87 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
91 stock_picking p on (p.id=m.picking_id)
93 procurement_order mp on (mp.move_id=m.id)
95 p.sale_id IN %s GROUP BY m.state, mp.state, p.sale_id, p.type''', (tuple(ids),))
97 for item in cr.dictfetchall():
98 if item['move_state'] == 'cancel':
101 if item['picking_type'] == 'in':#this is a returned picking
102 tmp[item['sale_order_id']]['total'] -= item['nbr'] or 0.0 # Deducting the return picking qty
103 if item['procurement_state'] == 'done' or item['move_state'] == 'done':
104 tmp[item['sale_order_id']]['picked'] -= item['nbr'] or 0.0
106 tmp[item['sale_order_id']]['total'] += item['nbr'] or 0.0
107 if item['procurement_state'] == 'done' or item['move_state'] == 'done':
108 tmp[item['sale_order_id']]['picked'] += item['nbr'] or 0.0
110 for order in self.browse(cr, uid, ids, context=context):
112 res[order.id] = 100.0
114 res[order.id] = tmp[order.id]['total'] and (100.0 * tmp[order.id]['picked'] / tmp[order.id]['total']) or 0.0
118 'state': fields.selection([
119 ('draft', 'Draft Quotation'),
120 ('sent', 'Quotation Sent'),
121 ('cancel', 'Cancelled'),
122 ('waiting_date', 'Waiting Schedule'),
123 ('progress', 'Sales Order'),
124 ('manual', 'Sale to Invoice'),
125 ('shipping_except', 'Shipping Exception'),
126 ('invoice_except', 'Invoice Exception'),
128 ], 'Status', readonly=True, track_visibility='onchange',
129 help="Gives the status of the quotation or sales order.\
130 \nThe exception status is automatically set when a cancel operation occurs \
131 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\
132 but waiting for the scheduler to run on the order date.", select=True),
133 'incoterm': fields.many2one('stock.incoterms', 'Incoterm', help="International Commercial Terms are a series of predefined commercial terms used in international transactions."),
134 'picking_policy': fields.selection([('direct', 'Deliver each product when available'), ('one', 'Deliver all products at once')],
135 'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
136 help="""Pick 'Deliver each product when available' if you allow partial delivery."""),
137 'order_policy': fields.selection([
138 ('manual', 'On Demand'),
139 ('picking', 'On Delivery Order'),
140 ('prepaid', 'Before Delivery'),
141 ], 'Create Invoice', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
142 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."""),
143 '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."),
144 '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."),
145 'picked_rate': fields.function(_picked_rate, string='Picked', type='float'),
146 'invoice_quantity': fields.selection([('order', 'Ordered Quantities'), ('procurement', 'Shipped Quantities')], 'Invoice on',
147 help="The sales order will automatically create the invoice proposition (draft invoice).\
148 You have to choose if you want your invoice based on ordered ", required=True, readonly=True, states={'draft': [('readonly', False)]}),
151 'picking_policy': 'direct',
152 'order_policy': 'manual',
153 'invoice_quantity': 'order',
157 def unlink(self, cr, uid, ids, context=None):
158 sale_orders = self.read(cr, uid, ids, ['state'], context=context)
160 for s in sale_orders:
161 if s['state'] in ['draft', 'cancel']:
162 unlink_ids.append(s['id'])
164 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.'))
166 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
168 def action_view_delivery(self, cr, uid, ids, context=None):
170 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.
172 mod_obj = self.pool.get('ir.model.data')
173 act_obj = self.pool.get('ir.actions.act_window')
175 result = mod_obj.get_object_reference(cr, uid, 'stock', 'action_picking_tree')
176 id = result and result[1] or False
177 result = act_obj.read(cr, uid, [id], context=context)[0]
178 #compute the number of delivery orders to display
180 for so in self.browse(cr, uid, ids, context=context):
181 pick_ids += [picking.id for picking in so.picking_ids]
182 #choose the view_mode accordingly
183 if len(pick_ids) > 1:
184 result['domain'] = "[('id','in',["+','.join(map(str, pick_ids))+"])]"
186 res = mod_obj.get_object_reference(cr, uid, 'stock', 'view_picking_out_form')
187 result['views'] = [(res and res[1] or False, 'form')]
188 result['res_id'] = pick_ids and pick_ids[0] or False
191 def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception'], date_invoice = False, context=None):
192 picking_obj = self.pool.get('stock.picking')
193 res = super(sale_order,self).action_invoice_create( cr, uid, ids, grouped=grouped, states=states, date_invoice = date_invoice, context=context)
194 for order in self.browse(cr, uid, ids, context=context):
195 if order.order_policy == 'picking':
196 picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
199 def action_cancel(self, cr, uid, ids, context=None):
200 wf_service = netsvc.LocalService("workflow")
203 sale_order_line_obj = self.pool.get('sale.order.line')
204 proc_obj = self.pool.get('procurement.order')
205 for sale in self.browse(cr, uid, ids, context=context):
206 for pick in sale.picking_ids:
207 if pick.state not in ('draft', 'cancel'):
208 raise osv.except_osv(
209 _('Cannot cancel sales order!'),
210 _('You must first cancel all delivery order(s) attached to this sales order.'))
211 if pick.state == 'cancel':
212 for mov in pick.move_lines:
213 proc_ids = proc_obj.search(cr, uid, [('move_id', '=', mov.id)])
215 for proc in proc_ids:
216 wf_service.trg_validate(uid, 'procurement.order', proc, 'button_check', cr)
217 for r in self.read(cr, uid, ids, ['picking_ids']):
218 for pick in r['picking_ids']:
219 wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
220 return super(sale_order, self).action_cancel(cr, uid, ids, context=context)
222 def action_wait(self, cr, uid, ids, context=None):
223 res = super(sale_order, self).action_wait(cr, uid, ids, context=context)
224 for o in self.browse(cr, uid, ids):
225 noprod = self.test_no_product(cr, uid, o, context)
226 if noprod and o.order_policy=='picking':
227 self.write(cr, uid, [o.id], {'order_policy': 'manual'}, context=context)
230 def procurement_lines_get(self, cr, uid, ids, *args):
232 for order in self.browse(cr, uid, ids, context={}):
233 for line in order.order_line:
234 if line.procurement_id:
235 res.append(line.procurement_id.id)
238 def date_to_datetime(self, cr, uid, userdate, context=None):
239 """ Convert date values expressed in user's timezone to
240 server-side UTC timestamp, assuming a default arbitrary
241 time of 12:00 AM - because a time is needed.
243 :param str userdate: date string in in user time zone
244 :return: UTC datetime string for server-side use
246 # TODO: move to fields.datetime in server after 7.0
247 user_date = datetime.strptime(userdate, DEFAULT_SERVER_DATE_FORMAT)
248 if context and context.get('tz'):
249 tz_name = context['tz']
251 tz_name = self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
253 utc = pytz.timezone('UTC')
254 context_tz = pytz.timezone(tz_name)
255 user_datetime = user_date + relativedelta(hours=12.0)
256 local_timestamp = context_tz.localize(user_datetime, is_dst=False)
257 user_datetime = local_timestamp.astimezone(utc)
258 return user_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
259 return user_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
261 # if mode == 'finished':
262 # returns True if all lines are done, False otherwise
263 # if mode == 'canceled':
264 # returns True if there is at least one canceled line, False otherwise
265 def test_state(self, cr, uid, ids, mode, *args):
266 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
270 write_cancel_ids = []
271 for order in self.browse(cr, uid, ids, context={}):
272 for line in order.order_line:
273 if (not line.procurement_id) or (line.procurement_id.state=='done'):
274 if line.state != 'done':
275 write_done_ids.append(line.id)
278 if line.procurement_id:
279 if (line.procurement_id.state == 'cancel'):
281 if line.state != 'exception':
282 write_cancel_ids.append(line.id)
284 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
286 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
288 if mode == 'finished':
290 elif mode == 'canceled':
293 def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, context=None):
296 'origin': order.name,
297 'date_planned': date_planned,
298 'product_id': line.product_id.id,
299 'product_qty': line.product_uom_qty,
300 'product_uom': line.product_uom.id,
301 'product_uos_qty': (line.product_uos and line.product_uos_qty)\
302 or line.product_uom_qty,
303 'product_uos': (line.product_uos and line.product_uos.id)\
304 or line.product_uom.id,
305 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
306 'procure_method': line.type,
308 'company_id': order.company_id.id,
312 def _prepare_order_line_move(self, cr, uid, order, line, picking_id, date_planned, context=None):
313 location_id = order.shop_id.warehouse_id.lot_stock_id.id
314 output_id = order.shop_id.warehouse_id.lot_output_id.id
317 'picking_id': picking_id,
318 'product_id': line.product_id.id,
319 'date': date_planned,
320 'date_expected': date_planned,
321 'product_qty': line.product_uom_qty,
322 'product_uom': line.product_uom.id,
323 'product_uos_qty': (line.product_uos and line.product_uos_qty) or line.product_uom_qty,
324 'product_uos': (line.product_uos and line.product_uos.id)\
325 or line.product_uom.id,
326 'product_packaging': line.product_packaging.id,
327 'partner_id': line.address_allotment_id.id or order.partner_shipping_id.id,
328 'location_id': location_id,
329 'location_dest_id': output_id,
330 'sale_line_id': line.id,
331 'tracking_id': False,
334 'company_id': order.company_id.id,
335 'price_unit': line.product_id.standard_price or 0.0
338 def _prepare_order_picking(self, cr, uid, order, context=None):
339 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
342 'origin': order.name,
343 'date': self.date_to_datetime(cr, uid, order.date_order, context),
346 'move_type': order.picking_policy,
348 'partner_id': order.partner_shipping_id.id,
350 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
351 'company_id': order.company_id.id,
354 def ship_recreate(self, cr, uid, order, line, move_id, proc_id):
356 Define ship_recreate for process after shipping exception
357 param order: sales order to which the order lines belong
358 param line: sales order line records to procure
359 param move_id: the ID of stock move
360 param proc_id: the ID of procurement
362 move_obj = self.pool.get('stock.move')
363 proc_obj = self.pool.get('procurement.order')
364 if move_id and order.state == 'shipping_except':
365 current_move = move_obj.browse(cr, uid, move_id)
367 for picking in order.picking_ids:
368 if picking.id != current_move.picking_id.id and picking.state != 'cancel':
369 moves.extend(move for move in picking.move_lines if move.state != 'cancel' and move.sale_line_id.id == line.id)
371 product_qty = current_move.product_qty
372 product_uos_qty = current_move.product_uos_qty
374 product_qty -= move.product_qty
375 product_uos_qty -= move.product_uos_qty
376 if product_qty > 0 or product_uos_qty > 0:
377 move_obj.write(cr, uid, [move_id], {'product_qty': product_qty, 'product_uos_qty': product_uos_qty})
378 proc_obj.write(cr, uid, [proc_id], {'product_qty': product_qty, 'product_uos_qty': product_uos_qty})
380 current_move.unlink()
381 proc_obj.unlink(cr, uid, [proc_id])
384 def _get_date_planned(self, cr, uid, order, line, start_date, context=None):
385 start_date = self.date_to_datetime(cr, uid, start_date, context)
386 date_planned = datetime.strptime(start_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=line.delay or 0.0)
387 date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
390 def _create_pickings_and_procurements(self, cr, uid, order, order_lines, picking_id=False, context=None):
391 """Create the required procurements to supply sales order lines, also connecting
392 the procurements to appropriate stock moves in order to bring the goods to the
393 sales order's requested location.
395 If ``picking_id`` is provided, the stock moves will be added to it, otherwise
396 a standard outgoing picking will be created to wrap the stock moves, as returned
397 by :meth:`~._prepare_order_picking`.
399 Modules that wish to customize the procurements or partition the stock moves over
400 multiple stock pickings may override this method and call ``super()`` with
401 different subsets of ``order_lines`` and/or preset ``picking_id`` values.
403 :param browse_record order: sales order to which the order lines belong
404 :param list(browse_record) order_lines: sales order line records to procure
405 :param int picking_id: optional ID of a stock picking to which the created stock moves
406 will be added. A new picking will be created if ommitted.
409 move_obj = self.pool.get('stock.move')
410 picking_obj = self.pool.get('stock.picking')
411 procurement_obj = self.pool.get('procurement.order')
414 for line in order_lines:
415 if line.state == 'done':
418 date_planned = self._get_date_planned(cr, uid, order, line, order.date_order, context=context)
421 if line.product_id.type in ('product', 'consu'):
423 picking_id = picking_obj.create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
424 move_id = move_obj.create(cr, uid, self._prepare_order_line_move(cr, uid, order, line, picking_id, date_planned, context=context))
426 # a service has no stock move
429 proc_id = procurement_obj.create(cr, uid, self._prepare_order_line_procurement(cr, uid, order, line, move_id, date_planned, context=context))
430 proc_ids.append(proc_id)
431 line.write({'procurement_id': proc_id})
432 self.ship_recreate(cr, uid, order, line, move_id, proc_id)
434 wf_service = netsvc.LocalService("workflow")
436 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
437 for proc_id in proc_ids:
438 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
441 if order.state == 'shipping_except':
442 val['state'] = 'progress'
443 val['shipped'] = False
445 if (order.order_policy == 'manual'):
446 for line in order.order_line:
447 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
448 val['state'] = 'manual'
453 def action_ship_create(self, cr, uid, ids, context=None):
454 for order in self.browse(cr, uid, ids, context=context):
455 self._create_pickings_and_procurements(cr, uid, order, order.order_line, None, context=context)
458 def action_ship_end(self, cr, uid, ids, context=None):
459 for order in self.browse(cr, uid, ids, context=context):
460 val = {'shipped': True}
461 if order.state == 'shipping_except':
462 val['state'] = 'progress'
463 if (order.order_policy == 'manual'):
464 for line in order.order_line:
465 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
466 val['state'] = 'manual'
468 for line in order.order_line:
470 if line.state == 'exception':
471 towrite.append(line.id)
473 self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
474 res = self.write(cr, uid, [order.id], val)
477 def has_stockable_products(self, cr, uid, ids, *args):
478 for order in self.browse(cr, uid, ids):
479 for order_line in order.order_line:
480 if order_line.product_id and order_line.product_id.type in ('product', 'consu'):
485 class sale_order_line(osv.osv):
487 def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
489 for line in self.browse(cr, uid, ids, context=context):
491 res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
496 _inherit = 'sale.order.line'
498 '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)]}),
499 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
500 'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft': [('readonly', False)]}),
501 'product_packaging': fields.many2one('product.packaging', 'Packaging'),
502 'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
503 'number_packages': fields.function(_number_packages, type='integer', string='Number Packages'),
507 'product_packaging': False,
510 def _get_line_qty(self, cr, uid, line, context=None):
511 if line.procurement_id and not (line.order_id.invoice_quantity=='order'):
512 return self.pool.get('procurement.order').quantity_get(cr, uid,
513 line.procurement_id.id, context=context)
515 return super(sale_order_line, self)._get_line_qty(cr, uid, line, context=context)
518 def _get_line_uom(self, cr, uid, line, context=None):
519 if line.procurement_id and not (line.order_id.invoice_quantity=='order'):
520 return self.pool.get('procurement.order').uom_get(cr, uid,
521 line.procurement_id.id, context=context)
523 return super(sale_order_line, self)._get_line_uom(cr, uid, line, context=context)
525 def button_cancel(self, cr, uid, ids, context=None):
526 res = super(sale_order_line, self).button_cancel(cr, uid, ids, context=context)
527 for line in self.browse(cr, uid, ids, context=context):
528 for move_line in line.move_ids:
529 if move_line.state != 'cancel':
530 raise osv.except_osv(
531 _('Cannot cancel sales order line!'),
532 _('You must first cancel stock moves attached to this sales order line.'))
535 def copy_data(self, cr, uid, id, default=None, context=None):
538 default.update({'move_ids': []})
539 return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
541 def product_packaging_change(self, cr, uid, ids, pricelist, product, qty=0, uom=False,
542 partner_id=False, packaging=False, flag=False, context=None):
544 return {'value': {'product_packaging': False}}
545 product_obj = self.pool.get('product.product')
546 product_uom_obj = self.pool.get('product.uom')
547 pack_obj = self.pool.get('product.packaging')
552 res = self.product_id_change(cr, uid, ids, pricelist=pricelist,
553 product=product, qty=qty, uom=uom, partner_id=partner_id,
554 packaging=packaging, flag=False, context=context)
555 warning_msgs = res.get('warning') and res['warning']['message']
557 products = product_obj.browse(cr, uid, product, context=context)
558 if not products.packaging:
559 packaging = result['product_packaging'] = False
560 elif not packaging and products.packaging and not flag:
561 packaging = products.packaging[0].id
562 result['product_packaging'] = packaging
565 default_uom = products.uom_id and products.uom_id.id
566 pack = pack_obj.browse(cr, uid, packaging, context=context)
567 q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
568 # qty = qty - qty % q + q
569 if qty and (q and not (qty % q) == 0):
570 ean = pack.ean or _('(n/a)')
574 warn_msg = _("You selected a quantity of %d Units.\n"
575 "But it's not compatible with the selected packaging.\n"
576 "Here is a proposition of quantities according to the packaging:\n"
577 "EAN: %s Quantity: %s Type of ul: %s") % \
578 (qty, ean, qty_pack, type_ul.name)
579 warning_msgs += _("Picking Information ! : ") + warn_msg + "\n\n"
581 'title': _('Configuration Error!'),
582 'message': warning_msgs
584 result['product_uom_qty'] = qty
586 return {'value': result, 'warning': warning}
588 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
589 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
590 lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False, context=None):
591 context = context or {}
592 product_uom_obj = self.pool.get('product.uom')
593 partner_obj = self.pool.get('res.partner')
594 product_obj = self.pool.get('product.product')
596 res = super(sale_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty=qty,
597 uom=uom, qty_uos=qty_uos, uos=uos, name=name, partner_id=partner_id,
598 lang=lang, update_tax=update_tax, date_order=date_order, packaging=packaging, fiscal_position=fiscal_position, flag=flag, context=context)
601 res['value'].update({'product_packaging': False})
604 #update of result obtained in super function
605 product_obj = product_obj.browse(cr, uid, product, context=context)
606 res['value']['delay'] = (product_obj.sale_delay or 0.0)
607 res['value']['type'] = product_obj.procure_method
609 #check if product is available, and if not: raise an error
612 uom2 = product_uom_obj.browse(cr, uid, uom, context=context)
613 if product_obj.uom_id.category_id.id != uom2.category_id.id:
616 uom2 = product_obj.uom_id
618 # Calling product_packaging_change function after updating UoM
619 res_packing = self.product_packaging_change(cr, uid, ids, pricelist, product, qty, uom, partner_id, packaging, context=context)
620 res['value'].update(res_packing.get('value', {}))
621 warning_msgs = res_packing.get('warning') and res_packing['warning']['message'] or ''
622 compare_qty = float_compare(product_obj.virtual_available, qty, precision_rounding=uom2.rounding)
623 if (product_obj.type=='product') and int(compare_qty) == -1 \
624 and (product_obj.procure_method=='make_to_stock'):
625 warn_msg = _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') % \
627 max(0,product_obj.virtual_available), uom2.name,
628 max(0,product_obj.qty_available), uom2.name)
629 warning_msgs += _("Not enough stock ! : ") + warn_msg + "\n\n"
631 #update of warning messages
634 'title': _('Configuration Error!'),
635 'message' : warning_msgs
637 res.update({'warning': warning})
641 class sale_advance_payment_inv(osv.osv_memory):
642 _inherit = "sale.advance.payment.inv"
644 def _create_invoices(self, cr, uid, inv_values, sale_id, context=None):
645 result = super(sale_advance_payment_inv, self)._create_invoices(cr, uid, inv_values, sale_id, context=context)
646 sale_obj = self.pool.get('sale.order')
647 sale_line_obj = self.pool.get('sale.order.line')
648 wizard = self.browse(cr, uid, [result], context)
649 sale = sale_obj.browse(cr, uid, sale_id, context=context)
651 # If invoice on picking: add the cost on the SO
652 # If not, the advance will be deduced when generating the final invoice
653 line_name = inv_values.get('invoice_line') and inv_values.get('invoice_line')[0][2].get('name') or ''
654 line_tax = inv_values.get('invoice_line') and inv_values.get('invoice_line')[0][2].get('invoice_line_tax_id') or False
655 if sale.order_policy == 'picking':
659 'price_unit': -inv_amount,
660 'product_uom_qty': wizard.qtty or 1.0,
661 'product_uos_qty': wizard.qtty or 1.0,
662 'product_uos': res.get('uos_id', False),
663 'product_uom': res.get('uom_id', False),
664 'product_id': wizard.product_id.id or False,
668 sale_line_obj.create(cr, uid, vals, context=context)