2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-21 06:16:29 +00:00

Adds function to duplicate a BOM from a parent part

- Improves form validation workflow
- More 'djangoesque'
This commit is contained in:
Oliver Walters
2020-10-30 10:08:06 +11:00
parent a148cfe593
commit 2d583d19c2
10 changed files with 706 additions and 407 deletions

View File

@ -19,10 +19,15 @@ from .models import PartParameterTemplate, PartParameter
from .models import PartTestTemplate
from .models import PartSellPriceBreak
from common.models import Currency
class PartModelChoiceField(forms.ModelChoiceField):
""" Extending string representation of Part instance with available stock """
def label_from_instance(self, part):
return f'{part} - {part.available_stock}'
class PartImageForm(HelperForm):
""" Form for uploading a Part image """
@ -77,6 +82,38 @@ class BomExportForm(forms.Form):
self.fields['file_format'].choices = self.get_choices()
class BomDuplicateForm(HelperForm):
"""
Simple confirmation form for BOM duplication.
Select which parent to select from.
"""
parent = PartModelChoiceField(
label=_('Parent Part'),
help_text=_('Select parent part to copy BOM from'),
queryset=Part.objects.filter(is_template=True),
)
clear = forms.BooleanField(
required=False, initial=True,
help_text=_('Clear existing BOM items')
)
confirm = forms.BooleanField(
required=False, initial=False,
help_text=_('Confirm BOM duplication')
)
class Meta:
model = Part
fields = [
'parent',
'clear',
'confirm',
]
class BomValidateForm(HelperForm):
""" Simple confirmation form for BOM validation.
User is presented with a single checkbox input,
@ -210,12 +247,6 @@ class EditCategoryForm(HelperForm):
]
class PartModelChoiceField(forms.ModelChoiceField):
""" Extending string representation of Part instance with available stock """
def label_from_instance(self, part):
return f'{part} - {part.available_stock}'
class EditBomItemForm(HelperForm):
""" Form for editing a BomItem object """

View File

@ -1087,6 +1087,35 @@ class Part(MPTTModel):
max(buy_price_range[1], bom_price_range[1])
)
@transaction.atomic
def copy_bom_from(self, other, clear=True, **kwargs):
"""
Copy the BOM from another part.
args:
other - The part to copy the BOM from
clear - Remove existing BOM items first (default=True)
"""
if clear:
# Remove existing BOM items
self.bom_items.all().delete()
for bom_item in other.bom_items.all():
# If this part already has a BomItem pointing to the same sub-part,
# delete that BomItem from this part first!
try:
existing = BomItem.objects.get(part=self, sub_part=bom_item.sub_part)
existing.delete()
except (BomItem.DoesNotExist):
pass
bom_item.part = self
bom_item.pk = None
bom_item.save()
def deepCopy(self, other, **kwargs):
""" Duplicates non-field data from another part.
Does not alter the normal fields of this part,
@ -1106,12 +1135,7 @@ class Part(MPTTModel):
# Copy the BOM data
if kwargs.get('bom', False):
for item in other.bom_items.all():
# Point the item to THIS part.
# Set the pk to None so a new entry is created.
item.part = self
item.pk = None
item.save()
self.copy_bom_from(other)
# Copy the parameters data
if kwargs.get('parameters', True):

View File

@ -39,8 +39,13 @@
<span class='fas fa-trash-alt'></span>
</button>
<button class='btn btn-primary' type='button' title='{% trans "Import BOM data" %}' id='bom-upload'>
<span class='fas fa-file-upload'></span> {% trans "Upload" %}
<span class='fas fa-file-upload'></span> {% trans "Import from File" %}
</button>
{% if part.variant_of %}
<button class='btn btn-default' type='button' title='{% trans "Copy BOM from parent part" %}' id='bom-duplicate'>
<span class='fas fa-clone'></span> {% trans "Copy from Parent" %}
</button>
{% endif %}
<button class='btn btn-default' type='button' title='{% trans "New BOM Item" %}' id='bom-item-new'>
<span class='fas fa-plus-circle'></span> {% trans "Add Item" %}
</button>
@ -157,6 +162,17 @@
location.href = "{% url 'upload-bom' part.id %}";
});
$('#bom-duplicate').click(function() {
launchModalForm(
"{% url 'duplicate-bom' part.id %}",
{
success: function() {
$('#bom-table').bootstrapTable('refresh');
}
}
);
});
$("#bom-item-new").click(function () {
launchModalForm(
"{% url 'bom-item-create' %}?parent={{ part.id }}",

View File

@ -0,0 +1,17 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %}
<p>
{% trans "Select parent part to copy BOM from" %}
</p>
{% if part.has_bom %}
<div class='alert alert-block alert-danger'>
<b>{% trans "Warning" %}</b><br>
{% trans "This part already has a Bill of Materials" %}<br>
</div>
{% endif %}
{% endblock %}

View File

@ -46,6 +46,7 @@ part_detail_urls = [
url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'),
url(r'^bom-upload/?', views.BomUpload.as_view(), name='upload-bom'),
url(r'^bom-duplicate/?', views.BomDuplicate.as_view(), name='duplicate-bom'),
url(r'^params/', views.PartDetail.as_view(template_name='part/params.html'), name='part-params'),
url(r'^variants/?', views.PartDetail.as_view(template_name='part/variants.html'), name='part-variants'),

View File

@ -831,8 +831,60 @@ class PartEdit(AjaxUpdateView):
return form
class BomDuplicate(AjaxUpdateView):
"""
View for duplicating BOM from a parent item.
"""
model = Part
context_object_name = 'part'
ajax_form_title = _('Duplicate BOM')
ajax_template_name = 'part/bom_duplicate.html'
form_class = part_forms.BomDuplicateForm
role_required = 'part.change'
def get_form(self):
form = super().get_form()
# Limit choices to parents of the current part
parents = self.get_object().get_ancestors()
form.fields['parent'].queryset = parents
return form
def get_initial(self):
initials = super().get_initial()
parents = self.get_object().get_ancestors()
if parents.count() == 1:
initials['parent'] = parents[0]
return initials
def validate(self, part, form):
confirm = str2bool(form.cleaned_data.get('confirm', False))
if not confirm:
form.add_error('confirm', _('Confirm duplication of BOM from parent'))
def post_save(self, part, form):
parent = form.cleaned_data.get('parent', None)
clear = str2bool(form.cleaned_data.get('clear', True))
if parent:
part.copy_bom_from(parent, clear=clear)
class BomValidate(AjaxUpdateView):
""" Modal form view for validating a part BOM """
"""
Modal form view for validating a part BOM
"""
model = Part
ajax_form_title = _("Validate BOM")