mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #804 from SchrodingersGat/order-auto-increment
Order auto increment
This commit is contained in:
commit
cf9891398a
@ -120,6 +120,59 @@ def normalize(d):
|
|||||||
return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
|
return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
|
||||||
|
|
||||||
|
|
||||||
|
def increment(n):
|
||||||
|
"""
|
||||||
|
Attempt to increment an integer (or a string that looks like an integer!)
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
001 -> 002
|
||||||
|
2 -> 3
|
||||||
|
AB01 -> AB02
|
||||||
|
QQQ -> QQQ
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
value = str(n).strip()
|
||||||
|
|
||||||
|
# Ignore empty strings
|
||||||
|
if not value:
|
||||||
|
return value
|
||||||
|
|
||||||
|
pattern = r"(.*?)(\d+)?$"
|
||||||
|
|
||||||
|
result = re.search(pattern, value)
|
||||||
|
|
||||||
|
# No match!
|
||||||
|
if result is None:
|
||||||
|
return value
|
||||||
|
|
||||||
|
groups = result.groups()
|
||||||
|
|
||||||
|
# If we cannot match the regex, then simply return the provided value
|
||||||
|
if not len(groups) == 2:
|
||||||
|
return value
|
||||||
|
|
||||||
|
prefix, number = groups
|
||||||
|
|
||||||
|
# No number extracted? Simply return the prefix (without incrementing!)
|
||||||
|
if not number:
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
# Record the width of the number
|
||||||
|
width = len(number)
|
||||||
|
|
||||||
|
try:
|
||||||
|
number = int(number) + 1
|
||||||
|
number = str(number)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
number = number.zfill(width)
|
||||||
|
|
||||||
|
return prefix + number
|
||||||
|
|
||||||
|
|
||||||
def decimal2string(d):
|
def decimal2string(d):
|
||||||
"""
|
"""
|
||||||
Format a Decimal number as a string,
|
Format a Decimal number as a string,
|
||||||
|
@ -108,6 +108,29 @@ class TestQuoteWrap(TestCase):
|
|||||||
self.assertEqual(helpers.WrapWithQuotes('hello"'), '"hello"')
|
self.assertEqual(helpers.WrapWithQuotes('hello"'), '"hello"')
|
||||||
|
|
||||||
|
|
||||||
|
class TestIncrement(TestCase):
|
||||||
|
|
||||||
|
def tests(self):
|
||||||
|
""" Test 'intelligent' incrementing function """
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
("", ""),
|
||||||
|
(1, "2"),
|
||||||
|
("001", "002"),
|
||||||
|
("1001", "1002"),
|
||||||
|
("ABC123", "ABC124"),
|
||||||
|
("XYZ0", "XYZ1"),
|
||||||
|
("123Q", "123Q"),
|
||||||
|
("QQQ", "QQQ"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for test in tests:
|
||||||
|
a, b = test
|
||||||
|
|
||||||
|
result = helpers.increment(a)
|
||||||
|
self.assertEqual(result, b)
|
||||||
|
|
||||||
|
|
||||||
class TestMakeBarcode(TestCase):
|
class TestMakeBarcode(TestCase):
|
||||||
""" Tests for barcode string creation """
|
""" Tests for barcode string creation """
|
||||||
|
|
||||||
|
@ -85,10 +85,10 @@ InvenTree | {% trans "Company" %} - {{ company.name }}
|
|||||||
|
|
||||||
$('#company-edit').click(function() {
|
$('#company-edit').click(function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
"{% url 'company-edit' company.id %}",
|
"{% url 'company-edit' company.id %}",
|
||||||
{
|
{
|
||||||
reload: true
|
reload: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#company-order-2").click(function() {
|
$("#company-order-2").click(function() {
|
||||||
@ -104,10 +104,10 @@ InvenTree | {% trans "Company" %} - {{ company.name }}
|
|||||||
|
|
||||||
$('#company-delete').click(function() {
|
$('#company-delete').click(function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
"{% url 'company-delete' company.id %}",
|
"{% url 'company-delete' company.id %}",
|
||||||
{
|
{
|
||||||
redirect: "{% url 'company-index' %}"
|
redirect: "{% url 'company-index' %}"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
enableDragAndDrop(
|
enableDragAndDrop(
|
||||||
@ -123,11 +123,11 @@ InvenTree | {% trans "Company" %} - {{ company.name }}
|
|||||||
|
|
||||||
$("#company-thumb").click(function() {
|
$("#company-thumb").click(function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
"{% url 'company-image' company.id %}",
|
"{% url 'company-image' company.id %}",
|
||||||
{
|
{
|
||||||
reload: true
|
reload: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -33,9 +33,16 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$("#new-sales-order").click(function() {
|
$("#new-sales-order").click(function() {
|
||||||
// TODO - Create a new sales order
|
launchModalForm(
|
||||||
|
"{% url 'so-create' %}",
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
customer: {{ company.id }},
|
||||||
|
},
|
||||||
|
follow: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -4,7 +4,7 @@
|
|||||||
- model: order.purchaseorder
|
- model: order.purchaseorder
|
||||||
pk: 1
|
pk: 1
|
||||||
fields:
|
fields:
|
||||||
reference: 0001
|
reference: '0001'
|
||||||
description: "Ordering some screws"
|
description: "Ordering some screws"
|
||||||
supplier: 1
|
supplier: 1
|
||||||
|
|
||||||
@ -12,7 +12,7 @@
|
|||||||
- model: order.purchaseorder
|
- model: order.purchaseorder
|
||||||
pk: 2
|
pk: 2
|
||||||
fields:
|
fields:
|
||||||
reference: 0002
|
reference: '0002'
|
||||||
description: "Ordering some more screws"
|
description: "Ordering some more screws"
|
||||||
supplier: 3
|
supplier: 3
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ from stock import models as stock_models
|
|||||||
from company.models import Company, SupplierPart
|
from company.models import Company, SupplierPart
|
||||||
|
|
||||||
from InvenTree.fields import RoundingDecimalField
|
from InvenTree.fields import RoundingDecimalField
|
||||||
from InvenTree.helpers import decimal2string
|
from InvenTree.helpers import decimal2string, increment
|
||||||
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus
|
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus
|
||||||
from InvenTree.models import InvenTreeAttachment
|
from InvenTree.models import InvenTreeAttachment
|
||||||
|
|
||||||
@ -49,6 +49,43 @@ class Order(models.Model):
|
|||||||
|
|
||||||
ORDER_PREFIX = ""
|
ORDER_PREFIX = ""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getNextOrderNumber(cls):
|
||||||
|
"""
|
||||||
|
Try to predict the next order-number
|
||||||
|
"""
|
||||||
|
|
||||||
|
if cls.objects.count() == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# We will assume that the latest pk has the highest PO number
|
||||||
|
order = cls.objects.last()
|
||||||
|
ref = order.reference
|
||||||
|
|
||||||
|
if not ref:
|
||||||
|
return None
|
||||||
|
|
||||||
|
tries = set()
|
||||||
|
|
||||||
|
tries.add(ref)
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
new_ref = increment(ref)
|
||||||
|
|
||||||
|
if new_ref in tries:
|
||||||
|
# We are in a looping situation - simply return the original one
|
||||||
|
return ref
|
||||||
|
|
||||||
|
# Check that the new ref does not exist in the database
|
||||||
|
if cls.objects.filter(reference=new_ref).exists():
|
||||||
|
tries.add(new_ref)
|
||||||
|
new_ref = increment(new_ref)
|
||||||
|
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
return new_ref
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
el = []
|
el = []
|
||||||
|
|
||||||
|
@ -31,11 +31,17 @@ class OrderTest(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(order.get_absolute_url(), '/order/purchase-order/1/')
|
self.assertEqual(order.get_absolute_url(), '/order/purchase-order/1/')
|
||||||
|
|
||||||
self.assertEqual(str(order), 'PO 1 - ACME')
|
self.assertEqual(str(order), 'PO 0001 - ACME')
|
||||||
|
|
||||||
line = PurchaseOrderLineItem.objects.get(pk=1)
|
line = PurchaseOrderLineItem.objects.get(pk=1)
|
||||||
|
|
||||||
self.assertEqual(str(line), "100 x ACME0001 from ACME (for PO 1 - ACME)")
|
self.assertEqual(str(line), "100 x ACME0001 from ACME (for PO 0001 - ACME)")
|
||||||
|
|
||||||
|
def test_increment(self):
|
||||||
|
|
||||||
|
next_ref = PurchaseOrder.getNextOrderNumber()
|
||||||
|
|
||||||
|
self.assertEqual(next_ref, '0003')
|
||||||
|
|
||||||
def test_on_order(self):
|
def test_on_order(self):
|
||||||
""" There should be 3 separate items on order for the M2x4 LPHS part """
|
""" There should be 3 separate items on order for the M2x4 LPHS part """
|
||||||
|
@ -29,7 +29,7 @@ from . import forms as order_forms
|
|||||||
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
||||||
from InvenTree.helpers import DownloadFile, str2bool
|
from InvenTree.helpers import DownloadFile, str2bool
|
||||||
|
|
||||||
from InvenTree.status_codes import PurchaseOrderStatus, StockStatus
|
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -290,6 +290,7 @@ class PurchaseOrderCreate(AjaxCreateView):
|
|||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
initials = super().get_initial().copy()
|
initials = super().get_initial().copy()
|
||||||
|
|
||||||
|
initials['reference'] = PurchaseOrder.getNextOrderNumber()
|
||||||
initials['status'] = PurchaseOrderStatus.PENDING
|
initials['status'] = PurchaseOrderStatus.PENDING
|
||||||
|
|
||||||
supplier_id = self.request.GET.get('supplier', None)
|
supplier_id = self.request.GET.get('supplier', None)
|
||||||
@ -320,7 +321,8 @@ class SalesOrderCreate(AjaxCreateView):
|
|||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
initials = super().get_initial().copy()
|
initials = super().get_initial().copy()
|
||||||
|
|
||||||
initials['status'] = PurchaseOrderStatus.PENDING
|
initials['reference'] = SalesOrder.getNextOrderNumber()
|
||||||
|
initials['status'] = SalesOrderStatus.PENDING
|
||||||
|
|
||||||
customer_id = self.request.GET.get('customer', None)
|
customer_id = self.request.GET.get('customer', None)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user