mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master' into 0.4.x
This commit is contained in:
commit
c4570a79de
@ -111,8 +111,8 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<li><a href='#' id='build-cancel'><span class='fas fa-times-circle icon-red'></span> {% trans "Cancel Build" %}</a></li>
|
<li><a href='#' id='build-cancel'><span class='fas fa-times-circle icon-red'></span> {% trans "Cancel Build" %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if build.status == BuildStatus.CANCELLED and roles.build.delete %}
|
{% if build.status == BuildStatus.CANCELLED and roles.build.delete %}
|
||||||
<li><a href='#' id='build-delete'><span class='fas fa-trash-alt'></span> {% trans "Delete Build"% }</a>
|
<li><a href='#' id='build-delete'><span class='fas fa-trash-alt'></span> {% trans "Delete Build" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -443,6 +443,8 @@ class PartFilter(rest_filters.FilterSet):
|
|||||||
else:
|
else:
|
||||||
queryset = queryset.filter(IPN='')
|
queryset = queryset.filter(IPN='')
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
# Regex filter for name
|
# Regex filter for name
|
||||||
name_regex = rest_filters.CharFilter(label='Filter by name (regex)', field_name='name', lookup_expr='iregex')
|
name_regex = rest_filters.CharFilter(label='Filter by name (regex)', field_name='name', lookup_expr='iregex')
|
||||||
|
|
||||||
|
@ -899,7 +899,7 @@
|
|||||||
{% for line in price_history %}'{{ line.date }}',{% endfor %}
|
{% for line in price_history %}'{{ line.date }}',{% endfor %}
|
||||||
],
|
],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: '{% blocktrans %}Single Price - {{currency}}{% endblocktrans %}',
|
label: '{% blocktrans %}Purchase Unit Price - {{currency}}{% endblocktrans %}',
|
||||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||||
borderColor: 'rgb(255, 99, 132)',
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
yAxisID: 'y',
|
yAxisID: 'y',
|
||||||
@ -911,7 +911,7 @@
|
|||||||
},
|
},
|
||||||
{% if 'price_diff' in price_history.0 %}
|
{% if 'price_diff' in price_history.0 %}
|
||||||
{
|
{
|
||||||
label: '{% blocktrans %}Single Price Difference - {{currency}}{% endblocktrans %}',
|
label: '{% blocktrans %}Unit Price-Cost Difference - {{currency}}{% endblocktrans %}',
|
||||||
backgroundColor: 'rgba(68, 157, 68, 0.2)',
|
backgroundColor: 'rgba(68, 157, 68, 0.2)',
|
||||||
borderColor: 'rgb(68, 157, 68)',
|
borderColor: 'rgb(68, 157, 68)',
|
||||||
yAxisID: 'y2',
|
yAxisID: 'y2',
|
||||||
@ -923,7 +923,7 @@
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '{% blocktrans %}Part Single Price - {{currency}}{% endblocktrans %}',
|
label: '{% blocktrans %}Supplier Unit Cost - {{currency}}{% endblocktrans %}',
|
||||||
backgroundColor: 'rgba(70, 127, 155, 0.2)',
|
backgroundColor: 'rgba(70, 127, 155, 0.2)',
|
||||||
borderColor: 'rgb(70, 127, 155)',
|
borderColor: 'rgb(70, 127, 155)',
|
||||||
yAxisID: 'y',
|
yAxisID: 'y',
|
||||||
|
@ -10,132 +10,253 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class='panel panel-default panel-inventree'>
|
<div class="panel panel-default panel-inventree">
|
||||||
|
<!-- Default panel contents -->
|
||||||
|
<div class="panel-heading"><h3>{{ part.full_name }}</h3></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
{% include "part/part_thumb.html" %}
|
||||||
|
<div class="media-body">
|
||||||
|
<p>
|
||||||
|
<h3>
|
||||||
|
<!-- Admin View -->
|
||||||
|
{% if user.is_staff and roles.part.change %}
|
||||||
|
<a href="{% url 'admin:part_part_change' part.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a> 
|
||||||
|
{% endif %}
|
||||||
|
<!-- Properties -->
|
||||||
|
<div id='part-properties' class='btn-group' role='group'>
|
||||||
|
{% if part.is_template %}
|
||||||
|
<span class='fas fa-clone' title='{% trans "Part is a template part (variants can be made from this part)" %}'></span>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.assembly %}
|
||||||
|
<span class='fas fa-tools' title='{% trans "Part can be assembled from other parts" %}'></span>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.component %}
|
||||||
|
<span class='fas fa-th' title='{% trans "Part can be used in assemblies" %}'></span>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.trackable %}
|
||||||
|
<span class='fas fa-directions' title='{% trans "Part stock is tracked by serial number" %}'></span>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.purchaseable %}
|
||||||
|
<span class='fas fa-shopping-cart' title='{% trans "Part can be purchased from external suppliers" %}'></span>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.salable %}
|
||||||
|
<span class='fas fa-dollar-sign' title='{% trans "Part can be sold to customers" %}'></span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<!-- Part active -->
|
||||||
|
{% if not part.active %}
|
||||||
|
 
