Merge pull request #2875 from SchrodingersGat/stock-tab-fix

Re-enable the "pricing" tab
This commit is contained in:
Oliver 2022-04-26 20:29:26 +10:00 committed by GitHub
commit e59c0f9d47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 93 additions and 151 deletions

View File

@ -4,11 +4,15 @@ InvenTree API version information
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 42 INVENTREE_API_VERSION = 43
""" """
Increment this API version number whenever there is a significant change to the API that any clients need to know about Increment this API version number whenever there is a significant change to the API that any clients need to know about
v43 -> 2022-04-26 : https://github.com/inventree/InvenTree/pull/2875
- Adds API detail endpoint for PartSalePrice model
- Adds API detail endpoint for PartInternalPrice model
v42 -> 2022-04-26 : https://github.com/inventree/InvenTree/pull/2833 v42 -> 2022-04-26 : https://github.com/inventree/InvenTree/pull/2833
- Adds variant stock information to the Part and BomItem serializers - Adds variant stock information to the Part and BomItem serializers

View File

@ -262,6 +262,15 @@ class CategoryTree(generics.ListAPIView):
ordering = ['level', 'name'] ordering = ['level', 'name']
class PartSalePriceDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Detail endpoint for PartSellPriceBreak model
"""
queryset = PartSellPriceBreak.objects.all()
serializer_class = part_serializers.PartSalePriceSerializer
class PartSalePriceList(generics.ListCreateAPIView): class PartSalePriceList(generics.ListCreateAPIView):
""" """
API endpoint for list view of PartSalePriceBreak model API endpoint for list view of PartSalePriceBreak model
@ -279,6 +288,15 @@ class PartSalePriceList(generics.ListCreateAPIView):
] ]
class PartInternalPriceDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Detail endpoint for PartInternalPriceBreak model
"""
queryset = PartInternalPriceBreak.objects.all()
serializer_class = part_serializers.PartInternalPriceSerializer
class PartInternalPriceList(generics.ListCreateAPIView): class PartInternalPriceList(generics.ListCreateAPIView):
""" """
API endpoint for list view of PartInternalPriceBreak model API endpoint for list view of PartInternalPriceBreak model
@ -1920,11 +1938,13 @@ part_api_urls = [
# Base URL for part sale pricing # Base URL for part sale pricing
url(r'^sale-price/', include([ url(r'^sale-price/', include([
url(r'^(?P<pk>\d+)/', PartSalePriceDetail.as_view(), name='api-part-sale-price-detail'),
url(r'^.*$', PartSalePriceList.as_view(), name='api-part-sale-price-list'), url(r'^.*$', PartSalePriceList.as_view(), name='api-part-sale-price-list'),
])), ])),
# Base URL for part internal pricing # Base URL for part internal pricing
url(r'^internal-price/', include([ url(r'^internal-price/', include([
url(r'^(?P<pk>\d+)/', PartInternalPriceDetail.as_view(), name='api-part-internal-price-detail'),
url(r'^.*$', PartInternalPriceList.as_view(), name='api-part-internal-price-list'), url(r'^.*$', PartInternalPriceList.as_view(), name='api-part-internal-price-list'),
])), ])),

View File

@ -17,6 +17,8 @@ from rest_framework import serializers
from sql_util.utils import SubqueryCount, SubquerySum from sql_util.utils import SubqueryCount, SubquerySum
from djmoney.contrib.django_rest_framework import MoneyField from djmoney.contrib.django_rest_framework import MoneyField
from common.settings import currency_code_default, currency_code_mappings
from InvenTree.serializers import (DataFileUploadSerializer, from InvenTree.serializers import (DataFileUploadSerializer,
DataFileExtractSerializer, DataFileExtractSerializer,
InvenTreeAttachmentSerializerField, InvenTreeAttachmentSerializerField,
@ -148,6 +150,13 @@ class PartSalePriceSerializer(InvenTreeModelSerializer):
allow_null=True allow_null=True
) )
price_currency = serializers.ChoiceField(
choices=currency_code_mappings(),
default=currency_code_default,
label=_('Currency'),
help_text=_('Purchase currency of this stock item'),
)
price_string = serializers.CharField(source='price', read_only=True) price_string = serializers.CharField(source='price', read_only=True)
class Meta: class Meta:
@ -157,6 +166,7 @@ class PartSalePriceSerializer(InvenTreeModelSerializer):
'part', 'part',
'quantity', 'quantity',
'price', 'price',
'price_currency',
'price_string', 'price_string',
] ]
@ -172,6 +182,13 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer):
allow_null=True allow_null=True
) )
price_currency = serializers.ChoiceField(
choices=currency_code_mappings(),
default=currency_code_default,
label=_('Currency'),
help_text=_('Purchase currency of this stock item'),
)
price_string = serializers.CharField(source='price', read_only=True) price_string = serializers.CharField(source='price', read_only=True)
class Meta: class Meta:
@ -181,6 +198,7 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer):
'part', 'part',
'quantity', 'quantity',
'price', 'price',
'price_currency',
'price_string', 'price_string',
] ]

