2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 12:35:46 +00:00

Merge branch 'master' of github.com:inventree/InvenTree into multi_part_forms

This commit is contained in:
eeintech
2021-05-11 10:22:34 -04:00
166 changed files with 776 additions and 727 deletions

View File

@ -64,7 +64,7 @@ class CancelSalesOrderForm(HelperForm):
fields = [
'confirm',
]
class ShipSalesOrderForm(HelperForm):

View File

@ -309,7 +309,7 @@ class PurchaseOrder(Order):
"""
A PurchaseOrder can only be cancelled under the following circumstances:
"""
return self.status in [
PurchaseOrderStatus.PLACED,
PurchaseOrderStatus.PENDING
@ -378,7 +378,7 @@ class PurchaseOrder(Order):
# Has this order been completed?
if len(self.pending_line_items()) == 0:
self.received_by = user
self.complete_order() # This will save the model
@ -419,7 +419,7 @@ class SalesOrder(Order):
except (ValueError, TypeError):
# Date processing error, return queryset unchanged
return queryset
# Construct a queryset for "completed" orders within the range
completed = Q(status__in=SalesOrderStatus.COMPLETE) & Q(shipment_date__gte=min_date) & Q(shipment_date__lte=max_date)
@ -495,7 +495,7 @@ class SalesOrder(Order):
for line in self.lines.all():
if not line.is_fully_allocated():
return False
return True
def is_over_allocated(self):
@ -590,11 +590,11 @@ class SalesOrderAttachment(InvenTreeAttachment):
class OrderLineItem(models.Model):
""" Abstract model for an order line item
Attributes:
quantity: Number of items
note: Annotation for the item
"""
class Meta:
@ -603,13 +603,13 @@ class OrderLineItem(models.Model):
quantity = RoundingDecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1, verbose_name=_('Quantity'), help_text=_('Item quantity'))
reference = models.CharField(max_length=100, blank=True, verbose_name=_('Reference'), help_text=_('Line item reference'))
notes = models.CharField(max_length=500, blank=True, verbose_name=_('Notes'), help_text=_('Line item notes'))
class PurchaseOrderLineItem(OrderLineItem):
""" Model for a purchase order line item.
Attributes:
order: Reference to a PurchaseOrder object
@ -637,7 +637,7 @@ class PurchaseOrderLineItem(OrderLineItem):
def get_base_part(self):
""" Return the base-part for the line item """
return self.part.part
# TODO - Function callback for when the SupplierPart is deleted?
part = models.ForeignKey(

View File

@ -61,7 +61,7 @@ class POSerializer(InvenTreeModelSerializer):
return queryset
supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True)
line_items = serializers.IntegerField(read_only=True)
status_text = serializers.CharField(source='get_status_display', read_only=True)
@ -70,7 +70,7 @@ class POSerializer(InvenTreeModelSerializer):
class Meta:
model = PurchaseOrder
fields = [
'pk',
'issue_date',
@ -89,7 +89,7 @@ class POSerializer(InvenTreeModelSerializer):
'target_date',
'notes',
]
read_only_fields = [
'reference',
'status'
@ -110,10 +110,10 @@ class POLineItemSerializer(InvenTreeModelSerializer):
quantity = serializers.FloatField()
received = serializers.FloatField()
part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True)
supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True)
purchase_price_string = serializers.CharField(source='purchase_price', read_only=True)
class Meta:
@ -144,7 +144,7 @@ class POAttachmentSerializer(InvenTreeModelSerializer):
class Meta:
model = PurchaseOrderAttachment
fields = [
'pk',
'order',
@ -270,7 +270,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer):
if allocations is not True:
self.fields.pop('allocations')
order_detail = SalesOrderSerializer(source='order', many=False, read_only=True)
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
allocations = SalesOrderAllocationSerializer(many=True, read_only=True)
@ -310,7 +310,7 @@ class SOAttachmentSerializer(InvenTreeModelSerializer):
class Meta:
model = SalesOrderAttachment
fields = [
'pk',
'order',

View File

@ -44,7 +44,7 @@ $("#new-attachment").click(function() {
$("#attachment-table").on('click', '.attachment-edit-button', function() {
var button = $(this);
var url = `/order/purchase-order/attachment/${button.attr('pk')}/edit/`;
launchModalForm(url, {

View File

@ -1,5 +1,6 @@
{% extends "modal_delete_form.html" %}
{% load i18n %}
{% block pre_form_content %}
Are you sure you wish to delete this line item?
{% trans "Are you sure you wish to delete this line item?" %}
{% endblock %}

View File

@ -193,11 +193,11 @@ $("#po-table").inventreeTable({
});
},
sorter: function(valA, valB, rowA, rowB) {
if (rowA.received == 0 && rowB.received == 0) {
return (rowA.quantity > rowB.quantity) ? 1 : -1;
}
var progressA = parseFloat(rowA.received) / rowA.quantity;
var progressB = parseFloat(rowB.received) / rowB.quantity;

View File

@ -83,7 +83,7 @@
var title = `${prefix}${order.reference} - ${order.supplier_detail.name}`;
var color = '#4c68f5';
if (order.complete_date) {
color = '#25c235';
} else if (order.overdue) {
@ -143,7 +143,7 @@ $('#view-calendar').click(function() {
$(".columns-right").hide();
$(".search").hide();
$('#filter-list-salesorder').hide();
$("#purchase-order-calendar").show();
$("#view-list").show();
@ -154,7 +154,7 @@ $("#view-list").click(function() {
// Hide the calendar view, show the list view
$("#purchase-order-calendar").hide();
$("#view-list").hide();
$(".fixed-table-pagination").show();
$(".columns-right").show();
$(".search").show();

View File

@ -51,13 +51,13 @@ $("#new-so-line").click(function() {
{% if order.status == SalesOrderStatus.PENDING %}
function showAllocationSubTable(index, row, element) {
// Construct a table showing stock items which have been allocated against this line item
var html = `<div class='sub-table'><table class='table table-striped table-condensed' id='allocation-table-${row.pk}'></table></div>`;
element.html(html);
var lineItem = row;
var table = $(`#allocation-table-${row.pk}`);
table.bootstrapTable({
@ -70,7 +70,7 @@ function showAllocationSubTable(index, row, element) {
title: '{% trans "Quantity" %}',
formatter: function(value, row, index, field) {
var text = '';
if (row.serial != null && row.quantity == 1) {
text = `{% trans "Serial Number" %}: ${row.serial}`;
} else {
@ -91,10 +91,10 @@ function showAllocationSubTable(index, row, element) {
field: 'buttons',
title: '{% trans "Actions" %}',
formatter: function(value, row, index, field) {
var html = "<div class='btn-group float-right' role='group'>";
var pk = row.pk;
{% if order.status == SalesOrderStatus.PENDING %}
html += makeIconButton('fa-edit icon-blue', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
@ -256,11 +256,11 @@ $("#so-lines-table").inventreeTable({
var A = rowA.fulfilled;
var B = rowB.fulfilled;
{% endif %}
if (A == 0 && B == 0) {
return (rowA.quantity > rowB.quantity) ? 1 : -1;
}
var progressA = parseFloat(A) / rowA.quantity;
var progressB = parseFloat(B) / rowB.quantity;
@ -279,7 +279,7 @@ $("#so-lines-table").inventreeTable({
var html = `<div class='btn-group float-right' role='group'>`;
var pk = row.pk;
if (row.part) {
var part = row.part_detail;
@ -292,14 +292,14 @@ $("#so-lines-table").inventreeTable({
if (part.purchaseable) {
html += makeIconButton('fa-shopping-cart', 'button-buy', row.part, '{% trans "Purchase stock" %}');
}
if (part.assembly) {
html += makeIconButton('fa-tools', 'button-build', row.part, '{% trans "Build stock" %}');
}
html += makeIconButton('fa-dollar-sign icon-green', 'button-price', pk, '{% trans "Calculate price" %}');
}
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}');
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line item " %}');

View File

@ -152,7 +152,7 @@ $("#view-list").click(function() {
// Hide the calendar view, show the list view
$("#sales-order-calendar").hide();
$("#view-list").hide();
$(".fixed-table-pagination").show();
$(".columns-right").show();
$(".search").show();

View File

@ -94,7 +94,7 @@ class PurchaseOrderTest(OrderTest):
url = '/api/order/po/1/'
response = self.get(url)
self.assertEqual(response.status_code, 200)
data = response.data
@ -109,7 +109,7 @@ class PurchaseOrderTest(OrderTest):
response = self.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
class SalesOrderTest(OrderTest):
"""

