Allow shipment numbers to be non-unique for different sales orders

- must be unique for a given sales order
This commit is contained in:
Oliver 2021-11-30 00:42:30 +11:00
parent f3f3030b37
commit 3f9b280e17
6 changed files with 100 additions and 52 deletions

View File

@ -22,7 +22,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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')), ('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')), ('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')), ('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')), ('order', models.ForeignKey(help_text='Sales Order', on_delete=django.db.models.deletion.CASCADE, related_name='shipments', to='order.salesorder', verbose_name='Order')),

View File

@ -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')},
),
]

View File

@ -900,35 +900,6 @@ class SalesOrderLineItem(OrderLineItem):
return self.shipped >= self.quantity 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): class SalesOrderShipment(models.Model):
""" """
The SalesOrderShipment model represents a physical shipment made against a SalesOrder. 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 notes: Custom notes field for this shipment
""" """
class Meta:
# Shipment reference must be unique for a given sales order
unique_together = [
'order', 'reference',
]
@staticmethod @staticmethod
def get_api_url(): def get_api_url():
return reverse('api-so-shipment-list') return reverse('api-so-shipment-list')
@ -976,10 +953,9 @@ class SalesOrderShipment(models.Model):
reference = models.CharField( reference = models.CharField(
max_length=100, max_length=100,
blank=False, blank=False,
unique=True, verbose_name=('Shipment'),
verbose_name=('Reference'), help_text=_('Shipment number'),
help_text=_('Shipment reference'), default='1',
default=get_next_shipment_number,
) )
notes = MarkdownxField( notes = MarkdownxField(

View File

@ -155,6 +155,7 @@
$('#new-shipment').click(function() { $('#new-shipment').click(function() {
createSalesOrderShipment({ createSalesOrderShipment({
order: {{ order.pk }}, order: {{ order.pk }},
reference: '{{ order.reference }}',
onSuccess: function(data) { onSuccess: function(data) {
$('#pending-shipments-table').bootstrapTable('refresh'); $('#pending-shipments-table').bootstrapTable('refresh');
} }

View File

@ -7,11 +7,15 @@ from django.core.exceptions import ValidationError
from datetime import datetime, timedelta from datetime import datetime, timedelta
from company.models import Company 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 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): class SalesOrderTest(TestCase):
""" """

View File

@ -75,9 +75,46 @@ function completeShipment(shipment_id) {
// Open a dialog to create a new sales order shipment // Open a dialog to create a new sales order shipment
function createSalesOrderShipment(options={}) { function createSalesOrderShipment(options={}) {
// 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" %}', { constructForm('{% url "api-so-shipment-list" %}', {
method: 'POST', method: 'POST',
fields: salesOrderShipmentFields(options), fields: fields,
title: '{% trans "Create New Shipment" %}', title: '{% trans "Create New Shipment" %}',
onSuccess: function(data) { onSuccess: function(data) {
if (options.onSuccess) { if (options.onSuccess) {
@ -86,6 +123,9 @@ function createSalesOrderShipment(options={}) {
} }
}); });
} }
}
);
}
// Create a new SalesOrder // Create a new SalesOrder
@ -1271,15 +1311,20 @@ function loadSalesOrderShipmentTable(table, options={}) {
title: '{% trans "Shipment" %}', title: '{% trans "Shipment" %}',
switchable: false, switchable: false,
}, },
{
field: 'status',
title: '{% trans "Status" %}',
},
{ {
field: 'shipment_date', field: 'shipment_date',
title: '{% trans "Shipment Date" %}', title: '{% trans "Shipment Date" %}',
visible: options.shipped, formatter: function(value, row) {
switchable: false, if (value) {
return value;
} else {
return '{% trans "Not shipped" %}';
}
}
},
{
field: 'tracking_number',
title: '{% trans "Tracking" %}',
}, },
{ {
field: 'notes', field: 'notes',
@ -1711,7 +1756,7 @@ function loadSalesOrderAllocationTable(table, options={}) {
field: 'quantity', field: 'quantity',
title: '{% trans "Quantity" %}', title: '{% trans "Quantity" %}',
sortable: true, sortable: true,
} },
] ]
}); });
} }