mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-03 04:00:57 +00:00
Adds form to assign stock item by serial numbers
This commit is contained in:
@ -14,6 +14,8 @@ from InvenTree.forms import HelperForm
|
|||||||
from InvenTree.fields import RoundingDecimalFormField
|
from InvenTree.fields import RoundingDecimalFormField
|
||||||
from InvenTree.fields import DatePickerFormField
|
from InvenTree.fields import DatePickerFormField
|
||||||
|
|
||||||
|
import part.models
|
||||||
|
|
||||||
from stock.models import StockLocation
|
from stock.models import StockLocation
|
||||||
from .models import PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderAttachment
|
from .models import PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderAttachment
|
||||||
from .models import SalesOrder, SalesOrderLineItem, SalesOrderAttachment
|
from .models import SalesOrder, SalesOrderLineItem, SalesOrderAttachment
|
||||||
@ -211,22 +213,43 @@ class EditSalesOrderLineItemForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CreateSalesOrderAllocationForm(HelperForm):
|
class AllocateSerialsToSalesOrderForm(HelperForm):
|
||||||
"""
|
"""
|
||||||
Form for creating a SalesOrderAllocation item.
|
Form for assigning stock to a sales order,
|
||||||
|
by serial number lookup
|
||||||
This can be allocated by selecting a specific stock item,
|
|
||||||
or by providing a sequence of serial numbers
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
quantity = RoundingDecimalFormField(max_digits = 10, decimal_places=5)
|
line = forms.ModelChoiceField(
|
||||||
|
queryset = SalesOrderLineItem.objects.all(),
|
||||||
|
)
|
||||||
|
|
||||||
|
part = forms.ModelChoiceField(
|
||||||
|
queryset = part.models.Part.objects.all(),
|
||||||
|
)
|
||||||
|
|
||||||
serials = forms.CharField(
|
serials = forms.CharField(
|
||||||
label=_("Serial Numbers"),
|
label=_("Serial Numbers"),
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Enter stock serial numbers'),
|
help_text=_('Enter stock item serial numbers'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SalesOrderAllocation
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
'line',
|
||||||
|
'part',
|
||||||
|
'serials',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CreateSalesOrderAllocationForm(HelperForm):
|
||||||
|
"""
|
||||||
|
Form for creating a SalesOrderAllocation item.
|
||||||
|
"""
|
||||||
|
|
||||||
|
quantity = RoundingDecimalFormField(max_digits = 10, decimal_places=5)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SalesOrderAllocation
|
model = SalesOrderAllocation
|
||||||
|
|
||||||
|
@ -732,6 +732,12 @@ class SalesOrderAllocation(models.Model):
|
|||||||
|
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not self.item:
|
||||||
|
raise ValidationError({'item': _('Stock item has not been assigned')})
|
||||||
|
except stock_models.StockItem.DoesNotExist:
|
||||||
|
raise ValidationError({'item': _('Stock item has not been assigned')})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.line.part == self.item.part:
|
if not self.line.part == self.item.part:
|
||||||
errors['item'] = _('Cannot allocate stock item to a line with a different part')
|
errors['item'] = _('Cannot allocate stock item to a line with a different part')
|
||||||
|
@ -275,15 +275,20 @@ $("#so-lines-table").inventreeTable({
|
|||||||
if (row.part) {
|
if (row.part) {
|
||||||
var part = row.part_detail;
|
var part = row.part_detail;
|
||||||
|
|
||||||
|
if (part.trackable) {
|
||||||
|
html += makeIconButton('fa-hashtag icon-green', 'button-add-by-sn', pk, '{% trans "Allocate serial numbers" %}');
|
||||||
|
}
|
||||||
|
|
||||||
|
html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', pk, '{% trans "Allocate stock" %}');
|
||||||
|
|
||||||
if (part.purchaseable) {
|
if (part.purchaseable) {
|
||||||
html += makeIconButton('fa-shopping-cart', 'button-buy', row.part, '{% trans "Buy parts" %}');
|
html += makeIconButton('fa-shopping-cart', 'button-buy', row.part, '{% trans "Purchase stock" %}');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.assembly) {
|
if (part.assembly) {
|
||||||
html += makeIconButton('fa-tools', 'button-build', row.part, '{% trans "Build parts" %}');
|
html += makeIconButton('fa-tools', 'button-build', row.part, '{% trans "Build stock" %}');
|
||||||
}
|
}
|
||||||
|
|
||||||
html += makeIconButton('fa-plus icon-green', 'button-add', pk, '{% trans "Allocate parts" %}');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}');
|
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}');
|
||||||
@ -316,10 +321,28 @@ function setupCallbacks() {
|
|||||||
var pk = $(this).attr('pk');
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
launchModalForm(`/order/sales-order/line/${pk}/delete/`, {
|
launchModalForm(`/order/sales-order/line/${pk}/delete/`, {
|
||||||
reload: true,
|
success: reloadTable,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
table.find(".button-add-by-sn").click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
inventreeGet(`/api/order/so-line/${pk}/`, {},
|
||||||
|
{
|
||||||
|
success: function(response) {
|
||||||
|
launchModalForm('{% url "so-assign-serials" %}', {
|
||||||
|
success: reloadTable,
|
||||||
|
data: {
|
||||||
|
line: pk,
|
||||||
|
part: response.part,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
table.find(".button-add").click(function() {
|
table.find(".button-add").click(function() {
|
||||||
var pk = $(this).attr('pk');
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ sales_order_urls = [
|
|||||||
# URLs for sales order allocations
|
# URLs for sales order allocations
|
||||||
url(r'^allocation/', include([
|
url(r'^allocation/', include([
|
||||||
url(r'^new/', views.SalesOrderAllocationCreate.as_view(), name='so-allocation-create'),
|
url(r'^new/', views.SalesOrderAllocationCreate.as_view(), name='so-allocation-create'),
|
||||||
|
url(r'^assign-serials/', views.SalesOrderAssignSerials.as_view(), name='so-assign-serials'),
|
||||||
url(r'(?P<pk>\d+)/', include([
|
url(r'(?P<pk>\d+)/', include([
|
||||||
url(r'^edit/', views.SalesOrderAllocationEdit.as_view(), name='so-allocation-edit'),
|
url(r'^edit/', views.SalesOrderAllocationEdit.as_view(), name='so-allocation-edit'),
|
||||||
url(r'^delete/', views.SalesOrderAllocationDelete.as_view(), name='so-allocation-delete'),
|
url(r'^delete/', views.SalesOrderAllocationDelete.as_view(), name='so-allocation-delete'),
|
||||||
|
@ -1291,6 +1291,75 @@ class SOLineItemDelete(AjaxDeleteView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SalesOrderAssignSerials(AjaxCreateView):
|
||||||
|
"""
|
||||||
|
View for assigning stock items to a sales order,
|
||||||
|
by serial number lookup.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = SalesOrderAllocation
|
||||||
|
role_required = 'sales_order.change'
|
||||||
|
ajax_form_title = _('Allocate Serial Numbers')
|
||||||
|
form_class = order_forms.AllocateSerialsToSalesOrderForm
|
||||||
|
|
||||||
|
# Keep track of SalesOrderLineItem and Part references
|
||||||
|
line = None
|
||||||
|
part = None
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
"""
|
||||||
|
Initial values are passed as query params
|
||||||
|
"""
|
||||||
|
|
||||||
|
initials = super().get_initial()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.line = SalesOrderLineItem.objects.get(pk=self.request.GET.get('line', None))
|
||||||
|
initials['line'] = self.line
|
||||||
|
except (ValueError, SalesOrderLineItem.DoesNotExist):
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.part = Part.objects.get(pk=self.request.GET.get('part', None))
|
||||||
|
initials['part'] = self.part
|
||||||
|
except (ValueError, Part.DoesNotExist):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return initials
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
|
||||||
|
form = super().get_form()
|
||||||
|
|
||||||
|
if self.line is not None:
|
||||||
|
form.fields['line'].widget = HiddenInput()
|
||||||
|
|
||||||
|
# Hide the 'part' field if value provided
|
||||||
|
try:
|
||||||
|
print(form['part'])
|
||||||
|
# self.part = Part.objects.get(form['part'].value())
|
||||||
|
except (ValueError, Part.DoesNotExist):
|
||||||
|
self.part = None
|
||||||
|
|
||||||
|
if self.part is not None:
|
||||||
|
form.fields['part'].widget = HiddenInput()
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
return {
|
||||||
|
'line': self.line,
|
||||||
|
'part': self.part,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return self.renderJsonResponse(
|
||||||
|
request,
|
||||||
|
self.get_form(),
|
||||||
|
context=self.get_context_data(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderAllocationCreate(AjaxCreateView):
|
class SalesOrderAllocationCreate(AjaxCreateView):
|
||||||
""" View for creating a new SalesOrderAllocation """
|
""" View for creating a new SalesOrderAllocation """
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user