mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Working towards item match form
This commit is contained in:
parent
e31452a6ad
commit
4f942fd9f7
@ -77,10 +77,11 @@ class MatchField(forms.Form):
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Setup headers
|
# Setup FileManager
|
||||||
file_manager.setup()
|
file_manager.setup()
|
||||||
|
# Get columns
|
||||||
columns = file_manager.columns()
|
columns = file_manager.columns()
|
||||||
# Find headers choices
|
# Get headers choices
|
||||||
headers_choices = [(header, header) for header in file_manager.HEADERS]
|
headers_choices = [(header, header) for header in file_manager.HEADERS]
|
||||||
|
|
||||||
# Create column fields
|
# Create column fields
|
||||||
@ -98,4 +99,38 @@ class MatchItem(forms.Form):
|
|||||||
""" Step 3 of FileManagementFormView """
|
""" Step 3 of FileManagementFormView """
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
# Get FileManager
|
||||||
|
file_manager = None
|
||||||
|
if 'file_manager' in kwargs:
|
||||||
|
file_manager = kwargs.pop('file_manager')
|
||||||
|
|
||||||
|
if 'row_data' in kwargs:
|
||||||
|
row_data = kwargs.pop('row_data')
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Setup FileManager
|
||||||
|
file_manager.setup()
|
||||||
|
# Get columns
|
||||||
|
columns = file_manager.columns()
|
||||||
|
|
||||||
|
# Create fields
|
||||||
|
# Item selection
|
||||||
|
for row in row_data:
|
||||||
|
for col in row['data']:
|
||||||
|
print(f'{row["index"]=} | {col["column"]["guess"]=}')
|
||||||
|
if col['column']['guess']:
|
||||||
|
if col['column']['guess'] in file_manager.PART_MATCH_HEADERS:
|
||||||
|
# Get item options
|
||||||
|
item_options = row['item_options']
|
||||||
|
# Get item match
|
||||||
|
item_match = row['item_match']
|
||||||
|
|
||||||
|
field_name = col['column']['guess'].lower() + '_' + str(row['index'])
|
||||||
|
self.fields[field_name] = forms.ChoiceField(
|
||||||
|
choices=item_options,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
if item_match:
|
||||||
|
self.fields[field_name].initial = item_match
|
||||||
|
@ -6,6 +6,8 @@ Django views for interacting with common models
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import ast
|
||||||
|
from decimal import Decimal, InvalidOperation
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.forms import CheckboxInput, Select
|
from django.forms import CheckboxInput, Select
|
||||||
@ -21,6 +23,8 @@ from . import models
|
|||||||
from . import forms
|
from . import forms
|
||||||
from .files import FileManager
|
from .files import FileManager
|
||||||
|
|
||||||
|
from part.models import SupplierPart
|
||||||
|
|
||||||
|
|
||||||
class SettingEdit(AjaxUpdateView):
|
class SettingEdit(AjaxUpdateView):
|
||||||
"""
|
"""
|
||||||
@ -243,19 +247,25 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
self.getFileManager(step)
|
self.getFileManager(step)
|
||||||
|
|
||||||
if step == 'upload':
|
if step == 'upload':
|
||||||
if self.name:
|
|
||||||
# Dynamically build upload form
|
# Dynamically build upload form
|
||||||
|
if self.name:
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'name': self.name
|
'name': self.name
|
||||||
}
|
}
|
||||||
return kwargs
|
return kwargs
|
||||||
elif step == 'fields':
|
elif step == 'fields':
|
||||||
if self.file_manager:
|
|
||||||
# Dynamically build match field form
|
# Dynamically build match field form
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'file_manager': self.file_manager
|
'file_manager': self.file_manager
|
||||||
}
|
}
|
||||||
return kwargs
|
return kwargs
|
||||||
|
elif step == 'items':
|
||||||
|
# Dynamically build match item form
|
||||||
|
kwargs = {}
|
||||||
|
kwargs['file_manager'] = self.file_manager
|
||||||
|
self.getFieldSelections()
|
||||||
|
kwargs['row_data'] = self.rows
|
||||||
|
return kwargs
|
||||||
|
|
||||||
return super().get_form_kwargs()
|
return super().get_form_kwargs()
|
||||||
|
|
||||||
@ -281,7 +291,7 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
self.row_data = {}
|
self.row_data = {}
|
||||||
|
|
||||||
for item in form_data:
|
for item in form_data:
|
||||||
# print(f'{item} | {form_data[item]}')
|
# print(f'{item} | {form_data[item]} | {type(form_data[item])}')
|
||||||
value = form_data[item]
|
value = form_data[item]
|
||||||
|
|
||||||
# Column names as passed as col_name_<idx> where idx is an integer
|
# Column names as passed as col_name_<idx> where idx is an integer
|
||||||
@ -323,7 +333,9 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
if row_id not in self.row_data:
|
if row_id not in self.row_data:
|
||||||
self.row_data[row_id] = {}
|
self.row_data[row_id] = {}
|
||||||
|
|
||||||
self.row_data[row_id][col_id] = value
|
# TODO: this is a hack
|
||||||
|
value = value.replace("'", '"')
|
||||||
|
self.row_data[row_id][col_id] = ast.literal_eval(value)
|
||||||
|
|
||||||
# self.col_ids = sorted(self.column_names.keys())
|
# self.col_ids = sorted(self.column_names.keys())
|
||||||
|
|
||||||
@ -395,6 +407,91 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
self.extra_context_data['missing_columns'] = missing_columns
|
self.extra_context_data['missing_columns'] = missing_columns
|
||||||
self.extra_context_data['duplicates'] = duplicates
|
self.extra_context_data['duplicates'] = duplicates
|
||||||
|
|
||||||
|
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 getFieldSelections(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
|
||||||
|
q_idx = self.getColumnIndex('Quantity')
|
||||||
|
s_idx = self.getColumnIndex('Supplier_SKU')
|
||||||
|
# m_idx = self.getColumnIndex('Manufacturer_MPN')
|
||||||
|
# p_idx = self.getColumnIndex('Unit_Price')
|
||||||
|
# e_idx = self.getColumnIndex('Extended_Price')
|
||||||
|
|
||||||
|
self.allowed_items = SupplierPart.objects.all()
|
||||||
|
|
||||||
|
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
|
||||||
|
item_options = self.allowed_items
|
||||||
|
|
||||||
|
# Check if there is a column corresponding to "quantity"
|
||||||
|
if q_idx >= 0:
|
||||||
|
q_val = row['data'][q_idx]['cell']
|
||||||
|
|
||||||
|
if q_val:
|
||||||
|
# Delete commas
|
||||||
|
q_val = q_val.replace(',','')
|
||||||
|
|
||||||
|
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:
|
||||||
|
sku = row['data'][s_idx]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Attempt SupplierPart lookup based on SKU value
|
||||||
|
exact_match_part = SupplierPart.objects.get(SKU__contains=sku)
|
||||||
|
except (ValueError, SupplierPart.DoesNotExist, SupplierPart.MultipleObjectsReturned):
|
||||||
|
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['item_options'] = item_options
|
||||||
|
|
||||||
|
# Unless found, the 'part_match' is blank
|
||||||
|
row['item_match'] = None
|
||||||
|
|
||||||
|
if exact_match_part:
|
||||||
|
# If there is an exact match based on SKU or MPN, use that
|
||||||
|
row['item_match'] = exact_match_part
|
||||||
|
|
||||||
def checkFieldSelection(self, form):
|
def checkFieldSelection(self, form):
|
||||||
""" Check field matching """
|
""" Check field matching """
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in rows %}
|
{% for row in form %}
|
||||||
<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" %}'>
|
||||||
@ -47,9 +47,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>{% add row.index 1 %}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<button class='btn btn-default btn-create' onClick='newPartFromBomWizard()' id='new_part_row_{{ row.index }}' title='{% trans "Create new part" %}' type='button'>
|
{% comment %} {% add row.index 1 %} {% endcomment %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ row }}
|
||||||
|
{% 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>
|
||||||
<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 }}'>
|
||||||
@ -62,10 +65,11 @@
|
|||||||
</select>
|
</select>
|
||||||
{% if row.errors.part %}
|
{% if row.errors.part %}
|
||||||
<p class='help-inline'>{{ row.errors.part }}</p>
|
<p class='help-inline'>{{ row.errors.part }}</p>
|
||||||
{% endif %}
|
{% endif %} {% endcomment %}
|
||||||
</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 %}'/>
|
<input name='quantity_{{ row.index }}' class='numberinput' type='number' min='1' step='any' value='{% decimal row.quantity %}'/>
|
||||||
{% if row.errors.quantity %}
|
{% if row.errors.quantity %}
|
||||||
@ -74,7 +78,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{{ item.cell }}
|
{{ item.cell }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type='hidden' name='row_{{ row.index }}_col_{{ forloop.counter0 }}' value='{{ item.cell }}'/>
|
<input type='hidden' name='row_{{ row.index }}_col_{{ forloop.counter0 }}' value='{{ item.cell }}'/> {% endcomment %}
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
Loading…
Reference in New Issue
Block a user