mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
d0ad3f0e37
@ -103,6 +103,7 @@ def GetExportFormats():
|
|||||||
'xls',
|
'xls',
|
||||||
'xlsx',
|
'xlsx',
|
||||||
'json',
|
'json',
|
||||||
|
'yaml',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ class CompanyResource(ModelResource):
|
|||||||
model = Company
|
model = Company
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
|
clean_model_instances = True
|
||||||
|
|
||||||
|
|
||||||
class CompanyAdmin(ImportExportModelAdmin):
|
class CompanyAdmin(ImportExportModelAdmin):
|
||||||
@ -43,6 +44,7 @@ class SupplierPartResource(ModelResource):
|
|||||||
model = SupplierPart
|
model = SupplierPart
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
|
clean_model_instances = True
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartAdmin(ImportExportModelAdmin):
|
class SupplierPartAdmin(ImportExportModelAdmin):
|
||||||
@ -63,6 +65,7 @@ class SupplierPriceBreakResource(ModelResource):
|
|||||||
model = SupplierPriceBreak
|
model = SupplierPriceBreak
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
|
clean_model_instances = True
|
||||||
|
|
||||||
|
|
||||||
class SupplierPriceBreakAdmin(ImportExportModelAdmin):
|
class SupplierPriceBreakAdmin(ImportExportModelAdmin):
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<div id='button-bar'>
|
<div id='button-bar'>
|
||||||
<div class='btn-group'>
|
<div class='btn-group'>
|
||||||
<button class='btn btn-primary' type='button' id='company-order-2' title='Create new purchase order'>New Purchase Order</button>
|
<button class='btn btn-primary' type='button' id='company-order2' title='Create new purchase order'>New Purchase Order</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -38,7 +38,7 @@
|
|||||||
newOrder();
|
newOrder();
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#company-order-2").click(function() {
|
$("#company-order2").click(function() {
|
||||||
newOrder();
|
newOrder();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -47,4 +47,4 @@
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,8 +2,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from import_export.admin import ImportExportModelAdmin
|
from import_export.admin import ImportExportModelAdmin
|
||||||
|
|
||||||
|
from import_export.resources import ModelResource
|
||||||
|
from import_export.fields import Field
|
||||||
|
|
||||||
from .models import PurchaseOrder, PurchaseOrderLineItem
|
from .models import PurchaseOrder, PurchaseOrderLineItem
|
||||||
|
|
||||||
|
|
||||||
@ -18,8 +22,28 @@ class PurchaseOrderAdmin(ImportExportModelAdmin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class POLineItemResource(ModelResource):
|
||||||
|
""" Class for managing import / export of POLineItem data """
|
||||||
|
|
||||||
|
part_name = Field(attribute='part__part__name', readonly=True)
|
||||||
|
|
||||||
|
manufacturer = Field(attribute='part__manufacturer', readonly=True)
|
||||||
|
|
||||||
|
MPN = Field(attribute='part__MPN', readonly=True)
|
||||||
|
|
||||||
|
SKU = Field(attribute='part__SKU', readonly=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PurchaseOrderLineItem
|
||||||
|
skip_unchanged = True
|
||||||
|
report_skipped = False
|
||||||
|
clean_model_instances = True
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
|
resource_class = POLineItemResource
|
||||||
|
|
||||||
list_display = (
|
list_display = (
|
||||||
'order',
|
'order',
|
||||||
'part',
|
'part',
|
||||||
|
@ -12,7 +12,6 @@ from django.contrib.auth.models import User
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
import tablib
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from stock.models import StockItem
|
from stock.models import StockItem
|
||||||
@ -126,54 +125,6 @@ class PurchaseOrder(Order):
|
|||||||
related_name='+'
|
related_name='+'
|
||||||
)
|
)
|
||||||
|
|
||||||
def export_to_file(self, **kwargs):
|
|
||||||
""" Export order information to external file """
|
|
||||||
|
|
||||||
file_format = kwargs.get('format', 'csv').lower()
|
|
||||||
|
|
||||||
data = tablib.Dataset(headers=[
|
|
||||||
'Line',
|
|
||||||
'Part',
|
|
||||||
'Description',
|
|
||||||
'Manufacturer',
|
|
||||||
'MPN',
|
|
||||||
'Order Code',
|
|
||||||
'Quantity',
|
|
||||||
'Received',
|
|
||||||
'Reference',
|
|
||||||
'Notes',
|
|
||||||
])
|
|
||||||
|
|
||||||
idx = 0
|
|
||||||
|
|
||||||
for item in self.lines.all():
|
|
||||||
|
|
||||||
line = []
|
|
||||||
|
|
||||||
line.append(idx)
|
|
||||||
|
|
||||||
if item.part:
|
|
||||||
line.append(item.part.part.name)
|
|
||||||
line.append(item.part.part.description)
|
|
||||||
|
|
||||||
line.append(item.part.manufacturer)
|
|
||||||
line.append(item.part.MPN)
|
|
||||||
line.append(item.part.SKU)
|
|
||||||
|
|
||||||
else:
|
|
||||||
line += [[] * 5]
|
|
||||||
|
|
||||||
line.append(item.quantity)
|
|
||||||
line.append(item.received)
|
|
||||||
line.append(item.reference)
|
|
||||||
line.append(item.notes)
|
|
||||||
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
data.append(line)
|
|
||||||
|
|
||||||
return data.export(file_format)
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('purchase-order-detail', kwargs={'pk': self.id})
|
return reverse('purchase-order-detail', kwargs={'pk': self.id})
|
||||||
|
|
||||||
|
@ -139,13 +139,3 @@ class OrderTest(TestCase):
|
|||||||
order.receive_line_item(line, loc, line.quantity, user=None)
|
order.receive_line_item(line, loc, line.quantity, user=None)
|
||||||
|
|
||||||
self.assertEqual(order.status, OrderStatus.COMPLETE)
|
self.assertEqual(order.status, OrderStatus.COMPLETE)
|
||||||
|
|
||||||
def test_export(self):
|
|
||||||
""" Test order exporting """
|
|
||||||
|
|
||||||
order = PurchaseOrder.objects.get(pk=1)
|
|
||||||
|
|
||||||
output = order.export_to_file(format='csv')
|
|
||||||
|
|
||||||
self.assertIn('M2x4 LPHS', output)
|
|
||||||
self.assertIn('Line,Part,Description', output)
|
|
||||||
|
@ -14,6 +14,7 @@ from django.forms import HiddenInput
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .models import PurchaseOrder, PurchaseOrderLineItem
|
from .models import PurchaseOrder, PurchaseOrderLineItem
|
||||||
|
from .admin import POLineItemResource
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
from company.models import Company, SupplierPart
|
from company.models import Company, SupplierPart
|
||||||
from stock.models import StockItem, StockLocation
|
from stock.models import StockItem, StockLocation
|
||||||
@ -165,7 +166,9 @@ class PurchaseOrderExport(AjaxView):
|
|||||||
fmt=export_format
|
fmt=export_format
|
||||||
)
|
)
|
||||||
|
|
||||||
filedata = order.export_to_file(format=export_format)
|
dataset = POLineItemResource().export(queryset=order.lines.all())
|
||||||
|
|
||||||
|
filedata = dataset.export(format=export_format)
|
||||||
|
|
||||||
return DownloadFile(filedata, filename)
|
return DownloadFile(filedata, filename)
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ class PartResource(ModelResource):
|
|||||||
|
|
||||||
variant_of = Field(attribute='variant_of', widget=widgets.ForeignKeyWidget(Part))
|
variant_of = Field(attribute='variant_of', widget=widgets.ForeignKeyWidget(Part))
|
||||||
|
|
||||||
|
suppliers = Field(attribute='supplier_count', readonly=True)
|
||||||
|
|
||||||
# Extra calculated meta-data (readonly)
|
# Extra calculated meta-data (readonly)
|
||||||
in_stock = Field(attribute='total_stock', readonly=True, widget=widgets.IntegerWidget())
|
in_stock = Field(attribute='total_stock', readonly=True, widget=widgets.IntegerWidget())
|
||||||
|
|
||||||
@ -46,6 +48,7 @@ class PartResource(ModelResource):
|
|||||||
model = Part
|
model = Part
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
|
clean_model_instances = True
|
||||||
exclude = [
|
exclude = [
|
||||||
'bom_checksum', 'bom_checked_by', 'bom_checked_date'
|
'bom_checksum', 'bom_checked_by', 'bom_checked_date'
|
||||||
]
|
]
|
||||||
@ -89,6 +92,7 @@ class PartCategoryResource(ModelResource):
|
|||||||
model = PartCategory
|
model = PartCategory
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
|
clean_model_instances = True
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
# Exclude MPTT internal model fields
|
# Exclude MPTT internal model fields
|
||||||
@ -127,12 +131,21 @@ class BomItemResource(ModelResource):
|
|||||||
|
|
||||||
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
||||||
|
|
||||||
sub_part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
part_name = Field(attribute='part__full_name', readonly=True)
|
||||||
|
|
||||||
|
sub_part = Field(attribute='sub_part', widget=widgets.ForeignKeyWidget(Part))
|
||||||
|
|
||||||
|
sub_part_name = Field(attribute='sub_part__full_name', readonly=True)
|
||||||
|
|
||||||
|
stock = Field(attribute='sub_part__total_stock', readonly=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BomItem
|
model = BomItem
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
|
clean_model_instances = True
|
||||||
|
|
||||||
|
exclude = ('checksum')
|
||||||
|
|
||||||
|
|
||||||
class BomItemAdmin(ImportExportModelAdmin):
|
class BomItemAdmin(ImportExportModelAdmin):
|
||||||
@ -163,6 +176,7 @@ class ParameterResource(ModelResource):
|
|||||||
model = PartParameter
|
model = PartParameter
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
|
clean_model_instance = True
|
||||||
|
|
||||||
|
|
||||||
class ParameterAdmin(ImportExportModelAdmin):
|
class ParameterAdmin(ImportExportModelAdmin):
|
||||||
|
@ -10,13 +10,16 @@ import os
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from InvenTree.helpers import DownloadFile
|
from InvenTree.helpers import DownloadFile, GetExportFormats
|
||||||
|
|
||||||
|
from .admin import BomItemResource
|
||||||
|
from .models import BomItem
|
||||||
|
|
||||||
|
|
||||||
def IsValidBOMFormat(fmt):
|
def IsValidBOMFormat(fmt):
|
||||||
""" Test if a file format specifier is in the valid list of BOM file formats """
|
""" Test if a file format specifier is in the valid list of BOM file formats """
|
||||||
|
|
||||||
return fmt.strip().lower() in ['csv', 'xls', 'xlsx', 'tsv']
|
return fmt.strip().lower() in GetExportFormats()
|
||||||
|
|
||||||
|
|
||||||
def MakeBomTemplate(fmt):
|
def MakeBomTemplate(fmt):
|
||||||
@ -27,21 +30,33 @@ def MakeBomTemplate(fmt):
|
|||||||
if not IsValidBOMFormat(fmt):
|
if not IsValidBOMFormat(fmt):
|
||||||
fmt = 'csv'
|
fmt = 'csv'
|
||||||
|
|
||||||
fields = [
|
query = BomItem.objects.filter(pk=None)
|
||||||
'Part',
|
dataset = BomItemResource().export(queryset=query)
|
||||||
'Quantity',
|
|
||||||
'Overage',
|
|
||||||
'Reference',
|
|
||||||
'Notes'
|
|
||||||
]
|
|
||||||
|
|
||||||
data = tablib.Dataset(headers=fields).export(fmt)
|
data = dataset.export(fmt)
|
||||||
|
|
||||||
filename = 'InvenTree_BOM_Template.' + fmt
|
filename = 'InvenTree_BOM_Template.' + fmt
|
||||||
|
|
||||||
return DownloadFile(data, filename)
|
return DownloadFile(data, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def ExportBom(part, fmt='csv'):
|
||||||
|
""" Export a BOM (Bill of Materials) for a given part.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not IsValidBOMFormat(fmt):
|
||||||
|
fmt = 'csv'
|
||||||
|
|
||||||
|
bom_items = part.bom_items.all().order_by('id')
|
||||||
|
|
||||||
|
dataset = BomItemResource().export(queryset=bom_items)
|
||||||
|
data = dataset.export(fmt)
|
||||||
|
|
||||||
|
filename = '{n}_BOM.{fmt}'.format(n=part.full_name, fmt=fmt)
|
||||||
|
|
||||||
|
return DownloadFile(data, filename)
|
||||||
|
|
||||||
|
|
||||||
class BomUploadManager:
|
class BomUploadManager:
|
||||||
""" Class for managing an uploaded BOM file """
|
""" Class for managing an uploaded BOM file """
|
||||||
|
|
||||||
|
@ -7,8 +7,6 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import tablib
|
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -857,76 +855,6 @@ class Part(models.Model):
|
|||||||
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def export_bom(self, **kwargs):
|
|
||||||
""" Export Bill of Materials to a spreadsheet file.
|
|
||||||
Includes a row for each item in the BOM.
|
|
||||||
Also includes extra information such as supplier data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
items = self.bom_items.all().order_by('id')
|
|
||||||
|
|
||||||
supplier_names = set()
|
|
||||||
|
|
||||||
headers = [
|
|
||||||
'Part',
|
|
||||||
'Description',
|
|
||||||
'Quantity',
|
|
||||||
'Overage',
|
|
||||||
'Reference',
|
|
||||||
'Note',
|
|
||||||
'',
|
|
||||||
'In Stock',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Contstruct list of suppliers for each part
|
|
||||||
for item in items:
|
|
||||||
part = item.sub_part
|
|
||||||
supplier_parts = part.supplier_parts.all()
|
|
||||||
item.suppliers = {}
|
|
||||||
|
|
||||||
for sp in supplier_parts:
|
|
||||||
name = sp.supplier.name
|
|
||||||
supplier_names.add(name)
|
|
||||||
item.suppliers[name] = sp
|
|
||||||
|
|
||||||
if len(supplier_names) > 0:
|
|
||||||
headers.append('')
|
|
||||||
for name in supplier_names:
|
|
||||||
headers.append(name)
|
|
||||||
|
|
||||||
data = tablib.Dataset(headers=headers)
|
|
||||||
|
|
||||||
for it in items:
|
|
||||||
line = []
|
|
||||||
|
|
||||||
# Information about each BOM item
|
|
||||||
line.append(it.sub_part.full_name)
|
|
||||||
line.append(it.sub_part.description)
|
|
||||||
line.append(it.quantity)
|
|
||||||
line.append(it.overage)
|
|
||||||
line.append(it.reference)
|
|
||||||
line.append(it.note)
|
|
||||||
|
|
||||||
# Extra information about the part
|
|
||||||
line.append('')
|
|
||||||
line.append(it.sub_part.available_stock)
|
|
||||||
|
|
||||||
if len(supplier_names) > 0:
|
|
||||||
line.append('') # Blank column separates supplier info
|
|
||||||
|
|
||||||
for name in supplier_names:
|
|
||||||
sp = it.suppliers.get(name, None)
|
|
||||||
if sp:
|
|
||||||
line.append(sp.SKU)
|
|
||||||
else:
|
|
||||||
line.append('')
|
|
||||||
|
|
||||||
data.append(line)
|
|
||||||
|
|
||||||
file_format = kwargs.get('format', 'csv').lower()
|
|
||||||
|
|
||||||
return data.export(file_format)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attachment_count(self):
|
def attachment_count(self):
|
||||||
""" Count the number of attachments for this part.
|
""" Count the number of attachments for this part.
|
||||||
|
@ -32,15 +32,6 @@ class BomItemTest(TestCase):
|
|||||||
|
|
||||||
self.assertIn(self.orphan, parts)
|
self.assertIn(self.orphan, parts)
|
||||||
|
|
||||||
def test_bom_export(self):
|
|
||||||
parts = self.bob.required_parts()
|
|
||||||
|
|
||||||
data = self.bob.export_bom(format='csv')
|
|
||||||
|
|
||||||
for p in parts:
|
|
||||||
self.assertIn(p.name, data)
|
|
||||||
self.assertIn(p.description, data)
|
|
||||||
|
|
||||||
def test_used_in(self):
|
def test_used_in(self):
|
||||||
self.assertEqual(self.bob.used_in_count, 0)
|
self.assertEqual(self.bob.used_in_count, 0)
|
||||||
self.assertEqual(self.orphan.used_in_count, 1)
|
self.assertEqual(self.orphan.used_in_count, 1)
|
||||||
|
@ -14,8 +14,6 @@ from django.views.generic import DetailView, ListView, FormView
|
|||||||
from django.forms.models import model_to_dict
|
from django.forms.models import model_to_dict
|
||||||
from django.forms import HiddenInput, CheckboxInput
|
from django.forms import HiddenInput, CheckboxInput
|
||||||
|
|
||||||
import tablib
|
|
||||||
|
|
||||||
from fuzzywuzzy import fuzz
|
from fuzzywuzzy import fuzz
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
@ -28,7 +26,9 @@ from common.models import Currency
|
|||||||
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 .bom import MakeBomTemplate, BomUploadManager, ExportBom, IsValidBOMFormat
|
||||||
|
|
||||||
|
from .admin import PartResource
|
||||||
|
|
||||||
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
|
||||||
@ -1216,108 +1216,9 @@ class PartExport(AjaxView):
|
|||||||
|
|
||||||
parts = self.get_parts(request)
|
parts = self.get_parts(request)
|
||||||
|
|
||||||
headers = [
|
dataset = PartResource().export(queryset=parts)
|
||||||
'ID',
|
|
||||||
'Name',
|
|
||||||
'Description',
|
|
||||||
'Category',
|
|
||||||
'Category ID',
|
|
||||||
'IPN',
|
|
||||||
'Revision',
|
|
||||||
'URL',
|
|
||||||
'Keywords',
|
|
||||||
'Notes',
|
|
||||||
'Assembly',
|
|
||||||
'Component',
|
|
||||||
'Template',
|
|
||||||
'Trackable',
|
|
||||||
'Purchaseable',
|
|
||||||
'Salable',
|
|
||||||
'Active',
|
|
||||||
'Virtual',
|
|
||||||
|
|
||||||
# Part meta-data
|
csv = dataset.export('csv')
|
||||||
'Used In',
|
|
||||||
|
|
||||||
# Stock Information
|
|
||||||
'Stock Info',
|
|
||||||
'In Stock',
|
|
||||||
'Allocated',
|
|
||||||
'Building',
|
|
||||||
'On Order',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Construct list of suppliers for each part
|
|
||||||
supplier_names = set()
|
|
||||||
|
|
||||||
for part in parts:
|
|
||||||
supplier_parts = part.supplier_parts.all()
|
|
||||||
part.suppliers = {}
|
|
||||||
|
|
||||||
for sp in supplier_parts:
|
|
||||||
name = sp.supplier.name
|
|
||||||
supplier_names.add(name)
|
|
||||||
part.suppliers[name] = sp
|
|
||||||
|
|
||||||
if len(supplier_names) > 0:
|
|
||||||
headers.append('Suppliers')
|
|
||||||
for name in supplier_names:
|
|
||||||
headers.append(name)
|
|
||||||
|
|
||||||
data = tablib.Dataset(headers=headers)
|
|
||||||
|
|
||||||
for part in parts:
|
|
||||||
line = []
|
|
||||||
|
|
||||||
line.append(part.pk)
|
|
||||||
line.append(part.name)
|
|
||||||
line.append(part.description)
|
|
||||||
|
|
||||||
if part.category:
|
|
||||||
line.append(str(part.category))
|
|
||||||
line.append(part.category.pk)
|
|
||||||
else:
|
|
||||||
line.append('')
|
|
||||||
line.append('')
|
|
||||||
|
|
||||||
line.append(part.IPN)
|
|
||||||
line.append(part.revision)
|
|
||||||
line.append(part.URL)
|
|
||||||
line.append(part.keywords)
|
|
||||||
line.append(part.notes)
|
|
||||||
line.append(part.assembly)
|
|
||||||
line.append(part.component)
|
|
||||||
line.append(part.is_template)
|
|
||||||
line.append(part.trackable)
|
|
||||||
line.append(part.purchaseable)
|
|
||||||
line.append(part.salable)
|
|
||||||
line.append(part.active)
|
|
||||||
line.append(part.virtual)
|
|
||||||
|
|
||||||
# Part meta-data
|
|
||||||
line.append(part.used_in_count)
|
|
||||||
|
|
||||||
# Stock information
|
|
||||||
line.append('')
|
|
||||||
line.append(part.total_stock)
|
|
||||||
line.append(part.allocation_count)
|
|
||||||
line.append(part.quantity_being_built)
|
|
||||||
line.append(part.on_order)
|
|
||||||
|
|
||||||
# Supplier Information
|
|
||||||
if len(supplier_names) > 0:
|
|
||||||
line.append('')
|
|
||||||
|
|
||||||
for name in supplier_names:
|
|
||||||
sp = part.suppliers.get(name, None)
|
|
||||||
if sp:
|
|
||||||
line.append(sp.SKU)
|
|
||||||
else:
|
|
||||||
line.append('')
|
|
||||||
|
|
||||||
data.append(line)
|
|
||||||
|
|
||||||
csv = data.export('csv')
|
|
||||||
return DownloadFile(csv, 'InvenTree_Parts.csv')
|
return DownloadFile(csv, 'InvenTree_Parts.csv')
|
||||||
|
|
||||||
|
|
||||||
@ -1348,12 +1249,10 @@ class BomDownload(AjaxView):
|
|||||||
|
|
||||||
export_format = request.GET.get('format', 'csv')
|
export_format = request.GET.get('format', 'csv')
|
||||||
|
|
||||||
# Placeholder to test file export
|
if not IsValidBOMFormat(export_format):
|
||||||
filename = '"' + part.name + '_BOM.' + export_format + '"'
|
export_format = 'csv'
|
||||||
|
|
||||||
filedata = part.export_bom(format=export_format)
|
return ExportBom(part, fmt=export_format)
|
||||||
|
|
||||||
return DownloadFile(filedata, filename)
|
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
|
@ -28,6 +28,7 @@ class LocationResource(ModelResource):
|
|||||||
model = StockLocation
|
model = StockLocation
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
|
clean_model_instances = True
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
# Exclude MPTT internal model fields
|
# Exclude MPTT internal model fields
|
||||||
@ -57,10 +58,16 @@ class StockItemResource(ModelResource):
|
|||||||
# Custom manaegrs for ForeignKey fields
|
# Custom manaegrs for ForeignKey fields
|
||||||
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
||||||
|
|
||||||
part_name = Field(attribute='part__name', readonly=True)
|
part_name = Field(attribute='part__full_ame', readonly=True)
|
||||||
|
|
||||||
supplier_part = Field(attribute='supplier_part', widget=widgets.ForeignKeyWidget(SupplierPart))
|
supplier_part = Field(attribute='supplier_part', widget=widgets.ForeignKeyWidget(SupplierPart))
|
||||||
|
|
||||||
|
supplier = Field(attribute='supplier_part__supplier__id', readonly=True)
|
||||||
|
|
||||||
|
supplier_name = Field(attribute='supplier_part__supplier__name', readonly=True)
|
||||||
|
|
||||||
|
status_label = Field(attribute='status_label', readonly=True)
|
||||||
|
|
||||||
location = Field(attribute='location', widget=widgets.ForeignKeyWidget(StockLocation))
|
location = Field(attribute='location', widget=widgets.ForeignKeyWidget(StockLocation))
|
||||||
|
|
||||||
location_name = Field(attribute='location__name', readonly=True)
|
location_name = Field(attribute='location__name', readonly=True)
|
||||||
@ -82,6 +89,7 @@ class StockItemResource(ModelResource):
|
|||||||
model = StockItem
|
model = StockItem
|
||||||
skip_unchanged = True
|
skip_unchanged = True
|
||||||
report_skipped = False
|
report_skipped = False
|
||||||
|
clean_model_instance = True
|
||||||
|
|
||||||
|
|
||||||
class StockItemAdmin(ImportExportModelAdmin):
|
class StockItemAdmin(ImportExportModelAdmin):
|
||||||
|
@ -140,6 +140,11 @@ class StockItem(models.Model):
|
|||||||
system=True
|
system=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_label(self):
|
||||||
|
|
||||||
|
return StockStatus.label(self.status)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serialized(self):
|
def serialized(self):
|
||||||
""" Return True if this StockItem is serialized """
|
""" Return True if this StockItem is serialized """
|
||||||
|
@ -18,17 +18,16 @@ from InvenTree.views import AjaxView
|
|||||||
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
|
||||||
from InvenTree.views import QRCodeView
|
from InvenTree.views import QRCodeView
|
||||||
|
|
||||||
from InvenTree.status_codes import StockStatus
|
|
||||||
from InvenTree.helpers import str2bool, DownloadFile, GetExportFormats
|
from InvenTree.helpers import str2bool, DownloadFile, GetExportFormats
|
||||||
from InvenTree.helpers import ExtractSerialNumbers
|
from InvenTree.helpers import ExtractSerialNumbers
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import tablib
|
|
||||||
|
|
||||||
from company.models import Company
|
from company.models import Company
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from .models import StockItem, StockLocation, StockItemTracking
|
from .models import StockItem, StockLocation, StockItemTracking
|
||||||
|
|
||||||
|
from .admin import StockItemResource
|
||||||
|
|
||||||
from .forms import EditStockLocationForm
|
from .forms import EditStockLocationForm
|
||||||
from .forms import CreateStockItemForm
|
from .forms import CreateStockItemForm
|
||||||
from .forms import EditStockItemForm
|
from .forms import EditStockItemForm
|
||||||
@ -226,75 +225,9 @@ class StockExport(AjaxView):
|
|||||||
# Pre-fetch related fields to reduce DB queries
|
# Pre-fetch related fields to reduce DB queries
|
||||||
stock_items = stock_items.prefetch_related('part', 'supplier_part__supplier', 'location', 'purchase_order', 'build')
|
stock_items = stock_items.prefetch_related('part', 'supplier_part__supplier', 'location', 'purchase_order', 'build')
|
||||||
|
|
||||||
# Column headers
|
dataset = StockItemResource().export(queryset=stock_items)
|
||||||
headers = [
|
|
||||||
_('Stock ID'),
|
|
||||||
_('Part ID'),
|
|
||||||
_('Part'),
|
|
||||||
_('Supplier Part ID'),
|
|
||||||
_('Supplier ID'),
|
|
||||||
_('Supplier'),
|
|
||||||
_('Location ID'),
|
|
||||||
_('Location'),
|
|
||||||
_('Quantity'),
|
|
||||||
_('Batch'),
|
|
||||||
_('Serial'),
|
|
||||||
_('Status'),
|
|
||||||
_('Notes'),
|
|
||||||
_('Review Needed'),
|
|
||||||
_('Last Updated'),
|
|
||||||
_('Last Stocktake'),
|
|
||||||
_('Purchase Order ID'),
|
|
||||||
_('Build ID'),
|
|
||||||
]
|
|
||||||
|
|
||||||
data = tablib.Dataset(headers=headers)
|
filedata = dataset.export(export_format)
|
||||||
|
|
||||||
for item in stock_items:
|
|
||||||
line = []
|
|
||||||
|
|
||||||
line.append(item.pk)
|
|
||||||
line.append(item.part.pk)
|
|
||||||
line.append(item.part.full_name)
|
|
||||||
|
|
||||||
if item.supplier_part:
|
|
||||||
line.append(item.supplier_part.pk)
|
|
||||||
line.append(item.supplier_part.supplier.pk)
|
|
||||||
line.append(item.supplier_part.supplier.name)
|
|
||||||
else:
|
|
||||||
line.append('')
|
|
||||||
line.append('')
|
|
||||||
line.append('')
|
|
||||||
|
|
||||||
if item.location:
|
|
||||||
line.append(item.location.pk)
|
|
||||||
line.append(item.location.name)
|
|
||||||
else:
|
|
||||||
line.append('')
|
|
||||||
line.append('')
|
|
||||||
|
|
||||||
line.append(item.quantity)
|
|
||||||
line.append(item.batch)
|
|
||||||
line.append(item.serial)
|
|
||||||
line.append(StockStatus.label(item.status))
|
|
||||||
line.append(item.notes)
|
|
||||||
line.append(item.review_needed)
|
|
||||||
line.append(item.updated)
|
|
||||||
line.append(item.stocktake_date)
|
|
||||||
|
|
||||||
if item.purchase_order:
|
|
||||||
line.append(item.purchase_order.pk)
|
|
||||||
else:
|
|
||||||
line.append('')
|
|
||||||
|
|
||||||
if item.build:
|
|
||||||
line.append(item.build.pk)
|
|
||||||
else:
|
|
||||||
line.append('')
|
|
||||||
|
|
||||||
data.append(line)
|
|
||||||
|
|
||||||
filedata = data.export(export_format)
|
|
||||||
|
|
||||||
return DownloadFile(filedata, filename)
|
return DownloadFile(filedata, filename)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user