Merge pull request #2034 from SchrodingersGat/build-complete-scheduling

Build completion scheduling
This commit is contained in:
Oliver 2021-09-07 22:51:52 +10:00 committed by GitHub
commit 50ae331afd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 96 additions and 4 deletions

View File

@ -63,6 +63,13 @@ class InvenTreeConfig(AppConfig):
schedule_type=Schedule.DAILY,
)
# Delete "old" stock items
InvenTree.tasks.schedule_task(
'stock.tasks.delete_old_stock_items',
schedule_type=Schedule.MINUTES,
minutes=30,
)
def update_exchange_rates(self):
"""
Update exchange rates each time the server is started, *if*:

View File

@ -8,8 +8,9 @@ from django.db.utils import IntegrityError
from InvenTree import status_codes as status
from build.models import Build, BuildItem, get_next_build_number
from stock.models import StockItem
from part.models import Part, BomItem
from stock.models import StockItem
from stock.tasks import delete_old_stock_items
class BuildTest(TestCase):
@ -352,6 +353,11 @@ class BuildTest(TestCase):
# the original BuildItem objects should have been deleted!
self.assertEqual(BuildItem.objects.count(), 0)
self.assertEqual(StockItem.objects.count(), 8)
# Clean up old stock items
delete_old_stock_items()
# New stock items should have been created!
self.assertEqual(StockItem.objects.count(), 7)

View File

@ -653,6 +653,9 @@ class StockList(generics.ListCreateAPIView):
queryset = StockItemSerializer.annotate_queryset(queryset)
# Do not expose StockItem objects which are scheduled for deletion
queryset = queryset.filter(scheduled_for_deletion=False)
return queryset
def filter_queryset(self, queryset):

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-09-07 06:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stock', '0065_auto_20210701_0509'),
]
operations = [
migrations.AddField(
model_name='stockitem',
name='scheduled_for_deletion',
field=models.BooleanField(default=False, help_text='This StockItem will be deleted by the background worker', verbose_name='Scheduled for deletion'),
),
]

View File

@ -209,12 +209,18 @@ class StockItem(MPTTModel):
belongs_to=None,
customer=None,
is_building=False,
status__in=StockStatus.AVAILABLE_CODES
status__in=StockStatus.AVAILABLE_CODES,
scheduled_for_deletion=False,
)
# A query filter which can be used to filter StockItem objects which have expired
EXPIRED_FILTER = IN_STOCK_FILTER & ~Q(expiry_date=None) & Q(expiry_date__lt=datetime.now().date())
def mark_for_deletion(self):
self.scheduled_for_deletion = True
self.save()
def save(self, *args, **kwargs):
"""
Save this StockItem to the database. Performs a number of checks:
@ -588,6 +594,12 @@ class StockItem(MPTTModel):
help_text=_('Select Owner'),
related_name='stock_items')
scheduled_for_deletion = models.BooleanField(
default=False,
verbose_name=_('Scheduled for deletion'),
help_text=_('This StockItem will be deleted by the background worker'),
)
def is_stale(self):
"""
Returns True if this Stock item is "stale".
@ -1294,9 +1306,8 @@ class StockItem(MPTTModel):
self.quantity = quantity
if quantity == 0 and self.delete_on_deplete and self.can_delete():
self.mark_for_deletion()
# TODO - Do not actually "delete" stock at this point - instead give it a "DELETED" flag
self.delete()
return False
else:
self.save()

35
InvenTree/stock/tasks.py Normal file
View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import logging
from django.core.exceptions import AppRegistryNotReady
logger = logging.getLogger('inventree')
def delete_old_stock_items():
"""
This function removes StockItem objects which have been marked for deletion.
Bulk "delete" operations for database entries with foreign-key relationships
can be pretty expensive, and thus can "block" the UI for a period of time.
Thus, instead of immediately deleting multiple StockItems, some UI actions
simply mark each StockItem as "scheduled for deletion".
The background worker then manually deletes these at a later stage
"""
try:
from stock.models import StockItem
except AppRegistryNotReady:
logger.info("Could not delete scheduled StockItems - AppRegistryNotReady")
return
items = StockItem.objects.filter(scheduled_for_deletion=True)
if items.count() > 0:
logger.info(f"Removing {items.count()} StockItem objects scheduled for deletion")
items.delete()

View File

@ -332,6 +332,8 @@ class StockTest(TestCase):
w1 = StockItem.objects.get(pk=100)
w2 = StockItem.objects.get(pk=101)
self.assertFalse(w2.scheduled_for_deletion)
# Take 25 units from w1 (there are only 10 in stock)
w1.take_stock(30, None, notes='Took 30')
@ -342,6 +344,16 @@ class StockTest(TestCase):
# Take 25 units from w2 (will be deleted)
w2.take_stock(30, None, notes='Took 30')
# w2 should now be marked for future deletion
w2 = StockItem.objects.get(pk=101)
self.assertTrue(w2.scheduled_for_deletion)
from stock.tasks import delete_old_stock_items
# Now run the "background task" to delete these stock items
delete_old_stock_items()
# This StockItem should now have been deleted
with self.assertRaises(StockItem.DoesNotExist):
w2 = StockItem.objects.get(pk=101)