mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Adds a new API endpoint for creating build outputs
This commit is contained in:
parent
dc4c1f138b
commit
f90a27d01d
@ -232,6 +232,29 @@ class BuildUnallocate(generics.CreateAPIView):
|
|||||||
return ctx
|
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):
|
class BuildOutputComplete(generics.CreateAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for completing build outputs
|
API endpoint for completing build outputs
|
||||||
@ -455,6 +478,7 @@ build_api_urls = [
|
|||||||
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'^complete/', BuildOutputComplete.as_view(), name='api-build-output-complete'),
|
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'^delete-outputs/', BuildOutputDelete.as_view(), name='api-build-output-delete'),
|
||||||
url(r'^finish/', BuildFinish.as_view(), name='api-build-finish'),
|
url(r'^finish/', BuildFinish.as_view(), name='api-build-finish'),
|
||||||
url(r'^unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),
|
url(r'^unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),
|
||||||
|
@ -19,6 +19,7 @@ from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentS
|
|||||||
from InvenTree.serializers import UserSerializerBrief, ReferenceIndexingSerializerMixin
|
from InvenTree.serializers import UserSerializerBrief, ReferenceIndexingSerializerMixin
|
||||||
|
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
|
from InvenTree.helpers import extract_serial_numbers
|
||||||
from InvenTree.serializers import InvenTreeDecimalField
|
from InvenTree.serializers import InvenTreeDecimalField
|
||||||
from InvenTree.status_codes import StockStatus
|
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):
|
class BuildOutputDeleteSerializer(serializers.Serializer):
|
||||||
"""
|
"""
|
||||||
DRF serializer for deleting (cancelling) one or more build outputs
|
DRF serializer for deleting (cancelling) one or more build outputs
|
||||||
|
@ -321,6 +321,15 @@
|
|||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
$('#btn-create-output').click(function() {
|
$('#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 %}',
|
launchModalForm('{% url "build-output-create" build.id %}',
|
||||||
{
|
{
|
||||||
reload: true,
|
reload: true,
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
/* exported
|
/* exported
|
||||||
allocateStockToBuild,
|
allocateStockToBuild,
|
||||||
completeBuildOrder,
|
completeBuildOrder,
|
||||||
|
createBuildOutput,
|
||||||
editBuildOrder,
|
editBuildOrder,
|
||||||
loadAllocationTable,
|
loadAllocationTable,
|
||||||
loadBuildOrderAllocationTable,
|
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
|
* Construct a set of output buttons for a particular build output
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user