mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-01 13:06:45 +00:00
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:
parent
0076c0cec5
commit
cd07ea835d
@ -79,12 +79,17 @@ class ShipSalesOrderForm(HelperForm):
|
|||||||
|
|
||||||
class ReceivePurchaseOrderForm(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:
|
class Meta:
|
||||||
model = PurchaseOrder
|
model = PurchaseOrder
|
||||||
fields = [
|
fields = [
|
||||||
'location',
|
"location",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -195,6 +200,7 @@ class EditPurchaseOrderLineItemForm(HelperForm):
|
|||||||
'quantity',
|
'quantity',
|
||||||
'reference',
|
'reference',
|
||||||
'purchase_price',
|
'purchase_price',
|
||||||
|
'destination',
|
||||||
'notes',
|
'notes',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -20,6 +20,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from common.settings import currency_code_default
|
from common.settings import currency_code_default
|
||||||
|
|
||||||
from markdownx.models import MarkdownxField
|
from markdownx.models import MarkdownxField
|
||||||
|
from mptt.models import TreeForeignKey
|
||||||
|
|
||||||
from djmoney.models.fields import MoneyField
|
from djmoney.models.fields import MoneyField
|
||||||
|
|
||||||
@ -672,6 +673,29 @@ class PurchaseOrderLineItem(OrderLineItem):
|
|||||||
help_text=_('Unit purchase price'),
|
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):
|
def remaining(self):
|
||||||
""" Calculate the number of items remaining to be received """
|
""" Calculate the number of items remaining to be received """
|
||||||
r = self.quantity - self.received
|
r = self.quantity - self.received
|
||||||
|
@ -17,6 +17,7 @@ from InvenTree.serializers import InvenTreeAttachmentSerializerField
|
|||||||
|
|
||||||
from company.serializers import CompanyBriefSerializer, SupplierPartSerializer
|
from company.serializers import CompanyBriefSerializer, SupplierPartSerializer
|
||||||
from part.serializers import PartBriefSerializer
|
from part.serializers import PartBriefSerializer
|
||||||
|
from stock.serializers import LocationBriefSerializer
|
||||||
|
|
||||||
from .models import PurchaseOrder, PurchaseOrderLineItem
|
from .models import PurchaseOrder, PurchaseOrderLineItem
|
||||||
from .models import PurchaseOrderAttachment, SalesOrderAttachment
|
from .models import PurchaseOrderAttachment, SalesOrderAttachment
|
||||||
@ -116,6 +117,8 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
purchase_price_string = serializers.CharField(source='purchase_price', read_only=True)
|
purchase_price_string = serializers.CharField(source='purchase_price', read_only=True)
|
||||||
|
|
||||||
|
destination = LocationBriefSerializer(source='get_destination', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PurchaseOrderLineItem
|
model = PurchaseOrderLineItem
|
||||||
|
|
||||||
@ -132,6 +135,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
'purchase_price',
|
'purchase_price',
|
||||||
'purchase_price_currency',
|
'purchase_price_currency',
|
||||||
'purchase_price_string',
|
'purchase_price_string',
|
||||||
|
'destination',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -204,6 +204,10 @@ $("#po-table").inventreeTable({
|
|||||||
return (progressA < progressB) ? 1 : -1;
|
return (progressA < progressB) ? 1 : -1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'destination.pathstring',
|
||||||
|
title: '{% trans "Destination" %}',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'notes',
|
field: 'notes',
|
||||||
title: '{% trans "Notes" %}',
|
title: '{% trans "Notes" %}',
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
<th>{% trans "Received" %}</th>
|
<th>{% trans "Received" %}</th>
|
||||||
<th>{% trans "Receive" %}</th>
|
<th>{% trans "Receive" %}</th>
|
||||||
<th>{% trans "Status" %}</th>
|
<th>{% trans "Status" %}</th>
|
||||||
|
<th>{% trans "Destination" %}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for line in lines %}
|
{% for line in lines %}
|
||||||
@ -53,6 +54,9 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ line.get_destination }}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class='btn btn-default btn-remove' onClick="removeOrderRowFromOrderWizard()" id='del_item_{{ line.id }}' title='{% trans "Remove line" %}' type='button'>
|
<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>
|
<span row='line_row_{{ line.id }}' class='fas fa-times-circle icon-red'></span>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user