mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Now displaying item match table
This commit is contained in:
parent
4f942fd9f7
commit
6e269ae41a
@ -20,8 +20,8 @@ class FileManager:
|
|||||||
# Fields which are absolutely necessary for valid upload
|
# Fields which are absolutely necessary for valid upload
|
||||||
REQUIRED_HEADERS = []
|
REQUIRED_HEADERS = []
|
||||||
|
|
||||||
# Fields which are used for part matching (only one of them is needed)
|
# Fields which are used for item matching (only one of them is needed)
|
||||||
PART_MATCH_HEADERS = []
|
ITEM_MATCH_HEADERS = []
|
||||||
|
|
||||||
# Fields which would be helpful but are not required
|
# Fields which would be helpful but are not required
|
||||||
OPTIONAL_HEADERS = []
|
OPTIONAL_HEADERS = []
|
||||||
@ -83,7 +83,7 @@ class FileManager:
|
|||||||
def update_headers(self):
|
def update_headers(self):
|
||||||
""" Update headers """
|
""" Update headers """
|
||||||
|
|
||||||
self.HEADERS = self.REQUIRED_HEADERS + self.PART_MATCH_HEADERS + self.OPTIONAL_HEADERS
|
self.HEADERS = self.REQUIRED_HEADERS + self.ITEM_MATCH_HEADERS + self.OPTIONAL_HEADERS
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
""" Setup headers depending on the file name """
|
""" Setup headers depending on the file name """
|
||||||
@ -96,7 +96,7 @@ class FileManager:
|
|||||||
'Quantity',
|
'Quantity',
|
||||||
]
|
]
|
||||||
|
|
||||||
self.PART_MATCH_HEADERS = [
|
self.ITEM_MATCH_HEADERS = [
|
||||||
'Manufacturer_MPN',
|
'Manufacturer_MPN',
|
||||||
'Supplier_SKU',
|
'Supplier_SKU',
|
||||||
]
|
]
|
||||||
|
@ -119,18 +119,40 @@ class MatchItem(forms.Form):
|
|||||||
# Item selection
|
# Item selection
|
||||||
for row in row_data:
|
for row in row_data:
|
||||||
for col in row['data']:
|
for col in row['data']:
|
||||||
print(f'{row["index"]=} | {col["column"]["guess"]=}')
|
if col['column']['guess'] in file_manager.REQUIRED_HEADERS:
|
||||||
if col['column']['guess']:
|
field_name = col['column']['guess'].lower() + '-' + str(row['index'] - 1)
|
||||||
if col['column']['guess'] in file_manager.PART_MATCH_HEADERS:
|
if 'quantity' in col['column']['guess'].lower():
|
||||||
|
self.fields[field_name] = forms.CharField(
|
||||||
|
required=True,
|
||||||
|
widget=forms.NumberInput(attrs={
|
||||||
|
'name': 'quantity' + str(row['index']),
|
||||||
|
'class': 'numberinput',
|
||||||
|
'type': 'number',
|
||||||
|
'min': '1',
|
||||||
|
'step': 'any',
|
||||||
|
'value': row['quantity'],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.fields[field_name] = forms.Input(
|
||||||
|
required=True,
|
||||||
|
widget=forms.Select(attrs={
|
||||||
|
})
|
||||||
|
)
|
||||||
|
elif col['column']['guess'] in file_manager.ITEM_MATCH_HEADERS:
|
||||||
|
print(f'{row["index"]=} | {col["column"]["guess"]=} | {row.get("item_match", "No Match")}')
|
||||||
|
|
||||||
# Get item options
|
# Get item options
|
||||||
item_options = row['item_options']
|
item_options = [(option.id, option) for option in row['item_options']]
|
||||||
# Get item match
|
# Get item match
|
||||||
item_match = row['item_match']
|
item_match = row['item_match']
|
||||||
|
|
||||||
field_name = col['column']['guess'].lower() + '_' + str(row['index'])
|
field_name = col['column']['guess'].lower() + '-' + str(row['index'] - 1)
|
||||||
self.fields[field_name] = forms.ChoiceField(
|
self.fields[field_name] = forms.ChoiceField(
|
||||||
choices=item_options,
|
choices=[('', '-' * 10)] + item_options,
|
||||||
required=True,
|
required=True,
|
||||||
|
widget=forms.Select(attrs={'class': 'bomselect'})
|
||||||
)
|
)
|
||||||
if item_match:
|
if item_match:
|
||||||
self.fields[field_name].initial = item_match
|
print(f'{item_match=}')
|
||||||
|
self.fields[field_name].initial = item_match.id
|
||||||
|
@ -23,7 +23,7 @@ from . import models
|
|||||||
from . import forms
|
from . import forms
|
||||||
from .files import FileManager
|
from .files import FileManager
|
||||||
|
|
||||||
from part.models import SupplierPart
|
from company.models import ManufacturerPart, SupplierPart
|
||||||
|
|
||||||
|
|
||||||
class SettingEdit(AjaxUpdateView):
|
class SettingEdit(AjaxUpdateView):
|
||||||
@ -195,12 +195,41 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
def get_context_data(self, form, **kwargs):
|
def get_context_data(self, form, **kwargs):
|
||||||
context = super().get_context_data(form=form, **kwargs)
|
context = super().get_context_data(form=form, **kwargs)
|
||||||
|
|
||||||
if self.steps.current == 'fields':
|
if self.steps.current == 'fields' or self.steps.current == 'items':
|
||||||
# Get columns and row data
|
# Get columns and row data
|
||||||
columns = self.file_manager.columns()
|
columns = self.file_manager.columns()
|
||||||
rows = self.file_manager.rows()
|
rows = self.file_manager.rows()
|
||||||
|
|
||||||
|
|
||||||
|
key_item_select = ''
|
||||||
|
key_quantity_select = ''
|
||||||
|
if self.steps.current == 'items':
|
||||||
|
# Get file manager
|
||||||
|
self.getFileManager()
|
||||||
|
# Find column key for item selection
|
||||||
|
for item in self.file_manager.ITEM_MATCH_HEADERS:
|
||||||
|
item = item.lower()
|
||||||
|
for key in form.fields.keys():
|
||||||
|
print(f'{item=} is in {key=} ?')
|
||||||
|
if item in key:
|
||||||
|
key_item_select = item
|
||||||
|
break
|
||||||
|
break
|
||||||
|
|
||||||
|
# Find column key for quantity selection
|
||||||
|
key_quantity_select = 'quantity'
|
||||||
|
|
||||||
# Optimize for template
|
# Optimize for template
|
||||||
for row in rows:
|
for row in rows:
|
||||||
|
|
||||||
|
# Add item select field
|
||||||
|
if key_item_select:
|
||||||
|
row['item_select'] = key_item_select + '-' + str(row['index'])
|
||||||
|
print(f'{row["item_select"]}')
|
||||||
|
# Add quantity select field
|
||||||
|
if key_quantity_select:
|
||||||
|
row['quantity_select'] = key_quantity_select + '-' + str(row['index'])
|
||||||
|
|
||||||
row_data = row['data']
|
row_data = row['data']
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
@ -209,12 +238,16 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
data.append({
|
data.append({
|
||||||
'cell': item,
|
'cell': item,
|
||||||
'idx': idx,
|
'idx': idx,
|
||||||
'column': columns[idx]
|
'column': columns[idx],
|
||||||
})
|
})
|
||||||
|
|
||||||
row['data'] = data
|
row['data'] = data
|
||||||
|
|
||||||
|
print(f'\n{row=}')
|
||||||
|
|
||||||
context.update({'rows': rows})
|
context.update({'rows': rows})
|
||||||
|
if self.steps.current == 'items':
|
||||||
|
context.update({'columns': columns})
|
||||||
|
|
||||||
# Load extra context data
|
# Load extra context data
|
||||||
print(f'{self.extra_context_data=}')
|
print(f'{self.extra_context_data=}')
|
||||||
@ -393,14 +426,14 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
|
|
||||||
# Check that at least one of the part match field is present
|
# Check that at least one of the part match field is present
|
||||||
part_match_found = False
|
part_match_found = False
|
||||||
for col in self.file_manager.PART_MATCH_HEADERS:
|
for col in self.file_manager.ITEM_MATCH_HEADERS:
|
||||||
if col in self.column_selections.values():
|
if col in self.column_selections.values():
|
||||||
part_match_found = True
|
part_match_found = True
|
||||||
break
|
break
|
||||||
|
|
||||||
# If not, notify user
|
# If not, notify user
|
||||||
if not part_match_found:
|
if not part_match_found:
|
||||||
for col in self.file_manager.PART_MATCH_HEADERS:
|
for col in self.file_manager.ITEM_MATCH_HEADERS:
|
||||||
missing_columns.append(col)
|
missing_columns.append(col)
|
||||||
|
|
||||||
# Store extra context data
|
# Store extra context data
|
||||||
@ -425,15 +458,16 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
The pre-fill data are then passed through to the part selection form.
|
The pre-fill data are then passed through to the part selection form.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
match_supplier = False
|
||||||
|
match_manufacturer = False
|
||||||
|
|
||||||
# Fields prefixed with "Part_" can be used to do "smart matching" against Part objects in the database
|
# Fields prefixed with "Part_" can be used to do "smart matching" against Part objects in the database
|
||||||
q_idx = self.getColumnIndex('Quantity')
|
q_idx = self.getColumnIndex('Quantity')
|
||||||
s_idx = self.getColumnIndex('Supplier_SKU')
|
s_idx = self.getColumnIndex('Supplier_SKU')
|
||||||
# m_idx = self.getColumnIndex('Manufacturer_MPN')
|
m_idx = self.getColumnIndex('Manufacturer_MPN')
|
||||||
# p_idx = self.getColumnIndex('Unit_Price')
|
# p_idx = self.getColumnIndex('Unit_Price')
|
||||||
# e_idx = self.getColumnIndex('Extended_Price')
|
# e_idx = self.getColumnIndex('Extended_Price')
|
||||||
|
|
||||||
self.allowed_items = SupplierPart.objects.all()
|
|
||||||
|
|
||||||
for row in self.rows:
|
for row in self.rows:
|
||||||
|
|
||||||
# Initially use a quantity of zero
|
# Initially use a quantity of zero
|
||||||
@ -442,9 +476,6 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
# Initially we do not have a part to reference
|
# Initially we do not have a part to reference
|
||||||
exact_match_part = None
|
exact_match_part = None
|
||||||
|
|
||||||
# A list of potential Part matches
|
|
||||||
item_options = self.allowed_items
|
|
||||||
|
|
||||||
# Check if there is a column corresponding to "quantity"
|
# Check if there is a column corresponding to "quantity"
|
||||||
if q_idx >= 0:
|
if q_idx >= 0:
|
||||||
q_val = row['data'][q_idx]['cell']
|
q_val = row['data'][q_idx]['cell']
|
||||||
@ -464,7 +495,10 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
|
|
||||||
# Check if there is a column corresponding to "Supplier SKU"
|
# Check if there is a column corresponding to "Supplier SKU"
|
||||||
if s_idx >= 0:
|
if s_idx >= 0:
|
||||||
sku = row['data'][s_idx]
|
sku = row['data'][s_idx]['cell']
|
||||||
|
|
||||||
|
# Match for supplier
|
||||||
|
match_supplier = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Attempt SupplierPart lookup based on SKU value
|
# Attempt SupplierPart lookup based on SKU value
|
||||||
@ -473,17 +507,27 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
exact_match_part = None
|
exact_match_part = None
|
||||||
|
|
||||||
# Check if there is a column corresponding to "Manufacturer MPN"
|
# Check if there is a column corresponding to "Manufacturer MPN"
|
||||||
# if m_idx >= 0:
|
if m_idx >= 0:
|
||||||
# row['part_mpn'] = row['data'][m_idx]
|
mpn = row['data'][m_idx]['cell']
|
||||||
|
|
||||||
# try:
|
# Match for manufacturer
|
||||||
# # Attempt ManufacturerPart lookup based on MPN value
|
if not match_supplier:
|
||||||
# exact_match_part = ManufacturerPart.objects.get(MPN=row['part_mpn'])
|
match_manufacturer = True
|
||||||
# except (ValueError, ManufacturerPart.DoesNotExist):
|
|
||||||
# exact_match_part = None
|
try:
|
||||||
|
# Attempt ManufacturerPart lookup based on MPN value
|
||||||
|
exact_match_part = ManufacturerPart.objects.get(MPN__contains=mpn)
|
||||||
|
except (ValueError, ManufacturerPart.DoesNotExist, ManufacturerPart.MultipleObjectsReturned):
|
||||||
|
exact_match_part = None
|
||||||
|
|
||||||
|
# Check if matching for supplier or manufacturer parts
|
||||||
|
if match_supplier:
|
||||||
|
self.allowed_items = SupplierPart.objects.all()
|
||||||
|
elif match_manufacturer:
|
||||||
|
self.allowed_items = ManufacturerPart.objects.all()
|
||||||
|
|
||||||
# Supply list of part options for each row, sorted by how closely they match the part name
|
# Supply list of part options for each row, sorted by how closely they match the part name
|
||||||
row['item_options'] = item_options
|
row['item_options'] = self.allowed_items
|
||||||
|
|
||||||
# Unless found, the 'part_match' is blank
|
# Unless found, the 'part_match' is blank
|
||||||
row['item_match'] = None
|
row['item_match'] = None
|
||||||
@ -502,6 +546,16 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
def checkPartSelection(self, form):
|
||||||
|
""" Check part matching """
|
||||||
|
|
||||||
|
# Extract form data
|
||||||
|
self.getFormTableData(form.data)
|
||||||
|
|
||||||
|
valid = len(self.extra_context_data.get('missing_columns', [])) == 0 and not self.extra_context_data.get('duplicates', [])
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
def validate(self, step, form):
|
def validate(self, step, form):
|
||||||
""" Validate forms """
|
""" Validate forms """
|
||||||
|
|
||||||
@ -519,11 +573,10 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
form.add_error(None, 'Fields matching failed')
|
form.add_error(None, 'Fields matching failed')
|
||||||
|
|
||||||
elif step == 'items':
|
elif step == 'items':
|
||||||
# valid = self.checkPartSelection(form)
|
valid = self.checkPartSelection(form)
|
||||||
|
|
||||||
# if not valid:
|
if not valid:
|
||||||
# form.add_error(None, 'Items matching failed')
|
form.add_error(None, 'Items matching failed')
|
||||||
pass
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in form %}
|
{% 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" %}'>
|
||||||
@ -48,13 +48,18 @@
|
|||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
{% comment %} {% add row.index 1 %} {% endcomment %}
|
{% add row.index 1 %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ row }}
|
<button class='btn btn-default btn-create' onClick='newPartFromBomWizard()' id='new_part_row_{{ row.index }}' title='{% trans "Create new part" %}' type='button'>
|
||||||
{% comment %} <button class='btn btn-default btn-create' onClick='newPartFromBomWizard()' id='new_part_row_{{ row.index }}' title='{% trans "Create new part" %}' type='button'>
|
|
||||||
<span row_id='{{ row.index }}' class='fas fa-plus icon-green'/>
|
<span row_id='{{ row.index }}' class='fas fa-plus icon-green'/>
|
||||||
</button>
|
</button>
|
||||||
|
{% for field in form.visible_fields %}
|
||||||
|
{% if field.name == row.item_select %}
|
||||||
|
{{ field }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% comment %}
|
||||||
<select class='select bomselect' id='select_part_{{ row.index }}' name='part_{{ row.index }}'>
|
<select class='select bomselect' id='select_part_{{ row.index }}' name='part_{{ row.index }}'>
|
||||||
<option value=''>--- {% trans "Select Part" %} ---</option>
|
<option value=''>--- {% trans "Select Part" %} ---</option>
|
||||||
{% for part in row.part_options %}
|
{% for part in row.part_options %}
|
||||||
@ -69,16 +74,20 @@
|
|||||||
</td>
|
</td>
|
||||||
{% for item in row.data %}
|
{% for item in row.data %}
|
||||||
<td>
|
<td>
|
||||||
{% comment %}
|
|
||||||
{% if item.column.guess == 'Quantity' %}
|
{% if item.column.guess == 'Quantity' %}
|
||||||
<input name='quantity_{{ row.index }}' class='numberinput' type='number' min='1' step='any' value='{% decimal row.quantity %}'/>
|
{% for field in form.visible_fields %}
|
||||||
|
{% if field.name == row.quantity_select %}
|
||||||
|
{{ field }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% comment %} <input name='quantity_{{ row.index }}' class='numberinput' type='number' min='1' step='any' value='{% decimal row.quantity %}'/> {% endcomment %}
|
||||||
{% if row.errors.quantity %}
|
{% if row.errors.quantity %}
|
||||||
<p class='help-inline'>{{ row.errors.quantity }}</p>
|
<p class='help-inline'>{{ row.errors.quantity }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ item.cell }}
|
{{ item.cell }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type='hidden' name='row_{{ row.index }}_col_{{ forloop.counter0 }}' value='{{ item.cell }}'/> {% endcomment %}
|
<input type='hidden' name='row_{{ row.index }}_col_{{ forloop.counter0 }}' value='{{ item.cell }}'/>
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
Loading…
Reference in New Issue
Block a user