|
||||||
|
<div class='label label-large label-large-red'>
|
||||||
|
<span class='fas fa-skull-crossbones' title='{% trans "Part is virtual (not a physical part)" %}'></span>
|
||||||
|
{% trans 'Inactive' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<!-- Part virtual -->
|
||||||
|
{% if part.virtual and part.active %}
|
||||||
|
 
|
||||||
|
<div class='label label-large label-large-yellow'>
|
||||||
|
<span class='fas fa-ghost' title='{% trans "Part is virtual (not a physical part)" %}'></span>
|
||||||
|
{% trans 'Virtual' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</h3>
|
||||||
|
</p>
|
||||||
|
|
||||||
<div class="row">
|
<div class='btn-group action-buttons' role='group'>
|
||||||
<div class="col-sm-6">
|
<button type='button' class='btn btn-default' id='toggle-starred' title='{% trans "Star this part" %}'>
|
||||||
{% include "part/part_thumb.html" %}
|
<span id='part-star-icon' class='fas fa-star {% if starred %}icon-yellow{% endif %}'/>
|
||||||
<div class="media-body">
|
</button>
|
||||||
<h3>
|
|
||||||
{{ part.full_name }}
|
{% if barcodes %}
|
||||||
{% if user.is_staff and roles.part.change %}
|
<!-- Barcode actions menu -->
|
||||||
<a href="{% url 'admin:part_part_change' part.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
|
<div class='btn-group'>
|
||||||
{% endif %}
|
<button id='barcode-options' title='{% trans "Barcode actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-qrcode'></span> <span class='caret'></span></button>
|
||||||
{% if not part.active %}
|
<ul class='dropdown-menu'>
|
||||||
<div class='label label-large label-large-red'>
|
<li><a href='#' id='show-qr-code'><span class='fas fa-qrcode'></span> {% trans "Show QR Code" %}</a></li>
|
||||||
{% trans 'Inactive' %}
|
<li><a href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li>
|
||||||
</div>
|
</ul>
|
||||||
{% endif %}
|
</div>
|
||||||
</h3>
|
|
||||||
{% if part.description %}
|
|
||||||
<p><em>{{ part.description }}</em></p>
|
|
||||||
{% endif %}
|
|
||||||
<p>
|
|
||||||
<div id='part-properties' class='btn-group' role='group'>
|
|
||||||
{% if part.virtual %}
|
|
||||||
<span class='fas fa-ghost' title='{% trans "Part is virtual (not a physical part)" %}'></span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.is_template %}
|
{% if part.active %}
|
||||||
<span class='fas fa-clone' title='{% trans "Part is a template part (variants can be made from this part)" %}'></span>
|
<button type='button' class='btn btn-default' id='price-button' title='{% trans "Show pricing information" %}'>
|
||||||
{% endif %}
|
<span id='part-price-icon' class='fas fa-dollar-sign'/>
|
||||||
{% if part.assembly %}
|
</button>
|
||||||
<span class='fas fa-tools' title='{% trans "Part can be assembled from other parts" %}'></span>
|
{% if roles.stock.change %}
|
||||||
{% endif %}
|
<div class='btn-group'>
|
||||||
{% if part.component %}
|
<button id='stock-actions' title='{% trans "Stock actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'>
|
||||||
<span class='fas fa-th' title='{% trans "Part can be used in assemblies" %}'></span>
|
<span class='fas fa-boxes'></span> <span class='caret'></span>
|
||||||
{% endif %}
|
</button>
|
||||||
{% if part.trackable %}
|
<ul class='dropdown-menu'>
|
||||||
<span class='fas fa-directions' title='{% trans "Part stock is tracked by serial number" %}'></span>
|
<li>
|
||||||
|
<a href='#' id='part-count'>
|
||||||
|
<span class='fas fa-clipboard-list'></span>
|
||||||
|
{% trans "Count part stock" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href='#' id='part-move'>
|
||||||
|
<span class='fas fa-exchange-alt'></span>
|
||||||
|
{% trans "Transfer part stock" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.purchaseable %}
|
{% if part.purchaseable %}
|
||||||
<span class='fas fa-shopping-cart' title='{% trans "Part can be purchased from external suppliers" %}'></span>
|
{% if roles.purchase_order.add %}
|
||||||
{% endif %}
|
<button type='button' class='btn btn-default' id='part-order' title='{% trans "Order part" %}'>
|
||||||
{% if part.salable %}
|
<span id='part-order-icon' class='fas fa-shopping-cart'/>
|
||||||
<span class='fas fa-dollar-sign' title='{% trans "Part can be sold to customers" %}'></span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class='btn-group action-buttons' role='group'>
|
|
||||||
<button type='button' class='btn btn-default' id='toggle-starred' title='{% trans "Star this part" %}'>
|
|
||||||
<span id='part-star-icon' class='fas fa-star {% if starred %}icon-yellow{% endif %}'/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{% if barcodes %}
|
|
||||||
<!-- Barcode actions menu -->
|
|
||||||
<div class='btn-group'>
|
|
||||||
<button id='barcode-options' title='{% trans "Barcode actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-qrcode'></span> <span class='caret'></span></button>
|
|
||||||
<ul class='dropdown-menu'>
|
|
||||||
<li><a href='#' id='show-qr-code'><span class='fas fa-qrcode'></span> {% trans "Show QR Code" %}</a></li>
|
|
||||||
<li><a href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if part.active %}
|
|
||||||
<button type='button' class='btn btn-default' id='price-button' title='{% trans "Show pricing information" %}'>
|
|
||||||
<span id='part-price-icon' class='fas fa-dollar-sign'/>
|
|
||||||
</button>
|
|
||||||
{% if roles.stock.change %}
|
|
||||||
<div class='btn-group'>
|
|
||||||
<button id='stock-actions' title='{% trans "Stock actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'>
|
|
||||||
<span class='fas fa-boxes'></span> <span class='caret'></span>
|
|
||||||
</button>
|
</button>
|
||||||
<ul class='dropdown-menu'>
|
{% endif %}
|
||||||
<li>
|
{% endif %}
|
||||||
<a href='#' id='part-count'>
|
{% endif %}
|
||||||
<span class='fas fa-clipboard-list'></span>
|
<!-- Part actions -->
|
||||||
{% trans "Count part stock" %}
|
{% if roles.part.add or roles.part.change or roles.part.delete %}
|
||||||
</a>
|
<div class='btn-group'>
|
||||||
</li>
|
<button id='part-actions' title='{% trans "Part actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'> <span class='fas fa-shapes'></span> <span class='caret'></span></button>
|
||||||
<li>
|
<ul class='dropdown-menu'>
|
||||||
<a href='#' id='part-move'>
|
{% if roles.part.add %}
|
||||||
<span class='fas fa-exchange-alt'></span>
|
<li><a href='#' id='part-duplicate'><span class='fas fa-copy'></span> {% trans "Duplicate part" %}</a></li>
|
||||||
{% trans "Transfer part stock" %}
|
{% endif %}
|
||||||
</a>
|
{% if roles.part.change %}
|
||||||
</li>
|
<li><a href='#' id='part-edit'><span class='fas fa-edit icon-blue'></span> {% trans "Edit part" %}</a></li>
|
||||||
</ul>
|
{% endif %}
|
||||||
|
{% if not part.active and roles.part.delete %}
|
||||||
|
<li><a href='#' id='part-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete part" %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
{% if part.purchaseable %}
|
</div>
|
||||||
{% if roles.purchase_order.add %}
|
<div class='info-messages'>
|
||||||
<button type='button' class='btn btn-default' id='part-order' title='{% trans "Order part" %}'>
|
{% if part.variant_of %}
|
||||||
<span id='part-order-icon' class='fas fa-shopping-cart'/>
|
<div class='alert alert-info alert-block' style='padding: 10px;'>
|
||||||
</button>
|
{% object_link 'part-detail' part.variant_of.id part.variant_of.full_name as link %}
|
||||||
{% endif %}
|
{% blocktrans %}This part is a variant of {{link}}{% endblocktrans %}
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<!-- Part actions -->
|
|
||||||
{% if roles.part.add or roles.part.change or roles.part.delete %}
|
|
||||||
<div class='btn-group'>
|
|
||||||
<button id='part-actions' title='{% trans "Part actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'> <span class='fas fa-shapes'></span> <span class='caret'></span></button>
|
|
||||||
<ul class='dropdown-menu'>
|
|
||||||
{% if roles.part.add %}
|
|
||||||
<li><a href='#' id='part-duplicate'><span class='fas fa-copy'></span> {% trans "Duplicate part" %}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% if roles.part.change %}
|
|
||||||
<li><a href='#' id='part-edit'><span class='fas fa-edit icon-blue'></span> {% trans "Edit part" %}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% if not part.active and roles.part.delete %}
|
|
||||||
<li><a href='#' id='part-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete part" %}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<table class='table table-condensed'>
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<table class='table table-condensed table-striped'>
|
||||||
|
<col width='25'>
|
||||||
|
<tr>
|
||||||
|
<td><h4><span class='fas fa-boxes'></span></h4></td>
|
||||||
|
<td><h4>{% trans "Available Stock" %}</h4></td>
|
||||||
|
<td><h4>{% decimal available %}{% if part.units %} {{ part.units }}{% endif %}</h4></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-map-marker-alt'></span></td>
|
||||||
|
<td>{% trans "In Stock" %}</td>
|
||||||
|
<td>{% include "part/stock_count.html" %}</td>
|
||||||
|
</tr>
|
||||||
|
{% if on_order > 0 %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-shopping-cart'></span></td>
|
||||||
|
<td>{% trans "On Order" %}</td>
|
||||||
|
<td>{% decimal on_order %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if required_build_order_quantity > 0 %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-clipboard-list'></span></td>
|
||||||
|
<td>{% trans "Required for Build Orders" %}</td>
|
||||||
|
<td>{% decimal required_build_order_quantity %}
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if required_sales_order_quantity > 0 %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-clipboard-list'></span></td>
|
||||||
|
<td>{% trans "Required for Sales Orders" %}</td>
|
||||||
|
<td>{% decimal required_sales_order_quantity %}
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if allocated > 0 %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-dolly'></span></td>
|
||||||
|
<td>{% trans "Allocated to Orders" %}</td>
|
||||||
|
<td>{% decimal allocated %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not part.is_template %}
|
||||||
|
{% if part.assembly %}
|
||||||
|
<tr>
|
||||||
|
<td><h4><span class='fas fa-tools'></span></h4></td>
|
||||||
|
<td colspan='2'>
|
||||||
|
<h4>{% trans "Build Status" %}</h4>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>{% trans "Can Build" %}</td>
|
||||||
|
<td>{% decimal part.can_build %}</td>
|
||||||
|
</tr>
|
||||||
|
{% if quantity_being_built > 0 %}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>{% trans "Building" %}</td>
|
||||||
|
<td>{% decimal quantity_being_built %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<!-- Details show/hide button -->
|
||||||
|
<button id="toggle-part-details" class="btn btn-primary" data-toggle="collapse" data-target="#collapsible-part-details" value="show">
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="collapse" id="collapsible-part-details">
|
||||||
|
<div class="card card-body">
|
||||||
|
<!-- Details Table -->
|
||||||
|
<table class="table table-striped">
|
||||||
<col width='25'>
|
<col width='25'>
|
||||||
|
{% if part.IPN %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-tag'></span></td>
|
||||||
|
<td>{% trans "IPN" %}</td>
|
||||||
|
<td>{{ part.IPN }}{% include "clip.html"%}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-shapes'></span></td>
|
||||||
|
<td>{% trans "Name" %}</td>
|
||||||
|
<td>{{ part.name }}{% include "clip.html"%}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-info-circle'></span></td>
|
||||||
|
<td>{% trans "Description" %}</td>
|
||||||
|
<td>{{ part.description }}{% include "clip.html"%}</td>
|
||||||
|
</tr>
|
||||||
|
{% if part.revision %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-code-branch'></span></td>
|
||||||
|
<td>{% trans "Revision" %}</td>
|
||||||
|
<td>{{ part.revision }}{% include "clip.html"%}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
{% if part.keywords %}
|
{% if part.keywords %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-key'></span></td>
|
<td><span class='fas fa-key'></span></td>
|
||||||
<td>{% trans "Keywords" %}</td>
|
<td>{% trans "Keywords" %}</td>
|
||||||
<td>{{ part.keywords }}</td>
|
<td>{{ part.keywords }}{% include "clip.html"%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.link %}
|
{% if part.link %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-link'></span></td>
|
<td><span class='fas fa-link'></span></td>
|
||||||
<td>{% trans "External Link" %}</td>
|
<td>{% trans "External Link" %}</td>
|
||||||
<td><a href="{{ part.link }}">{{ part.link }}</a></td>
|
<td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -154,95 +275,26 @@
|
|||||||
<td>{% trans "Latest Serial Number" %}</td>
|
<td>{% trans "Latest Serial Number" %}</td>
|
||||||
<td>{{ part.getLatestSerialNumber }}{% include "clip.html"%}</td>
|
<td>{{ part.getLatestSerialNumber }}{% include "clip.html"%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if part.default_location %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-search-location'></span></td>
|
||||||
|
<td>{% trans "Default Location" %}</td>
|
||||||
|
<td>{{ part.default_location }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.default_supplier %}
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-building'></span></td>
|
||||||
|
<td>{% trans "Default Supplier" %}</td>
|
||||||
|
<td>{{ part.default_supplier }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='info-messages'>
|
|
||||||
{% if part.virtual %}
|
|
||||||
<div class='alert alert-warning alert-block'>
|
|
||||||
{% trans "This is a virtual part" %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if part.variant_of %}
|
|
||||||
<div class='alert alert-info alert-block'>
|
|
||||||
{% object_link 'part-detail' part.variant_of.id part.variant_of.full_name as link %}
|
|
||||||
{% blocktrans %}This part is a variant of {{link}}{% endblocktrans %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<col width='25'>
|
|
||||||
<tr>
|
|
||||||
<td><span class='fas fa-boxes'></span></td>
|
|
||||||
<td>
|
|
||||||
<h4>{% trans "Available Stock" %}</h4>
|
|
||||||
</td>
|
|
||||||
<td><h4>{% decimal available %}{% if part.units %} {{ part.units }}{% endif %}</h4></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><span class='fas fa-map-marker-alt'></span></td>
|
|
||||||
<td>{% trans "In Stock" %}</td>
|
|
||||||
<td>{% include "part/stock_count.html" %}</td>
|
|
||||||
</tr>
|
|
||||||
{% if on_order > 0 %}
|
|
||||||
<tr>
|
|
||||||
<td><span class='fas fa-shopping-cart'></span></td>
|
|
||||||
<td>{% trans "On Order" %}</td>
|
|
||||||
<td>{% decimal on_order %}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% if required_build_order_quantity > 0 %}
|
|
||||||
<tr>
|
|
||||||
<td><span class='fas fa-clipboard-list'></span></td>
|
|
||||||
<td>{% trans "Required for Build Orders" %}</td>
|
|
||||||
<td>{% decimal required_build_order_quantity %}
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% if required_sales_order_quantity > 0 %}
|
|
||||||
<tr>
|
|
||||||
<td><span class='fas fa-clipboard-list'></span></td>
|
|
||||||
<td>{% trans "Required for Sales Orders" %}</td>
|
|
||||||
<td>{% decimal required_sales_order_quantity %}
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% if allocated > 0 %}
|
|
||||||
<tr>
|
|
||||||
<td><span class='fas fa-dolly'></span></td>
|
|
||||||
<td>{% trans "Allocated to Orders" %}</td>
|
|
||||||
<td>{% decimal allocated %}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not part.is_template %}
|
|
||||||
{% if part.assembly %}
|
|
||||||
<tr>
|
|
||||||
<td><span class='fas fa-tools'></span></td>
|
|
||||||
<td colspan='2'>
|
|
||||||
<strong>{% trans "Build Status" %}</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td>{% trans "Can Build" %}</td>
|
|
||||||
<td>{% decimal part.can_build %}</td>
|
|
||||||
</tr>
|
|
||||||
{% if quantity_being_built > 0 %}
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td>{% trans "Building" %}</td>
|
|
||||||
<td>{% decimal quantity_being_built %}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
@ -450,4 +502,42 @@
|
|||||||
});
|
});
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
$("#toggle-part-details").click(function() {
|
||||||
|
if (this.value == 'show') {
|
||||||
|
this.innerHTML = '<span class="fas fa-chevron-up"></span> {% trans "Hide Part Details" %}';
|
||||||
|
this.value = 'hide';
|
||||||
|
// Store state of part details section
|
||||||
|
localStorage.setItem("part-details-show", true);
|
||||||
|
} else {
|
||||||
|
this.innerHTML = '<span class="fas fa-chevron-down"></span> {% trans "Show Part Details" %}';
|
||||||
|
this.value = 'show';
|
||||||
|
// Store state of part details section
|
||||||
|
localStorage.setItem("part-details-show", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load part details section
|
||||||
|
window.onload = function() {
|
||||||
|
details_show = localStorage.getItem("part-details-show")
|
||||||
|
|
||||||
|
if (details_show === 'true') {
|
||||||
|
console.log(details_show)
|
||||||
|
// Get collapsible details section
|
||||||
|
details = document.getElementById('collapsible-part-details');
|
||||||
|
// Add "show" class
|
||||||
|
details.classList.add("in");
|
||||||
|
// Get toggle
|
||||||
|
toggle = document.getElementById('toggle-part-details');
|
||||||
|
// Change state of toggle
|
||||||
|
toggle.innerHTML = '<span class="fas fa-chevron-up"></span> {% trans "Hide Part Details" %}';
|
||||||
|
toggle.value = 'hide';
|
||||||
|
} else {
|
||||||
|
// Get toggle
|
||||||
|
toggle = document.getElementById('toggle-part-details');
|
||||||
|
// Change state of toggle
|
||||||
|
toggle.innerHTML = '<span class="fas fa-chevron-down"></span> {% trans "Show Part Details" %}';
|
||||||
|
toggle.value = 'show';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -161,7 +161,7 @@
|
|||||||
<div class='panel-content'>
|
<div class='panel-content'>
|
||||||
<h4>{% trans 'Stock Pricing' %}
|
<h4>{% trans 'Stock Pricing' %}
|
||||||
<i class="fas fa-info-circle" title="Shows the purchase prices of stock for this part.
|
<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>
|
The Supplier Unit Cost is the current purchase price for that supplier part."></i>
|
||||||
</h4>
|
</h4>
|
||||||
{% if price_history|length > 0 %}
|
{% if price_history|length > 0 %}
|
||||||
<div style="max-width: 99%; min-height: 300px">
|
<div style="max-width: 99%; min-height: 300px">
|
||||||
|
@ -8,7 +8,6 @@ from __future__ import unicode_literals
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.forms.utils import ErrorDict
|
from django.forms.utils import ErrorDict
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.core.validators import MinValueValidator
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from mptt.fields import TreeNodeChoiceField
|
from mptt.fields import TreeNodeChoiceField
|
||||||
@ -241,14 +240,9 @@ class InstallStockForm(HelperForm):
|
|||||||
help_text=_('Stock item to install')
|
help_text=_('Stock item to install')
|
||||||
)
|
)
|
||||||
|
|
||||||
quantity_to_install = RoundingDecimalFormField(
|
to_install = forms.BooleanField(
|
||||||
max_digits=10, decimal_places=5,
|
widget=forms.HiddenInput(),
|
||||||
initial=1,
|
required=False,
|
||||||
label=_('Quantity'),
|
|
||||||
help_text=_('Stock quantity to assign'),
|
|
||||||
validators=[
|
|
||||||
MinValueValidator(0.001)
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
notes = forms.CharField(
|
notes = forms.CharField(
|
||||||
@ -261,7 +255,7 @@ class InstallStockForm(HelperForm):
|
|||||||
fields = [
|
fields = [
|
||||||
'part',
|
'part',
|
||||||
'stock_item',
|
'stock_item',
|
||||||
'quantity_to_install',
|
# 'quantity_to_install',
|
||||||
'notes',
|
'notes',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -119,6 +119,11 @@
|
|||||||
<h4>{% trans "Installed Stock Items" %}</h4>
|
<h4>{% trans "Installed Stock Items" %}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class='panel-content'>
|
<div class='panel-content'>
|
||||||
|
<div class='btn-group'>
|
||||||
|
<button type='button' class='btn btn-success' id='stock-item-install'>
|
||||||
|
<span class='fas fa-plus-circle'></span> {% trans "Install Stock Item" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<table class='table table-striped table-condensed' id='installed-table'></table>
|
<table class='table table-striped table-condensed' id='installed-table'></table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -128,6 +133,20 @@
|
|||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
|
$('#stock-item-install').click(function() {
|
||||||
|
|
||||||
|
launchModalForm(
|
||||||
|
"{% url 'stock-item-install' item.pk %}",
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
'part': {{ item.part.pk }},
|
||||||
|
'install_item': true,
|
||||||
|
},
|
||||||
|
reload: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
loadInstalledInTable(
|
loadInstalledInTable(
|
||||||
$('#installed-table'),
|
$('#installed-table'),
|
||||||
{
|
{
|
||||||
|
@ -127,9 +127,11 @@
|
|||||||
<li><a href='#' id='stock-return-from-customer' title='{% trans "Return to stock" %}'><span class='fas fa-undo'></span> {% trans "Return to stock" %}</a></li>
|
<li><a href='#' id='stock-return-from-customer' title='{% trans "Return to stock" %}'><span class='fas fa-undo'></span> {% trans "Return to stock" %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if item.belongs_to %}
|
{% if item.belongs_to %}
|
||||||
<li>
|
<li><a href='#' id='stock-uninstall' title='{% trans "Uninstall stock item" %}'><span class='fas fa-unlink'></span> {% trans "Uninstall" %}</a></li>
|
||||||
<a href='#' id='stock-uninstall' title='{% trans "Uninstall stock item" %}'><span class='fas fa-unlink'></span> {% trans "Uninstall" %}</a>
|
{% else %}
|
||||||
</li>
|
{% if item.part.get_used_in %}
|
||||||
|
<li><a href='#' id='stock-install-in' title='{% trans "Install stock item" %}'><span class='fas fa-link'></span> {% trans "Install" %}</a></li>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -461,13 +463,27 @@ $("#stock-serialize").click(function() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#stock-install-in').click(function() {
|
||||||
|
|
||||||
|
launchModalForm(
|
||||||
|
"{% url 'stock-item-install' item.pk %}",
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
'part': {{ item.part.pk }},
|
||||||
|
'install_in': true,
|
||||||
|
},
|
||||||
|
reload: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
$('#stock-uninstall').click(function() {
|
$('#stock-uninstall').click(function() {
|
||||||
|
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
"{% url 'stock-item-uninstall' %}",
|
"{% url 'stock-item-uninstall' %}",
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
'items[]': [{{ item.pk}}],
|
'items[]': [{{ item.pk }}],
|
||||||
},
|
},
|
||||||
reload: true,
|
reload: true,
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,31 @@
|
|||||||
|
|
||||||
{% block pre_form_content %}
|
{% block pre_form_content %}
|
||||||
|
|
||||||
|
{% if install_item %}
|
||||||
<p>
|
<p>
|
||||||
{% trans "Install another StockItem into this item." %}
|
{% trans "Install another Stock Item into this item." %}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{% trans "Stock items can only be installed if they meet the following criteria" %}:
|
{% trans "Stock items can only be installed if they meet the following criteria" %}:
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>{% trans "The StockItem links to a Part which is in the BOM for this StockItem" %}</li>
|
<li>{% trans "The Stock Item links to a Part which is in the BOM for this Stock Item" %}</li>
|
||||||
<li>{% trans "The StockItem is currently in stock" %}</li>
|
<li>{% trans "The Stock Item is currently in stock" %}</li>
|
||||||
|
<li>{% trans "The Stock Item is serialized and does not belong to another item" %}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
|
{% elif install_in %}
|
||||||
|
<p>
|
||||||
|
{% trans "Install this Stock Item in another stock item." %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% trans "Stock items can only be installed if they meet the following criteria" %}:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>{% trans "The part associated to this Stock Item belongs to another part's BOM" %}</li>
|
||||||
|
<li>{% trans "This Stock Item is serialized and does not belong to another item" %}</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -536,36 +536,73 @@ class StockItemInstall(AjaxUpdateView):
|
|||||||
|
|
||||||
part = None
|
part = None
|
||||||
|
|
||||||
|
def get_params(self):
|
||||||
|
""" Retrieve GET parameters """
|
||||||
|
|
||||||
|
# Look at GET params
|
||||||
|
self.part_id = self.request.GET.get('part', None)
|
||||||
|
self.install_in = self.request.GET.get('install_in', False)
|
||||||
|
self.install_item = self.request.GET.get('install_item', False)
|
||||||
|
|
||||||
|
if self.part_id is None:
|
||||||
|
# Look at POST params
|
||||||
|
self.part_id = self.request.POST.get('part', None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.part = Part.objects.get(pk=self.part_id)
|
||||||
|
except (ValueError, Part.DoesNotExist):
|
||||||
|
self.part = None
|
||||||
|
|
||||||
def get_stock_items(self):
|
def get_stock_items(self):
|
||||||
"""
|
"""
|
||||||
Return a list of stock items suitable for displaying to the user.
|
Return a list of stock items suitable for displaying to the user.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- Items must be in stock
|
- Items must be in stock
|
||||||
|
- Items must be in BOM of stock item
|
||||||
Filters:
|
- Items must be serialized
|
||||||
- Items can be filtered by Part reference
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Filter items in stock
|
||||||
items = StockItem.objects.filter(StockItem.IN_STOCK_FILTER)
|
items = StockItem.objects.filter(StockItem.IN_STOCK_FILTER)
|
||||||
|
|
||||||
# Filter by Part association
|
# Filter serialized stock items
|
||||||
|
items = items.exclude(serial__isnull=True).exclude(serial__exact='')
|
||||||
|
|
||||||
# Look at GET params
|
if self.part:
|
||||||
part_id = self.request.GET.get('part', None)
|
# Filter for parts to install this item in
|
||||||
|
if self.install_in:
|
||||||
|
# Get parts using this part
|
||||||
|
allowed_parts = self.part.get_used_in()
|
||||||
|
# Filter
|
||||||
|
items = items.filter(part__in=allowed_parts)
|
||||||
|
|
||||||
if part_id is None:
|
# Filter for parts to install in this item
|
||||||
# Look at POST params
|
if self.install_item:
|
||||||
part_id = self.request.POST.get('part', None)
|
# Get parts used in this part's BOM
|
||||||
|
bom_items = self.part.get_bom_items()
|
||||||
try:
|
allowed_parts = [item.sub_part for item in bom_items]
|
||||||
self.part = Part.objects.get(pk=part_id)
|
# Filter
|
||||||
items = items.filter(part=self.part)
|
items = items.filter(part__in=allowed_parts)
|
||||||
except (ValueError, Part.DoesNotExist):
|
|
||||||
self.part = None
|
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
""" Retrieve parameters and update context """
|
||||||
|
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
# Get request parameters
|
||||||
|
self.get_params()
|
||||||
|
|
||||||
|
ctx.update({
|
||||||
|
'part': self.part,
|
||||||
|
'install_in': self.install_in,
|
||||||
|
'install_item': self.install_item,
|
||||||
|
})
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
|
|
||||||
initials = super().get_initial()
|
initials = super().get_initial()
|
||||||
@ -576,11 +613,16 @@ class StockItemInstall(AjaxUpdateView):
|
|||||||
if items.count() == 1:
|
if items.count() == 1:
|
||||||
item = items.first()
|
item = items.first()
|
||||||
initials['stock_item'] = item.pk
|
initials['stock_item'] = item.pk
|
||||||
initials['quantity_to_install'] = item.quantity
|
|
||||||
|
|
||||||
if self.part:
|
if self.part:
|
||||||
initials['part'] = self.part
|
initials['part'] = self.part
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Is this stock item being installed in the other stock item?
|
||||||
|
initials['to_install'] = self.install_in or not self.install_item
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
return initials
|
return initials
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
@ -593,6 +635,8 @@ class StockItemInstall(AjaxUpdateView):
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
self.get_params()
|
||||||
|
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
|
|
||||||
valid = form.is_valid()
|
valid = form.is_valid()
|
||||||
@ -602,13 +646,19 @@ class StockItemInstall(AjaxUpdateView):
|
|||||||
data = form.cleaned_data
|
data = form.cleaned_data
|
||||||
|
|
||||||
other_stock_item = data['stock_item']
|
other_stock_item = data['stock_item']
|
||||||
quantity = data['quantity_to_install']
|
# Quantity will always be 1 for serialized item
|
||||||
|
quantity = 1
|
||||||
notes = data['notes']
|
notes = data['notes']
|
||||||
|
|
||||||
# Install the other stock item into this one
|
# Get stock item
|
||||||
this_stock_item = self.get_object()
|
this_stock_item = self.get_object()
|
||||||
|
|
||||||
this_stock_item.installStockItem(other_stock_item, quantity, request.user, notes)
|
if data['to_install']:
|
||||||
|
# Install this stock item into the other stock item
|
||||||
|
other_stock_item.installStockItem(this_stock_item, quantity, request.user, notes)
|
||||||
|
else:
|
||||||
|
# Install the other stock item into this one
|
||||||
|
this_stock_item.installStockItem(other_stock_item, quantity, request.user, notes)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'form_valid': valid,
|
'form_valid': valid,
|
||||||
|
@ -262,13 +262,13 @@ function loadBomTable(table, options) {
|
|||||||
cols.push(
|
cols.push(
|
||||||
{
|
{
|
||||||
field: 'price_range',
|
field: 'price_range',
|
||||||
title: '{% trans "Buy Price" %}',
|
title: '{% trans "Supplier Cost" %}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
if (value) {
|
if (value) {
|
||||||
return value;
|
return value;
|
||||||
} else {
|
} else {
|
||||||
return "<span class='warning-msg'>{% trans 'No pricing available' %}</span>";
|
return "<span class='warning-msg'>{% trans 'No supplier pricing available' %}</span>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user