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:
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.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):
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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,))
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user