# 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 decimal import Decimal from functools import partial from dateutil.relativedelta import relativedelta from trytond.model.exceptions import AccessError from trytond.modules.company.tests import ( CompanyTestMixin, PartyCompanyCheckEraseMixin, create_company, set_company) from trytond.modules.party.tests import PartyCheckReplaceMixin from trytond.modules.stock.exceptions import ( LocationValidationError, MoveOriginWarning, PeriodCloseError, ProductStockWarning) from trytond.pool import Pool from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.transaction import Transaction, check_access class StockTestCase( PartyCompanyCheckEraseMixin, PartyCheckReplaceMixin, CompanyTestMixin, ModuleTestCase): 'Test Stock module' module = 'stock' longMessage = True @with_transaction() def test_move_internal_quantity(self): 'Test Move.internal_quantity' pool = Pool() Uom = pool.get('product.uom') Template = pool.get('product.template') Product = pool.get('product.product') Location = pool.get('stock.location') Move = pool.get('stock.move') kg, = Uom.search([('name', '=', 'Kilogram')]) g, = Uom.search([('name', '=', 'Gram')]) template, = Template.create([{ 'name': 'Test Move.internal_quantity', 'type': 'goods', 'default_uom': kg.id, }]) product, = Product.create([{ 'template': template.id, }]) supplier, = Location.search([('code', '=', 'SUP')]) storage, = Location.search([('code', '=', 'STO')]) company = create_company() currency = company.currency with set_company(company): tests = [ (kg, 10, 10, 0), (g, 100, 0.1, 1), (g, 1, 0, 0), # rounded (kg, 35.23, 35.23, 2), # check infinite loop ] for unit, quantity, internal_quantity, ndigits in tests: move, = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': quantity, 'from_location': supplier.id, 'to_location': storage.id, 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }]) self.assertEqual(round(move.internal_quantity, ndigits), internal_quantity) for unit, quantity, internal_quantity, ndigits in tests: Move.write([move], { 'unit': unit.id, 'quantity': quantity, }) self.assertEqual(round(move.internal_quantity, ndigits), internal_quantity) @with_transaction() def test_products_by_location(self): 'Test products_by_location' pool = Pool() Uom = pool.get('product.uom') Template = pool.get('product.template') Product = pool.get('product.product') Location = pool.get('stock.location') Move = pool.get('stock.move') Period = pool.get('stock.period') transaction = Transaction() kg, = Uom.search([('name', '=', 'Kilogram')]) g, = Uom.search([('name', '=', 'Gram')]) template, = Template.create([{ 'name': 'Test products_by_location', 'type': 'goods', 'default_uom': kg.id, }]) product, = Product.create([{ 'template': template.id, }]) supplier, = Location.search([('code', '=', 'SUP')]) customer, = Location.search([('code', '=', 'CUS')]) storage, = Location.search([('code', '=', 'STO')]) storage_empty, = Location.copy([storage]) company = create_company() currency = company.currency with set_company(company): today = datetime.date.today() moves = Move.create([{ 'product': product.id, 'unit': kg.id, 'quantity': 5, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': today + relativedelta(days=-5), 'effective_date': today + relativedelta(days=-5), 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }, { 'product': product.id, 'unit': kg.id, 'quantity': 1, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': today + relativedelta(days=-4), 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }, { 'product': product.id, 'unit': kg.id, 'quantity': 1, 'from_location': storage.id, 'to_location': customer.id, 'planned_date': today, 'effective_date': today, 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }, { 'product': product.id, 'unit': kg.id, 'quantity': 1, 'from_location': storage.id, 'to_location': customer.id, 'planned_date': today, 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }, { 'product': product.id, 'unit': kg.id, 'quantity': 2, 'from_location': storage.id, 'to_location': customer.id, 'planned_date': today + relativedelta(days=5), 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }, { 'product': product.id, 'unit': kg.id, 'quantity': 5, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': today + relativedelta(days=7), 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }]) Move.do([moves[0], moves[2]]) products_by_location = partial(Product.products_by_location, [storage.id], grouping_filter=([product.id],)) tests = [ ({'stock_date_end': today + relativedelta(days=-6), }, 0), ({'stock_date_end': today + relativedelta(days=-5), }, 5), ({'stock_date_end': today + relativedelta(days=-4), }, 5), ({'stock_date_end': today + relativedelta(days=-3), }, 5), ({'stock_date_end': today, }, 4), ({'stock_date_end': today + relativedelta(days=1), }, 3), ({'stock_date_end': today + relativedelta(days=5), }, 1), ({'stock_date_end': today + relativedelta(days=6), }, 1), ({'stock_date_end': today + relativedelta(days=7), }, 6), ({'stock_date_end': today + relativedelta(days=8), }, 6), ({'stock_date_end': False, }, 6), ({'stock_date_end': today + relativedelta(days=-6), 'forecast': True, }, 0), ({'stock_date_end': today + relativedelta(days=-5), 'forecast': True, }, 5), ({'stock_date_end': today + relativedelta(days=-4), 'forecast': True, }, 5), ({'stock_date_end': today + relativedelta(days=-3), 'forecast': True, }, 5), ({'stock_date_end': today, 'forecast': True, }, 3), ({'stock_date_end': today + relativedelta(days=1), 'forecast': True, }, 3), ({'stock_date_end': today + relativedelta(days=5), 'forecast': True, }, 1), ({'stock_date_end': today + relativedelta(days=6), 'forecast': True, }, 1), ({'stock_date_end': today + relativedelta(days=7), 'forecast': True, }, 6), ({'stock_date_end': False, 'forecast': True, }, 6), ] today_quantity = 4 def tests_product_quantity(context, quantity): product_reloaded = Product(product.id) if (not context.get('stock_date_end') or context['stock_date_end'] > today): self.assertEqual( product_reloaded.forecast_quantity, quantity, msg='context %r' % context) self.assertEqual( product_reloaded.quantity, today_quantity, msg='context %r' % context) elif context.get('forecast'): self.assertEqual( product_reloaded.forecast_quantity, quantity, msg='context %r' % context) elif context.get('stock_date_end') == today: self.assertEqual(product_reloaded.quantity, quantity, msg='context %r' % context) else: self.assertEqual( product_reloaded.forecast_quantity, quantity, msg='context %r' % context) self.assertEqual(product_reloaded.quantity, quantity, msg='context %r' % context) def tests_product_search_quantity(context, quantity): if (not context.get('stock_date_end') or context['stock_date_end'] > today or context.get('forecast')): fname = 'forecast_quantity' else: fname = 'quantity' found_products = Product.search([ (fname, '=', quantity), ]) self.assertIn(product, found_products) found_products = Product.search([ (fname, '!=', quantity), ]) self.assertNotIn(product, found_products) found_products = Product.search([ (fname, 'in', (quantity, quantity + 1)), ]) self.assertIn(product, found_products) found_products = Product.search([ (fname, 'not in', (quantity, quantity + 1)), ]) self.assertNotIn(product, found_products) found_products = Product.search([ (fname, '<', quantity), ]) self.assertNotIn(product, found_products) found_products = Product.search([ (fname, '<', quantity + 1), ]) self.assertIn(product, found_products) found_products = Product.search([ (fname, '>', quantity), ]) self.assertNotIn(product, found_products) found_products = Product.search([ (fname, '>', quantity - 1), ]) self.assertIn(product, found_products) found_products = Product.search([ (fname, '>=', quantity), ]) self.assertIn(product, found_products) found_products = Product.search([ (fname, '<=', quantity), ]) self.assertIn(product, found_products) def test_products_by_location(): for context, quantity in tests: with transaction.set_context(context): if not quantity: self.assertEqual(products_by_location(), {}) else: self.assertEqual(products_by_location(), {(storage.id, product.id): quantity}) with transaction.set_context( locations=[storage.id]): tests_product_quantity(context, quantity) tests_product_search_quantity( context, quantity) with transaction.set_context( locations=[storage.id, storage_empty.id]): tests_product_quantity(context, quantity) tests_product_search_quantity( context, quantity) test_products_by_location() periods = [ today + relativedelta(days=-6), today + relativedelta(days=-5), today + relativedelta(days=-4), today + relativedelta(days=-3), today + relativedelta(days=-2), ] moves = Move.create([{ 'product': product.id, 'unit': g.id, 'quantity': 1, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': today + relativedelta(days=-5), 'effective_date': (today + relativedelta(days=-5)), 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }]) Move.do(moves) # Nothing should change when adding a small quantity test_products_by_location() for period_date in periods: period, = Period.create([{ 'date': period_date, 'company': company.id, }]) Period.close([period]) test_products_by_location() @with_transaction() def test_products_by_location_with_childs(self): 'Test products_by_location with_childs and stock_skip_warehouse' pool = Pool() Uom = pool.get('product.uom') Template = pool.get('product.template') Product = pool.get('product.product') Location = pool.get('stock.location') Move = pool.get('stock.move') unit, = Uom.search([('name', '=', 'Unit')]) template, = Template.create([{ 'name': 'Test products_by_location', 'type': 'goods', 'default_uom': unit.id, }]) product, = Product.create([{ 'template': template.id, }]) lost_found, = Location.search([('type', '=', 'lost_found')]) warehouse, = Location.search([('type', '=', 'warehouse')]) storage, = Location.search([('code', '=', 'STO')]) input_, = Location.search([('code', '=', 'IN')]) storage1, = Location.create([{ 'name': 'Storage 1', 'type': 'view', 'parent': storage.id, }]) storage2, = Location.create([{ 'name': 'Storage 1.1', 'type': 'view', 'parent': storage1.id, }]) storage3, = Location.create([{ 'name': 'Storage 2', 'type': 'view', 'parent': storage.id, }]) company = create_company() with set_company(company): today = datetime.date.today() moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': lost_found.id, 'to_location': storage.id, 'planned_date': today, 'effective_date': today, 'company': company.id, }, { 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': input_.id, 'to_location': storage.id, 'planned_date': today, 'effective_date': today, 'company': company.id, }]) Move.do(moves) products_by_location = Product.products_by_location( [warehouse.id], grouping_filter=([product.id],), with_childs=True) self.assertEqual(products_by_location[(warehouse.id, product.id)], 1) products_by_location = Product.products_by_location( [warehouse.id, storage.id], grouping_filter=([product.id],), with_childs=True) self.assertEqual( products_by_location[(warehouse.id, product.id)], 1) self.assertEqual(products_by_location[(storage.id, product.id)], 2) with Transaction().set_context(locations=[warehouse.id]): found_products = Product.search([ ('quantity', '=', 1), ]) self.assertListEqual([product], found_products) with Transaction().set_context(stock_skip_warehouse=True): products_by_location = Product.products_by_location( [warehouse.id], grouping_filter=([product.id],), with_childs=True) products_by_location_all = Product.products_by_location( [warehouse.id], with_childs=True) self.assertEqual( products_by_location[(warehouse.id, product.id)], 2) self.assertEqual( products_by_location_all[(warehouse.id, product.id)], 2) with Transaction().set_context(locations=[warehouse.id]): found_products = Product.search([ ('quantity', '=', 2), ]) self.assertListEqual([product], found_products) @with_transaction() def test_products_by_location_flat_childs(self, period_closed=False): "Test products_by_location on flat_childs" pool = Pool() Uom = pool.get('product.uom') Template = pool.get('product.template') Product = pool.get('product.product') Location = pool.get('stock.location') Move = pool.get('stock.move') Date = pool.get('ir.date') Period = pool.get('stock.period') unit, = Uom.search([('name', '=', 'Unit')]) template, = Template.create([{ 'name': "Product", 'type': 'goods', 'default_uom': unit.id, }]) product, = Product.create([{ 'template': template.id, }]) lost_found, = Location.search([('type', '=', 'lost_found')]) storage, = Location.search([('code', '=', 'STO')]) storage.flat_childs = True storage.save() storage1, = Location.create([{ 'name': 'Storage 1', 'type': 'storage', 'parent': storage.id, }]) storage2, = Location.create([{ 'name': 'Storage 2', 'type': 'storage', 'parent': storage.id, }]) company = create_company() with set_company(company): date = Date.today() - relativedelta(days=1) moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': lost_found.id, 'to_location': storage.id, 'planned_date': date, 'effective_date': date, 'company': company.id, }, { 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': lost_found.id, 'to_location': storage1.id, 'planned_date': date, 'effective_date': date, 'company': company.id, }, { 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': storage1.id, 'to_location': storage2.id, 'planned_date': date, 'effective_date': date, 'company': company.id, }]) Move.do(moves) if period_closed: period, = Period.create([{ 'date': date, 'company': company.id, }]) Period.close([period]) # Test flat location products_by_location = Product.products_by_location( [storage.id], grouping_filter=([product.id],), with_childs=True) self.assertEqual( products_by_location[(storage.id, product.id)], 2) # Test mixed flat and nested location products_by_location = Product.products_by_location( [storage.parent.id, storage.id, storage1.id, storage2.id], grouping_filter=([product.id],), with_childs=True) self.assertEqual( products_by_location[(storage.parent.id, product.id)], 2) self.assertEqual( products_by_location[(storage.id, product.id)], 2) self.assertEqual( products_by_location[(storage1.id, product.id)], 0) self.assertEqual( products_by_location[(storage2.id, product.id)], 1) # Test non flat products_by_location = Product.products_by_location( [lost_found.id], grouping_filter=([product.id],), with_childs=True) self.assertEqual( products_by_location[(lost_found.id, product.id)], -2) def test_products_by_location_flat_childs_period_closed(self): "Test products_by_location on flat_childs with period closed" self.test_products_by_location_flat_childs(period_closed=True) @with_transaction() def test_products_by_location_2nd_level_flat_childs(self): "Test products_by_location on 2nd level flat_childs" pool = Pool() Uom = pool.get('product.uom') Template = pool.get('product.template') Product = pool.get('product.product') Location = pool.get('stock.location') Move = pool.get('stock.move') Date = pool.get('ir.date') unit, = Uom.search([('name', '=', 'Unit')]) template, = Template.create([{ 'name': "Product", 'type': 'goods', 'default_uom': unit.id, }]) product, = Product.create([{ 'template': template.id, }]) supplier, = Location.search([('code', '=', 'SUP')]) storage, = Location.search([('code', '=', 'STO')]) storage1, = Location.create([{ 'name': 'Storage 1', 'type': 'storage', 'flat_childs': True, 'parent': storage.id, }]) storage2, = Location.create([{ 'name': 'Storage 2', 'type': 'storage', 'parent': storage1.id, }]) company = create_company() with set_company(company): date = Date.today() - relativedelta(days=1) moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 80, 'from_location': supplier.id, 'to_location': storage.id, 'effective_date': date, 'company': company.id, 'unit_price': 10.0, 'currency': company.currency.id, }, { 'product': product.id, 'unit': unit.id, 'quantity': 60, 'from_location': storage.id, 'to_location': storage2.id, 'effective_date': date, 'company': company.id, }]) Move.do(moves) # Test 2nd level location products_by_location = Product.products_by_location( [storage.id], grouping_filter=([product.id],), with_childs=True) self.assertEqual( products_by_location[(storage.id, product.id)], 80) # Test 1st level and 2nd level nested locations products_by_location = Product.products_by_location( [storage.id, storage1.id], grouping_filter=([product.id],), with_childs=True) self.assertEqual( products_by_location[(storage.id, product.id)], 80) self.assertEqual( products_by_location[(storage1.id, product.id)], 60) # Test mixed flat and 2nd level nested locations products_by_location = Product.products_by_location( [storage.id, storage2.id], grouping_filter=([product.id],), with_childs=True) self.assertEqual( products_by_location[(storage.id, product.id)], 80) self.assertEqual( products_by_location[(storage2.id, product.id)], 60) @with_transaction() def test_products_by_location_grouped_by_date(self): "Test products_by_location grouped by date" pool = Pool() Uom = pool.get('product.uom') Template = pool.get('product.template') Product = pool.get('product.product') Location = pool.get('stock.location') Move = pool.get('stock.move') Date = pool.get('ir.date') unit, = Uom.search([('name', '=', 'Unit')]) template, = Template.create([{ 'name': "Template", 'type': 'goods', 'default_uom': unit.id, }]) product, = Product.create([{ 'template': template.id, }]) lost_found, = Location.search([('type', '=', 'lost_found')]) storage, = Location.search([('code', '=', 'STO')]) output, = Location.search([('code', '=', 'OUT')]) company = create_company() with set_company(company): today = Date.today() moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 10, 'from_location': lost_found.id, 'to_location': storage.id, 'effective_date': today, 'company': company.id, }, { 'product': product.id, 'unit': unit.id, 'quantity': 2, 'from_location': storage.id, 'to_location': output.id, 'effective_date': today + relativedelta(days=1), 'company': company.id, }, { 'product': product.id, 'unit': unit.id, 'quantity': 3, 'from_location': storage.id, 'to_location': output.id, 'effective_date': today + relativedelta(days=5), 'company': company.id, }]) with Transaction().set_context(_skip_warnings=True): Move.do(moves) with Transaction().set_context(stock_date_start=today): products_by_location = Product.products_by_location( [storage.id], grouping=('date', 'product'), grouping_filter=(None, [product.id])) self.assertDictEqual(products_by_location, { (storage.id, today, product.id): 10, (storage.id, today + relativedelta(days=1), product.id): -2, (storage.id, today + relativedelta(days=5), product.id): -3, }) @with_transaction() def test_templates_by_location(self, period_closed=False): "Test products_by_location grouped by template" pool = Pool() Uom = pool.get('product.uom') Template = pool.get('product.template') Product = pool.get('product.product') Location = pool.get('stock.location') Move = pool.get('stock.move') Period = pool.get('stock.period') Date = pool.get('ir.date') unit, = Uom.search([('name', '=', 'Unit')]) template, = Template.create([{ 'name': 'Template', 'type': 'goods', 'default_uom': unit.id, }]) product1, product2 = Product.create([{ 'template': template.id, }] * 2) lost_found, = Location.search([('type', '=', 'lost_found')]) storage, = Location.search([('code', '=', 'STO')]) input_, = Location.search([('code', '=', 'IN')]) company = create_company() with set_company(company): date = Date.today() - relativedelta(days=1) moves = Move.create([{ 'product': product1.id, 'unit': unit.id, 'quantity': 2, 'from_location': lost_found.id, 'to_location': storage.id, 'planned_date': date, 'effective_date': date, 'company': company.id, }, { 'product': product2.id, 'unit': unit.id, 'quantity': 3, 'from_location': input_.id, 'to_location': storage.id, 'planned_date': date, 'effective_date': date, 'company': company.id, }]) Move.do(moves) if period_closed: period, = Period.create([{ 'date': date, 'company': company.id, }]) Period.close([period]) templates_by_location = Product.products_by_location( [storage.id], grouping=('product.template',), grouping_filter=([template.id],)) self.assertDictEqual(templates_by_location, { (storage.id, template.id): 5, }) def test_templates_by_location_period_closed(self): "Test products_by_location grouped by template with period closed" self.test_templates_by_location(period_closed=True) @with_transaction() def test_period(self): 'Test period' pool = Pool() Uom = pool.get('product.uom') Template = pool.get('product.template') Product = pool.get('product.product') Location = pool.get('stock.location') Move = pool.get('stock.move') Period = pool.get('stock.period') transaction = Transaction() unit, = Uom.search([('name', '=', 'Unit')]) template, = Template.create([{ 'name': 'Test period', 'type': 'goods', 'default_uom': unit.id, }]) product, = Product.create([{ 'template': template.id, }]) supplier, = Location.search([('code', '=', 'SUP')]) customer, = Location.search([('code', '=', 'CUS')]) storage, = Location.search([('code', '=', 'STO')]) company = create_company() currency = company.currency with set_company(company): today = datetime.date.today() moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 10, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': today + relativedelta(days=-5), 'effective_date': today + relativedelta(days=-5), 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }, { 'product': product.id, 'unit': unit.id, 'quantity': 15, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': today + relativedelta(days=-4), 'effective_date': today + relativedelta(days=-4), 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }, { 'product': product.id, 'unit': unit.id, 'quantity': 5, 'from_location': storage.id, 'to_location': customer.id, 'planned_date': today + relativedelta(days=-3), 'effective_date': today + relativedelta(days=-3), 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }]) Move.do(moves) Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 3, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': None, 'effective_date': None, 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }]) tests = [ (-5, { supplier.id: -10, storage.id: 10, }), (-3, { supplier.id: -25, storage.id: 20, customer.id: 5, }) ] products_by_location = partial(Product.products_by_location, [storage.id], grouping_filter=([product.id],)) tests_pbl = [ ({'stock_date_end': today + relativedelta(days=-6)}, 0), ({'stock_date_end': today + relativedelta(days=-5)}, 10), ({'stock_date_end': today + relativedelta(days=-4)}, 25), ({'stock_date_end': today + relativedelta(days=-3)}, 20), ({'stock_date_end': today + relativedelta(days=-2)}, 20), ({'stock_date_end': today + relativedelta(days=-1)}, 20), ({'stock_date_end': today}, 20), ({'stock_date_end': datetime.date.max}, 23), ] def test_products_by_location(): for context, quantity in tests_pbl: with transaction.set_context(context): if not quantity: self.assertEqual(products_by_location(), {}) else: self.assertEqual(products_by_location(), {(storage.id, product.id): quantity}) test_products_by_location() for days, quantities in tests: period, = Period.create([{ 'date': today + relativedelta(days=days), 'company': company.id, }]) Period.close([period]) self.assertEqual(period.state, 'closed') caches = period.caches for cache in caches: self.assertEqual(cache.product, product) self.assertEqual(cache.internal_quantity, quantities[cache.location.id]) test_products_by_location() # Test check_period_closed moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 10, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': today, 'effective_date': today, 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }]) Move.do(moves) self.assertRaises(AccessError, Move.create, [{ 'product': product.id, 'unit': unit.id, 'quantity': 10, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': today + relativedelta(days=-5), 'effective_date': today + relativedelta(days=-5), 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }]) self.assertRaises(AccessError, Move.create, [{ 'product': product.id, 'unit': unit.id, 'quantity': 10, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': today + relativedelta(days=-3), 'effective_date': today + relativedelta(days=-3), 'company': company.id, 'unit_price': Decimal('1'), 'currency': currency.id, }]) # Test close period check period, = Period.create([{ 'date': today, 'company': company.id, }]) self.assertRaises(PeriodCloseError, Period.close, [period]) period, = Period.create([{ 'date': today + relativedelta(days=1), 'company': company.id, }]) self.assertRaises(PeriodCloseError, Period.close, [period]) @with_transaction() def test_check_origin(self): 'Test Move check_origin' pool = Pool() Uom = pool.get('product.uom') Template = pool.get('product.template') Product = pool.get('product.product') Location = pool.get('stock.location') Move = pool.get('stock.move') unit, = Uom.search([('name', '=', 'Unit')]) template, = Template.create([{ 'name': 'Test Move.check_origin', 'type': 'goods', 'default_uom': unit.id, }]) product, = Product.create([{ 'template': template.id, }]) storage, = Location.search([('code', '=', 'STO')]) customer, = Location.search([('code', '=', 'CUS')]) company = create_company() with set_company(company): moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': storage.id, 'to_location': customer.id, 'company': company.id, 'unit_price': Decimal(1), 'currency': company.currency.id, }]) Move.check_origin(moves, set()) Move.check_origin(moves, {'supplier'}) self.assertRaises(MoveOriginWarning, Move.check_origin, moves, {'customer'}) def test_assign_try(self): 'Test Move assign_try' for quantity, quantities, success, result in [ (10, [2], True, {'assigned': [2]}), (5, [10], False, {'assigned': [5], 'draft': [5]}), (0, [3], False, {'draft': [3]}), (6.8, [2.1, 1.7, 1.2, 1.8], True, {'assigned': sorted([2.1, 1.7, 1.2, 1.8])}), ]: self._test_assign_try(quantity, quantities, success, result) @with_transaction() def _test_assign_try(self, quantity, quantities, success, result): pool = Pool() Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') Location = pool.get('stock.location') Move = pool.get('stock.move') unit, = Uom.search([('name', '=', 'Meter')]) template = Template( name='Test Move.assign_try', type='goods', default_uom=unit, ) template.save() product = Product(template=template.id) product.save() supplier, = Location.search([('code', '=', 'SUP')]) storage, = Location.search([('code', '=', 'STO')]) customer, = Location.search([('code', '=', 'CUS')]) company = create_company() with set_company(company): move, = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': quantity, 'from_location': supplier.id, 'to_location': storage.id, 'company': company.id, 'unit_price': Decimal(1), 'currency': company.currency.id, }]) Move.do([move]) moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': qty, 'from_location': storage.id, 'to_location': customer.id, 'company': company.id, 'unit_price': Decimal(1), 'currency': company.currency.id, } for qty in quantities]) msg = 'quantity: %s, quantities: %s' % (quantity, quantities) self.assertEqual(Move.assign_try(moves), success, msg=msg) moves = Move.search([ ('product', '=', product.id), ('from_location', '=', storage.id), ('to_location', '=', customer.id), ('company', '=', company.id), ]) states = defaultdict(list) for move in moves: states[move.state].append(move.quantity) for state in states: states[state].sort() self.assertEqual(states, result, msg=msg) @with_transaction() def test_assign_try_chained(self): "Test Move assign_try chained" pool = Pool() Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') Location = pool.get('stock.location') Move = pool.get('stock.move') unit, = Uom.search([('name', '=', 'Meter')]) template = Template( name='Test Move.assign_try', type='goods', default_uom=unit, ) template.save() product = Product(template=template.id) product.save() supplier, = Location.search([('code', '=', 'SUP')]) storage, = Location.search([('code', '=', 'STO')]) storage2, = Location.copy([storage]) storage3, = Location.copy([storage]) company = create_company() with set_company(company): move, = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': supplier.id, 'to_location': storage.id, 'company': company.id, 'unit_price': Decimal(1), 'currency': company.currency.id, }]) Move.do([move]) moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': from_.id, 'to_location': to.id, 'company': company.id, } for from_, to in [ (storage, storage2), (storage2, storage3)]]) self.assertFalse(Move.assign_try(moves)) self.assertEqual([m.state for m in moves], ['assigned', 'draft']) @with_transaction() def test_assign_try_skip_to_location(self): "Test Move assign_try skip to_location" pool = Pool() Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') Location = pool.get('stock.location') Move = pool.get('stock.move') unit, = Uom.search([('name', '=', 'Meter')]) template = Template( name='Test Move.assign_try', type='goods', default_uom=unit, ) template.save() product = Product(template=template.id) product.save() supplier, = Location.search([('code', '=', 'SUP')]) storage, = Location.search([('code', '=', 'STO')]) child, = Location.copy([storage], default={'parent': storage.id}) company = create_company() with set_company(company): move, = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': supplier.id, 'to_location': child.id, 'company': company.id, 'unit_price': Decimal(1), 'currency': company.currency.id, }]) Move.do([move]) move, = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': storage.id, 'to_location': child.id, 'company': company.id, }]) self.assertFalse(Move.assign_try([move])) self.assertEqual(move.state, 'draft') @with_transaction() def test_assign_try_prefer_from_location(self): "Test Move assign_try prefer from location" pool = Pool() Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') Location = pool.get('stock.location') Move = pool.get('stock.move') unit, = Uom.search([('name', '=', 'Meter')]) template = Template( name='Test Move.assign_try', type='goods', default_uom=unit, ) template.save() product = Product(template=template.id) product.save() supplier, = Location.search([('code', '=', 'SUP')]) storage, = Location.search([('code', '=', 'STO')]) # Ensure storage2 comes first when ordering locations by name storage2, = Location.copy( [storage], default={'name': "AAA", 'parent': storage.id}) customer, = Location.search([('code', '=', 'CUS')]) company = create_company() with set_company(company): moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': supplier.id, 'to_location': storage.id, 'company': company.id, 'unit_price': Decimal(1), 'currency': company.currency.id, }, { 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': supplier.id, 'to_location': storage2.id, 'company': company.id, 'unit_price': Decimal(1), 'currency': company.currency.id, }]) Move.do(moves) move, = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': storage.id, 'to_location': customer.id, 'company': company.id, 'unit_price': Decimal(1), 'currency': company.currency.id, }]) Move.assign_try([move]) self.assertEqual(move.state, 'assigned') self.assertEqual(move.from_location, storage) @with_transaction() def test_assign_without_moves(self): "Test Move assign_try with empty moves" pool = Pool() Move = pool.get('stock.move') self.assertTrue(Move.assign_try([])) @with_transaction() def test_assign_try_modified_move(self): "Test Move assign_try with modified move" pool = Pool() Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') Location = pool.get('stock.location') Move = pool.get('stock.move') unit, = Uom.search([('name', '=', 'Meter')]) template = Template( name='Test Move.assign_try', type='goods', default_uom=unit, ) template.save() product = Product(template=template.id) product.save() supplier, = Location.search([('code', '=', 'SUP')]) storage, = Location.search([('code', '=', 'STO')]) storage2, = Location.copy([storage]) customer, = Location.search([('code', '=', 'CUS')]) company = create_company() with set_company(company): move, = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': supplier.id, 'to_location': storage.id, 'company': company.id, 'unit_price': Decimal(1), 'currency': company.currency.id, }]) Move.do([move]) move, = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 2, 'from_location': storage2.id, 'to_location': customer.id, 'company': company.id, 'unit_price': Decimal(1), 'currency': company.currency.id, }]) # Assign from a different location move.from_location = storage self.assertFalse(Move.assign_try([move])) move, = Move.search([ ('to_location', '=', customer.id), ('state', '=', 'assigned'), ]) self.assertEqual(move.from_location, storage) self.assertEqual(move.quantity, 1) move, = Move.search([ ('to_location', '=', customer.id), ('state', '=', 'draft'), ]) # Remaining move has the original location self.assertEqual(move.from_location, storage2) self.assertEqual(move.quantity, 1) @with_transaction() def test_products_by_location_assign(self): "Test products by location for assignation" pool = Pool() Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') Location = pool.get('stock.location') Move = pool.get('stock.move') today = datetime.date.today() unit, = Uom.search([('name', '=', 'Unit')]) template = Template( name="Product", type='goods', default_uom=unit, ) template.save() product, = Product.create([{'template': template.id}]) supplier, = Location.search([('code', '=', 'SUP')]) storage, = Location.search([('code', '=', 'STO')]) customer, = Location.search([('code', '=', 'CUS')]) company = create_company() with set_company(company): move_supplier, = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 10, 'from_location': supplier.id, 'to_location': storage.id, 'planned_date': today, 'company': company.id, 'unit_price': Decimal('1'), 'currency': company.currency.id, }]) move_customer, = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 5, 'from_location': storage.id, 'to_location': customer.id, 'planned_date': today + relativedelta(days=1), 'company': company.id, 'unit_price': Decimal('1'), 'currency': company.currency.id, }]) Move.assign([move_supplier]) with Transaction().set_context( stock_date_end=today, stock_assign=True): pbl = Product.products_by_location( [storage.id], grouping_filter=([product.id],)) self.assertDictEqual(pbl, {}) Move.assign([move_customer]) with Transaction().set_context( stock_date_end=today, stock_assign=True): pbl = Product.products_by_location( [storage.id], grouping_filter=([product.id],)) self.assertDictEqual(pbl, {(storage.id, product.id): -5}) Move.do([move_supplier]) with Transaction().set_context( stock_date_end=today, stock_assign=True): pbl = Product.products_by_location( [storage.id], grouping_filter=([product.id],)) self.assertDictEqual(pbl, {(storage.id, product.id): 5}) with Transaction().set_context( stock_date_end=today + relativedelta(days=1), stock_assign=True): pbl = Product.products_by_location( [storage.id], grouping_filter=([product.id],)) self.assertDictEqual(pbl, {(storage.id, product.id): 5}) with Transaction().set_context( stock_date_start=today - relativedelta(days=1), stock_date_end=today - relativedelta(days=1), stock_assign=True): pbl = Product.products_by_location( [storage.id], grouping_filter=([product.id],)) self.assertDictEqual(pbl, {}) with Transaction().set_context( stock_date_start=today - relativedelta(days=1), stock_date_end=today, stock_assign=True): pbl = Product.products_by_location( [storage.id], grouping_filter=([product.id],)) self.assertDictEqual(pbl, {(storage.id, product.id): 5}) with Transaction().set_context( stock_date_start=today, stock_date_end=today, stock_assign=True): pbl = Product.products_by_location( [storage.id], grouping_filter=([product.id],)) self.assertDictEqual(pbl, {(storage.id, product.id): 5}) with Transaction().set_context( stock_date_start=today, stock_date_end=today + relativedelta(days=1), stock_assign=True): pbl = Product.products_by_location( [storage.id], grouping_filter=([product.id],)) self.assertDictEqual(pbl, {(storage.id, product.id): 5}) with Transaction().set_context( stock_date_start=today + relativedelta(days=1), stock_date_end=today + relativedelta(days=1), stock_assign=True): pbl = Product.products_by_location( [storage.id], grouping_filter=([product.id],)) self.assertDictEqual(pbl, {}) @with_transaction() def test_location_inactive_without_move(self): "Test inactivate location without move" pool = Pool() Location = pool.get('stock.location') storage, = Location.search([('code', '=', 'STO')]) location, = Location.create([{ 'name': "Location", 'parent': storage.id, }]) location.active = False location.save() @with_transaction() def test_location_inactive_with_quantity(self): "Test inactivate location with quantity" pool = Pool() Location = pool.get('stock.location') Move = pool.get('stock.move') Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') storage, = Location.search([('code', '=', 'STO')]) location, = Location.create([{ 'name': "Location", 'parent': storage.id, }]) unit, = Uom.search([('name', '=', "Unit")]) template, = Template.create([{ 'name': "Product", 'type': 'goods', 'default_uom': unit.id, }]) product, = Product.create([{'template': template.id}]) company = create_company() with set_company(company): moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': storage.id, 'to_location': location.id, 'company': company.id, }]) Move.do(moves) with self.assertRaises(LocationValidationError): location.active = False location.save() @with_transaction() def test_location_inactive_with_draft_moves(self): "Test inactivate location with draft moves" pool = Pool() Location = pool.get('stock.location') Move = pool.get('stock.move') Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') storage, = Location.search([('code', '=', 'STO')]) location, = Location.create([{ 'name': "Location", 'parent': storage.id, }]) unit, = Uom.search([('name', '=', "Unit")]) template, = Template.create([{ 'name': "Product", 'type': 'goods', 'default_uom': unit.id, }]) product, = Product.create([{'template': template.id}]) company = create_company() with set_company(company): Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': storage.id, 'to_location': location.id, 'company': company.id, }, { 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': location.id, 'to_location': storage.id, 'company': company.id, }]) with self.assertRaises(LocationValidationError): location.active = False location.save() @with_transaction() def test_inactive_products_with_stock(self): "Test inactivate products with stock" pool = Pool() Location = pool.get('stock.location') Move = pool.get('stock.move') Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') storage, = Location.search([('code', '=', 'STO')]) supplier, = Location.search([('code', '=', 'SUP')]) unit, = Uom.search([('name', '=', "Unit")]) template, = Template.create([{ 'name': "Product", 'type': 'goods', 'default_uom': unit.id, }]) products = Product.create([{'template': template.id}] * 2) product, product2 = products company = create_company() with set_company(company): Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': supplier.id, 'to_location': storage.id, 'unit_price': Decimal('5'), 'currency': company.currency.id, 'company': company.id, }, { 'product': product2.id, 'unit': unit.id, 'quantity': 1, 'from_location': supplier.id, 'to_location': storage.id, 'unit_price': Decimal('5'), 'currency': company.currency.id, 'company': company.id, }]) with check_access(): with self.assertRaises(ProductStockWarning): Product.write(products, {'active': False}) with self.assertRaises(ProductStockWarning): Template.write([template], {'active': False}) Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': storage.id, 'to_location': supplier.id, 'unit_price': Decimal('5'), 'currency': company.currency.id, 'company': company.id, }, { 'product': product2.id, 'unit': unit.id, 'quantity': 1, 'from_location': storage.id, 'to_location': supplier.id, 'unit_price': Decimal('5'), 'currency': company.currency.id, 'company': company.id, }]) with check_access(): Template.write([template], {'active': False}) Product.write(products, {'active': False}) @with_transaction() def test_inactive_products_one_with_stock(self): "Test inactivate products one with stock" pool = Pool() Location = pool.get('stock.location') Move = pool.get('stock.move') Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') storage, = Location.search([('code', '=', 'STO')]) supplier, = Location.search([('code', '=', 'SUP')]) unit, = Uom.search([('name', '=', "Unit")]) template, = Template.create([{ 'name': "Product", 'type': 'goods', 'default_uom': unit.id, }]) products = Product.create([{'template': template.id}] * 2) product, product2 = products company = create_company() with set_company(company): Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': supplier.id, 'to_location': storage.id, 'unit_price': Decimal('5'), 'currency': company.currency.id, 'company': company.id, }]) with check_access(): with self.assertRaises(ProductStockWarning): Product.write(products, {'active': False}) with self.assertRaises(ProductStockWarning): Template.write([template], {'active': False}) Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': storage.id, 'to_location': supplier.id, 'unit_price': Decimal('5'), 'currency': company.currency.id, 'company': company.id, }]) with check_access(): Template.write([template], {'active': False}) Product.write(products, {'active': False}) @with_transaction() def test_location_inactive_with_consumable_product(self): "Test inactive location with consumable products" pool = Pool() Location = pool.get('stock.location') Move = pool.get('stock.move') Template = pool.get('product.template') Product = pool.get('product.product') Uom = pool.get('product.uom') warehouse, = Location.search([('type', '=', 'warehouse')]) storage, = Location.search([('code', '=', 'STO')]) lost_found, = Location.search([('type', '=', 'lost_found')]) unit, = Uom.search([('name', '=', "Unit")]) template, = Template.create([{ 'name': "Product", 'type': 'goods', 'default_uom': unit.id, 'consumable': True, }]) product, = Product.create([{'template': template.id}]) company = create_company() with set_company(company): today = datetime.date.today() moves = Move.create([{ 'product': product.id, 'unit': unit.id, 'quantity': 1, 'from_location': lost_found.id, 'to_location': storage.id, 'planned_date': today, 'effective_date': today, 'company': company.id, }]) Move.do(moves) warehouse.active = False warehouse.save() del ModuleTestCase