mirror of
https://github.com/inventree/InvenTree.git
synced 2025-08-05 19:41:41 +00:00
Merge remote-tracking branch 'upstream/master' into receive-via-api
# Conflicts: # InvenTree/templates/js/dynamic/inventree.js # InvenTree/templates/js/translated/forms.js # InvenTree/templates/js/translated/tables.js
This commit is contained in:
@@ -9,13 +9,14 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf.urls import url, include
|
||||
from django.db import transaction
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters import rest_framework as rest_filters
|
||||
from rest_framework import generics
|
||||
from rest_framework import filters, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
|
||||
from InvenTree.filters import InvenTreeOrderingFilter
|
||||
from InvenTree.helpers import str2bool
|
||||
from InvenTree.api import AttachmentMixin
|
||||
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
|
||||
@@ -149,7 +150,7 @@ class POList(generics.ListCreateAPIView):
|
||||
return queryset
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter,
|
||||
]
|
||||
@@ -323,6 +324,14 @@ class POLineItemList(generics.ListCreateAPIView):
|
||||
queryset = PurchaseOrderLineItem.objects.all()
|
||||
serializer_class = POLineItemSerializer
|
||||
|
||||
def get_queryset(self, *args, **kwargs):
|
||||
|
||||
queryset = super().get_queryset(*args, **kwargs)
|
||||
|
||||
queryset = POLineItemSerializer.annotate_queryset(queryset)
|
||||
|
||||
return queryset
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
|
||||
try:
|
||||
@@ -335,18 +344,26 @@ class POLineItemList(generics.ListCreateAPIView):
|
||||
return self.serializer_class(*args, **kwargs)
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter
|
||||
InvenTreeOrderingFilter
|
||||
]
|
||||
|
||||
ordering_field_aliases = {
|
||||
'MPN': 'part__manufacturer_part__MPN',
|
||||
'SKU': 'part__SKU',
|
||||
'part_name': 'part__part__name',
|
||||
}
|
||||
|
||||
ordering_fields = [
|
||||
'part__part__name',
|
||||
'part__MPN',
|
||||
'part__SKU',
|
||||
'reference',
|
||||
'MPN',
|
||||
'part_name',
|
||||
'purchase_price',
|
||||
'quantity',
|
||||
'received',
|
||||
'reference',
|
||||
'SKU',
|
||||
'total_price',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
@@ -371,6 +388,14 @@ class POLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
queryset = PurchaseOrderLineItem.objects.all()
|
||||
serializer_class = POLineItemSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
queryset = super().get_queryset()
|
||||
|
||||
queryset = POLineItemSerializer.annotate_queryset(queryset)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||
"""
|
||||
@@ -381,7 +406,7 @@ class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||
serializer_class = SOAttachmentSerializer
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
]
|
||||
|
||||
filter_fields = [
|
||||
@@ -505,7 +530,7 @@ class SOList(generics.ListCreateAPIView):
|
||||
return queryset
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter,
|
||||
]
|
||||
@@ -604,7 +629,7 @@ class SOLineItemList(generics.ListCreateAPIView):
|
||||
return queryset
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter
|
||||
]
|
||||
@@ -689,7 +714,7 @@ class SOAllocationList(generics.ListCreateAPIView):
|
||||
return queryset
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
]
|
||||
|
||||
# Default filterable fields
|
||||
@@ -707,7 +732,7 @@ class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||
serializer_class = POAttachmentSerializer
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
]
|
||||
|
||||
filter_fields = [
|
||||
|
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 3.2.4 on 2021-09-02 00:42
|
||||
|
||||
from django.db import migrations
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0065_auto_20210701_0509'),
|
||||
('order', '0049_alter_purchaseorderlineitem_unique_together'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
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.SET_NULL, related_name='po_lines', to='stock.stocklocation', verbose_name='Destination'),
|
||||
),
|
||||
]
|
@@ -767,7 +767,13 @@ class PurchaseOrderLineItem(OrderLineItem):
|
||||
help_text=_("Supplier part"),
|
||||
)
|
||||
|
||||
received = models.DecimalField(decimal_places=5, max_digits=15, default=0, verbose_name=_('Received'), help_text=_('Number of items received'))
|
||||
received = models.DecimalField(
|
||||
decimal_places=5,
|
||||
max_digits=15,
|
||||
default=0,
|
||||
verbose_name=_('Received'),
|
||||
help_text=_('Number of items received')
|
||||
)
|
||||
|
||||
purchase_price = InvenTreeModelMoneyField(
|
||||
max_digits=19,
|
||||
@@ -778,7 +784,7 @@ class PurchaseOrderLineItem(OrderLineItem):
|
||||
)
|
||||
|
||||
destination = TreeForeignKey(
|
||||
'stock.StockLocation', on_delete=models.DO_NOTHING,
|
||||
'stock.StockLocation', on_delete=models.SET_NULL,
|
||||
verbose_name=_('Destination'),
|
||||
related_name='po_lines',
|
||||
blank=True, null=True,
|
||||
|
@@ -7,8 +7,9 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Case, When, Value
|
||||
from django.db.models import BooleanField
|
||||
from django.db.models import BooleanField, ExpressionWrapper, F
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import ValidationError
|
||||
@@ -116,6 +117,23 @@ class POSerializer(InvenTreeModelSerializer):
|
||||
|
||||
class POLineItemSerializer(InvenTreeModelSerializer):
|
||||
|
||||
@staticmethod
|
||||
def annotate_queryset(queryset):
|
||||
"""
|
||||
Add some extra annotations to this queryset:
|
||||
|
||||
- Total price = purchase_price * quantity
|
||||
"""
|
||||
|
||||
queryset = queryset.annotate(
|
||||
total_price=ExpressionWrapper(
|
||||
F('purchase_price') * F('quantity'),
|
||||
output_field=models.DecimalField()
|
||||
)
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
part_detail = kwargs.pop('part_detail', False)
|
||||
@@ -130,6 +148,8 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
||||
quantity = serializers.FloatField(default=1)
|
||||
received = serializers.FloatField(default=0)
|
||||
|
||||
total_price = serializers.FloatField(read_only=True)
|
||||
|
||||
part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True)
|
||||
supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True)
|
||||
|
||||
@@ -165,6 +185,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
||||
'purchase_price_string',
|
||||
'destination',
|
||||
'destination_detail',
|
||||
'total_price',
|
||||
]
|
||||
|
||||
|
||||
|
@@ -57,7 +57,7 @@
|
||||
{% for duplicate in duplicates %}
|
||||
{% if duplicate == col.value %}
|
||||
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
|
||||
<b>{% trans "Duplicate selection" %}</b>
|
||||
<strong>{% trans "Duplicate selection" %}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@@ -38,7 +38,7 @@
|
||||
<tr id='part_row_{{ part.id }}'>
|
||||
<td>
|
||||
{% include "hover_image.html" with image=part.image hover=False %}
|
||||
{{ part.full_name }} <small><i>{{ part.description }}</i></small>
|
||||
{{ part.full_name }} <small><em>{{ part.description }}</em></small>
|
||||
</td>
|
||||
<td>
|
||||
<button class='btn btn-default btn-create' onClick='newSupplierPartFromOrderWizard()' id='new_supplier_part_{{ part.id }}' part='{{ part.pk }}' title='{% trans "Create new supplier part" %}' type='button'>
|
||||
@@ -62,7 +62,7 @@
|
||||
</select>
|
||||
</div>
|
||||
{% if not part.order_supplier %}
|
||||
<span class='help-inline'>{% blocktrans with name=part.name %}Select a supplier for <i>{{name}}</i>{% endblocktrans %}</span>
|
||||
<span class='help-inline'>{% blocktrans with name=part.name %}Select a supplier for <em>{{name}}</em>{% endblocktrans %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
|
@@ -28,7 +28,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<table class='table table-striped table-condensed' id='po-table' data-toolbar='#order-toolbar-buttons'>
|
||||
<table class='table table-striped table-condensed' id='po-line-table' data-toolbar='#order-toolbar-buttons'>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -208,13 +208,13 @@ $('#new-po-line').click(function() {
|
||||
{% endif %}
|
||||
|
||||
function reloadTable() {
|
||||
$("#po-table").bootstrapTable("refresh");
|
||||
$("#po-line-table").bootstrapTable("refresh");
|
||||
}
|
||||
|
||||
function setupCallbacks() {
|
||||
// Setup callbacks for the line buttons
|
||||
|
||||
var table = $("#po-table");
|
||||
var table = $("#po-line-table");
|
||||
|
||||
{% if order.status == PurchaseOrderStatus.PENDING %}
|
||||
table.find(".button-line-edit").click(function() {
|
||||
@@ -273,9 +273,9 @@ function setupCallbacks() {
|
||||
|
||||
}
|
||||
|
||||
$("#po-table").inventreeTable({
|
||||
$("#po-line-table").inventreeTable({
|
||||
onPostBody: setupCallbacks,
|
||||
name: 'purchaseorder',
|
||||
name: 'purchaseorderlines',
|
||||
sidePagination: 'server',
|
||||
formatNoMatches: function() { return "{% trans 'No line items found' %}"; },
|
||||
queryParams: {
|
||||
@@ -294,7 +294,7 @@ $("#po-table").inventreeTable({
|
||||
{
|
||||
field: 'part',
|
||||
sortable: true,
|
||||
sortName: 'part__part__name',
|
||||
sortName: 'part_name',
|
||||
title: '{% trans "Part" %}',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
@@ -314,7 +314,7 @@ $("#po-table").inventreeTable({
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
sortName: 'part__SKU',
|
||||
sortName: 'SKU',
|
||||
field: 'supplier_part_detail.SKU',
|
||||
title: '{% trans "SKU" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
@@ -327,7 +327,7 @@ $("#po-table").inventreeTable({
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
sortName: 'part__MPN',
|
||||
sortName: 'MPN',
|
||||
field: 'supplier_part_detail.manufacturer_part_detail.MPN',
|
||||
title: '{% trans "MPN" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
@@ -364,7 +364,9 @@ $("#po-table").inventreeTable({
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'total_price',
|
||||
sortable: true,
|
||||
field: 'total_price',
|
||||
title: '{% trans "Total price" %}',
|
||||
formatter: function(value, row) {
|
||||
var total = row.purchase_price * row.quantity;
|
||||
@@ -383,7 +385,7 @@ $("#po-table").inventreeTable({
|
||||
}
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
sortable: false,
|
||||
field: 'received',
|
||||
switchable: false,
|
||||
title: '{% trans "Received" %}',
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block form %}
|
||||
|
||||
{% blocktrans with desc=order.description %}Receive outstanding parts for <b>{{order}}</b> - <i>{{desc}}</i>{% endblocktrans %}
|
||||
{% blocktrans with desc=order.description %}Receive outstanding parts for <strong>{{order}}</strong> - <em>{{desc}}</em>{% endblocktrans %}
|
||||
|
||||
<form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
|
||||
{% csrf_token %}
|
||||
|
@@ -22,7 +22,7 @@
|
||||
{% endif %}
|
||||
|
||||
<div class='alert alert-block alert-info'>
|
||||
<b>{% trans "Sales Order" %} {{ order.reference }} - {{ order.customer.name }}</b>
|
||||
<strong>{% trans "Sales Order" %} {{ order.reference }} - {{ order.customer.name }}</strong>
|
||||
<br>
|
||||
{% trans "Shipping this order means that the order will no longer be editable." %}
|
||||
</div>
|
||||
|
@@ -6,9 +6,9 @@
|
||||
<div class='alert alert-block alert-warning'>
|
||||
{% trans "This action will unallocate the following stock from the Sales Order" %}:
|
||||
<br>
|
||||
<b>
|
||||
<strong>
|
||||
{% decimal allocation.get_allocated %} x {{ allocation.line.part.full_name }}
|
||||
{% if allocation.item.location %} ({{ allocation.get_location }}){% endif %}
|
||||
</b>
|
||||
</strong>
|
||||
</div>
|
||||
{% endblock %}
|
Reference in New Issue
Block a user