From 338013803d8174ff9ed4847ae0ece7a103571b0e Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 16 Jan 2014 18:48:00 +0100 Subject: [PATCH 01/20] [WIP] Extending wizard with consume lines bzr revid: jco@openerp.com-20140116174800-kap083m5jvacsbh6 --- addons/mrp/mrp.py | 85 ++++++++++++++++++- addons/mrp/stock.py | 2 + addons/mrp/wizard/mrp_product_produce.py | 63 +++++++++++++- .../mrp/wizard/mrp_product_produce_view.xml | 21 ++++- 4 files changed, 166 insertions(+), 5 deletions(-) diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 68d87736e9d..478848a337e 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -772,7 +772,16 @@ class mrp_production(osv.osv): """ return 1 - def action_produce(self, cr, uid, production_id, production_qty, production_mode, context=None): + + + + + + + + + + def action_produce(self, cr, uid, production_id, production_qty, production_mode, wiz, context=None): """ To produce final product based on production mode (consume/consume&produce). If Production mode is consume, all stock move lines of raw materials will be done/consumed. If Production mode is consume & produce, all stock move lines of raw materials will be done/consumed @@ -784,7 +793,80 @@ class mrp_production(osv.osv): """ stock_mov_obj = self.pool.get('stock.move') production = self.browse(cr, uid, production_id, context=context) + if not production.move_lines and production.state == 'ready': + # trigger workflow if not products to consume (eg: services) + self.signal_button_produce(cr, uid, [production_id]) + produced_qty = 0 + for produced_product in production.move_created_ids2: + if (produced_product.scrapped) or (produced_product.product_id.id != production.product_id.id): + continue + produced_qty += produced_product.product_qty + if production_mode in ['consume','consume_produce']: + consumed_data = {} + + # Calculate already consumed qtys + for consumed in production.move_lines2: + if consumed.scrapped: + continue + if not consumed_data.get(consumed.product_id.id, False): + consumed_data[consumed.product_id.id] = 0 + consumed_data[consumed.product_id.id] += consumed.product_qty + for consume in wiz.consume_lines: + # Search move for the product + corresponding_move = [x for x in production.move_lines if x.product_id.id == consume.product_id.id] + if corresponding_move: + corresponding_move = corresponding_move[0] + if corresponding_move.product_qty > consume.product_qty: + stock_mov_obj.action_consume(cr, uid, [corresponding_move.id], consume.product_qty, corresponding_move.location_id.id, + restrict_lot_id = consume.lot_id.id, context=context) + else: + stock_mov_obj.action_consume(cr, uid, [corresponding_move.id], corresponding_move.product_qty, corresponding_move.location_id.id, + restrict_lot_id = consume.lot_id.id, context=context) + + if production_mode == 'consume_produce': + # To produce remaining qty of final product + #vals = {'state':'confirmed'} + #final_product_todo = [x.id for x in production.move_created_ids] + #stock_mov_obj.write(cr, uid, final_product_todo, vals) + #stock_mov_obj.action_confirm(cr, uid, final_product_todo, context) + produced_products = {} + for produced_product in production.move_created_ids2: + if produced_product.scrapped: + continue + if not produced_products.get(produced_product.product_id.id, False): + produced_products[produced_product.product_id.id] = 0 + produced_products[produced_product.product_id.id] += produced_product.product_qty + + for produce_product in production.move_created_ids: + produced_qty = produced_products.get(produce_product.product_id.id, 0) + subproduct_factor = self._get_subproduct_factor(cr, uid, production.id, produce_product.id, context=context) + rest_qty = (subproduct_factor * production.product_qty) - produced_qty + + if rest_qty < (subproduct_factor * production_qty): + prod_name = produce_product.product_id.name_get()[0][1] + raise osv.except_osv(_('Warning!'), _('You are going to produce total %s quantities of "%s".\nBut you can only produce up to total %s quantities.') % ((subproduct_factor * production_qty), prod_name, rest_qty)) + if rest_qty > 0 : + stock_mov_obj.action_consume(cr, uid, [produce_product.id], (subproduct_factor * production_qty), context=context) + + self.message_post(cr, uid, production_id, body=_("%s produced") % self._description, context=context) + self.signal_button_produce_done(cr, uid, [production_id]) + return True + + + + def action_produce2(self, cr, uid, production_id, production_qty, production_mode, context=None): + """ To produce final product based on production mode (consume/consume&produce). + If Production mode is consume, all stock move lines of raw materials will be done/consumed. + If Production mode is consume & produce, all stock move lines of raw materials will be done/consumed + and stock move lines of final product will be also done/produced. + @param production_id: the ID of mrp.production object + @param production_qty: specify qty to produce + @param production_mode: specify production mode (consume/consume&produce). + @return: True + """ + stock_mov_obj = self.pool.get('stock.move') + production = self.browse(cr, uid, production_id, context=context) if not production.move_lines and production.state == 'ready': # trigger workflow if not products to consume (eg: services) self.signal_button_produce(cr, uid, [production_id]) @@ -992,7 +1074,6 @@ class mrp_production(osv.osv): 'product_uos': production_line.product_uos and production_line.product_uos.id or False, 'location_id': source_location_id, 'location_dest_id': destination_location_id, - 'move_dest_id': parent_move_id, 'company_id': production.company_id.id, 'procure_method': 'make_to_order', }) diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py index ff59fcd5118..518755627ef 100644 --- a/addons/mrp/stock.py +++ b/addons/mrp/stock.py @@ -30,6 +30,8 @@ class StockMove(osv.osv): _columns = { 'production_id': fields.many2one('mrp.production', 'Production Order for Produced Products', select=True), 'raw_material_production_id': fields.many2one('mrp.production', 'Production Order for Raw Materials', select=True), + 'consumed_for': fields.many2one('stock.move', 'Consumed for'), + 'track_production': fields.related('product_id', 'track_production'), } def check_tracking(self, cr, uid, move, lot_id, context=None): diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py index 0851657587c..6ccfbc25adc 100644 --- a/addons/mrp/wizard/mrp_product_produce.py +++ b/addons/mrp/wizard/mrp_product_produce.py @@ -22,6 +22,18 @@ from openerp.osv import fields, osv import openerp.addons.decimal_precision as dp + +class mrp_product_produce_line(osv.osv_memory): + _name="mrp.product.produce.line" + _description = "Product Produce Consume lines" + + _columns = { + 'product_id': fields.many2one('product.product', 'Product'), + 'product_qty': fields.float('Quantity'), + 'lot_id': fields.many2one('stock.production.lot', 'Lot'), + 'produce_id': fields.many2one('mrp.product.produce') + } + class mrp_product_produce(osv.osv_memory): _name = "mrp.product.produce" _description = "Product Produce" @@ -33,7 +45,56 @@ class mrp_product_produce(osv.osv_memory): help="'Consume only' mode will only consume the products with the quantity selected.\n" "'Consume & Produce' mode will consume as well as produce the products with the quantity selected " "and it will finish the production order when total ordered quantities are produced."), + 'lot_id': fields.many2one('stock.production.lot', 'Lot'), #Should only be visible when it is consume and produce mode + 'consume_lines': fields.one2many('mrp.product.produce.line', 'produce_id', 'Products Consumed'), } + + + def on_change_qty(self, cr, uid, ids, product_qty, consume_lines, context=None): + """ Will calculate number of products based on + """ + prod_obj = self.pool.get("mrp.production") + production = prod_obj.browse(cr, uid, context['active_id'], context=context) + + produced_qty = 0 + for produced_product in production.move_created_ids2: + if (produced_product.scrapped) or (produced_product.product_id.id != production.product_id.id): + continue + produced_qty += produced_product.product_qty + + + #Calculate consume lines + consumed_data = {} + for consumed in production.move_lines2: + if consumed.scrapped: + continue + if not consumed_data.get(consumed.product_id.id, False): + consumed_data[consumed.product_id.id] = 0 + consumed_data[consumed.product_id.id] += consumed.product_qty + + print "Consumed data", consumed_data + + new_consume_lines = [] + + # Find product qty to be consumed and consume it + for scheduled in production.product_lines: + + # total qty of consumed product we need after this consumption + total_consume = ((product_qty + produced_qty) * scheduled.product_qty / production.product_qty) + + # qty available for consume and produce + qty_avail = scheduled.product_qty - consumed_data.get(scheduled.product_id.id, 0.0) + + if qty_avail <= 0.0: + # there will be nothing to consume for this raw material + continue + + qty = total_consume - consumed_data.get(scheduled.product_id.id, 0.0) + dict_new = {'product_id': scheduled.product_id.id, 'product_qty': qty} + new_consume_lines.append([0, False, dict_new]) + + return {'value': {'consume_lines': new_consume_lines}} + def _get_product_qty(self, cr, uid, context=None): """ To obtain product quantity @@ -64,7 +125,7 @@ class mrp_product_produce(osv.osv_memory): assert production_id, "Production Id should be specified in context as a Active ID." data = self.browse(cr, uid, ids[0], context=context) self.pool.get('mrp.production').action_produce(cr, uid, production_id, - data.product_qty, data.mode, context=context) + data.product_qty, data.mode, data, context=context) return {} diff --git a/addons/mrp/wizard/mrp_product_produce_view.xml b/addons/mrp/wizard/mrp_product_produce_view.xml index f3e892a1b0a..0c9c1b7e060 100644 --- a/addons/mrp/wizard/mrp_product_produce_view.xml +++ b/addons/mrp/wizard/mrp_product_produce_view.xml @@ -12,8 +12,25 @@
- - + + + + + + + + + + + + +
- -
- - - - Split inventory lines - ir.actions.act_window - stock.inventory.line.split - form - form - new - - - - diff --git a/addons/stock/wizard/stock_inventory_merge.py b/addons/stock/wizard/stock_inventory_merge.py deleted file mode 100644 index 9245628b040..00000000000 --- a/addons/stock/wizard/stock_inventory_merge.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- 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 . -# -############################################################################## - -from openerp.osv import fields, osv -from openerp.tools.translate import _ - -class stock_inventory_merge(osv.osv_memory): - _name = "stock.inventory.merge" - _description = "Merge Inventory" - - def fields_view_get(self, cr, uid, view_id=None, view_type='form', - context=None, toolbar=False, submenu=False): - """ - Changes the view dynamically - @param self: The object pointer. - @param cr: A database cursor - @param uid: ID of the user currently logged in - @param context: A standard dictionary - @return: New arch of view. - """ - if context is None: - context={} - res = super(stock_inventory_merge, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False) - if context.get('active_model','') == 'stock.inventory' and len(context['active_ids']) < 2: - raise osv.except_osv(_('Warning!'), - _('Please select multiple physical inventories to merge in the list view.')) - return res - - def do_merge(self, cr, uid, ids, context=None): - """ To merge selected Inventories. - @param self: The object pointer. - @param cr: A database cursor - @param uid: ID of the user currently logged in - @param ids: List of IDs selected - @param context: A standard dictionary - @return: - """ - invent_obj = self.pool.get('stock.inventory') - invent_line_obj = self.pool.get('stock.inventory.line') - invent_lines = {} - if context is None: - context = {} - for inventory in invent_obj.browse(cr, uid, context['active_ids'], context=context): - if inventory.state == "done": - raise osv.except_osv(_('Warning!'), - _('Merging is only allowed on draft inventories.')) - - for line in inventory.inventory_line_id: - key = (line.location_id.id, line.product_id.id, line.product_uom_id.id) - if key in invent_lines: - invent_lines[key] += line.product_qty - else: - invent_lines[key] = line.product_qty - - - new_invent = invent_obj.create(cr, uid, { - 'name': 'Merged inventory' - }, context=context) - - for key, quantity in invent_lines.items(): - invent_line_obj.create(cr, uid, { - 'inventory_id': new_invent, - 'location_id': key[0], - 'product_id': key[1], - 'product_uom_id': key[2], - 'product_qty': quantity, - }) - - return {'type': 'ir.actions.act_window_close'} - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/addons/stock/wizard/stock_inventory_merge_view.xml b/addons/stock/wizard/stock_inventory_merge_view.xml deleted file mode 100644 index bf661b426a2..00000000000 --- a/addons/stock/wizard/stock_inventory_merge_view.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - stock.inventory.merge.form - stock.inventory.merge - -
- -
-
- - -
-
diff --git a/addons/stock/wizard/stock_move.py b/addons/stock/wizard/stock_move.py index 488e825a361..c0cb5dbcf2b 100644 --- a/addons/stock/wizard/stock_move.py +++ b/addons/stock/wizard/stock_move.py @@ -23,9 +23,11 @@ from openerp.osv import fields, osv from openerp.tools.translate import _ import openerp.addons.decimal_precision as dp -class stock_move_consume(osv.osv_memory): - _name = "stock.move.consume" - _description = "Consume Products" + + +class stock_move_scrap(osv.osv_memory): + _name = "stock.move.scrap" + _description = "Scrap Products" _columns = { 'product_id': fields.many2one('product.product', 'Product', required=True, select=True), @@ -35,59 +37,6 @@ class stock_move_consume(osv.osv_memory): 'restrict_lot_id': fields.many2one('stock.production.lot', 'Lot'), } - #TOFIX: product_uom should not have differemt category of default UOM of product. Qty should be convert into UOM of original move line before going in consume and scrap - def default_get(self, cr, uid, fields, context=None): - """ Get default values - @param self: The object pointer. - @param cr: A database cursor - @param uid: ID of the user currently logged in - @param fields: List of fields for default value - @param context: A standard dictionary - @return: default values of fields - """ - if context is None: - context = {} - res = super(stock_move_consume, self).default_get(cr, uid, fields, context=context) - move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context) - if 'product_id' in fields: - res.update({'product_id': move.product_id.id}) - if 'product_uom' in fields: - res.update({'product_uom': move.product_uom.id}) - if 'product_qty' in fields: - res.update({'product_qty': move.product_qty}) - if 'location_id' in fields: - res.update({'location_id': move.location_id.id}) - - return res - - - - def do_move_consume(self, cr, uid, ids, context=None): - """ To move consumed products - @param self: The object pointer. - @param cr: A database cursor - @param uid: ID of the user currently logged in - @param ids: the ID or list of IDs if we want more than one - @param context: A standard dictionary - @return: - """ - if context is None: - context = {} - move_obj = self.pool.get('stock.move') - move_ids = context['active_ids'] - for data in self.browse(cr, uid, ids, context=context): - move_obj.action_consume(cr, uid, move_ids, - data.product_qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id and data.restrict_lot_id.id or False, - context=context) - return {'type': 'ir.actions.act_window_close'} - - - -class stock_move_scrap(osv.osv_memory): - _name = "stock.move.scrap" - _description = "Scrap Products" - _inherit = "stock.move.consume" - _defaults = { 'location_id': lambda *x: False } @@ -103,8 +52,9 @@ class stock_move_scrap(osv.osv_memory): """ if context is None: context = {} - res = super(stock_move_consume, self).default_get(cr, uid, fields, context=context) + res = super(stock_move_scrap, self).default_get(cr, uid, fields, context=context) move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context) + location_obj = self.pool.get('stock.location') scrap_location_id = location_obj.search(cr, uid, [('scrap_location','=',True)]) @@ -139,7 +89,6 @@ class stock_move_scrap(osv.osv_memory): return {'type': 'ir.actions.act_window_close'} - class split_in_production_lot(osv.osv_memory): _name = "stock.move.split" _description = "Split in Serial Numbers" diff --git a/addons/stock/wizard/stock_move_view.xml b/addons/stock/wizard/stock_move_view.xml index 0a567be651e..a5dda8954c0 100644 --- a/addons/stock/wizard/stock_move_view.xml +++ b/addons/stock/wizard/stock_move_view.xml @@ -1,41 +1,7 @@ - - - - Consume Move - stock.move.consume - -
- - - -
-
-
-
-
- - - Consume Move - ir.actions.act_window - stock.move.consume - form - form - new - + Scrap Move From 34b923a19e795afa4f5cb74c4047c40dd463d268 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 28 Jan 2014 16:41:25 +0100 Subject: [PATCH 10/20] [IMP] change split signature and take into account for action_consume bzr revid: jco@openerp.com-20140128154125-3j1rpc4w6maywcmt --- addons/mrp/mrp.py | 6 ++---- addons/mrp/stock.py | 7 ++++--- addons/stock/stock.py | 13 ++++++------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index eadda8c69f9..4da68d522c2 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -920,14 +920,12 @@ class mrp_production(osv.osv): if corresponding_move: corresponding_move = corresponding_move[0] default_values = {} - if main_production_move: - default_values = {'consumed_for': main_production_move} if corresponding_move.product_qty > consume['product_qty']: stock_mov_obj.action_consume(cr, uid, [corresponding_move.id], consume['product_qty'], corresponding_move.location_id.id, - restrict_lot_id = consume['lot_id'], default_values = default_values, context=context) + restrict_lot_id = consume['lot_id'], consumed_for = main_production_move, context=context) else: stock_mov_obj.action_consume(cr, uid, [corresponding_move.id], corresponding_move.product_qty, corresponding_move.location_id.id, - restrict_lot_id = consume['lot_id'], default_values = default_values, context=context) + restrict_lot_id = consume['lot_id'], consumed_for = main_production_move, context=context) self.message_post(cr, uid, production_id, body=_("%s produced") % self._description, context=context) self.signal_button_produce_done(cr, uid, [production_id]) diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py index efe5a1a1c42..294a315a72e 100644 --- a/addons/mrp/stock.py +++ b/addons/mrp/stock.py @@ -130,9 +130,10 @@ class StockMove(osv.osv): ctx = context.copy() if location_id: ctx['source_location_id'] = location_id - res.append(self.split(cr, uid, move, move_qty - quantity_rest, restrict_lot_id=restrict_lot_id, - restrict_partner_id=restrict_partner_id, context=ctx)) - #TODO need to add consumed_for here + new_mov = self.split(cr, uid, move, move_qty - quantity_rest, restrict_lot_id=restrict_lot_id, + restrict_partner_id=restrict_partner_id, context=ctx) + self.write(cr, uid, new_mov, {'consumed_for': consumed_for},context=context) + res.append(new_mov) else: res.append(move.id) if location_id: diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 3ccbf3bca50..4510815a090 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1900,14 +1900,12 @@ class stock_move(osv.osv): return res - def split(self, cr, uid, move, qty, restrict_lot_id=False, default_values = None, context=None): + def split(self, cr, uid, move, qty, restrict_lot_id=False, restrict_partner_id=False, context=None): """ Splits qty from move move into a new move :param move: browse record :param qty: float. quantity to split (given in product UoM) :param context: dictionay. can contains the special key 'source_location_id' in order to force the source location when copying the move """ - if default_values is None: - default_values = {} if move.product_qty <= qty or qty == 0: return move.id @@ -1920,9 +1918,7 @@ class stock_move(osv.osv): if move.state in ('done', 'cancel'): raise osv.except_osv(_('Error'), _('You cannot split a move done')) - - defaults = default_values.copy() - defaults.update({ + defaults = { 'product_uom_qty': uom_qty, 'product_uos_qty': uos_qty, 'state': move.state, @@ -1930,7 +1926,8 @@ class stock_move(osv.osv): 'move_dest_id': False, 'reserved_quant_ids': [], 'restrict_lot_id': restrict_lot_id, - }) + 'restrict_partner_id': restrict_partner_id + } if context.get('source_location_id'): defaults['location_id'] = context['source_location_id'] new_move = self.copy(cr, uid, move.id, defaults) @@ -1950,6 +1947,8 @@ class stock_move(osv.osv): self.action_confirm(cr, uid, [new_move], context=context) return new_move + + class stock_inventory(osv.osv): _name = "stock.inventory" _description = "Inventory" From 3562a6d3bf64b15fc1d62f2dd33893b7476489f1 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 28 Jan 2014 16:51:48 +0100 Subject: [PATCH 11/20] [IMP] Remove inventory split merge bzr revid: jco@openerp.com-20140128155148-hhz9oskv13qz1ds3 --- addons/stock/__openerp__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/addons/stock/__openerp__.py b/addons/stock/__openerp__.py index 9e286faf81c..74f1c39d605 100644 --- a/addons/stock/__openerp__.py +++ b/addons/stock/__openerp__.py @@ -74,9 +74,7 @@ Dashboard / Reports for Warehouse Management will include: 'stock_data.yml', 'wizard/stock_move_view.xml', 'wizard/stock_change_product_qty_view.xml', - 'wizard/stock_inventory_merge_view.xml', 'wizard/stock_location_product_view.xml', - 'wizard/stock_inventory_line_split_view.xml', 'wizard/stock_return_picking_view.xml', 'wizard/make_procurement_view.xml', 'wizard/mrp_procurement_view.xml', From 68f0f8db8fcb5050a189885fe73a2f1daa304da0 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 28 Jan 2014 17:40:48 +0100 Subject: [PATCH 12/20] [FIX] Use correct wizard and solve infinite loop bzr revid: jco@openerp.com-20140128164048-43ptahppk2w2tyn7 --- addons/mrp/__openerp__.py | 2 +- addons/mrp/mrp_view.xml | 2 +- addons/mrp/stock.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/mrp/__openerp__.py b/addons/mrp/__openerp__.py index d4e383bd702..aff4f6343af 100644 --- a/addons/mrp/__openerp__.py +++ b/addons/mrp/__openerp__.py @@ -63,6 +63,7 @@ Dashboard / Reports for MRP will include: 'wizard/change_production_qty_view.xml', 'wizard/mrp_price_view.xml', 'wizard/mrp_workcenter_load_view.xml', + 'wizard/stock_move_view.xml', 'mrp_view.xml', 'mrp_report.xml', 'company_view.xml', @@ -73,7 +74,6 @@ Dashboard / Reports for MRP will include: 'report/mrp_production_order_view.xml', 'board_manufacturing_view.xml', 'res_config_view.xml', - 'wizard/stock_move_view.xml', ], 'demo': ['mrp_demo.xml'], 'test': [ diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml index ae6ec900152..1ad47a02784 100644 --- a/addons/mrp/mrp_view.xml +++ b/addons/mrp/mrp_view.xml @@ -726,7 +726,7 @@ -