From 41eff97c7ce92097a50c735d385aacf63e9ef039 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 14 May 2020 14:59:49 +1000 Subject: [PATCH 1/7] Add function to "increment" a number or a number-like string - Observe string width - Keep prefix if one exists --- InvenTree/InvenTree/helpers.py | 53 ++++++++++++++++++++++++++++++++++ InvenTree/InvenTree/tests.py | 23 +++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index b9a4d73740..8c5d59181d 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -120,6 +120,59 @@ def normalize(d): 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): """ Format a Decimal number as a string, diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 203748de3e..877adab919 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -108,6 +108,29 @@ class TestQuoteWrap(TestCase): 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): """ Tests for barcode string creation """ From cebfe9a30f0cecb350efbdc238aa17a8371dab6f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 14 May 2020 15:00:00 +1000 Subject: [PATCH 2/7] Function to predict the next purchase order number --- InvenTree/order/fixtures/order.yaml | 4 +-- InvenTree/order/models.py | 40 ++++++++++++++++++++++++++++- InvenTree/order/tests.py | 6 +++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/InvenTree/order/fixtures/order.yaml b/InvenTree/order/fixtures/order.yaml index 0ba4bdbeb5..ed4eae84eb 100644 --- a/InvenTree/order/fixtures/order.yaml +++ b/InvenTree/order/fixtures/order.yaml @@ -4,7 +4,7 @@ - model: order.purchaseorder pk: 1 fields: - reference: 0001 + reference: '0001' description: "Ordering some screws" supplier: 1 @@ -12,7 +12,7 @@ - model: order.purchaseorder pk: 2 fields: - reference: 0002 + reference: '0002' description: "Ordering some more screws" supplier: 3 diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 56da6c783d..5e6c5e521d 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -24,7 +24,7 @@ from stock import models as stock_models from company.models import Company, SupplierPart 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.models import InvenTreeAttachment @@ -96,6 +96,44 @@ class PurchaseOrder(Order): ORDER_PREFIX = "PO" + @classmethod + def getNextOrderNumber(cls): + """ + Try to predict the next order-number + """ + + if PurchaseOrder.objects.count() == 0: + return None + + # We will assume that the latest pk has the highest PO number + order = PurchaseOrder.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 PurchaseOrder.objects.filter(reference=new_ref).exists(): + tries.add(new_ref) + new_ref = increment(new_ref) + + else: + break + + return new_ref + + def __str__(self): return "PO {ref} - {company}".format(ref=self.reference, company=self.supplier.name) diff --git a/InvenTree/order/tests.py b/InvenTree/order/tests.py index ca24b9586d..89c39db8f5 100644 --- a/InvenTree/order/tests.py +++ b/InvenTree/order/tests.py @@ -37,6 +37,12 @@ class OrderTest(TestCase): self.assertEqual(str(line), "100 x ACME0001 from ACME (for PO 1 - ACME)") + def test_increment(self): + + next_ref = PurchaseOrder.getNextOrderNumber() + + self.assertEqual(next_ref, '0003') + def test_on_order(self): """ There should be 3 separate items on order for the M2x4 LPHS part """ From 08903f357ec70e1d2e4b56a40f9c5488dc8f3965 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 14 May 2020 15:02:46 +1000 Subject: [PATCH 3/7] Auto-increment the purchase-order number when creating a new one --- InvenTree/order/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 41a5b231a7..c8a78a8fa9 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -290,6 +290,7 @@ class PurchaseOrderCreate(AjaxCreateView): def get_initial(self): initials = super().get_initial().copy() + initials['reference'] = PurchaseOrder.getNextOrderNumber() initials['status'] = PurchaseOrderStatus.PENDING supplier_id = self.request.GET.get('supplier', None) From b619f260746f254f05254b3817d55b7eee2d2ee6 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 14 May 2020 15:05:55 +1000 Subject: [PATCH 4/7] Auto-increment sales order reference number --- InvenTree/order/models.py | 75 +++++++++++++++++++-------------------- InvenTree/order/views.py | 5 +-- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 5e6c5e521d..2ebc6a6793 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -49,6 +49,43 @@ class Order(models.Model): 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): el = [] @@ -96,44 +133,6 @@ class PurchaseOrder(Order): ORDER_PREFIX = "PO" - @classmethod - def getNextOrderNumber(cls): - """ - Try to predict the next order-number - """ - - if PurchaseOrder.objects.count() == 0: - return None - - # We will assume that the latest pk has the highest PO number - order = PurchaseOrder.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 PurchaseOrder.objects.filter(reference=new_ref).exists(): - tries.add(new_ref) - new_ref = increment(new_ref) - - else: - break - - return new_ref - - def __str__(self): return "PO {ref} - {company}".format(ref=self.reference, company=self.supplier.name) diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index c8a78a8fa9..0585c34cd7 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -29,7 +29,7 @@ from . import forms as order_forms from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView 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__) @@ -321,7 +321,8 @@ class SalesOrderCreate(AjaxCreateView): def get_initial(self): 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) From d3758981b43b054946ec783562f05f49d92d4b22 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 14 May 2020 15:09:31 +1000 Subject: [PATCH 5/7] Fix "New Sales Order" button in Customer detail view --- InvenTree/company/templates/company/sales_orders.html | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/InvenTree/company/templates/company/sales_orders.html b/InvenTree/company/templates/company/sales_orders.html index facfbce189..0b64bed2f5 100644 --- a/InvenTree/company/templates/company/sales_orders.html +++ b/InvenTree/company/templates/company/sales_orders.html @@ -33,9 +33,16 @@ } }); - $("#new-sales-order").click(function() { - // TODO - Create a new sales order + launchModalForm( + "{% url 'so-create' %}", + { + data: { + customer: {{ company.id }}, + }, + follow: true, + }, + ); }); {% endblock %} \ No newline at end of file From 6175c5408c9a9b2e6f848c4ecceae13945521e6c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 14 May 2020 15:10:48 +1000 Subject: [PATCH 6/7] Javascript indent cleanup --- .../templates/company/company_base.html | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/InvenTree/company/templates/company/company_base.html b/InvenTree/company/templates/company/company_base.html index da73a7b7b3..9de0e9e764 100644 --- a/InvenTree/company/templates/company/company_base.html +++ b/InvenTree/company/templates/company/company_base.html @@ -85,10 +85,10 @@ InvenTree | {% trans "Company" %} - {{ company.name }} $('#company-edit').click(function() { launchModalForm( - "{% url 'company-edit' company.id %}", - { - reload: true - }); + "{% url 'company-edit' company.id %}", + { + reload: true + }); }); $("#company-order-2").click(function() { @@ -104,10 +104,10 @@ InvenTree | {% trans "Company" %} - {{ company.name }} $('#company-delete').click(function() { launchModalForm( - "{% url 'company-delete' company.id %}", - { - redirect: "{% url 'company-index' %}" - }); + "{% url 'company-delete' company.id %}", + { + redirect: "{% url 'company-index' %}" + }); }); enableDragAndDrop( @@ -123,11 +123,11 @@ InvenTree | {% trans "Company" %} - {{ company.name }} $("#company-thumb").click(function() { launchModalForm( - "{% url 'company-image' company.id %}", - { - reload: true - } - ); + "{% url 'company-image' company.id %}", + { + reload: true + } + ); }); {% endblock %} \ No newline at end of file From 5167f542686dcdceb0f05798e3afd6918982c48d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 14 May 2020 15:20:40 +1000 Subject: [PATCH 7/7] Fix unit tests --- InvenTree/order/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/order/tests.py b/InvenTree/order/tests.py index 89c39db8f5..7b118dc60e 100644 --- a/InvenTree/order/tests.py +++ b/InvenTree/order/tests.py @@ -31,11 +31,11 @@ class OrderTest(TestCase): 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) - 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):