Refactor POLineItemCreate form

This commit is contained in:
Oliver 2021-07-03 21:43:22 +10:00
parent c524f754e9
commit 889834b693
6 changed files with 30 additions and 172 deletions

View File

@ -20,7 +20,7 @@ from common.forms import MatchItemForm
import part.models import part.models
from stock.models import StockLocation from stock.models import StockLocation
from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrder
from .models import SalesOrder, SalesOrderLineItem from .models import SalesOrder, SalesOrderLineItem
from .models import SalesOrderAllocation from .models import SalesOrderAllocation
@ -96,24 +96,6 @@ class ReceivePurchaseOrderForm(HelperForm):
] ]
class EditPurchaseOrderLineItemForm(HelperForm):
""" Form for editing a PurchaseOrderLineItem object """
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
class Meta:
model = PurchaseOrderLineItem
fields = [
'order',
'part',
'quantity',
'reference',
'purchase_price',
'destination',
'notes',
]
class EditSalesOrderLineItemForm(HelperForm): class EditSalesOrderLineItemForm(HelperForm):
""" Form for editing a SalesOrderLineItem object """ """ Form for editing a SalesOrderLineItem object """

View File

@ -350,7 +350,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer):
max_digits=19, max_digits=19,
decimal_places=4, decimal_places=4,
allow_null=True allow_null=True
) )
sale_price_string = serializers.CharField(source='sale_price', read_only=True) sale_price_string = serializers.CharField(source='sale_price', read_only=True)

View File

