From cd07ea835d9bceceaafc3606af38da1da71cfa65 Mon Sep 17 00:00:00 2001 From: Nigel Date: Fri, 14 May 2021 13:17:31 -0600 Subject: [PATCH 1/2] feat(purchase orders): show the preferred location for each PO Line Adds the ability for the Purchaser to specify where the item is intentended to go when received. If the Purchaser does not set a preferred location, then the default location for the part is displayed. If the item is received them where it was actually placed is shown. NOTE: if an item is split when received only one of the resulting StockItem location is used. Fixes #1467 Addresses some of the requests in #551 --- InvenTree/order/forms.py | 10 +++++-- .../0046_purchaseorderlineitem_destination.py | 29 +++++++++++++++++++ InvenTree/order/models.py | 24 +++++++++++++++ InvenTree/order/serializers.py | 4 +++ .../order/purchase_order_detail.html | 4 +++ .../order/templates/order/receive_parts.html | 4 +++ 6 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 InvenTree/order/migrations/0046_purchaseorderlineitem_destination.py diff --git a/InvenTree/order/forms.py b/InvenTree/order/forms.py index 3973888e95..effa696d43 100644 --- a/InvenTree/order/forms.py +++ b/InvenTree/order/forms.py @@ -79,12 +79,17 @@ class ShipSalesOrderForm(HelperForm): class ReceivePurchaseOrderForm(HelperForm): - location = TreeNodeChoiceField(queryset=StockLocation.objects.all(), required=True, label=_('Location'), help_text=_('Receive parts to this location')) + location = TreeNodeChoiceField( + queryset=StockLocation.objects.all(), + required=True, + label=_("Destination"), + help_text=_("Receive parts to this location"), + ) class Meta: model = PurchaseOrder fields = [ - 'location', + "location", ] @@ -195,6 +200,7 @@ class EditPurchaseOrderLineItemForm(HelperForm): 'quantity', 'reference', 'purchase_price', + 'destination', 'notes', ] diff --git a/InvenTree/order/migrations/0046_purchaseorderlineitem_destination.py b/InvenTree/order/migrations/0046_purchaseorderlineitem_destination.py new file mode 100644 index 0000000000..fa08c91e0d --- /dev/null +++ b/InvenTree/order/migrations/0046_purchaseorderlineitem_destination.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2 on 2021-05-13 22:38 + +from django.db import migrations +import django.db.models.deletion +import mptt.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("stock", "0063_auto_20210511_2343"), + ("order", "0045_auto_20210504_1946"), + ] + + operations = [ + migrations.AddField( + model_name="purchaseorderlineitem", + name="destination", + field=mptt.fields.TreeForeignKey( + blank=True, + help_text="Where does the Purchaser want this item to be stored?", + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="po_lines", + to="stock.stocklocation", + verbose_name="Destination", + ), + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 49504b6d89..c150331f94 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -20,6 +20,7 @@ from django.utils.translation import ugettext_lazy as _ from common.settings import currency_code_default from markdownx.models import MarkdownxField +from mptt.models import TreeForeignKey from djmoney.models.fields import MoneyField @@ -672,6 +673,29 @@ class PurchaseOrderLineItem(OrderLineItem): help_text=_('Unit purchase price'), ) + destination = TreeForeignKey( + 'stock.StockLocation', on_delete=models.DO_NOTHING, + verbose_name=_('Destination'), + related_name='po_lines', + blank=True, null=True, + help_text=_('Where does the Purchaser want this item to be stored?') + ) + + def get_destination(self): + """Show where the line item is or should be placed""" + # NOTE: If a line item gets split when recieved, only an arbitrary + # stock items location will be reported as the location for the + # entire line. + for stock in stock_models.StockItem.objects.filter( + supplier_part=self.part, purchase_order=self.order + ): + if stock.location: + return stock.location + if self.destination: + return self.destination + if self.part and self.part.part and self.part.part.default_location: + return self.part.part.default_location + def remaining(self): """ Calculate the number of items remaining to be received """ r = self.quantity - self.received diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 6091140313..a50c72e13e 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -17,6 +17,7 @@ from InvenTree.serializers import InvenTreeAttachmentSerializerField from company.serializers import CompanyBriefSerializer, SupplierPartSerializer from part.serializers import PartBriefSerializer +from stock.serializers import LocationBriefSerializer from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrderAttachment, SalesOrderAttachment @@ -116,6 +117,8 @@ class POLineItemSerializer(InvenTreeModelSerializer): purchase_price_string = serializers.CharField(source='purchase_price', read_only=True) + destination = LocationBriefSerializer(source='get_destination', read_only=True) + class Meta: model = PurchaseOrderLineItem @@ -132,6 +135,7 @@ class POLineItemSerializer(InvenTreeModelSerializer): 'purchase_price', 'purchase_price_currency', 'purchase_price_string', + 'destination', ] diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index ef844409fd..9169a89631 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -204,6 +204,10 @@ $("#po-table").inventreeTable({ return (progressA < progressB) ? 1 : -1; } }, + { + field: 'destination.pathstring', + title: '{% trans "Destination" %}', + }, { field: 'notes', title: '{% trans "Notes" %}', diff --git a/InvenTree/order/templates/order/receive_parts.html b/InvenTree/order/templates/order/receive_parts.html index 35ce4b6513..bfddf01ce9 100644 --- a/InvenTree/order/templates/order/receive_parts.html +++ b/InvenTree/order/templates/order/receive_parts.html @@ -22,6 +22,7 @@ {% trans "Received" %} {% trans "Receive" %} {% trans "Status" %} + {% trans "Destination" %} {% for line in lines %} @@ -53,6 +54,9 @@ + + {{ line.get_destination }} +