initial structure for single pricing view

This commit is contained in:
Matthias 2021-06-23 01:07:07 +02:00
parent 962badc36d
commit 093a181751
3 changed files with 495 additions and 5 deletions

View File

@ -70,13 +70,13 @@
</a> </a>
</li> </li>
{% endif %} {% endif %}
{% if part.purchaseable and roles.purchase_order.view %} <li class='list-group-item {% if tab == "prices" %}active{% endif %}' title='{% trans "Pricing Information" %}'>
<li class='list-group-item {% if tab == "order-prices" %}active{% endif %}' title='{% trans "Order Price Information" %}'> <a href='{% url "part-prices" part.id %}'>
<a href='{% url "part-order-prices" part.id %}'>
<span class='menu-tab-icon fas fa-dollar-sign sidebar-icon'></span> <span class='menu-tab-icon fas fa-dollar-sign sidebar-icon'></span>
{% trans "Order Price" %} {% trans "Prices" %}
</a> </a>
</li> </li>
{% if part.purchaseable and roles.purchase_order.view %}
<li class='list-group-item {% if tab == "manufacturers" %}active{% endif %}' title='{% trans "Manufacturers" %}'> <li class='list-group-item {% if tab == "manufacturers" %}active{% endif %}' title='{% trans "Manufacturers" %}'>
<a href='{% url "part-manufacturers" part.id %}'> <a href='{% url "part-manufacturers" part.id %}'>
<span class='menu-tab-icon fas fa-industry sidebar-icon'></span> <span class='menu-tab-icon fas fa-industry sidebar-icon'></span>

View File

