From 8f1192e0b04a3868d0cbdb6525bd244615c5d706 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 24 Feb 2014 10:41:24 +0100 Subject: [PATCH 01/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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/48] [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) From b6bde4c4588078117ef45fdff8f320c97f47afd8 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 5 Mar 2014 15:07:39 +0100 Subject: [PATCH 23/48] [IMP] Only create extra moves or rereserve quants when necessary bzr revid: jco@openerp.com-20140305140739-ciiyt2eiu0w435kj --- addons/stock/stock.py | 50 ++++++++++++++++----------- addons/stock_account/stock_account.py | 2 +- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 41fd9192bc6..e4883310625 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1103,11 +1103,14 @@ 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 + remaining_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: - quants_res = quants_res and pack_op_obj.recompute_rem_qty_from_operation(cr, uid, op_ids, context=context) - return quants_res + (quants_ok, remaining_ok) = pack_op_obj.recompute_rem_qty_from_operation(cr, uid, op_ids, context=context) + quants_res = quants_res and quants_ok + remaining_res = remaining_res and remaining_ok + return (quants_res, remaining_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 @@ -1142,6 +1145,7 @@ class stock_picking(osv.osv): stock_move_obj.do_unreserve(cr, uid, move_ids, context=context) stock_move_obj.action_assign(cr, uid, move_ids, context=context) + @profile(immediate=True) def do_transfer(self, cr, uid, picking_ids, context=None): """ If no pack operation, we do simple action_done of the picking @@ -1155,15 +1159,14 @@ class stock_picking(osv.osv): self.action_done(cr, uid, [picking.id], context=context) continue else: - quants_ok = self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context) + (quants_ok, remaining_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) - if not quants_ok: + if not remaining_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'): @@ -1171,17 +1174,15 @@ 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) - if not quants_ok or not no_rereserve: + if (not quants_ok or not remaining_ok) and not picking.location_id.usage in ("supplier", "production", "inventory"): 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) @@ -1316,7 +1317,7 @@ class stock_move(osv.osv): def get_price_unit(self, cr, uid, move, context=None): """ Returns the unit price to store on the quant """ - return move.price_unit or move.product_id.standard_price + return move.price_unit #or move.product_id.standard_price def name_get(self, cr, uid, ids, context=None): res = [] @@ -1923,6 +1924,7 @@ class stock_move(osv.osv): if check and not lot_id: raise osv.except_osv(_('Warning!'), _('You must assign a serial number for the product %s') % (move.product_id.name)) + @profile(immediate=True) def action_assign(self, cr, uid, ids, context=None): """ Checks the product type and accordingly writes the state. """ @@ -2022,7 +2024,6 @@ 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. """ @@ -3564,10 +3565,10 @@ class stock_pack_operation(osv.osv): 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) qty_to_assign -= qty_on_link if qty_to_assign <= 0: break + return qty_to_assign == 0 def _check_quants_reserved(ops): if ops.package_id and not ops.product_id: @@ -3576,7 +3577,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 - link_obj.create(cr, uid, {'move_id': quant.reservation_id.id, 'operation_id': ops.id, 'qty': quant.qty}, context=context) + cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values + (%s, %s, %s)""", (quant.reservation_id.id, ops.id, quant.qty,)) 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) @@ -3604,8 +3606,9 @@ class stock_pack_operation(osv.osv): qty_todo = quant_qty 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 + cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values + (%s, %s, %s)""", (move.id, ops.id, qty_todo,)) + qty_move_rem[move.id] -= qty_todo link_obj = self.pool.get('stock.move.operation.link') uom_obj = self.pool.get('product.uom') @@ -3623,8 +3626,11 @@ class stock_pack_operation(osv.osv): #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_rem[move.id] = prod_qty qty_move_rem[move.id] = prod_qty + for quant in move.reserved_quant_ids: + qty_rem -= quant.qty + quants_done[quant.id] = quant.qty sorted_moves = op.picking_id.move_lines sorted_moves.sort(key=lambda x: qty_rem[x.id]) @@ -3633,22 +3639,24 @@ class stock_pack_operation(osv.osv): link_obj.unlink(cr, uid, to_unlink_ids, context=context) _check_quants_reserved(op) - quants_reserve_ok = True + remaining_qty_ok = True for op in operations: op.refresh() if op.product_id: #TODO: Remaining qty: UoM conversions are done twice 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) + remaining_qty_ok = remaining_qty_ok and _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 + if op.product_id.type != 'consu': + quants_reserve_ok = False + remaining_qty_ok = remaining_qty_ok and _create_link_for_product(product_id, qty) + + quants_reserve_ok = all([quants_done[x] == 0 for x in quants_done.keys()]) + return (quants_reserve_ok, remaining_qty_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_account/stock_account.py b/addons/stock_account/stock_account.py index 86ba2eb5327..d51282d6226 100644 --- a/addons/stock_account/stock_account.py +++ b/addons/stock_account/stock_account.py @@ -127,7 +127,7 @@ class stock_quant(osv.osv): def move_single_quant(self, cr, uid, quant, location_to, qty, move, context=None): quant_record = super(stock_quant, self).move_single_quant(cr, uid, quant, location_to, qty, move, context=context) - self._account_entry_move(cr, uid, quant, move, context=context) + #self._account_entry_move(cr, uid, quant, move, context=context) return quant_record From 09bb2beb08f6d5b9185dfa5c582470bdc182d359 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 5 Mar 2014 15:11:21 +0100 Subject: [PATCH 24/48] [IMP] remove profile bzr revid: jco@openerp.com-20140305141121-7jgkfbejffbuh922 --- addons/stock/stock.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index e4883310625..651b7702c2a 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -30,7 +30,6 @@ 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__) #---------------------------------------------------------- @@ -1145,7 +1144,6 @@ class stock_picking(osv.osv): stock_move_obj.do_unreserve(cr, uid, move_ids, context=context) stock_move_obj.action_assign(cr, uid, move_ids, context=context) - @profile(immediate=True) def do_transfer(self, cr, uid, picking_ids, context=None): """ If no pack operation, we do simple action_done of the picking @@ -1924,7 +1922,6 @@ class stock_move(osv.osv): if check and not lot_id: raise osv.except_osv(_('Warning!'), _('You must assign a serial number for the product %s') % (move.product_id.name)) - @profile(immediate=True) def action_assign(self, cr, uid, ids, context=None): """ Checks the product type and accordingly writes the state. """ From abd79cc0360266a23276fd1eae35ceaade19ad6c Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 5 Mar 2014 15:19:20 +0100 Subject: [PATCH 25/48] [FIX] Fix missing value for dict bzr revid: jco@openerp.com-20140305141920-mhtrg8mx6ieppg6i --- 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 651b7702c2a..20fb03936e9 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -3626,7 +3626,7 @@ class stock_pack_operation(osv.osv): qty_rem[move.id] = prod_qty qty_move_rem[move.id] = prod_qty for quant in move.reserved_quant_ids: - qty_rem -= quant.qty + qty_rem[move.id] -= quant.qty quants_done[quant.id] = quant.qty sorted_moves = op.picking_id.move_lines sorted_moves.sort(key=lambda x: qty_rem[x.id]) From 3e2ebaae800ab7230877945280654152619c96d7 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 5 Mar 2014 16:48:36 +0100 Subject: [PATCH 26/48] [IMP] Improve recompute with dictionary of moves for every product bzr revid: jco@openerp.com-20140305154836-f279eafnickmyjz9 --- addons/stock/stock.py | 99 +++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 46 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 20fb03936e9..cc9ff7e5456 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -31,7 +31,7 @@ from openerp import SUPERUSER_ID import openerp.addons.decimal_precision as dp import logging _logger = logging.getLogger(__name__) - +from profilehooks import profile #---------------------------------------------------------- # Incoterms #---------------------------------------------------------- @@ -382,6 +382,8 @@ class stock_quant(osv.osv): the choice on the quants that match the prefered_domain as well. But if the qty requested is not reached it tries to find the remaining quantity by using the fallback_domain. ''' + if location.usage in ['inventory', 'production', 'supplier']: + return [(None, qty)] if prefered_domain and fallback_domain: if domain is None: domain = [] @@ -502,36 +504,38 @@ 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) - for quant_neg, qty in quants: - if not quant_neg: - continue - to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id)], context=context) - if not to_solve_quant_ids: - continue - solving_qty = qty - solved_quant_ids = [] - for to_solve_quant in self.browse(cr, uid, to_solve_quant_ids, context=context): - if solving_qty <= 0: + quants = self.search(cr, uid, [('product_id', '=', quant.product_id.id), ('qty','<', 0)], context=context) + if quants: + 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 - solved_quant_ids.append(to_solve_quant.id) - self._quant_split(cr, uid, to_solve_quant, min(solving_qty, to_solve_quant.qty), context=context) - solving_qty -= min(solving_qty, to_solve_quant.qty) - remaining_solving_quant = self._quant_split(cr, uid, solving_quant, qty, context=context) - remaining_neg_quant = self._quant_split(cr, uid, quant_neg, -qty, context=context) - #if the reconciliation was not complete, we need to link together the remaining parts - if remaining_neg_quant: - remaining_to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id), ('id', 'not in', solved_quant_ids)], context=context) - if remaining_to_solve_quant_ids: - self.write(cr, SUPERUSER_ID, remaining_to_solve_quant_ids, {'propagated_from_id': remaining_neg_quant.id}, context=context) - #delete the reconciled quants, as it is replaced by the solved quants - self.unlink(cr, SUPERUSER_ID, [quant_neg.id], context=context) - #price update + accounting entries adjustments - self._price_update(cr, uid, solved_quant_ids, solving_quant.cost, context=context) - #merge history (and cost?) - self._quants_merge(cr, uid, solved_quant_ids, solving_quant, context=context) - self.unlink(cr, SUPERUSER_ID, [solving_quant.id], context=context) - solving_quant = remaining_solving_quant + to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id)], context=context) + if not to_solve_quant_ids: + continue + solving_qty = qty + solved_quant_ids = [] + for to_solve_quant in self.browse(cr, uid, to_solve_quant_ids, context=context): + if solving_qty <= 0: + continue + solved_quant_ids.append(to_solve_quant.id) + self._quant_split(cr, uid, to_solve_quant, min(solving_qty, to_solve_quant.qty), context=context) + solving_qty -= min(solving_qty, to_solve_quant.qty) + remaining_solving_quant = self._quant_split(cr, uid, solving_quant, qty, context=context) + remaining_neg_quant = self._quant_split(cr, uid, quant_neg, -qty, context=context) + #if the reconciliation was not complete, we need to link together the remaining parts + if remaining_neg_quant: + remaining_to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id), ('id', 'not in', solved_quant_ids)], context=context) + if remaining_to_solve_quant_ids: + self.write(cr, SUPERUSER_ID, remaining_to_solve_quant_ids, {'propagated_from_id': remaining_neg_quant.id}, context=context) + #delete the reconciled quants, as it is replaced by the solved quants + self.unlink(cr, SUPERUSER_ID, [quant_neg.id], context=context) + #price update + accounting entries adjustments + self._price_update(cr, uid, solved_quant_ids, solving_quant.cost, context=context) + #merge history (and cost?) + self._quants_merge(cr, uid, solved_quant_ids, solving_quant, context=context) + self.unlink(cr, SUPERUSER_ID, [solving_quant.id], context=context) + solving_quant = remaining_solving_quant def _price_update(self, cr, uid, ids, newprice, context=None): self.write(cr, SUPERUSER_ID, ids, {'cost': newprice}, context=context) @@ -549,8 +553,8 @@ class stock_quant(osv.osv): domain += location and [('location_id', 'child_of', location.id)] or [] domain += [('product_id', '=', product.id)] + domain #don't take into account location that are production, supplier or inventory - ignore_location_ids = self.pool.get('stock.location').search(cr, uid, [('usage', 'in', ('production', 'supplier', 'inventory'))], context=context) - domain.append(('location_id','not in',ignore_location_ids)) + #ignore_location_ids = self.pool.get('stock.location').search(cr, uid, [('usage', 'in', ('production', 'supplier', 'inventory'))], context=context) + #domain.append(('location_id','not in',ignore_location_ids)) res = [] offset = 0 while quantity > 0: @@ -1144,6 +1148,7 @@ class stock_picking(osv.osv): stock_move_obj.do_unreserve(cr, uid, move_ids, context=context) stock_move_obj.action_assign(cr, uid, move_ids, context=context) + def do_transfer(self, cr, uid, picking_ids, context=None): """ If no pack operation, we do simple action_done of the picking @@ -3552,19 +3557,18 @@ class stock_pack_operation(osv.osv): res_id = super(stock_pack_operation, self).create(cr, uid, vals, context=context) self.recompute_rem_qty_from_operation(cr, uid, [res_id], context=context) return res_id - + @profile(immediate=True) def recompute_rem_qty_from_operation(self, cr, uid, op_ids, context=None): def _create_link_for_product(product_id, qty): 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(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 - qty_to_assign -= qty_on_link - if qty_to_assign <= 0: - break + for move in prod_move[product_id]: + 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 + qty_to_assign -= qty_on_link + if qty_to_assign <= 0: + break return qty_to_assign == 0 def _check_quants_reserved(ops): @@ -3612,7 +3616,7 @@ class stock_pack_operation(osv.osv): package_obj = self.pool.get('stock.quant.package') quant_obj = self.pool.get('stock.quant') quants_done = {} - + prod_move = {} qty_rem = {} qty_move_rem = {} operations = self.browse(cr, uid, op_ids, context=context) @@ -3628,8 +3632,13 @@ class stock_pack_operation(osv.osv): for quant in move.reserved_quant_ids: qty_rem[move.id] -= quant.qty quants_done[quant.id] = quant.qty - sorted_moves = op.picking_id.move_lines + sorted_moves = [x for x in op.picking_id.move_lines if x.state not in ['done', 'cancel']] sorted_moves.sort(key=lambda x: qty_rem[x.id]) + for move in sorted_moves: + if not prod_move.get(move.product_id.id): + prod_move[move.product_id.id] = [move] + else: + prod_move[move.product_id.id].append(move) to_unlink_ids = [x.id for x in op.linked_move_operation_ids] if to_unlink_ids: @@ -3648,8 +3657,6 @@ class stock_pack_operation(osv.osv): prod_quants = self._get_remaining_prod_quantities(cr, uid, op, context=context) for product_id, qty in prod_quants.items(): if qty > 0: - if op.product_id.type != 'consu': - quants_reserve_ok = False remaining_qty_ok = remaining_qty_ok and _create_link_for_product(product_id, qty) quants_reserve_ok = all([quants_done[x] == 0 for x in quants_done.keys()]) From 9aa49685ff4cdb362ebba272cec830ee53ef0f33 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 5 Mar 2014 18:05:51 +0100 Subject: [PATCH 27/48] [WIP] The time for recompute quantities can even become smaller when unlinking with a sql statement bzr revid: jco@openerp.com-20140305170551-5px8czdvsmotdva3 --- addons/sale/sale.py | 3 ++- addons/stock/stock.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 51b551e15c1..10e255031e6 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -27,6 +27,7 @@ from openerp.tools.translate import _ from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP, float_compare import openerp.addons.decimal_precision as dp from openerp import workflow +from profilehooks import profile class sale_order(osv.osv): _name = "sale.order" @@ -668,7 +669,7 @@ class sale_order(osv.osv): for sale_order in self.browse(cr, uid, ids, context=context): self.write(cr, uid, ids, {'state': 'progress' if sale_order.invoice_exists else 'manual'}, context=context) return True - + @profile(immediate=True) def action_ship_create(self, cr, uid, ids, context=None): """Create the required procurements to supply sales order lines, also connecting the procurements to appropriate stock moves in order to bring the goods to the diff --git a/addons/stock/stock.py b/addons/stock/stock.py index cc9ff7e5456..ed178d96495 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1148,7 +1148,7 @@ class stock_picking(osv.osv): stock_move_obj.do_unreserve(cr, uid, move_ids, context=context) stock_move_obj.action_assign(cr, uid, move_ids, context=context) - + @profile(immediate=True) def do_transfer(self, cr, uid, picking_ids, context=None): """ If no pack operation, we do simple action_done of the picking @@ -3557,7 +3557,7 @@ class stock_pack_operation(osv.osv): res_id = super(stock_pack_operation, self).create(cr, uid, vals, context=context) self.recompute_rem_qty_from_operation(cr, uid, [res_id], context=context) return res_id - @profile(immediate=True) + def recompute_rem_qty_from_operation(self, cr, uid, op_ids, context=None): def _create_link_for_product(product_id, qty): qty_to_assign = qty @@ -3610,6 +3610,7 @@ class stock_pack_operation(osv.osv): cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values (%s, %s, %s)""", (move.id, ops.id, qty_todo,)) qty_move_rem[move.id] -= qty_todo + qty_op_rem[ops] = qty link_obj = self.pool.get('stock.move.operation.link') uom_obj = self.pool.get('product.uom') @@ -3619,9 +3620,13 @@ class stock_pack_operation(osv.osv): prod_move = {} qty_rem = {} qty_move_rem = {} + qty_op_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 = [] + cr.execute("""DELETE FROM stock_move_operation_link WHERE + operation_id in %s + """, (tuple([x.id for x in operations]),)) for op in operations: if not sorted_moves: #sort moves in order to process first the ones that have already reserved quants @@ -3640,6 +3645,7 @@ class stock_pack_operation(osv.osv): else: prod_move[move.product_id.id].append(move) + to_unlink_ids = [x.id for x in op.linked_move_operation_ids] if to_unlink_ids: link_obj.unlink(cr, uid, to_unlink_ids, context=context) @@ -3650,7 +3656,7 @@ class stock_pack_operation(osv.osv): op.refresh() if op.product_id: #TODO: Remaining qty: UoM conversions are done twice - normalized_qty = self._get_remaining_qty_product_uom(cr, uid, op, context) + normalized_qty = qty_op_rem[op] if normalized_qty > 0: remaining_qty_ok = remaining_qty_ok and _create_link_for_product(op.product_id.id, normalized_qty) elif op.package_id: From 6d8ef17022a896f7d1ed21569e7141f380071e80 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 6 Mar 2014 14:24:03 +0100 Subject: [PATCH 28/48] [IMP] Wrong use of product_uom_qty not necessary anymore + limit first search reconcile negative to 1 bzr revid: jco@openerp.com-20140306132403-5l5706scs1i4izb2 --- addons/stock/stock.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index ed178d96495..80d07c2a591 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -504,7 +504,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.search(cr, uid, [('product_id', '=', quant.product_id.id), ('qty','<', 0)], context=context) + quants = self.search(cr, uid, [('product_id', '=', quant.product_id.id), ('qty','<', 0)], limit=1, context=context) if quants: quants = self.quants_get(cr, uid, quant.location_id, quant.product_id, quant.qty, dom, context=context) for quant_neg, qty in quants: @@ -2080,10 +2080,7 @@ 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] - 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) + 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) From e75cb3135caa585f8f145945e89f16eabc472dc4 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 6 Mar 2014 14:49:43 +0100 Subject: [PATCH 29/48] [IMP] Remove profilehooks and simplify reconcile quants bzr revid: jco@openerp.com-20140306134943-cdzw1mylyrtfzjaa --- addons/sale/sale.py | 4 ++-- addons/stock/stock.py | 18 ++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 10e255031e6..ebd9a6ac39a 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -27,7 +27,7 @@ from openerp.tools.translate import _ from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP, float_compare import openerp.addons.decimal_precision as dp from openerp import workflow -from profilehooks import profile + class sale_order(osv.osv): _name = "sale.order" @@ -669,7 +669,7 @@ class sale_order(osv.osv): for sale_order in self.browse(cr, uid, ids, context=context): self.write(cr, uid, ids, {'state': 'progress' if sale_order.invoice_exists else 'manual'}, context=context) return True - @profile(immediate=True) + def action_ship_create(self, cr, uid, ids, context=None): """Create the required procurements to supply sales order lines, also connecting the procurements to appropriate stock moves in order to bring the goods to the diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 80d07c2a591..4e7793003d0 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -31,7 +31,6 @@ from openerp import SUPERUSER_ID import openerp.addons.decimal_precision as dp import logging _logger = logging.getLogger(__name__) -from profilehooks import profile #---------------------------------------------------------- # Incoterms #---------------------------------------------------------- @@ -496,16 +495,16 @@ class stock_quant(osv.osv): """ if quant.location_id.usage != 'internal': return False - solving_quant = quant - dom = [('qty', '<', 0)] - if quant.lot_id: - dom += [('lot_id', '=', quant.lot_id.id)] - dom += [('owner_id', '=', quant.owner_id.id)] - dom += [('package_id', '=', quant.package_id.id)] - if move.move_dest_id: - dom += [('negative_move_id', '=', move.move_dest_id.id)] quants = self.search(cr, uid, [('product_id', '=', quant.product_id.id), ('qty','<', 0)], limit=1, context=context) if quants: + solving_quant = quant + dom = [('qty', '<', 0)] + if quant.lot_id: + dom += [('lot_id', '=', quant.lot_id.id)] + dom += [('owner_id', '=', quant.owner_id.id)] + 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) for quant_neg, qty in quants: if not quant_neg: @@ -1148,7 +1147,6 @@ class stock_picking(osv.osv): stock_move_obj.do_unreserve(cr, uid, move_ids, context=context) stock_move_obj.action_assign(cr, uid, move_ids, context=context) - @profile(immediate=True) def do_transfer(self, cr, uid, picking_ids, context=None): """ If no pack operation, we do simple action_done of the picking From 428d5e1af184887275c6708765908ce22259ecae Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 6 Mar 2014 17:01:06 +0100 Subject: [PATCH 30/48] [FIX] Take case product in ops exist, but not in move bzr revid: jco@openerp.com-20140306160106-ydz47uoh66d1nbip --- addons/stock/stock.py | 17 +++++++++-------- .../wizard/stock_valuation_history.py | 5 +++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 4e7793003d0..0663c55f2e2 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -3556,14 +3556,15 @@ class stock_pack_operation(osv.osv): def recompute_rem_qty_from_operation(self, cr, uid, op_ids, context=None): def _create_link_for_product(product_id, qty): qty_to_assign = qty - for move in prod_move[product_id]: - 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 - qty_to_assign -= qty_on_link - if qty_to_assign <= 0: - break + if prod_move.get(product_id): + for move in prod_move[product_id]: + 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 + qty_to_assign -= qty_on_link + if qty_to_assign <= 0: + break return qty_to_assign == 0 def _check_quants_reserved(ops): diff --git a/addons/stock_account/wizard/stock_valuation_history.py b/addons/stock_account/wizard/stock_valuation_history.py index 7657ac53b4b..49eca2a97ea 100644 --- a/addons/stock_account/wizard/stock_valuation_history.py +++ b/addons/stock_account/wizard/stock_valuation_history.py @@ -48,8 +48,9 @@ class stock_history(osv.osv): if '__domain' in line: lines = self.search(cr, uid, line['__domain'], context=context) inv_value = 0.0 - for line2 in self.browse(cr, uid, lines, context=context): - inv_value += line2.inventory_value + lines2 = self._get_inventory_value(cr, uid, lines, "", [], context) + for line2 in lines2.keys(): + inv_value += lines2[line2] line['inventory_value'] = inv_value return res From e750b270a170094e522e560aeaf06714762176d7 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 6 Mar 2014 19:09:11 +0100 Subject: [PATCH 31/48] [IMP] Change packages to use dictionary in recompute remaining qtys also bzr revid: jco@openerp.com-20140306180911-6gumeek19otznb1w --- addons/stock/stock.py | 81 ++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 0663c55f2e2..0323aa6cc25 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 + _logger = logging.getLogger(__name__) #---------------------------------------------------------- # Incoterms @@ -3397,6 +3398,17 @@ class stock_package(osv.osv): child_package_ids = self.search(cr, uid, [('id', 'child_of', ids)], context=context) return self.pool.get('stock.quant').search(cr, uid, [('package_id', 'in', child_package_ids)], context=context) + def get_contents(self, cr, uid, pack, context=None): + quants = pack.quant_ids + children_ids = pack.children_ids + while children_ids: + children2_ids = [] + for child in children_ids: + quants += child.quant_ids + children2_ids += child.children_ids + children_ids = children2_ids + return quants + def get_content_package(self, cr, uid, ids, context=None): quants_ids = self.get_content(cr, uid, ids, context=context) res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'stock', 'quantsact', context=context) @@ -3569,7 +3581,8 @@ class stock_pack_operation(osv.osv): def _check_quants_reserved(ops): if ops.package_id and not ops.product_id: - for quant in quant_obj.browse(cr, uid, package_obj.get_content(cr, uid, [ops.package_id.id]), context=context): + qty_op_rem[ops.id] = {} + for quant in self.get_contents(cr, uid, ops.package_id): if quant.reservation_id and quant.reservation_id.id in [x.id for x in ops.picking_id.move_lines] and (not quants_done.get(quant.id)): #Entire packages means entire quants from those packages if not quants_done.get(quant.id): @@ -3577,36 +3590,40 @@ class stock_pack_operation(osv.osv): cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values (%s, %s, %s)""", (quant.reservation_id.id, ops.id, quant.qty,)) qty_move_rem[quant.reservation_id.id] -= quant.qty + else: + if qty_op_rem[ops.id].get(quant.product_id.id): + qty_op_rem[ops.id][quant.product_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 - 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: - if not qty > 0: - break - if ops.package_id: - flag = quant.package_id and bool(package_obj.search(cr, uid, [('id', 'child_of', [ops.package_id.id]), ('id', '=', quant.package_id.id)], context=context)) or False - else: - flag = not quant.package_id.id - flag = flag and ((ops.lot_id and ops.lot_id.id == quant.lot_id.id) or not ops.lot_id) - flag = flag and (ops.owner_id.id == quant.owner_id.id) - if flag: - quant_qty = quant.qty - if quants_done.get(quant.id): - if quants_done[quant.id] == 0: - continue - quant_qty = quants_done[quant.id] - if quant_qty > qty: - qty_todo = qty - quants_done[quant.id] = quant_qty - qty + if prod_move.get(ops.product_id.id): + for move in prod_move[ops.product_id.id]: + for quant in move.reserved_quant_ids: + if not qty > 0: + break + if ops.package_id: + flag = quant.package_id and bool(package_obj.search(cr, uid, [('id', 'child_of', [ops.package_id.id]), ('id', '=', quant.package_id.id)], context=context)) or False else: - qty_todo = quant_qty - quants_done[quant.id] = 0 - qty -= qty_todo - cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values - (%s, %s, %s)""", (move.id, ops.id, qty_todo,)) - qty_move_rem[move.id] -= qty_todo - qty_op_rem[ops] = qty + flag = not quant.package_id.id + flag = flag and ((ops.lot_id and ops.lot_id.id == quant.lot_id.id) or not ops.lot_id) + flag = flag and (ops.owner_id.id == quant.owner_id.id) + if flag: + quant_qty = quant.qty + if quants_done.get(quant.id): + if quants_done[quant.id] == 0: + continue + quant_qty = quants_done[quant.id] + if quant_qty > qty: + qty_todo = qty + quants_done[quant.id] = quant_qty - qty + else: + qty_todo = quant_qty + quants_done[quant.id] = 0 + qty -= qty_todo + cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values + (%s, %s, %s)""", (move.id, ops.id, qty_todo,)) + qty_move_rem[move.id] -= qty_todo + qty_op_rem[ops.id] = qty link_obj = self.pool.get('stock.move.operation.link') uom_obj = self.pool.get('product.uom') @@ -3640,11 +3657,6 @@ class stock_pack_operation(osv.osv): prod_move[move.product_id.id] = [move] else: prod_move[move.product_id.id].append(move) - - - to_unlink_ids = [x.id for x in op.linked_move_operation_ids] - if to_unlink_ids: - link_obj.unlink(cr, uid, to_unlink_ids, context=context) _check_quants_reserved(op) remaining_qty_ok = True @@ -3652,12 +3664,11 @@ class stock_pack_operation(osv.osv): op.refresh() if op.product_id: #TODO: Remaining qty: UoM conversions are done twice - normalized_qty = qty_op_rem[op] + normalized_qty = qty_op_rem[op.id] if normalized_qty > 0: remaining_qty_ok = remaining_qty_ok and _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(): + for product_id, qty in qty_op_rem[op.id].items(): if qty > 0: remaining_qty_ok = remaining_qty_ok and _create_link_for_product(product_id, qty) From 63763ef224ca5ed4f1dd84f6919291f9db45a5cc Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 6 Mar 2014 19:10:01 +0100 Subject: [PATCH 32/48] [IMP] Create accounting entries to pass runbot bzr revid: jco@openerp.com-20140306181001-cnna3jojs91fm1d5 --- addons/stock_account/stock_account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py index d51282d6226..86ba2eb5327 100644 --- a/addons/stock_account/stock_account.py +++ b/addons/stock_account/stock_account.py @@ -127,7 +127,7 @@ class stock_quant(osv.osv): def move_single_quant(self, cr, uid, quant, location_to, qty, move, context=None): quant_record = super(stock_quant, self).move_single_quant(cr, uid, quant, location_to, qty, move, context=context) - #self._account_entry_move(cr, uid, quant, move, context=context) + self._account_entry_move(cr, uid, quant, move, context=context) return quant_record From 146f04cff022861d7cd0ceb557aa9a2df686b178 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Fri, 7 Mar 2014 10:29:53 +0100 Subject: [PATCH 33/48] [FIX] Match reserved package should be correct bzr revid: jco@openerp.com-20140307092953-zg05xusw9xctol8b --- addons/stock/stock.py | 5 +++-- addons/stock_account/wizard/stock_valuation_history.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 0323aa6cc25..3dcaf7f7abf 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -3582,8 +3582,9 @@ class stock_pack_operation(osv.osv): def _check_quants_reserved(ops): if ops.package_id and not ops.product_id: qty_op_rem[ops.id] = {} - for quant in self.get_contents(cr, uid, ops.package_id): - if quant.reservation_id and quant.reservation_id.id in [x.id for x in ops.picking_id.move_lines] and (not quants_done.get(quant.id)): + package_obj.get_contents(cr, uid, ops.package_id) + for quant in package_obj.get_contents(cr, uid, ops.package_id): + if quant.id in quants_done.keys() and (quants_done[quant.id] == quant.qty): #Entire packages means entire quants from those packages if not quants_done.get(quant.id): quants_done[quant.id] = 0 diff --git a/addons/stock_account/wizard/stock_valuation_history.py b/addons/stock_account/wizard/stock_valuation_history.py index 49eca2a97ea..614f83fde6b 100644 --- a/addons/stock_account/wizard/stock_valuation_history.py +++ b/addons/stock_account/wizard/stock_valuation_history.py @@ -57,6 +57,7 @@ class stock_history(osv.osv): def _get_inventory_value(self, cr, uid, ids, name, attr, context=None): product_obj = self.pool.get("product.product") res = {} + #Browse takes an immense amount of time because it seems to reload the report for line in self.browse(cr, uid, ids, context=context): if line.product_id.cost_method == 'real': res[line.id] = line.quantity * line.price_unit_on_quant From 7d2665a9247415ebdcc7e0b18fd529f3e07b60e2 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Fri, 7 Mar 2014 11:33:12 +0100 Subject: [PATCH 34/48] [IMP] To optimize quants_reserve, pass reserved availability bzr revid: jco@openerp.com-20140307103312-oecawgyyjf4u3l25 --- addons/stock/stock.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 3dcaf7f7abf..a1106c73dcf 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__) #---------------------------------------------------------- @@ -296,7 +297,7 @@ class stock_quant(osv.osv): result['domain'] = "[('id','in',[" + ','.join(map(str, move_ids)) + "])]" return result - def quants_reserve(self, cr, uid, quants, move, link=False, context=None): + def quants_reserve(self, cr, uid, quants, move, link=False, qty_assigned_before = False, context=None): '''This function reserves quants for the given move (and optionally given link). If the total of quantity reserved is enough, the move's state is also set to 'assigned' @@ -305,6 +306,10 @@ class stock_quant(osv.osv): :param link: browse record (stock.move.operation.link) ''' toreserve = [] + if not qty_assigned_before: + reserved_availability = move.reserved_availability + else: + reserved_availability = qty_assigned_before #split quants if needed for quant, qty in quants: if qty <= 0.0 or (quant and quant.qty <= 0.0): @@ -313,14 +318,14 @@ class stock_quant(osv.osv): continue self._quant_split(cr, uid, quant, qty, context=context) toreserve.append(quant.id) + reserved_availability += quant.qty #reserve quants if toreserve: self.write(cr, SUPERUSER_ID, toreserve, {'reservation_id': move.id, 'link_move_operation_id': link and link.id or False}, context=context) #check if move'state needs to be set as 'assigned' - move.refresh() - if move.reserved_availability == move.product_qty and move.state in ('confirmed', 'waiting'): + if 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: + elif 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, location_dest_id = False, context=None): @@ -958,6 +963,8 @@ class stock_picking(osv.osv): remaining_dict[product] = [(qtys_remaining[product], picking.location_dest_id,)] return (quant_dict, remaining_dict,) + + @profile(immediate=True) def do_prepare_partial(self, cr, uid, picking_ids, context=None): context = context or {} pack_operation_obj = self.pool.get('stock.pack.operation') @@ -1088,6 +1095,7 @@ class stock_picking(osv.osv): 'location_dest_id': key[5], 'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id, }, context=context) + self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context) def do_unreserve(self, cr, uid, picking_ids, context=None): """ @@ -1926,6 +1934,7 @@ class stock_move(osv.osv): if check and not lot_id: raise osv.except_osv(_('Warning!'), _('You must assign a serial number for the product %s') % (move.product_id.name)) + @profile(immediate=True) def action_assign(self, cr, uid, ids, context=None): """ Checks the product type and accordingly writes the state. """ @@ -1988,7 +1997,7 @@ class stock_move(osv.osv): qty_already_assigned = move.reserved_availability qty = move.product_qty - qty_already_assigned quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain[move.id], prefered_domain=[], fallback_domain=[], restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) - quant_obj.quants_reserve(cr, uid, quants, move, context=context) + quant_obj.quants_reserve(cr, uid, quants, move, qty_assigned_before = qty_already_assigned, context=context) #force assignation of consumable products and picking type auto_force_assign if to_assign_moves: @@ -3557,12 +3566,12 @@ class stock_pack_operation(osv.osv): res = super(stock_pack_operation, self).write(cr, uid, ids, vals, context=context) if isinstance(ids, (int, long)): ids = [ids] - self.recompute_rem_qty_from_operation(cr, uid, ids, context=context) + #self.recompute_rem_qty_from_operation(cr, uid, ids, context=context) return res def create(self, cr, uid, vals, context=None): res_id = super(stock_pack_operation, self).create(cr, uid, vals, context=context) - self.recompute_rem_qty_from_operation(cr, uid, [res_id], context=context) + #self.recompute_rem_qty_from_operation(cr, uid, [res_id], context=context) return res_id def recompute_rem_qty_from_operation(self, cr, uid, op_ids, context=None): From 07a5526f0873de72487bb97994e2b2026f7782d4 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Fri, 7 Mar 2014 14:11:09 +0100 Subject: [PATCH 35/48] [IMP] For package checking in action_done, pass browserecords instead of ids bzr revid: jco@openerp.com-20140307131109-z7x1sy2n8v8x0h4n --- addons/stock/stock.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index a1106c73dcf..d71e653fa57 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -30,7 +30,6 @@ 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__) #---------------------------------------------------------- @@ -964,7 +963,7 @@ class stock_picking(osv.osv): return (quant_dict, remaining_dict,) - @profile(immediate=True) + def do_prepare_partial(self, cr, uid, picking_ids, context=None): context = context or {} pack_operation_obj = self.pool.get('stock.pack.operation') @@ -1156,6 +1155,7 @@ class stock_picking(osv.osv): stock_move_obj.do_unreserve(cr, uid, move_ids, context=context) stock_move_obj.action_assign(cr, uid, move_ids, context=context) + def do_transfer(self, cr, uid, picking_ids, context=None): """ If no pack operation, we do simple action_done of the picking @@ -1934,7 +1934,6 @@ class stock_move(osv.osv): if check and not lot_id: raise osv.except_osv(_('Warning!'), _('You must assign a serial number for the product %s') % (move.product_id.name)) - @profile(immediate=True) def action_assign(self, cr, uid, ids, context=None): """ Checks the product type and accordingly writes the state. """ @@ -2027,11 +2026,11 @@ class stock_move(osv.osv): self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}) return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}) - def _check_package_from_moves(self, cr, uid, ids, context=None): + def _check_package_from_moves(self, cr, uid, moves, context=None): pack_obj = self.pool.get("stock.quant.package") packs = set() - for move in self.browse(cr, uid, ids, context=context): - packs |= set([q.package_id.id for q in move.quant_ids if q.package_id and q.qty > 0]) + for move in moves: + packs |= set([q.package_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) def action_done(self, cr, uid, ids, context=None): @@ -2104,7 +2103,7 @@ class stock_move(osv.osv): procurement_ids.append(move.procurement_id.id) # Check the packages have been placed in the correct locations - self._check_package_from_moves(cr, uid, ids, context=context) + self._check_package_from_moves(cr, uid, self.browse(cr, uid, ids, context=context), context=context) # Apply on picking self.write(cr, uid, ids, {'state': 'done', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context) self.pool.get('procurement.order').check(cr, uid, procurement_ids, context=context) @@ -3361,18 +3360,16 @@ class stock_package(osv.osv): 'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack') } - def _check_location_constraint(self, cr, uid, ids, context=None): + def _check_location_constraint(self, cr, uid, packs, context=None): '''checks that all quants in a package are stored in the same location. This function cannot be used as a constraint because it needs to be checked on pack operations (they may not call write on the package) ''' - quant_obj = self.pool.get('stock.quant') - for pack in self.browse(cr, uid, ids, context=context): + for pack in packs: parent = pack while parent.parent_id: parent = parent.parent_id - quant_ids = self.get_content(cr, uid, [parent.id], context=context) - quants = quant_obj.browse(cr, uid, quant_ids, context=context) + quants = self.get_contents(cr, uid, parent, context=context) location_id = quants and quants[0].location_id.id or False if not all([quant.location_id.id == location_id for quant in quants if quant.qty > 0]): raise osv.except_osv(_('Error'), _('Everything inside a package should be in the same location')) From 4890b35af3ba14b9b88e049ed3ddd7a9ca05d405 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Fri, 7 Mar 2014 16:04:50 +0100 Subject: [PATCH 36/48] [IMP] In do_prepare_partial get contents of packages through browse records bzr revid: jco@openerp.com-20140307150450-vjpb60n88qkt7ekj --- addons/stock/stock.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index d71e653fa57..55821a1a99a 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -963,7 +963,6 @@ class stock_picking(osv.osv): return (quant_dict, remaining_dict,) - def do_prepare_partial(self, cr, uid, picking_ids, context=None): context = context or {} pack_operation_obj = self.pool.get('stock.pack.operation') @@ -1007,14 +1006,14 @@ class stock_picking(osv.osv): good_pack = False test_pack = pack while loop: - quants = pack_obj.get_content(cr, uid, [test_pack.id], context=context) + quants = pack_obj.get_contents(cr, uid, test_pack, context=context) quants_to_compare = putaway_quants_dict.keys() # move_list = [m.id for m in picking.move_lines] common_location = False all_in = True - for quant in quant_obj.browse(cr, uid, quants, context=context): + for quant in quants: # If the quant is not in the quants to compare and not in the common location - if not quant in quants_to_compare: + if not quant.id in quants_to_compare: all_in = False break else: From 8ac2d3557935ad0c736288718bae2d66402a17af Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Fri, 7 Mar 2014 17:15:38 +0100 Subject: [PATCH 37/48] [IMP] Use cached browse records instead of reserved_availability bzr revid: jco@openerp.com-20140307161538-pu23ueb784ldd2z4 --- addons/stock/stock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 55821a1a99a..aa6440dbaed 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -987,7 +987,7 @@ class stock_picking(osv.osv): continue reserved_move[move.id] = set([x.id for x in move.reserved_quant_ids]) if move.state == 'assigned': - qty = move.product_qty - move.reserved_availability + qty = move.product_qty - sum([x.qty for x in move.reserved_quant_ids]) else: qty = 0 @@ -997,7 +997,7 @@ class stock_picking(osv.osv): else: qtys_remaining[move.product_id] = qty (putaway_quants_dict, putaway_remaining_dict) = self._putaway_apply(cr, uid, picking, quants, qtys_remaining, context=context) - packages = list(set([x.package_id for x in putaway_quants_dict.keys() if x and x.package_id])) + packages = list(set([x.package_id for x in quants if x and x.package_id])) # Try to find as much as possible top-level packages that can be moved top_lvl_packages = set() From 2e7fedb889c44b5b4b1e9c38367f6e8eedea1b03 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 10 Mar 2014 16:11:33 +0100 Subject: [PATCH 38/48] [IMP] Recompute quantities always on picking and disabling when doing prepare partial + prepare partial / recompute optimize bzr revid: jco@openerp.com-20140310151133-ku0v80g148a6eyks --- addons/stock/stock.py | 273 +++++++++++++++++++++--------------------- 1 file changed, 139 insertions(+), 134 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index aa6440dbaed..2fc5e6a642d 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -965,16 +965,18 @@ class stock_picking(osv.osv): def do_prepare_partial(self, cr, uid, picking_ids, context=None): context = context or {} + ctx = context.copy() + ctx['no_recompute'] = True pack_operation_obj = self.pool.get('stock.pack.operation') pack_obj = self.pool.get("stock.quant.package") quant_obj = self.pool.get("stock.quant") - #get list of existing operations and delete them existing_package_ids = pack_operation_obj.search(cr, uid, [('picking_id', 'in', picking_ids)], context=context) if existing_package_ids: pack_operation_obj.unlink(cr, uid, existing_package_ids, context) + for picking in self.browse(cr, uid, picking_ids, context=context): reserved_move = {} qtys_remaining = {} #Quantity remaining after calculating reserved quants @@ -1006,14 +1008,12 @@ class stock_picking(osv.osv): good_pack = False test_pack = pack while loop: - quants = pack_obj.get_contents(cr, uid, test_pack, context=context) - quants_to_compare = putaway_quants_dict.keys() -# move_list = [m.id for m in picking.move_lines] + quants_pack = pack_obj.get_contents(cr, uid, test_pack, context=context) common_location = False all_in = True - for quant in quants: + for quant in quants_pack: # If the quant is not in the quants to compare and not in the common location - if not quant.id in quants_to_compare: + if not quant in quants: all_in = False break else: @@ -1023,13 +1023,8 @@ class stock_picking(osv.osv): elif common_location != quants_tup[1]: all_in = False break -# if not (quant in quants_to_compare and (not common_location or putaway_quants_dict[quant][1] == common_location)): -# all_in = False -# break -# elif not common_location and quant in quants_to_compare: -# common_location = putaway_quants_dict[quant][1] if all_in: - good_pack = test_pack.id + good_pack = test_pack if test_pack.parent_id: test_pack = test_pack.parent_id else: @@ -1042,18 +1037,19 @@ class stock_picking(osv.osv): if good_pack: top_lvl_packages.add(good_pack) + # Create pack operations for the top-level packages found - for pack in pack_obj.browse(cr, uid, list(top_lvl_packages), context=context): - quants = pack_obj.get_content(cr, uid, [pack.id], context=context) - quant = quant_obj.browse(cr, uid, quants[0], context=context) + for pack in list(top_lvl_packages): + quants = pack_obj.get_contents(cr, uid, pack, context=context) + quant = quants[0] pack_operation_obj.create(cr, uid, { 'picking_id': picking.id, 'package_id': pack.id, 'product_qty': 1.0, 'location_id': pack.location_id.id, 'location_dest_id': putaway_quants_dict[quant][0][1].id, - }, context=context) - for quant in quant_obj.browse(cr, uid, quants, context=context): + }, context=ctx) + for quant in quants: reserved_move[quant.reservation_id.id] -= set([quant.id]) del putaway_quants_dict[quant] qtys_remaining[quant.product_id] -= quant.qty @@ -1092,8 +1088,8 @@ class stock_picking(osv.osv): 'location_id': key[4], 'location_dest_id': key[5], 'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id, - }, context=context) - self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context) + }, context=ctx) + self.recompute_remaining_qty(cr, uid, picking, context=context) def do_unreserve(self, cr, uid, picking_ids, context=None): """ @@ -1109,18 +1105,129 @@ class stock_picking(osv.osv): self.pool.get('stock.pack.operation').unlink(cr, uid, pack_line_to_unreserve, context=context) self.pool.get('stock.move').do_unreserve(cr, uid, moves_to_unreserve, context=context) + def recompute_remaining_qty(self, cr, uid, picking, context=None): + def _create_link_for_product(product_id, qty): + qty_to_assign = qty + if prod_move.get(product_id): + for move in prod_move[product_id]: + 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 + qty_to_assign -= qty_on_link + if qty_to_assign <= 0: + break + return qty_to_assign == 0 + + def _check_quants_reserved(ops): + if ops.package_id and not ops.product_id: + qty_op_rem[ops.id] = {} + package_obj.get_contents(cr, uid, ops.package_id) + for quant in package_obj.get_contents(cr, uid, ops.package_id): + if quant.id in quants_done.keys() and (quants_done[quant.id] == quant.qty): + #Entire packages means entire quants from those packages + if not quants_done.get(quant.id): + quants_done[quant.id] = 0 + cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values + (%s, %s, %s)""", (quant.reservation_id.id, ops.id, quant.qty,)) + qty_move_rem[quant.reservation_id.id] -= quant.qty + else: + if qty_op_rem[ops.id].get(quant.product_id.id): + qty_op_rem[ops.id][quant.product_id.id] += quant.qty + else: + qty_op_rem[ops.id][quant.product_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 + if prod_move.get(ops.product_id.id): + for move in prod_move[ops.product_id.id]: + for quant in move.reserved_quant_ids: + if not qty > 0: + break + if ops.package_id: + flag = quant.package_id and bool(package_obj.search(cr, uid, [('id', 'child_of', [ops.package_id.id]), ('id', '=', quant.package_id.id)], context=context)) or False + else: + flag = not quant.package_id.id + flag = flag and ((ops.lot_id and ops.lot_id.id == quant.lot_id.id) or not ops.lot_id) + flag = flag and (ops.owner_id.id == quant.owner_id.id) + if flag: + quant_qty = quant.qty + if quants_done.get(quant.id): + if quants_done[quant.id] == 0: + continue + quant_qty = quants_done[quant.id] + if quant_qty > qty: + qty_todo = qty + quants_done[quant.id] = quant_qty - qty + else: + qty_todo = quant_qty + quants_done[quant.id] = 0 + qty -= qty_todo + cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values + (%s, %s, %s)""", (move.id, ops.id, qty_todo,)) + qty_move_rem[move.id] -= qty_todo + qty_op_rem[ops.id] = qty + + uom_obj = self.pool.get('product.uom') + package_obj = self.pool.get('stock.quant.package') + quants_done = {} + prod_move = {} + qty_rem = {} + qty_move_rem = {} + qty_op_rem = {} + operations = picking.pack_operation_ids + 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)) + cr.execute("""DELETE FROM stock_move_operation_link WHERE + operation_id in %s + """, (tuple([x.id for x in operations]),)) + for move in picking.move_lines: + prod_qty = move.product_qty + qty_rem[move.id] = prod_qty + qty_move_rem[move.id] = prod_qty + #Fill moves by product dict + if not prod_move.get(move.product_id.id): + prod_move[move.product_id.id] = [move] + else: + prod_move[move.product_id.id].append(move) + # Fill qty remaining dict + for quant in move.reserved_quant_ids: + quants_done[quant.id] = quant.qty + sorted_moves = [x for x in picking.move_lines if x.state not in ['done', 'cancel']] + sorted_moves.sort(key=lambda x: qty_rem[x.id]) + + for op in operations: + _check_quants_reserved(op) + + remaining_qty_ok = True + for op in operations: + op.refresh() + if op.product_id: + #TODO: Remaining qty: UoM conversions are done twice + normalized_qty = qty_op_rem[op.id] + if normalized_qty > 0: + remaining_qty_ok = remaining_qty_ok and _create_link_for_product(op.product_id.id, normalized_qty) + elif op.package_id: + for product_id, qty in qty_op_rem[op.id].items(): + if qty > 0: + remaining_qty_ok = remaining_qty_ok and _create_link_for_product(product_id, qty) + + quants_reserve_ok = all([quants_done[x] == 0 for x in quants_done.keys()]) + return (quants_reserve_ok, remaining_qty_ok) + + def do_recompute_remaining_quantities(self, cr, uid, picking_ids, context=None): - pack_op_obj = self.pool.get('stock.pack.operation') quants_res = True remaining_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: - (quants_ok, remaining_ok) = pack_op_obj.recompute_rem_qty_from_operation(cr, uid, op_ids, context=context) + if picking.pack_operation_ids: + (quants_ok, remaining_ok) = self.recompute_remaining_qty(cr, uid, picking, context=context) quants_res = quants_res and quants_ok remaining_res = remaining_res and remaining_ok return (quants_res, remaining_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 unexpected product transfers (or exceeding quantities) found in the pack operations. @@ -3562,124 +3669,22 @@ class stock_pack_operation(osv.osv): res = super(stock_pack_operation, self).write(cr, uid, ids, vals, context=context) if isinstance(ids, (int, long)): ids = [ids] - #self.recompute_rem_qty_from_operation(cr, uid, ids, context=context) + if not context.get("no_recompute"): + if vals.get('picking_id'): + self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, [vals['picking_id']], context=context) + else: + moves = self.browse(cr, uid, ids, context=context) + pickings = list(set([x.picking_id.id for x in moves])) + self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, pickings, context=context) return res def create(self, cr, uid, vals, context=None): res_id = super(stock_pack_operation, self).create(cr, uid, vals, context=context) - #self.recompute_rem_qty_from_operation(cr, uid, [res_id], context=context) + if vals.get("picking_id") and not context.get("no_recompute"): + self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, [vals['picking_id']], context=context) return res_id - def recompute_rem_qty_from_operation(self, cr, uid, op_ids, context=None): - def _create_link_for_product(product_id, qty): - qty_to_assign = qty - if prod_move.get(product_id): - for move in prod_move[product_id]: - 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 - qty_to_assign -= qty_on_link - if qty_to_assign <= 0: - break - return qty_to_assign == 0 - def _check_quants_reserved(ops): - if ops.package_id and not ops.product_id: - qty_op_rem[ops.id] = {} - package_obj.get_contents(cr, uid, ops.package_id) - for quant in package_obj.get_contents(cr, uid, ops.package_id): - if quant.id in quants_done.keys() and (quants_done[quant.id] == quant.qty): - #Entire packages means entire quants from those packages - if not quants_done.get(quant.id): - quants_done[quant.id] = 0 - cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values - (%s, %s, %s)""", (quant.reservation_id.id, ops.id, quant.qty,)) - qty_move_rem[quant.reservation_id.id] -= quant.qty - else: - if qty_op_rem[ops.id].get(quant.product_id.id): - qty_op_rem[ops.id][quant.product_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 - if prod_move.get(ops.product_id.id): - for move in prod_move[ops.product_id.id]: - for quant in move.reserved_quant_ids: - if not qty > 0: - break - if ops.package_id: - flag = quant.package_id and bool(package_obj.search(cr, uid, [('id', 'child_of', [ops.package_id.id]), ('id', '=', quant.package_id.id)], context=context)) or False - else: - flag = not quant.package_id.id - flag = flag and ((ops.lot_id and ops.lot_id.id == quant.lot_id.id) or not ops.lot_id) - flag = flag and (ops.owner_id.id == quant.owner_id.id) - if flag: - quant_qty = quant.qty - if quants_done.get(quant.id): - if quants_done[quant.id] == 0: - continue - quant_qty = quants_done[quant.id] - if quant_qty > qty: - qty_todo = qty - quants_done[quant.id] = quant_qty - qty - else: - qty_todo = quant_qty - quants_done[quant.id] = 0 - qty -= qty_todo - cr.execute("""insert into stock_move_operation_link (move_id, operation_id, qty) values - (%s, %s, %s)""", (move.id, ops.id, qty_todo,)) - qty_move_rem[move.id] -= qty_todo - qty_op_rem[ops.id] = qty - - link_obj = self.pool.get('stock.move.operation.link') - uom_obj = self.pool.get('product.uom') - package_obj = self.pool.get('stock.quant.package') - quant_obj = self.pool.get('stock.quant') - quants_done = {} - prod_move = {} - qty_rem = {} - qty_move_rem = {} - qty_op_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 = [] - cr.execute("""DELETE FROM stock_move_operation_link WHERE - operation_id in %s - """, (tuple([x.id for x in operations]),)) - 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 - qty_move_rem[move.id] = prod_qty - for quant in move.reserved_quant_ids: - qty_rem[move.id] -= quant.qty - quants_done[quant.id] = quant.qty - sorted_moves = [x for x in op.picking_id.move_lines if x.state not in ['done', 'cancel']] - sorted_moves.sort(key=lambda x: qty_rem[x.id]) - for move in sorted_moves: - if not prod_move.get(move.product_id.id): - prod_move[move.product_id.id] = [move] - else: - prod_move[move.product_id.id].append(move) - _check_quants_reserved(op) - - remaining_qty_ok = True - for op in operations: - op.refresh() - if op.product_id: - #TODO: Remaining qty: UoM conversions are done twice - normalized_qty = qty_op_rem[op.id] - if normalized_qty > 0: - remaining_qty_ok = remaining_qty_ok and _create_link_for_product(op.product_id.id, normalized_qty) - elif op.package_id: - for product_id, qty in qty_op_rem[op.id].items(): - if qty > 0: - remaining_qty_ok = remaining_qty_ok and _create_link_for_product(product_id, qty) - - quants_reserve_ok = all([quants_done[x] == 0 for x in quants_done.keys()]) - return (quants_reserve_ok, remaining_qty_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 From 1a5e98ad6b402dade1e8c7c43a07537ec414c65a Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 10 Mar 2014 18:54:45 +0100 Subject: [PATCH 39/48] [WIP] Move multiple quants at once bzr revid: jco@openerp.com-20140310175445-qza0jpc3tx724vks --- addons/stock/stock.py | 109 +++++++++++++++----------- addons/stock_account/stock_account.py | 7 ++ 2 files changed, 72 insertions(+), 44 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 2fc5e6a642d..52327888046 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -341,9 +341,24 @@ class stock_quant(osv.osv): 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, force_location = location_dest_id, context=context) - if not location_dest_id: - location_dest_id = move.location_dest_id - self.move_single_quant_tuple(cr, uid, quant, qty, move, location_dest_id, context=context) + if not location_dest_id: + location_dest_id = move.location_dest_id + self.move_single_quant_tuples(cr, uid, [x for x in quants if x[0]], move, location_dest_id, context=context) + + + + def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, context=None): + whole_quants = [] + for quant, qty in quants: + if not quant: + continue + new_quant = self._quant_split(cr, uid, quant, qty, context=context) + whole_quants.append(quant.id) + if self._check_location(cr, uid, location_dest_id, context=context) and whole_quants: + self.write(cr, SUPERUSER_ID, whole_quants, {'location_id': location_dest_id.id, + 'history_ids': [(4, move.id)]}, context=context) + self._quants_reconcile_negative(cr, uid, whole_quants, move, context=context) + def move_single_quant(self, cr, uid, quant, location_to, qty, move, context=None): @@ -492,54 +507,60 @@ class stock_quant(osv.osv): path.append((4, move.id)) self.write(cr, SUPERUSER_ID, solved_quant_ids, {'history_ids': path}, context=context) + + def _quants_reconcile_negative(self, cr, uid, quants, move, context=None): + if quants[0].location_id.usage != 'internal': + return False + quants_rec = self.search(cr, uid, [('product_id', '=', move.product_id.id), ('qty','<', 0)], limit=1, context=context) + if quants_rec: + for quant in quants: + quant.refresh() + self._quant_reconcile_negative(cr, uid, quant, move, context=context) + 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 quant to the conter-part of the negative quant. """ - if quant.location_id.usage != 'internal': - return False - quants = self.search(cr, uid, [('product_id', '=', quant.product_id.id), ('qty','<', 0)], limit=1, context=context) - if quants: - solving_quant = quant - dom = [('qty', '<', 0)] - if quant.lot_id: - dom += [('lot_id', '=', quant.lot_id.id)] - dom += [('owner_id', '=', quant.owner_id.id)] - 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) - for quant_neg, qty in quants: - if not quant_neg: + solving_quant = quant + dom = [('qty', '<', 0)] + if quant.lot_id: + dom += [('lot_id', '=', quant.lot_id.id)] + dom += [('owner_id', '=', quant.owner_id.id)] + 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) + for quant_neg, qty in quants: + if not quant_neg: + continue + to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id)], context=context) + if not to_solve_quant_ids: + continue + solving_qty = qty + solved_quant_ids = [] + for to_solve_quant in self.browse(cr, uid, to_solve_quant_ids, context=context): + if solving_qty <= 0: continue - to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id)], context=context) - if not to_solve_quant_ids: - continue - solving_qty = qty - solved_quant_ids = [] - for to_solve_quant in self.browse(cr, uid, to_solve_quant_ids, context=context): - if solving_qty <= 0: - continue - solved_quant_ids.append(to_solve_quant.id) - self._quant_split(cr, uid, to_solve_quant, min(solving_qty, to_solve_quant.qty), context=context) - solving_qty -= min(solving_qty, to_solve_quant.qty) - remaining_solving_quant = self._quant_split(cr, uid, solving_quant, qty, context=context) - remaining_neg_quant = self._quant_split(cr, uid, quant_neg, -qty, context=context) - #if the reconciliation was not complete, we need to link together the remaining parts - if remaining_neg_quant: - remaining_to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id), ('id', 'not in', solved_quant_ids)], context=context) - if remaining_to_solve_quant_ids: - self.write(cr, SUPERUSER_ID, remaining_to_solve_quant_ids, {'propagated_from_id': remaining_neg_quant.id}, context=context) - #delete the reconciled quants, as it is replaced by the solved quants - self.unlink(cr, SUPERUSER_ID, [quant_neg.id], context=context) - #price update + accounting entries adjustments - self._price_update(cr, uid, solved_quant_ids, solving_quant.cost, context=context) - #merge history (and cost?) - self._quants_merge(cr, uid, solved_quant_ids, solving_quant, context=context) - self.unlink(cr, SUPERUSER_ID, [solving_quant.id], context=context) - solving_quant = remaining_solving_quant + solved_quant_ids.append(to_solve_quant.id) + self._quant_split(cr, uid, to_solve_quant, min(solving_qty, to_solve_quant.qty), context=context) + solving_qty -= min(solving_qty, to_solve_quant.qty) + remaining_solving_quant = self._quant_split(cr, uid, solving_quant, qty, context=context) + remaining_neg_quant = self._quant_split(cr, uid, quant_neg, -qty, context=context) + #if the reconciliation was not complete, we need to link together the remaining parts + if remaining_neg_quant: + remaining_to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id), ('id', 'not in', solved_quant_ids)], context=context) + if remaining_to_solve_quant_ids: + self.write(cr, SUPERUSER_ID, remaining_to_solve_quant_ids, {'propagated_from_id': remaining_neg_quant.id}, context=context) + #delete the reconciled quants, as it is replaced by the solved quants + self.unlink(cr, SUPERUSER_ID, [quant_neg.id], context=context) + #price update + accounting entries adjustments + self._price_update(cr, uid, solved_quant_ids, solving_quant.cost, context=context) + #merge history (and cost?) + self._quants_merge(cr, uid, solved_quant_ids, solving_quant, context=context) + self.unlink(cr, SUPERUSER_ID, [solving_quant.id], context=context) + solving_quant = remaining_solving_quant def _price_update(self, cr, uid, ids, newprice, context=None): self.write(cr, SUPERUSER_ID, ids, {'cost': newprice}, context=context) diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py index 86ba2eb5327..322f4777511 100644 --- a/addons/stock_account/stock_account.py +++ b/addons/stock_account/stock_account.py @@ -130,6 +130,13 @@ class stock_quant(osv.osv): self._account_entry_move(cr, uid, quant, move, context=context) return quant_record + def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, context=None): + quant_record = super(stock_quant, self).move_single_quant_tuples(cr, uid, quants, move, location_dest_id, context=context) + if move.product_id.valuation == 'real_time': + for quant in quants: + self._account_entry_move(cr, uid, quant, move, context=context) + return quant_record + def _get_accounting_data_for_valuation(self, cr, uid, move, context=None): """ From 782181aba64b038737757b0e15aff67c762e46d3 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 11 Mar 2014 12:20:47 +0100 Subject: [PATCH 40/48] [WIP] Group accounting lines of same cost and correct move_single_quant_tuple bzr revid: jco@openerp.com-20140311112047-xx1p2i92bzcq0l2y --- addons/stock/stock.py | 12 ++++-- addons/stock_account/stock_account.py | 62 ++++++++++++++++----------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 52327888046..9be9e7961a0 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -337,13 +337,15 @@ 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 """ + quantsto = [] 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, force_location = location_dest_id, context=context) + quantsto.append((quant, qty),) if not location_dest_id: location_dest_id = move.location_dest_id - self.move_single_quant_tuples(cr, uid, [x for x in quants if x[0]], move, location_dest_id, context=context) + self.move_single_quant_tuples(cr, uid, quantsto, move, location_dest_id, context=context) @@ -353,10 +355,12 @@ class stock_quant(osv.osv): if not quant: continue new_quant = self._quant_split(cr, uid, quant, qty, context=context) - whole_quants.append(quant.id) + quant.refresh() + whole_quants.append(quant) if self._check_location(cr, uid, location_dest_id, context=context) and whole_quants: - self.write(cr, SUPERUSER_ID, whole_quants, {'location_id': location_dest_id.id, - 'history_ids': [(4, move.id)]}, context=context) + vals = {'location_id': location_dest_id.id, + 'history_ids': [(4, move.id)]} + self.write(cr, SUPERUSER_ID, [x.id for x in whole_quants], vals, context=context) self._quants_reconcile_negative(cr, uid, whole_quants, move, context=context) diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py index 322f4777511..9e95ebf8f81 100644 --- a/addons/stock_account/stock_account.py +++ b/addons/stock_account/stock_account.py @@ -70,7 +70,7 @@ class stock_quant(osv.osv): move = self._get_latest_move(cr, uid, quant, context=context) # this is where we post accounting entries for adjustment ctx['force_valuation_amount'] = newprice - quant.cost - self._account_entry_move(cr, uid, quant, move, context=ctx) + self._account_entry_move(cr, uid, [quant], move, context=ctx) #update the standard price of the product, only if we would have done it if we'd have had enough stock at first, which means #1) the product cost's method is 'real' #2) we just fixed a negative quant caused by an outgoing shipment @@ -80,25 +80,27 @@ class stock_quant(osv.osv): """ Accounting Valuation Entries - location_from: can be None if it's a new quant + quants: Quants to create accounting valuation entries for + move: Move to use """ - def _account_entry_move(self, cr, uid, quant, move, context=None): + def _account_entry_move(self, cr, uid, quants, move, context=None): location_from = move.location_id - location_to = quant.location_id + location_to = quants[0].location_id if context is None: context = {} - if quant.product_id.valuation != 'real_time': + if quants[0].product_id.valuation != 'real_time': return False - if quant.owner_id: + if quants[0].owner_id: #if the quant isn't owned by the company, we don't make any valuation entry return False - if quant.qty <= 0: + if quants[0].qty <= 0: #we don't make any stock valuation for negative quants because the valuation is already made for the counterpart. #At that time the valuation will be made at the product cost price and afterward there will be new accounting entries #to make the adjustments when we know the real cost price. return False - company_from = self._location_owner(cr, uid, quant, location_from, context=context) - company_to = self._location_owner(cr, uid, quant, location_to, context=context) + + company_from = self._location_owner(cr, uid, quants[0], location_from, context=context) + company_to = self._location_owner(cr, uid, quants[0], location_to, context=context) if company_from == company_to: return False @@ -109,9 +111,9 @@ class stock_quant(osv.osv): journal_id, acc_src, acc_dest, acc_valuation = self._get_accounting_data_for_valuation(cr, uid, move, context=ctx) if location_from and location_from.usage == 'customer': #goods returned from customer - self._create_account_move_line(cr, uid, quant, move, acc_dest, acc_valuation, journal_id, context=ctx) + self._create_account_move_line(cr, uid, quants, move, acc_dest, acc_valuation, journal_id, context=ctx) else: - self._create_account_move_line(cr, uid, quant, move, acc_src, acc_valuation, journal_id, context=ctx) + self._create_account_move_line(cr, uid, quants, move, acc_src, acc_valuation, journal_id, context=ctx) # Create Journal Entry for products leaving the company if company_from: @@ -120,9 +122,9 @@ class stock_quant(osv.osv): journal_id, acc_src, acc_dest, acc_valuation = self._get_accounting_data_for_valuation(cr, uid, move, context=ctx) if location_to and location_to.usage == 'supplier': #goods returned to supplier - self._create_account_move_line(cr, uid, quant, move, acc_valuation, acc_src, journal_id, context=ctx) + self._create_account_move_line(cr, uid, quants, move, acc_valuation, acc_src, journal_id, context=ctx) else: - self._create_account_move_line(cr, uid, quant, move, acc_valuation, acc_dest, journal_id, context=ctx) + self._create_account_move_line(cr, uid, quants, move, acc_valuation, acc_dest, journal_id, context=ctx) def move_single_quant(self, cr, uid, quant, location_to, qty, move, context=None): @@ -133,8 +135,8 @@ class stock_quant(osv.osv): def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, context=None): quant_record = super(stock_quant, self).move_single_quant_tuples(cr, uid, quants, move, location_dest_id, context=context) if move.product_id.valuation == 'real_time': - for quant in quants: - self._account_entry_move(cr, uid, quant, move, context=context) + quants_filt = [x[0] for x in quants] + self._account_entry_move(cr, uid, quants_filt, move, context=context) return quant_record @@ -171,11 +173,13 @@ class stock_quant(osv.osv): ''') % (acc_src, acc_dest, acc_valuation, journal_id)) return journal_id, acc_src, acc_dest, acc_valuation - def _prepare_account_move_line(self, cr, uid, quant, move, credit_account_id, debit_account_id, context=None): + def _prepare_account_move_line(self, cr, uid, quant, move, credit_account_id, debit_account_id, qty=0, context=None): """ Generate the account.move.line values to post to track the stock valuation difference due to the processing of the given quant. """ + if qty == 0: + qty = quant.qty if context is None: context = {} currency_obj = self.pool.get('res.currency') @@ -185,12 +189,12 @@ class stock_quant(osv.osv): valuation_amount = quant.product_id.cost_method == 'real' and quant.cost or quant.product_id.standard_price #the standard_price of the product may be in another decimal precision, or not compatible with the coinage of #the company currency... so we need to use round() before creating the accounting entries. - valuation_amount = currency_obj.round(cr, uid, quant.company_id.currency_id, valuation_amount * quant.qty) + valuation_amount = currency_obj.round(cr, uid, quant.company_id.currency_id, valuation_amount * qty) partner_id = (move.picking_id.partner_id and self.pool.get('res.partner')._find_accounting_partner(move.picking_id.partner_id).id) or False debit_line_vals = { 'name': move.name, 'product_id': quant.product_id.id, - 'quantity': quant.qty, + 'quantity': qty, 'product_uom_id': quant.product_id.uom_id.id, 'ref': move.picking_id and move.picking_id.name or False, 'date': time.strftime('%Y-%m-%d'), @@ -202,7 +206,7 @@ class stock_quant(osv.osv): credit_line_vals = { 'name': move.name, 'product_id': quant.product_id.id, - 'quantity': quant.qty, + 'quantity': qty, 'product_uom_id': quant.product_id.uom_id.id, 'ref': move.picking_id and move.picking_id.name or False, 'date': time.strftime('%Y-%m-%d'), @@ -213,12 +217,22 @@ class stock_quant(osv.osv): } return [(0, 0, debit_line_vals), (0, 0, credit_line_vals)] - def _create_account_move_line(self, cr, uid, quant, move, credit_account_id, debit_account_id, journal_id, context=None): + def _create_account_move_line(self, cr, uid, quants, move, credit_account_id, debit_account_id, journal_id, context=None): + #group quants by cost + quant_cost = {} + quant_cost_qty = {} + for quant in quants: + if quant_cost.get(quant.cost): + quant_cost_qty[quant.cost] += quant.qty + else: + quant_cost[quant.cost] = quant + quant_cost[quant.cost] = quant.qty move_obj = self.pool.get('account.move') - move_lines = self._prepare_account_move_line(cr, uid, quant, move, credit_account_id, debit_account_id, context=context) - return move_obj.create(cr, uid, {'journal_id': journal_id, - 'line_id': move_lines, - 'ref': move.picking_id and move.picking_id.name}, context=context) + for cost in quant_cost_qty.keys(): + move_lines = self._prepare_account_move_line(cr, uid, quant_cost[quant.cost], move, credit_account_id, debit_account_id, qty = quant_cost_qty[cost], context=context) + return move_obj.create(cr, uid, {'journal_id': journal_id, + 'line_id': move_lines, + 'ref': move.picking_id and move.picking_id.name}, context=context) #def _reconcile_single_negative_quant(self, cr, uid, to_solve_quant, quant, quant_neg, qty, context=None): # move = self._get_latest_move(cr, uid, to_solve_quant, context=context) From 886812e4d0caaff460f873db812a3a661a398ccd Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 11 Mar 2014 16:29:04 +0100 Subject: [PATCH 41/48] [FIX] Fix and clean from multiple quants move bzr revid: jco@openerp.com-20140311152904-k1qrofmayeatymxc --- addons/stock/stock.py | 50 +++------------------------ addons/stock_account/stock_account.py | 8 ++--- 2 files changed, 6 insertions(+), 52 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 9be9e7961a0..506bc3be0a8 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -364,42 +364,6 @@ class stock_quant(osv.osv): self._quants_reconcile_negative(cr, uid, whole_quants, move, context=context) - - def move_single_quant(self, cr, uid, quant, location_to, qty, move, context=None): - '''Moves the given 'quant' in 'location_to' for the given 'qty', and logs the stock.move that triggered this move in the quant history. - If needed, the quant may be split if it's not totally moved. - - :param quant: browse record (stock.quant) - :param location_to: browse record (stock.location) - :param qty: float - :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) - quant.refresh() - return new_quant - - def move_single_quant_tuple(self, cr, uid, quant, qty, move, location_dest_id, context=None): - '''Effectively process the move of a tuple (quant record, qty to move). This may result in several quants moved - if the preferred locations on the move say so but by default it will only move the quant record given as argument - :param quant: browse record (stock.quant) - :param qty: float - :param move: browse record (stock.move) - ''' - if not quant: - return True - new_quant = self.move_single_quant(cr, uid, quant, location_dest_id, qty, move, context=context) - self._quant_reconcile_negative(cr, uid, quant, move, context=context) - quant = new_quant - 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 the choice on the quants that match the prefered_domain as well. But if the qty requested is not reached @@ -1458,7 +1422,7 @@ class stock_move(osv.osv): def get_price_unit(self, cr, uid, move, context=None): """ Returns the unit price to store on the quant """ - return move.price_unit #or move.product_id.standard_price + return move.price_unit or move.product_id.standard_price def name_get(self, cr, uid, ids, context=None): res = [] @@ -1965,7 +1929,7 @@ class stock_move(osv.osv): 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']: + if context.get("no_picking_assign"): return False move_dict = {} for move in moves: @@ -3609,14 +3573,6 @@ 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') @@ -3691,6 +3647,7 @@ class stock_pack_operation(osv.osv): } def write(self, cr, uid, ids, vals, context=None): + context = context or {} res = super(stock_pack_operation, self).write(cr, uid, ids, vals, context=context) if isinstance(ids, (int, long)): ids = [ids] @@ -3704,6 +3661,7 @@ class stock_pack_operation(osv.osv): return res def create(self, cr, uid, vals, context=None): + context = context or {} res_id = super(stock_pack_operation, self).create(cr, uid, vals, context=context) if vals.get("picking_id") and not context.get("no_recompute"): self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, [vals['picking_id']], context=context) diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py index 9e95ebf8f81..850f575f79f 100644 --- a/addons/stock_account/stock_account.py +++ b/addons/stock_account/stock_account.py @@ -127,11 +127,6 @@ class stock_quant(osv.osv): self._create_account_move_line(cr, uid, quants, move, acc_valuation, acc_dest, journal_id, context=ctx) - def move_single_quant(self, cr, uid, quant, location_to, qty, move, context=None): - quant_record = super(stock_quant, self).move_single_quant(cr, uid, quant, location_to, qty, move, context=context) - self._account_entry_move(cr, uid, quant, move, context=context) - return quant_record - def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, context=None): quant_record = super(stock_quant, self).move_single_quant_tuples(cr, uid, quants, move, location_dest_id, context=context) if move.product_id.valuation == 'real_time': @@ -191,6 +186,7 @@ class stock_quant(osv.osv): #the company currency... so we need to use round() before creating the accounting entries. valuation_amount = currency_obj.round(cr, uid, quant.company_id.currency_id, valuation_amount * qty) partner_id = (move.picking_id.partner_id and self.pool.get('res.partner')._find_accounting_partner(move.picking_id.partner_id).id) or False + debit_line_vals = { 'name': move.name, 'product_id': quant.product_id.id, @@ -226,7 +222,7 @@ class stock_quant(osv.osv): quant_cost_qty[quant.cost] += quant.qty else: quant_cost[quant.cost] = quant - quant_cost[quant.cost] = quant.qty + quant_cost_qty[quant.cost] = quant.qty move_obj = self.pool.get('account.move') for cost in quant_cost_qty.keys(): move_lines = self._prepare_account_move_line(cr, uid, quant_cost[quant.cost], move, credit_account_id, debit_account_id, qty = quant_cost_qty[cost], context=context) From 9c17f269a1243e25abf6db23991b481173f6fe27 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 11 Mar 2014 17:57:47 +0100 Subject: [PATCH 42/48] [IMP] Improve account entries grouping bzr revid: jco@openerp.com-20140311165747-okk437oyeq7e2q8g --- addons/stock_account/stock_account.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py index 850f575f79f..7215300cabd 100644 --- a/addons/stock_account/stock_account.py +++ b/addons/stock_account/stock_account.py @@ -168,30 +168,28 @@ class stock_quant(osv.osv): ''') % (acc_src, acc_dest, acc_valuation, journal_id)) return journal_id, acc_src, acc_dest, acc_valuation - def _prepare_account_move_line(self, cr, uid, quant, move, credit_account_id, debit_account_id, qty=0, context=None): + def _prepare_account_move_line(self, cr, uid, move, qty, cost, credit_account_id, debit_account_id, context=None): """ Generate the account.move.line values to post to track the stock valuation difference due to the processing of the given quant. """ - if qty == 0: - qty = quant.qty if context is None: context = {} currency_obj = self.pool.get('res.currency') if context.get('force_valuation_amount'): valuation_amount = context.get('force_valuation_amount') else: - valuation_amount = quant.product_id.cost_method == 'real' and quant.cost or quant.product_id.standard_price + valuation_amount = move.product_id.cost_method == 'real' and cost or move.product_id.standard_price #the standard_price of the product may be in another decimal precision, or not compatible with the coinage of #the company currency... so we need to use round() before creating the accounting entries. - valuation_amount = currency_obj.round(cr, uid, quant.company_id.currency_id, valuation_amount * qty) + valuation_amount = currency_obj.round(cr, uid, move.company_id.currency_id, valuation_amount * qty) partner_id = (move.picking_id.partner_id and self.pool.get('res.partner')._find_accounting_partner(move.picking_id.partner_id).id) or False debit_line_vals = { 'name': move.name, - 'product_id': quant.product_id.id, + 'product_id': move.product_id.id, 'quantity': qty, - 'product_uom_id': quant.product_id.uom_id.id, + 'product_uom_id': move.product_id.uom_id.id, 'ref': move.picking_id and move.picking_id.name or False, 'date': time.strftime('%Y-%m-%d'), 'partner_id': partner_id, @@ -201,9 +199,9 @@ class stock_quant(osv.osv): } credit_line_vals = { 'name': move.name, - 'product_id': quant.product_id.id, + 'product_id': move.product_id.id, 'quantity': qty, - 'product_uom_id': quant.product_id.uom_id.id, + 'product_uom_id': move.product_id.uom_id.id, 'ref': move.picking_id and move.picking_id.name or False, 'date': time.strftime('%Y-%m-%d'), 'partner_id': partner_id, @@ -225,7 +223,7 @@ class stock_quant(osv.osv): quant_cost_qty[quant.cost] = quant.qty move_obj = self.pool.get('account.move') for cost in quant_cost_qty.keys(): - move_lines = self._prepare_account_move_line(cr, uid, quant_cost[quant.cost], move, credit_account_id, debit_account_id, qty = quant_cost_qty[cost], context=context) + move_lines = self._prepare_account_move_line(cr, uid, move, quant_cost_qty[cost], cost, credit_account_id, debit_account_id, context=context) return move_obj.create(cr, uid, {'journal_id': journal_id, 'line_id': move_lines, 'ref': move.picking_id and move.picking_id.name}, context=context) From ee7024820e50f10a2f4e4f1593af36545fe633ae Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 12 Mar 2014 10:45:44 +0100 Subject: [PATCH 43/48] [IMP] Improve process packaging: processed when moving quants except for entire packages bzr revid: jco@openerp.com-20140312094544-3xg5ar6wr2o39pyn --- addons/stock/stock.py | 33 ++++++++++++--------------- addons/stock_account/stock_account.py | 4 ++-- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 506bc3be0a8..b206908a77c 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -345,11 +345,11 @@ class stock_quant(osv.osv): quantsto.append((quant, qty),) if not location_dest_id: location_dest_id = move.location_dest_id - self.move_single_quant_tuples(cr, uid, quantsto, move, location_dest_id, context=context) + self.move_single_quant_tuples(cr, uid, quantsto, move, location_dest_id, dest_package_id, context=context) - def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, context=None): + def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None): whole_quants = [] for quant, qty in quants: if not quant: @@ -359,7 +359,8 @@ class stock_quant(osv.osv): whole_quants.append(quant) if self._check_location(cr, uid, location_dest_id, context=context) and whole_quants: vals = {'location_id': location_dest_id.id, - 'history_ids': [(4, move.id)]} + 'history_ids': [(4, move.id)], + 'package_id': dest_package_id} self.write(cr, SUPERUSER_ID, [x.id for x in whole_quants], vals, context=context) self._quants_reconcile_negative(cr, uid, whole_quants, move, context=context) @@ -2166,13 +2167,15 @@ class stock_move(osv.osv): quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, record.qty, domain=dom, 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) package_id = False - if not record.operation_id.package_id: - #if a package and a result_package is given, we don't enter here because it will be processed by process_packaging() later + if not ops.product_id and ops.package_id: + #if a package and a result_package is given, we will put package_id, otherwise it will be result_pakege #but for operations having only result_package_id, we will create new quants in the final package directly - package_id = record.operation_id.result_package_id.id or False + package_id = ops.package_id.id + else: + package_id = ops.result_package_id.id quant_obj.quants_move(cr, uid, quants, move, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=package_id, location_dest_id = ops.location_dest_id, context=context) - #packaging process - pack_op_obj.process_packaging(cr, uid, ops, [x[0].id for x in quants if x[0]], context=context) + # Handle pack in pack + pack_op_obj.process_packaging(cr, uid, ops, context=context) move_qty[move.id] -= record.qty #Check for remaining qtys and unreserve/check move_dest_id in for move in self.browse(cr, uid, ids, context=context): @@ -3669,20 +3672,12 @@ class stock_pack_operation(osv.osv): - def process_packaging(self, cr, uid, operation, quants, context=None): + def process_packaging(self, cr, uid, operation, context=None): ''' Process the packaging of a given operation, after the quants have been moved. If there was not enough quants found a quant already has been with the good package information so we don't consider that case in this method''' - quant_obj = self.pool.get("stock.quant") pack_obj = self.pool.get("stock.quant.package") - for quant in quants: - if quant: - if operation.product_id: - #if a product + a package information is given, we consider that we took a part of an existing package (unpacking) - quant_obj.write(cr, SUPERUSER_ID, quant, {'package_id': operation.result_package_id.id}, context=context) - elif operation.package_id and operation.result_package_id: - #move the whole pack into the final package if any - pack_obj.write(cr, uid, [operation.package_id.id], {'parent_id': operation.result_package_id.id}, context=context) - + if not operation.product_id and operation.package_id and operation.result_package_id.id != operation.package_id.parent_id.id: + pack_obj.write(cr, SUPERUSER_ID, [operation.package_id.id], {'result_package_id': operation.result_package_id.id}, context=context) diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py index 7215300cabd..0459756ba36 100644 --- a/addons/stock_account/stock_account.py +++ b/addons/stock_account/stock_account.py @@ -127,8 +127,8 @@ class stock_quant(osv.osv): self._create_account_move_line(cr, uid, quants, move, acc_valuation, acc_dest, journal_id, context=ctx) - def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, context=None): - quant_record = super(stock_quant, self).move_single_quant_tuples(cr, uid, quants, move, location_dest_id, context=context) + def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None): + quant_record = super(stock_quant, self).move_single_quant_tuples(cr, uid, quants, move, location_dest_id, dest_package_id, context=context) if move.product_id.valuation == 'real_time': quants_filt = [x[0] for x in quants] self._account_entry_move(cr, uid, quants_filt, move, context=context) From 4cbceed4975d7c56310d694e5ab8d6a786e2e042 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 12 Mar 2014 11:19:17 +0100 Subject: [PATCH 44/48] [IMP] When creating quants, they should not be moved afterwards, just reconciled bzr revid: jco@openerp.com-20140312101917-no0kjhslm4lk7akw --- addons/stock/stock.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index b206908a77c..e7b39822290 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -337,32 +337,35 @@ 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 """ - quantsto = [] + quants_reconcile = [] + quants_move = [] + if not location_dest_id: + location_dest_id = move.location_dest_id + self._check_location(cr, uid, location_dest_id, context=context) 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, force_location = location_dest_id, context=context) - quantsto.append((quant, qty),) - if not location_dest_id: - location_dest_id = move.location_dest_id - self.move_single_quant_tuples(cr, uid, quantsto, move, location_dest_id, dest_package_id, context=context) - + else: + quants_move += [(quant, qty)] + quants_reconcile.append(quant) + if quants_move: + self.move_single_quant_tuples(cr, uid, quants_move, move, location_dest_id, dest_package_id, context=context) + self._quants_reconcile_negative(cr, uid, quants_reconcile, move, context=context) def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None): whole_quants = [] for quant, qty in quants: - if not quant: - continue new_quant = self._quant_split(cr, uid, quant, qty, context=context) quant.refresh() - whole_quants.append(quant) - if self._check_location(cr, uid, location_dest_id, context=context) and whole_quants: + whole_quants.append(quant.id) + if whole_quants: vals = {'location_id': location_dest_id.id, 'history_ids': [(4, move.id)], 'package_id': dest_package_id} - self.write(cr, SUPERUSER_ID, [x.id for x in whole_quants], vals, context=context) - self._quants_reconcile_negative(cr, uid, whole_quants, move, context=context) + self.write(cr, SUPERUSER_ID, whole_quants, vals, context=context) + 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): From c5a8943ebbf535792f505040d7d0c3a1d8fbb343 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 12 Mar 2014 12:29:25 +0100 Subject: [PATCH 45/48] [IMP] At quants creation it should do accounting entries also bzr revid: jco@openerp.com-20140312112925-o2dvsqg2urfnu2sr --- addons/stock_account/stock_account.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py index 0459756ba36..977ff2e448e 100644 --- a/addons/stock_account/stock_account.py +++ b/addons/stock_account/stock_account.py @@ -127,6 +127,12 @@ class stock_quant(osv.osv): self._create_account_move_line(cr, uid, quants, move, acc_valuation, acc_dest, journal_id, context=ctx) + def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location=False, context=None): + quant = super(stock_quant, self)._quant_create(cr, uid, qty, move, lot_id, owner_id, src_package_id, dest_package_id, force_location, context=context) + if move.product_id.valuation == 'real_time': + self._account_entry_move(cr, uid, [quant], move, context) + return quant + def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None): quant_record = super(stock_quant, self).move_single_quant_tuples(cr, uid, quants, move, location_dest_id, dest_package_id, context=context) if move.product_id.valuation == 'real_time': From 50cd5eae53962e184b998d4b97d01f28a2d58c3b Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 13 Mar 2014 15:20:02 +0100 Subject: [PATCH 46/48] [FIX] Be sure the reserved quants are handled well in recompute and action_assign should not assign moves not in its ids, even if in ops bzr revid: jco@openerp.com-20140313142002-c4zs75p1oay3gkpd --- addons/stock/stock.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index a5aff273cf7..e3b8542141c 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -31,6 +31,8 @@ from openerp import SUPERUSER_ID import openerp.addons.decimal_precision as dp import logging +from profilehooks import profile + _logger = logging.getLogger(__name__) #---------------------------------------------------------- # Incoterms @@ -1100,12 +1102,13 @@ class stock_picking(osv.osv): if prod_move.get(product_id): for move in prod_move[product_id]: 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 - qty_to_assign -= qty_on_link - if qty_to_assign <= 0: - break + if qty_on_link > 0: + 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 + qty_to_assign -= qty_on_link + if qty_to_assign <= 0: + break return qty_to_assign == 0 def _check_quants_reserved(ops): @@ -1141,10 +1144,9 @@ class stock_picking(osv.osv): flag = flag and (ops.owner_id.id == quant.owner_id.id) if flag: quant_qty = quant.qty - if quants_done.get(quant.id): - if quants_done[quant.id] == 0: - continue - quant_qty = quants_done[quant.id] + if quants_done[quant.id] == 0: + continue + quant_qty = quants_done[quant.id] if quant_qty > qty: qty_todo = qty quants_done[quant.id] = quant_qty - qty @@ -1186,7 +1188,6 @@ class stock_picking(osv.osv): for op in operations: _check_quants_reserved(op) - remaining_qty_ok = True for op in operations: op.refresh() @@ -2028,6 +2029,7 @@ class stock_move(osv.osv): if check and not lot_id: raise osv.except_osv(_('Warning!'), _('You must assign a serial number for the product %s') % (move.product_id.name)) + @profile(immediate=True) def action_assign(self, cr, uid, ids, context=None): """ Checks the product type and accordingly writes the state. """ @@ -2076,11 +2078,12 @@ class stock_move(osv.osv): #first try to find quants based on specific domains given by linked operations for record in ops.linked_move_operation_ids: move = record.move_id - domain = main_domain[move.id] + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context) - qty_already_assigned = sum([q.qty for q in record.reserved_quant_ids]) - qty = record.qty - qty_already_assigned - quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id, move.product_id, qty, domain=domain, prefered_domain=[], fallback_domain=[], restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) - quant_obj.quants_reserve(cr, uid, quants, move, record, context=context) + if move.id in main_domain: + domain = main_domain[move.id] + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context) + qty_already_assigned = sum([q.qty for q in record.reserved_quant_ids]) + qty = record.qty - qty_already_assigned + quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id, move.product_id, qty, domain=domain, prefered_domain=[], fallback_domain=[], restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) + quant_obj.quants_reserve(cr, uid, quants, move, record, context=context) for move in todo_moves: #then if the move isn't totally assigned, try to find quants without any specific domain From 283e70ebceed259a4c323aa7870c4953051c98b4 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 13 Mar 2014 15:21:02 +0100 Subject: [PATCH 47/48] [IMP] Comment profiling bzr revid: jco@openerp.com-20140313142102-6dgiwvxlw9ul58ek --- addons/stock/stock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index e3b8542141c..4d88dac6d01 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -31,7 +31,7 @@ from openerp import SUPERUSER_ID import openerp.addons.decimal_precision as dp import logging -from profilehooks import profile +# from profilehooks import profile _logger = logging.getLogger(__name__) #---------------------------------------------------------- @@ -2029,7 +2029,7 @@ class stock_move(osv.osv): if check and not lot_id: raise osv.except_osv(_('Warning!'), _('You must assign a serial number for the product %s') % (move.product_id.name)) - @profile(immediate=True) +# @profile(immediate=True) def action_assign(self, cr, uid, ids, context=None): """ Checks the product type and accordingly writes the state. """ From eb7e737526aee05e83277ea1545de07c80b4f07b Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 13 Mar 2014 16:10:15 +0100 Subject: [PATCH 48/48] [IMP] Wait a second to be certain to have correct behaviour bzr revid: jco@openerp.com-20140313151015-yw1w6eumhje591nu --- addons/stock_dropshipping/test/lifo_price.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/stock_dropshipping/test/lifo_price.yml b/addons/stock_dropshipping/test/lifo_price.yml index 274c31e24f2..1ab280350e8 100644 --- a/addons/stock_dropshipping/test/lifo_price.yml +++ b/addons/stock_dropshipping/test/lifo_price.yml @@ -73,6 +73,12 @@ I confirm the second purchase order - !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lifo2} +- + The behavior of fifo/lifo is not garantee if the quants are created at the same second, so i just wait one second +- + !python {model: stock.picking}: | + import time + time.sleep(1) - Process the reception of purchase order 2 -