# Conflicts:
#	Makefile
This commit is contained in:
Oliver Walters 2019-04-26 21:24:03 +10:00
commit edad4f9cf1
14 changed files with 252 additions and 52 deletions

View File

@ -2,4 +2,8 @@
source = ./InvenTree 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

View File

@ -10,7 +10,6 @@ addons:
before_install: before_install:
- make setup - make setup
- make setup_ci
script: script:
- make coverage - make coverage

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -1,7 +0,0 @@
from django.test import TestCase
class StockItemTest(TestCase):
def setUp(self):
pass

View File

@ -1,7 +0,0 @@
from django.test import TestCase
class StockLocationTest(TestCase):
def setUp(self):
pass

View File

@ -1,7 +0,0 @@
from django.test import TestCase
class StockTrackingTest(TestCase):
def setUp(self):
pass

162
InvenTree/stock/tests.py Normal file
View 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)

View File

@ -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

View File

@ -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

View File

@ -1,4 +0,0 @@
-r base.txt
flake8==3.3.0
coverage>=4.5.3
python-coveralls==2.9.1

View File

@ -1,4 +0,0 @@
-r build.txt
django-extensions==1.7.8
graphviz==0.6
ipython==5.3.0