View File

@ -73,7 +73,7 @@ class SalesOrderTest(TestCase):
def test_add_duplicate_line_item(self):
# Adding a duplicate line item to a SalesOrder is accepted
for ii in range(1, 5):
SalesOrderLineItem.objects.create(order=self.order, part=self.part, quantity=ii)
@ -107,7 +107,7 @@ class SalesOrderTest(TestCase):
self.assertTrue(self.order.is_fully_allocated())
self.assertTrue(self.line.is_fully_allocated())
self.assertEqual(self.line.allocated_quantity(), 50)
def test_order_cancel(self):
# Allocate line items then cancel the order
@ -154,7 +154,7 @@ class SalesOrderTest(TestCase):
for item in outputs.all():
self.assertEqual(item.quantity, 25)
self.assertEqual(sa.sales_order, None)
self.assertEqual(sb.sales_order, None)
@ -162,7 +162,7 @@ class SalesOrderTest(TestCase):
self.assertEqual(SalesOrderAllocation.objects.count(), 0)
self.assertEqual(self.order.status, status.SalesOrderStatus.SHIPPED)
self.assertTrue(self.order.is_fully_allocated())
self.assertTrue(self.line.is_fully_allocated())
self.assertEqual(self.line.fulfilled_quantity(), 50)

View File

