mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Move "build unallocate" functionality to the API
- Much much simpler now! - Filtering is against bom_item, not part - Fixes a bug with the new (reasonably complex) substitution framework
This commit is contained in:
		@@ -21,7 +21,7 @@ from InvenTree.status_codes import BuildStatus
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from .models import Build, BuildItem, BuildOrderAttachment
 | 
					from .models import Build, BuildItem, BuildOrderAttachment
 | 
				
			||||||
from .serializers import BuildAttachmentSerializer, BuildSerializer, BuildItemSerializer
 | 
					from .serializers import BuildAttachmentSerializer, BuildSerializer, BuildItemSerializer
 | 
				
			||||||
from .serializers import BuildAllocationSerializer
 | 
					from .serializers import BuildAllocationSerializer, BuildUnallocationSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BuildFilter(rest_filters.FilterSet):
 | 
					class BuildFilter(rest_filters.FilterSet):
 | 
				
			||||||
@@ -184,6 +184,42 @@ class BuildDetail(generics.RetrieveUpdateAPIView):
 | 
				
			|||||||
    serializer_class = BuildSerializer
 | 
					    serializer_class = BuildSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BuildUnallocate(generics.CreateAPIView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    API endpoint for unallocating stock items from a build order
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    - The BuildOrder object is specified by the URL
 | 
				
			||||||
 | 
					    - "output" (StockItem) can optionally be specified
 | 
				
			||||||
 | 
					    - "bom_item" can optionally be specified
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    queryset = Build.objects.none()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    serializer_class = BuildUnallocationSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_build(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns the BuildOrder associated with this API endpoint
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pk = self.kwargs.get('pk', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            build = Build.objects.get(pk=pk)
 | 
				
			||||||
 | 
					        except (ValueError, Build.DoesNotExist):
 | 
				
			||||||
 | 
					            raise ValidationError(_("Matching build order does not exist"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_serializer_context(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx = super().get_serializer_context()
 | 
				
			||||||
 | 
					        ctx['build'] = self.get_build()
 | 
				
			||||||
 | 
					        ctx['request'] = self.request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ctx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BuildAllocate(generics.CreateAPIView):
 | 
					class BuildAllocate(generics.CreateAPIView):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    API endpoint to allocate stock items to a build order
 | 
					    API endpoint to allocate stock items to a build order
 | 
				
			||||||
@@ -349,6 +385,7 @@ build_api_urls = [
 | 
				
			|||||||
    # Build Detail
 | 
					    # Build Detail
 | 
				
			||||||
    url(r'^(?P<pk>\d+)/', include([
 | 
					    url(r'^(?P<pk>\d+)/', include([
 | 
				
			||||||
        url(r'^allocate/', BuildAllocate.as_view(), name='api-build-allocate'),
 | 
					        url(r'^allocate/', BuildAllocate.as_view(), name='api-build-allocate'),
 | 
				
			||||||
 | 
					        url(r'^unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),
 | 
				
			||||||
        url(r'^.*$', BuildDetail.as_view(), name='api-build-detail'),
 | 
					        url(r'^.*$', BuildDetail.as_view(), name='api-build-detail'),
 | 
				
			||||||
    ])),
 | 
					    ])),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -137,32 +137,6 @@ class BuildOutputDeleteForm(HelperForm):
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UnallocateBuildForm(HelperForm):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Form for auto-de-allocation of stock from a build
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    confirm = forms.BooleanField(required=False, label=_('Confirm'), help_text=_('Confirm unallocation of stock'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    output_id = forms.IntegerField(
 | 
					 | 
				
			||||||
        required=False,
 | 
					 | 
				
			||||||
        widget=forms.HiddenInput()
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    part_id = forms.IntegerField(
 | 
					 | 
				
			||||||
        required=False,
 | 
					 | 
				
			||||||
        widget=forms.HiddenInput(),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = Build
 | 
					 | 
				
			||||||
        fields = [
 | 
					 | 
				
			||||||
            'confirm',
 | 
					 | 
				
			||||||
            'output_id',
 | 
					 | 
				
			||||||
            'part_id',
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CompleteBuildForm(HelperForm):
 | 
					class CompleteBuildForm(HelperForm):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Form for marking a build as complete
 | 
					    Form for marking a build as complete
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -587,9 +587,13 @@ class Build(MPTTModel):
 | 
				
			|||||||
        self.save()
 | 
					        self.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @transaction.atomic
 | 
					    @transaction.atomic
 | 
				
			||||||
    def unallocateOutput(self, output, part=None):
 | 
					    def unallocateStock(self, bom_item=None, output=None):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Unallocate all stock which are allocated against the provided "output" (StockItem)
 | 
					        Unallocate stock from this Build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        arguments:
 | 
				
			||||||
 | 
					            - bom_item: Specify a particular BomItem to unallocate stock against
 | 
				
			||||||
 | 
					            - output: Specify a particular StockItem (output) to unallocate stock against
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        allocations = BuildItem.objects.filter(
 | 
					        allocations = BuildItem.objects.filter(
 | 
				
			||||||
@@ -597,34 +601,8 @@ class Build(MPTTModel):
 | 
				
			|||||||
            install_into=output
 | 
					            install_into=output
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if part:
 | 
					        if bom_item:
 | 
				
			||||||
            allocations = allocations.filter(stock_item__part=part)
 | 
					            allocations = allocations.filter(bom_item=bom_item)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        allocations.delete()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @transaction.atomic
 | 
					 | 
				
			||||||
    def unallocateUntracked(self, part=None):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Unallocate all "untracked" stock
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        allocations = BuildItem.objects.filter(
 | 
					 | 
				
			||||||
            build=self,
 | 
					 | 
				
			||||||
            install_into=None
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if part:
 | 
					 | 
				
			||||||
            allocations = allocations.filter(stock_item__part=part)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        allocations.delete()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @transaction.atomic
 | 
					 | 
				
			||||||
    def unallocateAll(self):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Deletes all stock allocations for this build.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        allocations = BuildItem.objects.filter(build=self)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        allocations.delete()
 | 
					        allocations.delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -120,6 +120,61 @@ class BuildSerializer(InvenTreeModelSerializer):
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BuildUnallocationSerializer(serializers.Serializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    DRF serializer for unallocating stock from a BuildOrder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Allocated stock can be unallocated with a number of filters:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    - output: Filter against a particular build output (blank = untracked stock)
 | 
				
			||||||
 | 
					    - bom_item: Filter against a particular BOM line item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bom_item = serializers.PrimaryKeyRelatedField(
 | 
				
			||||||
 | 
					        queryset=BomItem.objects.all(),
 | 
				
			||||||
 | 
					        many=False,
 | 
				
			||||||
 | 
					        allow_null=True,
 | 
				
			||||||
 | 
					        required=False,
 | 
				
			||||||
 | 
					        label=_('BOM Item'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    output = serializers.PrimaryKeyRelatedField(
 | 
				
			||||||
 | 
					        queryset=StockItem.objects.filter(
 | 
				
			||||||
 | 
					            is_building=True,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        many=False,
 | 
				
			||||||
 | 
					        allow_null=True,
 | 
				
			||||||
 | 
					        required=False,
 | 
				
			||||||
 | 
					        label=_("Build output"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate_output(self, stock_item):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Stock item must point to the same build order!
 | 
				
			||||||
 | 
					        build = self.context['build']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if stock_item and stock_item.build != build:
 | 
				
			||||||
 | 
					            raise ValidationError(_("Build output must point to the same build"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return stock_item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        'Save' the serializer data.
 | 
				
			||||||
 | 
					        This performs the actual unallocation against the build order
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        build = self.context['build']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = self.validated_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        build.unallocateStock(
 | 
				
			||||||
 | 
					            bom_item=data['bom_item'],
 | 
				
			||||||
 | 
					            output=data['output']
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BuildAllocationItemSerializer(serializers.Serializer):
 | 
					class BuildAllocationItemSerializer(serializers.Serializer):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    A serializer for allocating a single stock item against a build order
 | 
					    A serializer for allocating a single stock item against a build order
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -462,12 +462,9 @@ $("#btn-auto-allocate").on('click', function() {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$('#btn-unallocate').on('click', function() {
 | 
					$('#btn-unallocate').on('click', function() {
 | 
				
			||||||
    launchModalForm(
 | 
					    unallocateStock({{ build.id }}, {
 | 
				
			||||||
        "{% url 'build-unallocate' build.id %}",
 | 
					        table: '#allocation-table-untracked',
 | 
				
			||||||
        {
 | 
					    });
 | 
				
			||||||
            success: reloadTable,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$('#allocate-selected-items').click(function() {
 | 
					$('#allocate-selected-items').click(function() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +0,0 @@
 | 
				
			|||||||
{% extends "modal_form.html" %}
 | 
					 | 
				
			||||||
{% load i18n %}
 | 
					 | 
				
			||||||
{% load inventree_extras %}
 | 
					 | 
				
			||||||
{% block pre_form_content %}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{{ block.super }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<div class='alert alert-block alert-danger'>
 | 
					 | 
				
			||||||
    {% trans "Are you sure you wish to unallocate all stock for this build?" %}
 | 
					 | 
				
			||||||
    <br>
 | 
					 | 
				
			||||||
    {% trans "All incomplete stock allocations will be removed from the build" %}
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{% endblock %}
 | 
					 | 
				
			||||||
@@ -323,22 +323,3 @@ class TestBuildViews(TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        b = Build.objects.get(pk=1)
 | 
					        b = Build.objects.get(pk=1)
 | 
				
			||||||
        self.assertEqual(b.status, 30)  # Build status is now CANCELLED
 | 
					        self.assertEqual(b.status, 30)  # Build status is now CANCELLED
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_build_unallocate(self):
 | 
					 | 
				
			||||||
        """ Test the build unallocation view (ajax form) """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        url = reverse('build-unallocate', args=(1,))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Test without confirmation
 | 
					 | 
				
			||||||
        response = self.client.post(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
 | 
					 | 
				
			||||||
        self.assertEqual(response.status_code, 200)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data = json.loads(response.content)
 | 
					 | 
				
			||||||
        self.assertFalse(data['form_valid'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Test with confirmation
 | 
					 | 
				
			||||||
        response = self.client.post(url, {'confirm': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
 | 
					 | 
				
			||||||
        self.assertEqual(response.status_code, 200)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data = json.loads(response.content)
 | 
					 | 
				
			||||||
        self.assertTrue(data['form_valid'])
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,6 @@ build_detail_urls = [
 | 
				
			|||||||
    url(r'^create-output/', views.BuildOutputCreate.as_view(), name='build-output-create'),
 | 
					    url(r'^create-output/', views.BuildOutputCreate.as_view(), name='build-output-create'),
 | 
				
			||||||
    url(r'^delete-output/', views.BuildOutputDelete.as_view(), name='build-output-delete'),
 | 
					    url(r'^delete-output/', views.BuildOutputDelete.as_view(), name='build-output-delete'),
 | 
				
			||||||
    url(r'^complete-output/', views.BuildOutputComplete.as_view(), name='build-output-complete'),
 | 
					    url(r'^complete-output/', views.BuildOutputComplete.as_view(), name='build-output-complete'),
 | 
				
			||||||
    url(r'^unallocate/', views.BuildUnallocate.as_view(), name='build-unallocate'),
 | 
					 | 
				
			||||||
    url(r'^complete/', views.BuildComplete.as_view(), name='build-complete'),
 | 
					    url(r'^complete/', views.BuildComplete.as_view(), name='build-complete'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    url(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
 | 
					    url(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,14 +10,13 @@ from django.core.exceptions import ValidationError
 | 
				
			|||||||
from django.views.generic import DetailView, ListView
 | 
					from django.views.generic import DetailView, ListView
 | 
				
			||||||
from django.forms import HiddenInput
 | 
					from django.forms import HiddenInput
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from part.models import Part
 | 
					 | 
				
			||||||
from .models import Build
 | 
					from .models import Build
 | 
				
			||||||
from . import forms
 | 
					from . import forms
 | 
				
			||||||
from stock.models import StockLocation, StockItem
 | 
					from stock.models import StockLocation, StockItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from InvenTree.views import AjaxUpdateView, AjaxDeleteView
 | 
					from InvenTree.views import AjaxUpdateView, AjaxDeleteView
 | 
				
			||||||
from InvenTree.views import InvenTreeRoleMixin
 | 
					from InvenTree.views import InvenTreeRoleMixin
 | 
				
			||||||
from InvenTree.helpers import str2bool, extract_serial_numbers, isNull
 | 
					from InvenTree.helpers import str2bool, extract_serial_numbers
 | 
				
			||||||
from InvenTree.status_codes import BuildStatus, StockStatus
 | 
					from InvenTree.status_codes import BuildStatus, StockStatus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -246,88 +245,6 @@ class BuildOutputDelete(AjaxUpdateView):
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BuildUnallocate(AjaxUpdateView):
 | 
					 | 
				
			||||||
    """ View to un-allocate all parts from a build.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Provides a simple confirmation dialog with a BooleanField checkbox.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    model = Build
 | 
					 | 
				
			||||||
    form_class = forms.UnallocateBuildForm
 | 
					 | 
				
			||||||
    ajax_form_title = _("Unallocate Stock")
 | 
					 | 
				
			||||||
    ajax_template_name = "build/unallocate.html"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_initial(self):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        initials = super().get_initial()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Pointing to a particular build output?
 | 
					 | 
				
			||||||
        output = self.get_param('output')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if output:
 | 
					 | 
				
			||||||
            initials['output_id'] = output
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Pointing to a particular part?
 | 
					 | 
				
			||||||
        part = self.get_param('part')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if part:
 | 
					 | 
				
			||||||
            initials['part_id'] = part
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return initials
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def post(self, request, *args, **kwargs):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        build = self.get_object()
 | 
					 | 
				
			||||||
        form = self.get_form()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        confirm = request.POST.get('confirm', False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        output_id = request.POST.get('output_id', None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if output_id:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # If a "null" output is provided, we are trying to unallocate "untracked" stock
 | 
					 | 
				
			||||||
            if isNull(output_id):
 | 
					 | 
				
			||||||
                output = None
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    output = StockItem.objects.get(pk=output_id)
 | 
					 | 
				
			||||||
                except (ValueError, StockItem.DoesNotExist):
 | 
					 | 
				
			||||||
                    output = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        part_id = request.POST.get('part_id', None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            part = Part.objects.get(pk=part_id)
 | 
					 | 
				
			||||||
        except (ValueError, Part.DoesNotExist):
 | 
					 | 
				
			||||||
            part = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        valid = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if confirm is False:
 | 
					 | 
				
			||||||
            form.add_error('confirm', _('Confirm unallocation of build stock'))
 | 
					 | 
				
			||||||
            form.add_error(None, _('Check the confirmation box'))
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            valid = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Unallocate the entire build
 | 
					 | 
				
			||||||
            if not output_id:
 | 
					 | 
				
			||||||
                build.unallocateAll()
 | 
					 | 
				
			||||||
            # Unallocate a single output
 | 
					 | 
				
			||||||
            elif output:
 | 
					 | 
				
			||||||
                build.unallocateOutput(output, part=part)
 | 
					 | 
				
			||||||
            # Unallocate "untracked" parts
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                build.unallocateUntracked(part=part)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data = {
 | 
					 | 
				
			||||||
            'form_valid': valid,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return self.renderJsonResponse(request, form, data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BuildComplete(AjaxUpdateView):
 | 
					class BuildComplete(AjaxUpdateView):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    View to mark the build as complete.
 | 
					    View to mark the build as complete.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -208,15 +208,10 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var pk = $(this).attr('pk');
 | 
					        var pk = $(this).attr('pk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        launchModalForm(
 | 
					        unallocateStock(buildId, {
 | 
				
			||||||
            `/build/${buildId}/unallocate/`,
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                success: reloadTable,
 | 
					 | 
				
			||||||
                data: {
 | 
					 | 
				
			||||||
            output: pk,
 | 
					            output: pk,
 | 
				
			||||||
                }
 | 
					            table: table,
 | 
				
			||||||
            }
 | 
					        });
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $(panel).find(`#button-output-delete-${outputId}`).click(function() {
 | 
					    $(panel).find(`#button-output-delete-${outputId}`).click(function() {
 | 
				
			||||||
@@ -236,6 +231,49 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Unallocate stock against a particular build order
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * Options:
 | 
				
			||||||
 | 
					 * - output: pk value for a stock item "build output"
 | 
				
			||||||
 | 
					 * - bom_item: pk value for a particular BOMItem (build item)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function unallocateStock(build_id, options={}) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var url = `/api/build/${build_id}/unallocate/`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var html = `
 | 
				
			||||||
 | 
					    <div class='alert alert-block alert-warning'>
 | 
				
			||||||
 | 
					    {% trans "Are you sure you wish to unallocate stock items from this build?" %}
 | 
				
			||||||
 | 
					    </dvi>
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructForm(url, {
 | 
				
			||||||
 | 
					        method: 'POST',
 | 
				
			||||||
 | 
					        confirm: true,
 | 
				
			||||||
 | 
					        preFormContent: html,
 | 
				
			||||||
 | 
					        fields: {
 | 
				
			||||||
 | 
					            output: {
 | 
				
			||||||
 | 
					                hidden: true,
 | 
				
			||||||
 | 
					                value: options.output,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            bom_item: {
 | 
				
			||||||
 | 
					                hidden: true,
 | 
				
			||||||
 | 
					                value: options.bom_item,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        title: '{% trans "Unallocate Stock Items" %}',
 | 
				
			||||||
 | 
					        onSuccess: function(response, opts) {
 | 
				
			||||||
 | 
					            if (options.table) {
 | 
				
			||||||
 | 
					                // Reload the parent table
 | 
				
			||||||
 | 
					                $(options.table).bootstrapTable('refresh');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function loadBuildOrderAllocationTable(table, options={}) {
 | 
					function loadBuildOrderAllocationTable(table, options={}) {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Load a table showing all the BuildOrder allocations for a given part
 | 
					     * Load a table showing all the BuildOrder allocations for a given part
 | 
				
			||||||
@@ -469,17 +507,16 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Callback for 'unallocate' button
 | 
					        // Callback for 'unallocate' button
 | 
				
			||||||
        $(table).find('.button-unallocate').click(function() {
 | 
					        $(table).find('.button-unallocate').click(function() {
 | 
				
			||||||
            var pk = $(this).attr('pk');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            launchModalForm(`/build/${buildId}/unallocate/`,
 | 
					            // Extract row data from the table
 | 
				
			||||||
                {
 | 
					            var idx = $(this).closest('tr').attr('data-index');
 | 
				
			||||||
                    success: reloadTable,
 | 
					            var row = $(table).bootstrapTable('getData')[idx];
 | 
				
			||||||
                    data: {
 | 
					
 | 
				
			||||||
                        output: outputId,
 | 
					            unallocateStock(buildId, {
 | 
				
			||||||
                        part: pk,
 | 
					                bom_item: row.pk,
 | 
				
			||||||
                    }
 | 
					                output: outputId == 'untracked' ? null : outputId,
 | 
				
			||||||
                }
 | 
					                table: table,
 | 
				
			||||||
            );
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user