Now creating PurchaseOrderLineItem instances!

This commit is contained in:
eeintech 2021-05-07 18:00:30 -04:00
parent fbf24621f3
commit ceccdf3909
4 changed files with 164 additions and 146 deletions

View File

@ -114,16 +114,18 @@ class MatchItem(forms.Form):
# Setup FileManager
file_manager.setup()
# Get columns
columns = file_manager.columns()
# Create fields
if row_data:
# Create fields
# Navigate row data
for row in row_data:
# Navigate column data
for col in row['data']:
# print(f"{col=}")
# Create input for required headers
if col['column']['guess'] in file_manager.REQUIRED_HEADERS:
# Set field name
field_name = col['column']['guess'].lower() + '-' + str(row['index'])
# Set field input box
if 'quantity' in col['column']['guess'].lower():
self.fields[field_name] = forms.CharField(
required=True,
@ -142,15 +144,16 @@ class MatchItem(forms.Form):
widget=forms.Select(attrs={
})
)
# Create item selection box
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
item_options = [(option.id, option) for option in row['item_options']]
# Get item match
item_match = row['item_match']
field_name = col['column']['guess'].lower() + '-' + str(row['index'])
# Set field name
field_name = 'item_select-' + str(row['index'])
# Set field select box
self.fields[field_name] = forms.ChoiceField(
choices=[('', '-' * 10)] + item_options,
required=True,
@ -158,5 +161,6 @@ class MatchItem(forms.Form):
'class': 'select bomselect',
})
)
# Update initial selection
if item_match:
self.fields[field_name].initial = item_match.id

View File