@ -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 %}
<div class="row">
<div class="col col-md-6">
<h4>{% trans "Pricing ranges" %}</h4>
<table class='table table-striped table-condensed'>
{% if part.supplier_count > 0 %}
{% if min_total_buy_price %}
<tr>
<td><b>{% trans 'Supplier Pricing' %}</b></td>
<td>{% trans 'Unit Cost' %}</td>
<td>Min: {% include "price.html" with price=min_unit_buy_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_buy_price %}</td>
</tr>
{% if quantity > 1 %}
<tr>
<td></td>
<td>{% trans 'Total Cost' %}</td>
<td>Min: {% include "price.html" with price=min_total_buy_price %}</td>
<td>Max: {% include "price.html" with price=max_total_buy_price %}</td>
</tr>
{% endif %}
{% else %}
<tr>
<td colspan='4'>
<span class='warning-msg'><i>{% trans 'No supplier pricing available' %}</i></span>
</td>
</tr>
{% endif %}
{% endif %}
{% if part.bom_count > 0 %}
{% if min_total_bom_price %}
<tr>
<td><b>{% trans 'BOM Pricing' %}</b></td>
<td>{% trans 'Unit Cost' %}</td>
<td>Min: {% include "price.html" with price=min_unit_bom_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_bom_price %}</td>
</tr>
{% if quantity > 1 %}
<tr>
<td></td>
<td>{% trans 'Total Cost' %}</td>
<td>Min: {% include "price.html" with price=min_total_bom_price %}</td>
<td>Max: {% include "price.html" with price=max_total_bom_price %}</td>
</tr>
{% endif %}
{% if part.has_complete_bom_pricing == False %}
<tr>
<td colspan='4'>
<span class='warning-msg'><i>{% trans 'Note: BOM pricing is incomplete for this part' %}</i></span>
</td>
</tr>
{% endif %}
{% else %}
<tr>
<td colspan='4'>
<span class='warning-msg'><i>{% trans 'No BOM pricing available' %}</i></span>
</td>
</tr>
{% endif %}
{% endif %}
{% if show_internal_price and roles.sales_order.view %}
{% if total_internal_part_price %}
<tr>
<td><b>{% trans 'Internal Price' %}</b></td>
<td>{% trans 'Unit Cost' %}</td>
<td colspan='2'>{% include "price.html" with price=unit_internal_part_price %}</td>
</tr>
<tr>
<td></td>
<td>{% trans 'Total Cost' %}</td>
<td colspan='2'>{% include "price.html" with price=total_internal_part_price %}</td>
</tr>
{% endif %}
{% endif %}
{% if total_part_price %}
<tr>
<td><b>{% trans 'Sale Price' %}</b></td>
<td>{% trans 'Unit Cost' %}</td>
<td colspan='2'>{% include "price.html" with price=unit_part_price %}</td>
</tr>
<tr>
<td></td>
<td>{% trans 'Total Cost' %}</td>
<td colspan='2'>{% include "price.html" with price=total_part_price %}</td>
</tr>
{% endif %}
</table>
{% if min_unit_buy_price or min_unit_bom_price %}
{% else %}
<div class='alert alert-danger alert-block'>
{% trans 'No pricing information is available for this part.' %}
</div>
{% endif %}
</div>
<div class="col col-md-6">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" value="{% trans 'Calculate' %}" class="btn btn-primary btn-block">
</form>
</div>
</div>
{% 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 %}
<div class='panel panel-default panel-inventree'>
<div class='panel-heading'>
<h4>{% trans "Supplier Cost" %}</h4>
</div>
<div class='panel-content'>
PLACEHOLDER FOR SUPPLIER COST TABLE
</div>
</div>
<div class='panel panel-default panel-inventree'>
<div class='panel-heading'>
<h4>{% trans "Purchase Price" %}</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>
{% if price_history|length > 0 %}
<div style="max-width: 99%; min-height: 300px">
<canvas id="StockPriceChart"></canvas>
</div>
{% else %}
<div class='alert alert-danger alert-block'>
{% trans 'No stock pricing history is available for this part.' %}
</div>
{% endif %}
{% endif %}
</div>
{% endif %}
{% if show_internal_price and roles.sales_order.view %}
<div class='panel panel-default panel-inventree'>
<div class='panel-heading'>
<h4>{% trans "Internal Cost" %}</h4>
</div>
<div class='panel-content'>
<div id='internal-price-break-toolbar' class='btn-group'>
<button class='btn btn-primary' id='new-internal-price-break' type='button'>
<span class='fas fa-plus-circle'></span> {% trans "Add Internal Price Break" %}
</button>
</div>
<table class='table table-striped table-condensed' id='internal-price-break-table' data-toolbar='#internal-price-break-toolbar'>
</table>
</div>
</div>
{% endif %}
<div class='panel panel-default panel-inventree'>
<div class='panel-heading'>
<h4>{% trans "BOM Cost" %}</h4>
</div>
<div class='panel-content'><div class="row">
<div class="col col-md-6">
PLACEHOLDER FOR BOM ITEM COST TABLE
<table class='table table-striped table-condensed' id='sales-order-table'></table>
</div>
{% if part.bom_count > 0 %}
<div class="col col-md-6">
<h4>{% trans 'BOM Pricing' %}</h4>
<div style="max-width: 99%;">
<canvas id="BomChart"></canvas>
</div>
</div>
{% endif %}
</div></div>
</div>
{% if part.salable and roles.sales_order.view %}
<div class='panel panel-default panel-inventree'>
<div class='panel-heading'>
<h4>{% trans "Sale Cost" %}</h4>
</div>
<div class='panel-content'>
<div id='price-break-toolbar' class='btn-group'>
<button class='btn btn-primary' id='new-price-break' type='button'>
<span class='fas fa-plus-circle'></span> {% trans "Add Price Break" %}
</button>
</div>
<table class='table table-striped table-condensed' id='price-break-table' data-toolbar='#price-break-toolbar'>
</table>
</div>
</div>
<div class='panel panel-default panel-inventree'>
<div class='panel-heading'>
<h4>{% trans "Sale Price" %}</h4>
</div>
<div class='panel-content'>
PLACEHOLDER FOR SALE HISTORY
</div>
</div>
{% 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 += `<div class='btn-group float-right' role='group'>`
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 += `</div>`;
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 += `<div class='btn-group float-right' role='group'>`
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 += `</div>`;
return html;
}
},
]
})
{% endif %}
{% endblock %}

View File

@ -65,7 +65,7 @@ part_detail_urls = [
url(r'^bom/?', views.PartDetail.as_view(template_name='part/bom.html'), name='part-bom'), 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'^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'^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'^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'^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'), url(r'^orders/?', views.PartDetail.as_view(template_name='part/orders.html'), name='part-orders'),