@ -38,26 +38,34 @@
{% if order.status == PurchaseOrderStatus.PENDING %} {% if order.status == PurchaseOrderStatus.PENDING %}
$('#new-po-line').click(function() { $('#new-po-line').click(function() {
launchModalForm("{% url 'po-line-item-create' %}",
{
reload: true, constructForm('{% url "api-po-line-list" %}', {
data: { fields: {
order: {{ order.id }}, order: {
value: {{ order.pk }},
hidden: true,
}, },
secondary: [ part: {
{ filters: {
field: 'part', part_detail: true,
label: '{% trans "New Supplier Part" %}', supplier_detail: true,
title: '{% trans "Create new supplier part" %}', supplier: {{ order.supplier.pk }},
url: "{% url 'supplier-part-create' %}",
data: {
supplier: {{ order.supplier.id }},
},
}, },
], },
} quantity: {},
); reference: {},
purchase_price: {},
purchase_price_currency: {},
destination: {},
notes: {},
},
method: 'POST',
title: '{% trans "Add Line Item" %}',
onSuccess: reloadTable,
});
}); });
{% endif %} {% endif %}
function reloadTable() { function reloadTable() {
@ -77,6 +85,7 @@ function setupCallbacks() {
fields: { fields: {
part: { part: {
filters: { filters: {
part_detail: true,
supplier_detail: true, supplier_detail: true,
supplier: {{ order.supplier.pk }}, supplier: {{ order.supplier.pk }},
} }

View File

@ -104,57 +104,6 @@ class POTests(OrderViewTestCase):
order = PurchaseOrder.objects.get(pk=1) order = PurchaseOrder.objects.get(pk=1)
self.assertEqual(order.status, PurchaseOrderStatus.PLACED) self.assertEqual(order.status, PurchaseOrderStatus.PLACED)
def test_line_item_create(self):
""" Test the form for adding a new LineItem to a PurchaseOrder """
# Record the number of line items in the PurchaseOrder
po = PurchaseOrder.objects.get(pk=1)
n = po.lines.count()
self.assertEqual(po.status, PurchaseOrderStatus.PENDING)
url = reverse('po-line-item-create')
# GET the form (pass the correct info)
response = self.client.get(url, {'order': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
post_data = {
'part': 100,
'quantity': 45,
'reference': 'Test reference field',
'notes': 'Test notes field'
}
# POST with an invalid purchase order
post_data['order'] = 99
response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
data = json.loads(response.content)
self.assertFalse(data['form_valid'])
# POST with a part that does not match the purchase order
post_data['order'] = 1
post_data['part'] = 7
response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
data = json.loads(response.content)
self.assertFalse(data['form_valid'])
# POST with an invalid part
post_data['part'] = 12345
response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
data = json.loads(response.content)
self.assertFalse(data['form_valid'])
# POST the form with valid data
post_data['part'] = 100
response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertTrue(data['form_valid'])
self.assertEqual(n + 1, PurchaseOrder.objects.get(pk=1).lines.count())
line = PurchaseOrderLineItem.objects.get(order=1, part=100)
self.assertEqual(line.quantity, 45)
class TestPOReceive(OrderViewTestCase): class TestPOReceive(OrderViewTestCase):
""" Tests for receiving a purchase order """ """ Tests for receiving a purchase order """

View File

@ -1049,90 +1049,6 @@ class OrderParts(AjaxView):
order.add_line_item(supplier_part, quantity, purchase_price=purchase_price) order.add_line_item(supplier_part, quantity, purchase_price=purchase_price)
class POLineItemCreate(AjaxCreateView):
""" AJAX view for creating a new PurchaseOrderLineItem object
"""
model = PurchaseOrderLineItem
context_object_name = 'line'
form_class = order_forms.EditPurchaseOrderLineItemForm
ajax_form_title = _('Add Line Item')
def validate(self, item, form, **kwargs):
order = form.cleaned_data.get('order', None)
part = form.cleaned_data.get('part', None)
if not part:
form.add_error('part', _('Supplier part must be specified'))
if part and order:
if not part.supplier == order.supplier:
form.add_error(
'part',
_('Supplier must match for Part and Order')
)
def get_form(self):
""" Limit choice options based on the selected order, etc
"""
form = super().get_form()
# Limit the available to orders to ones that are PENDING
query = form.fields['order'].queryset
query = query.filter(status=PurchaseOrderStatus.PENDING)
form.fields['order'].queryset = query
order_id = form['order'].value()
try:
order = PurchaseOrder.objects.get(id=order_id)
query = form.fields['part'].queryset
# Only allow parts from the selected supplier
query = query.filter(supplier=order.supplier.id)
exclude = []
for line in order.lines.all():
if line.part and line.part.id not in exclude:
exclude.append(line.part.id)
# Remove parts that are already in the order
query = query.exclude(id__in=exclude)
form.fields['part'].queryset = query
form.fields['order'].widget = HiddenInput()
except (ValueError, PurchaseOrder.DoesNotExist):
pass
return form
def get_initial(self):
""" Extract initial data for the line item.
- The 'order' will be passed as a query parameter
- Use this to set the 'order' field and limit the options for 'part'
"""
initials = super().get_initial().copy()
order_id = self.request.GET.get('order', None)
if order_id:
try:
order = PurchaseOrder.objects.get(id=order_id)
initials['order'] = order
except (PurchaseOrder.DoesNotExist, ValueError):
pass
return initials
class SOLineItemCreate(AjaxCreateView): class SOLineItemCreate(AjaxCreateView):
""" Ajax view for creating a new SalesOrderLineItem object """ """ Ajax view for creating a new SalesOrderLineItem object """

View File

@ -145,9 +145,11 @@ function renderSupplierPart(name, data, parameters, options) {
var html = `<img src='${image}' class='select2-thumbnail'>`; var html = `<img src='${image}' class='select2-thumbnail'>`;
html += ` <span><b>${data.supplier_detail.name}</b> - ${data.SKU}</span>`; html += ` <span><b>${data.supplier_detail.name}</b> - ${data.SKU}</span>`;
html += ` - <i>${data.part_detail.full_name}</i>`;
html += `<span class='float-right'>{% trans "Supplier Part ID" %}: ${data.pk}</span>`; html += `<span class='float-right'>{% trans "Supplier Part ID" %}: ${data.pk}</span>`;
return html; return html;
} }