diff --git a/InvenTree/order/migrations/0053_salesordershipment.py b/InvenTree/order/migrations/0053_salesordershipment.py index c2cc40f4db..f36895c5ee 100644 --- a/InvenTree/order/migrations/0053_salesordershipment.py +++ b/InvenTree/order/migrations/0053_salesordershipment.py @@ -22,7 +22,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('shipment_date', models.DateField(blank=True, help_text='Date of shipment', null=True, verbose_name='Shipment Date')), - ('reference', models.CharField(default=order.models.get_next_shipment_number, unique=True, help_text='Shipment reference', max_length=100, verbose_name='Reference')), + ('reference', models.CharField(default='1', help_text='Shipment reference', max_length=100, verbose_name='Reference')), ('notes', markdownx.models.MarkdownxField(blank=True, help_text='Shipment notes', verbose_name='Notes')), ('checked_by', models.ForeignKey(blank=True, help_text='User who checked this shipment', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Checked By')), ('order', models.ForeignKey(help_text='Sales Order', on_delete=django.db.models.deletion.CASCADE, related_name='shipments', to='order.salesorder', verbose_name='Order')), diff --git a/InvenTree/order/migrations/0060_auto_20211129_1339.py b/InvenTree/order/migrations/0060_auto_20211129_1339.py new file mode 100644 index 0000000000..2166ec45ad --- /dev/null +++ b/InvenTree/order/migrations/0060_auto_20211129_1339.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.5 on 2021-11-29 13:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0059_salesordershipment_tracking_number'), + ] + + operations = [ + migrations.AlterField( + model_name='salesordershipment', + name='reference', + field=models.CharField(default='1', help_text='Shipment number', max_length=100, verbose_name='Shipment'), + ), + migrations.AlterUniqueTogether( + name='salesordershipment', + unique_together={('order', 'reference')}, + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 10b0a45f62..a7cadbf7a8 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -900,35 +900,6 @@ class SalesOrderLineItem(OrderLineItem): return self.shipped >= self.quantity -def get_next_shipment_number(): - """ - Returns the next available SalesOrderShipment reference number" - """ - - if SalesOrderShipment.objects.count() == 0: - return "001" - - shipment = SalesOrderShipment.objects.exclude(reference=None).last() - - attempts = set([shipment.reference]) - - reference = shipment.reference - - while 1: - reference = increment(reference) - - if reference in attempts: - # Escape infinite recursion - return reference - - if SalesOrderShipment.objects.filter(reference=reference).exists(): - attempts.add(reference) - else: - break - - return reference - - class SalesOrderShipment(models.Model): """ The SalesOrderShipment model represents a physical shipment made against a SalesOrder. @@ -945,6 +916,12 @@ class SalesOrderShipment(models.Model): notes: Custom notes field for this shipment """ + class Meta: + # Shipment reference must be unique for a given sales order + unique_together = [ + 'order', 'reference', + ] + @staticmethod def get_api_url(): return reverse('api-so-shipment-list') @@ -976,10 +953,9 @@ class SalesOrderShipment(models.Model): reference = models.CharField( max_length=100, blank=False, - unique=True, - verbose_name=('Reference'), - help_text=_('Shipment reference'), - default=get_next_shipment_number, + verbose_name=('Shipment'), + help_text=_('Shipment number'), + default='1', ) notes = MarkdownxField( diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html index bb90f4386f..7977274155 100644 --- a/InvenTree/order/templates/order/sales_order_detail.html +++ b/InvenTree/order/templates/order/sales_order_detail.html @@ -155,6 +155,7 @@ $('#new-shipment').click(function() { createSalesOrderShipment({ order: {{ order.pk }}, + reference: '{{ order.reference }}', onSuccess: function(data) { $('#pending-shipments-table').bootstrapTable('refresh'); } diff --git a/InvenTree/order/test_sales_order.py b/InvenTree/order/test_sales_order.py index 8337ff4b57..76f9275dac 100644 --- a/InvenTree/order/test_sales_order.py +++ b/InvenTree/order/test_sales_order.py @@ -7,11 +7,15 @@ from django.core.exceptions import ValidationError from datetime import datetime, timedelta from company.models import Company -from stock.models import StockItem -from order.models import SalesOrder, SalesOrderLineItem, SalesOrderAllocation -from part.models import Part + from InvenTree import status_codes as status +from order.models import SalesOrder, SalesOrderLineItem, SalesOrderShipment, SalesOrderAllocation + +from part.models import Part + +from stock.models import StockItem + class SalesOrderTest(TestCase): """ diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 50d6b4444e..6327ef81e9 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -75,16 +75,56 @@ function completeShipment(shipment_id) { // Open a dialog to create a new sales order shipment function createSalesOrderShipment(options={}) { - constructForm('{% url "api-so-shipment-list" %}', { - method: 'POST', - fields: salesOrderShipmentFields(options), - title: '{% trans "Create New Shipment" %}', - onSuccess: function(data) { - if (options.onSuccess) { - options.onSuccess(data); + + // Work out the next shipment number for the given order + inventreeGet( + '{% url "api-so-shipment-list" %}', + { + order: options.order, + }, + { + success: function(results) { + // "predict" the next reference number + var ref = results.length + 1; + + var found = false; + + while (!found) { + + var no_match = true; + + for (var ii = 0; ii < results.length; ii++) { + if (ref.toString() == results[ii].reference.toString()) { + no_match = false; + break; + } + } + + if (no_match) { + break; + } else { + ref++; + } + } + + var fields = salesOrderShipmentFields(options); + + fields.reference.value = ref; + fields.reference.prefix = global_settings.SALESORDER_REFERENCE_PREFIX + options.reference; + + constructForm('{% url "api-so-shipment-list" %}', { + method: 'POST', + fields: fields, + title: '{% trans "Create New Shipment" %}', + onSuccess: function(data) { + if (options.onSuccess) { + options.onSuccess(data); + } + } + }); } } - }); + ); } @@ -1271,15 +1311,20 @@ function loadSalesOrderShipmentTable(table, options={}) { title: '{% trans "Shipment" %}', switchable: false, }, - { - field: 'status', - title: '{% trans "Status" %}', - }, { field: 'shipment_date', title: '{% trans "Shipment Date" %}', - visible: options.shipped, - switchable: false, + formatter: function(value, row) { + if (value) { + return value; + } else { + return '{% trans "Not shipped" %}'; + } + } + }, + { + field: 'tracking_number', + title: '{% trans "Tracking" %}', }, { field: 'notes', @@ -1711,7 +1756,7 @@ function loadSalesOrderAllocationTable(table, options={}) { field: 'quantity', title: '{% trans "Quantity" %}', sortable: true, - } + }, ] }); }