From 4c26686ad8d7c67e8a89dcd18f4f162c48e81663 Mon Sep 17 00:00:00 2001 From: Sharoon Thomas Date: Fri, 2 Mar 2012 10:46:08 -0500 Subject: [PATCH] Fix corruption of outgoing_moves in packed state of stock.shipment.out The `move_obj.write` changes the value of `quantity` in the move, but the removed_qty computation used `move.quantity` from the browse record. The browse record gets the updated value and not the old value from browse_record cache. This fix just moves the computation before the move_obj.write and includes a scenario to confirm the behavior. issue2426 review254001 (transplanted from eedeb7ba47b1363919364b40a07862ccf012db97) --- COPYRIGHT | 1 + shipment.py | 6 +- tests/scenario_stock_shipment_out.rst | 197 ++++++++++++++++++++++++++ tests/test_stock.py | 19 +++ 4 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 tests/scenario_stock_shipment_out.rst diff --git a/COPYRIGHT b/COPYRIGHT index a9feb419c1..dc0d25a0ce 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,3 +1,4 @@ +Copyright (C) 2012 Openlabs Technologies & Consulting (P) LTD. Copyright (C) 2008-2011 Cédric Krier. Copyright (C) 2008-2011 Bertrand Chenal. Copyright (C) 2008-2011 B2CK SPRL. diff --git a/shipment.py b/shipment.py index 00150d4981..e252150797 100644 --- a/shipment.py +++ b/shipment.py @@ -1018,12 +1018,12 @@ class ShipmentOut(ModelWorkflow, ModelSQL, ModelView): if outgoing_qty.get(move.product.id, 0.0) > 0.0: exc_qty = uom_obj.compute_qty(move.product.default_uom, outgoing_qty[move.product.id], move.uom) - move_obj.write(move.id,{ - 'quantity': max(0.0, move.quantity-exc_qty), - }) removed_qty = uom_obj.compute_qty(move.uom, min(exc_qty, move.quantity), move.product.default_uom, round=False) + move_obj.write(move.id,{ + 'quantity': max(0.0, move.quantity-exc_qty), + }) outgoing_qty[move.product.id] -= removed_qty move_obj.write([x.id for x in shipment.outgoing_moves diff --git a/tests/scenario_stock_shipment_out.rst b/tests/scenario_stock_shipment_out.rst new file mode 100644 index 0000000000..85c438bcdb --- /dev/null +++ b/tests/scenario_stock_shipment_out.rst @@ -0,0 +1,197 @@ +=========================== +Stock Shipment Out Scenario +=========================== + +============= +General Setup +============= + +Imports:: + + >>> import datetime + >>> from dateutil.relativedelta import relativedelta + >>> from decimal import Decimal + >>> from proteus import config, Model, Wizard + >>> today = datetime.date.today() + +Create database:: + + >>> config = config.set_trytond() + >>> config.pool.test = True + +Install stock Module:: + + >>> Module = Model.get('ir.module.module') + >>> modules = Module.find([('name', '=', 'stock')]) + >>> Module.button_install([x.id for x in modules], config.context) + >>> Wizard('ir.module.module.install_upgrade').execute('upgrade') + +Create company:: + + >>> Currency = Model.get('currency.currency') + >>> CurrencyRate = Model.get('currency.currency.rate') + >>> Company = Model.get('company.company') + >>> company_config = Wizard('company.company.config') + >>> company_config.execute('company') + >>> company = company_config.form + >>> company.name = 'OPENLABS' + >>> currencies = Currency.find([('code', '=', 'EUR')]) + >>> if not currencies: + ... currency = Currency(name='Euro', symbol=u'€', code='EUR', + ... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]', + ... mon_decimal_point=',') + ... currency.save() + ... CurrencyRate(date=today + relativedelta(month=1, day=1), + ... rate=Decimal('1.0'), currency=currency).save() + ... else: + ... currency, = currencies + >>> company.currency = currency + >>> company_config.execute('add') + >>> company, = Company.find() + +Reload the context:: + + >>> User = Model.get('res.user') + >>> config._context = User.get_preferences(True, config.context) + +Create customer:: + + >>> Party = Model.get('party.party') + >>> customer = Party(name='Customer') + >>> customer.save() + +Create category:: + + >>> ProductCategory = Model.get('product.category') + >>> category = ProductCategory(name='Category') + >>> category.save() + +Create product:: + + >>> ProductUom = Model.get('product.uom') + >>> Product = Model.get('product.product') + >>> unit, = ProductUom.find([('name', '=', 'Unit')]) + >>> product = Product() + >>> product.name = 'Product' + >>> product.category = category + >>> product.default_uom = unit + >>> product.type = 'stockable' + >>> product.list_price = Decimal('20') + >>> product.cost_price = Decimal('8') + >>> product.save() + +Get stock locations:: + + >>> Location = Model.get('stock.location') + >>> warehouse_loc, = Location.find([('code', '=', 'WH')]) + >>> supplier_loc, = Location.find([('code', '=', 'SUP')]) + >>> customer_loc, = Location.find([('code', '=', 'CUS')]) + >>> output_loc, = Location.find([('code', '=', 'OUT')]) + >>> storage_loc, = Location.find([('code', '=', 'STO')]) + +Create Shipment Out:: + + >>> ShipmentOut = Model.get('stock.shipment.out') + >>> shipment_out = ShipmentOut() + >>> shipment_out.planned_date = today + >>> shipment_out.customer = customer + >>> shipment_out.warehouse = warehouse_loc + >>> shipment_out.company = company + +Add two shipment lines of same product:: + + >>> StockMove = Model.get('stock.move') + >>> shipment_out.outgoing_moves.extend([StockMove(), StockMove()]) + >>> for move in shipment_out.outgoing_moves: + ... move.product = product + ... move.uom =unit + ... move.quantity = 1 + ... move.from_location = output_loc + ... move.to_location = customer_loc + ... move.company = company + ... move.unit_price = Decimal('1') + ... move.currency = currency + >>> shipment_out.save() + +Set the shipment state to waiting:: + + >>> ShipmentOut.workflow_trigger_validate( + ... shipment_out.id, 'waiting', config.context) + >>> shipment_out.reload() + >>> len(shipment_out.outgoing_moves) + 2 + >>> len(shipment_out.inventory_moves) + 2 + +Make 1 unit of the product available:: + + >>> 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.state = 'done' + >>> incoming_move.company = company + >>> incoming_move.unit_price = Decimal('1') + >>> incoming_move.currency = currency + >>> incoming_move.save() + +Assign the shipment now:: + + >>> ShipmentOut.workflow_trigger_validate( + ... shipment_out.id, 'assign', config.context) + >>> shipment_out.reload() + >>> len(shipment_out.outgoing_moves) + 2 + >>> len(shipment_out.inventory_moves) + 2 + >>> states = [m.state for m in shipment_out.inventory_moves] + >>> states.sort() + >>> states + [u'assigned', u'draft'] + +Delete the draft move, assign and pack shipment:: + + >>> for move in shipment_out.inventory_moves: + ... if move.state == 'draft': + ... break + >>> shipment_out.inventory_moves.remove(move) + >>> shipment_out.save() + >>> ShipmentOut.workflow_trigger_validate( + ... shipment_out.id, 'assign', config.context) + >>> ShipmentOut.workflow_trigger_validate( + ... shipment_out.id, 'packed', config.context) + >>> shipment_out.reload() + >>> set([m.state for m in shipment_out.outgoing_moves]) + set([u'assigned']) + >>> len(shipment_out.outgoing_moves) + 2 + >>> len(shipment_out.inventory_moves) + 1 + >>> shipment_out.inventory_moves[0].state + u'done' + >>> sum([m.quantity for m in shipment_out.inventory_moves]) == \ + ... sum([m.quantity for m in shipment_out.outgoing_moves]) + True + +Set the state as Done:: + + >>> ShipmentOut.workflow_trigger_validate( + ... shipment_out.id, 'done', config.context) + >>> shipment_out.reload() + >>> set([m.state for m in shipment_out.outgoing_moves]) + set([u'done']) + >>> len(shipment_out.outgoing_moves) + 2 + >>> len(shipment_out.inventory_moves) + 1 + >>> shipment_out.inventory_moves[0].state + u'done' + >>> sum([m.quantity for m in shipment_out.inventory_moves]) == \ + ... sum([m.quantity for m in shipment_out.outgoing_moves]) + True + + diff --git a/tests/test_stock.py b/tests/test_stock.py index c7379ff3aa..d81ba98619 100644 --- a/tests/test_stock.py +++ b/tests/test_stock.py @@ -9,6 +9,7 @@ if os.path.isdir(DIR): sys.path.insert(0, os.path.dirname(DIR)) import unittest +import doctest import datetime from decimal import Decimal from dateutil.relativedelta import relativedelta @@ -16,6 +17,7 @@ from functools import partial import trytond.tests.test_tryton from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, test_view,\ test_depends +from trytond.backend.sqlite.database import Database as SQLiteDatabase from trytond.transaction import Transaction @@ -459,6 +461,19 @@ class StockTestCase(unittest.TestCase): self.assertRaises(Exception, self.period.button_close, [period_id]) +def doctest_dropdb(test): + ''' + Remove sqlite memory database + ''' + database = SQLiteDatabase().connect() + cursor = database.cursor(autocommit=True) + try: + database.drop(cursor, ':memory:') + cursor.commit() + finally: + cursor.close() + + def suite(): suite = trytond.tests.test_tryton.suite() from trytond.modules.company.tests import test_company @@ -466,6 +481,10 @@ def suite(): if test not in suite: suite.addTest(test) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(StockTestCase)) + suite.addTests(doctest.DocFileSuite('scenario_stock_shipment_out.rst', + setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8', + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) + return suite if __name__ == '__main__':