2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 19:46:46 +00:00

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() { $("#company-order-2").click(function() {
launchModalForm("{% url 'po-create' %}", createPurchaseOrder({
{ supplier: {{ company.pk }},
data: {
supplier: {{ company.id }},
},
follow: true,
}); });
}); });
$('#company-delete').click(function() { $('#company-delete').click(function() {
constructForm('{% url "api-company-detail" company.pk %}', { constructForm('{% url "api-company-detail" company.pk %}', {
method: 'DELETE', method: 'DELETE',

View File

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

View File

@ -5,11 +5,12 @@ JSON API for the Order app
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf.urls import url, include
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics from rest_framework import generics
from rest_framework import filters from rest_framework import filters, status
from rest_framework.response import Response
from django.conf.urls import url, include
from InvenTree.helpers import str2bool from InvenTree.helpers import str2bool
from InvenTree.api import AttachmentMixin from InvenTree.api import AttachmentMixin
@ -38,6 +39,20 @@ class POList(generics.ListCreateAPIView):
queryset = PurchaseOrder.objects.all() queryset = PurchaseOrder.objects.all()
serializer_class = POSerializer 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): def get_serializer(self, *args, **kwargs):
try: try:
@ -279,6 +294,20 @@ class SOList(generics.ListCreateAPIView):
queryset = SalesOrder.objects.all() queryset = SalesOrder.objects.all()
serializer_class = SalesOrderSerializer 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): def get_serializer(self, *args, **kwargs):
try: 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): class EditSalesOrderForm(HelperForm):
""" Form for editing a SalesOrder object """ """ 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 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): class Order(models.Model):
""" Abstract model for an order. """ Abstract model for an order.
@ -72,6 +126,8 @@ class Order(models.Model):
while 1: while 1:
new_ref = increment(ref) new_ref = increment(ref)
print("Reference:", new_ref)
if new_ref in tries: if new_ref in tries:
# We are in a looping situation - simply return the original one # We are in a looping situation - simply return the original one
return ref return ref
@ -95,8 +151,6 @@ class Order(models.Model):
class Meta: class Meta:
abstract = True 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')) 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')) 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}" 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(), status = models.PositiveIntegerField(default=PurchaseOrderStatus.PENDING, choices=PurchaseOrderStatus.items(),
help_text=_('Purchase order status')) help_text=_('Purchase order status'))
@ -459,6 +522,15 @@ class SalesOrder(Order):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('so-detail', kwargs={'pk': self.id}) 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( customer = models.ForeignKey(
Company, Company,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,

View File

@ -69,6 +69,8 @@ class POSerializer(InvenTreeModelSerializer):
overdue = serializers.BooleanField(required=False, read_only=True) overdue = serializers.BooleanField(required=False, read_only=True)
reference = serializers.CharField(required=True)
class Meta: class Meta:
model = PurchaseOrder model = PurchaseOrder
@ -212,6 +214,8 @@ class SalesOrderSerializer(InvenTreeModelSerializer):
overdue = serializers.BooleanField(required=False, read_only=True) overdue = serializers.BooleanField(required=False, read_only=True)
reference = serializers.CharField(required=True)
class Meta: class Meta:
model = SalesOrder model = SalesOrder

View File

@ -176,18 +176,7 @@ $("#order-print").click(function() {
}) })
$("#po-create").click(function() { $("#po-create").click(function() {
launchModalForm("{% url 'po-create' %}", createPurchaseOrder();
{
follow: true,
secondary: [
{
field: 'supplier',
label: '{% trans "New Supplier" %}',
title: '{% trans "Create new Supplier" %}',
}
]
}
);
}); });
loadPurchaseOrderTable("#purchase-order-table", { loadPurchaseOrderTable("#purchase-order-table", {

View File

@ -152,22 +152,6 @@ class POTests(OrderViewTestCase):
keys = response.context.keys() keys = response.context.keys()
self.assertIn('PurchaseOrderStatus', 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): def test_po_export(self):
""" Export PurchaseOrder """ """ Export PurchaseOrder """

View File

@ -60,12 +60,6 @@ class OrderTest(TestCase):
order.save() order.save()
self.assertFalse(order.is_overdue) self.assertFalse(order.is_overdue)
def test_increment(self):
next_ref = PurchaseOrder.getNextOrderNumber()
self.assertEqual(next_ref, '0008')
def test_on_order(self): def test_on_order(self):
""" There should be 3 separate items on order for the M2x4 LPHS part """ """ 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 = [ 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'^order-parts/', views.OrderParts.as_view(), name='order-parts'),
url(r'^pricing/', views.LineItemPricing.as_view(), name='line-pricing'), url(r'^pricing/', views.LineItemPricing.as_view(), name='line-pricing'),

View File

@ -143,43 +143,6 @@ class SalesOrderNotes(InvenTreeRoleMixin, UpdateView):
return ctx 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): class SalesOrderCreate(AjaxCreateView):
""" View for creating a new SalesOrder object """ """ View for creating a new SalesOrder object """

View File

@ -2,17 +2,21 @@
JSON API for the Stock app 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.conf.urls import url, include
from django.urls import reverse from django.urls import reverse
from django.http import JsonResponse from django.http import JsonResponse
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ 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 StockLocation, StockItem
from .models import StockItemTracking from .models import StockItemTracking
from .models import StockItemAttachment from .models import StockItemAttachment
@ -44,11 +48,6 @@ from decimal import Decimal, InvalidOperation
from datetime import datetime, timedelta 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): class StockCategoryTree(TreeSerializer):
title = _('Stock') title = _('Stock')

View File

@ -1,6 +1,38 @@
{% load i18n %} {% load i18n %}
{% load inventree_extras %} {% 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) { function removeOrderRowFromOrderWizard(e) {
/* Remove a part selection from an order form. */ /* Remove a part selection from an order form. */