mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Specify 'default' functions for 'reference' field in SalesOrder and PurchaseOrder
- Refactor CreatePurchaseOrder form - Some migrations
This commit is contained in:
parent
7e5c9aa043
commit
984828f3bb
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. */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user