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)
This commit is contained in:
1 parent
90a0929cdd
commit
4c26686ad8
4 files changed
+220
-3
No files matched your search
@@ -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.
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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__':
|
||||
|
||||
Reference in new issue
Block a user