@ -17,7 +17,7 @@ import json
class OrderViewTestCase(TestCase):
fixtures = [
'category',
'part',
@ -193,7 +193,7 @@ class POTests(OrderViewTestCase):
# Test without confirmation
response = self.client.post(url, {'confirm': 0}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertFalse(data['form_valid'])
@ -221,7 +221,7 @@ class POTests(OrderViewTestCase):
# 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,
@ -303,7 +303,7 @@ class TestPOReceive(OrderViewTestCase):
self.client.get(self.url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
def test_receive_lines(self):
post_data = {
}
@ -330,7 +330,7 @@ class TestPOReceive(OrderViewTestCase):
# Receive negative number
post_data['line-1'] = -100
self.post(post_data, validate=False)
# Receive 75 items

View File

@ -36,7 +36,7 @@ class OrderTest(TestCase):
self.assertEqual(order.get_absolute_url(), '/order/purchase-order/1/')
self.assertEqual(str(order), 'PO0001 - ACME')
line = PurchaseOrderLineItem.objects.get(pk=1)
self.assertEqual(str(line), "100 x ACME0001 from ACME (for PO0001 - ACME)")
@ -113,7 +113,7 @@ class OrderTest(TestCase):
# Try to order a supplier part from the wrong supplier
sku = SupplierPart.objects.get(SKU='ZERG-WIDGET')
with self.assertRaises(django_exceptions.ValidationError):
order.add_line_item(sku, 99)
@ -153,7 +153,7 @@ class OrderTest(TestCase):
with self.assertRaises(django_exceptions.ValidationError):
order.receive_line_item(line, loc, 'not a number', user=None)
# Receive the rest of the items
order.receive_line_item(line, loc, 50, user=None)

View File

@ -157,7 +157,7 @@ class SalesOrderAttachmentCreate(AjaxCreateView):
"""
Save the user that uploaded the attachment
"""
attachment = form.save(commit=False)
attachment.user = self.request.user
attachment.save()
@ -335,7 +335,7 @@ class PurchaseOrderCreate(AjaxCreateView):
order = form.save(commit=False)
order.created_by = self.request.user
return super().save(form)
@ -370,7 +370,7 @@ class SalesOrderCreate(AjaxCreateView):
order = form.save(commit=False)
order.created_by = self.request.user
return super().save(form)
@ -419,7 +419,7 @@ class PurchaseOrderCancel(AjaxUpdateView):
form_class = order_forms.CancelPurchaseOrderForm
def validate(self, order, form, **kwargs):
confirm = str2bool(form.cleaned_data.get('confirm', False))
if not confirm:
@ -541,11 +541,11 @@ class SalesOrderShip(AjaxUpdateView):
order = self.get_object()
self.object = order
form = self.get_form()
confirm = str2bool(request.POST.get('confirm', False))
valid = False
if not confirm:
@ -1025,7 +1025,7 @@ class OrderParts(AjaxView):
for supplier in self.suppliers:
supplier.order_items = []
suppliers[supplier.name] = supplier
for part in self.parts:
@ -1046,9 +1046,9 @@ class OrderParts(AjaxView):
supplier.selected_purchase_order = orders.first().id
else:
supplier.selected_purchase_order = None
suppliers[supplier.name] = supplier
suppliers[supplier.name].order_items.append(part)
self.suppliers = [suppliers[key] for key in suppliers.keys()]
@ -1066,7 +1066,7 @@ class OrderParts(AjaxView):
if 'stock[]' in self.request.GET:
stock_id_list = self.request.GET.getlist('stock[]')
""" Get a list of all the parts associated with the stock items.
- Base part must be purchaseable.
- Return a set of corresponding Part IDs
@ -1109,7 +1109,7 @@ class OrderParts(AjaxView):
parts = build.required_parts
for part in parts:
# If ordering from a Build page, ignore parts that we have enough of
if part.quantity_to_order <= 0:
continue
@ -1165,19 +1165,19 @@ class OrderParts(AjaxView):
# Extract part information from the form
for item in self.request.POST:
if item.startswith('part-supplier-'):
pk = item.replace('part-supplier-', '')
# Check that the part actually exists
try:
part = Part.objects.get(id=pk)
except (Part.DoesNotExist, ValueError):
continue
supplier_part_id = self.request.POST[item]
quantity = self.request.POST.get('part-quantity-' + str(pk), 0)
# Ensure a valid supplier has been passed
@ -1591,7 +1591,7 @@ class SalesOrderAssignSerials(AjaxView, FormMixin):
self.form.fields['line'].widget = HiddenInput()
else:
self.form.add_error('line', _('Select line item'))
if self.part:
self.form.fields['part'].widget = HiddenInput()
else:
@ -1626,7 +1626,7 @@ class SalesOrderAssignSerials(AjaxView, FormMixin):
continue
# Now we have a valid stock item - but can it be added to the sales order?
# If not in stock, cannot be added to the order
if not stock_item.in_stock:
self.form.add_error(
@ -1694,7 +1694,7 @@ class SalesOrderAllocationCreate(AjaxCreateView):
model = SalesOrderAllocation
form_class = order_forms.CreateSalesOrderAllocationForm
ajax_form_title = _('Allocate Stock to Order')
def get_initial(self):
initials = super().get_initial().copy()
@ -1709,10 +1709,10 @@ class SalesOrderAllocationCreate(AjaxCreateView):
items = StockItem.objects.filter(part=line.part)
quantity = line.quantity - line.allocated_quantity()
if quantity < 0:
quantity = 0
if items.count() == 1:
item = items.first()
initials['item'] = item
@ -1728,7 +1728,7 @@ class SalesOrderAllocationCreate(AjaxCreateView):
return initials
def get_form(self):
form = super().get_form()
line_id = form['line'].value()
@ -1756,10 +1756,10 @@ class SalesOrderAllocationCreate(AjaxCreateView):
# Hide the 'line' field
form.fields['line'].widget = HiddenInput()
except (ValueError, SalesOrderLineItem.DoesNotExist):
pass
return form
@ -1768,7 +1768,7 @@ class SalesOrderAllocationEdit(AjaxUpdateView):
model = SalesOrderAllocation
form_class = order_forms.EditSalesOrderAllocationForm
ajax_form_title = _('Edit Allocation Quantity')
def get_form(self):
form = super().get_form()