mirror of
https://github.com/inventree/InvenTree.git
synced 2025-08-09 21:30:54 +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:
@@ -8,6 +8,7 @@ from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import filters
|
||||
|
||||
from InvenTree.api import AttachmentMixin, ListCreateDestroyAPIView
|
||||
from InvenTree.filters import InvenTreeOrderingFilter
|
||||
from InvenTree.helpers import str2bool
|
||||
from InvenTree.mixins import ListCreateAPI, RetrieveUpdateDestroyAPI
|
||||
|
||||
@@ -338,12 +339,30 @@ class SupplierPartList(ListCreateDestroyAPIView):
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter,
|
||||
InvenTreeOrderingFilter,
|
||||
]
|
||||
|
||||
filterset_fields = [
|
||||
]
|
||||
|
||||
ordering_fields = [
|
||||
'SKU',
|
||||
'part',
|
||||
'supplier',
|
||||
'manufacturer',
|
||||
'MPN',
|
||||
'packaging',
|
||||
'pack_size',
|
||||
'in_stock',
|
||||
]
|
||||
|
||||
ordering_field_aliases = {
|
||||
'part': 'part__name',
|
||||
'supplier': 'supplier__name',
|
||||
'manufacturer': 'manufacturer_part__manufacturer__name',
|
||||
'MPN': 'manufacturer_part__MPN',
|
||||
}
|
||||
|
||||
search_fields = [
|
||||
'SKU',
|
||||
'supplier__name',
|
||||
|
20
InvenTree/company/migrations/0047_supplierpart_pack_size.py
Normal file
20
InvenTree/company/migrations/0047_supplierpart_pack_size.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.2.15 on 2022-09-05 04:21
|
||||
|
||||
import InvenTree.fields
|
||||
import django.core.validators
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('company', '0046_alter_company_image'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='supplierpart',
|
||||
name='pack_size',
|
||||
field=InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Unit quantity supplied in a single pack', max_digits=15, validators=[django.core.validators.MinValueValidator(0.001)], verbose_name='Pack Quantity'),
|
||||
),
|
||||
]
|
@@ -20,7 +20,7 @@ import InvenTree.fields
|
||||
import InvenTree.helpers
|
||||
import InvenTree.validators
|
||||
from common.settings import currency_code_default
|
||||
from InvenTree.fields import InvenTreeURLField
|
||||
from InvenTree.fields import InvenTreeURLField, RoundingDecimalField
|
||||
from InvenTree.models import InvenTreeAttachment
|
||||
from InvenTree.status_codes import PurchaseOrderStatus
|
||||
|
||||
@@ -406,6 +406,7 @@ class SupplierPart(models.Model):
|
||||
multiple: Multiple that the part is provided in
|
||||
lead_time: Supplier lead time
|
||||
packaging: packaging that the part is supplied in, e.g. "Reel"
|
||||
pack_size: Quantity of item supplied in a single pack (e.g. 30ml in a single tube)
|
||||
"""
|
||||
|
||||
objects = SupplierPartManager()
|
||||
@@ -527,6 +528,14 @@ class SupplierPart(models.Model):
|
||||
|
||||
packaging = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('Packaging'), help_text=_('Part packaging'))
|
||||
|
||||
pack_size = RoundingDecimalField(
|
||||
verbose_name=_('Pack Quantity'),
|
||||
help_text=_('Unit quantity supplied in a single pack'),
|
||||
default=1,
|
||||
max_digits=15, decimal_places=5,
|
||||
validators=[MinValueValidator(0.001)],
|
||||
)
|
||||
|
||||
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], verbose_name=_('multiple'), help_text=_('Order multiple'))
|
||||
|
||||
# TODO - Reimplement lead-time as a charfield with special validation (pattern matching).
|
||||
|
@@ -239,6 +239,8 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
|
||||
|
||||
pretty_name = serializers.CharField(read_only=True)
|
||||
|
||||
pack_size = serializers.FloatField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize this serializer with extra detail fields as required"""
|
||||
|
||||
@@ -273,6 +275,8 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
|
||||
|
||||
manufacturer_part_detail = ManufacturerPartSerializer(source='manufacturer_part', read_only=True)
|
||||
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options."""
|
||||
|
||||
@@ -291,12 +295,14 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
|
||||
'note',
|
||||
'pk',
|
||||
'packaging',
|
||||
'pack_size',
|
||||
'part',
|
||||
'part_detail',
|
||||
'pretty_name',
|
||||
'SKU',
|
||||
'supplier',
|
||||
'supplier_detail',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
|
@@ -49,6 +49,11 @@
|
||||
<span class='fas fa-edit icon-green'></span> {% trans "Edit Supplier Part" %}
|
||||
</a></li>
|
||||
{% endif %}
|
||||
{% if roles.purchase_order.add %}
|
||||
<li><a class='dropdown-item' href='#' id='duplicate-part' title='{% trans "Duplicate Supplier Part" %}'>
|
||||
<span class='fas fa-clone'></span> {% trans "Duplicate Supplier Part" %}
|
||||
</a></li>
|
||||
{% endif %}
|
||||
{% if roles.purchase_order.delete %}
|
||||
<li><a class='dropdown-item' href='#' id='delete-part' title='{% trans "Delete Supplier Part" %}'>
|
||||
<span class='fas fa-trash-alt icon-red'></span> {% trans "Delete Supplier Part" %}
|
||||
@@ -140,6 +145,13 @@ src="{% static 'img/blank_image.png' %}"
|
||||
<td>{{ part.packaging }}{% include "clip.html"%}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if part.pack_size != 1.0 %}
|
||||
<tr>
|
||||
<td><span class='fas fa-box'></span></td>
|
||||
<td>{% trans "Pack Quantity" %}</td>
|
||||
<td>{% decimal part.pack_size %}{% if part.part.units %} {{ part.part.units }}{% endif %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if part.note %}
|
||||
<tr>
|
||||
<td><span class='fas fa-sticky-note'></span></td>
|
||||
@@ -386,6 +398,12 @@ $('#update-part-availability').click(function() {
|
||||
});
|
||||
});
|
||||
|
||||
$('#duplicate-part').click(function() {
|
||||
duplicateSupplierPart({{ part.pk }}, {
|
||||
follow: true
|
||||
});
|
||||
});
|
||||
|
||||
$('#edit-part').click(function () {
|
||||
|
||||
editSupplierPart({{ part.pk }}, {
|
||||
|
Reference in New Issue
Block a user