diff --git a/InvenTree/part/templates/part/prices.html b/InvenTree/part/templates/part/prices.html
new file mode 100644
index 0000000000..e71179284a
--- /dev/null
+++ b/InvenTree/part/templates/part/prices.html
@@ -0,0 +1,490 @@
+{% extends "part/part_base.html" %}
+{% load static %}
+{% load i18n %}
+{% load crispy_forms_tags %}
+{% load inventree_extras %}
+
+{% block menubar %}
+{% include 'part/navbar.html' with tab='prices' %}
+{% endblock %}
+
+{% block heading %}
+{% trans "General Price Information" %}
+{% endblock %}
+
+
+{% block details %}
+{% default_currency as currency %}
+
+
+
+
{% trans "Pricing ranges" %}
+
+ {% if part.supplier_count > 0 %}
+ {% if min_total_buy_price %}
+
+ {% trans 'Supplier Pricing' %} |
+ {% trans 'Unit Cost' %} |
+ Min: {% include "price.html" with price=min_unit_buy_price %} |
+ Max: {% include "price.html" with price=max_unit_buy_price %} |
+
+ {% if quantity > 1 %}
+
+ |
+ {% trans 'Total Cost' %} |
+ Min: {% include "price.html" with price=min_total_buy_price %} |
+ Max: {% include "price.html" with price=max_total_buy_price %} |
+
+ {% endif %}
+ {% else %}
+
+
+ {% trans 'No supplier pricing available' %}
+ |
+
+ {% endif %}
+ {% endif %}
+
+ {% if part.bom_count > 0 %}
+ {% if min_total_bom_price %}
+
+ {% trans 'BOM Pricing' %} |
+ {% trans 'Unit Cost' %} |
+ Min: {% include "price.html" with price=min_unit_bom_price %} |
+ Max: {% include "price.html" with price=max_unit_bom_price %} |
+
+ {% if quantity > 1 %}
+
+ |
+ {% trans 'Total Cost' %} |
+ Min: {% include "price.html" with price=min_total_bom_price %} |
+ Max: {% include "price.html" with price=max_total_bom_price %} |
+
+ {% endif %}
+ {% if part.has_complete_bom_pricing == False %}
+
+
+ {% trans 'Note: BOM pricing is incomplete for this part' %}
+ |
+
+ {% endif %}
+ {% else %}
+
+
+ {% trans 'No BOM pricing available' %}
+ |
+
+ {% endif %}
+ {% endif %}
+
+ {% if show_internal_price and roles.sales_order.view %}
+ {% if total_internal_part_price %}
+
+ {% trans 'Internal Price' %} |
+ {% trans 'Unit Cost' %} |
+ {% include "price.html" with price=unit_internal_part_price %} |
+
+
+ |
+ {% trans 'Total Cost' %} |
+ {% include "price.html" with price=total_internal_part_price %} |
+
+ {% endif %}
+ {% endif %}
+
+ {% if total_part_price %}
+
+ {% trans 'Sale Price' %} |
+ {% trans 'Unit Cost' %} |
+ {% include "price.html" with price=unit_part_price %} |
+
+
+ |
+ {% trans 'Total Cost' %} |
+ {% include "price.html" with price=total_part_price %} |
+
+ {% endif %}
+
+
+ {% if min_unit_buy_price or min_unit_bom_price %}
+ {% else %}
+
+ {% trans 'No pricing information is available for this part.' %}
+
+ {% endif %}
+
+
+
+
+
+
+{% endblock %}
+
+{% block post_content_panel %}
+{% default_currency as currency %}
+{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
+
+
+{% if part.purchaseable and roles.purchase_order.view %}
+
+
+
{% trans "Supplier Cost" %}
+
+
+
+ PLACEHOLDER FOR SUPPLIER COST TABLE
+
+
+
+
+
+
+
{% trans "Purchase Price" %}
+
+
+ {% if price_history %}
+
{% trans 'Stock Pricing' %}
+ {% if price_history|length > 0 %}
+
+
+
+ {% else %}
+
+ {% trans 'No stock pricing history is available for this part.' %}
+
+ {% endif %}
+ {% endif %}
+
+{% endif %}
+
+
+{% if show_internal_price and roles.sales_order.view %}
+
+
+
{% trans "Internal Cost" %}
+
+
+
+
+
+
+
+
+
+
+{% endif %}
+
+
+
+
+
{% trans "BOM Cost" %}
+
+
+
+
+ PLACEHOLDER FOR BOM ITEM COST TABLE
+
+
+
+ {% if part.bom_count > 0 %}
+
+
{% trans 'BOM Pricing' %}
+
+
+
+
+ {% endif %}
+
+
+
+{% if part.salable and roles.sales_order.view %}
+
+
+
{% trans "Sale Cost" %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{% trans "Sale Price" %}
+
+
+
+ PLACEHOLDER FOR SALE HISTORY
+
+
+{% endif %}
+
+{% endblock %}
+
+
+
+{% block js_ready %}
+ {{ block.super }}
+
+ // history graphs
+ {% default_currency as currency %}
+ {% if price_history %}
+ var pricedata = {
+ labels: [
+ {% for line in price_history %}'{{ line.date }}',{% endfor %}
+ ],
+ datasets: [{
+ label: '{% blocktrans %}Single Price - {{currency}}{% endblocktrans %}',
+ backgroundColor: 'rgba(255, 99, 132, 0.2)',
+ borderColor: 'rgb(255, 99, 132)',
+ yAxisID: 'y',
+ data: [
+ {% for line in price_history %}{{ line.price|stringformat:".2f" }},{% endfor %}
+ ],
+ borderWidth: 1,
+ type: 'line'
+ },
+ {% if 'price_diff' in price_history.0 %}
+ {
+ label: '{% blocktrans %}Single Price Difference - {{currency}}{% endblocktrans %}',
+ backgroundColor: 'rgba(68, 157, 68, 0.2)',
+ borderColor: 'rgb(68, 157, 68)',
+ yAxisID: 'y2',
+ data: [
+ {% for line in price_history %}{{ line.price_diff|stringformat:".2f" }},{% endfor %}
+ ],
+ borderWidth: 1,
+ type: 'line',
+ hidden: true,
+ },
+ {
+ label: '{% blocktrans %}Part Single Price - {{currency}}{% endblocktrans %}',
+ backgroundColor: 'rgba(70, 127, 155, 0.2)',
+ borderColor: 'rgb(70, 127, 155)',
+ yAxisID: 'y',
+ data: [
+ {% for line in price_history %}{{ line.price_part|stringformat:".2f" }},{% endfor %}
+ ],
+ borderWidth: 1,
+ type: 'line',
+ hidden: true,
+ },
+ {% endif %}
+ {
+ label: '{% trans "Quantity" %}',
+ backgroundColor: 'rgba(255, 206, 86, 0.2)',
+ borderColor: 'rgb(255, 206, 86)',
+ yAxisID: 'y1',
+ data: [
+ {% for line in price_history %}{{ line.qty|stringformat:"f" }},{% endfor %}
+ ],
+ borderWidth: 1
+ }]
+ }
+ var StockPriceChart = loadStockPricingChart(document.getElementById('StockPriceChart'), pricedata)
+ var bom_colors = randomColor({hue: 'green', count: {{ bom_parts|length }} })
+ var bomdata = {
+ labels: [{% for line in bom_parts %}'{{ line.name }}',{% endfor %}],
+ datasets: [
+ {
+ label: 'Price',
+ data: [{% for line in bom_parts %}{{ line.min_price }},{% endfor %}],
+ backgroundColor: bom_colors,
+ },
+ {% if bom_pie_max %}
+ {
+ label: 'Max Price',
+ data: [{% for line in bom_parts %}{{ line.max_price }},{% endfor %}],
+ backgroundColor: bom_colors,
+ },
+ {% endif %}
+ ]
+ };
+ var BomChart = loadBomChart(document.getElementById('BomChart'), bomdata)
+
+ {% endif %}
+
+
+ // Internal pricebreaks
+ {% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
+ {% if show_internal_price and roles.sales_order.view %}
+ function reloadPriceBreaks() {
+ $("#internal-price-break-table").bootstrapTable("refresh");
+ }
+
+ $('#new-internal-price-break').click(function() {
+ launchModalForm("{% url 'internal-price-break-create' %}",
+ {
+ success: reloadPriceBreaks,
+ data: {
+ part: {{ part.id }},
+ }
+ }
+ );
+ });
+
+ $('#internal-price-break-table').inventreeTable({
+ name: 'internalprice',
+ formatNoMatches: function() { return "{% trans 'No internal price break information found' %}"; },
+ queryParams: {
+ part: {{ part.id }},
+ },
+ url: "{% url 'api-part-internal-price-list' %}",
+ onPostBody: function() {
+ var table = $('#internal-price-break-table');
+
+ table.find('.button-internal-price-break-delete').click(function() {
+ var pk = $(this).attr('pk');
+
+ launchModalForm(
+ `/part/internal-price/${pk}/delete/`,
+ {
+ success: reloadPriceBreaks
+ }
+ );
+ });
+
+ table.find('.button-internal-price-break-edit').click(function() {
+ var pk = $(this).attr('pk');
+
+ launchModalForm(
+ `/part/internal-price/${pk}/edit/`,
+ {
+ success: reloadPriceBreaks
+ }
+ );
+ });
+ },
+ columns: [
+ {
+ field: 'pk',
+ title: 'ID',
+ visible: false,
+ switchable: false,
+ },
+ {
+ field: 'quantity',
+ title: '{% trans "Quantity" %}',
+ sortable: true,
+ },
+ {
+ field: 'price',
+ title: '{% trans "Price" %}',
+ sortable: true,
+ formatter: function(value, row, index) {
+ var html = value;
+
+ html += ``
+
+ html += makeIconButton('fa-edit icon-blue', 'button-internal-price-break-edit', row.pk, '{% trans "Edit internal price break" %}');
+ html += makeIconButton('fa-trash-alt icon-red', 'button-internal-price-break-delete', row.pk, '{% trans "Delete internal price break" %}');
+
+ html += `
`;
+
+ return html;
+ }
+ },
+ ]
+ })
+ {% endif %}
+
+
+ // Sales pricebreaks
+ {% if part.salable and roles.sales_order.view %}
+ function reloadPriceBreaks() {
+ $("#price-break-table").bootstrapTable("refresh");
+ }
+
+ $('#new-price-break').click(function() {
+ launchModalForm("{% url 'sale-price-break-create' %}",
+ {
+ success: reloadPriceBreaks,
+ data: {
+ part: {{ part.id }},
+ }
+ }
+ );
+ });
+
+ $('#price-break-table').inventreeTable({
+ name: 'saleprice',
+ formatNoMatches: function() { return "{% trans 'No price break information found' %}"; },
+ queryParams: {
+ part: {{ part.id }},
+ },
+ url: "{% url 'api-part-sale-price-list' %}",
+ onPostBody: function() {
+ var table = $('#price-break-table');
+
+ table.find('.button-price-break-delete').click(function() {
+ var pk = $(this).attr('pk');
+
+ launchModalForm(
+ `/part/sale-price/${pk}/delete/`,
+ {
+ success: reloadPriceBreaks
+ }
+ );
+ });
+
+ table.find('.button-price-break-edit').click(function() {
+ var pk = $(this).attr('pk');
+
+ launchModalForm(
+ `/part/sale-price/${pk}/edit/`,
+ {
+ success: reloadPriceBreaks
+ }
+ );
+ });
+ },
+ columns: [
+ {
+ field: 'pk',
+ title: 'ID',
+ visible: false,
+ switchable: false,
+ },
+ {
+ field: 'quantity',
+ title: '{% trans "Quantity" %}',
+ sortable: true,
+ },
+ {
+ field: 'price',
+ title: '{% trans "Price" %}',
+ sortable: true,
+ formatter: function(value, row, index) {
+ var html = value;
+
+ html += ``
+
+ html += makeIconButton('fa-edit icon-blue', 'button-price-break-edit', row.pk, '{% trans "Edit price break" %}');
+ html += makeIconButton('fa-trash-alt icon-red', 'button-price-break-delete', row.pk, '{% trans "Delete price break" %}');
+
+ html += `
`;
+
+ return html;
+ }
+ },
+ ]
+ })
+
+ {% endif %}
+
+{% endblock %}
diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py
index d12612c604..7bd58f52f2 100644
--- a/InvenTree/part/urls.py
+++ b/InvenTree/part/urls.py
@@ -65,7 +65,7 @@ part_detail_urls = [
url(r'^bom/?', views.PartDetail.as_view(template_name='part/bom.html'), name='part-bom'),
url(r'^build/?', views.PartDetail.as_view(template_name='part/build.html'), name='part-build'),
url(r'^used/?', views.PartDetail.as_view(template_name='part/used_in.html'), name='part-used-in'),
- url(r'^order-prices/', views.PartPricingView.as_view(template_name='part/order_prices.html'), name='part-order-prices'),
+ url(r'^prices/', views.PartPricingView.as_view(template_name='part/prices.html'), name='part-prices'),
url(r'^manufacturers/?', views.PartDetail.as_view(template_name='part/manufacturer.html'), name='part-manufacturers'),
url(r'^suppliers/?', views.PartDetail.as_view(template_name='part/supplier.html'), name='part-suppliers'),
url(r'^orders/?', views.PartDetail.as_view(template_name='part/orders.html'), name='part-orders'),