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
This commit is contained in:
Nigel 2021-05-14 13:17:31 -06:00
parent 0076c0cec5
commit cd07ea835d
No known key found for this signature in database
GPG Key ID: 3AB9572E33E501E6
6 changed files with 73 additions and 2 deletions

View File

@ -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',
]

View File

@ -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",
),
),
]

View File

@ -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

View File

@ -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',
]

View File

@ -204,6 +204,10 @@ $("#po-table").inventreeTable({
return (progressA < progressB) ? 1 : -1;
}
},
{
field: 'destination.pathstring',
title: '{% trans "Destination" %}',
},
{
field: 'notes',
title: '{% trans "Notes" %}',

View File

@ -22,6 +22,7 @@
<th>{% trans "Received" %}</th>
<th>{% trans "Receive" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Destination" %}</th>
<th></th>
</tr>
{% for line in lines %}
@ -53,6 +54,9 @@
</select>
</div>
</td>
<td>
{{ line.get_destination }}
</td>
<td>
<button class='btn btn-default btn-remove' onClick="removeOrderRowFromOrderWizard()" id='del_item_{{ line.id }}' title='{% trans "Remove line" %}' type='button'>
<span row='line_row_{{ line.id }}' class='fas fa-times-circle icon-red'></span>