mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Separate concept of "OrderStatus" into "SalesOrderStatus" and "PurchaseOrderStatus"
This commit is contained in:
parent
7f020cbbf6
commit
435c13cf7c
@ -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));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
37
InvenTree/order/migrations/0028_auto_20200423_0956.py
Normal file
37
InvenTree/order/migrations/0028_auto_20200423_0956.py
Normal 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)]),
|
||||||
|
),
|
||||||
|
]
|
@ -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):
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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,))
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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')
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user