View File

@ -124,8 +124,7 @@
</div> </div>
</div> </div>
{% settings_value "PART_SHOW_PRICE_HISTORY" as show_price_history %} {% if part.purchaseable or part.salable %}
{% if show_price_history %}
<div class='panel panel-hidden' id='panel-pricing'> <div class='panel panel-hidden' id='panel-pricing'>
{% include "part/prices.html" %} {% include "part/prices.html" %}
</div> </div>
@ -1009,7 +1008,7 @@
pb_url_slug: 'internal-price', pb_url_slug: 'internal-price',
pb_url: '{% url 'api-part-internal-price-list' %}', pb_url: '{% url 'api-part-internal-price-list' %}',
pb_new_btn: $('#new-internal-price-break'), pb_new_btn: $('#new-internal-price-break'),
pb_new_url: '{% url 'internal-price-break-create' %}', pb_new_url: '{% url 'api-part-internal-price-list' %}',
linkedGraph: $('#InternalPriceBreakChart'), linkedGraph: $('#InternalPriceBreakChart'),
}, },
); );
@ -1025,7 +1024,7 @@
pb_url_slug: 'sale-price', pb_url_slug: 'sale-price',
pb_url: "{% url 'api-part-sale-price-list' %}", pb_url: "{% url 'api-part-sale-price-list' %}",
pb_new_btn: $('#new-price-break'), pb_new_btn: $('#new-price-break'),
pb_new_url: '{% url 'sale-price-break-create' %}', pb_new_url: '{% url 'api-part-sale-price-list' %}',
linkedGraph: $('#SalePriceBreakChart'), linkedGraph: $('#SalePriceBreakChart'),
}, },
); );

View File

@ -4,7 +4,6 @@
{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %} {% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
{% settings_value 'PART_SHOW_RELATED' as show_related %} {% settings_value 'PART_SHOW_RELATED' as show_related %}
{% settings_value "PART_SHOW_PRICE_HISTORY" as show_price_history %}
{% trans "Parameters" as text %} {% trans "Parameters" as text %}
{% include "sidebar_item.html" with label="part-parameters" text=text icon="fa-th-list" %} {% include "sidebar_item.html" with label="part-parameters" text=text icon="fa-th-list" %}
@ -28,7 +27,7 @@
{% trans "Used In" as text %} {% trans "Used In" as text %}
{% include "sidebar_item.html" with label="used-in" text=text icon="fa-layer-group" %} {% include "sidebar_item.html" with label="used-in" text=text icon="fa-layer-group" %}
{% endif %} {% endif %}
{% if show_price_history %} {% if part.purchaseable or part.salable %}
{% trans "Pricing" as text %} {% trans "Pricing" as text %}
{% include "sidebar_item.html" with label="pricing" text=text icon="fa-dollar-sign" %} {% include "sidebar_item.html" with label="pricing" text=text icon="fa-dollar-sign" %}
{% endif %} {% endif %}

View File

@ -3,6 +3,9 @@
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% load inventree_extras %} {% load inventree_extras %}
{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
{% if show_price_history %}
<div class='panel-heading'> <div class='panel-heading'>
<h4>{% trans "Pricing Information" %}</h4> <h4>{% trans "Pricing Information" %}</h4>
</div> </div>
@ -43,7 +46,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if part.bom_count > 0 %} {% if part.assembly and part.bom_count > 0 %}
{% if min_total_bom_price %} {% if min_total_bom_price %}
<tr> <tr>
<td><strong>{% trans 'BOM Pricing' %}</strong> <td><strong>{% trans 'BOM Pricing' %}</strong>
@ -147,7 +150,7 @@
</div> </div>
</div> </div>
{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %} {% endif %}
{% if part.purchaseable and roles.purchase_order.view %} {% if part.purchaseable and roles.purchase_order.view %}
<a class="anchor" id="supplier-cost"></a> <a class="anchor" id="supplier-cost"></a>
@ -170,7 +173,7 @@
</div> </div>
</div> </div>
{% if price_history %} {% if show_price_history %}
<a class="anchor" id="purchase-price"></a> <a class="anchor" id="purchase-price"></a>
<div class='panel-heading'> <div class='panel-heading'>
<h4>{% trans "Purchase Price" %} <h4>{% trans "Purchase Price" %}
@ -279,6 +282,7 @@
</div> </div>
</div> </div>
{% if show_price_history %}
<a class="anchor" id="sale-price"></a> <a class="anchor" id="sale-price"></a>
<div class='panel-heading'> <div class='panel-heading'>
<h4>{% trans "Sale Price" %} <h4>{% trans "Sale Price" %}
@ -298,3 +302,5 @@
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
{% endif %}

