From 000348f70f64b8b712ab2fb51b5e06d23910342d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 18 Dec 2020 12:19:16 +1100 Subject: [PATCH 1/7] Add 'target_date' field to SalesOrder model --- .../migrations/0040_salesorder_target_date.py | 18 +++++++ InvenTree/order/models.py | 54 ++++++++++++------- 2 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 InvenTree/order/migrations/0040_salesorder_target_date.py diff --git a/InvenTree/order/migrations/0040_salesorder_target_date.py b/InvenTree/order/migrations/0040_salesorder_target_date.py new file mode 100644 index 0000000000..29a90a6ad5 --- /dev/null +++ b/InvenTree/order/migrations/0040_salesorder_target_date.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-12-18 01:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0039_auto_20201112_2203'), + ] + + operations = [ + migrations.AddField( + model_name='salesorder', + name='target_date', + field=models.DateField(blank=True, help_text='Target date for order completion. Order will be overdue after this date.', null=True, verbose_name='Target completion date'), + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index b2eb59c52e..9045ab86d7 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -9,7 +9,7 @@ from datetime import datetime from decimal import Decimal from django.db import models, transaction -from django.db.models import F, Sum +from django.db.models import Q, F, Sum from django.db.models.functions import Coalesce from django.core.validators import MinValueValidator from django.core.exceptions import ValidationError @@ -26,7 +26,7 @@ from stock import models as stock_models from company.models import Company, SupplierPart from InvenTree.fields import RoundingDecimalField -from InvenTree.helpers import decimal2string, increment +from InvenTree.helpers import decimal2string, increment, getSetting from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus from InvenTree.models import InvenTreeAttachment @@ -49,8 +49,6 @@ class Order(models.Model): """ - ORDER_PREFIX = "" - @classmethod def getNextOrderNumber(cls): """ @@ -88,16 +86,6 @@ class Order(models.Model): return new_ref - def __str__(self): - el = [] - - if self.ORDER_PREFIX: - el.append(self.ORDER_PREFIX) - - el.append(self.reference) - - return " ".join(el) - def save(self, *args, **kwargs): if not self.creation_date: self.creation_date = datetime.now().date() @@ -132,11 +120,12 @@ class PurchaseOrder(Order): supplier_reference: Optional field for supplier order reference code received_by: User that received the goods """ - - ORDER_PREFIX = "PO" def __str__(self): - return "PO {ref} - {company}".format(ref=self.reference, company=self.supplier.name) + + prefix = getSetting('PURCHASEORDER_REFERENCE_PREFIX') + + return f"{prefix}{self.reference} - {self.supplier.name}" status = models.PositiveIntegerField(default=PurchaseOrderStatus.PENDING, choices=PurchaseOrderStatus.items(), help_text=_('Purchase order status')) @@ -307,10 +296,16 @@ class SalesOrder(Order): Attributes: customer: Reference to the company receiving the goods in the order customer_reference: Optional field for customer order reference code + target_date: Target date for SalesOrder completion (optional) """ + OVERDUE_FILTER = Q(status__in=SalesOrderStatus.OPEN) & ~Q(target_date=None) & Q(target_date__lte=datetime.now().date()) + def __str__(self): - return "SO {ref} - {company}".format(ref=self.reference, company=self.customer.name) + + prefix = getSetting('SALESORDER_REFERENCE_PREFIX') + + return f"{prefix}{self.reference} - {self.customer.name}" def get_absolute_url(self): return reverse('so-detail', kwargs={'pk': self.id}) @@ -329,6 +324,12 @@ class SalesOrder(Order): customer_reference = models.CharField(max_length=64, blank=True, help_text=_("Customer order reference code")) + target_date = models.DateField( + null=True, blank=True, + verbose_name=_('Target completion date'), + help_text=_('Target date for order completion. Order will be overdue after this date.') + ) + shipment_date = models.DateField(blank=True, null=True) shipped_by = models.ForeignKey( @@ -338,6 +339,23 @@ class SalesOrder(Order): related_name='+' ) + @property + def is_overdue(self): + """ + Returns true if this SalesOrder is "overdue": + + - Not completed + - Target date is "in the past" + """ + + # Order cannot be deemed overdue if target_date is not set + if self.target_date is None: + return False + + today = datetime.now().date() + + return self.is_pending and self.target_date < today + @property def is_pending(self): return self.status == SalesOrderStatus.PENDING From c6134b54ab93224c22aa4f2c4a40ab9bae6d02a0 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 18 Dec 2020 12:26:58 +1100 Subject: [PATCH 2/7] Add "overdue" status to SalesOrder serializer --- InvenTree/order/serializers.py | 28 +++++++++++++++++++++++----- InvenTree/templates/js/order.js | 8 +++++++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index c91ec6e02d..84327e19b8 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -9,6 +9,9 @@ from rest_framework import serializers from sql_util.utils import SubqueryCount +from django.db.models import Case, When, Value +from django.db.models import BooleanField + from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeAttachmentSerializerField @@ -152,12 +155,24 @@ class SalesOrderSerializer(InvenTreeModelSerializer): def annotate_queryset(queryset): """ Add extra information to the queryset + + - Number of line items in the SalesOrder + - Overdue status of the SalesOrder """ queryset = queryset.annotate( line_items=SubqueryCount('lines') ) + queryset = queryset.annotate( + overdue=Case( + When( + SalesOrder.OVERDUE_FILTER, then=Value(True, output_field=BooleanField()), + ), + default=Value(False, output_field=BooleanField()) + ) + ) + return queryset customer_detail = CompanyBriefSerializer(source='customer', many=False, read_only=True) @@ -166,6 +181,8 @@ class SalesOrderSerializer(InvenTreeModelSerializer): status_text = serializers.CharField(source='get_status_display', read_only=True) + overdue = serializers.BooleanField() + class Meta: model = SalesOrder @@ -173,17 +190,18 @@ class SalesOrderSerializer(InvenTreeModelSerializer): 'pk', 'shipment_date', 'creation_date', - 'description', - 'line_items', - 'link', - 'reference', 'customer', 'customer_detail', 'customer_reference', + 'description', + 'line_items', + 'link', + 'notes', + 'overdue', + 'reference', 'status', 'status_text', 'shipment_date', - 'notes', ] read_only_fields = [ diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/order.js index 69d4f584d9..027347e37c 100644 --- a/InvenTree/templates/js/order.js +++ b/InvenTree/templates/js/order.js @@ -235,7 +235,13 @@ function loadSalesOrderTable(table, options) { value = `${prefix}${value}`; } - return renderLink(value, `/order/sales-order/${row.pk}/`); + var html = renderLink(value, `/order/sales-order/${row.pk}/`); + + if (row.overdue) { + html += makeIconBadge('fa-calendar-times icon-red', '{% trans "Order is overdue" %}'); + } + + return html; }, }, { From b21c6f0b994160f83b54ae10f21be0b5ec26ef03 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 18 Dec 2020 12:27:08 +1100 Subject: [PATCH 3/7] Add overdue filter for salesorder table --- InvenTree/templates/js/table_filters.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/InvenTree/templates/js/table_filters.js b/InvenTree/templates/js/table_filters.js index f1f5c12732..d2ea26d8c3 100644 --- a/InvenTree/templates/js/table_filters.js +++ b/InvenTree/templates/js/table_filters.js @@ -217,6 +217,10 @@ function getAvailableTableFilters(tableKey) { type: 'bool', title: '{% trans "Outstanding" %}', }, + overdue: { + type: 'bool', + title: '{% trans "Overdue" %}', + }, }; } From c34196538b644ea8c959d0fb5bec153661e71de6 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 18 Dec 2020 12:40:47 +1100 Subject: [PATCH 4/7] Filter API by overdue status --- InvenTree/order/api.py | 11 +++++++++++ InvenTree/order/forms.py | 8 ++++++++ InvenTree/order/serializers.py | 2 +- .../order/templates/order/sales_order_base.html | 17 ++++++++++++++++- InvenTree/templates/js/order.js | 5 +++++ 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 4a9dbfa2ac..6b8a3c81e0 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -266,6 +266,17 @@ class SOList(generics.ListCreateAPIView): else: queryset = queryset.exclude(status__in=SalesOrderStatus.OPEN) + # Filter by 'overdue' status + overdue = params.get('overdue', None) + + if overdue is not None: + overdue = str2bool(overdue) + + if overdue: + queryset = queryset.filter(SalesOrder.OVERDUE_FILTER) + else: + queryset = queryset.exclude(SalesOrder.OVERDUE_FILTER) + status = params.get('status', None) if status is not None: diff --git a/InvenTree/order/forms.py b/InvenTree/order/forms.py index aa0c897c8a..d797a4e42d 100644 --- a/InvenTree/order/forms.py +++ b/InvenTree/order/forms.py @@ -128,6 +128,13 @@ class EditSalesOrderForm(HelperForm): super().__init__(*args, **kwargs) + # TODO: Improve this using a better date picker + target_date = forms.DateField( + widget=forms.DateInput( + attrs={'type': 'date'}, + ) + ) + class Meta: model = SalesOrder fields = [ @@ -135,6 +142,7 @@ class EditSalesOrderForm(HelperForm): 'customer', 'customer_reference', 'description', + 'target_date', 'link' ] diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 84327e19b8..f0505728f6 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -188,7 +188,6 @@ class SalesOrderSerializer(InvenTreeModelSerializer): fields = [ 'pk', - 'shipment_date', 'creation_date', 'customer', 'customer_detail', @@ -202,6 +201,7 @@ class SalesOrderSerializer(InvenTreeModelSerializer): 'status', 'status_text', 'shipment_date', + 'target_date', ] read_only_fields = [ diff --git a/InvenTree/order/templates/order/sales_order_base.html b/InvenTree/order/templates/order/sales_order_base.html index 59ff0eeda8..378020850b 100644 --- a/InvenTree/order/templates/order/sales_order_base.html +++ b/InvenTree/order/templates/order/sales_order_base.html @@ -37,6 +37,9 @@ src="{% static 'img/blank_image.png' %}"

