mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Allow shipment numbers to be non-unique for different sales orders
- must be unique for a given sales order
This commit is contained in:
parent
f3f3030b37
commit
3f9b280e17
@ -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')),
|
||||||
|
22
InvenTree/order/migrations/0060_auto_20211129_1339.py
Normal file
22
InvenTree/order/migrations/0060_auto_20211129_1339.py
Normal 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')},
|
||||||
|
),
|
||||||
|
]
|
@ -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(
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user