mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 05:05: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 = | omit = | ||||||
|     # Do not run coverage on migration files |     # Do not run coverage on migration files | ||||||
|     */migrations/* |     */migrations/* | ||||||
|  |     InvenTree/manage.py | ||||||
|  |     Inventree/InvenTree/middleware.py | ||||||
|  |     Inventree/InvenTree/utils.py | ||||||
|  |     Inventree/InvenTree/wsgi.py | ||||||
| @@ -10,7 +10,6 @@ addons: | |||||||
|  |  | ||||||
| before_install: | before_install: | ||||||
|     - make setup |     - make setup | ||||||
|     - make setup_ci |  | ||||||
|  |  | ||||||
| script: | script: | ||||||
|     - make coverage |     - make coverage | ||||||
|   | |||||||
| @@ -1,6 +1,9 @@ | |||||||
| from django.test import TestCase | 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): | class CompanySimpleTest(TestCase): | ||||||
| @@ -16,4 +19,43 @@ class CompanySimpleTest(TestCase): | |||||||
|     def test_company_model(self): |     def test_company_model(self): | ||||||
|         c = Company.objects.get(pk=1) |         c = Company.objects.get(pk=1) | ||||||
|         self.assertEqual(c.name, 'ABC Co.') |         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/') |         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() |         childs = self.p1.getUniqueChildren() | ||||||
|  |  | ||||||
|         self.assertIn(self.p2.id, childs) |         self.assertIn(self.p2.id, childs) | ||||||
|         self.assertIn(self.p2.id, childs) |         self.assertIn(self.p3.id, childs) | ||||||
|  |  | ||||||
|     def test_unique_parents(self): |     def test_unique_parents(self): | ||||||
|         parents = self.p2.getUniqueParents() |         parents = self.p2.getUniqueParents() | ||||||
| @@ -64,3 +64,13 @@ class CategoryTest(TestCase): | |||||||
|         self.assertEqual(self.p1.partcount, 3) |         self.assertEqual(self.p1.partcount, 3) | ||||||
|         self.assertEqual(self.p2.partcount, 3) |         self.assertEqual(self.p2.partcount, 3) | ||||||
|         self.assertEqual(self.p3.partcount, 1) |         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 | from django.test import TestCase | ||||||
|  |  | ||||||
|  | import os | ||||||
|  |  | ||||||
| from .models import Part, PartCategory | from .models import Part, PartCategory | ||||||
|  | from .models import rename_part_image | ||||||
|  |  | ||||||
|  |  | ||||||
| class SimplePartTest(TestCase): | class SimplePartTest(TestCase): | ||||||
| @@ -21,3 +24,15 @@ class SimplePartTest(TestCase): | |||||||
|     def test_category(self): |     def test_category(self): | ||||||
|         self.assertEqual(self.px.category_path, '') |         self.assertEqual(self.px.category_path, '') | ||||||
|         self.assertEqual(self.pz.category_path, 'TLC') |         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): |     def get_absolute_url(self): | ||||||
|         return reverse('stock-location-detail', kwargs={'pk': self.id}) |         return reverse('stock-location-detail', kwargs={'pk': self.id}) | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def stock_items(self): |  | ||||||
|         return self.stockitem_set.all() |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def has_items(self): |     def has_items(self): | ||||||
|         return self.stock_items.count() > 0 |         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 |     # 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, |     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?') |                                  help_text='Where is this stock item located?') | ||||||
|  |  | ||||||
|     # If this StockItem belongs to another StockItem (e.g. as part of a sub-assembly) |     # 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) |         count = int(count) | ||||||
|  |  | ||||||
|         if count < 0 or self.infinite: |         if count < 0 or self.infinite: | ||||||
|             return |             return False | ||||||
|  |  | ||||||
|         self.quantity = count |         self.quantity = count | ||||||
|         self.stocktake_date = datetime.now().date() |         self.stocktake_date = datetime.now().date() | ||||||
| @@ -265,6 +260,8 @@ class StockItem(models.Model): | |||||||
|                                   notes=notes, |                                   notes=notes, | ||||||
|                                   system=True) |                                   system=True) | ||||||
|  |  | ||||||
|  |         return True | ||||||
|  |  | ||||||
|     @transaction.atomic |     @transaction.atomic | ||||||
|     def add_stock(self, quantity, user, notes=''): |     def add_stock(self, quantity, user, notes=''): | ||||||
|         """ Add items to stock |         """ Add items to stock | ||||||
| @@ -276,7 +273,7 @@ class StockItem(models.Model): | |||||||
|  |  | ||||||
|         # Ignore amounts that do not make sense |         # Ignore amounts that do not make sense | ||||||
|         if quantity <= 0 or self.infinite: |         if quantity <= 0 or self.infinite: | ||||||
|             return |             return False | ||||||
|  |  | ||||||
|         self.quantity += quantity |         self.quantity += quantity | ||||||
|  |  | ||||||
| @@ -287,18 +284,20 @@ class StockItem(models.Model): | |||||||
|                                   notes=notes, |                                   notes=notes, | ||||||
|                                   system=True) |                                   system=True) | ||||||
|  |  | ||||||
|  |         return True | ||||||
|  |  | ||||||
|     @transaction.atomic |     @transaction.atomic | ||||||
|     def take_stock(self, quantity, user, notes=''): |     def take_stock(self, quantity, user, notes=''): | ||||||
|         """ Remove items from stock |         """ Remove items from stock | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         if self.quantity == 0: |         if self.quantity == 0: | ||||||
|             return |             return False | ||||||
|  |  | ||||||
|         quantity = int(quantity) |         quantity = int(quantity) | ||||||
|  |  | ||||||
|         if quantity <= 0 or self.infinite: |         if quantity <= 0 or self.infinite: | ||||||
|             return |             return False | ||||||
|  |  | ||||||
|         self.quantity -= quantity |         self.quantity -= quantity | ||||||
|  |  | ||||||
| @@ -312,6 +311,8 @@ class StockItem(models.Model): | |||||||
|                                   notes=notes, |                                   notes=notes, | ||||||
|                                   system=True) |                                   system=True) | ||||||
|  |  | ||||||
|  |         return True | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         s = '{n} x {part}'.format( |         s = '{n} x {part}'.format( | ||||||
|             n=self.quantity, |             n=self.quantity, | ||||||
| @@ -322,10 +323,6 @@ class StockItem(models.Model): | |||||||
|  |  | ||||||
|         return s |         return s | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def is_trackable(self): |  | ||||||
|         return self.part.trackable |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StockItemTracking(models.Model): | class StockItemTracking(models.Model): | ||||||
|     """ Stock tracking entry |     """ 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: | test: | ||||||
| 	python InvenTree/manage.py check | 	python InvenTree/manage.py check | ||||||
| 	python manage.py test build company part stock | 	python InvenTree/manage.py test build company part stock | ||||||
|  |  | ||||||
| migrate: | migrate: | ||||||
| 	python InvenTree/manage.py makemigrations company | 	python InvenTree/manage.py makemigrations company | ||||||
| @@ -23,14 +23,11 @@ migrate: | |||||||
| 	python InvenTree/manage.py check | 	python InvenTree/manage.py check | ||||||
|  |  | ||||||
| install: | install: | ||||||
| 	pip install -U -r requirements/base.txt | 	pip install -U -r requirements.txt | ||||||
| 	python InvenTree/key.py | 	python InvenTree/key.py | ||||||
|  |  | ||||||
| setup: install migrate | setup: install migrate | ||||||
|  |  | ||||||
| setup_ci: |  | ||||||
| 	pip install -U -r requirements/build.txt |  | ||||||
|  |  | ||||||
| coverage: | coverage: | ||||||
| 	python InvenTree/manage.py check | 	python InvenTree/manage.py check | ||||||
| 	coverage run InvenTree/manage.py test build company part stock | 	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-import-export>=1.0.0 | ||||||
| django-cleanup>=2.1.0 | django-cleanup>=2.1.0 | ||||||
| django-qr-code==1.0.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