Adds a new API endpoint for creating build outputs

This commit is contained in:
Oliver 2022-02-15 12:51:48 +11:00
parent dc4c1f138b
commit f90a27d01d
4 changed files with 213 additions and 0 deletions

View File

@ -232,6 +232,29 @@ class BuildUnallocate(generics.CreateAPIView):
return ctx
class BuildOutputCreate(generics.CreateAPIView):
"""
API endpoint for creating new build output(s)
"""
queryset = Build.objects.none()
serializer_class = build.serializers.BuildOutputCreateSerializer
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['request'] = self.request
ctx['to_complete'] = True
try:
ctx['build'] = Build.objects.get(pk=self.kwargs.get('pk', None))
except:
pass
return ctx
class BuildOutputComplete(generics.CreateAPIView):
"""
API endpoint for completing build outputs
@ -455,6 +478,7 @@ build_api_urls = [
url(r'^(?P<pk>\d+)/', include([
url(r'^allocate/', BuildAllocate.as_view(), name='api-build-allocate'),
url(r'^complete/', BuildOutputComplete.as_view(), name='api-build-output-complete'),
url(r'^create-output/', BuildOutputCreate.as_view(), name='api-build-output-create'),
url(r'^delete-outputs/', BuildOutputDelete.as_view(), name='api-build-output-delete'),
url(r'^finish/', BuildFinish.as_view(), name='api-build-finish'),
url(r'^unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),

View File

@ -19,6 +19,7 @@ from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentS
from InvenTree.serializers import UserSerializerBrief, ReferenceIndexingSerializerMixin
import InvenTree.helpers
from InvenTree.helpers import extract_serial_numbers
from InvenTree.serializers import InvenTreeDecimalField
from InvenTree.status_codes import StockStatus
@ -170,6 +171,122 @@ class BuildOutputSerializer(serializers.Serializer):
]
class BuildOutputCreateSerializer(serializers.Serializer):
"""
Serializer for creating a new BuildOutput against a BuildOrder.
URL pattern is "/api/build/<pk>/create-output/", where <pk> is the PK of a Build.
The Build object is provided to the serializer context.
"""
quantity = serializers.DecimalField(
max_digits=15,
decimal_places=5,
min_value=0,
required=True,
label=_('Quantity'),
help_text=_('Enter quantity for build output'),
)
def get_build(self):
return self.context["build"]
def get_part(self):
return self.get_build().part
def validate_quantity(self, quantity):
if quantity < 0:
raise ValidationError(_("Quantity must be greater than zero"))
part = self.get_part()
if int(quantity) != quantity:
# Quantity must be an integer value if the part being built is trackable
if part.trackable:
raise ValidationError(_("Integer quantity required for trackable parts"))
if part.has_trackable_parts():
raise ValidationError(_("Integer quantity required, as the bill of materials contains tracakble parts"))
return quantity
batch_code = serializers.CharField(
required=False,
allow_blank=True,
label=_('Batch Code'),
help_text=_('Batch code for this build output'),
)
serial_numbers = serializers.CharField(
allow_blank=True,
required=False,
label=_('Serial Numbers'),
help_text=_('Enter serial numbers for build outputs'),
)
def validate_serial_numbers(self, serial_numbers):
serial_numbers = serial_numbers.strip()
# TODO: Field level validation necessary here?
return serial_numbers
auto_allocate = serializers.BooleanField(
required=False,
default=False,
label=_('Auto Allocate Serial Numbers'),
help_text=_('Automatically allocate required items with matching serial numbers'),
)
def validate(self, data):
"""
Perform form validation
"""
build = self.get_build()
part = self.get_part()
serials = None
quantity = data['quantity']
serial_numbers = data.get('serial_numbers', '')
if serial_numbers:
try:
serials = extract_serial_numbers(serial_numbers, quantity, part.getLatestSerialNumberInt())
except DjangoValidationError as e:
raise ValidationError({
'serial_numbers': e.messages,
})
# Check for conflicting serial numbesr
existing = []
for serial in serials:
if part.checkIfSerialNumberExists(serial):
existing.append(serial)
if len(existing) > 0:
msg = _("The following serial numbers already exist")
msg += " : "
msg += ",".join([str(e) for e in existing])
raise ValidationError({
'serial_numbers': msg,
})
return data
def save(self):
"""
Generate the new build output(s)
"""
...
class BuildOutputDeleteSerializer(serializers.Serializer):
"""
DRF serializer for deleting (cancelling) one or more build outputs

View File

@ -321,6 +321,15 @@
{{ block.super }}
$('#btn-create-output').click(function() {
createBuildOutput(
{{ build.pk }},
{
trackable_parts: {% if build.part.has_trackable_parts %}true{% else %}false{% endif%},
}
);
return;
launchModalForm('{% url "build-output-create" build.id %}',
{
reload: true,

View File

@ -21,6 +21,7 @@
/* exported
allocateStockToBuild,
completeBuildOrder,
createBuildOutput,
editBuildOrder,
loadAllocationTable,
loadBuildOrderAllocationTable,
@ -175,6 +176,68 @@ function completeBuildOrder(build_id, options={}) {
}
/*
* Construct a new build output against the provided build
*/
function createBuildOutput(build_id, options) {
// Request build order information from the server
inventreeGet(
`/api/build/${build_id}/`,
{},
{
success: function(build) {
var html = '';
var trackable = build.part_detail.trackable;
var remaining = Math.max(0, build.quantity - build.completed);
var fields = {
quantity: {
value: remaining,
},
serial_numbers: {
hidden: !trackable,
required: options.trackable_parts || trackable,
},
batch_code: {},
auto_allocate: {},
};
if (options.trackable_parts) {
html += `
<div class='alert alert-block alert-info'>
{% trans "The Bill of Materials contains trackable parts" %}.<br>
{% trans "Build outputs must be generated individually" %}.
</div>
`;
}
if (trackable) {
html += `
<div class='alert alert-block alert-info'>
{% trans "Trackable parts can have serial numbers specified" %}<br>
{% trans "Enter serial numbers to generate multiple single build outputs" %}
</div>
`;
}
constructForm(`/api/build/${build_id}/create-output/`, {
method: 'POST',
title: '{% trans "Create Build Output" %}',
confirm: true,
fields: fields,
preFormContent: html,
});
}
}
);
}
/*
* Construct a set of output buttons for a particular build output
*/