Lots of work towards multiple build output

This commit is contained in:
Oliver Walters
2020-11-02 01:24:31 +11:00
parent f1b83f1c17
commit b02c87ea50
16 changed files with 487 additions and 561 deletions

View File

@ -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