mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Now creating PurchaseOrderLineItem instances!
This commit is contained in:
parent
fbf24621f3
commit
ceccdf3909
@ -114,16 +114,18 @@ class MatchItem(forms.Form):
|
|||||||
|
|
||||||
# Setup FileManager
|
# Setup FileManager
|
||||||
file_manager.setup()
|
file_manager.setup()
|
||||||
# Get columns
|
|
||||||
columns = file_manager.columns()
|
|
||||||
|
|
||||||
if row_data:
|
|
||||||
# Create fields
|
# Create fields
|
||||||
|
if row_data:
|
||||||
|
# Navigate row data
|
||||||
for row in row_data:
|
for row in row_data:
|
||||||
|
# Navigate column data
|
||||||
for col in row['data']:
|
for col in row['data']:
|
||||||
# print(f"{col=}")
|
# Create input for required headers
|
||||||
if col['column']['guess'] in file_manager.REQUIRED_HEADERS:
|
if col['column']['guess'] in file_manager.REQUIRED_HEADERS:
|
||||||
|
# Set field name
|
||||||
field_name = col['column']['guess'].lower() + '-' + str(row['index'])
|
field_name = col['column']['guess'].lower() + '-' + str(row['index'])
|
||||||
|
# Set field input box
|
||||||
if 'quantity' in col['column']['guess'].lower():
|
if 'quantity' in col['column']['guess'].lower():
|
||||||
self.fields[field_name] = forms.CharField(
|
self.fields[field_name] = forms.CharField(
|
||||||
required=True,
|
required=True,
|
||||||
@ -142,15 +144,16 @@ class MatchItem(forms.Form):
|
|||||||
widget=forms.Select(attrs={
|
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")}')
|
|
||||||
|
|
||||||
|
# Create item selection box
|
||||||
|
elif col['column']['guess'] in file_manager.ITEM_MATCH_HEADERS:
|
||||||
# Get item options
|
# Get item options
|
||||||
item_options = [(option.id, option) for option in 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']
|
||||||
|
# Set field name
|
||||||
field_name = col['column']['guess'].lower() + '-' + str(row['index'])
|
field_name = 'item_select-' + str(row['index'])
|
||||||
|
# Set field select box
|
||||||
self.fields[field_name] = forms.ChoiceField(
|
self.fields[field_name] = forms.ChoiceField(
|
||||||
choices=[('', '-' * 10)] + item_options,
|
choices=[('', '-' * 10)] + item_options,
|
||||||
required=True,
|
required=True,
|
||||||
@ -158,5 +161,6 @@ class MatchItem(forms.Form):
|
|||||||
'class': 'select bomselect',
|
'class': 'select bomselect',
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
# Update initial selection
|
||||||
if item_match:
|
if item_match:
|
||||||
self.fields[field_name].initial = item_match.id
|
self.fields[field_name].initial = item_match.id
|
||||||
|
@ -6,8 +6,6 @@ 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
|
||||||
@ -23,8 +21,6 @@ from . import models
|
|||||||
from . import forms
|
from . import forms
|
||||||
from .files import FileManager
|
from .files import FileManager
|
||||||
|
|
||||||
from company.models import ManufacturerPart, SupplierPart
|
|
||||||
|
|
||||||
|
|
||||||
class SettingEdit(AjaxUpdateView):
|
class SettingEdit(AjaxUpdateView):
|
||||||
"""
|
"""
|
||||||
@ -192,19 +188,26 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
media_folder = 'file_upload/'
|
media_folder = 'file_upload/'
|
||||||
extra_context_data = {}
|
extra_context_data = {}
|
||||||
|
|
||||||
|
# Set keys for item matching
|
||||||
|
key_item_select = 'item_select'
|
||||||
|
key_quantity_select = 'quantity'
|
||||||
|
|
||||||
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' or self.steps.current == 'items':
|
if self.steps.current == 'fields' or self.steps.current == 'items':
|
||||||
|
|
||||||
# Get columns and row data
|
# Get columns and row data
|
||||||
self.columns = self.file_manager.columns()
|
self.columns = self.file_manager.columns()
|
||||||
self.rows = self.file_manager.rows()
|
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
|
# Set form table data
|
||||||
self.set_form_table_data(form=form)
|
self.set_form_table_data(form=form)
|
||||||
|
|
||||||
# if self.steps.current == 'items':
|
# Update context
|
||||||
# for row in self.rows:
|
|
||||||
# print(f'{row=}')
|
|
||||||
context.update({'rows': self.rows})
|
context.update({'rows': self.rows})
|
||||||
context.update({'columns': self.columns})
|
context.update({'columns': self.columns})
|
||||||
|
|
||||||
@ -288,8 +291,6 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
self.row_data = {}
|
self.row_data = {}
|
||||||
|
|
||||||
for item, value in form_data.items():
|
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
|
# 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
|
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):
|
def set_form_table_data(self, form=None):
|
||||||
if self.row_data:
|
if self.row_data:
|
||||||
# Re-construct the 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
|
# In the item selection step: update row data to contain fields
|
||||||
if form and self.steps.current == 'items':
|
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
|
# Update row data
|
||||||
for row in self.rows:
|
for row in self.rows:
|
||||||
# Add item select field
|
# Add item select field
|
||||||
if key_item_select:
|
row['item_select'] = self.key_item_select + '-' + str(row['index'])
|
||||||
row['item_select'] = key_item_select + '-' + str(row['index'])
|
|
||||||
# Add quantity select field
|
# Add quantity select field
|
||||||
if key_quantity_select:
|
row['quantity_select'] = self.key_quantity_select + '-' + str(row['index'])
|
||||||
row['quantity_select'] = key_quantity_select + '-' + str(row['index'])
|
|
||||||
|
|
||||||
if self.column_names:
|
if self.column_names:
|
||||||
# Re-construct the column data
|
# Re-construct the column data
|
||||||
@ -406,7 +364,7 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
for key in self.column_names:
|
for key in self.column_names:
|
||||||
header = ({
|
header = ({
|
||||||
'name': key,
|
'name': key,
|
||||||
'guess': self.column_selections[key],
|
'guess': self.column_selections.get(key, ''),
|
||||||
})
|
})
|
||||||
self.columns.append(header)
|
self.columns.append(header)
|
||||||
|
|
||||||
@ -426,88 +384,12 @@ class FileManagementFormView(MultiStepFormView):
|
|||||||
""" Once data columns have been selected, attempt to pre-select the proper data from the database.
|
""" 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.
|
This function is called once the field selection has been validated.
|
||||||
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.
|
||||||
|
|
||||||
|
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
|
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
|
|
||||||
|
|
||||||
def check_field_selection(self, form):
|
def check_field_selection(self, form):
|
||||||
""" Check field matching """
|
""" Check field matching """
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>{% trans "Row" %}</th>
|
<th>{% trans "Row" %}</th>
|
||||||
<th>{% trans "Select Part" %}</th>
|
<th>{% trans "Select Supplier Part" %}</th>
|
||||||
{% for col in 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 }}'/>
|
||||||
@ -48,7 +48,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
{% add row.index 1 %}
|
{{ row.index }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% for field in form.visible_fields %}
|
{% for field in form.visible_fields %}
|
||||||
|
@ -6,6 +6,7 @@ Django views for interacting with Order app
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -581,16 +582,147 @@ class PurchaseOrderUpload(FileManagementFormView):
|
|||||||
_("Match Supplier Parts"),
|
_("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):
|
def get_context_data(self, form, **kwargs):
|
||||||
context = super().get_context_data(form=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})
|
context.update({'order': order})
|
||||||
|
|
||||||
return context
|
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):
|
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']}))
|
return HttpResponseRedirect(reverse('po-detail', kwargs={'pk': self.kwargs['pk']}))
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user