diff --git a/issue13211002_190001.diff b/issue13211002_190001.diff new file mode 100644 index 0000000..3e90e58 --- /dev/null +++ b/issue13211002_190001.diff @@ -0,0 +1,315 @@ +diff -r 694d32df45f1 inventory.py +--- a/trytond/trytond/modules/stock/inventory.py Wed Jun 17 12:08:14 2015 +0200 ++++ b/trytond/trytond/modules/stock/inventory.py Wed Jun 17 12:14:25 2015 +0200 +@@ -1,6 +1,6 @@ + #This file is part of Tryton. The COPYRIGHT file at the top level + #of this repository contains the full copyright notices and license terms. +-from trytond.model import Workflow, ModelView, ModelSQL, fields ++from trytond.model import Workflow, Model, ModelView, ModelSQL, fields + from trytond.pyson import Not, Equal, Eval, Or, Bool + from trytond import backend + from trytond.transaction import Transaction +@@ -166,7 +166,12 @@ + return new_inventories + + @staticmethod +- def complete_lines(inventories): ++ def grouping(): ++ return ('product',) ++ ++ @classmethod ++ @ModelView.button ++ def complete_lines(cls, inventories): + ''' + Complete or update the inventories + ''' +@@ -174,25 +179,21 @@ + Line = pool.get('stock.inventory.line') + Product = pool.get('product.product') + ++ grouping = cls.grouping() + to_create = [] + for inventory in inventories: + # Compute product quantities + with Transaction().set_context(stock_date_end=inventory.date): +- pbl = Product.products_by_location([inventory.location.id]) ++ pbl = Product.products_by_location( ++ [inventory.location.id], grouping=grouping) + + # Index some data +- product2uom = {} + product2type = {} + product2consumable = {} + for product in Product.browse([line[1] for line in pbl]): +- product2uom[product.id] = product.default_uom.id + product2type[product.id] = product.type + product2consumable[product.id] = product.consumable + +- product_qty = {} +- for (location, product), quantity in pbl.iteritems(): +- product_qty[product] = (quantity, product2uom[product]) +- + # Update existing lines + for line in inventory.lines: + if not (line.product.active and +@@ -200,26 +201,28 @@ + and not line.product.consumable): + Line.delete([line]) + continue +- if line.product.id in product_qty: +- quantity, uom_id = product_qty.pop(line.product.id) +- elif line.product.id in product2uom: +- quantity, uom_id = 0.0, product2uom[line.product.id] ++ ++ key = (inventory.location.id,) + line.unique_key ++ if key in pbl: ++ quantity = pbl.pop(key) + else: +- quantity, uom_id = 0.0, line.product.default_uom.id +- values = line.update_values4complete(quantity, uom_id) ++ quantity = 0.0 ++ values = line.update_values4complete(quantity) + if values: + Line.write([line], values) + + # Create lines if needed +- for product_id in product_qty: ++ for key, quantity in pbl.iteritems(): ++ product_id = key[grouping.index('product') + 1] + if (product2type[product_id] != 'goods' + or product2consumable[product_id]): + continue +- quantity, uom_id = product_qty[product_id] + if not quantity: + continue +- values = Line.create_values4complete(product_id, inventory, +- quantity, uom_id) ++ ++ values = Line.create_values4complete(inventory, quantity) ++ for i, fname in enumerate(grouping, 1): ++ values[fname] = key[i] + to_create.append(values) + if to_create: + Line.create(to_create) +@@ -310,7 +313,13 @@ + + @property + def unique_key(self): +- return (self.product,) ++ key = [] ++ for fname in self.inventory.grouping(): ++ value = getattr(self, fname) ++ if isinstance(value, Model): ++ value = value.id ++ key.append(value) ++ return tuple(key) + + @classmethod + def cancel_move(cls, lines): +@@ -349,31 +358,27 @@ + origin=self, + ) + +- def update_values4complete(self, quantity, uom_id): ++ def update_values4complete(self, quantity): + ''' + Return update values to complete inventory + ''' + values = {} + # if nothing changed, no update +- if self.quantity == self.expected_quantity == quantity \ +- and self.uom.id == uom_id: ++ if self.quantity == self.expected_quantity == quantity: + return values + values['expected_quantity'] = quantity +- values['uom'] = uom_id + # update also quantity field if not edited + if self.quantity == self.expected_quantity: + values['quantity'] = max(quantity, 0.0) + return values + + @classmethod +- def create_values4complete(cls, product_id, inventory, quantity, uom_id): ++ def create_values4complete(cls, inventory, quantity): + ''' + Return create values to complete inventory + ''' + return { + 'inventory': inventory.id, +- 'product': product_id, + 'expected_quantity': quantity, + 'quantity': max(quantity, 0.0), +- 'uom': uom_id, + } +diff -r 694d32df45f1 tests/scenario_stock_inventory.rst +--- a/trytond/trytond/modules/stock/tests/scenario_stock_inventory.rst Wed Jun 17 12:08:14 2015 +0200 ++++ b/trytond/trytond/modules/stock/tests/scenario_stock_inventory.rst Wed Jun 17 12:14:25 2015 +0200 +@@ -64,13 +64,11 @@ + >>> storage_loc, = Location.find([('code', '=', 'STO')]) + >>> customer_loc, = Location.find([('code', '=', 'CUS')]) + +-Create product:: ++Create products:: + + >>> ProductUom = Model.get('product.uom') + >>> ProductTemplate = Model.get('product.template') +- >>> Product = Model.get('product.product') + >>> unit, = ProductUom.find([('name', '=', 'Unit')]) +- >>> product = Product() + >>> template = ProductTemplate() + >>> template.name = 'Product' + >>> template.default_uom = unit +@@ -79,8 +77,18 @@ + >>> template.cost_price = Decimal('80') + >>> template.cost_price_method = 'average' + >>> template.save() +- >>> product.template = template +- >>> product.save() ++ >>> product, = template.products ++ ++ >>> kg, = ProductUom.find([('name', '=', 'Kilogram')]) ++ >>> template2 = ProductTemplate() ++ >>> template2.name = 'Product' ++ >>> template2.default_uom = kg ++ >>> template2.type = 'goods' ++ >>> template2.list_price = Decimal('140') ++ >>> template2.cost_price = Decimal('60') ++ >>> template2.cost_price_method = 'average' ++ >>> template2.save() ++ >>> product2, = template2.products + + Fill storage:: + +@@ -96,7 +104,19 @@ + >>> incoming_move.company = company + >>> incoming_move.unit_price = Decimal('100') + >>> incoming_move.currency = currency +- >>> incoming_move.save() ++ >>> incoming_move.click('do') ++ ++ >>> incoming_move = StockMove() ++ >>> incoming_move.product = product2 ++ >>> incoming_move.uom = kg ++ >>> incoming_move.quantity = 2.5 ++ >>> incoming_move.from_location = supplier_loc ++ >>> incoming_move.to_location = storage_loc ++ >>> incoming_move.planned_date = today ++ >>> incoming_move.effective_date = today ++ >>> incoming_move.company = company ++ >>> incoming_move.unit_price = Decimal('70') ++ >>> incoming_move.currency = company.currency + >>> incoming_move.click('do') + + Create an inventory:: +@@ -106,28 +126,81 @@ + >>> inventory.location = storage_loc + >>> inventory.save() + >>> inventory.click('complete_lines') +- >>> line, = inventory.lines +- >>> line.expected_quantity == 1 +- True +- >>> line.quantity = 2 ++ >>> line_by_product = {l.product.id: l for l in inventory.lines} ++ >>> line_p1 = line_by_product[product.id] ++ >>> line_p1.expected_quantity ++ 1.0 ++ >>> line_p1.quantity = 3 ++ >>> line_p2 = line_by_product[product2.id] ++ >>> line_p2.expected_quantity ++ 2.5 ++ >>> line_p2.quantity ++ 2.5 + >>> inventory.save() ++ ++Fill storage with more quantities:: ++ ++ >>> incoming_move = StockMove() ++ >>> incoming_move.product = product ++ >>> incoming_move.uom = unit ++ >>> incoming_move.quantity = 1 ++ >>> incoming_move.from_location = supplier_loc ++ >>> incoming_move.to_location = storage_loc ++ >>> incoming_move.planned_date = today ++ >>> incoming_move.effective_date = today ++ >>> incoming_move.company = company ++ >>> incoming_move.unit_price = Decimal('100') ++ >>> incoming_move.currency = company.currency ++ >>> incoming_move.click('do') ++ ++ >>> incoming_move = StockMove() ++ >>> incoming_move.product = product2 ++ >>> incoming_move.uom = kg ++ >>> incoming_move.quantity = 1.3 ++ >>> incoming_move.from_location = supplier_loc ++ >>> incoming_move.to_location = storage_loc ++ >>> incoming_move.planned_date = today ++ >>> incoming_move.effective_date = today ++ >>> incoming_move.company = company ++ >>> incoming_move.unit_price = Decimal('70') ++ >>> incoming_move.currency = company.currency ++ >>> incoming_move.click('do') ++ ++Update the inventory:: ++ ++ >>> inventory.click('complete_lines') ++ >>> line_p1.reload() ++ >>> line_p1.expected_quantity ++ 2.0 ++ >>> line_p1.quantity ++ 3.0 ++ >>> line_p2.reload() ++ >>> line_p2.expected_quantity ++ 3.8 ++ >>> line_p2.quantity ++ 3.8 ++ ++Confirm the inventory:: ++ + >>> inventory.click('confirm') +- >>> line.reload() +- >>> move, = line.moves +- >>> move.quantity == 1 +- True ++ >>> line_p1.reload() ++ >>> move, = line_p1.moves ++ >>> move.quantity ++ 1.0 + >>> move.from_location == inventory.lost_found + True + >>> move.to_location == inventory.location + True ++ >>> line_p2.reload() ++ >>> len(line_p2.moves) ++ 0 + + Empty storage:: + +- >>> StockMove = Model.get('stock.move') + >>> outgoing_move = StockMove() + >>> outgoing_move.product = product + >>> outgoing_move.uom = unit +- >>> outgoing_move.quantity = 2 ++ >>> outgoing_move.quantity = 3 + >>> outgoing_move.from_location = storage_loc + >>> outgoing_move.to_location = customer_loc + >>> outgoing_move.planned_date = today +@@ -137,6 +210,19 @@ + >>> outgoing_move.currency = currency + >>> outgoing_move.click('do') + ++ >>> outgoing_move = StockMove() ++ >>> outgoing_move.product = product2 ++ >>> outgoing_move.uom = kg ++ >>> outgoing_move.quantity = 3.8 ++ >>> outgoing_move.from_location = storage_loc ++ >>> outgoing_move.to_location = customer_loc ++ >>> outgoing_move.planned_date = today ++ >>> outgoing_move.effective_date = today ++ >>> outgoing_move.company = company ++ >>> outgoing_move.unit_price = Decimal('140') ++ >>> outgoing_move.currency = company.currency ++ >>> outgoing_move.click('do') ++ + Create an inventory that should be empty after completion:: + + >>> Inventory = Model.get('stock.inventory') diff --git a/issue17281002_20001.diff b/issue17281002_20001.diff new file mode 100644 index 0000000..ea78f8e --- /dev/null +++ b/issue17281002_20001.diff @@ -0,0 +1,89 @@ +diff -r e9a7f9fd73aa stock.py +--- a/trytond/trytond/modules/stock_lot/stock.py Wed Jun 17 11:46:34 2015 +0200 ++++ b/trytond/trytond/modules/stock_lot/stock.py Wed Jun 17 11:47:43 2015 +0200 +@@ -1,7 +1,6 @@ + #This file is part of Tryton. The COPYRIGHT file at the top level of + #this repository contains the full copyright notices and license terms. + import datetime +-from collections import defaultdict + + from trytond.model import ModelView, ModelSQL, Workflow, fields + from trytond.pyson import PYSONEncoder, Eval +@@ -200,64 +199,8 @@ + __name__ = 'stock.inventory' + + @classmethod +- def complete_lines(cls, inventories): +- pool = Pool() +- Product = pool.get('product.product') +- Line = pool.get('stock.inventory.line') +- +- super(Inventory, cls).complete_lines(inventories) +- +- # Create and/or update lines with product that will require lot for +- # their moves. +- to_create = [] +- for inventory in inventories: +- product2lines = defaultdict(list) +- for line in inventory.lines: +- if (line.product.lot_is_required(inventory.location, +- inventory.lost_found) +- or line.product.lot_is_required(inventory.lost_found, +- inventory.location)): +- product2lines[line.product.id].append(line) +- if product2lines: +- with Transaction().set_context(stock_date_end=inventory.date): +- pbl = Product.products_by_location([inventory.location.id], +- product_ids=product2lines.keys(), +- grouping=('product', 'lot')) +- product_qty = defaultdict(dict) +- for (location_id, product_id, lot_id), quantity \ +- in pbl.iteritems(): +- product_qty[product_id][lot_id] = quantity +- +- products = Product.browse(product_qty.keys()) +- product2uom = dict((p.id, p.default_uom.id) for p in products) +- +- for product_id, lines in product2lines.iteritems(): +- quantities = product_qty[product_id] +- uom_id = product2uom[product_id] +- for line in lines: +- lot_id = line.lot.id if line.lot else None +- if lot_id in quantities: +- quantity = quantities.pop(lot_id) +- elif lot_id is None and quantities: +- lot_id = quantities.keys()[0] +- quantity = quantities.pop(lot_id) +- else: +- lot_id = None +- quantity = 0.0 +- +- values = line.update_values4complete(quantity, uom_id) +- if (values or lot_id != (line.lot.id +- if line.lot else None)): +- values['lot'] = lot_id +- Line.write([line], values) +- if quantities: +- for lot_id, quantity in quantities.iteritems(): +- values = Line.create_values4complete(product_id, +- inventory, quantity, uom_id) +- values['lot'] = lot_id +- to_create.append(values) +- if to_create: +- Line.create(to_create) ++ def grouping(cls): ++ return super(Inventory, cls).grouping() + ('lot', ) + + + class InventoryLine: +@@ -279,10 +222,6 @@ + rec_name += ' - %s' % self.lot.rec_name + return rec_name + +- @property +- def unique_key(self): +- return super(InventoryLine, self).unique_key + (self.lot,) +- + def get_move(self): + move = super(InventoryLine, self).get_move() + if move: diff --git a/series b/series index 9a57fde..d876a04 100644 --- a/series +++ b/series @@ -54,3 +54,5 @@ issue9801002_40001.diff issue19281002_1.diff issue13181002_1.diff issue12191002_1.diff +#issue13211002_190001.diff +#issue17281002_20001.diff