From 07cda765f03d3cef81c544ce7c504b0245ab4dfd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 3 Jan 2021 23:56:35 +1100 Subject: [PATCH] Add "expiry_date" field to StockItem model - Also adds "is_expired" function --- .../migrations/0056_stockitem_expiry_date.py | 18 ++++++ InvenTree/stock/models.py | 64 +++++++++++-------- InvenTree/stock/tests.py | 34 ++++++++++ 3 files changed, 89 insertions(+), 27 deletions(-) create mode 100644 InvenTree/stock/migrations/0056_stockitem_expiry_date.py diff --git a/InvenTree/stock/migrations/0056_stockitem_expiry_date.py b/InvenTree/stock/migrations/0056_stockitem_expiry_date.py new file mode 100644 index 0000000000..f558d615a6 --- /dev/null +++ b/InvenTree/stock/migrations/0056_stockitem_expiry_date.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2021-01-03 12:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0055_auto_20201117_1453'), + ] + + operations = [ + migrations.AddField( + model_name='stockitem', + name='expiry_date', + field=models.DateField(blank=True, help_text='Expiry date for stock item. Stock will be considered expired after this date', null=True, verbose_name='Expiry Date'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index d1e46c53a7..24db434d38 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -125,6 +125,7 @@ class StockItem(MPTTModel): serial: Unique serial number for this StockItem link: Optional URL to link to external resource updated: Date that this stock item was last updated (auto) + expiry_date: Expiry date of the StockItem (optional) stocktake_date: Date of last stocktake for this item stocktake_user: User that performed the most recent stocktake review_needed: Flag if StockItem needs review @@ -428,11 +429,19 @@ class StockItem(MPTTModel): related_name='stock_items', null=True, blank=True) - # last time the stock was checked / counted + expiry_date = models.DateField( + blank=True, null=True, + verbose_name=_('Expiry Date'), + help_text=_('Expiry date for stock item. Stock will be considered expired after this date'), + ) + stocktake_date = models.DateField(blank=True, null=True) - stocktake_user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, - related_name='stocktake_stock') + stocktake_user = models.ForeignKey( + User, on_delete=models.SET_NULL, + blank=True, null=True, + related_name='stocktake_stock' + ) review_needed = models.BooleanField(default=False) @@ -459,6 +468,27 @@ class StockItem(MPTTModel): help_text=_('Single unit purchase price at time of purchase'), ) + def is_expired(self): + """ + Returns true if this StockItem is "expired" + + To be "expired", the following conditions must be met: + + - Expiry date is not None + - Expiry date is "in the past" + - The StockItem is otherwise "in stock" + """ + + if self.expiry_date is None: + return False + + if not self.in_stock: + return False + + today = datetime.now().date() + + return self.expiry_date < today + def clearAllocations(self): """ Clear all order allocations for this StockItem: @@ -721,36 +751,16 @@ class StockItem(MPTTModel): @property def in_stock(self): """ - Returns True if this item is in stock + Returns True if this item is in stock. See also: IN_STOCK_FILTER """ - # Quantity must be above zero (unless infinite) - if self.quantity <= 0 and not self.infinite: - return False + query = StockItem.objects.filter(pk=self.pk) - # Not 'in stock' if it has been installed inside another StockItem - if self.belongs_to is not None: - return False - - # Not 'in stock' if it has been sent to a customer - if self.sales_order is not None: - return False + query = query.filter(StockItem.IN_STOCK_FILTER) - # Not 'in stock' if it has been assigned to a customer - if self.customer is not None: - return False - - # Not 'in stock' if it is building - if self.is_building: - return False - - # Not 'in stock' if the status code makes it unavailable - if self.status in StockStatus.UNAVAILABLE_CODES: - return False - - return True + return query.exists() @property def tracking_info_count(self): diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 3d309c0360..274a1328fe 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -49,6 +49,40 @@ class StockTest(TestCase): Part.objects.rebuild() StockItem.objects.rebuild() + def test_expiry(self): + """ + Test expiry date functionality for StockItem model. + """ + + today = datetime.datetime.now().date() + + item = StockItem.objects.create( + location=self.office, + part=Part.objects.get(pk=1), + quantity=10, + ) + + # Without an expiry_date set, item should not be "expired" + self.assertFalse(item.is_expired()) + + # Set the expiry date to today + item.expiry_date = today + item.save() + + self.assertFalse(item.is_expired()) + + # Set the expiry date in the future + item.expiry_date = today + datetime.timedelta(days=5) + item.save() + + self.assertFalse(item.is_expired()) + + # Set the expiry date in the past + item.expiry_date = today - datetime.timedelta(days=5) + item.save() + + self.assertTrue(item.is_expired()) + def test_is_building(self): """ Test that the is_building flag does not count towards stock.