mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Create new file bom.py for BOM helper functions
- New class for managing BOM upload
This commit is contained in:
parent
65c7454124
commit
45d16f2c42
@ -92,33 +92,6 @@ def MakeBarcode(object_type, object_id, object_url, data={}):
|
|||||||
return json.dumps(data, sort_keys=True)
|
return json.dumps(data, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
def IsValidSpreadsheetFormat(fmt):
|
|
||||||
""" Test if a file format specifier is in the valid list of spreadsheet file formats """
|
|
||||||
|
|
||||||
return fmt.lower() in ['csv', 'xls', 'xlsx', 'tsv']
|
|
||||||
|
|
||||||
|
|
||||||
def MakeBomTemplate(fmt):
|
|
||||||
""" Generate a Bill of Materials upload template file (for user download) """
|
|
||||||
|
|
||||||
if not IsValidSpreadsheetFormat(fmt):
|
|
||||||
fmt = 'csv'
|
|
||||||
|
|
||||||
fields = [
|
|
||||||
'Part',
|
|
||||||
'Quantity',
|
|
||||||
'Overage',
|
|
||||||
'Reference',
|
|
||||||
'Notes'
|
|
||||||
]
|
|
||||||
|
|
||||||
data = tablib.Dataset(headers=fields).export(fmt)
|
|
||||||
|
|
||||||
filename = 'InvenTree_BOM_Template.' + fmt
|
|
||||||
|
|
||||||
return DownloadFile(data, filename)
|
|
||||||
|
|
||||||
|
|
||||||
def DownloadFile(data, filename, content_type='application/text'):
|
def DownloadFile(data, filename, content_type='application/text'):
|
||||||
""" Create a dynamic file for the user to download.
|
""" Create a dynamic file for the user to download.
|
||||||
|
|
||||||
|
92
InvenTree/part/bom.py
Normal file
92
InvenTree/part/bom.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
"""
|
||||||
|
Functionality for Bill of Material (BOM) management.
|
||||||
|
Primarily BOM upload tools.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fuzzywuzzy import fuzz
|
||||||
|
import tablib
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from .models import Part, BomItem
|
||||||
|
|
||||||
|
from InvenTree.helpers import DownloadFile
|
||||||
|
|
||||||
|
|
||||||
|
def IsValidBOMFormat(fmt):
|
||||||
|
""" Test if a file format specifier is in the valid list of BOM file formats """
|
||||||
|
|
||||||
|
return fmt.strip().lower() in ['csv', 'xls', 'xlsx', 'tsv']
|
||||||
|
|
||||||
|
|
||||||
|
def MakeBomTemplate(fmt):
|
||||||
|
""" Generate a Bill of Materials upload template file (for user download) """
|
||||||
|
|
||||||
|
fmt = fmt.strip().lower()
|
||||||
|
|
||||||
|
if not IsValidBOMFormat(fmt):
|
||||||
|
fmt = 'csv'
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
'Part',
|
||||||
|
'Quantity',
|
||||||
|
'Overage',
|
||||||
|
'Reference',
|
||||||
|
'Notes'
|
||||||
|
]
|
||||||
|
|
||||||
|
data = tablib.Dataset(headers=fields).export(fmt)
|
||||||
|
|
||||||
|
filename = 'InvenTree_BOM_Template.' + fmt
|
||||||
|
|
||||||
|
return DownloadFile(data, filename)
|
||||||
|
|
||||||
|
|
||||||
|
class BomUploadManager:
|
||||||
|
""" Class for managing an uploaded BOM file """
|
||||||
|
|
||||||
|
# Fields which are absolutely necessary for valid upload
|
||||||
|
REQUIRED_HEADERS = [
|
||||||
|
'Part',
|
||||||
|
'Quantity',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Fields which are not necessary but can be populated
|
||||||
|
USEFUL_HEADERS = [
|
||||||
|
'REFERENCE',
|
||||||
|
'OVERAGE',
|
||||||
|
'NOTES'
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, bom_file, starting_row=2):
|
||||||
|
""" Initialize the BomUpload class with a user-uploaded file object """
|
||||||
|
self.starting_row = starting_row
|
||||||
|
print("Starting on row", starting_row)
|
||||||
|
self.process(bom_file)
|
||||||
|
|
||||||
|
def process(self, bom_file):
|
||||||
|
""" Process a BOM file """
|
||||||
|
|
||||||
|
ext = os.path.splitext(bom_file.name)[-1].lower()
|
||||||
|
|
||||||
|
if ext in ['.csv', '.tsv', ]:
|
||||||
|
# These file formats need string decoding
|
||||||
|
raw_data = bom_file.read().decode('utf-8')
|
||||||
|
elif ext in ['.xls', '.xlsx']:
|
||||||
|
raw_data = bom_file.read()
|
||||||
|
else:
|
||||||
|
raise ValidationError({'bom_file': _('Unsupported file format: {f}'.format(f=ext))})
|
||||||
|
|
||||||
|
try:
|
||||||
|
bom_data = tablib.Dataset().load(raw_data)
|
||||||
|
except tablib.UnsupportedFormat:
|
||||||
|
raise ValidationError({'bom_file': _('Error reading BOM file (invalid data)')})
|
||||||
|
|
||||||
|
# Now we have BOM data in memory!
|
||||||
|
|
||||||
|
headers = [h.lower() for h in bom_data.headers]
|
||||||
|
|
||||||
|
for header in self.REQUIRED_HEADERS:
|
||||||
|
if not header.lower() in headers:
|
||||||
|
raise ValidationError({'bom_file': _("Missing required field '{f}'".format(f=header))})
|
@ -8,6 +8,7 @@ from __future__ import unicode_literals
|
|||||||
import tablib
|
import tablib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
@ -23,11 +24,12 @@ from .models import match_part_names
|
|||||||
from company.models import SupplierPart
|
from company.models import SupplierPart
|
||||||
|
|
||||||
from . import forms as part_forms
|
from . import forms as part_forms
|
||||||
|
from .bom import MakeBomTemplate, BomUploadManager
|
||||||
|
|
||||||
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
||||||
from InvenTree.views import QRCodeView
|
from InvenTree.views import QRCodeView
|
||||||
|
|
||||||
from InvenTree.helpers import DownloadFile, str2bool, MakeBomTemplate
|
from InvenTree.helpers import DownloadFile, str2bool
|
||||||
from InvenTree.status_codes import OrderStatus
|
from InvenTree.status_codes import OrderStatus
|
||||||
|
|
||||||
|
|
||||||
@ -679,35 +681,15 @@ class BomUpload(AjaxView, FormMixin):
|
|||||||
'form_valid': False
|
'form_valid': False
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract the file format data
|
# Create a BomUploadManager object - will perform initial data validation
|
||||||
ext = os.path.splitext(bom_file.name)[-1].lower()
|
# (and raise a ValidationError if there is something wrong with the file)
|
||||||
|
|
||||||
if ext in ['.csv', '.tsv', ]:
|
|
||||||
# These file formats need string decoding
|
|
||||||
raw_data = bom_file.read().decode('utf-8')
|
|
||||||
elif ext in ['.xls', '.xlsx']:
|
|
||||||
raw_data = bom_file.read()
|
|
||||||
else:
|
|
||||||
self.form.errors['bom_file'] = ['Unsupported file format: ' + ext]
|
|
||||||
return self.renderJsonResponse(self.request, self.form, data)
|
|
||||||
|
|
||||||
# Now try to read the data
|
|
||||||
try:
|
try:
|
||||||
bom_data = tablib.Dataset().load(raw_data)
|
manager = BomUploadManager(bom_file, self.form['starting_row'].value())
|
||||||
|
except ValidationError as e:
|
||||||
|
errors = e.error_dict
|
||||||
|
|
||||||
headers = [h.lower() for h in bom_data.headers]
|
for k,v in errors.items():
|
||||||
|
self.form.errors[k] = v
|
||||||
# Minimal set of required fields
|
|
||||||
for header in ['part', 'quantity', 'reference']:
|
|
||||||
if not header in headers:
|
|
||||||
self.form.errors['bom_file'] = [_("Missing required field '{f}'".format(f=header))]
|
|
||||||
break
|
|
||||||
|
|
||||||
except tablib.UnsupportedFormat:
|
|
||||||
valid = False
|
|
||||||
self.form.errors['bom_file'] = [
|
|
||||||
"Error reading '{f}' (Unsupported file format)".format(f=str(bom_file)),
|
|
||||||
]
|
|
||||||
|
|
||||||
return self.renderJsonResponse(self.request, self.form, data=data)
|
return self.renderJsonResponse(self.request, self.form, data=data)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user