From c57e613c0a6423e1a2447b5258cfc0deac7d157a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Thu, 22 Sep 2011 13:07:02 -0300 Subject: [PATCH 1/4] [IMP] sale: refactored action_ship_create to: 1) enable created objects like picking, move and procurement to take take their creation context into account in potential overriders 2) enable to partion the moves over different pickings whenever required by simply overriding a key generation bzr revid: rvalyi@gmail.com-20110922160702-r0h48975vxbx1by0 --- addons/sale/sale.py | 73 +++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 12c27c197ed..ca3775848ea 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -665,41 +665,64 @@ class sale_order(osv.osv): return False return canceled + def _get_order_picking_key(self, cr, uid, order, order_data, order_line, pickings_dict, context=None): + """Allows to partition a sale over several pickings: just override it and return + a unique key for the given parameters. If key changes, a new picking will be created. + :params order: the sale order + :params order_data: a place holder to store data while iterating the order lines + :params line: the sale order line + :params pickings_dict: the current dictionary of key / picking_id for this order + :return: unique key for those parameters + :rtype: string + """ + return "default" + + def _create_order_picking(self, cr, uid, order, order_data, line, picking_dict, picking_vals, context=None): + picking_vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out') + return self.pool.get('stock.picking').create(cr, uid, picking_vals) + + def _create_line_move(self, cr, uid, order, order_data, line, move_vals, context=None): + return self.pool.get('stock.move').create(cr, uid, move_vals) + + def _create_line_procurement(self, cr, uid, order, order_data, line, move_id, proc_vals, context=None): + return self.pool.get('procurement.order').create(cr, uid, proc_vals) + def action_ship_create(self, cr, uid, ids, *args): wf_service = netsvc.LocalService("workflow") - picking_id = False move_obj = self.pool.get('stock.move') proc_obj = self.pool.get('procurement.order') - company = self.pool.get('res.users').browse(cr, uid, uid).company_id for order in self.browse(cr, uid, ids, context={}): - proc_ids = [] output_id = order.shop_id.warehouse_id.lot_output_id.id - picking_id = False + proc_ids = [] + pickings = {} + order_data = {} #a place holder to store data while iteraing the lines if needed + for line in order.order_line: - proc_id = False date_planned = datetime.strptime(order.date_order, '%Y-%m-%d') + relativedelta(days=line.delay or 0.0) - date_planned = (date_planned - timedelta(days=company.security_lead)).strftime('%Y-%m-%d %H:%M:%S') + date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime('%Y-%m-%d %H:%M:%S') if line.state == 'done': continue move_id = False if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'): location_id = order.shop_id.warehouse_id.lot_stock_id.id + picking_key = self._get_order_picking_key(cr, uid, order, order_data, line, pickings, *args) + picking_id = pickings.get(picking_key, False) if not picking_id: - pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out') - picking_id = self.pool.get('stock.picking').create(cr, uid, { - 'name': pick_name, - 'origin': order.name, - 'type': 'out', - 'state': 'auto', - 'move_type': order.picking_policy, - 'sale_id': order.id, - 'address_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, - }) - move_id = self.pool.get('stock.move').create(cr, uid, { + picking_id = self._create_order_picking(cr, uid, order, order_data, line, pickings, { + 'origin': order.name, + 'type': 'out', + 'state': 'auto', + 'move_type': order.picking_policy, + 'sale_id': order.id, + 'address_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, + }, *args) + pickings[picking_key] = picking_id + + move_id = self._create_line_move(cr, uid, order, order_data, line, { 'name': line.name[:64], 'picking_id': picking_id, 'product_id': line.product_id.id, @@ -717,13 +740,12 @@ class sale_order(osv.osv): 'sale_line_id': line.id, 'tracking_id': False, 'state': 'draft', - #'state': 'waiting', 'note': line.notes, 'company_id': order.company_id.id, - }) + }, *args) if line.product_id: - proc_id = self.pool.get('procurement.order').create(cr, uid, { + proc_id = self._create_line_procurement(cr, uid, order, order_data, line, move_id, { 'name': line.name, 'origin': order.name, 'date_planned': date_planned, @@ -739,8 +761,7 @@ class sale_order(osv.osv): 'move_id': move_id, 'property_ids': [(6, 0, [x.id for x in line.property_ids])], 'company_id': order.company_id.id, - 'sale_line_id': line.id, - }) + }, *args) proc_ids.append(proc_id) self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id}) if order.state == 'shipping_except': @@ -755,7 +776,7 @@ class sale_order(osv.osv): val = {} - if picking_id: + for k, picking_id in pickings.items(): wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr) for proc_id in proc_ids: From d56c6a1e40dc49c570997278d58e9a9425d3508b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Thu, 22 Sep 2011 13:31:31 -0300 Subject: [PATCH 2/4] [FIX] sale: forgot sale_line_id field in procurement generation in previous refactoring bzr revid: rvalyi@gmail.com-20110922163131-c76u3dg2afrahh16 --- addons/sale/sale.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index ca3775848ea..86c62b44a69 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -761,6 +761,7 @@ class sale_order(osv.osv): 'move_id': move_id, 'property_ids': [(6, 0, [x.id for x in line.property_ids])], 'company_id': order.company_id.id, + 'sale_line_id': line.id, }, *args) proc_ids.append(proc_id) self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id}) From ff43f2198a8215ac63454ebed86f2538fe30daad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 2 Oct 2011 14:39:52 -0300 Subject: [PATCH 3/4] [REF] sale: totally reworked the action_ship_create modularization: - action_ship_create now calls into a a method allowing to partition the picking - picking, moves and procurements can be customized BEFORE they are created into the database - there is no empty method - I commented in the shipment exception management as it looks buggy. I just reproduced the original code however bzr revid: rvalyi@gmail.com-20111002173952-xlcmt6u1pgj6k9k4 --- addons/sale/sale.py | 238 +++++++++++++++++++++----------------------- 1 file changed, 115 insertions(+), 123 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index fc702181206..3e7e58c72c6 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -673,135 +673,127 @@ class sale_order(osv.osv): return False return canceled - def _get_order_picking_key(self, cr, uid, order, order_data, order_line, pickings_dict, context=None): - """Allows to partition a sale over several pickings: just override it and return - a unique key for the given parameters. If key changes, a new picking will be created. - :params order: the sale order - :params order_data: a place holder to store data while iterating the order lines - :params line: the sale order line - :params pickings_dict: the current dictionary of key / picking_id for this order - :return: unique key for those parameters - :rtype: string - """ - return "default" - def _create_order_picking(self, cr, uid, order, order_data, line, picking_dict, picking_vals, context=None): - picking_vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out') - return self.pool.get('stock.picking').create(cr, uid, picking_vals) + def _prepare_proc(self, cr, uid, order, line, move_id, date_planned, *args): + return { + 'name': line.name, + 'origin': order.name, + 'date_planned': date_planned, + 'product_id': line.product_id.id, + 'product_qty': line.product_uom_qty, + 'product_uom': line.product_uom.id, + 'product_uos_qty': (line.product_uos and line.product_uos_qty)\ + or line.product_uom_qty, + 'product_uos': (line.product_uos and line.product_uos.id)\ + or line.product_uom.id, + 'location_id': order.shop_id.warehouse_id.lot_stock_id.id, + 'procure_method': line.type, + 'move_id': move_id, + 'property_ids': [(6, 0, [x.id for x in line.property_ids])], + 'company_id': order.company_id.id, + 'sale_line_id': line.id, + } - def _create_line_move(self, cr, uid, order, order_data, line, move_vals, context=None): - return self.pool.get('stock.move').create(cr, uid, move_vals) - def _create_line_procurement(self, cr, uid, order, order_data, line, move_id, proc_vals, context=None): - return self.pool.get('procurement.order').create(cr, uid, proc_vals) + def _prepare_move(self, cr, uid, order, line, picking_id, date_planned, *args): + location_id = order.shop_id.warehouse_id.lot_stock_id.id + output_id = order.shop_id.warehouse_id.lot_output_id.id + return { + 'name': line.name[:64], + '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_qty, + 'product_uos': (line.product_uos and line.product_uos.id)\ + or line.product_uom.id, + 'product_packaging': line.product_packaging.id, + 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id, + 'location_id': location_id, + 'location_dest_id': output_id, + 'sale_line_id': line.id, + 'tracking_id': False, + 'state': 'draft', + #'state': 'waiting', + 'note': line.notes, + 'company_id': order.company_id.id, + 'price_unit': line.product_id.standard_price or 0.0 + } + + + + def _prepare_picking(self, cr, uid, order, *args): + pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out') + return { + 'name': pick_name, + 'origin': order.name, + 'type': 'out', + 'state': 'auto', + 'move_type': order.picking_policy, + 'sale_id': order.id, + 'address_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 _create_pickings_an_procurements(self, cr, uid, order, order_lines, picking_id=False, *args): + proc_ids = [] + for line in order_lines: + if line.state == 'done': + continue + + date_planned = datetime.strptime(order.date_order, '%Y-%m-%d') + relativedelta(days=line.delay or 0.0) + date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime('%Y-%m-%d %H:%M:%S') + + if line.product_id: + if line.product_id.product_tmpl_id.type in ('product', 'consu'): + if not picking_id: + picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_picking(cr, uid, order, args)) + move_id = self.pool.get('stock.move').create(cr, uid, self._prepare_move(cr, uid, order, line, picking_id, date_planned, args)) + else: #a service has no stock move + move_id = False + + proc_id = self.pool.get('procurement.order').create(cr, uid, self._prepare_proc(cr, uid, order, line, move_id, date_planned, args)) + proc_ids.append(proc_id) + + if order.state == 'shipping_except': #deals with potentially cancelled shipments FIXME seems broken, see below + for pick in order.picking_ids: + for move in pick.move_lines: + if move.state == 'cancel': + mov_ids = self.pool.get('stock.move').search(cr, uid, [('state', '=', 'cancel'),('sale_line_id', '=', line.id),('picking_id', '=', pick.id)]) + if mov_ids: + for mov in move_obj.browse(cr, uid, mov_ids): + #FIXME: the following seems broken: what if move_id doesn't exist? What if there are several mov_ids? Shouldn't that be a sum? + self.pool.get('stock.move').write(cr, uid, [move_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty}) + self.pool.get('procurement.order').write(cr, uid, [proc_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty}) + + wf_service = netsvc.LocalService("workflow") + if picking_id: + wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr) + + for proc_id in proc_ids: + wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr) + + val = {} + if order.state == 'shipping_except': + val['state'] = 'progress' + val['shipped'] = False + + if (order.order_policy == 'manual'): + for line in order.order_line: + if (not line.invoiced) and (line.state not in ('cancel', 'draft')): + val['state'] = 'manual' + break + self.write(cr, uid, [order.id], val) + return True def action_ship_create(self, cr, uid, ids, *args): - wf_service = netsvc.LocalService("workflow") - move_obj = self.pool.get('stock.move') - proc_obj = self.pool.get('procurement.order') for order in self.browse(cr, uid, ids, context={}): - output_id = order.shop_id.warehouse_id.lot_output_id.id - proc_ids = [] - pickings = {} - order_data = {} #a place holder to store data while iteraing the lines if needed - - for line in order.order_line: - date_planned = datetime.strptime(order.date_order, '%Y-%m-%d') + relativedelta(days=line.delay or 0.0) - date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime('%Y-%m-%d %H:%M:%S') - - if line.state == 'done': - continue - move_id = False - if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'): - location_id = order.shop_id.warehouse_id.lot_stock_id.id - picking_key = self._get_order_picking_key(cr, uid, order, order_data, line, pickings, *args) - picking_id = pickings.get(picking_key, False) - if not picking_id: - picking_id = self._create_order_picking(cr, uid, order, order_data, line, pickings, { - 'origin': order.name, - 'type': 'out', - 'state': 'auto', - 'move_type': order.picking_policy, - 'sale_id': order.id, - 'address_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, - }, *args) - pickings[picking_key] = picking_id - - move_id = self._create_line_move(cr, uid, order, order_data, line, { - 'name': line.name[:64], - '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_qty, - 'product_uos': (line.product_uos and line.product_uos.id)\ - or line.product_uom.id, - 'product_packaging': line.product_packaging.id, - 'address_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', - 'note': line.notes, - 'company_id': order.company_id.id, - 'price_unit': line.product_id.standard_price or 0.0 - }, *args) - - if line.product_id: - proc_id = self._create_line_procurement(cr, uid, order, order_data, line, move_id, { - 'name': line.name, - 'origin': order.name, - 'date_planned': date_planned, - 'product_id': line.product_id.id, - 'product_qty': line.product_uom_qty, - 'product_uom': line.product_uom.id, - 'product_uos_qty': (line.product_uos and line.product_uos_qty)\ - or line.product_uom_qty, - 'product_uos': (line.product_uos and line.product_uos.id)\ - or line.product_uom.id, - 'location_id': order.shop_id.warehouse_id.lot_stock_id.id, - 'procure_method': line.type, - 'move_id': move_id, - 'property_ids': [(6, 0, [x.id for x in line.property_ids])], - 'company_id': order.company_id.id, - 'sale_line_id': line.id, - }, *args) - proc_ids.append(proc_id) - self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id}) - if order.state == 'shipping_except': - for pick in order.picking_ids: - for move in pick.move_lines: - if move.state == 'cancel': - mov_ids = move_obj.search(cr, uid, [('state', '=', 'cancel'),('sale_line_id', '=', line.id),('picking_id', '=', pick.id)]) - if mov_ids: - for mov in move_obj.browse(cr, uid, mov_ids): - move_obj.write(cr, uid, [move_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty}) - proc_obj.write(cr, uid, [proc_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty}) - - val = {} - - for k, picking_id in pickings.items(): - wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr) - - for proc_id in proc_ids: - wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr) - - 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 - self.write(cr, uid, [order.id], val) + self._create_pickings_an_procurements(cr, uid, order, [line for line in order.order_line], False, args) return True def action_ship_end(self, cr, uid, ids, context=None): From ec101c5a357012c37e36e683578e012176795086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 3 Oct 2011 11:25:54 -0300 Subject: [PATCH 4/4] [REF] sale: more explicit method names, added docstring on main non trivial method bzr revid: rvalyi@gmail.com-20111003142554-yest0alzq00h83b7 --- addons/sale/sale.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 3e7e58c72c6..8cf54f4057e 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -673,8 +673,7 @@ class sale_order(osv.osv): return False return canceled - - def _prepare_proc(self, cr, uid, order, line, move_id, date_planned, *args): + def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, *args): return { 'name': line.name, 'origin': order.name, @@ -694,8 +693,7 @@ class sale_order(osv.osv): 'sale_line_id': line.id, } - - def _prepare_move(self, cr, uid, order, line, picking_id, date_planned, *args): + def _prepare_order_line_move(self, cr, uid, order, line, picking_id, date_planned, *args): location_id = order.shop_id.warehouse_id.lot_stock_id.id output_id = order.shop_id.warehouse_id.lot_output_id.id return { @@ -722,9 +720,7 @@ class sale_order(osv.osv): 'price_unit': line.product_id.standard_price or 0.0 } - - - def _prepare_picking(self, cr, uid, order, *args): + def _prepare_order_picking(self, cr, uid, order, *args): pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out') return { 'name': pick_name, @@ -739,8 +735,18 @@ class sale_order(osv.osv): 'company_id': order.company_id.id, } - def _create_pickings_an_procurements(self, cr, uid, order, order_lines, picking_id=False, *args): + """ + Create pickings and procurements for given order lines. Filtering the order lines allows to partition the delivery + over several pickings. + + :param cr: database cursor + :param uid: current user id + :param order: sale order object + :param order_lines: sale order line objects + :param picking_id: id of picking to use evenually + :return: True + """ proc_ids = [] for line in order_lines: if line.state == 'done': @@ -752,12 +758,12 @@ class sale_order(osv.osv): if line.product_id: if line.product_id.product_tmpl_id.type in ('product', 'consu'): if not picking_id: - picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_picking(cr, uid, order, args)) - move_id = self.pool.get('stock.move').create(cr, uid, self._prepare_move(cr, uid, order, line, picking_id, date_planned, args)) + picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_order_picking(cr, uid, order, args)) + move_id = self.pool.get('stock.move').create(cr, uid, self._prepare_order_line_move(cr, uid, order, line, picking_id, date_planned, args)) else: #a service has no stock move move_id = False - proc_id = self.pool.get('procurement.order').create(cr, uid, self._prepare_proc(cr, uid, order, line, move_id, date_planned, args)) + proc_id = self.pool.get('procurement.order').create(cr, uid, self._prepare_order_line_procurement(cr, uid, order, line, move_id, date_planned, args)) proc_ids.append(proc_id) if order.state == 'shipping_except': #deals with potentially cancelled shipments FIXME seems broken, see below