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:
Oliver 2021-06-17 17:01:05 +10:00 committed by GitHub
commit 93e83d0bf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 93 additions and 7 deletions

View File

@ -52,3 +52,10 @@
part: 2
supplier: 2
SKU: 'ZERGM312'
- model: company.supplierpart
pk: 5
fields:
part: 4
supplier: 2
SKU: 'R_4K7_0603'

View File

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

View File

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

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

@ -234,6 +234,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>

View File

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