Refactor part pricing page

This commit is contained in:
Oliver 2021-07-15 23:06:37 +10:00
parent 0fc558068f
commit c61fc7c1df
9 changed files with 366 additions and 435 deletions

View File

@ -7,9 +7,8 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.views.generic import DetailView, ListView, UpdateView
from django.views.generic import DetailView, ListView
from django.forms import HiddenInput
from django.urls import reverse
from part.models import Part
from .models import Build, BuildItem

View File

@ -1,6 +1,8 @@
{% extends "part/part_base.html" %}
{% load static %}
{% load i18n %}
{% load inventree_extras %}
{% load crispy_forms_tags %}
{% load markdownify %}
{% block menubar %}
@ -92,6 +94,10 @@
</div>
</div>
<div class='panel panel-default panel-inventree panel-hidden' id='panel-pricing'>
{% include "part/prices.html" %}
</div>
<div class='panel panel-default panel-inventree panel-hidden' id='panel-part-notes'>
<div class='panel-heading'>
<div class='row'>
@ -893,6 +899,152 @@
});
});
{% default_currency as currency %}
// history graphs
{% if price_history %}
var purchasepricedata = {
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($('#StockPriceChart'), purchasepricedata)
{% endif %}
{% if bom_parts %}
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 %}
initPriceBreakSet(
$('#internal-price-break-table'),
{
part_id: {{part.id}},
pb_human_name: 'internal price break',
pb_url_slug: 'internal-price',
pb_url: '{% url 'api-part-internal-price-list' %}',
pb_new_btn: $('#new-internal-price-break'),
pb_new_url: '{% url 'internal-price-break-create' %}',
linkedGraph: $('#InternalPriceBreakChart'),
},
);
{% endif %}
// Sales pricebreaks
{% if part.salable and roles.sales_order.view %}
initPriceBreakSet(
$('#price-break-table'),
{
part_id: {{part.id}},
pb_human_name: 'sale price break',
pb_url_slug: 'sale-price',
pb_url: "{% url 'api-part-sale-price-list' %}",
pb_new_btn: $('#new-price-break'),
pb_new_url: '{% url 'sale-price-break-create' %}',
linkedGraph: $('#SalePriceBreakChart'),
},
);
{% endif %}
// Sale price history
{% if sale_history %}
var salepricedata = {
labels: [
{% for line in sale_history %}'{{ line.date }}',{% endfor %}
],
datasets: [{
label: '{% blocktrans %}Unit Price - {{currency}}{% endblocktrans %}',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgb(255, 99, 132)',
yAxisID: 'y',
data: [
{% for line in sale_history %}{{ line.price|stringformat:".2f" }},{% endfor %}
],
borderWidth: 1,
},
{
label: '{% trans "Quantity" %}',
backgroundColor: 'rgba(255, 206, 86, 0.2)',
borderColor: 'rgb(255, 206, 86)',
yAxisID: 'y1',
data: [
{% for line in sale_history %}{{ line.qty|stringformat:"f" }},{% endfor %}
],
borderWidth: 1,
type: 'bar',
}]
}
var SalePriceChart = loadSellPricingChart($('#SalePriceChart'), salepricedata)
{% endif %}
attachNavCallbacks({
name: 'part',
default: 'part-stock'

View File

@ -56,7 +56,7 @@
</li>
{% endif %}
<li class='list-group-item' title='{% trans "Pricing Information" %}'>
<a href='#' id='select-part-pricing' class='nav-toggle'>
<a href='#' id='select-pricing' class='nav-toggle'>
<span class='menu-tab-icon fas fa-dollar-sign sidebar-icon'></span>
{% trans "Prices" %}
</a>

View File

@ -1,20 +1,14 @@
{% 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 %}
<div class='panel-heading'>
<h4>{% trans "Pricing Information" %}</h4>
</div>
{% block heading %}
{% trans "General Price Information" %}
{% endblock %}
{% block details %}
{% default_currency as currency %}
<div class='panel-content'>
<div class="row">
<a class="anchor" id="overview"></a>
@ -132,15 +126,11 @@
</form>
</div>
</div>
{% endblock %}
</div>
{% 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 %}
<div class='panel panel-default panel-inventree'>
<a class="anchor" id="supplier-cost"></a>
<div class='panel-heading'>
<h4>{% trans "Supplier Cost" %}
@ -148,7 +138,8 @@
</h4>
</div>
<div class='panel-content'><div class="row">
<div class='panel-content'>
<div class="row">
<div class="col col-md-6">
<h4>{% trans "Suppliers" %}</h4>
<table class="table table-striped table-condensed" id='supplier-table' data-toolbar='#button-toolbar'></table>
@ -157,20 +148,21 @@
<h4>{% trans "Manufacturers" %}</h4>
<table class="table table-striped table-condensed" id='manufacturer-table' data-toolbar='#button-toolbar'></table>
</div>
</div></div>
</div>
</div>
<div class='panel panel-default panel-inventree'>
{% if price_history %}
<a class="anchor" id="purchase-price"></a>
<div class='panel-heading'>
<h4>{% trans "Purchase Price" %}
<a href="#overview" title='{% trans "Jump to overview" %}'><span class="fas fa-level-up-alt"></span></a>
</h4>
</div>
{% if price_history %}
<h4>{% trans 'Stock Pricing' %}<i class="fas fa-info-circle" title="Shows the purchase prices of stock for this part.
The part single price is the current purchase price for that supplier part."></i></h4>
<div class='panel-content'>
<h4>{% trans 'Stock Pricing' %}
<i class="fas fa-info-circle" title="Shows the purchase prices of stock for this part.
The part single price is the current purchase price for that supplier part."></i>
</h4>
{% if price_history|length > 0 %}
<div style="max-width: 99%; min-height: 300px">
<canvas id="StockPriceChart"></canvas>
@ -180,13 +172,11 @@
{% trans 'No stock pricing history is available for this part.' %}
</div>
{% endif %}
{% endif %}
</div>
{% endif %}
{% endif %}
{% if show_internal_price and roles.sales_order.view %}
<div class='panel panel-default panel-inventree'>
<a class="anchor" id="internal-cost"></a>
<div class='panel-heading'>
<h4>{% trans "Internal Cost" %}
@ -194,7 +184,8 @@
</h4>
</div>
<div class='panel-content'><div class="row full-height">
<div class='panel-content'>
<div class="row full-height">
<div class="col col-md-8">
<div style="max-width: 99%; height: 100%;">
<canvas id="InternalPriceBreakChart"></canvas>
@ -211,13 +202,11 @@
data-sort-name="quantity" data-sort-order="asc">
</table>
</div>
</div></div>
</div>
</div>
{% endif %}
{% if part.has_bom and roles.sales_order.view %}
<div class='panel panel-default panel-inventree'>
<a class="anchor" id="bom-cost"></a>
<div class='panel-heading'>
<h4>{% trans "BOM Cost" %}
@ -225,7 +214,8 @@
</h4>
</div>
<div class='panel-content'><div class="row">
<div class='panel-content'>
<div class="row">
<div class="col col-md-6">
<table class='table table-bom table-condensed' data-toolbar="#button-toolbar" id='bom-table'></table>
</div>
@ -238,13 +228,11 @@
</div>
</div>
{% endif %}
</div></div>
</div>
</div>
{% endif %}
{% if part.salable and roles.sales_order.view %}
<div class='panel panel-default panel-inventree'>
<a class="anchor" id="sale-cost"></a>
<div class='panel-heading'>
<h4>{% trans "Sale Cost" %}
@ -252,7 +240,8 @@
</h4>
</div>
<div class='panel-content'><div class="row full-height">
<div class='panel-content'>
<div class="row full-height">
<div class="col col-md-8">
<div style="max-width: 99%; height: 100%;">
<canvas id="SalePriceBreakChart"></canvas>
@ -269,10 +258,9 @@
data-sort-name="quantity" data-sort-order="asc">
</table>
</div>
</div></div>
</div>
</div>
<div class='panel panel-default panel-inventree'>
<a class="anchor" id="sale-price"></a>
<div class='panel-heading'>
<h4>{% trans "Sale Price" %}
@ -291,198 +279,4 @@
</div>
{% endif %}
</div>
</div>
{% endif %}
{% endblock %}
{% block js_ready %}
{{ block.super }}
{% default_currency as currency %}
loadSupplierPartTable(
"#supplier-table",
"{% url 'api-supplier-part-list' %}",
{
params: {
part: {{ part.id }},
part_detail: false,
supplier_detail: true,
manufacturer_detail: true,
},
}
);
loadManufacturerPartTable(
"#manufacturer-table",
"{% url 'api-manufacturer-part-list' %}",
{
params: {
part: {{ part.id }},
part_detail: false,
manufacturer_detail: true,
},
}
);
// history graphs
{% if price_history %}
var purchasepricedata = {
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($('#StockPriceChart'), purchasepricedata)
{% endif %}
{% if bom_parts %}
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 %}
initPriceBreakSet(
$('#internal-price-break-table'),
{
part_id: {{part.id}},
pb_human_name: 'internal price break',
pb_url_slug: 'internal-price',
pb_url: '{% url 'api-part-internal-price-list' %}',
pb_new_btn: $('#new-internal-price-break'),
pb_new_url: '{% url 'internal-price-break-create' %}',
linkedGraph: $('#InternalPriceBreakChart'),
},
);
{% endif %}
// Load the BOM table data
loadBomTable($("#bom-table"), {
editable: {{ editing_enabled }},
bom_url: "{% url 'api-bom-list' %}",
part_url: "{% url 'api-part-list' %}",
parent_id: {{ part.id }} ,
sub_part_detail: true,
});
// Sales pricebreaks
{% if part.salable and roles.sales_order.view %}
initPriceBreakSet(
$('#price-break-table'),
{
part_id: {{part.id}},
pb_human_name: 'sale price break',
pb_url_slug: 'sale-price',
pb_url: "{% url 'api-part-sale-price-list' %}",
pb_new_btn: $('#new-price-break'),
pb_new_url: '{% url 'sale-price-break-create' %}',
linkedGraph: $('#SalePriceBreakChart'),
},
);
{% endif %}
// Sale price history
{% if sale_history %}
var salepricedata = {
labels: [
{% for line in sale_history %}'{{ line.date }}',{% endfor %}
],
datasets: [{
label: '{% blocktrans %}Unit Price - {{currency}}{% endblocktrans %}',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgb(255, 99, 132)',
yAxisID: 'y',
data: [
{% for line in sale_history %}{{ line.price|stringformat:".2f" }},{% endfor %}
],
borderWidth: 1,
},
{
label: '{% trans "Quantity" %}',
backgroundColor: 'rgba(255, 206, 86, 0.2)',
borderColor: 'rgb(255, 206, 86)',
yAxisID: 'y1',
data: [
{% for line in sale_history %}{{ line.qty|stringformat:"f" }},{% endfor %}
],
borderWidth: 1,
type: 'bar',
}]
}
var SalePriceChart = loadSellPricingChart($('#SalePriceChart'), salepricedata)
{% endif %}
{% endblock %}

View File

@ -47,8 +47,6 @@ part_detail_urls = [
url(r'^bom-upload/?', views.BomUpload.as_view(), name='upload-bom'),
url(r'^bom-duplicate/?', views.BomDuplicate.as_view(), name='duplicate-bom'),
url(r'^prices/', views.PartPricingView.as_view(template_name='part/prices.html'), name='part-prices'),
url(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'),
# Normal thumbnail with form

View File

@ -754,6 +754,7 @@ class PartDetail(InvenTreeRoleMixin, DetailView):
context_object_name = 'part'
queryset = Part.objects.all().select_related('category')
template_name = 'part/detail.html'
form_class = part_forms.PartPriceForm
# Add in some extra context information based on query params
def get_context_data(self, **kwargs):
@ -774,25 +775,12 @@ class PartDetail(InvenTreeRoleMixin, DetailView):
ctx = part.get_context_data(self.request)
context.update(**ctx)
return context
class PartPricingView(PartDetail):
""" Detail view for Part object
"""
context_object_name = 'part'
template_name = 'part/order_prices.html'
form_class = part_forms.PartPriceForm
# Add in some extra context information based on query params
def get_context_data(self, **kwargs):
""" Provide extra context data to template """
context = super().get_context_data(**kwargs)
# Pricing information
ctx = self.get_pricing(self.get_quantity())
ctx['form'] = self.form_class(initial=self.get_initials())
context.update(ctx)
return context
def get_quantity(self):

View File

@ -921,7 +921,7 @@ function loadBuildTable(table, options) {
}
else
{
return '{% trans "No user information" %}';
return `<i>{% trans "No user information" %}</i>`;
}
}
},

View File

@ -714,7 +714,7 @@ function loadPartTable(table, url, options={}) {
var html = '';
html = `<div class='row'>`;
html = `<div class='row full-height'>`;
data.forEach(function(row, index) {

View File

@ -1495,7 +1495,7 @@ function loadStockTrackingTable(table, options) {
}
else
{
return '{% trans "No user information" %}';
return `<i>{% trans "No user information" %}</i>`;
}
}
});