mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #1712 from matmair/one-pricing-view
One pricing view
This commit is contained in:
commit
2b32f04af2
@ -961,3 +961,20 @@ input[type="date"].form-control, input[type="time"].form-control, input[type="da
|
|||||||
.sidebar-icon {
|
.sidebar-icon {
|
||||||
min-width: 19px;
|
min-width: 19px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.row.full-height {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.full-height > [class*='col-'] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.anchor {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
top: -60px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
@ -1,122 +0,0 @@
|
|||||||
{% extends "part/part_base.html" %}
|
|
||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load inventree_extras %}
|
|
||||||
|
|
||||||
{% block menubar %}
|
|
||||||
{% include 'part/navbar.html' with tab='internal-prices' %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block heading %}
|
|
||||||
{% trans "Internal Price Information" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block details %}
|
|
||||||
{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
|
|
||||||
{% if show_internal_price and roles.sales_order.view %}
|
|
||||||
<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>
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
<div class='container-fluid'>
|
|
||||||
<h3>{% trans "Permission Denied" %}</h3>
|
|
||||||
|
|
||||||
<div class='alert alert-danger alert-block'>
|
|
||||||
{% trans "You do not have permission to view this page." %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block js_ready %}
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
{% 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 %}
|
|
||||||
{% endblock %}
|
|
@ -71,13 +71,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>
|
||||||
@ -97,19 +97,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if show_internal_price and roles.sales_order.view %}
|
{% if roles.sales_order.view %}
|
||||||
<li class='list-group-item {% if tab == "internal-prices" %}active{% endif %}' title='{% trans "Internal Price Information" %}'>
|
|
||||||
<a href='{% url "part-internal-prices" part.id %}'>
|
|
||||||
<span class='menu-tab-icon fas fa-dollar-sign' style='width: 20px;'></span>
|
|
||||||
{% trans "Internal Price" %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class='list-group-item {% if tab == "sales-prices" %}active{% endif %}' title='{% trans "Sales Price Information" %}'>
|
|
||||||
<a href='{% url "part-sale-prices" part.id %}'>
|
|
||||||
<span class='menu-tab-icon fas fa-dollar-sign sidebar-icon'></span>
|
|
||||||
{% trans "Sale Price" %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class='list-group-item {% if tab == "sales-orders" %}active{% endif %}' title='{% trans "Sales Orders" %}'>
|
<li class='list-group-item {% if tab == "sales-orders" %}active{% endif %}' title='{% trans "Sales Orders" %}'>
|
||||||
<a href='{% url "part-sales-orders" part.id %}'>
|
<a href='{% url "part-sales-orders" part.id %}'>
|
||||||
<span class='menu-tab-icon fas fa-truck sidebar-icon'></span>
|
<span class='menu-tab-icon fas fa-truck sidebar-icon'></span>
|
||||||
|
@ -1,236 +0,0 @@
|
|||||||
{% extends "part/part_base.html" %}
|
|
||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
{% load inventree_extras %}
|
|
||||||
|
|
||||||
{% block menubar %}
|
|
||||||
{% include 'part/navbar.html' with tab='order-prices' %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block heading %}
|
|
||||||
{% trans "Order Price Information" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block details %}
|
|
||||||
{% default_currency as currency %}
|
|
||||||
{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
|
|
||||||
|
|
||||||
|
|
||||||
<form method="post" class="form-horizontal">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-9">{{ form|crispy }}</div>
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input type="submit" value="{% trans 'Calculate' %}" class="btn btn-primary btn-block">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
{% 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>
|
|
||||||
|
|
||||||
{% if price_history %}
|
|
||||||
<hr>
|
|
||||||
<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 %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% block js_ready %}
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
{% 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 %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
488
InvenTree/part/templates/part/prices.html
Normal file
488
InvenTree/part/templates/part/prices.html
Normal file
@ -0,0 +1,488 @@
|
|||||||
|
{% 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">
|
||||||
|
<a class="anchor" id="overview"></a>
|
||||||
|
<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>
|
||||||
|
<a href="#supplier-cost" title='{% trans "Show supplier cost" %}'><span class="fas fa-search-dollar"></span></a>
|
||||||
|
<a href="#purchase-price" title='{% trans "Show purchase price" %}'><span class="fas fa-chart-bar"></span></a>
|
||||||
|
</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>
|
||||||
|
<a href="#bom-cost" title='{% trans "Show BOM cost" %}'><span class="fas fa-search-dollar"></span></a>
|
||||||
|
</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>
|
||||||
|
<a href="#sale-cost" title='{% trans "Show sale cost" %}'><span class="fas fa-search-dollar"></span></a>
|
||||||
|
<a href="#sale-price" title='{% trans "Show sale price" %}'><span class="fas fa-chart-bar"></span></a>
|
||||||
|
</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">
|
||||||
|
<h4>{% trans "Calculation parameters" %}</h4>
|
||||||
|
<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'>
|
||||||
|
<a class="anchor" id="supplier-cost"></a>
|
||||||
|
<div class='panel-heading'>
|
||||||
|
<h4>{% trans "Supplier Cost" %}
|
||||||
|
<a href="#overview" title='{% trans "Jump to overview" %}'><span class="fas fa-level-up-alt"></span></a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<h4>{% trans "Manufacturers" %}</h4>
|
||||||
|
<table class="table table-striped table-condensed" id='manufacturer-table' data-toolbar='#button-toolbar'></table>
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='panel panel-default panel-inventree'>
|
||||||
|
<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>
|
||||||
|
{% 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'>
|
||||||
|
<a class="anchor" id="internal-cost"></a>
|
||||||
|
<div class='panel-heading'>
|
||||||
|
<h4>{% trans "Internal Cost" %}
|
||||||
|
<a href="#overview" title='{% trans "Jump to overview" %}'><span class="fas fa-level-up-alt"></span></a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-4">
|
||||||
|
<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'
|
||||||
|
data-sort-name="quantity" data-sort-order="asc">
|
||||||
|
</table>
|
||||||
|
</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" %}
|
||||||
|
<a href="#overview" title='{% trans "Jump to overview" %}'><span class="fas fa-level-up-alt"></span></a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{% 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>
|
||||||
|
{% 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" %}
|
||||||
|
<a href="#overview" title='{% trans "Jump to overview" %}'><span class="fas fa-level-up-alt"></span></a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-4">
|
||||||
|
<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'
|
||||||
|
data-sort-name="quantity" data-sort-order="asc">
|
||||||
|
</table>
|
||||||
|
</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" %}
|
||||||
|
<a href="#overview" title='{% trans "Jump to overview" %}'><span class="fas fa-level-up-alt"></span></a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='panel-content'>
|
||||||
|
{% if sale_history|length > 0 %}
|
||||||
|
<div style="max-width: 99%; min-height: 300px">
|
||||||
|
<canvas id="SalePriceChart"></canvas>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class='alert alert-danger alert-block'>
|
||||||
|
{% trans 'No sale pice history available for this part.' %}
|
||||||
|
</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 %}
|
@ -1,108 +0,0 @@
|
|||||||
{% extends "part/part_base.html" %}
|
|
||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block menubar %}
|
|
||||||
{% include 'part/navbar.html' with tab='sales-prices' %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block heading %}
|
|
||||||
{% trans "Sell Price Information" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block details %}
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block js_ready %}
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -65,13 +65,11 @@ 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'),
|
||||||
url(r'^sales-orders/', views.PartDetail.as_view(template_name='part/sales_orders.html'), name='part-sales-orders'),
|
url(r'^sales-orders/', views.PartDetail.as_view(template_name='part/sales_orders.html'), name='part-sales-orders'),
|
||||||
url(r'^sale-prices/', views.PartDetail.as_view(template_name='part/sale_prices.html'), name='part-sale-prices'),
|
|
||||||
url(r'^internal-prices/', views.PartDetail.as_view(template_name='part/internal_prices.html'), name='part-internal-prices'),
|
|
||||||
url(r'^tests/', views.PartDetail.as_view(template_name='part/part_tests.html'), name='part-test-templates'),
|
url(r'^tests/', views.PartDetail.as_view(template_name='part/part_tests.html'), name='part-test-templates'),
|
||||||
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
||||||
url(r'^related-parts/?', views.PartDetail.as_view(template_name='part/related.html'), name='part-related'),
|
url(r'^related-parts/?', views.PartDetail.as_view(template_name='part/related.html'), name='part-related'),
|
||||||
|
@ -50,6 +50,7 @@ import common.settings as inventree_settings
|
|||||||
|
|
||||||
from . import forms as part_forms
|
from . import forms as part_forms
|
||||||
from .bom import MakeBomTemplate, BomUploadManager, ExportBom, IsValidBOMFormat
|
from .bom import MakeBomTemplate, BomUploadManager, ExportBom, IsValidBOMFormat
|
||||||
|
from order.models import PurchaseOrderLineItem
|
||||||
|
|
||||||
from .admin import PartResource
|
from .admin import PartResource
|
||||||
|
|
||||||
@ -979,18 +980,20 @@ class PartPricingView(PartDetail):
|
|||||||
""" returns context with pricing information """
|
""" returns context with pricing information """
|
||||||
ctx = PartPricing.get_pricing(self, quantity, currency)
|
ctx = PartPricing.get_pricing(self, quantity, currency)
|
||||||
part = self.get_part()
|
part = self.get_part()
|
||||||
|
default_currency = inventree_settings.currency_code_default()
|
||||||
|
|
||||||
# Stock history
|
# Stock history
|
||||||
if part.total_stock > 1:
|
if part.total_stock > 1:
|
||||||
price_history = []
|
price_history = []
|
||||||
stock = part.stock_entries(include_variants=False, in_stock=True) # .order_by('purchase_order__date')
|
stock = part.stock_entries(include_variants=False, in_stock=True).\
|
||||||
stock = stock.prefetch_related('purchase_order', 'supplier_part')
|
order_by('purchase_order__issue_date').prefetch_related('purchase_order', 'supplier_part')
|
||||||
|
|
||||||
for stock_item in stock:
|
for stock_item in stock:
|
||||||
if None in [stock_item.purchase_price, stock_item.quantity]:
|
if None in [stock_item.purchase_price, stock_item.quantity]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# convert purchase price to current currency - only one currency in the graph
|
# convert purchase price to current currency - only one currency in the graph
|
||||||
price = convert_money(stock_item.purchase_price, inventree_settings.currency_code_default())
|
price = convert_money(stock_item.purchase_price, default_currency)
|
||||||
line = {
|
line = {
|
||||||
'price': price.amount,
|
'price': price.amount,
|
||||||
'qty': stock_item.quantity
|
'qty': stock_item.quantity
|
||||||
@ -1036,6 +1039,36 @@ class PartPricingView(PartDetail):
|
|||||||
# add to global context
|
# add to global context
|
||||||
ctx['bom_parts'] = ctx_bom_parts
|
ctx['bom_parts'] = ctx_bom_parts
|
||||||
|
|
||||||
|
# Sale price history
|
||||||
|
sale_items = PurchaseOrderLineItem.objects.filter(part__part=part).order_by('order__issue_date').\
|
||||||
|
prefetch_related('order', ).all()
|
||||||
|
|
||||||
|
if sale_items:
|
||||||
|
sale_history = []
|
||||||
|
|
||||||
|
for sale_item in sale_items:
|
||||||
|
# check for not fully defined elements
|
||||||
|
if None in [sale_item.purchase_price, sale_item.quantity]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
price = convert_money(sale_item.purchase_price, default_currency)
|
||||||
|
line = {
|
||||||
|
'price': price.amount if price else 0,
|
||||||
|
'qty': sale_item.quantity,
|
||||||
|
}
|
||||||
|
|
||||||
|
# set date for graph labels
|
||||||
|
if sale_item.order.issue_date:
|
||||||
|
line['date'] = sale_item.order.issue_date.strftime('%d.%m.%Y')
|
||||||
|
elif sale_item.order.creation_date:
|
||||||
|
line['date'] = sale_item.order.creation_date.strftime('%d.%m.%Y')
|
||||||
|
else:
|
||||||
|
line['date'] = _('None')
|
||||||
|
|
||||||
|
sale_history.append(line)
|
||||||
|
|
||||||
|
ctx['sale_history'] = sale_history
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def get_initials(self):
|
def get_initials(self):
|
||||||
|
@ -769,6 +769,159 @@ function loadPartTestTemplateTable(table, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadPriceBreakTable(table, options) {
|
||||||
|
/*
|
||||||
|
* Load PriceBreak table.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var name = options.name || 'pricebreak';
|
||||||
|
var human_name = options.human_name || 'price break';
|
||||||
|
var linkedGraph = options.linkedGraph || null;
|
||||||
|
var chart = null;
|
||||||
|
|
||||||
|
table.inventreeTable({
|
||||||
|
name: name,
|
||||||
|
method: 'get',
|
||||||
|
formatNoMatches: function() {
|
||||||
|
return `{% trans "No ${human_name} information found" %}`;
|
||||||
|
},
|
||||||
|
url: options.url,
|
||||||
|
onLoadSuccess: function(tableData) {
|
||||||
|
if (linkedGraph) {
|
||||||
|
// sort array
|
||||||
|
tableData = tableData.sort((a,b)=>a.quantity-b.quantity);
|
||||||
|
|
||||||
|
// split up for graph definition
|
||||||
|
var graphLabels = Array.from(tableData, x => x.quantity);
|
||||||
|
var graphData = Array.from(tableData, x => parseFloat(x.price));
|
||||||
|
|
||||||
|
// destroy chart if exists
|
||||||
|
if (chart){
|
||||||
|
chart.destroy();
|
||||||
|
}
|
||||||
|
chart = loadLineChart(linkedGraph,
|
||||||
|
{
|
||||||
|
labels: graphLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '{% trans "Unit Price" %}',
|
||||||
|
data: graphData,
|
||||||
|
backgroundColor: 'rgba(255, 206, 86, 0.2)',
|
||||||
|
borderColor: 'rgb(255, 206, 86)',
|
||||||
|
stepped: true,
|
||||||
|
fill: true,
|
||||||
|
},]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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-${name}-edit`, row.pk, `{% trans "Edit ${human_name}" %}`);
|
||||||
|
html += makeIconButton('fa-trash-alt icon-red', `button-${name}-delete`, row.pk, `{% trans "Delete ${human_name}" %}`);
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLineChart(context, data) {
|
||||||
|
return new Chart(context, {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {position: 'bottom'},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPriceBreakSet(table, options) {
|
||||||
|
|
||||||
|
var part_id = options.part_id;
|
||||||
|
var pb_human_name = options.pb_human_name;
|
||||||
|
var pb_url_slug = options.pb_url_slug;
|
||||||
|
var pb_url = options.pb_url;
|
||||||
|
var pb_new_btn = options.pb_new_btn;
|
||||||
|
var pb_new_url = options.pb_new_url;
|
||||||
|
|
||||||
|
var linkedGraph = options.linkedGraph || null;
|
||||||
|
|
||||||
|
loadPriceBreakTable(
|
||||||
|
table,
|
||||||
|
{
|
||||||
|
name: pb_url_slug,
|
||||||
|
human_name: pb_human_name,
|
||||||
|
url: pb_url,
|
||||||
|
linkedGraph: linkedGraph,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function reloadPriceBreakTable(){
|
||||||
|
table.bootstrapTable("refresh");
|
||||||
|
}
|
||||||
|
|
||||||
|
pb_new_btn.click(function() {
|
||||||
|
launchModalForm(pb_new_url,
|
||||||
|
{
|
||||||
|
success: reloadPriceBreakTable,
|
||||||
|
data: {
|
||||||
|
part: part_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on('click', `.button-${pb_url_slug}-delete`, function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
launchModalForm(
|
||||||
|
`/part/${pb_url_slug}/${pk}/delete/`,
|
||||||
|
{
|
||||||
|
success: reloadPriceBreakTable
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on('click', `.button-${pb_url_slug}-edit`, function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
launchModalForm(
|
||||||
|
`/part/${pb_url_slug}/${pk}/edit/`,
|
||||||
|
{
|
||||||
|
success: reloadPriceBreakTable
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function loadStockPricingChart(context, data) {
|
function loadStockPricingChart(context, data) {
|
||||||
return new Chart(context, {
|
return new Chart(context, {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
@ -824,3 +977,36 @@ function loadBomChart(context, data) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadSellPricingChart(context, data) {
|
||||||
|
return new Chart(context, {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {legend: {position: 'bottom'}},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
type: 'linear',
|
||||||
|
position: 'left',
|
||||||
|
grid: {display: false},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: '{% trans "Unit Price" %}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y1: {
|
||||||
|
type: 'linear',
|
||||||
|
position: 'right',
|
||||||
|
grid: {display: false},
|
||||||
|
titel: {
|
||||||
|
display: true,
|
||||||
|
text: '{% trans "Quantity" %}',
|
||||||
|
position: 'right'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user