mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-14 19:15:41 +00:00
Working towards part matching
This commit is contained in:
@ -18,9 +18,9 @@
|
|||||||
{% endblock form_alert %}
|
{% endblock form_alert %}
|
||||||
|
|
||||||
{% block form_buttons_top %}
|
{% block form_buttons_top %}
|
||||||
{% if wizard.steps.prev %}
|
{% comment %} {% if wizard.steps.prev %}
|
||||||
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-default">{% trans "Previous Step" %}</button>
|
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-default">{% trans "Previous Step" %}</button>
|
||||||
{% endif %}
|
{% endif %} {% endcomment %}
|
||||||
<button type="submit" class="save btn btn-default">{% trans "Submit Selections" %}</button>
|
<button type="submit" class="save btn btn-default">{% trans "Submit Selections" %}</button>
|
||||||
{% endblock form_buttons_top %}
|
{% endblock form_buttons_top %}
|
||||||
|
|
||||||
|
@ -12,9 +12,9 @@
|
|||||||
{% endblock form_alert %}
|
{% endblock form_alert %}
|
||||||
|
|
||||||
{% block form_buttons_top %}
|
{% block form_buttons_top %}
|
||||||
{% if wizard.steps.prev %}
|
{% comment %} {% if wizard.steps.prev %}
|
||||||
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-default">{% trans "Previous Step" %}</button>
|
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-default">{% trans "Previous Step" %}</button>
|
||||||
{% endif %}
|
{% endif %} {% endcomment %}
|
||||||
<button type="submit" class="save btn btn-default">{% trans "Submit Selections" %}</button>
|
<button type="submit" class="save btn btn-default">{% trans "Submit Selections" %}</button>
|
||||||
{% endblock form_buttons_top %}
|
{% endblock form_buttons_top %}
|
||||||
|
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
<th>{% trans "Row" %}</th>
|
<th>{% trans "Row" %}</th>
|
||||||
<th>{% trans "Select Part" %}</th>
|
<th>{% trans "Select Part" %}</th>
|
||||||
{% for col in bom_columns %}
|
{% for col in columns %}
|
||||||
<th>
|
<th>
|
||||||
<input type='hidden' name='col_name_{{ forloop.counter0 }}' value='{{ col.name }}'/>
|
<input type='hidden' name='col_name_{{ forloop.counter0 }}' value='{{ col.name }}'/>
|
||||||
<input type='hidden' name='col_guess_{{ forloop.counter0 }}' value='{{ col.guess }}'/>
|
<input type='hidden' name='col_guess_{{ forloop.counter0 }}' value='{{ col.guess }}'/>
|
||||||
@ -39,7 +39,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in bom_rows %}
|
{% for row in rows %}
|
||||||
<tr {% if row.errors %} style='background: #ffeaea;'{% endif %} part-name='{{ row.part_name }}' part-description='{{ row.description }}' part-select='#select_part_{{ row.index }}'>
|
<tr {% if row.errors %} style='background: #ffeaea;'{% endif %} part-name='{{ row.part_name }}' part-description='{{ row.description }}' part-select='#select_part_{{ row.index }}'>
|
||||||
<td>
|
<td>
|
||||||
<button class='btn btn-default btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ forloop.counter }}' style='display: inline; float: right;' title='{% trans "Remove row" %}'>
|
<button class='btn btn-default btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ forloop.counter }}' style='display: inline; float: right;' title='{% trans "Remove row" %}'>
|
||||||
|
@ -23,7 +23,7 @@ from .models import SalesOrder, SalesOrderLineItem, SalesOrderAttachment
|
|||||||
from .models import SalesOrderAllocation
|
from .models import SalesOrderAllocation
|
||||||
from .admin import POLineItemResource
|
from .admin import POLineItemResource
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
from company.models import Company, SupplierPart
|
from company.models import Company, ManufacturerPart, SupplierPart
|
||||||
from stock.models import StockItem, StockLocation
|
from stock.models import StockItem, StockLocation
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
|
|
||||||
@ -745,6 +745,87 @@ class PurchaseOrderUpload(MultiStepFormView):
|
|||||||
for col in self.file_manager.PART_MATCH_HEADERS:
|
for col in self.file_manager.PART_MATCH_HEADERS:
|
||||||
self.missing_columns.append(col)
|
self.missing_columns.append(col)
|
||||||
|
|
||||||
|
def getColumnIndex(self, name):
|
||||||
|
""" Return the index of the column with the given name.
|
||||||
|
It named column is not found, return -1
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
idx = list(self.column_selections.values()).index(name)
|
||||||
|
except ValueError:
|
||||||
|
idx = -1
|
||||||
|
|
||||||
|
return idx
|
||||||
|
|
||||||
|
def preFillSelections(self):
|
||||||
|
""" Once data columns have been selected, attempt to pre-select the proper data from the database.
|
||||||
|
This function is called once the field selection has been validated.
|
||||||
|
The pre-fill data are then passed through to the part selection form.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Fields prefixed with "Part_" can be used to do "smart matching" against Part objects in the database
|
||||||
|
s_idx = self.getColumnIndex('Supplier_SKU')
|
||||||
|
m_idx = self.getColumnIndex('Manufacturer_MPN')
|
||||||
|
q_idx = self.getColumnIndex('Quantity')
|
||||||
|
p_idx = self.getColumnIndex('Unit_Price')
|
||||||
|
e_idx = self.getColumnIndex('Extended_Price')
|
||||||
|
|
||||||
|
for row in self.rows:
|
||||||
|
|
||||||
|
# Initially use a quantity of zero
|
||||||
|
quantity = Decimal(0)
|
||||||
|
|
||||||
|
# Initially we do not have a part to reference
|
||||||
|
exact_match_part = None
|
||||||
|
|
||||||
|
# A list of potential Part matches
|
||||||
|
part_options = SupplierPart.objects.all()
|
||||||
|
|
||||||
|
# Check if there is a column corresponding to "quantity"
|
||||||
|
if q_idx >= 0:
|
||||||
|
q_val = row['data'][q_idx]
|
||||||
|
|
||||||
|
if q_val:
|
||||||
|
try:
|
||||||
|
# Attempt to extract a valid quantity from the field
|
||||||
|
quantity = Decimal(q_val)
|
||||||
|
except (ValueError, InvalidOperation):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Store the 'quantity' value
|
||||||
|
row['quantity'] = quantity
|
||||||
|
|
||||||
|
# Check if there is a column corresponding to "Supplier SKU"
|
||||||
|
if s_idx >= 0:
|
||||||
|
row['part_sku'] = row['data'][s_idx]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Attempt SupplierPart lookup based on SKU value
|
||||||
|
exact_match_part = SupplierPart.objects.get(SKU=row['part_sku'])
|
||||||
|
except (ValueError, SupplierPart.DoesNotExist):
|
||||||
|
exact_match_part = None
|
||||||
|
|
||||||
|
# Check if there is a column corresponding to "Manufacturer MPN"
|
||||||
|
if m_idx >= 0:
|
||||||
|
row['part_mpn'] = row['data'][m_idx]
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# # Attempt ManufacturerPart lookup based on MPN value
|
||||||
|
# exact_match_part = ManufacturerPart.objects.get(MPN=row['part_mpn'])
|
||||||
|
# except (ValueError, ManufacturerPart.DoesNotExist):
|
||||||
|
# exact_match_part = None
|
||||||
|
|
||||||
|
# Supply list of part options for each row, sorted by how closely they match the part name
|
||||||
|
row['part_options'] = part_options
|
||||||
|
|
||||||
|
# Unless found, the 'part_match' is blank
|
||||||
|
row['part_match'] = None
|
||||||
|
|
||||||
|
if exact_match_part:
|
||||||
|
# If there is an exact match based on SKU or MPN, use that
|
||||||
|
row['part_match'] = exact_match_part
|
||||||
|
|
||||||
|
|
||||||
def getFileManager(self, form=None):
|
def getFileManager(self, form=None):
|
||||||
""" Create FileManager instance from upload file """
|
""" Create FileManager instance from upload file """
|
||||||
|
|
||||||
@ -793,6 +874,10 @@ class PurchaseOrderUpload(MultiStepFormView):
|
|||||||
return valid
|
return valid
|
||||||
|
|
||||||
def handlePartSelection(self, form):
|
def handlePartSelection(self, form):
|
||||||
|
|
||||||
|
# Extract form data
|
||||||
|
self.getTableDataFromForm(form.data)
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_form_step_data(self, form):
|
def get_form_step_data(self, form):
|
||||||
@ -807,8 +892,9 @@ class PurchaseOrderUpload(MultiStepFormView):
|
|||||||
# Process steps
|
# Process steps
|
||||||
if self.steps.current == 'upload':
|
if self.steps.current == 'upload':
|
||||||
self.setupFieldSelection(form)
|
self.setupFieldSelection(form)
|
||||||
# elif self.steps.current == 'fields':
|
elif self.steps.current == 'fields':
|
||||||
# self.handleFieldSelection(form)
|
self.preFillSelections()
|
||||||
|
print(self.rows)
|
||||||
# elif self.steps.current == 'parts':
|
# elif self.steps.current == 'parts':
|
||||||
# self.handlePartSelection(form)
|
# self.handlePartSelection(form)
|
||||||
|
|
||||||
@ -831,7 +917,7 @@ class PurchaseOrderUpload(MultiStepFormView):
|
|||||||
|
|
||||||
if not valid:
|
if not valid:
|
||||||
form.add_error(None, 'Fields matching failed')
|
form.add_error(None, 'Fields matching failed')
|
||||||
# Set headers
|
# Reload headers
|
||||||
self.headers = self.file_manager.HEADERS
|
self.headers = self.file_manager.HEADERS
|
||||||
|
|
||||||
elif step == 'parts':
|
elif step == 'parts':
|
||||||
|
Reference in New Issue
Block a user