mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #1902 from SchrodingersGat/bom-item-form
Use API forms for creating and editing BomItem objects
This commit is contained in:
commit
5aa111b0aa
@ -18,7 +18,6 @@ import common.models
|
|||||||
from common.forms import MatchItemForm
|
from common.forms import MatchItemForm
|
||||||
|
|
||||||
from .models import Part, PartCategory, PartRelated
|
from .models import Part, PartCategory, PartRelated
|
||||||
from .models import BomItem
|
|
||||||
from .models import PartParameterTemplate, PartParameter
|
from .models import PartParameterTemplate, PartParameter
|
||||||
from .models import PartCategoryParameterTemplate
|
from .models import PartCategoryParameterTemplate
|
||||||
from .models import PartSellPriceBreak, PartInternalPriceBreak
|
from .models import PartSellPriceBreak, PartInternalPriceBreak
|
||||||
@ -317,33 +316,6 @@ class EditCategoryParameterTemplateForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class EditBomItemForm(HelperForm):
|
|
||||||
""" Form for editing a BomItem object """
|
|
||||||
|
|
||||||
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
|
|
||||||
|
|
||||||
sub_part = PartModelChoiceField(queryset=Part.objects.all(), label=_('Sub part'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = BomItem
|
|
||||||
fields = [
|
|
||||||
'part',
|
|
||||||
'sub_part',
|
|
||||||
'quantity',
|
|
||||||
'reference',
|
|
||||||
'overage',
|
|
||||||
'note',
|
|
||||||
'allow_variants',
|
|
||||||
'inherited',
|
|
||||||
'optional',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Prevent editing of the part associated with this BomItem
|
|
||||||
widgets = {
|
|
||||||
'part': forms.HiddenInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PartPriceForm(forms.Form):
|
class PartPriceForm(forms.Form):
|
||||||
""" Simple form for viewing part pricing information """
|
""" Simple form for viewing part pricing information """
|
||||||
|
|
||||||
|
@ -440,22 +440,22 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("#bom-item-new").click(function () {
|
$("#bom-item-new").click(function () {
|
||||||
launchModalForm(
|
|
||||||
"{% url 'bom-item-create' %}?parent={{ part.id }}",
|
var fields = bomItemFields();
|
||||||
{
|
|
||||||
success: function() {
|
fields.part.value = {{ part.pk }};
|
||||||
$("#bom-table").bootstrapTable('refresh');
|
fields.sub_part.filters = {
|
||||||
},
|
active: true,
|
||||||
secondary: [
|
};
|
||||||
{
|
|
||||||
field: 'sub_part',
|
constructForm('{% url "api-bom-list" %}', {
|
||||||
label: '{% trans "New Part" %}',
|
fields: fields,
|
||||||
title: '{% trans "Create New Part" %}',
|
method: 'POST',
|
||||||
url: "{% url 'part-create' %}",
|
title: '{% trans "Create BOM Item" %}',
|
||||||
},
|
onSuccess: function() {
|
||||||
]
|
$('#bom-table').bootstrapTable('refresh');
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -259,22 +259,3 @@ class CategoryTest(PartViewTestCase):
|
|||||||
|
|
||||||
response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
class BomItemTests(PartViewTestCase):
|
|
||||||
""" Tests for BomItem related views """
|
|
||||||
|
|
||||||
def test_create_valid_parent(self):
|
|
||||||
""" Create a BomItem for a valid part """
|
|
||||||
response = self.client.get(reverse('bom-item-create'), {'parent': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_create_no_parent(self):
|
|
||||||
""" Create a BomItem without a parent """
|
|
||||||
response = self.client.get(reverse('bom-item-create'), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_create_invalid_parent(self):
|
|
||||||
""" Create a BomItem with an invalid parent """
|
|
||||||
response = self.client.get(reverse('bom-item-create'), {'parent': 99999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
@ -78,10 +78,6 @@ category_urls = [
|
|||||||
]))
|
]))
|
||||||
]
|
]
|
||||||
|
|
||||||
part_bom_urls = [
|
|
||||||
url(r'^edit/?', views.BomItemEdit.as_view(), name='bom-item-edit'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# URL list for part web interface
|
# URL list for part web interface
|
||||||
part_urls = [
|
part_urls = [
|
||||||
|
|
||||||
@ -92,9 +88,6 @@ part_urls = [
|
|||||||
url(r'^import/', views.PartImport.as_view(), name='part-import'),
|
url(r'^import/', views.PartImport.as_view(), name='part-import'),
|
||||||
url(r'^import-api/', views.PartImportAjax.as_view(), name='api-part-import'),
|
url(r'^import-api/', views.PartImportAjax.as_view(), name='api-part-import'),
|
||||||
|
|
||||||
# Create a new BOM item
|
|
||||||
url(r'^bom/new/?', views.BomItemCreate.as_view(), name='bom-item-create'),
|
|
||||||
|
|
||||||
# Download a BOM upload template
|
# Download a BOM upload template
|
||||||
url(r'^bom_template/?', views.BomUploadTemplate.as_view(), name='bom-upload-template'),
|
url(r'^bom_template/?', views.BomUploadTemplate.as_view(), name='bom-upload-template'),
|
||||||
|
|
||||||
@ -122,9 +115,6 @@ part_urls = [
|
|||||||
# Change category for multiple parts
|
# Change category for multiple parts
|
||||||
url(r'^set-category/?', views.PartSetCategory.as_view(), name='part-set-category'),
|
url(r'^set-category/?', views.PartSetCategory.as_view(), name='part-set-category'),
|
||||||
|
|
||||||
# Bom Items
|
|
||||||
url(r'^bom/(?P<pk>\d+)/', include(part_bom_urls)),
|
|
||||||
|
|
||||||
# Individual part using IPN as slug
|
# Individual part using IPN as slug
|
||||||
url(r'^(?P<slug>[-\w]+)/', views.PartDetailFromIPN.as_view(), name='part-detail-from-ipn'),
|
url(r'^(?P<slug>[-\w]+)/', views.PartDetailFromIPN.as_view(), name='part-detail-from-ipn'),
|
||||||
|
|
||||||
|
@ -2078,134 +2078,6 @@ class CategoryParameterTemplateDelete(AjaxDeleteView):
|
|||||||
return self.object
|
return self.object
|
||||||
|
|
||||||
|
|
||||||
class BomItemCreate(AjaxCreateView):
|
|
||||||
"""
|
|
||||||
Create view for making a new BomItem object
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = BomItem
|
|
||||||
form_class = part_forms.EditBomItemForm
|
|
||||||
ajax_template_name = 'modal_form.html'
|
|
||||||
ajax_form_title = _('Create BOM Item')
|
|
||||||
|
|
||||||
def get_form(self):
|
|
||||||
""" Override get_form() method to reduce Part selection options.
|
|
||||||
|
|
||||||
- Do not allow part to be added to its own BOM
|
|
||||||
- Remove any Part items that are already in the BOM
|
|
||||||
"""
|
|
||||||
|
|
||||||
form = super(AjaxCreateView, self).get_form()
|
|
||||||
|
|
||||||
part_id = form['part'].value()
|
|
||||||
|
|
||||||
# Construct a queryset for the part field
|
|
||||||
part_query = Part.objects.filter(active=True)
|
|
||||||
|
|
||||||
# Construct a queryset for the sub_part field
|
|
||||||
sub_part_query = Part.objects.filter(
|
|
||||||
component=True,
|
|
||||||
active=True
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
part = Part.objects.get(id=part_id)
|
|
||||||
|
|
||||||
# Hide the 'part' field
|
|
||||||
form.fields['part'].widget = HiddenInput()
|
|
||||||
|
|
||||||
# Exclude the part from its own BOM
|
|
||||||
sub_part_query = sub_part_query.exclude(id=part.id)
|
|
||||||
|
|
||||||
# Eliminate any options that are already in the BOM!
|
|
||||||
sub_part_query = sub_part_query.exclude(id__in=[item.id for item in part.getRequiredParts()])
|
|
||||||
|
|
||||||
except (ValueError, Part.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Set the querysets for the fields
|
|
||||||
form.fields['part'].queryset = part_query
|
|
||||||
form.fields['sub_part'].queryset = sub_part_query
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
""" Provide initial data for the BomItem:
|
|
||||||
|
|
||||||
- If 'parent' provided, set the parent part field
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Look for initial values
|
|
||||||
initials = super(BomItemCreate, self).get_initial().copy()
|
|
||||||
|
|
||||||
# Parent part for this item?
|
|
||||||
parent_id = self.request.GET.get('parent', None)
|
|
||||||
|
|
||||||
if parent_id:
|
|
||||||
try:
|
|
||||||
initials['part'] = Part.objects.get(pk=parent_id)
|
|
||||||
except Part.DoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return initials
|
|
||||||
|
|
||||||
|
|
||||||
class BomItemEdit(AjaxUpdateView):
|
|
||||||
""" Update view for editing BomItem """
|
|
||||||
|
|
||||||
model = BomItem
|
|
||||||
form_class = part_forms.EditBomItemForm
|
|
||||||
ajax_template_name = 'modal_form.html'
|
|
||||||
ajax_form_title = _('Edit BOM item')
|
|
||||||
|
|
||||||
def get_form(self):
|
|
||||||
""" Override get_form() method to filter part selection options
|
|
||||||
|
|
||||||
- Do not allow part to be added to its own BOM
|
|
||||||
- Remove any part items that are already in the BOM
|
|
||||||
"""
|
|
||||||
|
|
||||||
item = self.get_object()
|
|
||||||
|
|
||||||
form = super().get_form()
|
|
||||||
|
|
||||||
part_id = form['part'].value()
|
|
||||||
|
|
||||||
try:
|
|
||||||
part = Part.objects.get(pk=part_id)
|
|
||||||
|
|
||||||
# Construct a queryset
|
|
||||||
query = Part.objects.filter(component=True)
|
|
||||||
|
|
||||||
# Limit to "active" items, *unless* the currently selected item is not active
|
|
||||||
if item.sub_part.active:
|
|
||||||
query = query.filter(active=True)
|
|
||||||
|
|
||||||
# Prevent the parent part from being selected
|
|
||||||
query = query.exclude(pk=part_id)
|
|
||||||
|
|
||||||
# Eliminate any options that are already in the BOM,
|
|
||||||
# *except* for the item which is already selected
|
|
||||||
try:
|
|
||||||
sub_part_id = int(form['sub_part'].value())
|
|
||||||
except ValueError:
|
|
||||||
sub_part_id = -1
|
|
||||||
|
|
||||||
existing = [item.pk for item in part.getRequiredParts()]
|
|
||||||
|
|
||||||
if sub_part_id in existing:
|
|
||||||
existing.remove(sub_part_id)
|
|
||||||
|
|
||||||
query = query.exclude(id__in=existing)
|
|
||||||
|
|
||||||
form.fields['sub_part'].queryset = query
|
|
||||||
|
|
||||||
except (ValueError, Part.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
|
|
||||||
class PartSalePriceBreakCreate(AjaxCreateView):
|
class PartSalePriceBreakCreate(AjaxCreateView):
|
||||||
"""
|
"""
|
||||||
View for creating a sale price break for a part
|
View for creating a sale price break for a part
|
||||||
|
@ -8,6 +8,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
function bomItemFields() {
|
||||||
|
|
||||||
|
return {
|
||||||
|
part: {
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
sub_part: {
|
||||||
|
},
|
||||||
|
quantity: {},
|
||||||
|
reference: {},
|
||||||
|
overage: {},
|
||||||
|
note: {},
|
||||||
|
allow_variants: {},
|
||||||
|
inherited: {},
|
||||||
|
optional: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function reloadBomTable(table, options) {
|
function reloadBomTable(table, options) {
|
||||||
|
|
||||||
table.bootstrapTable('refresh');
|
table.bootstrapTable('refresh');
|
||||||
@ -528,14 +548,15 @@ function loadBomTable(table, options) {
|
|||||||
var pk = $(this).attr('pk');
|
var pk = $(this).attr('pk');
|
||||||
var url = `/part/bom/${pk}/edit/`;
|
var url = `/part/bom/${pk}/edit/`;
|
||||||
|
|
||||||
launchModalForm(
|
var fields = bomItemFields();
|
||||||
url,
|
|
||||||
{
|
constructForm(`/api/bom/${pk}/`, {
|
||||||
success: function() {
|
fields: fields,
|
||||||
reloadBomTable(table);
|
title: '{% trans "Edit BOM Item" %}',
|
||||||
}
|
onSuccess: function() {
|
||||||
|
reloadBomTable(table);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on('click', '.bom-validate-button', function() {
|
table.on('click', '.bom-validate-button', function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user