mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #1587 from nwns/feature/see_default_location
feat(purchase orders): show the preferred location for each PO Line
This commit is contained in:
commit
93e83d0bf9
@ -52,3 +52,10 @@
|
||||
part: 2
|
||||
supplier: 2
|
||||
SKU: 'ZERGM312'
|
||||
|
||||
- model: company.supplierpart
|
||||
pk: 5
|
||||
fields:
|
||||
part: 4
|
||||
supplier: 2
|
||||
SKU: 'R_4K7_0603'
|
||||
|
@ -65,7 +65,7 @@ class CompanySimpleTest(TestCase):
|
||||
self.assertEqual(acme.supplied_part_count, 4)
|
||||
|
||||
self.assertTrue(appel.has_parts)
|
||||
self.assertEqual(appel.supplied_part_count, 3)
|
||||
self.assertEqual(appel.supplied_part_count, 4)
|
||||
|
||||
self.assertTrue(zerg.has_parts)
|
||||
self.assertEqual(zerg.supplied_part_count, 2)
|
||||
|
@ -68,6 +68,7 @@
|
||||
order: 1
|
||||
part: 1
|
||||
quantity: 100
|
||||
destination: 5 # Desk/Drawer_1
|
||||
|
||||
# 250 x ACME0002 (M2x4 LPHS)
|
||||
# Partially received (50)
|
||||
@ -95,3 +96,10 @@
|
||||
part: 3
|
||||
quantity: 100
|
||||
|
||||
# 1 x R_4K7_0603
|
||||
- model: order.purchaseorderlineitem
|
||||
pk: 23
|
||||
fields:
|
||||
order: 1
|
||||
part: 5
|
||||
quantity: 1
|
||||
|
@ -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',
|
||||
]
|
||||
|
||||
|
@ -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 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
|
||||
|
@ -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',
|
||||
]
|
||||
|
||||
|
||||
|
@ -234,6 +234,10 @@ $("#po-table").inventreeTable({
|
||||
return (progressA < progressB) ? 1 : -1;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'destination.pathstring',
|
||||
title: '{% trans "Destination" %}',
|
||||
},
|
||||
{
|
||||
field: 'notes',
|
||||
title: '{% trans "Notes" %}',
|
||||
|
@ -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>
|
||||
|
@ -87,7 +87,7 @@ class OrderTest(TestCase):
|
||||
order = PurchaseOrder.objects.get(pk=1)
|
||||
|
||||
self.assertEqual(order.status, PurchaseOrderStatus.PENDING)
|
||||
self.assertEqual(order.lines.count(), 3)
|
||||
self.assertEqual(order.lines.count(), 4)
|
||||
|
||||
sku = SupplierPart.objects.get(SKU='ACME-WIDGET')
|
||||
part = sku.part
|
||||
@ -105,11 +105,11 @@ class OrderTest(TestCase):
|
||||
order.add_line_item(sku, 100)
|
||||
|
||||
self.assertEqual(part.on_order, 100)
|
||||
self.assertEqual(order.lines.count(), 4)
|
||||
self.assertEqual(order.lines.count(), 5)
|
||||
|
||||
# Order the same part again (it should be merged)
|
||||
order.add_line_item(sku, 50)
|
||||
self.assertEqual(order.lines.count(), 4)
|
||||
self.assertEqual(order.lines.count(), 5)
|
||||
self.assertEqual(part.on_order, 150)
|
||||
|
||||
# Try to order a supplier part from the wrong supplier
|
||||
@ -163,7 +163,7 @@ class OrderTest(TestCase):
|
||||
loc = StockLocation.objects.get(id=1)
|
||||
|
||||
# There should be two lines against this order
|
||||
self.assertEqual(len(order.pending_line_items()), 3)
|
||||
self.assertEqual(len(order.pending_line_items()), 4)
|
||||
|
||||
# Should fail, as order is 'PENDING' not 'PLACED"
|
||||
self.assertEqual(order.status, PurchaseOrderStatus.PENDING)
|
||||
|
Loading…
Reference in New Issue
Block a user