{% sales_order_status_label order.status large=True %} + {% if order.is_overdue %} + {% trans "Overdue" %} + {% endif %}


{{ order.description }}

@@ -74,7 +77,12 @@ src="{% static 'img/blank_image.png' %}" {% trans "Order Status" %} - {% sales_order_status_label order.status %} + + {% sales_order_status_label order.status %} + {% if order.is_overdue %} + {% trans "Overdue" %} + {% endif %} + @@ -100,6 +108,13 @@ src="{% static 'img/blank_image.png' %}" {% trans "Created" %} {{ order.creation_date }}{{ order.created_by }} + {% if order.target_date %} + + + {% trans "Target Date" %} + {{ order.target_date }} + + {% endif %} {% if order.shipment_date %} diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/order.js index 027347e37c..18c315d17e 100644 --- a/InvenTree/templates/js/order.js +++ b/InvenTree/templates/js/order.js @@ -275,6 +275,11 @@ function loadSalesOrderTable(table, options) { field: 'creation_date', title: '{% trans "Creation Date" %}', }, + { + sortable: true, + field: 'target_date', + title: '{% trans "Target Date" %}', + }, { sortable: true, field: 'shipment_date', From 8e13a7b470a289e315e6be96a6df6c59573f9c7b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 18 Dec 2020 12:45:42 +1100 Subject: [PATCH 5/7] Add "overdue sales orders" to index page --- InvenTree/templates/InvenTree/index.html | 15 +++++++++++++++ InvenTree/templates/InvenTree/so_overdue.html | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 InvenTree/templates/InvenTree/so_overdue.html diff --git a/InvenTree/templates/InvenTree/index.html b/InvenTree/templates/InvenTree/index.html index 2f6898788c..f862175920 100644 --- a/InvenTree/templates/InvenTree/index.html +++ b/InvenTree/templates/InvenTree/index.html @@ -29,6 +29,7 @@ InvenTree | {% trans "Index" %} {% endif %} {% if roles.sales_order.view %} {% include "InvenTree/so_outstanding.html" with collapse_id="so_outstanding" %} + {% include "InvenTree/so_overdue.html" with collapse_id="so_overdue" %} {% endif %} @@ -112,6 +113,14 @@ loadSalesOrderTable("#so-outstanding-table", { }, }); +loadSalesOrderTable("#so-overdue-table", { + url: "{% url 'api-so-list' %}", + params: { + overdue: true, + customer_detail: true, + } +}); + $("#latest-parts-table").on('load-success.bs.table', function() { var count = $("#latest-parts-table").bootstrapTable('getData').length; @@ -166,4 +175,10 @@ $("#so-outstanding-table").on('load-success.bs.table', function() { $("#so-outstanding-count").html(count); }); +$("#so-overdue-table").on('load-success.bs.table', function() { + var count = $("#so-overdue-table").bootstrapTable('getData').length; + + $("#so-overdue-count").html(count); +}); + {% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/so_overdue.html b/InvenTree/templates/InvenTree/so_overdue.html new file mode 100644 index 0000000000..bf2b64a1e3 --- /dev/null +++ b/InvenTree/templates/InvenTree/so_overdue.html @@ -0,0 +1,15 @@ +{% extends "collapse_index.html" %} + +{% load i18n %} + +{% block collapse_title %} + +{% trans "Overdue Sales Orders" %} +{% endblock %} + +{% block collapse_content %} + + +
+ +{% endblock %} \ No newline at end of file From 13e924cc0533c0672ec1b269e9f7f47d3529a3c5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 18 Dec 2020 16:10:55 +1100 Subject: [PATCH 6/7] Fix default value for PO and SO codes --- InvenTree/common/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 2f27dd602d..fa1d4dc5c9 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -153,11 +153,13 @@ class InvenTreeSetting(models.Model): 'SALESORDER_REFERENCE_PREFIX': { 'name': _('Sales Order Reference Prefix'), 'description': _('Prefix value for sales order reference'), + 'default': 'SO', }, 'PURCHASEORDER_REFERENCE_PREFIX': { 'name': _('Purchase Order Reference Prefix'), 'description': _('Prefix value for purchase order reference'), + 'default': 'PO', }, } From 08a8556fe76cb13f2e398ec8e56b371b604b89ae Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 18 Dec 2020 19:46:02 +1100 Subject: [PATCH 7/7] Fix unit testing --- InvenTree/order/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/order/tests.py b/InvenTree/order/tests.py index 7b118dc60e..47dfe63d0f 100644 --- a/InvenTree/order/tests.py +++ b/InvenTree/order/tests.py @@ -31,11 +31,11 @@ class OrderTest(TestCase): self.assertEqual(order.get_absolute_url(), '/order/purchase-order/1/') - self.assertEqual(str(order), 'PO 0001 - ACME') + self.assertEqual(str(order), 'PO0001 - ACME') line = PurchaseOrderLineItem.objects.get(pk=1) - self.assertEqual(str(line), "100 x ACME0001 from ACME (for PO 0001 - ACME)") + self.assertEqual(str(line), "100 x ACME0001 from ACME (for PO0001 - ACME)") def test_increment(self):