@ -6,8 +6,6 @@ Django views for interacting with common models
from __future__ import unicode_literals
import os
import ast
from decimal import Decimal, InvalidOperation
from django.utils.translation import ugettext_lazy as _
from django.forms import CheckboxInput, Select
@ -23,8 +21,6 @@ from . import models
from . import forms
from .files import FileManager
from company.models import ManufacturerPart, SupplierPart
class SettingEdit(AjaxUpdateView):
"""
@ -192,19 +188,26 @@ class FileManagementFormView(MultiStepFormView):
media_folder = 'file_upload/'
extra_context_data = {}
# Set keys for item matching
key_item_select = 'item_select'
key_quantity_select = 'quantity'
def get_context_data(self, form, **kwargs):
context = super().get_context_data(form=form, **kwargs)
if self.steps.current == 'fields' or self.steps.current == 'items':
# Get columns and row data
self.columns = self.file_manager.columns()
self.rows = self.file_manager.rows()
# Check for stored data
stored_data = self.storage.get_step_data(self.steps.current)
if stored_data:
self.get_form_table_data(stored_data)
# Set form table data
self.set_form_table_data(form=form)
# if self.steps.current == 'items':
# for row in self.rows:
# print(f'{row=}')
# Update context
context.update({'rows': self.rows})
context.update({'columns': self.columns})
@ -288,8 +291,6 @@ class FileManagementFormView(MultiStepFormView):
self.row_data = {}
for item, value in form_data.items():
# print(f'{item} | {form_data[item]} | {type(form_data[item])}')
# value = form.data[item]
# Column names as passed as col_name_<idx> where idx is an integer
@ -312,32 +313,6 @@ class FileManagementFormView(MultiStepFormView):
self.column_selections[col_name] = value
# Extract the row data
if item.startswith('row_'):
# Item should be of the format row_<r>_col_<c>
s = item.split('_')
if len(s) < 4:
continue
# Ignore row/col IDs which are not correct numeric values
try:
row_id = int(s[1])
col_id = int(s[3])
except ValueError:
continue
if row_id not in self.row_data:
self.row_data[row_id] = {}
# TODO: this is a hack
value = value.replace("'", '"')
# print(f'{type(value)=} | {value=}')
try:
self.row_data[row_id][col_id] = ast.literal_eval(value)
except (ValueError, SyntaxError):
pass
def set_form_table_data(self, form=None):
if self.row_data:
# Re-construct the row data
@ -375,29 +350,12 @@ class FileManagementFormView(MultiStepFormView):
# In the item selection step: update row data to contain fields
if form and self.steps.current == 'items':
key_item_select = ''
key_quantity_select = ''
# Find column key for item selection
for item in self.file_manager.ITEM_MATCH_HEADERS:
item = item.lower()
for key in form.fields.keys():
if item in key:
key_item_select = item
break
break
# Find column key for quantity selection
key_quantity_select = 'quantity'
# Update row data
for row in self.rows:
# Add item select field
if key_item_select:
row['item_select'] = key_item_select + '-' + str(row['index'])
row['item_select'] = self.key_item_select + '-' + str(row['index'])
# Add quantity select field
if key_quantity_select:
row['quantity_select'] = key_quantity_select + '-' + str(row['index'])
row['quantity_select'] = self.key_quantity_select + '-' + str(row['index'])
if self.column_names:
# Re-construct the column data
@ -406,7 +364,7 @@ class FileManagementFormView(MultiStepFormView):
for key in self.column_names:
header = ({
'name': key,
'guess': self.column_selections[key],
'guess': self.column_selections.get(key, ''),
})
self.columns.append(header)
@ -426,87 +384,11 @@ class FileManagementFormView(MultiStepFormView):
""" 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.
This method is very specific to the type of data found in the file,
therefore overwrite it in the subclass.
"""
match_supplier = False
match_manufacturer = False
self.allowed_items = None
# Fields prefixed with "Part_" can be used to do "smart matching" against Part objects in the database
q_idx = self.get_column_index('Quantity')
s_idx = self.get_column_index('Supplier_SKU')
m_idx = self.get_column_index('Manufacturer_MPN')
# p_idx = self.get_column_index('Unit_Price')
# e_idx = self.get_column_index('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
# 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:
print(f'{row["data"][s_idx]=}')
sku = row['data'][s_idx]['cell']
# Match for supplier
match_supplier = True
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:
mpn = row['data'][m_idx]['cell']
# Match for manufacturer
if not match_supplier:
match_manufacturer = True
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
row['item_options'] = self.allowed_items
# 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
pass
def check_field_selection(self, form):
""" Check field matching """

View File

@ -24,7 +24,7 @@
<th></th>
<th></th>
<th>{% trans "Row" %}</th>
<th>{% trans "Select Part" %}</th>
<th>{% trans "Select Supplier Part" %}</th>
{% for col in columns %}
<th>
<input type='hidden' name='col_name_{{ forloop.counter0 }}' value='{{ col.name }}'/>
@ -48,7 +48,7 @@
</td>
<td></td>
<td>
{% add row.index 1 %}
{{ row.index }}
</td>
<td>
{% for field in form.visible_fields %}

View File

@ -6,6 +6,7 @@ Django views for interacting with Order app
from __future__ import unicode_literals
from django.db import transaction
from django.db.utils import IntegrityError
from django.shortcuts import get_object_or_404
from django.core.exceptions import ValidationError
from django.urls import reverse
@ -581,16 +582,147 @@ class PurchaseOrderUpload(FileManagementFormView):
_("Match Supplier Parts"),
]
def get_order(self):
""" Get order or return 404 """
return get_object_or_404(PurchaseOrder, pk=self.kwargs['pk'])
def get_context_data(self, form, **kwargs):
context = super().get_context_data(form=form, **kwargs)
order = get_object_or_404(PurchaseOrder, pk=self.kwargs['pk'])
order = self.get_order()
context.update({'order': order})
return context
def get_field_selection(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 SupplierPart selection form.
"""
order = self.get_order()
self.allowed_items = SupplierPart.objects.filter(supplier=order.supplier).prefetch_related('manufacturer_part')
# Fields prefixed with "Part_" can be used to do "smart matching" against Part objects in the database
q_idx = self.get_column_index('Quantity')
s_idx = self.get_column_index('Supplier_SKU')
m_idx = self.get_column_index('Manufacturer_MPN')
# p_idx = self.get_column_index('Unit_Price')
# e_idx = self.get_column_index('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
# 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]['cell']
try:
# Attempt SupplierPart lookup based on SKU value
exact_match_part = self.allowed_items.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:
mpn = row['data'][m_idx]['cell']
try:
# Attempt SupplierPart lookup based on MPN value
exact_match_part = self.allowed_items.get(manufacturer_part__MPN__contains=mpn)
except (ValueError, SupplierPart.DoesNotExist, SupplierPart.MultipleObjectsReturned):
exact_match_part = None
# Supply list of part options for each row, sorted by how closely they match the part name
row['item_options'] = self.allowed_items
# 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 done(self, form_list, **kwargs):
""" Once all the data is in, process it to add SupplierPart items to the order """
order = self.get_order()
items = {}
for form_key, form_value in self.get_all_cleaned_data().items():
# Split key from row value
try:
(field, idx) = form_key.split('-')
except ValueError:
continue
if field == self.key_item_select:
if idx not in items:
# Insert into items
items.update({
idx: {
'field': form_value,
}
})
else:
# Update items
items[idx]['field'] = form_value
if field == self.key_quantity_select:
if idx not in items:
# Insert into items
items.update({
idx: {
'quantity': form_value,
}
})
else:
# Update items
items[idx]['quantity'] = form_value
# Create PurchaseOrderLineItem instances
for purchase_order_item in items.values():
try:
supplier_part = SupplierPart.objects.get(pk=int(purchase_order_item['field']))
except (ValueError, SupplierPart.DoesNotExist):
continue
purchase_order_line_item = PurchaseOrderLineItem(
order=order,
part=supplier_part,
quantity=purchase_order_item['quantity'],
)
try:
purchase_order_line_item.save()
except IntegrityError:
# PurchaseOrderLineItem already exists
pass
return HttpResponseRedirect(reverse('po-detail', kwargs={'pk': self.kwargs['pk']}))