mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Lots of work towards multiple build output
This commit is contained in:
@ -95,6 +95,9 @@ class BuildAutoAllocate(AjaxUpdateView):
|
||||
role_required = 'build.change'
|
||||
|
||||
def get_initial(self):
|
||||
"""
|
||||
Initial values for the form.
|
||||
"""
|
||||
|
||||
initials = super().get_initial()
|
||||
|
||||
@ -102,62 +105,73 @@ class BuildAutoAllocate(AjaxUpdateView):
|
||||
output = self.get_param('output')
|
||||
|
||||
if output:
|
||||
initials['output_id'] = output
|
||||
try:
|
||||
output = StockItem.objects.get(pk=output)
|
||||
initials['output'] = output
|
||||
except (ValueError, StockItem.DoesNotExist):
|
||||
pass
|
||||
|
||||
return initials
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
""" Get the context data for form rendering. """
|
||||
"""
|
||||
Get the context data for form rendering.
|
||||
"""
|
||||
|
||||
context = {}
|
||||
|
||||
output = self.get_form()['output_id'].value()
|
||||
build = self.get_object()
|
||||
|
||||
form = self.get_form()
|
||||
|
||||
output_id = form['output'].value()
|
||||
|
||||
try:
|
||||
build = Build.objects.get(id=self.kwargs['pk'])
|
||||
context['build'] = build
|
||||
output = StockItem.objects.get(pk=output_id)
|
||||
except (ValueError, StockItem.DoesNotExist):
|
||||
output = None
|
||||
|
||||
if output:
|
||||
context['output'] = output
|
||||
context['allocations'] = build.getAutoAllocations(output)
|
||||
except Build.DoesNotExist:
|
||||
context['error'] = _('No matching build found')
|
||||
|
||||
context['build'] = build
|
||||
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
""" Handle POST request. Perform auto allocations.
|
||||
def get_form(self):
|
||||
|
||||
- If the form validation passes, perform allocations
|
||||
- Otherwise, the form is passed back to the client
|
||||
form = super().get_form()
|
||||
|
||||
if form['output'].value():
|
||||
# Hide the 'output' field
|
||||
form.fields['output'].widget = HiddenInput()
|
||||
|
||||
return form
|
||||
|
||||
def validate(self, build, form, **kwargs):
|
||||
|
||||
output = form.cleaned_data.get('output', None)
|
||||
|
||||
if not output:
|
||||
form.add_error(None, _('Build output must be specified'))
|
||||
|
||||
def post_save(self, build, form, **kwargs):
|
||||
"""
|
||||
Once the form has been validated,
|
||||
perform auto-allocations
|
||||
"""
|
||||
|
||||
build = self.get_object()
|
||||
form = self.get_form()
|
||||
output = form.cleaned_data.get('output', None)
|
||||
|
||||
confirm = request.POST.get('confirm', False)
|
||||
build.autoAllocate(output)
|
||||
|
||||
output = None
|
||||
output_id = request.POST.get('output_id', None)
|
||||
|
||||
if output_id:
|
||||
try:
|
||||
output = StockItem.objects.get(pk=output_id)
|
||||
except (ValueError, StockItem.DoesNotExist):
|
||||
pass
|
||||
|
||||
valid = False
|
||||
|
||||
if confirm:
|
||||
build.auto_allocate(output)
|
||||
valid = True
|
||||
else:
|
||||
form.add_error('confirm', _('Confirm stock allocation'))
|
||||
form.add_error(None, _('Check the confirmation box at the bottom of the list'))
|
||||
|
||||
data = {
|
||||
'form_valid': valid,
|
||||
def get_data(self):
|
||||
return {
|
||||
'success': _('Allocated stock to build output'),
|
||||
}
|
||||
|
||||
return self.renderJsonResponse(request, form, data, context=self.get_context_data())
|
||||
|
||||
|
||||
class BuildOutputDelete(AjaxUpdateView):
|
||||
"""
|
||||
@ -292,37 +306,60 @@ class BuildComplete(AjaxUpdateView):
|
||||
model = Build
|
||||
form_class = forms.CompleteBuildForm
|
||||
context_object_name = "build"
|
||||
ajax_form_title = _("Complete Build")
|
||||
ajax_form_title = _("Complete Build Output")
|
||||
ajax_template_name = "build/complete.html"
|
||||
role_required = 'build.change'
|
||||
|
||||
def get_form(self):
|
||||
""" Get the form object.
|
||||
|
||||
If the part is trackable, include a field for serial numbers.
|
||||
"""
|
||||
|
||||
build = self.get_object()
|
||||
|
||||
form = super().get_form()
|
||||
|
||||
if not build.part.trackable:
|
||||
form.fields.pop('serial_numbers')
|
||||
else:
|
||||
# Extract the build output object
|
||||
output = None
|
||||
output_id = form['output'].value()
|
||||
|
||||
form.field_placeholder['serial_numbers'] = build.part.getSerialNumberString(build.quantity)
|
||||
try:
|
||||
output = StockItem.objects.get(pk=output_id)
|
||||
except (ValueError, StockItem.DoesNotExist):
|
||||
pass
|
||||
|
||||
form.rebuild_layout()
|
||||
if output:
|
||||
if build.isFullyAllocated(output):
|
||||
form.fields['confirm_incomplete'].widget = HiddenInput()
|
||||
|
||||
return form
|
||||
|
||||
def validate(self, build, form, **kwargs):
|
||||
|
||||
data = form.cleaned_data
|
||||
|
||||
output = data.get('output', None)
|
||||
|
||||
if output:
|
||||
|
||||
quantity = data.get('quantity', None)
|
||||
|
||||
if quantity and quantity > output.quantity:
|
||||
form.add_error('quantity', _('Quantity to complete cannot exceed build output quantity'))
|
||||
|
||||
if not build.isFullyAllocated(output):
|
||||
confirm = str2bool(data.get('confirm_incomplete', False))
|
||||
|
||||
if not confirm:
|
||||
form.add_error('confirm_incomplete', _('Confirm completion of incomplete build'))
|
||||
|
||||
else:
|
||||
form.add_error(None, _('Build output must be specified'))
|
||||
|
||||
def get_initial(self):
|
||||
""" Get initial form data for the CompleteBuild form
|
||||
|
||||
- If the part being built has a default location, pre-select that location
|
||||
"""
|
||||
|
||||
initials = super(BuildComplete, self).get_initial().copy()
|
||||
|
||||
initials = super().get_initial()
|
||||
build = self.get_object()
|
||||
|
||||
if build.part.default_location is not None:
|
||||
@ -332,94 +369,77 @@ class BuildComplete(AjaxUpdateView):
|
||||
except StockLocation.DoesNotExist:
|
||||
pass
|
||||
|
||||
output = self.get_param('output', None)
|
||||
|
||||
if output:
|
||||
try:
|
||||
output = StockItem.objects.get(pk=output)
|
||||
except (ValueError, StockItem.DoesNotExist):
|
||||
output = None
|
||||
|
||||
# Output has not been supplied? Try to "guess"
|
||||
if not output:
|
||||
|
||||
incomplete = build.get_build_outputs(complete=False)
|
||||
|
||||
if incomplete.count() == 1:
|
||||
output = incomplete[0]
|
||||
|
||||
if output is not None:
|
||||
initials['output'] = output
|
||||
|
||||
initials['location'] = build.destination
|
||||
|
||||
return initials
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
""" Get context data for passing to the rendered form
|
||||
"""
|
||||
Get context data for passing to the rendered form
|
||||
|
||||
- Build information is required
|
||||
"""
|
||||
|
||||
build = Build.objects.get(id=self.kwargs['pk'])
|
||||
build = self.get_object()
|
||||
|
||||
context = {}
|
||||
|
||||
# Build object
|
||||
context['build'] = build
|
||||
|
||||
# Items to be removed from stock
|
||||
taking = BuildItem.objects.filter(build=build.id)
|
||||
context['taking'] = taking
|
||||
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
""" Handle POST request. Mark the build as COMPLETE
|
||||
|
||||
- If the form validation passes, the Build objects completeBuild() method is called
|
||||
- Otherwise, the form is passed back to the client
|
||||
"""
|
||||
|
||||
build = self.get_object()
|
||||
|
||||
form = self.get_form()
|
||||
|
||||
confirm = str2bool(request.POST.get('confirm', False))
|
||||
output = form['output'].value()
|
||||
|
||||
loc_id = request.POST.get('location', None)
|
||||
|
||||
valid = False
|
||||
|
||||
if confirm is False:
|
||||
form.add_error('confirm', _('Confirm completion of build'))
|
||||
else:
|
||||
if output:
|
||||
try:
|
||||
location = StockLocation.objects.get(id=loc_id)
|
||||
valid = True
|
||||
except (ValueError, StockLocation.DoesNotExist):
|
||||
form.add_error('location', _('Invalid location selected'))
|
||||
output = StockItem.objects.get(pk=output)
|
||||
context['output'] = output
|
||||
context['fully_allocated'] = build.isFullyAllocated(output)
|
||||
context['allocated_parts'] = build.allocatedParts(output)
|
||||
context['unallocated_parts'] = build.unallocatedParts(output)
|
||||
except (ValueError, StockItem.DoesNotExist):
|
||||
pass
|
||||
|
||||
serials = []
|
||||
return context
|
||||
|
||||
if build.part.trackable:
|
||||
# A build for a trackable part may optionally specify serial numbers.
|
||||
def post_save(self, build, form, **kwargs):
|
||||
|
||||
sn = request.POST.get('serial_numbers', '')
|
||||
data = form.cleaned_data
|
||||
|
||||
sn = str(sn).strip()
|
||||
|
||||
# If the user has specified serial numbers, check they are valid
|
||||
if len(sn) > 0:
|
||||
try:
|
||||
# Exctract a list of provided serial numbers
|
||||
serials = ExtractSerialNumbers(sn, build.quantity)
|
||||
|
||||
existing = build.part.find_conflicting_serial_numbers(serials)
|
||||
|
||||
if len(existing) > 0:
|
||||
exists = ",".join([str(x) for x in existing])
|
||||
form.add_error('serial_numbers', _('The following serial numbers already exist: ({sn})'.format(sn=exists)))
|
||||
valid = False
|
||||
|
||||
except ValidationError as e:
|
||||
form.add_error('serial_numbers', e.messages)
|
||||
valid = False
|
||||
|
||||
if valid:
|
||||
if not build.completeBuild(location, serials, request.user):
|
||||
form.add_error(None, _('Build could not be completed'))
|
||||
valid = False
|
||||
|
||||
data = {
|
||||
'form_valid': valid,
|
||||
}
|
||||
|
||||
return self.renderJsonResponse(request, form, data, context=self.get_context_data())
|
||||
location = data.get('location', None)
|
||||
output = data.get('output', None)
|
||||
|
||||
# Complete the build output
|
||||
build.completeBuildOutput(
|
||||
output,
|
||||
self.request.user,
|
||||
location=location,
|
||||
)
|
||||
|
||||
def get_data(self):
|
||||
""" Provide feedback data back to the form """
|
||||
return {
|
||||
'info': _('Build marked as COMPLETE')
|
||||
'success': _('Build output completed')
|
||||
}
|
||||
|
||||
|
||||
@ -588,19 +608,6 @@ class BuildCreate(AjaxCreateView):
|
||||
'serial_numbers',
|
||||
_('Serial numbers already exist') + ': ' + msg
|
||||
)
|
||||
|
||||
def post_save(self, **kwargs):
|
||||
"""
|
||||
Called immediately after a new Build object is created.
|
||||
"""
|
||||
|
||||
build = kwargs['new_object']
|
||||
request = kwargs['request']
|
||||
data = kwargs['data']
|
||||
|
||||
serials = data['serial_numbers']
|
||||
|
||||
build.createInitialStockItem(serials, request.user)
|
||||
|
||||
|
||||
class BuildUpdate(AjaxUpdateView):
|
||||
@ -684,11 +691,13 @@ class BuildItemCreate(AjaxCreateView):
|
||||
|
||||
return ctx
|
||||
|
||||
def validate(self, request, form, data):
|
||||
def validate(self, build_item, form, **kwargs):
|
||||
"""
|
||||
Extra validation steps as required
|
||||
"""
|
||||
|
||||
data = form.cleaned_data
|
||||
|
||||
stock_item = data.get('stock_item', None)
|
||||
quantity = data.get('quantity', None)
|
||||
|
||||
@ -702,7 +711,9 @@ class BuildItemCreate(AjaxCreateView):
|
||||
available = stock_item.unallocated_quantity()
|
||||
if quantity > available:
|
||||
form.add_error('stock_item', _('Stock item is over-allocated'))
|
||||
form.add_error('quantity', _('Avaialabe') + ': ' + str(normalize(available)))
|
||||
form.add_error('quantity', _('Available') + ': ' + str(normalize(available)))
|
||||
else:
|
||||
form.add_error('stock_item', _('Stock item must be selected'))
|
||||
|
||||
def get_form(self):
|
||||
""" Create Form for making / editing new Part object """
|
||||
@ -758,7 +769,7 @@ class BuildItemCreate(AjaxCreateView):
|
||||
form.fields['install_into'].widget = HiddenInput()
|
||||
|
||||
if self.build and self.part:
|
||||
available_items = self.build.getAvailableStockItems(part=self.part, output=self.output)
|
||||
available_items = self.build.availableStockItems(self.part, self.output)
|
||||
form.fields['stock_item'].queryset = available_items
|
||||
|
||||
self.available_stock = form.fields['stock_item'].queryset.all()
|
||||
@ -819,7 +830,7 @@ class BuildItemCreate(AjaxCreateView):
|
||||
|
||||
# Work out how much stock is required
|
||||
if build and part:
|
||||
required_quantity = build.getUnallocatedQuantity(part, output=output)
|
||||
required_quantity = build.unallocatedQuantity(part, output)
|
||||
else:
|
||||
required_quantity = None
|
||||
|
||||
|
Reference in New Issue
Block a user