Specify 'default' functions for 'reference' field in SalesOrder and PurchaseOrder

- Refactor CreatePurchaseOrder form
- Some migrations
This commit is contained in:
Oliver 2021-07-02 23:59:02 +10:00
parent 7e5c9aa043
commit 984828f3bb
15 changed files with 200 additions and 137 deletions

View 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'),
),
]

View File

@ -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',

View File

@ -39,14 +39,9 @@
}
});
function newOrder() {
launchModalForm("{% url 'po-create' %}",
{
data: {
supplier: {{ company.id }},
},
follow: true,
createPurchaseOrder({
supplier: {{ company.pk }},
});
}

View File

@ -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:

View File

@ -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 """

View 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'),
),
]

View File

@ -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,

View File

@ -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

View File

@ -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", {

View File

@ -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 """

View File

@ -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 """

View File

@ -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'),

View File

@ -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 """

View File

@ -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')

View File

@ -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. */