mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	Merge branch 'master' of https://github.com/SchrodingersGat/InvenTree
# Conflicts: # Makefile
This commit is contained in:
		| @@ -3,3 +3,7 @@ source = ./InvenTree | ||||
| omit = | ||||
|     # Do not run coverage on migration files | ||||
|     */migrations/* | ||||
|     InvenTree/manage.py | ||||
|     Inventree/InvenTree/middleware.py | ||||
|     Inventree/InvenTree/utils.py | ||||
|     Inventree/InvenTree/wsgi.py | ||||
| @@ -10,7 +10,6 @@ addons: | ||||
|  | ||||
| before_install: | ||||
|     - make setup | ||||
|     - make setup_ci | ||||
|  | ||||
| script: | ||||
|     - make coverage | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| from django.test import TestCase | ||||
|  | ||||
| from .models import Company | ||||
| import os | ||||
|  | ||||
| from .models import Company, Contact | ||||
| from .models import rename_company_image | ||||
|  | ||||
|  | ||||
| class CompanySimpleTest(TestCase): | ||||
| @@ -16,4 +19,43 @@ class CompanySimpleTest(TestCase): | ||||
|     def test_company_model(self): | ||||
|         c = Company.objects.get(pk=1) | ||||
|         self.assertEqual(c.name, 'ABC Co.') | ||||
|         self.assertEqual(str(c), 'ABC Co. - Seller of ABC products') | ||||
|  | ||||
|     def test_company_url(self): | ||||
|         c = Company.objects.get(pk=1) | ||||
|         self.assertEqual(c.get_absolute_url(), '/company/1/') | ||||
|  | ||||
|     def test_image_renamer(self): | ||||
|         c = Company.objects.get(pk=1) | ||||
|         rn = rename_company_image(c, 'test.png') | ||||
|         self.assertEqual(rn, 'company_images' + os.path.sep + 'company_1_img.png') | ||||
|  | ||||
|         rn = rename_company_image(c, 'test2') | ||||
|         self.assertEqual(rn, 'company_images' + os.path.sep + 'company_1_img') | ||||
|  | ||||
|     def test_part_count(self): | ||||
|         # Initially the company should have no associated parts | ||||
|         c = Company.objects.get(pk=1) | ||||
|         self.assertEqual(c.has_parts, False) | ||||
|  | ||||
|         # TODO - Add some supplier parts here | ||||
|  | ||||
|  | ||||
| class ContactSimpleTest(TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         # Create a simple company | ||||
|         c = Company.objects.create(name='Test Corp.', description='We make stuff good') | ||||
|  | ||||
|         # Add some contacts | ||||
|         Contact.objects.create(name='Joe Smith', company=c) | ||||
|         Contact.objects.create(name='Fred Smith', company=c) | ||||
|         Contact.objects.create(name='Sally Smith', company=c) | ||||
|  | ||||
|     def test_exists(self): | ||||
|         self.assertEqual(Contact.objects.count(), 3) | ||||
|  | ||||
|     def test_delete(self): | ||||
|         # Remove the parent company | ||||
|         Company.objects.get(pk=1).delete() | ||||
|         self.assertEqual(Contact.objects.count(), 0) | ||||
|   | ||||
| @@ -42,7 +42,7 @@ class CategoryTest(TestCase): | ||||
|         childs = self.p1.getUniqueChildren() | ||||
|  | ||||
|         self.assertIn(self.p2.id, childs) | ||||
|         self.assertIn(self.p2.id, childs) | ||||
|         self.assertIn(self.p3.id, childs) | ||||
|  | ||||
|     def test_unique_parents(self): | ||||
|         parents = self.p2.getUniqueParents() | ||||
| @@ -64,3 +64,13 @@ class CategoryTest(TestCase): | ||||
|         self.assertEqual(self.p1.partcount, 3) | ||||
|         self.assertEqual(self.p2.partcount, 3) | ||||
|         self.assertEqual(self.p3.partcount, 1) | ||||
|  | ||||
|     def test_delete(self): | ||||
|         self.assertEqual(Part.objects.filter(category=self.p1).count(), 0) | ||||
|  | ||||
|         # Delete p2 (it has 2 direct parts and one child category) | ||||
|         self.p2.delete() | ||||
|  | ||||
|         self.assertEqual(Part.objects.filter(category=self.p1).count(), 2) | ||||
|  | ||||
|         self.assertEqual(PartCategory.objects.get(pk=self.p3.id).parent, self.p1) | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| from django.test import TestCase | ||||
|  | ||||
| import os | ||||
|  | ||||
| from .models import Part, PartCategory | ||||
| from .models import rename_part_image | ||||
|  | ||||
|  | ||||
| class SimplePartTest(TestCase): | ||||
| @@ -21,3 +24,15 @@ class SimplePartTest(TestCase): | ||||
|     def test_category(self): | ||||
|         self.assertEqual(self.px.category_path, '') | ||||
|         self.assertEqual(self.pz.category_path, 'TLC') | ||||
|  | ||||
|     def test_rename_img(self): | ||||
|         img = rename_part_image(self.px, 'hello.png') | ||||
|         self.assertEqual(img, os.path.join('part_images', 'part_1_img.png')) | ||||
|  | ||||
|         img = rename_part_image(self.pz, 'test') | ||||
|         self.assertEqual(img, os.path.join('part_images', 'part_3_img')) | ||||
|  | ||||
|     def test_stock(self): | ||||
|         # Stock should initially be zero | ||||
|         self.assertEqual(self.px.total_stock, 0) | ||||
|         self.assertEqual(self.py.available_stock, 0) | ||||
|   | ||||
| @@ -28,11 +28,6 @@ class StockLocation(InvenTreeTree): | ||||
|     def get_absolute_url(self): | ||||
|         return reverse('stock-location-detail', kwargs={'pk': self.id}) | ||||
|  | ||||
|     @property | ||||
|     def stock_items(self): | ||||
|         return self.stockitem_set.all() | ||||
|  | ||||
|     @property | ||||
|     def has_items(self): | ||||
|         return self.stock_items.count() > 0 | ||||
|  | ||||
| @@ -128,7 +123,7 @@ class StockItem(models.Model): | ||||
|  | ||||
|     # Where the part is stored. If the part has been used to build another stock item, the location may not make sense | ||||
|     location = models.ForeignKey(StockLocation, on_delete=models.DO_NOTHING, | ||||
|                                  related_name='items', blank=True, null=True, | ||||
|                                  related_name='stock_items', blank=True, null=True, | ||||
|                                  help_text='Where is this stock item located?') | ||||
|  | ||||
|     # If this StockItem belongs to another StockItem (e.g. as part of a sub-assembly) | ||||
| @@ -253,7 +248,7 @@ class StockItem(models.Model): | ||||
|         count = int(count) | ||||
|  | ||||
|         if count < 0 or self.infinite: | ||||
|             return | ||||
|             return False | ||||
|  | ||||
|         self.quantity = count | ||||
|         self.stocktake_date = datetime.now().date() | ||||
| @@ -265,6 +260,8 @@ class StockItem(models.Model): | ||||
|                                   notes=notes, | ||||
|                                   system=True) | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     @transaction.atomic | ||||
|     def add_stock(self, quantity, user, notes=''): | ||||
|         """ Add items to stock | ||||
| @@ -276,7 +273,7 @@ class StockItem(models.Model): | ||||
|  | ||||
|         # Ignore amounts that do not make sense | ||||
|         if quantity <= 0 or self.infinite: | ||||
|             return | ||||
|             return False | ||||
|  | ||||
|         self.quantity += quantity | ||||
|  | ||||
| @@ -287,18 +284,20 @@ class StockItem(models.Model): | ||||
|                                   notes=notes, | ||||
|                                   system=True) | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     @transaction.atomic | ||||
|     def take_stock(self, quantity, user, notes=''): | ||||
|         """ Remove items from stock | ||||
|         """ | ||||
|  | ||||
|         if self.quantity == 0: | ||||
|             return | ||||
|             return False | ||||
|  | ||||
|         quantity = int(quantity) | ||||
|  | ||||
|         if quantity <= 0 or self.infinite: | ||||
|             return | ||||
|             return False | ||||
|  | ||||
|         self.quantity -= quantity | ||||
|  | ||||
| @@ -312,6 +311,8 @@ class StockItem(models.Model): | ||||
|                                   notes=notes, | ||||
|                                   system=True) | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     def __str__(self): | ||||
|         s = '{n} x {part}'.format( | ||||
|             n=self.quantity, | ||||
| @@ -322,10 +323,6 @@ class StockItem(models.Model): | ||||
|  | ||||
|         return s | ||||
|  | ||||
|     @property | ||||
|     def is_trackable(self): | ||||
|         return self.part.trackable | ||||
|  | ||||
|  | ||||
| class StockItemTracking(models.Model): | ||||
|     """ Stock tracking entry | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| from django.test import TestCase | ||||
|  | ||||
|  | ||||
| class StockItemTest(TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         pass | ||||
| @@ -1,7 +0,0 @@ | ||||
| from django.test import TestCase | ||||
|  | ||||
|  | ||||
| class StockLocationTest(TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         pass | ||||
| @@ -1,7 +0,0 @@ | ||||
| from django.test import TestCase | ||||
|  | ||||
|  | ||||
| class StockTrackingTest(TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         pass | ||||
							
								
								
									
										162
									
								
								InvenTree/stock/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								InvenTree/stock/tests.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | ||||
| from django.test import TestCase | ||||
|  | ||||
| from .models import StockLocation, StockItem, StockItemTracking | ||||
| from part.models import Part | ||||
|  | ||||
|  | ||||
| class StockTest(TestCase): | ||||
|     """ | ||||
|     Tests to ensure that the stock location tree functions correcly | ||||
|     """ | ||||
|  | ||||
|     def setUp(self): | ||||
|         # Initialize some categories | ||||
|         self.loc1 = StockLocation.objects.create(name='L0', | ||||
|                                                  description='Top level category', | ||||
|                                                  parent=None) | ||||
|  | ||||
|         self.loc2 = StockLocation.objects.create(name='L1.1', | ||||
|                                                  description='Second level 1/2', | ||||
|                                                  parent=self.loc1) | ||||
|  | ||||
|         self.loc3 = StockLocation.objects.create(name='L1.2', | ||||
|                                                  description='Second level 2/2', | ||||
|                                                  parent=self.loc1) | ||||
|  | ||||
|         self.loc4 = StockLocation.objects.create(name='L2.1', | ||||
|                                                  description='Third level 1/2', | ||||
|                                                  parent=self.loc2) | ||||
|  | ||||
|         self.loc5 = StockLocation.objects.create(name='L2.2', | ||||
|                                                  description='Third level 2/2', | ||||
|                                                  parent=self.loc3) | ||||
|  | ||||
|         # Add some items to loc4 (all copies of a single part) | ||||
|         p = Part.objects.create(name='ACME Part', description='This is a part!') | ||||
|  | ||||
|         StockItem.objects.create(part=p, location=self.loc4, quantity=1000) | ||||
|         StockItem.objects.create(part=p, location=self.loc4, quantity=250) | ||||
|         StockItem.objects.create(part=p, location=self.loc4, quantity=12) | ||||
|  | ||||
|     def test_simple(self): | ||||
|         it = StockItem.objects.get(pk=2) | ||||
|         self.assertEqual(it.get_absolute_url(), '/stock/item/2/') | ||||
|         self.assertEqual(self.loc4.get_absolute_url(), '/stock/location/4/') | ||||
|  | ||||
|     def test_strings(self): | ||||
|         it = StockItem.objects.get(pk=2) | ||||
|         self.assertEqual(str(it), '250 x ACME Part @ L2.1') | ||||
|  | ||||
|     def test_parent(self): | ||||
|         self.assertEqual(StockLocation.objects.count(), 5) | ||||
|         self.assertEqual(self.loc1.parent, None) | ||||
|         self.assertEqual(self.loc2.parent, self.loc1) | ||||
|         self.assertEqual(self.loc5.parent, self.loc3) | ||||
|  | ||||
|     def test_children(self): | ||||
|         self.assertTrue(self.loc1.has_children) | ||||
|         self.assertFalse(self.loc5.has_children) | ||||
|  | ||||
|         childs = self.loc1.getUniqueChildren() | ||||
|  | ||||
|         self.assertIn(self.loc2.id, childs) | ||||
|         self.assertIn(self.loc4.id, childs) | ||||
|  | ||||
|     def test_paths(self): | ||||
|         self.assertEqual(self.loc5.pathstring, 'L0/L1.2/L2.2') | ||||
|  | ||||
|     def test_items(self): | ||||
|         # Location 5 should have no items | ||||
|         self.assertFalse(self.loc5.has_items()) | ||||
|         self.assertFalse(self.loc3.has_items()) | ||||
|  | ||||
|         # Location 4 should have three stock items | ||||
|         self.assertEqual(self.loc4.stock_items.count(), 3) | ||||
|  | ||||
|     def test_stock_count(self): | ||||
|         part = Part.objects.get(pk=1) | ||||
|  | ||||
|         # There should be 1262 items in stock | ||||
|         self.assertEqual(part.total_stock, 1262) | ||||
|  | ||||
|     def test_delete_location(self): | ||||
|         # Delete location - parts should move to parent location | ||||
|         self.loc4.delete() | ||||
|  | ||||
|         # There should still be 3 stock items | ||||
|         self.assertEqual(StockItem.objects.count(), 3) | ||||
|  | ||||
|         # Parent location should have moved up to loc2 | ||||
|         for it in StockItem.objects.all(): | ||||
|             self.assertEqual(it.location, self.loc2) | ||||
|  | ||||
|     def test_move(self): | ||||
|         # Move the first stock item to loc5 | ||||
|         it = StockItem.objects.get(pk=1) | ||||
|         self.assertNotEqual(it.location, self.loc5) | ||||
|         self.assertTrue(it.move(self.loc5, 'Moved to another place', None)) | ||||
|         self.assertEqual(it.location, self.loc5) | ||||
|  | ||||
|         # Check that a tracking item was added | ||||
|         track = StockItemTracking.objects.filter(item=it).latest('id') | ||||
|  | ||||
|         self.assertEqual(track.item, it) | ||||
|         self.assertIn('Moved to', track.title) | ||||
|         self.assertEqual(track.notes, 'Moved to another place') | ||||
|  | ||||
|     def test_self_move(self): | ||||
|         # Try to move an item to its current location (should fail) | ||||
|         it = StockItem.objects.get(pk=1) | ||||
|  | ||||
|         n = it.tracking_info.count() | ||||
|         self.assertFalse(it.move(it.location, 'Moved to same place', None)) | ||||
|  | ||||
|         # Ensure tracking info was not added | ||||
|         self.assertEqual(it.tracking_info.count(), n) | ||||
|  | ||||
|     def test_stocktake(self): | ||||
|         # Perform stocktake | ||||
|         it = StockItem.objects.get(pk=2) | ||||
|         self.assertEqual(it.quantity, 250) | ||||
|         it.stocktake(255, None, notes='Counted items!') | ||||
|  | ||||
|         self.assertEqual(it.quantity, 255) | ||||
|  | ||||
|         # Check that a tracking item was added | ||||
|         track = StockItemTracking.objects.filter(item=it).latest('id') | ||||
|  | ||||
|         self.assertIn('Stocktake', track.title) | ||||
|         self.assertIn('Counted items', track.notes) | ||||
|  | ||||
|         n = it.tracking_info.count() | ||||
|         self.assertFalse(it.stocktake(-1, None, 'test negative stocktake')) | ||||
|  | ||||
|         # Ensure tracking info was not added | ||||
|         self.assertEqual(it.tracking_info.count(), n) | ||||
|  | ||||
|     def test_add_stock(self): | ||||
|         it = StockItem.objects.get(pk=2) | ||||
|         n = it.quantity | ||||
|         it.add_stock(45, None, notes='Added some items') | ||||
|  | ||||
|         self.assertEqual(it.quantity, n + 45) | ||||
|  | ||||
|         # Check that a tracking item was added | ||||
|         track = StockItemTracking.objects.filter(item=it).latest('id') | ||||
|  | ||||
|         self.assertIn('Added', track.title) | ||||
|         self.assertIn('Added some items', track.notes) | ||||
|  | ||||
|     def test_take_stock(self): | ||||
|         it = StockItem.objects.get(pk=2) | ||||
|         n = it.quantity | ||||
|         it.take_stock(15, None, notes='Removed some items') | ||||
|  | ||||
|         self.assertEqual(it.quantity, n - 15) | ||||
|  | ||||
|         # Check that a tracking item was added | ||||
|         track = StockItemTracking.objects.filter(item=it).latest('id') | ||||
|  | ||||
|         self.assertIn('Removed', track.title) | ||||
|         self.assertIn('Removed some items', track.notes) | ||||
|         self.assertTrue(it.has_tracking_info) | ||||
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							| @@ -12,7 +12,7 @@ style: | ||||
|  | ||||
| test: | ||||
| 	python InvenTree/manage.py check | ||||
| 	python manage.py test build company part stock | ||||
| 	python InvenTree/manage.py test build company part stock | ||||
|  | ||||
| migrate: | ||||
| 	python InvenTree/manage.py makemigrations company | ||||
| @@ -23,14 +23,11 @@ migrate: | ||||
| 	python InvenTree/manage.py check | ||||
|  | ||||
| install: | ||||
| 	pip install -U -r requirements/base.txt | ||||
| 	pip install -U -r requirements.txt | ||||
| 	python InvenTree/key.py | ||||
|  | ||||
| setup: install migrate | ||||
|  | ||||
| setup_ci: | ||||
| 	pip install -U -r requirements/build.txt | ||||
|  | ||||
| coverage: | ||||
| 	python InvenTree/manage.py check | ||||
| 	coverage run InvenTree/manage.py test build company part stock | ||||
|   | ||||
| @@ -10,3 +10,6 @@ django-crispy-forms>=1.7.2 | ||||
| django-import-export>=1.0.0 | ||||
| django-cleanup>=2.1.0 | ||||
| django-qr-code==1.0.0 | ||||
| flake8==3.3.0 | ||||
| coverage>=4.5.3 | ||||
| python-coveralls==2.9.1 | ||||
| @@ -1,4 +0,0 @@ | ||||
| -r base.txt | ||||
| flake8==3.3.0 | ||||
| coverage>=4.5.3 | ||||
| python-coveralls==2.9.1 | ||||
| @@ -1,4 +0,0 @@ | ||||
| -r build.txt | ||||
| django-extensions==1.7.8 | ||||
| graphviz==0.6 | ||||
| ipython==5.3.0 | ||||
		Reference in New Issue
	
	Block a user