mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 21:25:42 +00:00 
			
		
		
		
	Specify 'default' functions for 'reference' field in SalesOrder and PurchaseOrder
- Refactor CreatePurchaseOrder form - Some migrations
This commit is contained in:
		
							
								
								
									
										20
									
								
								InvenTree/company/migrations/0040_alter_company_currency.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								InvenTree/company/migrations/0040_alter_company_currency.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| # Generated by Django 3.2.4 on 2021-07-02 13:21 | ||||
|  | ||||
| import InvenTree.validators | ||||
| import common.settings | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('company', '0039_auto_20210701_0509'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='company', | ||||
|             name='currency', | ||||
|             field=models.CharField(blank=True, default=common.settings.currency_code_default, help_text='Default currency used for this company', max_length=3, validators=[InvenTree.validators.validate_currency_code], verbose_name='Currency'), | ||||
|         ), | ||||
|     ] | ||||
| @@ -115,16 +115,11 @@ | ||||
|     }); | ||||
|  | ||||
|     $("#company-order-2").click(function() { | ||||
|         launchModalForm("{% url 'po-create' %}", | ||||
|         { | ||||
|             data: { | ||||
|                 supplier: {{ company.id }}, | ||||
|             }, | ||||
|             follow: true, | ||||
|         createPurchaseOrder({ | ||||
|             supplier: {{ company.pk }}, | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|  | ||||
|     $('#company-delete').click(function() { | ||||
|         constructForm('{% url "api-company-detail" company.pk %}', { | ||||
|             method: 'DELETE', | ||||
|   | ||||
| @@ -39,14 +39,9 @@ | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|  | ||||
|     function newOrder() { | ||||
|         launchModalForm("{% url 'po-create' %}", | ||||
|         { | ||||
|             data: { | ||||
|                 supplier: {{ company.id }}, | ||||
|             }, | ||||
|             follow: true, | ||||
|         createPurchaseOrder({ | ||||
|             supplier: {{ company.pk }}, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -5,11 +5,12 @@ JSON API for the Order app | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from django.conf.urls import url, include | ||||
|  | ||||
| from django_filters.rest_framework import DjangoFilterBackend | ||||
| from rest_framework import generics | ||||
| from rest_framework import filters | ||||
|  | ||||
| from django.conf.urls import url, include | ||||
| from rest_framework import filters, status | ||||
| from rest_framework.response import Response | ||||
|  | ||||
| from InvenTree.helpers import str2bool | ||||
| from InvenTree.api import AttachmentMixin | ||||
| @@ -38,6 +39,20 @@ class POList(generics.ListCreateAPIView): | ||||
|     queryset = PurchaseOrder.objects.all() | ||||
|     serializer_class = POSerializer | ||||
|  | ||||
|     def create(self, request, *args, **kwargs): | ||||
|         """ | ||||
|         Save user information on create | ||||
|         """ | ||||
|         serializer = self.get_serializer(data=request.data) | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|          | ||||
|         item = serializer.save() | ||||
|         item.created_by = request.user | ||||
|         item.save() | ||||
|  | ||||
|         headers = self.get_success_headers(serializer.data) | ||||
|         return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) | ||||
|  | ||||
|     def get_serializer(self, *args, **kwargs): | ||||
|  | ||||
|         try: | ||||
| @@ -279,6 +294,20 @@ class SOList(generics.ListCreateAPIView): | ||||
|     queryset = SalesOrder.objects.all() | ||||
|     serializer_class = SalesOrderSerializer | ||||
|  | ||||
|     def create(self, request, *args, **kwargs): | ||||
|         """ | ||||
|         Save user information on create | ||||
|         """ | ||||
|         serializer = self.get_serializer(data=request.data) | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|          | ||||
|         item = serializer.save() | ||||
|         item.created_by = request.user | ||||
|         item.save() | ||||
|  | ||||
|         headers = self.get_success_headers(serializer.data) | ||||
|         return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) | ||||
|  | ||||
|     def get_serializer(self, *args, **kwargs): | ||||
|  | ||||
|         try: | ||||
|   | ||||
| @@ -97,41 +97,6 @@ class ReceivePurchaseOrderForm(HelperForm): | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class EditPurchaseOrderForm(HelperForm): | ||||
|     """ Form for editing a PurchaseOrder object """ | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|  | ||||
|         self.field_prefix = { | ||||
|             'reference': 'PO', | ||||
|             'link': 'fa-link', | ||||
|             'target_date': 'fa-calendar-alt', | ||||
|         } | ||||
|  | ||||
|         self.field_placeholder = { | ||||
|             'reference': _('Purchase Order reference'), | ||||
|         } | ||||
|  | ||||
|         super().__init__(*args, **kwargs) | ||||
|  | ||||
|     target_date = DatePickerFormField( | ||||
|         label=_('Target Date'), | ||||
|         help_text=_('Target date for order delivery. Order will be overdue after this date.'), | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         model = PurchaseOrder | ||||
|         fields = [ | ||||
|             'reference', | ||||
|             'supplier', | ||||
|             'supplier_reference', | ||||
|             'description', | ||||
|             'target_date', | ||||
|             'link', | ||||
|             'responsible', | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class EditSalesOrderForm(HelperForm): | ||||
|     """ Form for editing a SalesOrder object """ | ||||
|  | ||||
|   | ||||
							
								
								
									
										24
									
								
								InvenTree/order/migrations/0048_auto_20210702_2321.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								InvenTree/order/migrations/0048_auto_20210702_2321.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| # Generated by Django 3.2.4 on 2021-07-02 13:21 | ||||
|  | ||||
| from django.db import migrations, models | ||||
| import order.models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('order', '0047_auto_20210701_0509'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='purchaseorder', | ||||
|             name='reference', | ||||
|             field=models.CharField(default=order.models.get_next_po_number, help_text='Order reference', max_length=64, unique=True, verbose_name='Reference'), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='salesorder', | ||||
|             name='reference', | ||||
|             field=models.CharField(default=order.models.get_next_so_number, help_text='Order reference', max_length=64, unique=True, verbose_name='Reference'), | ||||
|         ), | ||||
|     ] | ||||
| @@ -31,6 +31,60 @@ from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockS | ||||
| from InvenTree.models import InvenTreeAttachment | ||||
|  | ||||
|  | ||||
| def get_next_po_number(): | ||||
|     """ | ||||
|     Returns the next available PurchaseOrder reference number | ||||
|     """ | ||||
|  | ||||
|     if PurchaseOrder.objects.count() == 0: | ||||
|         return | ||||
|  | ||||
|     order = PurchaseOrder.objects.exclude(reference=None).last() | ||||
|  | ||||
|     attempts = set([order.reference]) | ||||
|  | ||||
|     while 1: | ||||
|         reference = increment(order.reference) | ||||
|  | ||||
|         if reference in attempts: | ||||
|             # Escape infinite recursion | ||||
|             return reference | ||||
|  | ||||
|         if PurchaseOrder.objects.filter(reference=reference).exists(): | ||||
|             attempts.add(reference) | ||||
|         else: | ||||
|             break | ||||
|  | ||||
|     return reference | ||||
|  | ||||
|  | ||||
| def get_next_so_number(): | ||||
|     """ | ||||
|     Returns the next available SalesOrder reference number | ||||
|     """ | ||||
|  | ||||
|     if SalesOrder.objects.count() == 0: | ||||
|         return | ||||
|  | ||||
|     order = SalesOrder.objects.exclude(reference=None).last() | ||||
|  | ||||
|     attempts = set([order.reference]) | ||||
|  | ||||
|     while 1: | ||||
|         reference = increment(order.reference) | ||||
|  | ||||
|         if reference in attempts: | ||||
|             # Escape infinite recursion | ||||
|             return reference | ||||
|  | ||||
|         if SalesOrder.objects.filter(reference=reference).exists(): | ||||
|             attempts.add(reference) | ||||
|         else: | ||||
|             break | ||||
|  | ||||
|     return reference | ||||
|  | ||||
|  | ||||
| class Order(models.Model): | ||||
|     """ Abstract model for an order. | ||||
|  | ||||
| @@ -72,6 +126,8 @@ class Order(models.Model): | ||||
|         while 1: | ||||
|             new_ref = increment(ref) | ||||
|  | ||||
|             print("Reference:", new_ref) | ||||
|  | ||||
|             if new_ref in tries: | ||||
|                 # We are in a looping situation - simply return the original one | ||||
|                 return ref | ||||
| @@ -95,8 +151,6 @@ class Order(models.Model): | ||||
|     class Meta: | ||||
|         abstract = True | ||||
|  | ||||
|     reference = models.CharField(unique=True, max_length=64, blank=False, verbose_name=_('Reference'), help_text=_('Order reference')) | ||||
|  | ||||
|     description = models.CharField(max_length=250, verbose_name=_('Description'), help_text=_('Order description')) | ||||
|  | ||||
|     link = models.URLField(blank=True, verbose_name=_('Link'), help_text=_('Link to external page')) | ||||
| @@ -181,6 +235,15 @@ class PurchaseOrder(Order): | ||||
|  | ||||
|         return f"{prefix}{self.reference} - {self.supplier.name}" | ||||
|  | ||||
|     reference = models.CharField( | ||||
|         unique=True, | ||||
|         max_length=64, | ||||
|         blank=False, | ||||
|         verbose_name=_('Reference'), | ||||
|         help_text=_('Order reference'), | ||||
|         default=get_next_po_number, | ||||
|     ) | ||||
|  | ||||
|     status = models.PositiveIntegerField(default=PurchaseOrderStatus.PENDING, choices=PurchaseOrderStatus.items(), | ||||
|                                          help_text=_('Purchase order status')) | ||||
|  | ||||
| @@ -459,6 +522,15 @@ class SalesOrder(Order): | ||||
|     def get_absolute_url(self): | ||||
|         return reverse('so-detail', kwargs={'pk': self.id}) | ||||
|  | ||||
|     reference = models.CharField( | ||||
|         unique=True, | ||||
|         max_length=64, | ||||
|         blank=False, | ||||
|         verbose_name=_('Reference'), | ||||
|         help_text=_('Order reference'), | ||||
|         default=get_next_so_number, | ||||
|     ) | ||||
|  | ||||
|     customer = models.ForeignKey( | ||||
|         Company, | ||||
|         on_delete=models.SET_NULL, | ||||
|   | ||||
| @@ -69,6 +69,8 @@ class POSerializer(InvenTreeModelSerializer): | ||||
|  | ||||
|     overdue = serializers.BooleanField(required=False, read_only=True) | ||||
|  | ||||
|     reference = serializers.CharField(required=True) | ||||
|  | ||||
|     class Meta: | ||||
|         model = PurchaseOrder | ||||
|  | ||||
| @@ -212,6 +214,8 @@ class SalesOrderSerializer(InvenTreeModelSerializer): | ||||
|  | ||||
|     overdue = serializers.BooleanField(required=False, read_only=True) | ||||
|  | ||||
|     reference = serializers.CharField(required=True) | ||||
|  | ||||
|     class Meta: | ||||
|         model = SalesOrder | ||||
|  | ||||
|   | ||||
| @@ -176,18 +176,7 @@ $("#order-print").click(function() { | ||||
| }) | ||||
|  | ||||
| $("#po-create").click(function() { | ||||
|     launchModalForm("{% url 'po-create' %}", | ||||
|         { | ||||
|             follow: true, | ||||
|             secondary: [ | ||||
|                 { | ||||
|                     field: 'supplier', | ||||
|                     label: '{% trans "New Supplier" %}', | ||||
|                     title: '{% trans "Create new Supplier" %}', | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     ); | ||||
|     createPurchaseOrder(); | ||||
| }); | ||||
|  | ||||
| loadPurchaseOrderTable("#purchase-order-table", { | ||||
|   | ||||
| @@ -152,22 +152,6 @@ class POTests(OrderViewTestCase): | ||||
|         keys = response.context.keys() | ||||
|         self.assertIn('PurchaseOrderStatus', keys) | ||||
|  | ||||
|     def test_po_create(self): | ||||
|         """ Launch forms to create new PurchaseOrder""" | ||||
|         url = reverse('po-create') | ||||
|  | ||||
|         # Without a supplier ID | ||||
|         response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         # With a valid supplier ID | ||||
|         response = self.client.get(url, {'supplier': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         # With an invalid supplier ID | ||||
|         response = self.client.get(url, {'supplier': 'goat'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|     def test_po_export(self): | ||||
|         """ Export PurchaseOrder """ | ||||
|  | ||||
|   | ||||
| @@ -60,12 +60,6 @@ class OrderTest(TestCase): | ||||
|         order.save() | ||||
|         self.assertFalse(order.is_overdue) | ||||
|  | ||||
|     def test_increment(self): | ||||
|  | ||||
|         next_ref = PurchaseOrder.getNextOrderNumber() | ||||
|  | ||||
|         self.assertEqual(next_ref, '0008') | ||||
|  | ||||
|     def test_on_order(self): | ||||
|         """ There should be 3 separate items on order for the M2x4 LPHS part """ | ||||
|  | ||||
|   | ||||
| @@ -28,8 +28,6 @@ purchase_order_detail_urls = [ | ||||
|  | ||||
| purchase_order_urls = [ | ||||
|  | ||||
|     url(r'^new/', views.PurchaseOrderCreate.as_view(), name='po-create'), | ||||
|  | ||||
|     url(r'^order-parts/', views.OrderParts.as_view(), name='order-parts'), | ||||
|     url(r'^pricing/', views.LineItemPricing.as_view(), name='line-pricing'), | ||||
|  | ||||
|   | ||||
| @@ -143,43 +143,6 @@ class SalesOrderNotes(InvenTreeRoleMixin, UpdateView): | ||||
|         return ctx | ||||
|  | ||||
|  | ||||
| class PurchaseOrderCreate(AjaxCreateView): | ||||
|     """ | ||||
|     View for creating a new PurchaseOrder object using a modal form | ||||
|     """ | ||||
|  | ||||
|     model = PurchaseOrder | ||||
|     ajax_form_title = _("Create Purchase Order") | ||||
|     form_class = order_forms.EditPurchaseOrderForm | ||||
|  | ||||
|     def get_initial(self): | ||||
|         initials = super().get_initial().copy() | ||||
|  | ||||
|         initials['reference'] = PurchaseOrder.getNextOrderNumber() | ||||
|         initials['status'] = PurchaseOrderStatus.PENDING | ||||
|  | ||||
|         supplier_id = self.request.GET.get('supplier', None) | ||||
|  | ||||
|         if supplier_id: | ||||
|             try: | ||||
|                 supplier = Company.objects.get(id=supplier_id) | ||||
|                 initials['supplier'] = supplier | ||||
|             except (Company.DoesNotExist, ValueError): | ||||
|                 pass | ||||
|  | ||||
|         return initials | ||||
|  | ||||
|     def save(self, form, **kwargs): | ||||
|         """ | ||||
|         Record the user who created this PurchaseOrder | ||||
|         """ | ||||
|  | ||||
|         order = form.save(commit=False) | ||||
|         order.created_by = self.request.user | ||||
|  | ||||
|         return super().save(form) | ||||
|  | ||||
|  | ||||
| class SalesOrderCreate(AjaxCreateView): | ||||
|     """ View for creating a new SalesOrder object """ | ||||
|  | ||||
|   | ||||
| @@ -2,17 +2,21 @@ | ||||
| JSON API for the Stock app | ||||
| """ | ||||
|  | ||||
| from django_filters.rest_framework import FilterSet, DjangoFilterBackend | ||||
| from django_filters import NumberFilter | ||||
|  | ||||
| from rest_framework import status | ||||
|  | ||||
| from django.conf.urls import url, include | ||||
| from django.urls import reverse | ||||
| from django.http import JsonResponse | ||||
| from django.db.models import Q | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
|  | ||||
| from rest_framework import status | ||||
| from rest_framework.serializers import ValidationError | ||||
| from rest_framework.views import APIView | ||||
| from rest_framework.response import Response | ||||
| from rest_framework import generics, filters, permissions | ||||
|  | ||||
| from django_filters.rest_framework import FilterSet, DjangoFilterBackend | ||||
| from django_filters import NumberFilter | ||||
|  | ||||
| from .models import StockLocation, StockItem | ||||
| from .models import StockItemTracking | ||||
| from .models import StockItemAttachment | ||||
| @@ -44,11 +48,6 @@ from decimal import Decimal, InvalidOperation | ||||
|  | ||||
| from datetime import datetime, timedelta | ||||
|  | ||||
| from rest_framework.serializers import ValidationError | ||||
| from rest_framework.views import APIView | ||||
| from rest_framework.response import Response | ||||
| from rest_framework import generics, filters, permissions | ||||
|  | ||||
|  | ||||
| class StockCategoryTree(TreeSerializer): | ||||
|     title = _('Stock') | ||||
|   | ||||
| @@ -1,6 +1,38 @@ | ||||
| {% load i18n %} | ||||
| {% load inventree_extras %} | ||||
|  | ||||
|  | ||||
| // Create a new purchase order | ||||
| function createPurchaseOrder(options={}) { | ||||
|  | ||||
|     constructForm('{% url "api-po-list" %}', { | ||||
|         method: 'POST', | ||||
|         fields: { | ||||
|             reference: { | ||||
|                 prefix: "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}",    | ||||
|             }, | ||||
|             supplier: { | ||||
|                 value: options.supplier, | ||||
|             }, | ||||
|             description: {}, | ||||
|             target_date: { | ||||
|                 icon: 'fa-calendar-alt', | ||||
|             }, | ||||
|             link: { | ||||
|                 icon: 'fa-link', | ||||
|             }, | ||||
|             responsible: { | ||||
|                 icon: 'fa-user', | ||||
|             } | ||||
|         }, | ||||
|         onSuccess: function(data) { | ||||
|             location.href = `/order/purchase-order/${data.pk}/`; | ||||
|         }, | ||||
|         title: '{% trans "Create Purchase Order" %}', | ||||
|     }); | ||||
| } | ||||
|  | ||||
|  | ||||
| function removeOrderRowFromOrderWizard(e) { | ||||
|     /* Remove a part selection from an order form. */ | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user