diff --git a/InvenTree/InvenTree/fields.py b/InvenTree/InvenTree/fields.py index 884bd0c49f..c0e1633ac7 100644 --- a/InvenTree/InvenTree/fields.py +++ b/InvenTree/InvenTree/fields.py @@ -5,10 +5,13 @@ from __future__ import unicode_literals from .validators import allowable_url_schemes +from django.utils.translation import ugettext as _ + from django.forms.fields import URLField as FormURLField from django.db import models as models from django.core import validators from django import forms + from decimal import Decimal import InvenTree.helpers @@ -31,6 +34,32 @@ class InvenTreeURLField(models.URLField): }) +class DatePickerFormField(forms.DateField): + """ + Custom date-picker field + """ + + def __init__(self, **kwargs): + + help_text = kwargs.get('help_text', _('Enter date')) + required = kwargs.get('required', False) + initial = kwargs.get('initial', None) + + widget = forms.DateInput( + attrs={ + 'type': 'date', + } + ) + + forms.DateField.__init__( + self, + required=required, + initial=initial, + help_text=help_text, + widget=widget + ) + + def round_decimal(value, places): """ Round value to the specified number of places. diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css index 92568eeb11..78c1808adf 100644 --- a/InvenTree/InvenTree/static/css/inventree.css +++ b/InvenTree/InvenTree/static/css/inventree.css @@ -748,3 +748,6 @@ input[type="submit"] { to { transform: scale(1) rotate(360deg);} } +input[type="date"].form-control, input[type="time"].form-control, input[type="datetime-local"].form-control, input[type="month"].form-control { + line-height: unset; +} \ No newline at end of file diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py index 70deeef7b1..136d21d553 100644 --- a/InvenTree/build/forms.py +++ b/InvenTree/build/forms.py @@ -10,6 +10,7 @@ from django import forms from InvenTree.forms import HelperForm from InvenTree.fields import RoundingDecimalFormField +from InvenTree.fields import DatePickerFormField from .models import Build, BuildItem, BuildOrderAttachment @@ -26,18 +27,16 @@ class EditBuildForm(HelperForm): 'batch': 'fa-layer-group', 'serial-numbers': 'fa-hashtag', 'location': 'fa-map-marker-alt', + 'target_date': 'fa-calendar-alt', } field_placeholder = { - 'reference': _('Build Order reference') + 'reference': _('Build Order reference'), + 'target_date': _('Order target date'), } - # TODO: Make this a more "presentable" date picker - # TODO: Currently does not render super nicely with crispy forms - target_date = forms.DateField( - widget=forms.DateInput( - attrs={'type': 'date'} - ) + target_date = DatePickerFormField( + help_text=_('Target date for build completion. Build will be overdue after this date.') ) class Meta: diff --git a/InvenTree/order/forms.py b/InvenTree/order/forms.py index d797a4e42d..6db51b55e6 100644 --- a/InvenTree/order/forms.py +++ b/InvenTree/order/forms.py @@ -12,6 +12,7 @@ from mptt.fields import TreeNodeChoiceField from InvenTree.forms import HelperForm from InvenTree.fields import RoundingDecimalFormField +from InvenTree.fields import DatePickerFormField from stock.models import StockLocation from .models import PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderAttachment @@ -120,6 +121,7 @@ class EditSalesOrderForm(HelperForm): self.field_prefix = { 'reference': 'SO', 'link': 'fa-link', + 'target_date': 'fa-calendar-alt', } self.field_placeholder = { @@ -128,11 +130,8 @@ 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'}, - ) + target_date = DatePickerFormField( + help_text=_('Target date for order completion. Order will be overdue after this date.'), ) class Meta: diff --git a/InvenTree/order/test_views.py b/InvenTree/order/test_views.py index 2f014e751f..51981af105 100644 --- a/InvenTree/order/test_views.py +++ b/InvenTree/order/test_views.py @@ -11,6 +11,7 @@ from django.contrib.auth.models import Group from InvenTree.status_codes import PurchaseOrderStatus from .models import PurchaseOrder, PurchaseOrderLineItem +from .models import SalesOrder import json @@ -59,6 +60,88 @@ class OrderListTest(OrderViewTestCase): self.assertEqual(response.status_code, 200) +class SalesOrderCreate(OrderViewTestCase): + """ + Create a SalesOrder using the form view + """ + + URL = reverse('so-create') + + def test_create_view(self): + """ + Retrieve the view for creating a sales order' + """ + + response = self.client.get(self.URL, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + + self.assertEqual(response.status_code, 200) + + def post(self, data, **kwargs): + + return self.client.post(self.URL, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest', **kwargs) + + def test_post_error(self): + """ + POST with errors + """ + + n = SalesOrder.objects.count() + + data = { + 'reference': '12345678', + } + + response = self.post(data) + + data = json.loads(response.content) + + self.assertIn('form_valid', data.keys()) + + # Customer is not specified - should return False + self.assertFalse(data['form_valid']) + + errors = json.loads(data['form_errors']) + + self.assertIn('customer', errors.keys()) + self.assertIn('description', errors.keys()) + + # No new SalesOrder objects should have been created + self.assertEqual(SalesOrder.objects.count(), n) + + def test_post_valid(self): + """ + POST a valid SalesOrder + """ + + n = SalesOrder.objects.count() + + data = { + 'reference': '12345678', + 'customer': 4, + 'description': 'A description', + } + + response = self.post(data) + + json_data = json.loads(response.content) + + self.assertTrue(json_data['form_valid']) + + # Create another SalesOrder, this time with a target date + data = { + 'reference': '12345679', + 'customer': 4, + 'description': 'Another order, this one with a target date!', + 'target_date': '2020-12-25', + } + + response = self.post(data) + + json_data = json.loads(response.content) + + self.assertEqual(SalesOrder.objects.count(), n + 2) + + class POTests(OrderViewTestCase): """ Tests for PurchaseOrder views """