Add form for creating a new StockItem allocation

This commit is contained in:
Oliver Walters 2020-04-22 21:26:38 +10:00
parent 2972aec759
commit 6ab03bd05a
5 changed files with 149 additions and 15 deletions

View File

@ -78,16 +78,16 @@ function getImageUrlFromTransfer(transfer) {
return url; return url;
} }
function makeIconButton(icon, id, opts) { function makeIconButton(icon, cls, pk, title) {
// Construct an 'icon button' using the fontawesome set // Construct an 'icon button' using the fontawesome set
var options = opts || {}; var classes = `btn btn-default btn-glyph ${cls}`;
var title = options.title || ''; var id = `${cls}-${pk}`;
var html = ''; var html = '';
html += `<button id='${id}' class='btn btn-default btn-glyph' title='${title}'>`; html += `<button pk='${pk}' id='${id}' class='${classes}' title='${title}'>`;
html += `<span class='fas ${icon}'></span>`; html += `<span class='fas ${icon}'></span>`;
html += `</button>`; html += `</button>`;

View File

@ -16,6 +16,7 @@ from InvenTree.fields import RoundingDecimalFormField
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
from .models import SalesOrderAllocation
class IssuePurchaseOrderForm(HelperForm): class IssuePurchaseOrderForm(HelperForm):
@ -144,3 +145,16 @@ class EditSalesOrderLineItemForm(HelperForm):
'reference', 'reference',
'notes' 'notes'
] ]
class EditSalesOrderAllocationForm(HelperForm):
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
class Meta:
model = SalesOrderAllocation
fields = [
'line',
'item',
'quantity']

View File

@ -136,32 +136,78 @@ $("#so-lines-table").inventreeTable({
field: 'buttons', field: 'buttons',
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
var html = ''; var html = `<div class='btn-group float-right' role='group'>`;
var pk = row.pk; var pk = row.pk;
if (row.part) { if (row.part) {
var part = row.part_detail; var part = row.part_detail;
html = `<div class='btn-group float-right' role='group'>`;
html += makeIconButton('fa-plus', `button-add-${pk}`);
if (part.purchaseable) { if (part.purchaseable) {
html += makeIconButton('fa-shopping-cart', `button-buy-${pk}`); html += makeIconButton('fa-shopping-cart', 'button-buy', pk, '{% trans "Buy parts" %}');
} }
if (part.assembly) { if (part.assembly) {
html += makeIconButton('fa-tools', `button-build-${pk}`); html += makeIconButton('fa-tools', 'button-build', pk, '{% trans "Build parts" %}');
} }
html += `</div>`; html += makeIconButton('fa-plus', 'button-add', pk, '{% trans "Allocate parts" %}');
} }
html += makeIconButton('fa-edit', 'button-edit', pk, '{% trans "Edit line item" %}');
html += `</div>`;
return html; return html;
} }
}, },
], ],
});
function reloadTable() {
$("#so-lines-table").bootstrapTable("refresh");
}
// Called when the table is loaded
$("#so-lines-table").on('load-success.bs.table', function() {
var table = $(this);
// Set up callbacks for the row buttons
table.find(".button-edit").click(function() {
var pk = $(this).attr('pk');
launchModalForm(`/order/sales-order/line/${pk}/edit/`, {
success: reloadTable,
});
console.log("clicked!");
}); });
table.find(".button-add").click(function() {
console.log("add");
var pk = $(this).attr('pk');
launchModalForm(`/order/sales-order/allocation/new/`, {
reload: table,
data: {
line: pk,
},
});
});
table.find(".button-build").click(function() {
console.log("build");
var pk = $(this).attr('pk');
});
table.find(".button-buy").click(function() {
console.log("buy");
});
});
{% endblock %} {% endblock %}

View File

@ -61,8 +61,12 @@ purchase_order_urls = [
url(r'^.*$', views.PurchaseOrderIndex.as_view(), name='po-index'), url(r'^.*$', views.PurchaseOrderIndex.as_view(), name='po-index'),
] ]
so_line_urls = [ so_line_urls = [
url(r'^new/', views.SOLineItemCreate.as_view(), name='so-line-item-create'), url(r'^new/', views.SOLineItemCreate.as_view(), name='so-line-item-create'),
url(r'^(?P<pk>\d+)/', include([
url(r'^edit/', views.SOLineItemEdit.as_view(), name='so-line-item-edit')
])),
] ]
sales_order_attachment_urls = [ sales_order_attachment_urls = [
@ -88,6 +92,11 @@ sales_order_urls = [
url(r'^line/', include(so_line_urls)), url(r'^line/', include(so_line_urls)),
# URLs for sales order allocations
url(r'^allocation/', include([
url(r'^new/', views.SalesOrderAllocationCreate.as_view(), name='so-allocation-create'),
])),
url(r'^attachments/', include(sales_order_attachment_urls)), url(r'^attachments/', include(sales_order_attachment_urls)),
# Display detail view for a single SalesOrder # Display detail view for a single SalesOrder

View File

@ -17,6 +17,7 @@ from decimal import Decimal, InvalidOperation
from .models import PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderAttachment from .models import PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderAttachment
from .models import SalesOrder, SalesOrderLineItem, SalesOrderAttachment from .models import SalesOrder, SalesOrderLineItem, SalesOrderAttachment
from .models import SalesOrderAllocation
from .admin import POLineItemResource from .admin import POLineItemResource
from build.models import Build from build.models import Build
from company.models import Company, SupplierPart from company.models import Company, SupplierPart
@ -1114,6 +1115,22 @@ class SOLineItemCreate(AjaxCreateView):
return initials return initials
class SOLineItemEdit(AjaxUpdateView):
""" View for editing a SalesOrderLineItem """
model = SalesOrderLineItem
form_class = order_forms.EditSalesOrderLineItemForm
ajax_form_title = _('Edit Line Item')
def get_form(self):
form = super().get_form()
form.fields.pop('order')
form.fields.pop('part')
return form
class POLineItemEdit(AjaxUpdateView): class POLineItemEdit(AjaxUpdateView):
""" View for editing a PurchaseOrderLineItem object in a modal form. """ View for editing a PurchaseOrderLineItem object in a modal form.
""" """
@ -1144,3 +1161,51 @@ class POLineItemDelete(AjaxDeleteView):
return { return {
'danger': _('Deleted line item'), 'danger': _('Deleted line item'),
} }
class SalesOrderAllocationCreate(AjaxCreateView):
""" View for creating a new SalesOrderAllocation """
model = SalesOrderAllocation
form_class = order_forms.EditSalesOrderAllocationForm
ajax_form_title = _('Allocate Stock to Order')
def get_initial(self):
initials = super().get_initial().copy()
line = self.request.GET.get('line', None)
if line is not None:
initials['line'] = SalesOrderLineItem.objects.get(pk=line)
return initials
def get_form(self):
form = super().get_form()
line_id = form['line'].value()
# If a line item has been specified, reduce the queryset for the stockitem accordingly
try:
line = SalesOrderLineItem.objects.get(pk=line_id)
queryset = form.fields['item'].queryset
# Ensure the part reference matches
queryset = queryset.filter(part=line.part)
# Exclude StockItem which are already allocated to this order
allocated = [allocation.item.pk for allocation in line.allocations.all()]
queryset = queryset.exclude(pk__in=allocated)
form.fields['item'].queryset = queryset
# Hide the 'line' field
form.fields['line'].widget = HiddenInput()
except KeyError: # (ValueError, SalesOrderLineItem.DoesNotExist):
pass
return form