diff --git a/addons/account_anglo_saxon/test/anglo_saxon.yml b/addons/account_anglo_saxon/test/anglo_saxon.yml index 6e9ebc0068e..3ea85fc0d73 100644 --- a/addons/account_anglo_saxon/test/anglo_saxon.yml +++ b/addons/account_anglo_saxon/test/anglo_saxon.yml @@ -123,10 +123,9 @@ - Reception is ready for process so now done the reception. - - !python {model: stock.partial.picking}: | - pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001")).picking_ids - partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]}) - self.do_partial(cr, uid, [partial_id]) + !python {model: stock.picking}: | + picking_id = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001")).picking_ids[0] + picking_id.do_partial(context=context) - I check the Stock Interim account (Received) is credited successfully. - @@ -189,7 +188,6 @@ - !record {model: stock.picking, id: stock_picking_out001}: partner_id: base.res_partner_13 - invoice_state: 2binvoiced move_lines: - company_id: base.main_company location_id: stock.stock_location_stock @@ -198,29 +196,28 @@ product_uom: product.product_uom_unit location_dest_id: stock.stock_location_customers move_type: direct - type: out + picking_type_id: stock.picking_type_out - I need to check the availability of the product, So I make my picking order for processing later. - !python {model: stock.picking}: | - self.draft_force_assign(cr, uid, [ref("stock_picking_out001")], {"lang": "en_US", "search_default_available": - 1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner", - "active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"), - }) + self.action_confirm(cr, uid, [ref('stock_picking_out001')], context=context) + - I check the product availability, Product is available in the stock and ready to be sent. - !python {model: stock.picking}: | - self.action_assign(cr, uid, [ref("stock_picking_out001")], {"lang": "en_US", "search_default_available": - 1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner", - "active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"), - }) + picking = self.browse(cr, uid, ref("stock_picking_out001")) + assert picking.state == "confirmed", "Picking should be confirmed." + for move_line in picking.move_lines: + assert move_line.state == "confirmed", "Move should be confirmed." + - I process the delivery. - - !python {model: stock.partial.picking}: | - partial_id = self.create(cr, uid, {}, context={'active_model':'stock.picking','active_ids':[ref('stock_picking_out001')]}) - self.do_partial(cr, uid, [partial_id]) + !python {model: stock.picking}: | + picking = self.pool.get('stock.picking').browse(cr, uid, ref("stock_picking_out001")) + picking.do_partial(context=context) - I check Stock Interim account (Delivery) is debited successfully. - diff --git a/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml b/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml index f7d52f4f5a8..bb6200e148b 100644 --- a/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml +++ b/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml @@ -131,10 +131,9 @@ - Reception is ready for process so now done the reception. - - !python {model: stock.partial.picking}: | - pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001_fifo")).picking_ids - partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]}) - self.do_partial(cr, uid, [partial_id]) + !python {model: stock.picking}: | + picking_id = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001_fifo")).picking_ids[0] + picking_id.do_partial(context=context) - I check the Stock Interim account (Received) is credit successfully. - @@ -192,7 +191,6 @@ - !record {model: stock.picking, id: stock_picking_out001_fifo}: partner_id: base.res_partner_13 - invoice_state: 2binvoiced move_lines: - company_id: base.main_company location_id: stock.stock_location_stock @@ -200,29 +198,26 @@ product_qty: 1.0 location_dest_id: stock.stock_location_customers move_type: direct - type: out + picking_type_id: stock.picking_type_out - I need to check the availability of the product, So I make my picking order for processing later. - !python {model: stock.picking}: | - self.draft_force_assign(cr, uid, [ref("stock_picking_out001_fifo")], {"lang": "en_US", "search_default_available": - 1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner", - "active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"), - }) + self.action_confirm(cr, uid, [ref('stock_picking_out001')], context=context) - I check the product availability, Product is available in the stock and ready to be sent. - !python {model: stock.picking}: | - self.action_assign(cr, uid, [ref("stock_picking_out001_fifo")], {"lang": "en_US", "search_default_available": - 1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner", - "active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"), - }) + picking = self.browse(cr, uid, ref("stock_picking_out001")) + assert picking.state == "confirmed", "Picking should be confirmed." + for move_line in picking.move_lines: + assert move_line.state == "confirmed", "Move should be confirmed." - I process the delivery. - - !python {model: stock.partial.picking}: | - partial_id = self.create(cr, uid, {}, context={'active_model':'stock.picking','active_ids':[ref('stock_picking_out001_fifo')]}) - self.do_partial(cr, uid, [partial_id]) + !python {model: stock.picking}: | + picking = self.pool.get('stock.picking').browse(cr, uid, ref("stock_picking_out001_fifo")) + picking.do_partial(context=context) - I check Stock Interim account (Delivery) is debited successfully. - diff --git a/addons/purchase/__openerp__.py b/addons/purchase/__openerp__.py index 56512be09da..a0bb133dd46 100644 --- a/addons/purchase/__openerp__.py +++ b/addons/purchase/__openerp__.py @@ -81,7 +81,6 @@ Dashboard / Reports for Purchase Management will include: 'test/ui/duplicate_order.yml', 'test/ui/delete_order.yml', 'test/average_price.yml', - 'test/lifo_price.yml', ], 'demo': [ 'purchase_order_demo.yml', diff --git a/addons/purchase/test/process/generate_invoice_from_reception.yml b/addons/purchase/test/process/generate_invoice_from_reception.yml index 6390413b418..37cf615c6ec 100644 --- a/addons/purchase/test/process/generate_invoice_from_reception.yml +++ b/addons/purchase/test/process/generate_invoice_from_reception.yml @@ -11,9 +11,9 @@ - Now I create an invoice for order on reception. - - !python {model: stock.picking}: | - pick_ids = self.search(cr, uid, [('purchase_id','=',ref('purchase_order_2'))]) - self.action_invoice_create(cr, uid, pick_ids, ref('account.expenses_journal')) + !python {model: purchase.order}: | + pick_ids = [self.browse(cr, uid, ref("purchase_order_2")).id] + self.action_invoice_create(cr, uid, pick_ids) - I check that the invoice of order. - diff --git a/addons/purchase/test/process/rfq2order2done.yml b/addons/purchase/test/process/rfq2order2done.yml index ff9158b3c09..a6999686026 100644 --- a/addons/purchase/test/process/rfq2order2done.yml +++ b/addons/purchase/test/process/rfq2order2done.yml @@ -45,10 +45,9 @@ - Reception is ready for process so now done the reception. - - !python {model: stock.partial.picking}: | - pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_1")).picking_ids - partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]}) - self.do_partial(cr, uid, [partial_id]) + !python {model: stock.picking}: | + pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_1")).picking_ids[0] + pick_ids.do_partial(context=context) - I check that purchase order is shipped. - diff --git a/addons/sale/sale.py b/addons/sale/sale.py index fa041b11370..354a3b48301 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -658,7 +658,7 @@ class sale_order(osv.osv): for order in self.browse(cr, uid, ids, context=context): proc_ids = [] group_id = self.pool.get("procurement.group").create(cr, uid, { - 'name': order.name, 'partner_id': order.partner_shipping_id.id, 'move_type': order.picking_policy + 'name': order.name, 'partner_id': order.partner_shipping_id.id }, context=context) order.write({'procurement_group_id': group_id}, context=context) for line in order.order_line: diff --git a/addons/sale_stock/stock.py b/addons/sale_stock/stock.py index dc50770ced9..e404ab3dd7b 100644 --- a/addons/sale_stock/stock.py +++ b/addons/sale_stock/stock.py @@ -123,7 +123,6 @@ class stock_picking(osv.osv): 'company_id': sale.company_id.id, 'currency_id': sale.pricelist_id.currency_id.id, 'journal_id': journal_id, - 'comment': sale.note }, context=context) invoices[key] = invoice_id diff --git a/addons/sale_stock/test/picking_order_policy.yml b/addons/sale_stock/test/picking_order_policy.yml index e28b4b066d4..f903d160400 100644 --- a/addons/sale_stock/test/picking_order_policy.yml +++ b/addons/sale_stock/test/picking_order_policy.yml @@ -31,20 +31,14 @@ from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT order = self.browse(cr, uid, ref("sale.sale_order_6")) for order_line in order.order_line: - procurement = order_line.procurement_id - sale_order_date = self.date_to_datetime(cr, uid, order.date_order, context) - date_planned = datetime.strptime(sale_order_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0) + procurement = order_line.procurement_ids[0] + date_planned = datetime.strptime(order.date_order, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0) date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT) assert procurement.date_planned == date_planned, "Scheduled date is not correspond." assert procurement.product_id.id == order_line.product_id.id, "Product is not correspond." assert procurement.product_qty == order_line.product_uom_qty, "Qty is not correspond." assert procurement.product_uom.id == order_line.product_uom.id, "UOM is not correspond." - assert procurement.procure_method == order_line.type, "Procurement method is not correspond." -- - I run the scheduler. -- - !python {model: procurement.order}: | - self.run_scheduler(cr, uid) + assert procurement.move_ids[0].procure_method == order_line.type, "Procurement method is not correspond." - I check the details of delivery order after confirmed quotation. - @@ -57,7 +51,7 @@ for picking in sale_order.picking_ids: assert picking.state == "auto" or "confirmed", "Delivery order should be in 'Waitting Availability' state." assert picking.origin == sale_order.name,"Origin of Delivery order is not correspond with sequence number of sale order." - assert picking.type == 'out',"Shipment should be Outgoing." + assert picking.picking_type_id == self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'picking_type_out', context=context),"Shipment should be Outgoing." assert picking.move_type == sale_order.picking_policy,"Delivery Method is not corresponding with delivery method of sale order." assert picking.partner_id.id == sale_order.partner_shipping_id.id,"Shipping Address is not correspond with sale order." assert picking.note == sale_order.note,"Note is not correspond with sale order." @@ -66,9 +60,8 @@ location_id = sale_order.warehouse_id.lot_stock_id.id output_id = sale_order.warehouse_id.lot_output_id.id for move in picking.move_lines: - order_line = move.sale_line_id - sale_order_date = self.date_to_datetime(cr, uid, sale_order.date_order, context) - date_planned = datetime.strptime(sale_order_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0) + order_line = move.procurement_id.sale_line_id + date_planned = datetime.strptime(sale_order.date_order, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0) date_planned = (date_planned - timedelta(days=sale_order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT) assert datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) == datetime.strptime(date_planned, DEFAULT_SERVER_DATETIME_FORMAT), "Excepted Date is not correspond with Planned Date." assert move.product_id.id == order_line.product_id.id,"Product is not correspond." @@ -84,13 +77,17 @@ - Now, I dispatch delivery order. - - !python {model: stock.partial.picking}: | + !python {model: stock.picking}: | order = self.pool.get('sale.order').browse(cr, uid, ref("sale.sale_order_6")) for pick in order.picking_ids: data = pick.force_assign() if data == True: - partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick.id]}) - self.do_partial(cr, uid, [partial_id]) + pick.do_partial(context=context) +- + I run the scheduler. +- + !python {model: procurement.order}: | + self.run_scheduler(cr, uid) - I check sale order to verify shipment. - @@ -123,7 +120,7 @@ assert invoice.reference == order.client_order_ref or order.name,"Reference is not correspond." assert invoice.partner_id.id == order.partner_invoice_id.id,"Customer does not correspond." assert invoice.currency_id.id == order.pricelist_id.currency_id.id, "Currency is not correspond." - assert invoice.comment == (order.note or ''),"Note is not correspond." + assert (invoice.comment or '') == (order.note or ''),"Note is not correspond." assert invoice.journal_id.id in journal_ids,"Sales Journal is not link on Invoice." assert invoice.payment_term.id == order.payment_term.id, "Payment term is not correspond." for so_line in order.order_line: diff --git a/addons/sale_stock/test/prepaid_order_policy.yml b/addons/sale_stock/test/prepaid_order_policy.yml index e35a9a82cf9..38652cb165d 100644 --- a/addons/sale_stock/test/prepaid_order_policy.yml +++ b/addons/sale_stock/test/prepaid_order_policy.yml @@ -1,12 +1,27 @@ +- + In order to test the Prepaid Order Policy, I create a product +- + !record {model: product.product, id: product_prepaid1}: + name: 'OpenERP Documentation Book' + list_price: 60.60 +- + Now i create a sale order that uses my new product +- + !record {model: sale.order, id: sale_order_prepaid1}: + partner_id: base.res_partner_2 + order_policy: prepaid + order_line: + - product_id: sale_stock.product_prepaid1 + product_uom_qty: 10 - Now I confirm the Quotation with "Pay before delivery" policy. - - !workflow {model: sale.order, action: order_confirm, ref: sale.sale_order_4} + !workflow {model: sale.order, action: order_confirm, ref: sale_order_prepaid1} - I check that delivery order should not created before invoice is paid. - !python {model: sale.order}: | - sale_order = self.browse(cr, uid, ref("sale.sale_order_4")) + sale_order = self.browse(cr, uid, ref("sale_order_prepaid1")) assert len(sale_order.picking_ids) == False, "Delivery order should not created before invoice." assert sale_order.invoice_ids, "Invoice should be created." diff --git a/addons/sale_stock_location/sale_stock_location_view.xml b/addons/sale_stock_location/sale_stock_location_view.xml index 3d59828424c..7fd0f1853b2 100644 --- a/addons/sale_stock_location/sale_stock_location_view.xml +++ b/addons/sale_stock_location/sale_stock_location_view.xml @@ -14,6 +14,7 @@ + sale.order.line.tree.sale.stock.location @@ -29,7 +30,10 @@ sale.order - + + + + diff --git a/addons/stock/stock.py b/addons/stock/stock.py index b09eee5ecb7..54f473f75c1 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -201,7 +201,6 @@ class stock_quant(osv.osv): else: self._quant_split(cr, uid, quant, qty, context=context) # FP Note: improve this using preferred locations - location_to = move.location_dest_id location_to = self.check_preferred_location(cr, uid, move, context=context) self.write(cr, uid, [quant.id], { 'location_id': location_to.id, @@ -239,7 +238,7 @@ class stock_quant(osv.osv): # Create a negative quant in the source location if it's an internal location # Reconcile a positive quant with a negative is possible # - def _quant_create(self, cr, uid, qty, move, context=None): + def _quant_create(self, cr, uid, qty, move, lot_id = False, context=None): # FP Note: TODO: compute the right price according to the move, with currency convert # QTY is normally already converted to main product's UoM price_unit = move.price_unit @@ -251,6 +250,7 @@ class stock_quant(osv.osv): 'history_ids': [(4, move.id)], 'in_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'company_id': move.company_id.id, + 'lot_id': lot_id, } negative_quant_id = False @@ -318,8 +318,22 @@ class stock_quant(osv.osv): def _price_update(self, cr, uid, quant, newprice, context=None): self.write(cr, uid, [quant.id], {'cost': newprice}, context=context) + def write(self, cr, uid, ids, vals, context=None): + #We want to trigger the move with nothing on reserved_quant_ids for the store of the remaining quantity + if 'reservation_id' in vals: + reservation_ids = self.browse(cr, uid, ids, context=context) + moves_to_warn = set() + for reser in reservation_ids: + if reser.reservation_id: + moves_to_warn.add(reser.reservation_id.id) + self.pool.get('stock.move').write(cr, uid, list(moves_to_warn), {'reserved_quant_ids': []}, context=context) + return super(stock_quant, self).write(cr, uid, ids, vals, context=context) + def quants_unreserve(self, cr, uid, move, context=None): - cr.execute('update stock_quant set reservation_id=NULL where reservation_id=%s', (move.id,)) + #cr.execute('update stock_quant set reservation_id=NULL where reservation_id=%s', (move.id,)) + #need write for related store of remaining qty + related_quants = self.search(cr, uid, [('reservation_id', '=', move.id)], context=context) + self.write(cr, uid, related_quants, {'reservation_id': False}, context=context) return True # @@ -610,16 +624,18 @@ class stock_picking(osv.osv): backorder_moves = picking.move_lines backorder_move_ids = [x.id for x in backorder_moves if x.state not in ('done','cancel')] - backorder_id = self.copy(cr, uid, picking.id, { - 'name': '/', - 'move_lines': [], - 'pack_operation_ids': [], - 'backorder_id': picking.id, - }) - back_order_name = self.browse(cr, uid, backorder_id, context=context).name - self.message_post(cr, uid, picking.id, body=_("Back order %s created.") % (back_order_name), context=context) - self.pool.get('stock.move').write(cr, uid, backorder_move_ids, {'picking_id': backorder_id}, context=context) - return backorder_id + if backorder_move_ids: + backorder_id = self.copy(cr, uid, picking.id, { + 'name': '/', + 'move_lines': [], + 'pack_operation_ids': [], + 'backorder_id': picking.id, + }) + back_order_name = self.browse(cr, uid, backorder_id, context=context).name + self.message_post(cr, uid, picking.id, body=_("Back order %s created.") % (back_order_name), context=context) + self.pool.get('stock.move').write(cr, uid, backorder_move_ids, {'picking_id': backorder_id}, context=context) + return backorder_id + return False def do_prepare_partial(self, cr, uid, picking_ids, context=None): context = context or {} @@ -649,7 +665,7 @@ class stock_picking(osv.osv): 'cost': move.product_id.standard_price, }, context=context) - def _do_partial_product_move(self, cr, uid, picking, product, qty, quant=False, context=None): + def _do_partial_product_move(self, cr, uid, picking, product, qty, pack_id = False, lot_id = False, quant=False, context=None): moves = [] stock_move_obj = self.pool.get('stock.move') for move in picking.move_lines: @@ -684,38 +700,132 @@ class stock_picking(osv.osv): moves.append(move) return moves + def do_rereserve(self, cr, uid, picking_ids, context=None): + ''' + Needed for parameter create + ''' + self.rereserve(cr, uid, picking_ids, context=context) + + + def rereserve(self, cr, uid, picking_ids, create=False, context=None): + """ + This will unreserve all products and reserve the quants from the operations + :return: tuple of dictionary with quantities of quant operation and product that can not be matched between ops and moves + and dictionary with remaining values on moves + + """ + quant_obj = self.pool.get("stock.quant") + move_obj = self.pool.get("stock.move") + op_obj = self.pool.get("stock.pack.operation") + res = {} + res2 = {} #what is left from moves + for picking in self.browse(cr, uid, picking_ids, context=context): + # unreserve everything and initialize res2 + for move in picking.move_lines: + quant_obj.quants_unreserve(cr, uid, move, context=context) + res2[move.id] = move.product_qty + for ops in picking.pack_operation_ids: + #Find moves that correspond + if ops.product_id: + #TODO: Should have order such that things with lots and packings are searched first + move_ids = move_obj.search(cr, uid, [('picking_id','=',picking.id), ('product_id', '=', ops.product_id.id), ('remaining_qty', '>', 0.0)], context=context) + qty_to_do = ops.product_qty + while qty_to_do > 0 and move_ids: + move = move_obj.browse(cr, uid, move_ids.pop(), context=context) + if move.remaining_qty > qty_to_do: + qty = qty_to_do + qty_to_do = 0 + else: + qty = move.remaining_qty + qty_to_do -= move.remaining_qty + + if create and move.location_id != 'internal': + # Create quants + vals = { + 'product_id': move.product_id.id, + 'location_id': move.location_id.id, + 'qty': qty, + #'cost': price_unit, + 'history_ids': [(4, move.id)], + 'in_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'company_id': move.company_id.id, + 'lot_id': ops.lot_id and ops.lot_id.id or False, + 'reservation_id': move.id, #Reserve at once + 'package_id': ops.result_package_id and ops.result_package_id.id or False, + } + quant_id = quant_obj.create(cr, uid, vals, context=context) + else: + #Quants get + domain = [('reservation_id', '=', False)] + prefered_order = op_obj._get_preferred_order(cr, uid, ops.id, context=context) + quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_order=prefered_order, context=context) + quant_obj.quants_reserve(cr, uid, quants, move, context=context) + res2[move.id] -= qty + res[ops.id] = {} + res[ops.id][ops.product_id.id] = qty_to_do + elif ops.package_id: + quants = ops.package_id.quant_ids #_get_package_lines + for quant in quants: + move_ids = move_obj.search(cr, uid, [('picking_id', '=', picking.id), ('product_id', '=', quant.product_id.id), ('remaining_qty', '>', 0.0)]) + qty_to_do = quant.qty + while qty_to_do > 0 and move_ids: + move = move_obj.browse(cr, uid, move_ids.pop(), context=context) + if move.remaining_qty > qty_to_do: + qty = qty_to_do + qty_to_do = 0.0 + else: + qty = move.remaining_qty + qty_to_do -= move.remaining_qty + quant_obj.quants_reserve(cr, uid, [(quant.id, qty)], move, context=context) + res2[move.id] -= qty + res.setdefault(ops.id, {}).setdefault(quant.product_id.id, 0.0) + res[ops.id][quant.product_id.id] += qty_to_do + return (res, res2) + + def do_partial(self, cr, uid, picking_ids, context=None): """ If no pack operation, we close the whole move Otherwise, do the pack operations """ - #TODO: this variable should be in argument - quant_obj = self.pool.get('stock.quant') - quant_package_obj = self.pool.get('stock.quant.package') + stock_move_obj = self.pool.get('stock.move') for picking in self.browse(cr, uid, picking_ids, context=context): if not picking.pack_operation_ids: self.action_done(cr, uid, [picking.id], context=context) continue - for op in picking.pack_operation_ids: - #TODO: op.package_id can not work as quants_get is not defined on quant package => gives traceback - if op.package_id: - for quant in quant_package_obj.quants_get(cr, uid, op.package_id, context=context): - self._do_partial_product_move(cr, uid, picking, quant.product_id, quant.qty, quant, context=context) - op.package_id.write(cr, uid, { - 'parent_id': op.result_package_id.id - }, context=context) - elif op.product_id: - moves = self._do_partial_product_move(cr, uid, picking, op.product_id, op.product_qty, op.quant_id, context=context) - - quants = [] - for m in moves: - for quant in m.quant_ids: - quants.append(quant.id) - quant_obj.write(cr, uid, quants, { - 'package_id': op.result_package_id.id - - }, context=context) + else: + #First thing that needs to happen is rereserving the quants + res = self.rereserve(cr, uid, [picking.id], create = True, context = context) #This time, quants need to be created + orig_moves = picking.move_lines + #Add moves that operations need extra + extra_moves = [] + for ops in res[0].keys(): + for prod in res[0][ops].keys(): + product = self.pool.get('product.product').browse(cr, uid, prod, context=context) + qty = res[0][ops][prod] + if qty > 0: + #TODO: Maybe should try to reserve quants? + quant = False + move_id = stock_move_obj.create(cr, uid, { + 'name': product.name, + 'product_id': product.id, + 'product_uom_qty': qty, + 'product_uom': product.uom_id.id, + 'location_id': picking.location_id.id, + 'location_dest_id': picking.location_dest_id.id, + 'picking_id': picking.id, + 'reserved_quant_ids': quant and [(4, quant.id)] or [], + 'picking_type_id': picking.picking_type_id.id + }, context=context) + extra_moves.append(move_id) + res2 = res[1] + for move in res2.keys(): + if res2[move] > 0: + mov = stock_move_obj.browse(cr, uid, move, context=context) + newmove_id = stock_move_obj.split(cr, uid, mov, res2[move], context=context) + stock_move_obj.action_done(cr, uid, extra_moves + [x.id for x in orig_moves], context=context) + picking.refresh() self._create_backorder(cr, uid, picking, context=context) return True @@ -884,20 +994,29 @@ class stock_move(osv.osv): res[m.id] = uom_obj._compute_qty_obj(cr, uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id) return res +# def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None): +# #TODO: this function assumes that there aren't several stock move in the same picking with the same product. what should we do in that case? +# #TODO take care of the quant on stock moves too +# res = dict.fromkeys(ids, False) +# for move in self.browse(cr, uid, ids, context=context): +# res[move.id] = move.product_qty +# if move.picking_id: +# for op in move.picking_id.pack_operation_ids: +# if op.product_id == move.product_id or (op.quant_id and op.quant_id.product_id == move.product_id): +# res[move.id] -= op.product_qty +# if op.package_id: +# #find the product qty recursively +# res[move.id] -= self.pool.get('stock.quant.package')._get_product_total_qty(cr, uid, op.package_id, move.product_id.id, context=context) +# return res + def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None): - #TODO: this function assumes that there aren't several stock move in the same picking with the same product. what should we do in that case? - #TODO take care of the quant on stock moves too - res = dict.fromkeys(ids, False) + res = {} for move in self.browse(cr, uid, ids, context=context): - res[move.id] = move.product_qty - if move.picking_id: - for op in move.picking_id.pack_operation_ids: - if op.product_id == move.product_id or (op.quant_id and op.quant_id.product_id == move.product_id): - res[move.id] -= op.product_qty - if op.package_id: - #find the product qty recursively - res[move.id] -= self.pool.get('stock.quant.package')._get_product_total_qty(cr, uid, op.package_id, move.product_id.id, context=context) + res[move.id] = move.product_uom_qty + for quant in move.reserved_quant_ids: + res[move.id] -= quant.qty return res + def _get_lot_ids(self, cr, uid, ids, field_name, args, context=None): res = dict.fromkeys(ids, False) @@ -923,6 +1042,14 @@ class stock_move(osv.osv): res[move.id] = min(move.product_qty, availability) return res + + def _get_move(self, cr, uid, ids, context=None): + res = set() + for quant in self.browse(cr, uid, ids, context=context): + if quant.reservation_id: + res.add(quant.reservation_id.id) + return list(res) + _columns = { 'name': fields.char('Description', required=True, select=True), 'priority': fields.selection([('0', 'Not urgent'), ('1', 'Urgent')], 'Priority'), @@ -988,7 +1115,10 @@ class stock_move(osv.osv): 'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Quants'), 'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'), - 'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]}), + 'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity', + digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]}, + store = {'stock.move': (lambda self, cr, uid, ids, c={}: ids , ['product_qty', 'product_uom', 'reserved_quant_ids'], 10), + 'stock.quant': (_get_move, ['reservation_id'], 10)}), 'procurement_id': fields.many2one('procurement.order', 'Procurement'), 'group_id': fields.related('procurement_id', 'group_id', type='many2one', relation="procurement.group", string='Procurement Group'), 'rule_id': fields.many2one('procurement.rule', 'Procurement Rule'), @@ -1215,7 +1345,6 @@ class stock_move(osv.osv): 'state': 'confirmed', 'group_id': move.group_id and move.group_id.id or False, 'picking_type_id': move.picking_type_id and move.picking_type_id.id or False, - 'move_lines': [(6, 0, [move.id])] } pick = pick_obj.create(cr, uid, values, context=context) move.write({'picking_id': pick}) @@ -1291,6 +1420,7 @@ class stock_move(osv.osv): for m2 in move.move_orig_ids: for q in m2.quant_ids: dp.append(str(q.id)) + qty -= q.qty domain = ['|', ('reservation_id', '=', False), ('reservation_id', '=', move.id)] quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_order = dp and ('id not in ('+','.join(dp)+')') or False, context=context) #Will only reserve physical quants, no negative @@ -1336,7 +1466,6 @@ class stock_move(osv.osv): """ context = context or {} quant_obj = self.pool.get("stock.quant") - picking_obj = self.pool.get('stock.picking') todo = [move.id for move in self.browse(cr, uid, ids, context=context) if move.state == "draft"] if todo: @@ -1353,7 +1482,13 @@ class stock_move(osv.osv): # quant_obj.quants_move(cr, uid, quants, move, location_dest_id, context=context) # should replace the above 2 lines domain = ['|', ('reservation_id', '=', False), ('reservation_id', '=', move.id)] - quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_order = 'reservation_id<>'+str(move.id), context=context) + prefered_order = 'reservation_id<>' + str(move.id) +# if lot_id: +# prefered_order = 'lot_id<>' + lot_id + ", " + prefered_order +# if pack_id: +# prefered_order = 'pack_id<>' + pack_id + ", " + prefered_order + quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_order = prefered_order, context=context) + #Will move all quants_get and as such create negative quants quant_obj.quants_move(cr, uid, quants, move, context=context) quant_obj.quants_unreserve(cr, uid, move, context=context) @@ -1510,7 +1645,7 @@ class stock_move(osv.osv): self.write(cr, uid, [move.id], { 'product_uom_qty': move.product_uom_qty - uom_qty, 'product_uos_qty': move.product_uos_qty - uos_qty, - 'reserved_quant_ids': [(6,0,[])] +# 'reserved_quant_ids': [(6,0,[])] SHOULD NOT CHANGE as it has been reserved already }, context=context) return new_move @@ -1945,6 +2080,19 @@ class stock_pack_operation(osv.osv): 'date': fields.date.context_today, } + def _get_preferred_order(self, cr, uid, id, context=None): + ops = self.browse(cr, uid, id, context=context) + res = "" + if ops.package_id: + res += "package_id <> " + str(ops.package_id.id) + if ops.lot_id: + if res: + res += ", lot_id <> " + str(ops.lot_id.id) + else: + res += "lot_id <> " + str(ops.lot_id.id) + return res + + #TODO: this function can be refactored def _search_and_increment(self, cr, uid, picking_id, key, context=None): '''Search for an operation on an existing key in a picking, if it exists increment the qty (+1) otherwise create it diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 575712ad771..af3fa1153f4 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -583,6 +583,7 @@