mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 05:05:42 +00:00 
			
		
		
		
	Barcode Refactor (#3640)
* define a simple model mixin class for barcode * Adds generic function for assigning a barcode to a model instance * StockItem model now implements the BarcodeMixin class * Implement simple unit tests for new code * Fix unit tests * Data migration for uid field * Remove references to old 'uid' field * Migration for removing old uid field from StockItem model * Bump API version * Change lookup_barcode to be a classmethod * Change barcode_model_type to be a class method * Cleanup for generic barcode scan and assign API: - Raise ValidationError as appropriate - Improved unit testing - Groundwork for future generic implementation * Further unit tests for barcode scanning * Adjust error messages for compatibility * Unit test fix * Fix hash_barcode function - Add unit tests to ensure it produces the same results as before the refactor * Add BarcodeMixin to Part model * Remove old format_barcode function from Part model * Further fixes for unit tests * Add support for assigning arbitrary barcode to Part instance - Simplify barcode API - Add more unit tests * More unit test fixes * Update unit test * Adds generic endpoint for unassigning barcode data * Update web dialog for unlinking a barcode * Template cleanup * Add Barcode mixin to StockLocation class * Add some simple unit tests for new model mixin * Support assigning / unassigning barcodes for StockLocation * remove failing outdated test * Update template to integrate new barcode support for StockLocation * Add BarcodeMixin to SupplierPart model * Adds QR code view for SupplierPart * Major simplification of barcode API endpoints - Separate existing barcode plugin into two separate classes - Simplify and consolidate the response from barcode scanning - Update unit testing * Yet more unit test fixes * Yet yet more unit test fixes
This commit is contained in:
		
							
								
								
									
										23
									
								
								InvenTree/company/migrations/0048_auto_20220913_0312.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								InvenTree/company/migrations/0048_auto_20220913_0312.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| # Generated by Django 3.2.15 on 2022-09-13 03:12 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('company', '0047_supplierpart_pack_size'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='supplierpart', | ||||
|             name='barcode_data', | ||||
|             field=models.CharField(blank=True, help_text='Third party barcode data', max_length=500, verbose_name='Barcode Data'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='supplierpart', | ||||
|             name='barcode_hash', | ||||
|             field=models.CharField(blank=True, help_text='Unique hash of barcode data', max_length=128, verbose_name='Barcode Hash'), | ||||
|         ), | ||||
|     ] | ||||
| @@ -21,7 +21,7 @@ import InvenTree.helpers | ||||
| import InvenTree.validators | ||||
| from common.settings import currency_code_default | ||||
| from InvenTree.fields import InvenTreeURLField, RoundingDecimalField | ||||
| from InvenTree.models import InvenTreeAttachment | ||||
| from InvenTree.models import InvenTreeAttachment, InvenTreeBarcodeMixin | ||||
| from InvenTree.status_codes import PurchaseOrderStatus | ||||
|  | ||||
|  | ||||
| @@ -391,7 +391,7 @@ class SupplierPartManager(models.Manager): | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class SupplierPart(models.Model): | ||||
| class SupplierPart(InvenTreeBarcodeMixin, models.Model): | ||||
|     """Represents a unique part as provided by a Supplier Each SupplierPart is identified by a SKU (Supplier Part Number) Each SupplierPart is also linked to a Part or ManufacturerPart object. A Part may be available from multiple suppliers. | ||||
|  | ||||
|     Attributes: | ||||
|   | ||||
| @@ -30,6 +30,22 @@ | ||||
| {% url 'admin:company_supplierpart_change' part.pk as url %} | ||||
| {% include "admin_button.html" with url=url %} | ||||
| {% endif %} | ||||
| {% if barcodes %} | ||||
| <!-- Barcode actions menu --> | ||||
| <div class='btn-group' role='group'> | ||||
|     <button id='barcode-options' title='{% trans "Barcode actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'> | ||||
|         <span class='fas fa-qrcode'></span> <span class='caret'></span> | ||||
|     </button> | ||||
|     <ul class='dropdown-menu' role='menu'> | ||||
|         <li><a class='dropdown-item' href='#' id='show-qr-code'><span class='fas fa-qrcode'></span> {% trans "Show QR Code" %}</a></li> | ||||
|         {% if part.barcode_hash %} | ||||
|         <li><a class='dropdown-item' href='#' id='barcode-unlink'><span class='fas fa-unlink'></span> {% trans "Unlink Barcode" %}</a></li> | ||||
|         {% else %} | ||||
|         <li><a class='dropdown-item' href='#' id='barcode-link'><span class='fas fa-link'></span> {% trans "Link Barcode" %}</a></li> | ||||
|         {% endif %} | ||||
|     </ul> | ||||
| </div> | ||||
| {% endif %} | ||||
| {% if roles.purchase_order.change or roles.purchase_order.add or roles.purchase_order.delete %} | ||||
| <div class='btn-group'> | ||||
|     <button id='supplier-part-actions' title='{% trans "Supplier part actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'> | ||||
| @@ -100,6 +116,13 @@ src="{% static 'img/blank_image.png' %}" | ||||
|         <td>{% decimal part.available %}<span class='badge bg-dark rounded-pill float-right'>{% render_date part.availability_updated %}</span></td> | ||||
|     </tr> | ||||
|     {% endif %} | ||||
|     {% if part.barcode_hash %} | ||||
|     <tr> | ||||
|         <td><span class='fas fa-barcode'></span></td> | ||||
|         <td>{% trans "Barcode Identifier" %}</td> | ||||
|         <td {% if part.barcode_data %}title='{{ part.barcode_data }}'{% endif %}>{{ part.barcode_hash }}</td> | ||||
|     </tr> | ||||
|     {% endif %} | ||||
| </table> | ||||
|  | ||||
| {% endblock details %} | ||||
| @@ -241,6 +264,33 @@ src="{% static 'img/blank_image.png' %}" | ||||
| {% block js_ready %} | ||||
| {{ block.super }} | ||||
|  | ||||
| {% if barcodes %} | ||||
|  | ||||
| $("#show-qr-code").click(function() { | ||||
|     launchModalForm("{% url 'supplier-part-qr' part.pk %}", | ||||
|     { | ||||
|         no_post: true, | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| $("#barcode-link").click(function() { | ||||
|     linkBarcodeDialog( | ||||
|         { | ||||
|             supplierpart: {{ part.pk }}, | ||||
|         }, | ||||
|         { | ||||
|             title: '{% trans "Link Barcode to Supplier Part" %}', | ||||
|         } | ||||
|     ); | ||||
| }); | ||||
|  | ||||
| $("#barcode-unlink").click(function() { | ||||
|     unlinkBarcode({ | ||||
|         supplierpart: {{ part.pk }}, | ||||
|     }); | ||||
| }); | ||||
| {% endif %} | ||||
|  | ||||
| function reloadPriceBreaks() { | ||||
|     $("#price-break-table").bootstrapTable("refresh"); | ||||
| } | ||||
|   | ||||
| @@ -25,5 +25,10 @@ manufacturer_part_urls = [ | ||||
| ] | ||||
|  | ||||
| supplier_part_urls = [ | ||||
|     re_path(r'^(?P<pk>\d+)/', views.SupplierPartDetail.as_view(template_name='company/supplier_part.html'), name='supplier-part-detail'), | ||||
|     re_path(r'^(?P<pk>\d+)/', include([ | ||||
|         re_path('^qr_code/?', views.SupplierPartQRCode.as_view(), name='supplier-part-qr'), | ||||
|         re_path('^.*$', views.SupplierPartDetail.as_view(template_name='company/supplier_part.html'), name='supplier-part-detail'), | ||||
|     ])) | ||||
|  | ||||
|  | ||||
| ] | ||||
|   | ||||
| @@ -4,7 +4,7 @@ from django.urls import reverse | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.views.generic import DetailView, ListView | ||||
|  | ||||
| from InvenTree.views import InvenTreeRoleMixin | ||||
| from InvenTree.views import InvenTreeRoleMixin, QRCodeView | ||||
| from plugin.views import InvenTreePluginViewMixin | ||||
|  | ||||
| from .models import Company, ManufacturerPart, SupplierPart | ||||
| @@ -112,3 +112,18 @@ class SupplierPartDetail(InvenTreePluginViewMixin, DetailView): | ||||
|     context_object_name = 'part' | ||||
|     queryset = SupplierPart.objects.all() | ||||
|     permission_required = 'purchase_order.view' | ||||
|  | ||||
|  | ||||
| class SupplierPartQRCode(QRCodeView): | ||||
|     """View for displaying a QR code for a StockItem object.""" | ||||
|  | ||||
|     ajax_form_title = _("Stock Item QR Code") | ||||
|     role_required = 'stock.view' | ||||
|  | ||||
|     def get_qr_data(self): | ||||
|         """Generate QR code data for the StockItem.""" | ||||
|         try: | ||||
|             part = SupplierPart.objects.get(id=self.pk) | ||||
|             return part.format_barcode() | ||||
|         except SupplierPart.DoesNotExist: | ||||
|             return None | ||||
|   | ||||
		Reference in New Issue
	
	Block a user