diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index 2401e9d936..bd8f379edc 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -46,6 +46,8 @@ class BuildList(generics.ListCreateAPIView): queryset = super().get_queryset().prefetch_related('part') + queryset = BuildSerializer.annotate_queryset(queryset) + return queryset def filter_queryset(self, queryset): diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py index ba7787798d..9c72113f3b 100644 --- a/InvenTree/build/forms.py +++ b/InvenTree/build/forms.py @@ -40,6 +40,7 @@ class EditBuildForm(HelperForm): 'part', 'quantity', 'batch', + 'target_date', 'take_from', 'destination', 'parent', diff --git a/InvenTree/build/migrations/0025_build_target_date.py b/InvenTree/build/migrations/0025_build_target_date.py new file mode 100644 index 0000000000..e834d74d0f --- /dev/null +++ b/InvenTree/build/migrations/0025_build_target_date.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-12-15 12:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('build', '0024_auto_20201201_1023'), + ] + + operations = [ + migrations.AddField( + model_name='build', + name='target_date', + field=models.DateField(blank=True, help_text='Target date for build completion. Build will be overdue after this date.', null=True, verbose_name='Target completion date'), + ), + ] diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 8616e11b2a..104b6e55c8 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -47,7 +47,8 @@ class Build(MPTTModel): status: Build status code batch: Batch code transferred to build parts (optional) creation_date: Date the build was created (auto) - completion_date: Date the build was completed + target_date: Date the build will be overdue + completion_date: Date the build was completed (or, if incomplete, the expected date of completion) link: External URL for extra information notes: Text notes """ @@ -164,6 +165,12 @@ class Build(MPTTModel): creation_date = models.DateField(auto_now_add=True, editable=False) + target_date = models.DateField( + null=True, blank=True, + verbose_name=_('Target completion date'), + help_text=_('Target date for build completion. Build will be overdue after this date.') + ) + completion_date = models.DateField(null=True, blank=True) completed_by = models.ForeignKey( @@ -183,6 +190,22 @@ class Build(MPTTModel): blank=True, help_text=_('Extra build notes') ) + def is_overdue(self): + """ + Returns true if this build is "overdue": + + - Not completed + - Target date is "in the past" + """ + + # Cannot be deemed overdue if target_date is not set + if self.target_date is None: + return False + + today = datetime.now().date() + + return self.active and self.target_date < today + @property def active(self): """ diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 4423619b8a..b1ef6405c3 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -5,12 +5,21 @@ JSON serializers for Build API # -*- coding: utf-8 -*- from __future__ import unicode_literals +import datetime + +from django.db.models import Q +from django.db.models import Case, When, Value +from django.db.models import BooleanField + from rest_framework import serializers + from InvenTree.serializers import InvenTreeModelSerializer +from InvenTree.status_codes import BuildStatus + from stock.serializers import StockItemSerializerBrief +from part.serializers import PartBriefSerializer from .models import Build, BuildItem -from part.serializers import PartBriefSerializer class BuildSerializer(InvenTreeModelSerializer): @@ -23,6 +32,38 @@ class BuildSerializer(InvenTreeModelSerializer): quantity = serializers.FloatField() + overdue = serializers.BooleanField() + + @staticmethod + def annotate_queryset(queryset): + """ + Add custom annotations to the BuildSerializer queryset, + performing database queries as efficiently as possible. + + The following annoted fields are added: + + - overdue: True if the build is outstanding *and* the completion date has past + + """ + + # Annotate a boolean 'overdue' flag + + # Construct a filter for finding overdue builds + today = datetime.datetime.now().date() + overdue = Q(status__in=BuildStatus.ACTIVE_CODES) & ~Q(target_date=None) & Q(target_date__lte=today) + + queryset = queryset.annotate( + overdue=Case( + When( + overdue, then=Value(True, output_field=BooleanField()), + ), + default=Value(False, output_field=BooleanField()) + ) + ) + + return queryset + + def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) @@ -42,11 +83,13 @@ class BuildSerializer(InvenTreeModelSerializer): 'completion_date', 'part', 'part_detail', + 'overdue', 'reference', 'sales_order', 'quantity', 'status', 'status_text', + 'target_date', 'notes', 'link', ] diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index 68a842755d..a9a2288c4e 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -95,33 +95,26 @@ {% trans "Created" %} {{ build.creation_date }} - - -
- - - - - - - - {% if build.completion_date %} - - - - - + + + + {% if build.target_date %} + + {% else %} + {% endif %} - + + + + + {% if build.completion_date %} + + {% else %} + + {% endif %} +
{% trans "BOM Price" %} - {% if bom_price %} - {{ bom_price }} - {% if build.part.has_complete_bom_pricing == False %} -
{% trans "BOM pricing is incomplete" %} - {% endif %} - {% else %} - {% trans "No pricing information" %} - {% endif %} -
{% trans "Completed" %}{{ build.completion_date }}{% if build.completed_by %}{{ build.completed_by }}{% endif %}
{% trans "Target Date" %} + {{ build.target_date }}{% if build.is_overdue %} {% endif %} + {% trans "No target date set" %}
{% trans "Completed" %}{{ build.completion_date }}{% if build.completed_by %}{{ build.completed_by }}{% endif %}{% trans "Build not complete" %}