Working towards item match form

This commit is contained in:
eeintech 2021-05-06 17:07:22 -04:00
parent e31452a6ad
commit 4f942fd9f7
3 changed files with 152 additions and 16 deletions

View File

@ -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

View File

@ -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 """

View File

@ -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>