From 8f1192e0b04a3868d0cbdb6525bd244615c5d706 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 24 Feb 2014 10:41:24 +0100 Subject: [PATCH 01/22] [ADD] Add meagtest bzr revid: jco@openerp.com-20140224094124-2vmukbsy006d8j4y --- addons/stock_megatest/__init__.py | 23 ++ addons/stock_megatest/__openerp__.py | 47 +++ addons/stock_megatest/test/megatest.yml | 286 ++++++++++++++++++ addons/stock_megatest/test/megatestmtobuy.yml | 203 +++++++++++++ 4 files changed, 559 insertions(+) create mode 100644 addons/stock_megatest/__init__.py create mode 100644 addons/stock_megatest/__openerp__.py create mode 100644 addons/stock_megatest/test/megatest.yml create mode 100644 addons/stock_megatest/test/megatestmtobuy.yml diff --git a/addons/stock_megatest/__init__.py b/addons/stock_megatest/__init__.py new file mode 100644 index 00000000000..1529644d61e --- /dev/null +++ b/addons/stock_megatest/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/stock_megatest/__openerp__.py b/addons/stock_megatest/__openerp__.py new file mode 100644 index 00000000000..3f167f72011 --- /dev/null +++ b/addons/stock_megatest/__openerp__.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + 'name': 'Dropshipping Module', + 'version': '1.0', + 'category': 'Hidden', + 'summary': 'Dropshipping', + 'description': """ +Manage sales quotations and stock locations +========================================== + +This adds the route to make dropshipping sales orders in which the product sold are directly transfered from the reseller to the customer (direct delivery) without creating any internal document for the transfer. + +""", + 'author': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'images': [], + 'depends': ['stock_dropshipping'], + 'init_xml': [], + 'data': [], + 'demo_xml': [], + 'test': [ + 'test/megatestmtobuy.yml' + ], + 'installable': True, + 'auto_install': False, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/stock_megatest/test/megatest.yml b/addons/stock_megatest/test/megatest.yml new file mode 100644 index 00000000000..4aeb0ee2c43 --- /dev/null +++ b/addons/stock_megatest/test/megatest.yml @@ -0,0 +1,286 @@ +- + I first create a warehouse with pick-pack-ship and reception in 2 steps +- + !record {model: stock.warehouse, id: mwh_pps}: + name: Mega WareHouse PickPackShip + code: mwhpps + reception_steps: 'two_steps' + delivery_steps: 'pick_pack_ship' +- + Next I create a new product in this warehouse +- + !record {model: product.product, id: mprod_mto}: + name: "My Product" + type: product + uom_id: product.product_uom_unit + uom_po_id: product.product_uom_unit + seller_ids: + - delay: 1 + name: base.res_partner_2 + min_qty: 2.0 + qty: 10.0 +- + I will create another product in this warehouse +- + !record {model: product.product, id: mprod_mto2}: + name: "My Product 2" + type: product + uom_id: product.product_uom_unit + uom_po_id: product.product_uom_unit + seller_ids: + - delay: 2 + name: base.res_partner_2 + min_qty: 2.0 + qty: 10.0 +- + I will create another product in this warehouse +- + !record {model: product.product, id: mprod_mto3}: + name: "My Product 3" + type: product + uom_id: product.product_uom_unit + uom_po_id: product.product_uom_unit + seller_ids: + - delay: 4 + name: base.res_partner_2 + min_qty: 2.0 + qty: 10.0 +- + I will create another product in this warehouse +- + !record {model: product.product, id: mprod_mts}: + name: "My Product MTS" + type: product + uom_id: product.product_uom_unit + uom_po_id: product.product_uom_unit + seller_ids: + - delay: 2 + name: base.res_partner_2 + min_qty: 2.0 + qty: 10.0 +- + Set routes on product to be MTO and Buy +- + !python {model: product.product}: | + route_warehouse0_buy = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock.warehouse0')).buy_pull_id.route_id.id + route_warehouse0_mto = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock.warehouse0')).mto_pull_id.route_id.id + self.write(cr, uid, [ref('mprod_mto'), ref('mprod_mto2'), ref('mprod_mto3')], { 'route_ids': [(6, 0, [route_warehouse0_mto,route_warehouse0_buy])]}, context=context) +- + Create a sales order with 36 lines. +- + !record {model: sale.order, id: sale_order_product_mto2}: + partner_id: base.res_partner_3 + note: Create Sales order + warehouse_id: mwh_pps + order_line: + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 100.00 + - product_id: mprod_mto + product_uom_qty: 67.00 + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto2 + product_uom_qty: 100.00 + - product_id: mprod_mto2 + product_uom_qty: 99.00 + - product_id: mprod_mto3 + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto2 + product_uom_qty: 100.00 + - product_id: mprod_mto2 + product_uom_qty: 67.00 + - product_id: mprod_mto3 + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mts + product_uom_qty: 100.00 + - product_id: mprod_mts + product_uom_qty: 67.00 + - product_id: mprod_mts + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 100.00 + - product_id: mprod_mto + product_uom_qty: 67.00 + - product_id: mprod_mto2 + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto3 + product_uom_qty: 100.00 + - product_id: mprod_mto + product_uom_qty: 67.00 + - product_id: mprod_mts + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto2 + product_uom_qty: 100.00 + - product_id: mprod_mto + product_uom_qty: 67.00 + - product_id: mprod_mts + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto2 + product_uom_qty: 100.00 + - product_id: mprod_mto + product_uom_qty: 67.00 + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto3 + product_uom_qty: 100.00 + - product_id: mprod_mto2 + product_uom_qty: 67.00 + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto3 + product_uom_qty: 100.00 + - product_id: mprod_mto2 + product_uom_qty: 67.00 + - product_id: mprod_mto2 + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto2 + product_uom_qty: 100.00 + - product_id: mprod_mto3 + product_uom_qty: 67.00 + - product_id: mprod_mto2 + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 100.00 + - product_id: mprod_mto2 + product_uom_qty: 67.00 +- + Confirm the sale order +- + !workflow {model: sale.order, action: order_confirm, ref: sale_order_product_mto2} +- + Check sales order is confirmed +- + !python {model: sale.order}: + print self.browse(cr, uid, ref('sale_order_product_mto2')).state +- + Create a sales order with 36 lines. +- + !record {model: sale.order, id: sale_order_product_mto3}: + partner_id: base.res_partner_3 + note: Create Sales order + warehouse_id: mwh_pps + order_line: + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping + - product_id: mprod_mto + product_uom_qty: 500.00 + route_id: stock_dropshipping.route_drop_shipping +- + Confirm the sale order +- + !workflow {model: sale.order, action: order_confirm, ref: sale_order_product_mto3} +- + Check sales order is confirmed +- + !python {model: sale.order}: + print self.browse(cr, uid, ref('sale_order_product_mto3')).state +- + Confirm all purchase orders that are in draft related +- + !python {model: purchase.order}: | + po_ids = self.search(cr, uid, [('partner_id', '=', ref('base.res_partner_2')), ('state', '=', 'draft')]) + self.signal_purchase_confirm(cr, uid, po_ids) +- + Doing all incoming shipments +- + !python {model: purchase.order.line}: | + import time + beforebefore = time.time() + print time.time() + prod_list = [ref('mprod_mto'), ref('mprod_mto2'), ref('mprod_mto3'), ref('mprod_mts')] + po_line_ids = self.search(cr, uid, [('product_id', 'in', prod_list)]) + # Search all related moves + move_obj = self.pool.get("stock.move") + related_moves = move_obj.search(cr, uid, [('purchase_line_id', 'in', po_line_ids)]) + moves = move_obj.browse(cr, uid, related_moves) + related_pickings = [x.picking_id for x in moves] + pickings = list(set(related_pickings)) + op_obj = self.pool.get('stock.pack.operation') + pack_obj = self.pool.get('stock.quant.package') + pick_obj = self.pool.get('stock.picking') + # Process those pickings and put in boxes of 20 pieces + for pick in pickings: + pick_obj.do_prepare_partial(cr, uid, [pick.id]) + #for ops in pick.pack_operation_ids: + # pick + # while ops.product_qty > 100: + # op_obj.write(cr, uid, [ops.id], {'product_qty': ops.product_qty - 100}) + # pack_id = pack_obj.create(cr, uid, {}, context=context) + # op_obj.copy(cr, uid, ops.id, {'product_qty': 100, 'result_package_id': pack_id}) + # print "copy" + # ops.refresh() + print "moves", len(pick.move_lines), "pack_ops", len(pick.pack_operation_ids), time.time() + pick.refresh() + pick_obj.do_transfer(cr, uid, [pick.id]) + print "Done transfer", time.time() + print "totaltime", time.time() - beforebefore + #Search all dests of moves + move_dest_ids = [x.move_dest_id for x in moves] + move_dest_ids = list(set(move_dest_ids)) + related_dest_pickings = [x.picking_id for x in move_dest_ids if x.picking_id] + picks = list(set(related_dest_pickings)) + print "picks", picks + for pick in picks: + pick_obj.do_prepare_partial(cr, uid, [pick.id]) + pick_obj.do_transfer(cr, uid, [pick.id]) + print "after second transfer", time.time() + move_dest_ids = [x.move_dest_id.move_dest_id for x in moves if x.move_dest_id] + move_dest_ids = list(set(move_dest_ids)) + related_dest_pickings = [x.picking_id for x in move_dest_ids if x.picking_id] + picks = list(set(related_dest_pickings)) + print "picks", picks + for pick in picks: + pick_obj.do_prepare_partial(cr, uid, [pick.id]) + pick_obj.do_transfer(cr, uid, [pick.id]) + print "after third transfer", time.time() \ No newline at end of file diff --git a/addons/stock_megatest/test/megatestmtobuy.yml b/addons/stock_megatest/test/megatestmtobuy.yml new file mode 100644 index 00000000000..e5129e22c15 --- /dev/null +++ b/addons/stock_megatest/test/megatestmtobuy.yml @@ -0,0 +1,203 @@ +- + I first create a warehouse with pick-pack-ship and reception in 2 steps +- + !record {model: stock.warehouse, id: mwh_pps}: + name: Mega WareHouse Simple + code: mwhpps +- + Next I create a new product in this warehouse +- + !record {model: product.product, id: mprod_mto}: + name: "My Product" + type: product + uom_id: product.product_uom_unit + uom_po_id: product.product_uom_unit + seller_ids: + - delay: 1 + name: base.res_partner_2 + min_qty: 2.0 + qty: 10.0 +- + I will create another product in this warehouse +- + !record {model: product.product, id: mprod_mto2}: + name: "My Product 2" + type: product + uom_id: product.product_uom_unit + uom_po_id: product.product_uom_unit + seller_ids: + - delay: 2 + name: base.res_partner_2 + min_qty: 2.0 + qty: 10.0 +- + I will create another product in this warehouse +- + !record {model: product.product, id: mprod_mto3}: + name: "My Product 3" + type: product + uom_id: product.product_uom_unit + uom_po_id: product.product_uom_unit + seller_ids: + - delay: 4 + name: base.res_partner_2 + min_qty: 2.0 + qty: 10.0 +- + I will create another product in this warehouse +- + !record {model: product.product, id: mprod_mts}: + name: "My Product MTS" + type: product + uom_id: product.product_uom_unit + uom_po_id: product.product_uom_unit + seller_ids: + - delay: 2 + name: base.res_partner_2 + min_qty: 2.0 + qty: 10.0 +- + Set routes on product to be MTO and Buy +- + !python {model: product.product}: | + route_warehouse0_buy = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock.warehouse0')).buy_pull_id.route_id.id + route_warehouse0_mto = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock.warehouse0')).mto_pull_id.route_id.id + self.write(cr, uid, [ref('mprod_mto'), ref('mprod_mto2'), ref('mprod_mto3')], { 'route_ids': [(6, 0, [route_warehouse0_mto,route_warehouse0_buy])]}, context=context) +- + Create a sales order with 36 lines. +- + !record {model: sale.order, id: sale_order_product_mto2}: + partner_id: base.res_partner_3 + note: Create Sales order + warehouse_id: mwh_pps + order_line: + - product_id: mprod_mto + product_uom_qty: 500.00 + - product_id: mprod_mto + product_uom_qty: 100.00 + - product_id: mprod_mto + product_uom_qty: 67.00 + - product_id: mprod_mto + product_uom_qty: 500.00 + - product_id: mprod_mto2 + product_uom_qty: 100.00 + - product_id: mprod_mto2 + product_uom_qty: 99.00 + - product_id: mprod_mto3 + product_uom_qty: 500.00 + - product_id: mprod_mto2 + product_uom_qty: 100.00 + - product_id: mprod_mto2 + product_uom_qty: 67.00 + - product_id: mprod_mto3 + product_uom_qty: 500.00 + - product_id: mprod_mts + product_uom_qty: 100.00 + - product_id: mprod_mts + product_uom_qty: 67.00 + - product_id: mprod_mts + product_uom_qty: 500.00 + - product_id: mprod_mto + product_uom_qty: 100.00 + - product_id: mprod_mto + product_uom_qty: 67.00 + - product_id: mprod_mto2 + product_uom_qty: 500.00 + - product_id: mprod_mto3 + product_uom_qty: 100.00 + - product_id: mprod_mto + product_uom_qty: 67.00 + - product_id: mprod_mts + product_uom_qty: 500.00 + - product_id: mprod_mto2 + product_uom_qty: 100.00 + - product_id: mprod_mto + product_uom_qty: 67.00 + - product_id: mprod_mts + product_uom_qty: 500.00 + - product_id: mprod_mto2 + product_uom_qty: 100.00 + - product_id: mprod_mto + product_uom_qty: 67.00 + - product_id: mprod_mto + product_uom_qty: 500.00 + - product_id: mprod_mto3 + product_uom_qty: 100.00 + - product_id: mprod_mto2 + product_uom_qty: 67.00 + - product_id: mprod_mto + product_uom_qty: 500.00 + - product_id: mprod_mto3 + product_uom_qty: 100.00 + - product_id: mprod_mto2 + product_uom_qty: 67.00 + - product_id: mprod_mto2 + product_uom_qty: 500.00 + - product_id: mprod_mto2 + product_uom_qty: 100.00 + - product_id: mprod_mto3 + product_uom_qty: 67.00 + - product_id: mprod_mto2 + product_uom_qty: 500.00 + - product_id: mprod_mto + product_uom_qty: 100.00 + - product_id: mprod_mto2 + product_uom_qty: 67.00 +- + Confirm the sale order +- + !workflow {model: sale.order, action: order_confirm, ref: sale_order_product_mto2} +- + Check sales order is confirmed +- + !python {model: sale.order}: + print self.browse(cr, uid, ref('sale_order_product_mto2')).state +- + Confirm all purchase orders that are in draft related +- + !python {model: purchase.order}: | + po_ids = self.search(cr, uid, [('partner_id', '=', ref('base.res_partner_2')), ('state', '=', 'draft')]) + self.signal_purchase_confirm(cr, uid, po_ids) +- + Doing all incoming shipments +- + !python {model: purchase.order.line}: | + import time + beforebefore = time.time() + print time.time() + prod_list = [ref('mprod_mto'), ref('mprod_mto2'), ref('mprod_mto3'), ref('mprod_mts')] + po_line_ids = self.search(cr, uid, [('product_id', 'in', prod_list)]) + # Search all related moves + move_obj = self.pool.get("stock.move") + related_moves = move_obj.search(cr, uid, [('purchase_line_id', 'in', po_line_ids)]) + moves = move_obj.browse(cr, uid, related_moves) + related_pickings = [x.picking_id for x in moves] + pickings = list(set(related_pickings)) + op_obj = self.pool.get('stock.pack.operation') + pack_obj = self.pool.get('stock.quant.package') + pick_obj = self.pool.get('stock.picking') + # Process those pickings and put in boxes of 20 pieces + for pick in pickings: + #pick_obj.do_prepare_partial(cr, uid, [pick.id]) + #for ops in pick.pack_operation_ids: + # pick + # while ops.product_qty > 100: + # op_obj.write(cr, uid, [ops.id], {'product_qty': ops.product_qty - 100}) + # pack_id = pack_obj.create(cr, uid, {}, context=context) + # op_obj.copy(cr, uid, ops.id, {'product_qty': 100, 'result_package_id': pack_id}) + # print "copy" + # ops.refresh() + print "moves", len(pick.move_lines), "pack_ops", len(pick.pack_operation_ids), time.time() + pick.refresh() + pick_obj.do_transfer(cr, uid, [pick.id]) + print "Done transfer", time.time() + print "totaltime", time.time() - beforebefore + #Search all dests of moves + move_dest_ids = [x.move_dest_id for x in moves] + move_dest_ids = list(set(move_dest_ids)) + related_dest_pickings = [x.picking_id for x in move_dest_ids if x.picking_id] + picks = list(set(related_dest_pickings)) + for pick in picks: + #pick_obj.do_prepare_partial(cr, uid, [pick.id]) + pick_obj.do_transfer(cr, uid, [pick.id]) + print "after second transfer", time.time() \ No newline at end of file From 4b57fc646541c253c4fd2cd200b367836c191966 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 24 Feb 2014 12:04:13 +0100 Subject: [PATCH 02/22] [IMP] Check if procurements are done before calling get shipped bzr revid: jco@openerp.com-20140224110413-o4wvbnch90yxxt2i --- addons/sale_stock/sale_stock.py | 3 +-- addons/stock/stock.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index ed89fa49a92..dbe64465047 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -67,7 +67,7 @@ class sale_order(osv.osv): def _get_orders_procurements(self, cr, uid, ids, context=None): res = set() for proc in self.pool.get('procurement.order').browse(cr, uid, ids, context=context): - if proc.sale_line_id: + if proc.state =='done' and proc.sale_line_id: res.add(proc.sale_line_id.order_id.id) return list(res) @@ -102,7 +102,6 @@ class sale_order(osv.osv): ], 'Create Invoice', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, 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."""), 'shipped': fields.function(_get_shipped, string='Delivered', type='boolean', store={ - 'stock.move': (_get_orders, ['state'], 10), 'procurement.order': (_get_orders_procurements, ['state'], 10) }), 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True), diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 12f3217f6e2..46b4776492b 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -32,7 +32,6 @@ import openerp.addons.decimal_precision as dp import logging _logger = logging.getLogger(__name__) - #---------------------------------------------------------- # Incoterms #---------------------------------------------------------- @@ -728,7 +727,7 @@ class stock_picking(osv.osv): 'note': fields.text('Notes', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}), 'move_type': fields.selection([('direct', 'Partial'), ('one', 'All at once')], 'Delivery Method', required=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="It specifies goods to be deliver partially or all at once"), 'state': fields.function(_state_get, type="selection", store={ - 'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_type', 'move_lines'], 20), + 'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_type'], 20), 'stock.move': (_get_pickings, ['state', 'picking_id'], 20), 'stock.quant': (_get_pickings_from_quant, ['reservation_id'], 20)}, selection=[ ('draft', 'Draft'), From 18b02ee71e6a3067b701525aea006eea6ea70672 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 24 Feb 2014 12:24:37 +0100 Subject: [PATCH 03/22] [IMP] Change store = True of product_qty of move bzr revid: jco@openerp.com-20140224112437-if4nkqaj9balf580 --- addons/stock/stock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 46b4776492b..57fc427dc3e 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1368,7 +1368,7 @@ class stock_move(osv.osv): 'date_expected': fields.datetime('Expected Date', states={'done': [('readonly', True)]}, required=True, select=True, help="Scheduled date for the processing of this move"), 'product_id': fields.many2one('product.product', 'Product', required=True, select=True, domain=[('type', '<>', 'service')], states={'done': [('readonly', True)]}), # TODO: improve store to add dependency on product UoM - 'product_qty': fields.function(_quantity_normalize, type='float', store=True, string='Quantity', + 'product_qty': fields.function(_quantity_normalize, type='float', store={'stock.move': (lambda self, cr, uid, ids, ctx: ids, ['product_uom_qty', 'product_uom'], 20)}, string='Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), help='Quantity in the default UoM of the product'), 'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), From ed978bb90547aadb74efa53870ec05fb7f43b34e Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 24 Feb 2014 14:41:26 +0100 Subject: [PATCH 04/22] [IMP] Only check pickings of purchases when state goes to done and optimize query bzr revid: jco@openerp.com-20140224134126-jmk38b3gxtvbh1ru --- addons/purchase/purchase.py | 20 ++++++++++++++------ addons/purchase/stock.py | 2 +- addons/stock/stock.py | 3 ++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 0626f785241..56eb12147c7 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -31,6 +31,7 @@ import openerp.addons.decimal_precision as dp from openerp.osv.orm import browse_record, browse_null from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP + class purchase_order(osv.osv): def _amount_all(self, cr, uid, ids, field_name, arg, context=None): @@ -155,12 +156,19 @@ class purchase_order(osv.osv): def _get_picking_ids(self, cr, uid, ids, field_names, args, context=None): res = {} - for purchase_id in ids: - picking_ids = set() - move_ids = self.pool.get('stock.move').search(cr, uid, [('purchase_line_id.order_id', '=', purchase_id)], context=context) - for move in self.pool.get('stock.move').browse(cr, uid, move_ids, context=context): - picking_ids.add(move.picking_id.id) - res[purchase_id] = list(picking_ids) + query = """ + SELECT picking_id, po.id FROM stock_picking p, stock_move m, purchase_order_line pol, purchase_order po + WHERE po.id in %s and po.id = pol.order_id and pol.id = m.purchase_line_id and m.picking_id = p.id + GROUP BY picking_id, po.id + + """ + cr.execute(query, (tuple(ids), )) + picks = cr.fetchall() + for pick in picks: + if not res.get(pick[1]): + res[pick[1]] = [pick[0]] + else: + res[pick[1]].append(pick[0]) return res STATE_SELECTION = [ diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index 5989584793d..dde18dca2c3 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -35,7 +35,7 @@ class stock_move(osv.osv): ids = [ids] res = super(stock_move, self).write(cr, uid, ids, vals, context=context) from openerp import workflow - if 'state' in vals: + if 'state' in vals and vals['state'] in ['done', 'cancel']: for move in self.browse(cr, uid, ids, context=context): if move.purchase_line_id and move.purchase_line_id.order_id: order_id = move.purchase_line_id.order_id.id diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 57fc427dc3e..041417797be 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1488,11 +1488,12 @@ class stock_move(osv.osv): if move.product_id.uom_id.category_id.id != move.product_uom.category_id.id: return False return True - + _constraints = [ (_check_uom, 'You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category.', ['product_uom'])] + def copy_data(self, cr, uid, id, default=None, context=None): if default is None: From 3bb1e2d64d7dd6755597e2f3158c6c295f75fce4 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 24 Feb 2014 15:48:35 +0100 Subject: [PATCH 05/22] [IMP] Check purchase order line in reception to invoice bzr revid: jco@openerp.com-20140224144835-0zvnrjv2dftf6vhg --- addons/purchase/stock.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index dde18dca2c3..177a4554e4a 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -71,7 +71,7 @@ class stock_picking(osv.osv): def _get_picking_to_recompute(self, cr, uid, ids, context=None): picking_ids = set() for move in self.pool.get('stock.move').browse(cr, uid, ids, context=context): - if move.picking_id: + if move.picking_id and move.purchase_line_id: picking_ids.add(move.picking_id.id) return list(picking_ids) @@ -79,7 +79,6 @@ class stock_picking(osv.osv): 'reception_to_invoice': fields.function(_get_to_invoice, type='boolean', string='Invoiceable on incoming shipment?', help='Does the picking contains some moves related to a purchase order invoiceable on the reception?', store={ - 'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 10), 'stock.move': (_get_picking_to_recompute, ['purchase_line_id', 'picking_id'], 10), }), } From 07276fbed6d4df583e32516590285a0eaf30d14e Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 24 Feb 2014 16:10:30 +0100 Subject: [PATCH 06/22] [IMP] Remove check location of quant bzr revid: jco@openerp.com-20140224151030-f5mn5uocjvpa47hh --- addons/stock/stock.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 041417797be..c388c6ff15d 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -369,7 +369,8 @@ class stock_quant(osv.osv): #if the quant we are moving had been split and was inside a package, it means we unpacked it if new_quant and new_quant.package_id: vals['package_id'] = False - self.write(cr, SUPERUSER_ID, [quant.id], vals, context=context) + if self._check_location(cr, uid, location_to, context): + self.write(cr, SUPERUSER_ID, [quant.id], vals, context=context) quant.refresh() return new_quant @@ -589,16 +590,11 @@ class stock_quant(osv.osv): ''' Return the company owning the location if any ''' return location and (location.usage == 'internal') and location.company_id or False - def _check_location(self, cr, uid, ids, context=None): - for record in self.browse(cr, uid, ids, context=context): - if record.location_id.usage == 'view': - raise osv.except_osv(_('Error'), _('You cannot move product %s to a location of type view %s.') % (record.product_id.name, record.location_id.name)) + def _check_location(self, cr, uid, location, context=None): + if location.usage == 'view': + raise osv.except_osv(_('Error'), _('You cannot move to a location of type view %s.') % (location.name)) return True - _constraints = [ - (_check_location, 'You cannot move products to a location of the type view.', ['location_id']) - ] - #---------------------------------------------------------- # Stock Picking From 23d5154e33289814801066e8239f313a28184383 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 24 Feb 2014 16:29:56 +0100 Subject: [PATCH 07/22] [IMP] Check UoM category move of move bzr revid: jco@openerp.com-20140224152956-kvwuo0mozpwnwwks --- addons/stock/stock.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index c388c6ff15d..afedc72b264 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1479,17 +1479,6 @@ class stock_move(osv.osv): 'propagate': True, } - def _check_uom(self, cr, uid, ids, context=None): - for move in self.browse(cr, uid, ids, context=context): - if move.product_id.uom_id.category_id.id != move.product_uom.category_id.id: - return False - return True - - _constraints = [ - (_check_uom, - 'You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category.', - ['product_uom'])] - def copy_data(self, cr, uid, id, default=None, context=None): if default is None: @@ -1607,6 +1596,17 @@ class stock_move(osv.osv): # Check that we do not modify a stock.move which is done frozen_fields = set(['product_qty', 'product_uom', 'product_uos_qty', 'product_uos', 'location_id', 'location_dest_id', 'product_id']) for move in self.browse(cr, uid, ids, context=context): + #Check UoM in meantime + if vals.get('product_uom') or vals.get('product_id'): + product_uom = move.product_uom + move_uom = move.product_uom + if vals.get('product_uom'): + move_uom = self.pool.get('product.uom').browse(cr, uid, vals['product_uom'], context=context) + if vals.get('product_id'): + product_uom = self.pool.get('product.product').browse(cr, uid, vals['product_id'], context=context) + if move_uom.category_id.id != product_uom.category_id.id: + raise osv.except_osv(_('Operation Forbidden'), + _('Category of Product UoM must be the same as the category of the UoM of the move')) if move.state == 'done': if frozen_fields.intersection(vals): raise osv.except_osv(_('Operation Forbidden!'), From 593b812712d13615cadfe6e4bbef69aeaf260322 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 25 Feb 2014 14:29:39 +0100 Subject: [PATCH 08/22] [IMP] When moving quants, do all the writes on the quants at once bzr revid: jco@openerp.com-20140225132939-c6dzv60sncsps19l --- addons/stock/stock.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index afedc72b264..3cfbc13624b 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -331,11 +331,21 @@ class stock_quant(osv.osv): :param src_package_id: ID of the package that contains the quants to move :param dest_package_id: ID of the package that must be set on the moved quant """ + to_move = {} for quant, qty in quants: if not quant: #If quant is None, we will create a quant to move (and potentially a negative counterpart too) quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, context=context) - self.move_single_quant_tuple(cr, uid, quant, qty, move, context=context) + to_move.update(self.move_single_quant_tuple(cr, uid, quant, qty, move, context=context)) + # Write quants + for loc in to_move.keys(): + self._check_location(cr, uid, loc, context=context) + vals = { + 'location_id': loc.id, + 'history_ids': [(4, move.id)], +# 'package_id': False + } + self.write(cr, uid, to_move[loc], vals, context=context) def check_preferred_location(self, cr, uid, move, qty, context=None): '''Checks the preferred location on the move, if any returned by a putaway strategy, and returns a list of @@ -362,15 +372,9 @@ class stock_quant(osv.osv): :param move: browse record (stock.move) ''' new_quant = self._quant_split(cr, uid, quant, qty, context=context) - vals = { - 'location_id': location_to.id, - 'history_ids': [(4, move.id)], - } #if the quant we are moving had been split and was inside a package, it means we unpacked it - if new_quant and new_quant.package_id: - vals['package_id'] = False - if self._check_location(cr, uid, location_to, context): - self.write(cr, SUPERUSER_ID, [quant.id], vals, context=context) +# if new_quant and new_quant.package_id: +# vals['package_id'] = False quant.refresh() return new_quant @@ -381,12 +385,18 @@ class stock_quant(osv.osv): :param qty: float :param move: browse record (stock.move) ''' + res = {} for location_to, qty in self.check_preferred_location(cr, uid, move, qty, context=context): if not quant: break new_quant = self.move_single_quant(cr, uid, quant, location_to, qty, move, context=context) - self._quant_reconcile_negative(cr, uid, quant, move, context=context) + self._quant_reconcile_negative(cr, uid, quant, location_to, move, context=context) + if res.get(location_to): + res[location_to].append(quant.id) + else: + res[location_to] = [quant.id] quant = new_quant + return res def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain=False, fallback_domain=False, restrict_lot_id=False, restrict_partner_id=False, context=None): ''' This function tries to find quants in the given location for the given domain, by trying to first limit @@ -497,7 +507,7 @@ class stock_quant(osv.osv): path.append((4, move.id)) self.write(cr, SUPERUSER_ID, solved_quant_ids, {'history_ids': path}, context=context) - def _quant_reconcile_negative(self, cr, uid, quant, move, context=None): + def _quant_reconcile_negative(self, cr, uid, quant, location_id, move, context=None): """ When new quant arrive in a location, try to reconcile it with negative quants. If it's possible, apply the cost of the new @@ -513,7 +523,7 @@ class stock_quant(osv.osv): dom += [('package_id', '=', quant.package_id.id)] if move.move_dest_id: dom += [('negative_move_id', '=', move.move_dest_id.id)] - quants = self.quants_get(cr, uid, quant.location_id, quant.product_id, quant.qty, dom, context=context) + quants = self.quants_get(cr, uid, location_id, quant.product_id, quant.qty, dom, context=context) for quant_neg, qty in quants: if not quant_neg: continue From f39d00a5ed639f6986a773180fc3f11b2624b66a Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 25 Feb 2014 17:22:11 +0100 Subject: [PATCH 09/22] [IMP] Do picking assign in group at the end of confirming all procurements for a sales order bzr revid: jco@openerp.com-20140225162211-7elk2pj4r8q8sx04 --- addons/sale/sale.py | 10 +++++++- addons/sale_stock/sale_stock.py | 15 ++++++++++++ addons/stock/procurement.py | 2 +- addons/stock/stock.py | 42 +++++++++++++++++++++++++++++++-- 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 45374eee18a..4f90015195c 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -681,6 +681,8 @@ class sale_order(osv.osv): :return: True """ + if not context: + context = {} procurement_obj = self.pool.get('procurement.order') sale_line_obj = self.pool.get('sale.order.line') for order in self.browse(cr, uid, ids, context=context): @@ -706,7 +708,11 @@ class sale_order(osv.osv): proc_ids.append(proc_id) #Confirm procurement order such that rules will be applied on it #note that the workflow normally ensure proc_ids isn't an empty list - procurement_obj.run(cr, uid, proc_ids, context=context) + ctx = context.copy() + ctx["no_picking_assign"] = True + procurement_obj.run(cr, uid, proc_ids, context=ctx) + #Check all moves associated and do the picking_assign + procurement_obj.group_picking_assign(cr, uid, proc_ids, context=context) #if shipping was in exception and the user choose to recreate the delivery order, write the new status of SO if order.state == 'shipping_except': @@ -1175,3 +1181,5 @@ class procurement_order(osv.osv): 'sale_line_id': fields.many2one('sale.order.line', string='Sale Order Line'), } + def group_picking_assign(self, cr, uid, proc_ids, context=None): + return True diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index dbe64465047..37dcd1ad8b1 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -453,3 +453,18 @@ class stock_picking(osv.osv): created_lines = sale_line_obj.invoice_line_create(cr, uid, sale_line_ids, context=context) invoice_line_obj.write(cr, uid, created_lines, {'invoice_id': invoice_id}, context=context) return invoice_id + + +class procurement_order(osv.osv): + _inherit = 'procurement.order' + + def group_picking_assign(self, cr, uid, proc_ids, context=None): + moves = [] + procurements = proc_ids + while procurements: + related_moves = [] + for proc in self.browse(cr, uid, procurements, context=context): + related_moves += proc.move_ids + procurements = self.search(cr, uid, [('move_dest_id', 'in', [x.id for x in related_moves])], context=context) + moves += related_moves + self.pool.get("stock.move")._group_picking_assign(cr, uid, moves, context=context) diff --git a/addons/stock/procurement.py b/addons/stock/procurement.py index 4e16dc93d2c..2db5ef24c47 100644 --- a/addons/stock/procurement.py +++ b/addons/stock/procurement.py @@ -95,7 +95,7 @@ class procurement_rule(osv.osv): class procurement_order(osv.osv): _inherit = "procurement.order" _columns = { - 'location_id': fields.many2one('stock.location', 'Procurement Location'), # not required because task may create procurements that aren't linked to a location with project_mrp + 'location_id': fields.many2one('stock.location', 'Procurement Location'), # not required because task may create procurements that aren't linked to a location with project_mrp 'move_ids': fields.one2many('stock.move', 'procurement_id', 'Moves', help="Moves created by the procurement"), 'move_dest_id': fields.many2one('stock.move', 'Destination Move', help="Move which caused (created) the procurement"), 'route_ids': fields.many2many('stock.location.route', 'stock_location_route_procurement', 'procurement_id', 'route_id', 'Preferred Routes', help="Preferred route to be followed by the procurement order. Usually copied from the generating document (SO) but could be set up manually."), diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 3cfbc13624b..deea464f060 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -344,8 +344,8 @@ class stock_quant(osv.osv): 'location_id': loc.id, 'history_ids': [(4, move.id)], # 'package_id': False - } - self.write(cr, uid, to_move[loc], vals, context=context) + } + self.write(cr, SUPERUSER_ID, to_move[loc], vals, context=context) def check_preferred_location(self, cr, uid, move, qty, context=None): '''Checks the preferred location on the move, if any returned by a putaway strategy, and returns a list of @@ -1766,6 +1766,10 @@ class stock_move(osv.osv): return {'value': result} def _picking_assign(self, cr, uid, move, context=None): + if not context: + context = {} + if context.get("no_picking_assign") and context['no_picking_assign']: + return False if move.picking_id or not move.picking_type_id: return False context = context or {} @@ -1791,6 +1795,40 @@ class stock_move(osv.osv): move.write({'picking_id': pick}) return True + def _group_picking_assign(self, cr, uid, moves, context=None): + if not context: + context = {} + if context.get("no_picking_assign") and context['no_picking_assign']: + return False + move_dict = {} + for move in moves: + group_by = (move.location_id, move.location_dest_id, move.group_id) + if not move_dict.get(group_by, False): + move_dict[group_by] = [move] + else: + move_dict[group_by].append(move) + pick_obj = self.pool.get("stock.picking") + for to_compare in move_dict.keys(): + picks = pick_obj.search(cr, uid, [ + ('group_id', '=', to_compare[2].id), + ('location_id', '=', to_compare[0].id), + ('location_dest_id', '=', to_compare[1].id), + ('state', 'in', ['draft', 'confirmed', 'waiting']), + ], context=context) + if picks: + pick = picks[0] + else: + move = move_dict[to_compare][0] + values = { + 'origin': move.origin, + 'company_id': move.company_id and move.company_id.id or False, + 'move_type': move.group_id and move.group_id.move_type or 'one', + 'partner_id': move.group_id and move.group_id.partner_id and move.group_id.partner_id.id or False, + 'picking_type_id': move.picking_type_id and move.picking_type_id.id or False, + } + pick = pick_obj.create(cr, uid, values, context=context) + self.write(cr, uid, [x.id for x in move_dict[to_compare]], {'picking_id': pick}, context=context) + def onchange_date(self, cr, uid, ids, date, date_expected, context=None): """ On change of Scheduled Date gives a Move date. @param date_expected: Scheduled Date From 043e4eefc973e2391a1fc79a694fa5761ad7ff86 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 26 Feb 2014 11:35:17 +0100 Subject: [PATCH 10/22] [REVERT] Revert commit for writing to multiple quants at once bzr revid: jco@openerp.com-20140226103517-ixltha9vw4qpnw71 --- addons/stock/stock.py | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index deea464f060..84abe210a25 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -331,21 +331,11 @@ class stock_quant(osv.osv): :param src_package_id: ID of the package that contains the quants to move :param dest_package_id: ID of the package that must be set on the moved quant """ - to_move = {} for quant, qty in quants: if not quant: #If quant is None, we will create a quant to move (and potentially a negative counterpart too) quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, context=context) - to_move.update(self.move_single_quant_tuple(cr, uid, quant, qty, move, context=context)) - # Write quants - for loc in to_move.keys(): - self._check_location(cr, uid, loc, context=context) - vals = { - 'location_id': loc.id, - 'history_ids': [(4, move.id)], -# 'package_id': False - } - self.write(cr, SUPERUSER_ID, to_move[loc], vals, context=context) + self.move_single_quant_tuple(cr, uid, quant, qty, move, context=context) def check_preferred_location(self, cr, uid, move, qty, context=None): '''Checks the preferred location on the move, if any returned by a putaway strategy, and returns a list of @@ -372,9 +362,15 @@ class stock_quant(osv.osv): :param move: browse record (stock.move) ''' new_quant = self._quant_split(cr, uid, quant, qty, context=context) + vals = { + 'location_id': location_to.id, + 'history_ids': [(4, move.id)], + } #if the quant we are moving had been split and was inside a package, it means we unpacked it -# if new_quant and new_quant.package_id: -# vals['package_id'] = False + if new_quant and new_quant.package_id: + vals['package_id'] = False + if self._check_location(cr, uid, location_to, context): + self.write(cr, SUPERUSER_ID, [quant.id], vals, context=context) quant.refresh() return new_quant @@ -385,18 +381,12 @@ class stock_quant(osv.osv): :param qty: float :param move: browse record (stock.move) ''' - res = {} for location_to, qty in self.check_preferred_location(cr, uid, move, qty, context=context): if not quant: break new_quant = self.move_single_quant(cr, uid, quant, location_to, qty, move, context=context) - self._quant_reconcile_negative(cr, uid, quant, location_to, move, context=context) - if res.get(location_to): - res[location_to].append(quant.id) - else: - res[location_to] = [quant.id] + self._quant_reconcile_negative(cr, uid, quant, move, context=context) quant = new_quant - return res def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain=False, fallback_domain=False, restrict_lot_id=False, restrict_partner_id=False, context=None): ''' This function tries to find quants in the given location for the given domain, by trying to first limit @@ -507,7 +497,7 @@ class stock_quant(osv.osv): path.append((4, move.id)) self.write(cr, SUPERUSER_ID, solved_quant_ids, {'history_ids': path}, context=context) - def _quant_reconcile_negative(self, cr, uid, quant, location_id, move, context=None): + def _quant_reconcile_negative(self, cr, uid, quant, move, context=None): """ When new quant arrive in a location, try to reconcile it with negative quants. If it's possible, apply the cost of the new @@ -523,7 +513,7 @@ class stock_quant(osv.osv): dom += [('package_id', '=', quant.package_id.id)] if move.move_dest_id: dom += [('negative_move_id', '=', move.move_dest_id.id)] - quants = self.quants_get(cr, uid, location_id, quant.product_id, quant.qty, dom, context=context) + quants = self.quants_get(cr, uid, quant.location_id, quant.product_id, quant.qty, dom, context=context) for quant_neg, qty in quants: if not quant_neg: continue From 39c5e97588bf02d331b9fb426f41b36c29a30c6c Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 26 Feb 2014 12:23:42 +0100 Subject: [PATCH 11/22] [FIX] uom compare bzr revid: jco@openerp.com-20140226112342-n702mbth1wfv4e7s --- addons/purchase/test/ui/duplicate_order.yml | 2 +- addons/stock/stock.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/purchase/test/ui/duplicate_order.yml b/addons/purchase/test/ui/duplicate_order.yml index d49cec22d73..c53e4900e5f 100644 --- a/addons/purchase/test/ui/duplicate_order.yml +++ b/addons/purchase/test/ui/duplicate_order.yml @@ -4,4 +4,4 @@ I duplicate order. - !python {model: purchase.order}: | - self.copy(cr, uid, ref('purchase_order_1'), context) + self.copy(cr, uid, ref('purchase_order_1'), context) \ No newline at end of file diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 84abe210a25..9b8a79df76b 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1598,12 +1598,12 @@ class stock_move(osv.osv): for move in self.browse(cr, uid, ids, context=context): #Check UoM in meantime if vals.get('product_uom') or vals.get('product_id'): - product_uom = move.product_uom + product_uom = move.product_id.uom_id move_uom = move.product_uom if vals.get('product_uom'): move_uom = self.pool.get('product.uom').browse(cr, uid, vals['product_uom'], context=context) if vals.get('product_id'): - product_uom = self.pool.get('product.product').browse(cr, uid, vals['product_id'], context=context) + product_uom = self.pool.get('product.product').browse(cr, uid, vals['product_id'], context=context).uom_id if move_uom.category_id.id != product_uom.category_id.id: raise osv.except_osv(_('Operation Forbidden'), _('Category of Product UoM must be the same as the category of the UoM of the move')) From 1469be6c5fbe1765e4942858f76e9594cf20e58b Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 26 Feb 2014 13:00:51 +0100 Subject: [PATCH 12/22] [IMP] Add partially available field on stock move bzr revid: jco@openerp.com-20140226120051-ur6co4aff9s9d1u7 --- addons/stock/stock.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 9b8a79df76b..759c02102f8 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -320,6 +320,8 @@ class stock_quant(osv.osv): move.refresh() if move.reserved_availability == move.product_qty and move.state in ('confirmed', 'waiting'): self.pool.get('stock.move').write(cr, uid, [move.id], {'state': 'assigned'}, context=context) + elif move.reserved_availability > 0 and not move.partially_available: + self.pool.get('stock.move').write(cr, uid, [move.id], {'partially_available': True}, context=context) def quants_move(self, cr, uid, quants, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None): """Moves all given stock.quant in the destination location of the given move. @@ -549,6 +551,8 @@ class stock_quant(osv.osv): def quants_unreserve(self, cr, uid, move, context=None): related_quants = [x.id for x in move.reserved_quant_ids] + if related_quants and move.partially_available: + self.pool.get("stock.move").write(cr, uid, [move.id], {'partially_available': False}, context=context) return self.write(cr, SUPERUSER_ID, related_quants, {'reservation_id': False, 'link_move_operation_id': False}, context=context) def _quants_get_order(self, cr, uid, location, product, quantity, domain=[], orderby='in_date', context=None): @@ -686,13 +690,6 @@ class stock_picking(osv.osv): res.add(move.picking_id.id) return list(res) - def _get_pickings_from_quant(self, cr, uid, ids, context=None): - res = set() - for quant in self.browse(cr, uid, ids, context=context): - if quant.reservation_id and quant.reservation_id.picking_id: - res.add(quant.reservation_id.picking_id.id) - return list(res) - def _get_pack_operation_exist(self, cr, uid, ids, field_name, arg, context=None): res = {} for pick in self.browse(cr, uid, ids, context=context): @@ -724,8 +721,7 @@ class stock_picking(osv.osv): 'move_type': fields.selection([('direct', 'Partial'), ('one', 'All at once')], 'Delivery Method', required=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="It specifies goods to be deliver partially or all at once"), 'state': fields.function(_state_get, type="selection", store={ 'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_type'], 20), - 'stock.move': (_get_pickings, ['state', 'picking_id'], 20), - 'stock.quant': (_get_pickings_from_quant, ['reservation_id'], 20)}, selection=[ + 'stock.move': (_get_pickings, ['state', 'picking_id', 'partially_available'], 20)}, selection=[ ('draft', 'Draft'), ('cancel', 'Cancelled'), ('waiting', 'Waiting Another Operation'), @@ -1408,7 +1404,7 @@ class stock_move(osv.osv): "* Waiting Availability: This state is reached when the procurement resolution is not straight forward. It may need the scheduler to run, a component to me manufactured...\n"\ "* Available: When products are reserved, it is set to \'Available\'.\n"\ "* Done: When the shipment is processed, the state is \'Done\'."), - + 'partially_available': fields.boolean('Partially Available', readonly = True, help = "Checks if the move has some stock reserved"), 'price_unit': fields.float('Unit Price', help="Technical field used to record the product cost set by the user during a picking confirmation (when costing method used is 'average price' or 'real'). Value given in company currency and in product uom."), # as it's a technical field, we intentionally don't provide the digits attribute 'company_id': fields.many2one('res.company', 'Company', required=True, select=True), From 781de160202c2173d070bef78b6162f6d08bdf7d Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 26 Feb 2014 14:01:56 +0100 Subject: [PATCH 13/22] [IMP] copy_data instead of copy for duplicate purchase order test bzr revid: jco@openerp.com-20140226130156-c5pexmfjkisoxq30 --- addons/purchase/test/ui/duplicate_order.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/purchase/test/ui/duplicate_order.yml b/addons/purchase/test/ui/duplicate_order.yml index c53e4900e5f..653af8c9f67 100644 --- a/addons/purchase/test/ui/duplicate_order.yml +++ b/addons/purchase/test/ui/duplicate_order.yml @@ -4,4 +4,4 @@ I duplicate order. - !python {model: purchase.order}: | - self.copy(cr, uid, ref('purchase_order_1'), context) \ No newline at end of file + self.copy_data(cr, uid, ref('purchase_order_1'), context) \ No newline at end of file From 1ca10a18dcd84c910dae28777bfaa6840b66b6f1 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 26 Feb 2014 15:12:23 +0100 Subject: [PATCH 14/22] [IMP] getpackages and getpickings use read instead of browse + use query for get_state bzr revid: jco@openerp.com-20140226141223-9yioe6j7a9x9lbs3 --- addons/mail/mail_message.py | 2 +- addons/stock/stock.py | 30 ++++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 40a51df91d2..20526e36bcb 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -208,7 +208,7 @@ class mail_message(osv.Model): raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias.")) def _get_default_author(self, cr, uid, context=None): - return self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id + return self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] _defaults = { 'type': 'email', diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 759c02102f8..72cc87d5862 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -656,19 +656,21 @@ class stock_picking(osv.osv): ''' res = {} for pick in self.browse(cr, uid, ids, context=context): - if (not pick.move_lines) or any([x.state == 'draft' for x in pick.move_lines]): + cr.execute("select state, partially_available from stock_move where picking_id = %s", (pick.id,)) + move_lines = cr.fetchall() #x[0] = state, x[1] partially available + if (not move_lines) or any([x[0] == 'draft' for x in move_lines]): res[pick.id] = 'draft' continue - if all([x.state == 'cancel' for x in pick.move_lines]): + if all([x[0] == 'cancel' for x in move_lines]): res[pick.id] = 'cancel' continue - if all([x.state in ('cancel', 'done') for x in pick.move_lines]): + if all([x[0] in ('cancel', 'done') for x in move_lines]): res[pick.id] = 'done' continue order = {'confirmed': 0, 'waiting': 1, 'assigned': 2} order_inv = {0: 'confirmed', 1: 'waiting', 2: 'assigned'} - lst = [order[x.state] for x in pick.move_lines if x.state not in ('cancel', 'done')] + lst = [order[x[0]] for x in move_lines if x[0] not in ('cancel', 'done')] if pick.move_type == 'one': res[pick.id] = order_inv[min(lst)] else: @@ -678,16 +680,16 @@ class stock_picking(osv.osv): res[pick.id] = order_inv[max(lst)] if not all(x == 2 for x in lst): #if all moves aren't assigned, check if we have one product partially available - for move in pick.move_lines: - if move.reserved_quant_ids: + for move in move_lines: + if move[1]: res[pick.id] = 'partially_available' return res def _get_pickings(self, cr, uid, ids, context=None): res = set() - for move in self.browse(cr, uid, ids, context=context): - if move.picking_id: - res.add(move.picking_id.id) + for move in self.read(cr, uid, ids, ['picking_id'], context=context): + if move['picking_id']: + res.add(move['picking_id'][0]) return list(res) def _get_pack_operation_exist(self, cr, uid, ids, field_name, arg, context=None): @@ -740,9 +742,9 @@ class stock_picking(osv.osv): ), 'priority': fields.selection([('0', 'Low'), ('1', 'Normal'), ('2', 'High')], states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, string='Priority', required=True), 'min_date': fields.function(get_min_max_date, multi="min_max_date", fnct_inv=_set_min_date, - store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, string='Scheduled Date', select=1, help="Scheduled time for the first part of the shipment to be processed. Setting manually a value here would set it as expected date for all the stock moves.", track_visibility='onchange'), + store={'stock.move': (_get_pickings, ['date_expected'], 20)}, type='datetime', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, string='Scheduled Date', select=1, help="Scheduled time for the first part of the shipment to be processed. Setting manually a value here would set it as expected date for all the stock moves.", track_visibility='onchange'), 'max_date': fields.function(get_min_max_date, multi="min_max_date", - store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', string='Max. Expected Date', select=2, help="Scheduled time for the last part of the shipment to be processed"), + store={'stock.move': (_get_pickings, ['date_expected'], 20)}, type='datetime', string='Max. Expected Date', select=2, help="Scheduled time for the last part of the shipment to be processed"), 'date': fields.datetime('Commitment Date', help="Date promised for the completion of the transfer order, usually set the time of the order and revised later on.", select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, track_visibility='onchange'), 'date_done': fields.datetime('Date of Transfer', help="Date of Completion", states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}), 'move_lines': fields.one2many('stock.move', 'picking_id', 'Internal Moves', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}), @@ -3270,9 +3272,9 @@ class stock_package(osv.osv): def _get_packages(self, cr, uid, ids, context=None): """Returns packages from quants for store""" res = set() - for quant in self.browse(cr, uid, ids, context=context): - if quant.package_id: - res.add(quant.package_id.id) + for quant in self.read(cr, uid, ids, ['package_id'], context=context): + if quant['package_id']: + res.add(quant['package_id'][0]) return list(res) def _get_packages_to_relocate(self, cr, uid, ids, context=None): From c80d3f88bc950c9b074a73b1f7c91339c677e304 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 26 Feb 2014 16:04:26 +0100 Subject: [PATCH 15/22] [IMP] Read partner_id from user browse instead bzr revid: jco@openerp.com-20140226150426-ysyosowmszztp9dm --- addons/mail/mail_message.py | 14 +++++++------- addons/mail/mail_thread.py | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 20526e36bcb..6ddc7c0732f 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -100,7 +100,7 @@ class mail_message(osv.Model): def _get_to_read(self, cr, uid, ids, name, arg, context=None): """ Compute if the message is unread by the current user. """ res = dict((id, False) for id in ids) - partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] notif_obj = self.pool.get('mail.notification') notif_ids = notif_obj.search(cr, uid, [ ('partner_id', 'in', [partner_id]), @@ -119,7 +119,7 @@ class mail_message(osv.Model): def _get_starred(self, cr, uid, ids, name, arg, context=None): """ Compute if the message is unread by the current user. """ res = dict((id, False) for id in ids) - partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] notif_obj = self.pool.get('mail.notification') notif_ids = notif_obj.search(cr, uid, [ ('partner_id', 'in', [partner_id]), @@ -266,7 +266,7 @@ class mail_message(osv.Model): :return number of message mark as read """ notification_obj = self.pool.get('mail.notification') - user_pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id + user_pid = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)] if not create_missing: domain += [('read', '=', not read)] @@ -294,7 +294,7 @@ class mail_message(osv.Model): (i.e. when acting on displayed messages not notified) """ notification_obj = self.pool.get('mail.notification') - user_pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id + user_pid = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)] if not create_missing: domain += [('starred', '=', not starred)] @@ -332,7 +332,7 @@ class mail_message(osv.Model): """ res_partner_obj = self.pool.get('res.partner') ir_attachment_obj = self.pool.get('ir.attachment') - pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id + pid = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] # 1. Aggregate partners (author_id and partner_ids) and attachments partner_ids = set() @@ -653,7 +653,7 @@ class mail_message(osv.Model): elif not ids: return ids - pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id + pid = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] author_ids, partner_ids, allowed_ids = set([]), set([]), set([]) model_ids = {} @@ -714,7 +714,7 @@ class mail_message(osv.Model): ids = [ids] not_obj = self.pool.get('mail.notification') fol_obj = self.pool.get('mail.followers') - partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=None).partner_id.id + partner_id = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] # Read mail_message.ids to have their values message_values = dict.fromkeys(ids, {}) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 9a8adee31c3..55b3aae04b9 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -276,7 +276,7 @@ class mail_thread(osv.AbstractModel): res = [] for field, operator, value in args: assert field == name - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] if (operator == '=' and value) or (operator == '!=' and not value): # is a follower res_ids = self.search(cr, uid, [('message_follower_ids', 'in', [partner_id])], context=context) else: # is not a follower or unknown domain @@ -344,7 +344,7 @@ class mail_thread(osv.AbstractModel): # subscribe uid unless asked not to if not context.get('mail_create_nosubscribe'): - pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid).partner_id.id + pid = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] message_follower_ids = values.get('message_follower_ids') or [] # webclient can send None or False message_follower_ids.append([4, pid]) values['message_follower_ids'] = message_follower_ids @@ -1556,7 +1556,7 @@ class mail_thread(osv.AbstractModel): mail_followers_obj = self.pool.get('mail.followers') subtype_obj = self.pool.get('mail.message.subtype') - user_pid = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + user_pid = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] if set(partner_ids) == set([user_pid]): if context.get('operation', '') != 'create': try: @@ -1744,7 +1744,7 @@ class mail_thread(osv.AbstractModel): def message_mark_as_unread(self, cr, uid, ids, context=None): """ Set as unread. """ - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] cr.execute(''' UPDATE mail_notification SET read=false @@ -1756,7 +1756,7 @@ class mail_thread(osv.AbstractModel): def message_mark_as_read(self, cr, uid, ids, context=None): """ Set as read. """ - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id + partner_id = self.pool.get('res.users').read(cr, SUPERUSER_ID, [uid], ['partner_id'], context=context)[0]['partner_id'][0] cr.execute(''' UPDATE mail_notification SET read=true From 23128a26ae574f719491b942a92fa1553bedacf4 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 27 Feb 2014 09:45:09 +0100 Subject: [PATCH 16/22] [IMP] Put remaining qty in variable not to calculate multiple times bzr revid: jco@openerp.com-20140227084509-cr589ynshxltt2fv --- addons/stock/stock.py | 17 +++++++++++++---- addons/stock_megatest/test/megatestmtobuy.yml | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 4d7ca421dd4..3d176264b42 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1100,16 +1100,17 @@ class stock_picking(osv.osv): todo_move_ids = [] toassign_move_ids = [] for move in picking.move_lines: + remaining_qty = move.remaining_qty if move.state in ('done', 'cancel'): #ignore stock moves cancelled or already done continue elif move.state == 'draft': toassign_move_ids.append(move.id) - if move.remaining_qty == 0: + if remaining_qty == 0: if move.state in ('draft', 'assigned', 'confirmed'): todo_move_ids.append(move.id) - elif move.remaining_qty > 0 and move.remaining_qty < move.product_qty: - new_move = stock_move_obj.split(cr, uid, move, move.remaining_qty, context=context) + elif remaining_qty > 0 and remaining_qty < move.product_qty: + new_move = stock_move_obj.split(cr, uid, move, remaining_qty, context=context) todo_move_ids.append(move.id) #Assign move as it was assigned before toassign_move_ids.append(new_move) @@ -1355,6 +1356,7 @@ class stock_move(osv.osv): res += [x.id for x in picking.move_lines] return res + _columns = { 'name': fields.char('Description', required=True, select=True), 'priority': fields.selection([('0', 'Not urgent'), ('1', 'Urgent')], 'Priority'), @@ -3528,7 +3530,11 @@ class stock_pack_operation(osv.osv): for move in sorted_moves: if move.product_id.id == product_id and move.state not in ['done', 'cancel']: qty_on_link = min(move.remaining_qty, qty_to_assign) - link_obj.create(cr, uid, {'move_id': move.id, 'operation_id': op.id, 'qty': qty_on_link}, context=context) + before = time.time() + cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values + (%s, %s, %s)""", (move.id, op.id, qty_on_link,)) +# link_obj.create(cr, uid, {'move_id': move.id, 'operation_id': op.id, 'qty': qty_on_link}, context=context) + print "Link create", time.time() - before qty_to_assign -= qty_on_link move.refresh() if qty_to_assign <= 0: @@ -3541,7 +3547,9 @@ class stock_pack_operation(osv.osv): #Entire packages means entire quants from those packages if not quants_done.get(quant.id): quants_done[quant.id] = 0 + before = time.time() link_obj.create(cr, uid, {'move_id': quant.reservation_id.id, 'operation_id': ops.id, 'qty': quant.qty}, context=context) + print "Link create", time.time() - before else: qty = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, ops.product_id.uom_id.id) #Check moves with same product @@ -3595,6 +3603,7 @@ class stock_pack_operation(osv.osv): if op.product_id: #TODO: Remaining qty: UoM conversions are done twice normalized_qty = uom_obj._compute_qty(cr, uid, op.product_uom_id.id, op.remaining_qty, op.product_id.uom_id.id) + if normalized_qty > 0: _create_link_for_product(op.product_id.id, normalized_qty) elif op.package_id: diff --git a/addons/stock_megatest/test/megatestmtobuy.yml b/addons/stock_megatest/test/megatestmtobuy.yml index e5129e22c15..0ec5d2d47be 100644 --- a/addons/stock_megatest/test/megatestmtobuy.yml +++ b/addons/stock_megatest/test/megatestmtobuy.yml @@ -178,7 +178,7 @@ pick_obj = self.pool.get('stock.picking') # Process those pickings and put in boxes of 20 pieces for pick in pickings: - #pick_obj.do_prepare_partial(cr, uid, [pick.id]) + pick_obj.do_prepare_partial(cr, uid, [pick.id]) #for ops in pick.pack_operation_ids: # pick # while ops.product_qty > 100: @@ -198,6 +198,6 @@ related_dest_pickings = [x.picking_id for x in move_dest_ids if x.picking_id] picks = list(set(related_dest_pickings)) for pick in picks: - #pick_obj.do_prepare_partial(cr, uid, [pick.id]) + pick_obj.do_prepare_partial(cr, uid, [pick.id]) pick_obj.do_transfer(cr, uid, [pick.id]) print "after second transfer", time.time() \ No newline at end of file From 634d0f3ff880959f19fa64e47f3566ea52509e3c Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 27 Feb 2014 11:04:01 +0100 Subject: [PATCH 17/22] [IMP] Do not search quants when source location requires to create quants bzr revid: jco@openerp.com-20140227100401-axy57wrto0ex2xf7 --- addons/stock/stock.py | 6 +++++- addons/stock_megatest/test/megatestmtobuy.yml | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 3d176264b42..5619c952ab2 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2018,6 +2018,7 @@ class stock_move(osv.osv): for link in move.linked_move_operation_ids: operations.add(link.operation_id) + #Sort operations according to entire packages first, then package + lot, package only, lot only operations = list(operations) operations.sort(key = lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0)) @@ -2051,7 +2052,10 @@ class stock_move(osv.osv): fallback_domain = [('reservation_id', '=', False)] self.check_tracking(cr, uid, move, move.restrict_lot_id.id, context=context) qty = move_qty[move.id] - quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) + if move.location_id.usage in ('supplier', 'inventory', 'production'): + quants = [(None, move.product_uom_qty)] + else: + quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) quant_obj.quants_move(cr, uid, quants, move, lot_id=move.restrict_lot_id.id, owner_id=move.restrict_partner_id.id, context=context) #unreserve the quants and make them available for other operations/moves quant_obj.quants_unreserve(cr, uid, move, context=context) diff --git a/addons/stock_megatest/test/megatestmtobuy.yml b/addons/stock_megatest/test/megatestmtobuy.yml index 0ec5d2d47be..e5129e22c15 100644 --- a/addons/stock_megatest/test/megatestmtobuy.yml +++ b/addons/stock_megatest/test/megatestmtobuy.yml @@ -178,7 +178,7 @@ pick_obj = self.pool.get('stock.picking') # Process those pickings and put in boxes of 20 pieces for pick in pickings: - pick_obj.do_prepare_partial(cr, uid, [pick.id]) + #pick_obj.do_prepare_partial(cr, uid, [pick.id]) #for ops in pick.pack_operation_ids: # pick # while ops.product_qty > 100: @@ -198,6 +198,6 @@ related_dest_pickings = [x.picking_id for x in move_dest_ids if x.picking_id] picks = list(set(related_dest_pickings)) for pick in picks: - pick_obj.do_prepare_partial(cr, uid, [pick.id]) + #pick_obj.do_prepare_partial(cr, uid, [pick.id]) pick_obj.do_transfer(cr, uid, [pick.id]) print "after second transfer", time.time() \ No newline at end of file From 0f52affe0b6c88d2ebbde820fe1fd82c2b577395 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 27 Feb 2014 14:12:37 +0100 Subject: [PATCH 18/22] [IMP] When reserved quants are ok, no need to rereserve quants bzr revid: jco@openerp.com-20140227131237-du11dk5fu71nfnex --- addons/stock/stock.py | 21 +++++++++++++------ addons/stock_megatest/test/megatestmtobuy.yml | 7 ++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 5619c952ab2..c348c876235 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1041,10 +1041,12 @@ class stock_picking(osv.osv): def do_recompute_remaining_quantities(self, cr, uid, picking_ids, context=None): pack_op_obj = self.pool.get('stock.pack.operation') + quants_res = True for picking in self.browse(cr, uid, picking_ids, context=context): op_ids = [op.id for op in picking.pack_operation_ids] if op_ids: - pack_op_obj.recompute_rem_qty_from_operation(cr, uid, op_ids, context=context) + quants_res = quants_res and pack_op_obj.recompute_rem_qty_from_operation(cr, uid, op_ids, context=context) + return quants_res def _create_extra_moves(self, cr, uid, picking, context=None): '''This function creates move lines on a picking, at the time of do_transfer, based on @@ -1092,13 +1094,15 @@ class stock_picking(osv.osv): self.action_done(cr, uid, [picking.id], context=context) continue else: - self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context) + quants_ok = self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context) #create extra moves in the picking (unexpected product moves coming from pack operations) - self._create_extra_moves(cr, uid, picking, context=context) + if not quants_ok: + self._create_extra_moves(cr, uid, picking, context=context) picking.refresh() #split move lines eventually todo_move_ids = [] toassign_move_ids = [] + no_rereserve = True for move in picking.move_lines: remaining_qty = move.remaining_qty if move.state in ('done', 'cancel'): @@ -1106,15 +1110,18 @@ class stock_picking(osv.osv): continue elif move.state == 'draft': toassign_move_ids.append(move.id) + no_rereserve = False if remaining_qty == 0: if move.state in ('draft', 'assigned', 'confirmed'): todo_move_ids.append(move.id) elif remaining_qty > 0 and remaining_qty < move.product_qty: + no_rereserve = False new_move = stock_move_obj.split(cr, uid, move, remaining_qty, context=context) todo_move_ids.append(move.id) #Assign move as it was assigned before toassign_move_ids.append(new_move) - self.rereserve_quants(cr, uid, picking, move_ids=todo_move_ids, context=context) + if not quants_ok or not no_rereserve: + self.rereserve_quants(cr, uid, picking, move_ids=todo_move_ids, context=context) if todo_move_ids and not context.get('do_only_split'): self.pool.get('stock.move').action_done(cr, uid, todo_move_ids, context=context) elif context.get('do_only_split'): @@ -3553,7 +3560,6 @@ class stock_pack_operation(osv.osv): quants_done[quant.id] = 0 before = time.time() link_obj.create(cr, uid, {'move_id': quant.reservation_id.id, 'operation_id': ops.id, 'qty': quant.qty}, context=context) - print "Link create", time.time() - before else: qty = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, ops.product_id.uom_id.id) #Check moves with same product @@ -3602,19 +3608,22 @@ class stock_pack_operation(osv.osv): link_obj.unlink(cr, uid, to_unlink_ids, context=context) _check_quants_reserved(op) + quants_reserve_ok = True for op in operations: op.refresh() if op.product_id: #TODO: Remaining qty: UoM conversions are done twice normalized_qty = uom_obj._compute_qty(cr, uid, op.product_uom_id.id, op.remaining_qty, op.product_id.uom_id.id) - if normalized_qty > 0: + quants_reserve_ok = False _create_link_for_product(op.product_id.id, normalized_qty) elif op.package_id: prod_quants = self._get_remaining_prod_quantities(cr, uid, op, context=context) for product_id, qty in prod_quants.items(): if qty > 0: + quants_reserve_ok = False _create_link_for_product(product_id, qty) + return quants_reserve_ok def process_packaging(self, cr, uid, operation, quants, context=None): ''' Process the packaging of a given operation, after the quants have been moved. If there was not enough quants found diff --git a/addons/stock_megatest/test/megatestmtobuy.yml b/addons/stock_megatest/test/megatestmtobuy.yml index e5129e22c15..130a1a4aaa3 100644 --- a/addons/stock_megatest/test/megatestmtobuy.yml +++ b/addons/stock_megatest/test/megatestmtobuy.yml @@ -1,3 +1,8 @@ +- + Do it with the rights of Mr. Demo +- + !context + uid: 'base.user_demo' - I first create a warehouse with pick-pack-ship and reception in 2 steps - @@ -198,6 +203,6 @@ related_dest_pickings = [x.picking_id for x in move_dest_ids if x.picking_id] picks = list(set(related_dest_pickings)) for pick in picks: - #pick_obj.do_prepare_partial(cr, uid, [pick.id]) + pick_obj.do_prepare_partial(cr, uid, [pick.id]) pick_obj.do_transfer(cr, uid, [pick.id]) print "after second transfer", time.time() \ No newline at end of file From bd0fcb70b78d51035ef28d58b9346f377257a027 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 27 Feb 2014 17:08:08 +0100 Subject: [PATCH 19/22] [IMP] name_get of product should not ask for seller_ids if not necessary bzr revid: jco@openerp.com-20140227160808-m53f0kkgbqno1ea8 --- addons/product/product.py | 5 ++++- addons/stock_megatest/test/megatestmtobuy.yml | 5 ----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/addons/product/product.py b/addons/product/product.py index 9169ec99ead..daeb22242cc 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -807,7 +807,10 @@ class product_product(osv.osv): result = [] for product in self.browse(cr, SUPERUSER_ID, ids, context=context): - sellers = filter(lambda x: x.name.id == partner_id, product.seller_ids) + if partner_id: + sellers = filter(lambda x: x.name.id == partner_id, product.seller_ids) + else: + sellers = False if sellers: for s in sellers: mydict = { diff --git a/addons/stock_megatest/test/megatestmtobuy.yml b/addons/stock_megatest/test/megatestmtobuy.yml index 130a1a4aaa3..97e2f4c6587 100644 --- a/addons/stock_megatest/test/megatestmtobuy.yml +++ b/addons/stock_megatest/test/megatestmtobuy.yml @@ -1,8 +1,3 @@ -- - Do it with the rights of Mr. Demo -- - !context - uid: 'base.user_demo' - I first create a warehouse with pick-pack-ship and reception in 2 steps - From 753c125024e6d424b945286a84604c4918c23b84 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 27 Feb 2014 17:10:20 +0100 Subject: [PATCH 20/22] [IMP] No pack operations create needed at force_assign bzr revid: jco@openerp.com-20140227161020-happi4qshw2uoabo --- addons/stock/stock.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index c348c876235..1e37916b7d7 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -852,8 +852,6 @@ class stock_picking(osv.osv): for pick in self.browse(cr, uid, ids, context=context): move_ids = [x.id for x in pick.move_lines if x.state in ['confirmed', 'waiting']] self.pool.get('stock.move').force_assign(cr, uid, move_ids, context=context) - if pick.pack_operation_exist: - self.do_prepare_partial(cr, uid, [pick.id], context=None) return True def action_cancel(self, cr, uid, ids, context=None): From d39056faec5bdc11db7fa039ce17c84e26485c1c Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Fri, 28 Feb 2014 18:32:41 +0100 Subject: [PATCH 21/22] [IMP] Change _compute_qty to0 _compute_qty_obj in order to pass browse records immediately instead bzr revid: jco@openerp.com-20140228173241-qfenlx2hb8h1k40j --- addons/product/product.py | 1 + addons/stock/stock.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/addons/product/product.py b/addons/product/product.py index daeb22242cc..bed0057b046 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -159,6 +159,7 @@ class product_uom(osv.osv): ('factor_gt_zero', 'CHECK (factor!=0)', 'The conversion ratio for a unit of measure cannot be 0!') ] + def _compute_qty(self, cr, uid, from_uom_id, qty, to_uom_id=False, round=True): if not from_uom_id or not qty or not to_uom_id: return qty diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 1e37916b7d7..166693dcaca 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1276,7 +1276,7 @@ class stock_move(osv.osv): uom_obj = self.pool.get('product.uom') res = {} for m in self.browse(cr, uid, ids, context=context): - res[m.id] = uom_obj._compute_qty_obj(cr, uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id, round=False) + res[m.id] = uom_obj._compute_qty_obj(cr, uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id, round=False, context=context) return res def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None): @@ -1287,7 +1287,7 @@ class stock_move(osv.osv): for record in move.linked_move_operation_ids: qty -= record.qty #converting the remaining quantity in the move UoM - res[move.id] = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, qty, move.product_uom.id) + res[move.id] = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_uom, round=False, context=context) return res def _get_lot_ids(self, cr, uid, ids, field_name, args, context=None): @@ -1323,7 +1323,7 @@ class stock_move(osv.osv): res[move.id] = '' # 'not applicable' or 'n/a' could work too continue total_available = min(move.product_qty, move.reserved_availability + move.availability) - total_available = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, total_available, move.product_uom.id) + total_available = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, total_available, move.product_uom, context=context) info = str(total_available) #look in the settings if we need to display the UoM name or not config_ids = settings_obj.search(cr, uid, [], limit=1, order='id DESC', context=context) @@ -1334,7 +1334,7 @@ class stock_move(osv.osv): if move.reserved_availability: if move.reserved_availability != total_available: #some of the available quantity is assigned and some are available but not reserved - reserved_available = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, move.reserved_availability, move.product_uom.id) + reserved_available = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, move.reserved_availability, move.product_uom, context=context) info += _(' (%s reserved)') % str(reserved_available) else: #all available quantity is assigned @@ -2164,7 +2164,7 @@ class stock_move(osv.osv): uom_obj = self.pool.get('product.uom') context = context or {} - uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, qty, move.product_uom.id) + uom_qty = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_uom) uos_qty = uom_qty * move.product_uos_qty / move.product_uom_qty defaults = { @@ -3463,12 +3463,12 @@ class stock_pack_operation(osv.osv): else: qty = ops.product_qty if ops.product_uom_id: - qty = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, ops.product_id.uom_id.id) + qty = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, context=context) for record in ops.linked_move_operation_ids: qty -= record.qty #converting the remaining quantity in the pack operation UoM if ops.product_uom_id: - qty = uom_obj._compute_qty(cr, uid, ops.product_id.uom_id.id, qty, ops.product_uom_id.id) + qty = uom_obj._compute_qty_obj(cr, uid, ops.product_id.uom_id, qty, ops.product_uom_id, context=context) res[ops.id] = qty return res @@ -3559,7 +3559,7 @@ class stock_pack_operation(osv.osv): before = time.time() link_obj.create(cr, uid, {'move_id': quant.reservation_id.id, 'operation_id': ops.id, 'qty': quant.qty}, context=context) else: - qty = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, ops.product_id.uom_id.id) + qty = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, context=context) #Check moves with same product for move in [x for x in ops.picking_id.move_lines if ops.product_id.id == x.product_id.id]: for quant in move.reserved_quant_ids: @@ -3611,7 +3611,7 @@ class stock_pack_operation(osv.osv): op.refresh() if op.product_id: #TODO: Remaining qty: UoM conversions are done twice - normalized_qty = uom_obj._compute_qty(cr, uid, op.product_uom_id.id, op.remaining_qty, op.product_id.uom_id.id) + normalized_qty = uom_obj._compute_qty_obj(cr, uid, op.product_uom_id, op.remaining_qty, op.product_id.uom_id) if normalized_qty > 0: quants_reserve_ok = False _create_link_for_product(op.product_id.id, normalized_qty) From 93d8da8dca3951fb554c4b7d12ab748704607e3b Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 4 Mar 2014 14:54:29 +0100 Subject: [PATCH 22/22] [IMP] Put move_lines remaining quantity in dictionary instead + start profiling bzr revid: jco@openerp.com-20140304135429-xhk5uf68xz5f23q5 --- addons/stock/stock.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index acfd67c4cfd..e77bae11f8b 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -30,6 +30,7 @@ from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FO from openerp import SUPERUSER_ID import openerp.addons.decimal_precision as dp import logging +from profilehooks import profile _logger = logging.getLogger(__name__) #---------------------------------------------------------- @@ -1996,6 +1997,7 @@ class stock_move(osv.osv): packs |= set([q.package_id.id for q in move.quant_ids if q.package_id and q.qty > 0]) return pack_obj._check_location_constraint(cr, uid, list(packs), context=context) + @profile(immediate=True) def action_done(self, cr, uid, ids, context=None): """ Process completly the moves given as ids and if all moves are done, it will finish the picking. """ @@ -3444,6 +3446,15 @@ class stock_pack_operation(osv.osv): res[record.move_id.product_id.id] -= record.qty return res + def _get_remaining_qty_product_uom(self, cr, uid, ops, context=None): + uom_obj = self.pool.get('product.uom') + qty = ops.product_qty + if ops.product_uom_id: + qty = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, context=context) + for record in ops.linked_move_operation_ids: + qty -= record.qty + return qty + def _get_remaining_qty(self, cr, uid, ids, name, args, context=None): uom_obj = self.pool.get('product.uom') res = {} @@ -3531,14 +3542,12 @@ class stock_pack_operation(osv.osv): qty_to_assign = qty for move in sorted_moves: if move.product_id.id == product_id and move.state not in ['done', 'cancel']: - qty_on_link = min(move.remaining_qty, qty_to_assign) - before = time.time() + qty_on_link = min(qty_move_rem[move.id], qty_to_assign) cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values (%s, %s, %s)""", (move.id, op.id, qty_on_link,)) + qty_move_rem[move.id] -= qty_on_link # link_obj.create(cr, uid, {'move_id': move.id, 'operation_id': op.id, 'qty': qty_on_link}, context=context) - print "Link create", time.time() - before qty_to_assign -= qty_on_link - move.refresh() if qty_to_assign <= 0: break @@ -3549,8 +3558,8 @@ class stock_pack_operation(osv.osv): #Entire packages means entire quants from those packages if not quants_done.get(quant.id): quants_done[quant.id] = 0 - before = time.time() link_obj.create(cr, uid, {'move_id': quant.reservation_id.id, 'operation_id': ops.id, 'qty': quant.qty}, context=context) + qty_move_rem[quant.reservation_id.id] -= quant.qty else: qty = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, context=context) #Check moves with same product @@ -3578,6 +3587,7 @@ class stock_pack_operation(osv.osv): quants_done[quant.id] = 0 qty -= qty_todo link_obj.create(cr, uid, {'move_id': quant.reservation_id.id, 'operation_id': ops.id, 'qty': qty_todo}, context=context) + qty_move_rem[quant.reservation_id.id] -= qty_todo link_obj = self.pool.get('stock.move.operation.link') uom_obj = self.pool.get('product.uom') @@ -3585,14 +3595,20 @@ class stock_pack_operation(osv.osv): quant_obj = self.pool.get('stock.quant') quants_done = {} + qty_rem = {} + qty_move_rem = {} operations = self.browse(cr, uid, op_ids, context=context) operations.sort(key=lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0)) sorted_moves = [] for op in operations: if not sorted_moves: #sort moves in order to process first the ones that have already reserved quants + for move in op.picking_id.move_lines: + prod_qty = move.product_qty + qty_rem[move.id] = prod_qty - move.reserved_availability + qty_move_rem[move.id] = prod_qty sorted_moves = op.picking_id.move_lines - sorted_moves.sort(key=lambda x: x.product_qty - x.reserved_availability) + sorted_moves.sort(key=lambda x: qty_rem[x.id]) to_unlink_ids = [x.id for x in op.linked_move_operation_ids] if to_unlink_ids: @@ -3604,7 +3620,7 @@ class stock_pack_operation(osv.osv): op.refresh() if op.product_id: #TODO: Remaining qty: UoM conversions are done twice - normalized_qty = uom_obj._compute_qty_obj(cr, uid, op.product_uom_id, op.remaining_qty, op.product_id.uom_id) + normalized_qty = self._get_remaining_qty_product_uom(cr, uid, op, context) if normalized_qty > 0: quants_reserve_ok = False _create_link_for_product(op.product_id.id, normalized_qty)