mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-17 12:35:46 +00:00
Feature: Supplier part pack size (#3644)
* Adds 'pack_size' field to SupplierPart model * Edit pack_size for SupplierPart via API * Display pack size in supplier part page template * Improve table ordering for SupplierPart table * Fix for API filtering - Need to use custom filter class * Adds functionality to duplicate an existing SupplierPart * Bump API version number * Display annotation of pack size in purchase order line item table * Display additional information in part purchase order table * Add UOM to purchase order table * Improve receive items functionality * Indicate quantity which will be received in modal form * Update the received quantity as the user changes the value * Take the pack_size into account when receiving line items * Take supplierpart pack size into account when receiving line items * Add "pack size" column to purchase order line item table * Tweak supplier part table * Update 'on_order' queryset annotation to take pack_size into account - May god have mercy on my soul * Adds a unit test to validate that the on_order queryset annotation is working as expected * Update Part.on_order method to take pack_size into account - Check in existing unit test also * Fix existing unit tests - Previous unit test was actually in error - Logic for calculating "on_order" was broked * More unit tests for receiving items against a purchase order * Allow pack_size < 1 * Display pack size when adding / editing PurchaseOrderLineItem * Fix bug in part purchase order table * Update part purchase order table again * Exclude notificationmessage when exporting dataset * Also display pack size when ordering parts from secondary form * javascript linting * Change user facing strings to "Pack Quantity"
This commit is contained in:
@ -1,5 +1,7 @@
|
||||
"""Unit tests for the various part API endpoints"""
|
||||
|
||||
from random import randint
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
import PIL
|
||||
@ -9,7 +11,7 @@ from rest_framework.test import APIClient
|
||||
import build.models
|
||||
import order.models
|
||||
from common.models import InvenTreeSetting
|
||||
from company.models import Company
|
||||
from company.models import Company, SupplierPart
|
||||
from InvenTree.api_tester import InvenTreeAPITestCase
|
||||
from InvenTree.status_codes import (BuildStatus, PurchaseOrderStatus,
|
||||
StockStatus)
|
||||
@ -1676,6 +1678,110 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
|
||||
self.assertEqual(part.total_stock, 91)
|
||||
self.assertEqual(part.available_stock, 56)
|
||||
|
||||
def test_on_order(self):
|
||||
"""Test that the 'on_order' queryset annotation works as expected.
|
||||
|
||||
This queryset annotation takes into account any outstanding line items for active orders,
|
||||
and should also use the 'pack_size' of the supplier part objects.
|
||||
"""
|
||||
|
||||
supplier = Company.objects.create(
|
||||
name='Paint Supplies',
|
||||
description='A supplier of paints',
|
||||
is_supplier=True
|
||||
)
|
||||
|
||||
# First, create some parts
|
||||
paint = PartCategory.objects.create(
|
||||
parent=None,
|
||||
name="Paint",
|
||||
description="Paints and such",
|
||||
)
|
||||
|
||||
for color in ['Red', 'Green', 'Blue', 'Orange', 'Yellow']:
|
||||
p = Part.objects.create(
|
||||
category=paint,
|
||||
units='litres',
|
||||
name=f"{color} Paint",
|
||||
description=f"Paint which is {color} in color"
|
||||
)
|
||||
|
||||
# Create multiple supplier parts in different sizes
|
||||
for pk_sz in [1, 10, 25, 100]:
|
||||
sp = SupplierPart.objects.create(
|
||||
part=p,
|
||||
supplier=supplier,
|
||||
SKU=f"PNT-{color}-{pk_sz}L",
|
||||
pack_size=pk_sz,
|
||||
)
|
||||
|
||||
self.assertEqual(p.supplier_parts.count(), 4)
|
||||
|
||||
# Check that we have the right base data to start with
|
||||
self.assertEqual(paint.parts.count(), 5)
|
||||
self.assertEqual(supplier.supplied_parts.count(), 20)
|
||||
|
||||
supplier_parts = supplier.supplied_parts.all()
|
||||
|
||||
# Create multiple orders
|
||||
for _ii in range(5):
|
||||
|
||||
po = order.models.PurchaseOrder.objects.create(
|
||||
supplier=supplier,
|
||||
description='ordering some paint',
|
||||
)
|
||||
|
||||
# Order an assortment of items
|
||||
for sp in supplier_parts:
|
||||
|
||||
# Generate random quantity to order
|
||||
quantity = randint(10, 20)
|
||||
|
||||
# Mark up to half of the quantity as received
|
||||
received = randint(0, quantity // 2)
|
||||
|
||||
# Add a line item
|
||||
item = order.models.PurchaseOrderLineItem.objects.create(
|
||||
part=sp,
|
||||
order=po,
|
||||
quantity=quantity,
|
||||
received=received,
|
||||
)
|
||||
|
||||
# Now grab a list of parts from the API
|
||||
response = self.get(
|
||||
reverse('api-part-list'),
|
||||
{
|
||||
'category': paint.pk,
|
||||
},
|
||||
expected_code=200,
|
||||
)
|
||||
|
||||
# Check that the correct number of items have been returned
|
||||
self.assertEqual(len(response.data), 5)
|
||||
|
||||
for item in response.data:
|
||||
# Calculate the 'ordering' quantity from first principles
|
||||
p = Part.objects.get(pk=item['pk'])
|
||||
|
||||
on_order = 0
|
||||
|
||||
for sp in p.supplier_parts.all():
|
||||
for line_item in sp.purchase_order_line_items.all():
|
||||
po = line_item.order
|
||||
|
||||
if po.status in PurchaseOrderStatus.OPEN:
|
||||
remaining = line_item.quantity - line_item.received
|
||||
|
||||
if remaining > 0:
|
||||
on_order += remaining * sp.pack_size
|
||||
|
||||
# The annotated quantity must be equal to the hand-calculated quantity
|
||||
self.assertEqual(on_order, item['ordering'])
|
||||
|
||||
# The annotated quantity must also match the part.on_order quantity
|
||||
self.assertEqual(on_order, p.on_order)
|
||||
|
||||
|
||||
class BomItemTest(InvenTreeAPITestCase):
|
||||
"""Unit tests for the BomItem API."""
|
||||
|
Reference in New Issue
Block a user