View File

@ -13,18 +13,6 @@ from django.conf.urls import url, include
from . import views from . import views
sale_price_break_urls = [
url(r'^new/', views.PartSalePriceBreakCreate.as_view(), name='sale-price-break-create'),
url(r'^(?P<pk>\d+)/edit/', views.PartSalePriceBreakEdit.as_view(), name='sale-price-break-edit'),
url(r'^(?P<pk>\d+)/delete/', views.PartSalePriceBreakDelete.as_view(), name='sale-price-break-delete'),
]
internal_price_break_urls = [
url(r'^new/', views.PartInternalPriceBreakCreate.as_view(), name='internal-price-break-create'),
url(r'^(?P<pk>\d+)/edit/', views.PartInternalPriceBreakEdit.as_view(), name='internal-price-break-edit'),
url(r'^(?P<pk>\d+)/delete/', views.PartInternalPriceBreakDelete.as_view(), name='internal-price-break-delete'),
]
part_parameter_urls = [ part_parameter_urls = [
url(r'^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'), url(r'^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'),
url(r'^template/(?P<pk>\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='part-param-template-edit'), url(r'^template/(?P<pk>\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='part-param-template-edit'),
@ -86,12 +74,6 @@ part_urls = [
# Part category # Part category
url(r'^category/', include(category_urls)), url(r'^category/', include(category_urls)),
# Part price breaks
url(r'^sale-price/', include(sale_price_break_urls)),
# Part internal price breaks
url(r'^internal-price/', include(internal_price_break_urls)),
# Part parameters # Part parameters
url(r'^parameter/', include(part_parameter_urls)), url(r'^parameter/', include(part_parameter_urls)),

View File

@ -18,7 +18,6 @@ from django.forms import HiddenInput
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from moneyed import CURRENCIES
from djmoney.contrib.exchange.models import convert_money from djmoney.contrib.exchange.models import convert_money
from djmoney.contrib.exchange.exceptions import MissingRate from djmoney.contrib.exchange.exceptions import MissingRate
@ -33,7 +32,6 @@ from decimal import Decimal
from .models import PartCategory, Part from .models import PartCategory, Part
from .models import PartParameterTemplate from .models import PartParameterTemplate
from .models import PartCategoryParameterTemplate from .models import PartCategoryParameterTemplate
from .models import PartSellPriceBreak, PartInternalPriceBreak
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
from company.models import SupplierPart from company.models import SupplierPart
@ -389,8 +387,12 @@ class PartDetail(InvenTreeRoleMixin, DetailView):
context.update(**ctx) context.update(**ctx)
show_price_history = InvenTreeSetting.get_setting('PART_SHOW_PRICE_HISTORY', False)
context['show_price_history'] = show_price_history
# Pricing information # Pricing information
if InvenTreeSetting.get_setting('PART_SHOW_PRICE_HISTORY', False): if show_price_history:
ctx = self.get_pricing(self.get_quantity()) ctx = self.get_pricing(self.get_quantity())
ctx['form'] = self.form_class(initial=self.get_initials()) ctx['form'] = self.form_class(initial=self.get_initials())
@ -1226,102 +1228,3 @@ class CategoryParameterTemplateDelete(AjaxDeleteView):
return None return None
return self.object return self.object
class PartSalePriceBreakCreate(AjaxCreateView):
"""
View for creating a sale price break for a part
"""
model = PartSellPriceBreak
form_class = part_forms.EditPartSalePriceBreakForm
ajax_form_title = _('Add Price Break')
def get_data(self):
return {
'success': _('Added new price break')
}
def get_part(self):
try:
part = Part.objects.get(id=self.request.GET.get('part'))
except (ValueError, Part.DoesNotExist):
part = None
if part is None:
try:
part = Part.objects.get(id=self.request.POST.get('part'))
except (ValueError, Part.DoesNotExist):
part = None
return part
def get_form(self):
form = super(AjaxCreateView, self).get_form()
form.fields['part'].widget = HiddenInput()
return form
def get_initial(self):
initials = super(AjaxCreateView, self).get_initial()
initials['part'] = self.get_part()
default_currency = inventree_settings.currency_code_default()
currency = CURRENCIES.get(default_currency, None)
if currency is not None:
initials['price'] = [1.0, currency]
return initials
class PartSalePriceBreakEdit(AjaxUpdateView):
""" View for editing a sale price break """
model = PartSellPriceBreak
form_class = part_forms.EditPartSalePriceBreakForm
ajax_form_title = _('Edit Price Break')
def get_form(self):
form = super().get_form()
form.fields['part'].widget = HiddenInput()
return form
class PartSalePriceBreakDelete(AjaxDeleteView):
""" View for deleting a sale price break """
model = PartSellPriceBreak
ajax_form_title = _("Delete Price Break")
ajax_template_name = "modal_delete_form.html"
class PartInternalPriceBreakCreate(PartSalePriceBreakCreate):
""" View for creating a internal price break for a part """
model = PartInternalPriceBreak
form_class = part_forms.EditPartInternalPriceBreakForm
ajax_form_title = _('Add Internal Price Break')
permission_required = 'roles.sales_order.add'
class PartInternalPriceBreakEdit(PartSalePriceBreakEdit):
""" View for editing a internal price break """
model = PartInternalPriceBreak
form_class = part_forms.EditPartInternalPriceBreakForm
ajax_form_title = _('Edit Internal Price Break')
permission_required = 'roles.sales_order.change'
class PartInternalPriceBreakDelete(PartSalePriceBreakDelete):
""" View for deleting a internal price break """
model = PartInternalPriceBreak
ajax_form_title = _("Delete Internal Price Break")
permission_required = 'roles.sales_order.delete'

View File

@ -1930,7 +1930,9 @@ function loadPriceBreakTable(table, options) {
formatNoMatches: function() { formatNoMatches: function() {
return `{% trans "No ${human_name} information found" %}`; return `{% trans "No ${human_name} information found" %}`;
}, },
queryParams: {part: options.part}, queryParams: {
part: options.part
},
url: options.url, url: options.url,
onLoadSuccess: function(tableData) { onLoadSuccess: function(tableData) {
if (linkedGraph) { if (linkedGraph) {
@ -2036,36 +2038,45 @@ function initPriceBreakSet(table, options) {
} }
pb_new_btn.click(function() { pb_new_btn.click(function() {
launchModalForm(pb_new_url,
{ constructForm(pb_new_url, {
success: reloadPriceBreakTable, fields: {
data: { part: {
part: part_id, hidden: true,
} value: part_id,
} },
); quantity: {},
price: {},
price_currency: {},
},
method: 'POST',
title: '{% trans "Add Price Break" %}',
onSuccess: reloadPriceBreakTable,
});
}); });
table.on('click', `.button-${pb_url_slug}-delete`, function() { table.on('click', `.button-${pb_url_slug}-delete`, function() {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
launchModalForm( constructForm(`${pb_url}${pk}/`, {
`/part/${pb_url_slug}/${pk}/delete/`, method: 'DELETE',
{ title: '{% trans "Delete Price Break" %}',
success: reloadPriceBreakTable onSuccess: reloadPriceBreakTable,
} });
);
}); });
table.on('click', `.button-${pb_url_slug}-edit`, function() { table.on('click', `.button-${pb_url_slug}-edit`, function() {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
launchModalForm( constructForm(`${pb_url}${pk}/`, {
`/part/${pb_url_slug}/${pk}/edit/`, fields: {
{ quantity: {},
success: reloadPriceBreakTable price: {},
} price_currency: {},
); },
title: '{% trans "Edit Price Break" %}',
onSuccess: reloadPriceBreakTable,
});
}); });
} }