diff --git a/InvenTree/InvenTree/static/script/inventree/filters.js b/InvenTree/InvenTree/static/script/inventree/filters.js index de04bcd86d..b29e787e18 100644 --- a/InvenTree/InvenTree/static/script/inventree/filters.js +++ b/InvenTree/InvenTree/static/script/inventree/filters.js @@ -18,6 +18,8 @@ function defaultFilters() { build: "", parts: "cascade=1", company: "", + salesorder: "", + purchaseorder: "", }; } @@ -258,6 +260,8 @@ function setupFilterList(tableKey, table, target) { var element = $(target); + console.log(tableKey + " - " + element); + // One blank slate, please element.empty(); @@ -298,6 +302,8 @@ function setupFilterList(tableKey, table, target) { element.find(`#filter-tag-${tableKey}`).on('change', function() { var list = element.find(`#filter-value-${tableKey}`); + console.log('index was changed!'); + list.replaceWith(generateFilterInput(tableKey, this.value)); }); diff --git a/InvenTree/InvenTree/static/script/inventree/order.js b/InvenTree/InvenTree/static/script/inventree/order.js index 1d331491ca..7e2b83a406 100644 --- a/InvenTree/InvenTree/static/script/inventree/order.js +++ b/InvenTree/InvenTree/static/script/inventree/order.js @@ -108,13 +108,13 @@ function loadPurchaseOrderTable(table, options) { options.params['supplier_detail'] = true; - var filters = loadTableFilters("order"); + var filters = loadTableFilters("purchaseorder"); for (var key in options.params) { filters[key] = options.params[key]; } - setupFilterList("order", $(table)); + setupFilterList("purchaseorder", $(table)); $(table).inventreeTable({ url: options.url, @@ -159,7 +159,7 @@ function loadPurchaseOrderTable(table, options) { field: 'status', title: 'Status', 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['customer_detail'] = true; - var filters = loadTableFilters("table"); + var filters = loadTableFilters("salesorder"); for (var key in options.params) { filters[key] = options.params[key]; } - setupFilterList("order", $(table)); + setupFilterList("salesorder", $(table)); $(table).inventreeTable({ url: options.url, @@ -232,7 +232,7 @@ function loadSalesOrderTable(table, options) { field: 'status', title: 'Status', formatter: function(value, row, index, field) { - return orderStatusDisplay(row.status, row.status_text); + return salesOrderStatusDisplay(row.status, row.status_text); } }, { diff --git a/InvenTree/InvenTree/status_codes.py b/InvenTree/InvenTree/status_codes.py index fa2a4d9bfd..f1367b9c65 100644 --- a/InvenTree/InvenTree/status_codes.py +++ b/InvenTree/InvenTree/status_codes.py @@ -70,11 +70,14 @@ class StatusCode: raise ValueError("Label not found") -class OrderStatus(StatusCode): +class PurchaseOrderStatus(StatusCode): + """ + Defines a set of status codes for a PurchaseOrder + """ # Order status codes 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 CANCELLED = 40 # Order was cancelled 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): OK = 10 # Item is OK diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 3b8f058bfd..ec87619cdb 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -25,7 +25,7 @@ from stdimage.models import StdImageField from InvenTree.helpers import getMediaUrl, getBlankImage, getBlankThumbnail from InvenTree.helpers import normalize from InvenTree.fields import InvenTreeURLField, RoundingDecimalField -from InvenTree.status_codes import OrderStatus +from InvenTree.status_codes import PurchaseOrderStatus from common.models import Currency @@ -185,11 +185,11 @@ class Company(models.Model): def outstanding_purchase_orders(self): """ 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): """ 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): """ Return purchase orders which are not 'outstanding' @@ -199,15 +199,15 @@ class Company(models.Model): - Returned """ - return self.purchase_orders.exclude(status__in=OrderStatus.OPEN) + return self.purchase_orders.exclude(status__in=PurchaseOrderStatus.OPEN) 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): """ 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): @@ -384,7 +384,7 @@ class SupplierPart(models.Model): 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): """ Return the total quantity of items currently on order. diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index ae88629505..5fe784ddc4 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -13,7 +13,7 @@ from django.urls import reverse from django.forms import HiddenInput 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 common.models import Currency @@ -137,7 +137,6 @@ class CompanyDetail(DetailView): def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) - ctx['OrderStatus'] = OrderStatus return ctx @@ -244,7 +243,6 @@ class SupplierPartDetail(DetailView): def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) - ctx['OrderStatus'] = OrderStatus return ctx diff --git a/InvenTree/order/migrations/0028_auto_20200423_0956.py b/InvenTree/order/migrations/0028_auto_20200423_0956.py new file mode 100644 index 0000000000..cf9cd1e0e2 --- /dev/null +++ b/InvenTree/order/migrations/0028_auto_20200423_0956.py @@ -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)]), + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index e6b35e1921..1f8dd2f2ac 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -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): diff --git a/InvenTree/order/templates/order/order_base.html b/InvenTree/order/templates/order/order_base.html index 85d766a02e..0105e71e83 100644 --- a/InvenTree/order/templates/order/order_base.html +++ b/InvenTree/order/templates/order/order_base.html @@ -67,7 +67,7 @@ src="{% static 'img/blank_image.png' %}"