2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-08-06 12:01:41 +00:00

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

@@ -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.helpers import decimal2string, normalize
from InvenTree.status_codes import OrderStatus
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
from InvenTree.models import InvenTreeAttachment
@@ -76,9 +76,6 @@ class Order(models.Model):
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,
on_delete=models.SET_NULL,
blank=True, null=True,
@@ -91,29 +88,6 @@ class Order(models.Model):
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):
""" A PurchaseOrder represents goods shipped inwards from an external supplier.
@@ -129,6 +103,9 @@ class PurchaseOrder(Order):
def __str__(self):
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(
Company, on_delete=models.CASCADE,
limit_choices_to={
@@ -195,6 +172,29 @@ class PurchaseOrder(Order):
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):
""" Return a list of pending line items for this order.
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
"""
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'")})
try:
@@ -275,6 +275,9 @@ class SalesOrder(Order):
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"))
def is_fully_allocated(self):

View File

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

View File

@@ -15,7 +15,7 @@ InvenTree | {% trans "Purchase Orders" %}
<div id='table-buttons'>
<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>
<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 -->
</div>
</div>

View File

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

View File

@@ -15,7 +15,7 @@ InvenTree | {% trans "Sales Orders" %}
<div id='table-buttons'>
<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>
<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 -->
</div>
</div>

View File

@@ -7,7 +7,7 @@ from django.test import TestCase
from django.urls import reverse
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
@@ -53,7 +53,7 @@ class POTests(OrderViewTestCase):
response = self.client.get(reverse('po-detail', args=(1,)))
self.assertEqual(response.status_code, 200)
keys = response.context.keys()
self.assertIn('OrderStatus', keys)
self.assertIn('PurchaseOrderStatus', keys)
def test_po_create(self):
""" Launch forms to create new PurchaseOrder"""
@@ -91,7 +91,7 @@ class POTests(OrderViewTestCase):
url = reverse('po-issue', args=(1,))
order = PurchaseOrder.objects.get(pk=1)
self.assertEqual(order.status, OrderStatus.PENDING)
self.assertEqual(order.status, PurchaseOrderStatus.PENDING)
# Test without confirmation
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
order = PurchaseOrder.objects.get(pk=1)
self.assertEqual(order.status, OrderStatus.PLACED)
self.assertEqual(order.status, PurchaseOrderStatus.PLACED)
def test_line_item_create(self):
""" 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
po = PurchaseOrder.objects.get(pk=1)
n = po.lines.count()
self.assertEqual(po.status, OrderStatus.PENDING)
self.assertEqual(po.status, PurchaseOrderStatus.PENDING)
url = reverse('po-line-item-create')
@@ -181,7 +181,7 @@ class TestPOReceive(OrderViewTestCase):
super().setUp()
self.po = PurchaseOrder.objects.get(pk=1)
self.po.status = OrderStatus.PLACED
self.po.status = PurchaseOrderStatus.PLACED
self.po.save()
self.url = reverse('po-receive', args=(1,))

View File

@@ -6,7 +6,7 @@ from .models import PurchaseOrder, PurchaseOrderLineItem
from stock.models import StockLocation
from company.models import SupplierPart
from InvenTree.status_codes import OrderStatus
from InvenTree.status_codes import PurchaseOrderStatus
class OrderTest(TestCase):
@@ -57,7 +57,7 @@ class OrderTest(TestCase):
order = PurchaseOrder.objects.get(pk=1)
self.assertEqual(order.status, OrderStatus.PENDING)
self.assertEqual(order.status, PurchaseOrderStatus.PENDING)
self.assertEqual(order.lines.count(), 3)
sku = SupplierPart.objects.get(SKU='ACME-WIDGET')
@@ -104,14 +104,14 @@ class OrderTest(TestCase):
self.assertEqual(len(order.pending_line_items()), 3)
# 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):
order.receive_line_item(line, loc, 50, user=None)
order.place_order()
self.assertEqual(order.status, OrderStatus.PLACED)
self.assertEqual(order.status, PurchaseOrderStatus.PLACED)
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)
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():
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.helpers import DownloadFile, str2bool
from InvenTree.status_codes import OrderStatus
from InvenTree.status_codes import PurchaseOrderStatus
logger = logging.getLogger(__name__)
@@ -52,8 +52,6 @@ class PurchaseOrderIndex(ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['OrderStatus'] = OrderStatus
return ctx
@@ -74,8 +72,6 @@ class PurchaseOrderDetail(DetailView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['OrderStatus'] = OrderStatus
return ctx
@@ -280,7 +276,7 @@ class PurchaseOrderCreate(AjaxCreateView):
def get_initial(self):
initials = super().get_initial().copy()
initials['status'] = OrderStatus.PENDING
initials['status'] = PurchaseOrderStatus.PENDING
supplier_id = self.request.GET.get('supplier', None)
@@ -310,7 +306,7 @@ class SalesOrderCreate(AjaxCreateView):
def get_initial(self):
initials = super().get_initial().copy()
initials['status'] = OrderStatus.PENDING
initials['status'] = PurchaseOrderStatus.PENDING
customer_id = self.request.GET.get('customer', None)
@@ -343,7 +339,7 @@ class PurchaseOrderEdit(AjaxUpdateView):
order = self.get_object()
# 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()
return form
@@ -455,7 +451,7 @@ class PurchaseOrderComplete(AjaxUpdateView):
if confirm:
po = self.get_object()
po.status = OrderStatus.COMPLETE
po.status = PurchaseOrderStatus.COMPLETE
po.save()
data = {
@@ -1024,7 +1020,7 @@ class POLineItemCreate(AjaxCreateView):
# Limit the available to orders to ones that are PENDING
query = form.fields['order'].queryset
query = query.filter(status=OrderStatus.PENDING)
query = query.filter(status=PurchaseOrderStatus.PENDING)
form.fields['order'].queryset = query
order_id = form['order'].value()