Separate concept of "OrderStatus" into "SalesOrderStatus" and "PurchaseOrderStatus"

This commit is contained in:
Oliver Walters 2020-04-23 20:38:09 +10:00
parent 7f020cbbf6
commit 435c13cf7c
19 changed files with 171 additions and 91 deletions

View File

@ -18,6 +18,8 @@ function defaultFilters() {
build: "", build: "",
parts: "cascade=1", parts: "cascade=1",
company: "", company: "",
salesorder: "",
purchaseorder: "",
}; };
} }
@ -258,6 +260,8 @@ function setupFilterList(tableKey, table, target) {
var element = $(target); var element = $(target);
console.log(tableKey + " - " + element);
// One blank slate, please // One blank slate, please
element.empty(); element.empty();
@ -298,6 +302,8 @@ function setupFilterList(tableKey, table, target) {
element.find(`#filter-tag-${tableKey}`).on('change', function() { element.find(`#filter-tag-${tableKey}`).on('change', function() {
var list = element.find(`#filter-value-${tableKey}`); var list = element.find(`#filter-value-${tableKey}`);
console.log('index was changed!');
list.replaceWith(generateFilterInput(tableKey, this.value)); list.replaceWith(generateFilterInput(tableKey, this.value));
}); });

View File

@ -108,13 +108,13 @@ function loadPurchaseOrderTable(table, options) {
options.params['supplier_detail'] = true; options.params['supplier_detail'] = true;
var filters = loadTableFilters("order"); var filters = loadTableFilters("purchaseorder");
for (var key in options.params) { for (var key in options.params) {
filters[key] = options.params[key]; filters[key] = options.params[key];
} }
setupFilterList("order", $(table)); setupFilterList("purchaseorder", $(table));
$(table).inventreeTable({ $(table).inventreeTable({
url: options.url, url: options.url,
@ -159,7 +159,7 @@ function loadPurchaseOrderTable(table, options) {
field: 'status', field: 'status',
title: 'Status', title: 'Status',
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
return orderStatusDisplay(row.status, row.status_text); return purchaseOrderStatusDisplay(row.status, row.status_text);
} }
}, },
{ {
@ -181,13 +181,13 @@ function loadSalesOrderTable(table, options) {
options.params = options.params || {}; options.params = options.params || {};
options.params['customer_detail'] = true; options.params['customer_detail'] = true;
var filters = loadTableFilters("table"); var filters = loadTableFilters("salesorder");
for (var key in options.params) { for (var key in options.params) {
filters[key] = options.params[key]; filters[key] = options.params[key];
} }
setupFilterList("order", $(table)); setupFilterList("salesorder", $(table));
$(table).inventreeTable({ $(table).inventreeTable({
url: options.url, url: options.url,
@ -232,7 +232,7 @@ function loadSalesOrderTable(table, options) {
field: 'status', field: 'status',
title: 'Status', title: 'Status',
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
return orderStatusDisplay(row.status, row.status_text); return salesOrderStatusDisplay(row.status, row.status_text);
} }
}, },
{ {

View File

@ -70,11 +70,14 @@ class StatusCode:
raise ValueError("Label not found") raise ValueError("Label not found")
class OrderStatus(StatusCode): class PurchaseOrderStatus(StatusCode):
"""
Defines a set of status codes for a PurchaseOrder
"""
# Order status codes # Order status codes
PENDING = 10 # Order is pending (not yet placed) PENDING = 10 # Order is pending (not yet placed)
PLACED = 20 # Order has been placed PLACED = 20 # Order has been placed with supplier
COMPLETE = 30 # Order has been completed COMPLETE = 30 # Order has been completed
CANCELLED = 40 # Order was cancelled CANCELLED = 40 # Order was cancelled
LOST = 50 # Order was lost LOST = 50 # Order was lost
@ -112,6 +115,31 @@ class OrderStatus(StatusCode):
] ]
class SalesOrderStatus(StatusCode):
""" Defines a set of status codes for a SalesOrder """
PENDING = 10 # Order is pending
SHIPPED = 20 # Order has been shipped to customer
CANCELLED = 40 # Order has been cancelled
LOST = 50 # Order was lost
RETURNED = 60 # Order was returned
options = {
PENDING: _("Pending"),
SHIPPED: _("Shipped"),
CANCELLED: _("Cancelled"),
LOST: _("Lost"),
RETURNED: _("Returned"),
}
labels = {
PENDING: "primary",
SHIPPED: "success",
CANCELLED: "danger",
LOST: "warning",
RETURNED: "warning",
}
class StockStatus(StatusCode): class StockStatus(StatusCode):
OK = 10 # Item is OK OK = 10 # Item is OK

View File

@ -25,7 +25,7 @@ from stdimage.models import StdImageField
from InvenTree.helpers import getMediaUrl, getBlankImage, getBlankThumbnail from InvenTree.helpers import getMediaUrl, getBlankImage, getBlankThumbnail
from InvenTree.helpers import normalize from InvenTree.helpers import normalize
from InvenTree.fields import InvenTreeURLField, RoundingDecimalField from InvenTree.fields import InvenTreeURLField, RoundingDecimalField
from InvenTree.status_codes import OrderStatus from InvenTree.status_codes import PurchaseOrderStatus
from common.models import Currency from common.models import Currency
@ -185,11 +185,11 @@ class Company(models.Model):
def outstanding_purchase_orders(self): def outstanding_purchase_orders(self):
""" Return purchase orders which are 'outstanding' """ """ Return purchase orders which are 'outstanding' """
return self.purchase_orders.filter(status__in=OrderStatus.OPEN) return self.purchase_orders.filter(status__in=PurchaseOrderStatus.OPEN)
def pending_purchase_orders(self): def pending_purchase_orders(self):
""" Return purchase orders which are PENDING (not yet issued) """ """ Return purchase orders which are PENDING (not yet issued) """
return self.purchase_orders.filter(status=OrderStatus.PENDING) return self.purchase_orders.filter(status=PurchaseOrderStatus.PENDING)
def closed_purchase_orders(self): def closed_purchase_orders(self):
""" Return purchase orders which are not 'outstanding' """ Return purchase orders which are not 'outstanding'
@ -199,15 +199,15 @@ class Company(models.Model):
- Returned - Returned
""" """
return self.purchase_orders.exclude(status__in=OrderStatus.OPEN) return self.purchase_orders.exclude(status__in=PurchaseOrderStatus.OPEN)
def complete_purchase_orders(self): def complete_purchase_orders(self):
return self.purchase_orders.filter(status=OrderStatus.COMPLETE) return self.purchase_orders.filter(status=PurchaseOrderStatus.COMPLETE)
def failed_purchase_orders(self): def failed_purchase_orders(self):
""" Return any purchase orders which were not successful """ """ Return any purchase orders which were not successful """
return self.purchase_orders.filter(status__in=OrderStatus.FAILED) return self.purchase_orders.filter(status__in=PurchaseOrderStatus.FAILED)
class Contact(models.Model): class Contact(models.Model):
@ -384,7 +384,7 @@ class SupplierPart(models.Model):
limited to purchase orders that are open / outstanding. limited to purchase orders that are open / outstanding.
""" """
return self.purchase_order_line_items.prefetch_related('order').filter(order__status__in=OrderStatus.OPEN) return self.purchase_order_line_items.prefetch_related('order').filter(order__status__in=PurchaseOrderStatus.OPEN)
def on_order(self): def on_order(self):
""" Return the total quantity of items currently on order. """ Return the total quantity of items currently on order.

View File

@ -13,7 +13,7 @@ from django.urls import reverse
from django.forms import HiddenInput from django.forms import HiddenInput
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
from InvenTree.status_codes import OrderStatus from InvenTree.status_codes import PurchaseOrderStatus
from InvenTree.helpers import str2bool from InvenTree.helpers import str2bool
from common.models import Currency from common.models import Currency
@ -137,7 +137,6 @@ class CompanyDetail(DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['OrderStatus'] = OrderStatus
return ctx return ctx
@ -244,7 +243,6 @@ class SupplierPartDetail(DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['OrderStatus'] = OrderStatus
return ctx return ctx

View File

@ -0,0 +1,37 @@
# Generated by Django 3.0.5 on 2020-04-23 09:56
import InvenTree.fields
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('stock', '0031_auto_20200422_0209'),
('order', '0027_auto_20200422_0236'),
]
operations = [
migrations.AlterField(
model_name='purchaseorder',
name='status',
field=models.PositiveIntegerField(choices=[(10, 'Pending'), (20, 'Placed'), (30, 'Complete'), (40, 'Cancelled'), (50, 'Lost'), (60, 'Returned')], default=10, help_text='Purchase order status'),
),
migrations.AlterField(
model_name='salesorder',
name='status',
field=models.PositiveIntegerField(choices=[(10, 'Pending'), (20, 'Shipped'), (40, 'Cancelled'), (50, 'Lost'), (60, 'Returned')], default=10, help_text='Purchase order status'),
),
migrations.AlterField(
model_name='salesorderallocation',
name='item',
field=models.ForeignKey(help_text='Select stock item to allocate', limit_choices_to={'part__salable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='sales_order_allocations', to='stock.StockItem'),
),
migrations.AlterField(
model_name='salesorderallocation',
name='quantity',
field=InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Enter stock allocation quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(0)]),
),
]

View File

@ -25,7 +25,7 @@ from company.models import Company, SupplierPart
from InvenTree.fields import RoundingDecimalField from InvenTree.fields import RoundingDecimalField
from InvenTree.helpers import decimal2string, normalize from InvenTree.helpers import decimal2string, normalize
from InvenTree.status_codes import OrderStatus from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
from InvenTree.models import InvenTreeAttachment from InvenTree.models import InvenTreeAttachment
@ -76,9 +76,6 @@ class Order(models.Model):
creation_date = models.DateField(blank=True, null=True) creation_date = models.DateField(blank=True, null=True)
status = models.PositiveIntegerField(default=OrderStatus.PENDING, choices=OrderStatus.items(),
help_text='Order status')
created_by = models.ForeignKey(User, created_by = models.ForeignKey(User,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
blank=True, null=True, blank=True, null=True,
@ -91,29 +88,6 @@ class Order(models.Model):
notes = MarkdownxField(blank=True, help_text=_('Order notes')) notes = MarkdownxField(blank=True, help_text=_('Order notes'))
def place_order(self):
""" Marks the order as PLACED. Order must be currently PENDING. """
if self.status == OrderStatus.PENDING:
self.status = OrderStatus.PLACED
self.issue_date = datetime.now().date()
self.save()
def complete_order(self):
""" Marks the order as COMPLETE. Order must be currently PLACED. """
if self.status == OrderStatus.PLACED:
self.status = OrderStatus.COMPLETE
self.complete_date = datetime.now().date()
self.save()
def cancel_order(self):
""" Marks the order as CANCELLED. """
if self.status in [OrderStatus.PLACED, OrderStatus.PENDING]:
self.status = OrderStatus.CANCELLED
self.save()
class PurchaseOrder(Order): class PurchaseOrder(Order):
""" A PurchaseOrder represents goods shipped inwards from an external supplier. """ A PurchaseOrder represents goods shipped inwards from an external supplier.
@ -129,6 +103,9 @@ class PurchaseOrder(Order):
def __str__(self): def __str__(self):
return "PO {ref} - {company}".format(ref=self.reference, company=self.supplier.name) return "PO {ref} - {company}".format(ref=self.reference, company=self.supplier.name)
status = models.PositiveIntegerField(default=PurchaseOrderStatus.PENDING, choices=PurchaseOrderStatus.items(),
help_text='Purchase order status')
supplier = models.ForeignKey( supplier = models.ForeignKey(
Company, on_delete=models.CASCADE, Company, on_delete=models.CASCADE,
limit_choices_to={ limit_choices_to={
@ -195,6 +172,29 @@ class PurchaseOrder(Order):
line.save() line.save()
def place_order(self):
""" Marks the PurchaseOrder as PLACED. Order must be currently PENDING. """
if self.status == PurchaseOrderStatus.PENDING:
self.status = PurchaseOrderStatus.PLACED
self.issue_date = datetime.now().date()
self.save()
def complete_order(self):
""" Marks the PurchaseOrder as COMPLETE. Order must be currently PLACED. """
if self.status == PurchaseOrderStatus.PLACED:
self.status = PurchaseOrderStatus.COMPLETE
self.complete_date = datetime.now().date()
self.save()
def cancel_order(self):
""" Marks the PurchaseOrder as CANCELLED. """
if self.status in [PurchaseOrderStatus.PLACED, PurchaseOrderStatus.PENDING]:
self.status = PurchaseOrderStatus.CANCELLED
self.save()
def pending_line_items(self): def pending_line_items(self):
""" Return a list of pending line items for this order. """ Return a list of pending line items for this order.
Any line item where 'received' < 'quantity' will be returned. Any line item where 'received' < 'quantity' will be returned.
@ -213,7 +213,7 @@ class PurchaseOrder(Order):
""" Receive a line item (or partial line item) against this PO """ Receive a line item (or partial line item) against this PO
""" """
if not self.status == OrderStatus.PLACED: if not self.status == PurchaseOrderStatus.PLACED:
raise ValidationError({"status": _("Lines can only be received against an order marked as 'Placed'")}) raise ValidationError({"status": _("Lines can only be received against an order marked as 'Placed'")})
try: try:
@ -275,6 +275,9 @@ class SalesOrder(Order):
help_text=_("Customer"), help_text=_("Customer"),
) )
status = models.PositiveIntegerField(default=SalesOrderStatus.PENDING, choices=SalesOrderStatus.items(),
help_text='Purchase order status')
customer_reference = models.CharField(max_length=64, blank=True, help_text=_("Customer order reference code")) customer_reference = models.CharField(max_length=64, blank=True, help_text=_("Customer order reference code"))
def is_fully_allocated(self): def is_fully_allocated(self):

View File

@ -67,7 +67,7 @@ src="{% static 'img/blank_image.png' %}"
<tr> <tr>
<td><span class='fas fa-info'></span></td> <td><span class='fas fa-info'></span></td>
<td>{% trans "Order Status" %}</td> <td>{% trans "Order Status" %}</td>
<td>{% order_status order.status %}</td> <td>{% purchase_order_status order.status %}</td>
</tr> </tr>
<tr> <tr>
<td><span class='fas fa-building'></span></td> <td><span class='fas fa-building'></span></td>

View File

@ -15,7 +15,7 @@ InvenTree | {% trans "Purchase Orders" %}
<div id='table-buttons'> <div id='table-buttons'>
<div class='button-toolbar container-fluid' style='float: right;'> <div class='button-toolbar container-fluid' style='float: right;'>
<button class='btn btn-primary' type='button' id='po-create' title='{% trans "Create new purchase order" %}'>{% trans "New Purchase Order" %}</button> <button class='btn btn-primary' type='button' id='po-create' title='{% trans "Create new purchase order" %}'>{% trans "New Purchase Order" %}</button>
<div class='filter-list' id='filter-list-order'> <div class='filter-list' id='filter-list-purchaseorder'>
<!-- An empty div in which the filter list will be constructed --> <!-- An empty div in which the filter list will be constructed -->
</div> </div>
</div> </div>

View File

@ -56,7 +56,7 @@ src="{% static 'img/blank_image.png' %}"
<tr> <tr>
<td><span class='fas fa-info'></span></td> <td><span class='fas fa-info'></span></td>
<td>{% trans "Order Status" %}</td> <td>{% trans "Order Status" %}</td>
<td>{% order_status order.status %}</td> <td>{% sales_order_status order.status %}</td>
</tr> </tr>
<tr> <tr>
<td><span class='fas fa-building'></span></td> <td><span class='fas fa-building'></span></td>

View File

@ -15,7 +15,7 @@ InvenTree | {% trans "Sales Orders" %}
<div id='table-buttons'> <div id='table-buttons'>
<div class='button-toolbar container-fluid' style='float: right;'> <div class='button-toolbar container-fluid' style='float: right;'>
<button class='btn btn-primary' type='button' id='so-create' title='{% trans "Create new sales order" %}'>{% trans "New Sales Order" %}</button> <button class='btn btn-primary' type='button' id='so-create' title='{% trans "Create new sales order" %}'>{% trans "New Sales Order" %}</button>
<div class='filter-list' id='filter-list-order'> <div class='filter-list' id='filter-list-salesorder'>
<!-- An empty div in which the filter list will be constructed --> <!-- An empty div in which the filter list will be constructed -->
</div> </div>
</div> </div>

View File

@ -7,7 +7,7 @@ from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from InvenTree.status_codes import OrderStatus from InvenTree.status_codes import PurchaseOrderStatus
from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrder, PurchaseOrderLineItem
@ -53,7 +53,7 @@ class POTests(OrderViewTestCase):
response = self.client.get(reverse('po-detail', args=(1,))) response = self.client.get(reverse('po-detail', args=(1,)))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
keys = response.context.keys() keys = response.context.keys()
self.assertIn('OrderStatus', keys) self.assertIn('PurchaseOrderStatus', keys)
def test_po_create(self): def test_po_create(self):
""" Launch forms to create new PurchaseOrder""" """ Launch forms to create new PurchaseOrder"""
@ -91,7 +91,7 @@ class POTests(OrderViewTestCase):
url = reverse('po-issue', args=(1,)) url = reverse('po-issue', args=(1,))
order = PurchaseOrder.objects.get(pk=1) order = PurchaseOrder.objects.get(pk=1)
self.assertEqual(order.status, OrderStatus.PENDING) self.assertEqual(order.status, PurchaseOrderStatus.PENDING)
# Test without confirmation # Test without confirmation
response = self.client.post(url, {'confirm': 0}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') response = self.client.post(url, {'confirm': 0}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
@ -109,7 +109,7 @@ class POTests(OrderViewTestCase):
# Test that the order was actually placed # Test that the order was actually placed
order = PurchaseOrder.objects.get(pk=1) order = PurchaseOrder.objects.get(pk=1)
self.assertEqual(order.status, OrderStatus.PLACED) self.assertEqual(order.status, PurchaseOrderStatus.PLACED)
def test_line_item_create(self): def test_line_item_create(self):
""" Test the form for adding a new LineItem to a PurchaseOrder """ """ Test the form for adding a new LineItem to a PurchaseOrder """
@ -117,7 +117,7 @@ class POTests(OrderViewTestCase):
# Record the number of line items in the PurchaseOrder # Record the number of line items in the PurchaseOrder
po = PurchaseOrder.objects.get(pk=1) po = PurchaseOrder.objects.get(pk=1)
n = po.lines.count() n = po.lines.count()
self.assertEqual(po.status, OrderStatus.PENDING) self.assertEqual(po.status, PurchaseOrderStatus.PENDING)
url = reverse('po-line-item-create') url = reverse('po-line-item-create')
@ -181,7 +181,7 @@ class TestPOReceive(OrderViewTestCase):
super().setUp() super().setUp()
self.po = PurchaseOrder.objects.get(pk=1) self.po = PurchaseOrder.objects.get(pk=1)
self.po.status = OrderStatus.PLACED self.po.status = PurchaseOrderStatus.PLACED
self.po.save() self.po.save()
self.url = reverse('po-receive', args=(1,)) self.url = reverse('po-receive', args=(1,))

View File

@ -6,7 +6,7 @@ from .models import PurchaseOrder, PurchaseOrderLineItem
from stock.models import StockLocation from stock.models import StockLocation
from company.models import SupplierPart from company.models import SupplierPart
from InvenTree.status_codes import OrderStatus from InvenTree.status_codes import PurchaseOrderStatus
class OrderTest(TestCase): class OrderTest(TestCase):
@ -57,7 +57,7 @@ class OrderTest(TestCase):
order = PurchaseOrder.objects.get(pk=1) order = PurchaseOrder.objects.get(pk=1)
self.assertEqual(order.status, OrderStatus.PENDING) self.assertEqual(order.status, PurchaseOrderStatus.PENDING)
self.assertEqual(order.lines.count(), 3) self.assertEqual(order.lines.count(), 3)
sku = SupplierPart.objects.get(SKU='ACME-WIDGET') sku = SupplierPart.objects.get(SKU='ACME-WIDGET')
@ -104,14 +104,14 @@ class OrderTest(TestCase):
self.assertEqual(len(order.pending_line_items()), 3) self.assertEqual(len(order.pending_line_items()), 3)
# Should fail, as order is 'PENDING' not 'PLACED" # Should fail, as order is 'PENDING' not 'PLACED"
self.assertEqual(order.status, OrderStatus.PENDING) self.assertEqual(order.status, PurchaseOrderStatus.PENDING)
with self.assertRaises(django_exceptions.ValidationError): with self.assertRaises(django_exceptions.ValidationError):
order.receive_line_item(line, loc, 50, user=None) order.receive_line_item(line, loc, 50, user=None)
order.place_order() order.place_order()
self.assertEqual(order.status, OrderStatus.PLACED) self.assertEqual(order.status, PurchaseOrderStatus.PLACED)
order.receive_line_item(line, loc, 50, user=None) order.receive_line_item(line, loc, 50, user=None)
@ -134,9 +134,9 @@ class OrderTest(TestCase):
order.receive_line_item(line, loc, 500, user=None) order.receive_line_item(line, loc, 500, user=None)
self.assertEqual(part.on_order, 800) self.assertEqual(part.on_order, 800)
self.assertEqual(order.status, OrderStatus.PLACED) self.assertEqual(order.status, PurchaseOrderStatus.PLACED)
for line in order.pending_line_items(): for line in order.pending_line_items():
order.receive_line_item(line, loc, line.quantity, user=None) order.receive_line_item(line, loc, line.quantity, user=None)
self.assertEqual(order.status, OrderStatus.COMPLETE) self.assertEqual(order.status, PurchaseOrderStatus.COMPLETE)

View File

@ -29,7 +29,7 @@ from . import forms as order_forms
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
from InvenTree.helpers import DownloadFile, str2bool from InvenTree.helpers import DownloadFile, str2bool
from InvenTree.status_codes import OrderStatus from InvenTree.status_codes import PurchaseOrderStatus
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -52,8 +52,6 @@ class PurchaseOrderIndex(ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['OrderStatus'] = OrderStatus
return ctx return ctx
@ -74,8 +72,6 @@ class PurchaseOrderDetail(DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['OrderStatus'] = OrderStatus
return ctx return ctx
@ -280,7 +276,7 @@ class PurchaseOrderCreate(AjaxCreateView):
def get_initial(self): def get_initial(self):
initials = super().get_initial().copy() initials = super().get_initial().copy()
initials['status'] = OrderStatus.PENDING initials['status'] = PurchaseOrderStatus.PENDING
supplier_id = self.request.GET.get('supplier', None) supplier_id = self.request.GET.get('supplier', None)
@ -310,7 +306,7 @@ class SalesOrderCreate(AjaxCreateView):
def get_initial(self): def get_initial(self):
initials = super().get_initial().copy() initials = super().get_initial().copy()
initials['status'] = OrderStatus.PENDING initials['status'] = PurchaseOrderStatus.PENDING
customer_id = self.request.GET.get('customer', None) customer_id = self.request.GET.get('customer', None)
@ -343,7 +339,7 @@ class PurchaseOrderEdit(AjaxUpdateView):
order = self.get_object() order = self.get_object()
# Prevent user from editing supplier if there are already lines in the order # Prevent user from editing supplier if there are already lines in the order
if order.lines.count() > 0 or not order.status == OrderStatus.PENDING: if order.lines.count() > 0 or not order.status == PurchaseOrderStatus.PENDING:
form.fields['supplier'].widget = HiddenInput() form.fields['supplier'].widget = HiddenInput()
return form return form
@ -455,7 +451,7 @@ class PurchaseOrderComplete(AjaxUpdateView):
if confirm: if confirm:
po = self.get_object() po = self.get_object()
po.status = OrderStatus.COMPLETE po.status = PurchaseOrderStatus.COMPLETE
po.save() po.save()
data = { data = {
@ -1024,7 +1020,7 @@ class POLineItemCreate(AjaxCreateView):
# Limit the available to orders to ones that are PENDING # Limit the available to orders to ones that are PENDING
query = form.fields['order'].queryset query = form.fields['order'].queryset
query = query.filter(status=OrderStatus.PENDING) query = query.filter(status=PurchaseOrderStatus.PENDING)
form.fields['order'].queryset = query form.fields['order'].queryset = query
order_id = form['order'].value() order_id = form['order'].value()

View File

@ -39,7 +39,7 @@ from InvenTree.models import InvenTreeTree, InvenTreeAttachment
from InvenTree.fields import InvenTreeURLField from InvenTree.fields import InvenTreeURLField
from InvenTree.helpers import decimal2string, normalize from InvenTree.helpers import decimal2string, normalize
from InvenTree.status_codes import BuildStatus, StockStatus, OrderStatus from InvenTree.status_codes import BuildStatus, StockStatus, PurchaseOrderStatus
from company.models import SupplierPart from company.models import SupplierPart
@ -955,18 +955,18 @@ class Part(models.Model):
def open_purchase_orders(self): def open_purchase_orders(self):
""" Return a list of open purchase orders against this part """ """ Return a list of open purchase orders against this part """
return [order for order in self.purchase_orders() if order.status in OrderStatus.OPEN] return [order for order in self.purchase_orders() if order.status in PurchaseOrderStatus.OPEN]
def closed_purchase_orders(self): def closed_purchase_orders(self):
""" Return a list of closed purchase orders against this part """ """ Return a list of closed purchase orders against this part """
return [order for order in self.purchase_orders() if order.status not in OrderStatus.OPEN] return [order for order in self.purchase_orders() if order.status not in PurchaseOrderStatus.OPEN]
@property @property
def on_order(self): def on_order(self):
""" Return the total number of items on order for this part. """ """ Return the total number of items on order for this part. """
orders = self.supplier_parts.filter(purchase_order_line_items__order__status__in=OrderStatus.OPEN).aggregate( orders = self.supplier_parts.filter(purchase_order_line_items__order__status__in=PurchaseOrderStatus.OPEN).aggregate(
quantity=Sum('purchase_order_line_items__quantity'), quantity=Sum('purchase_order_line_items__quantity'),
received=Sum('purchase_order_line_items__received') received=Sum('purchase_order_line_items__received')
) )

View File

@ -15,7 +15,7 @@ from decimal import Decimal
from django.db.models import Q, Sum from django.db.models import Q, Sum
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from InvenTree.status_codes import StockStatus, OrderStatus, BuildStatus from InvenTree.status_codes import StockStatus, PurchaseOrderStatus, BuildStatus
from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeModelSerializer
@ -120,7 +120,7 @@ class PartSerializer(InvenTreeModelSerializer):
stock_filter = Q(stock_items__status__in=StockStatus.AVAILABLE_CODES) stock_filter = Q(stock_items__status__in=StockStatus.AVAILABLE_CODES)
# Filter to limit orders to "open" # Filter to limit orders to "open"
order_filter = Q(supplier_parts__purchase_order_line_items__order__status__in=OrderStatus.OPEN) order_filter = Q(supplier_parts__purchase_order_line_items__order__status__in=PurchaseOrderStatus.OPEN)
# Filter to limit builds to "active" # Filter to limit builds to "active"
build_filter = Q(builds__status__in=BuildStatus.ACTIVE_CODES) build_filter = Q(builds__status__in=BuildStatus.ACTIVE_CODES)

View File

@ -4,14 +4,20 @@ Provide templates for the various model status codes.
from django import template from django import template
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from InvenTree.status_codes import OrderStatus, StockStatus, BuildStatus from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
from InvenTree.status_codes import StockStatus, BuildStatus
register = template.Library() register = template.Library()
@register.simple_tag @register.simple_tag
def order_status(key, *args, **kwargs): def purchase_order_status(key, *args, **kwargs):
return mark_safe(OrderStatus.render(key)) return mark_safe(PurchaseOrderStatus.render(key))
@register.simple_tag
def sales_order_status(key, *args, **kwargs):
return mark_safe(SalesOrderStatus.render(key))
@register.simple_tag @register.simple_tag
@ -30,7 +36,8 @@ def load_status_codes(context):
Make the various StatusCodes available to the page context Make the various StatusCodes available to the page context
""" """
context['order_status_codes'] = OrderStatus.list() context['purchase_order_status_codes'] = PurchaseOrderStatus.list()
context['sales_order_status_codes'] = SalesOrderStatus.list()
context['stock_status_codes'] = StockStatus.list() context['stock_status_codes'] = StockStatus.list()
context['build_status_codes'] = BuildStatus.list() context['build_status_codes'] = BuildStatus.list()

View File

@ -38,7 +38,7 @@ from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDelete
from InvenTree.views import QRCodeView from InvenTree.views import QRCodeView
from InvenTree.helpers import DownloadFile, str2bool from InvenTree.helpers import DownloadFile, str2bool
from InvenTree.status_codes import OrderStatus, BuildStatus from InvenTree.status_codes import PurchaseOrderStatus, BuildStatus
class PartIndex(ListView): class PartIndex(ListView):
@ -561,8 +561,6 @@ class PartNotes(UpdateView):
ctx['starred'] = part.isStarredBy(self.request.user) ctx['starred'] = part.isStarredBy(self.request.user)
ctx['disabled'] = not part.active ctx['disabled'] = not part.active
ctx['OrderStatus'] = OrderStatus
return ctx return ctx
@ -593,9 +591,6 @@ class PartDetail(DetailView):
context['starred'] = part.isStarredBy(self.request.user) context['starred'] = part.isStarredBy(self.request.user)
context['disabled'] = not part.active context['disabled'] = not part.active
context['OrderStatus'] = OrderStatus
context['BuildStatus'] = BuildStatus
return context return context

View File

@ -7,7 +7,8 @@
{% include "status_codes.html" with label='stock' options=stock_status_codes %} {% include "status_codes.html" with label='stock' options=stock_status_codes %}
{% include "status_codes.html" with label='build' options=build_status_codes %} {% include "status_codes.html" with label='build' options=build_status_codes %}
{% include "status_codes.html" with label='order' options=order_status_codes %} {% include "status_codes.html" with label='purchaseOrder' options=purchase_order_status_codes %}
{% include "status_codes.html" with label='salesOrder' options=sales_order_status_codes %}
function getAvailableTableFilters(tableKey) { function getAvailableTableFilters(tableKey) {
@ -52,11 +53,20 @@ function getAvailableTableFilters(tableKey) {
} }
// Filters for the "Order" table // Filters for the "Order" table
if (tableKey == "order") { if (tableKey == "purchaseorder") {
return { return {
status: { status: {
title: '{% trans "Order status" %}', title: '{% trans "Order status" %}',
options: orderCodes, options: purchaseOrderCodes,
},
};
}
if (tableKey == "salesorder") {
return {
status: {
title: '{% trans "Order status" %}',
options: salesOrderCodes,
}, },
}; };
} }