mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'master' of https://github.com/SchrodingersGat/InvenTree
# Conflicts: # Makefile
This commit is contained in:
commit
edad4f9cf1
@ -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
|
|
Loading…
Reference in New Issue
Block a user