Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2020-02-12 12:56:17 +11:00
commit 0498fd633a
37 changed files with 2146 additions and 874 deletions

View File

@ -1,5 +1,6 @@
function updateAllocationTotal(id, count, required) {
count = parseFloat(count);
$('#allocation-total-'+id).html(count);
@ -27,21 +28,24 @@ function loadAllocationTable(table, part_id, part, url, required, button) {
field: 'stock_item_detail',
title: 'Stock Item',
formatter: function(value, row, index, field) {
return '' + value.quantity + ' x ' + value.part_name + ' @ ' + value.location_name;
return '' + parseFloat(value.quantity) + ' x ' + value.part_name + ' @ ' + value.location_name;
}
},
{
field: 'stock_item_detail.quantity',
title: 'Available',
formatter: function(value, row, index, field) {
return parseFloat(value);
}
},
{
field: 'quantity',
title: 'Allocated',
formatter: function(value, row, index, field) {
var html = value;
var html = parseFloat(value);
var bEdit = "<button class='btn btn-primary item-edit-button btn-sm' type='button' title='Edit stock allocation' url='/build/item/" + row.pk + "/edit/'><span class='glyphicon glyphicon-small glyphicon-edit'></span></button>";
var bDel = "<button class='btn btn-danger item-del-button btn-sm' type='button' title='Delete stock allocation' url='/build/item/" + row.pk + "/delete/'><span class='glyphicon glyphicon-small glyphicon-trash'></span></button>";
var bEdit = "<button class='btn item-edit-button btn-sm' type='button' title='Edit stock allocation' url='/build/item/" + row.pk + "/edit/'><span class='glyphicon glyphicon-small glyphicon-edit'></span></button>";
var bDel = "<button class='btn item-del-button btn-sm' type='button' title='Delete stock allocation' url='/build/item/" + row.pk + "/delete/'><span class='glyphicon glyphicon-small glyphicon-trash'></span></button>";
html += "<div class='btn-group' style='float: right;'>" + bEdit + bDel + "</div>";

View File

@ -385,6 +385,9 @@ function loadStockTrackingTable(table, options) {
cols.push({
field: 'quantity',
title: 'Quantity',
formatter: function(value, row, index, field) {
return parseFloat(value);
},
});
cols.push({

View File

@ -20,6 +20,7 @@ from markdownx.models import MarkdownxField
from InvenTree.status_codes import BuildStatus
from InvenTree.fields import InvenTreeURLField
from InvenTree.helpers import decimal2string
from stock.models import StockItem
from part.models import Part, BomItem
@ -42,7 +43,7 @@ class Build(models.Model):
"""
def __str__(self):
return "Build {q} x {part}".format(q=self.quantity, part=str(self.part))
return "Build {q} x {part}".format(q=decimal2string(self.quantity), part=str(self.part))
def get_absolute_url(self):
return reverse('build-detail', kwargs={'pk': self.id})

View File

@ -15,7 +15,7 @@
{% block collapse_heading %}
<div class='col-sm-2'>
<b>{{ item.sub_part.total_stock }}</b>
<b>{% decimal item.sub_part.total_stock %}</b>
</div>
<div class='col-sm-2'>
<b>{% multiply build.quantity item.quantity %}</b>

View File

@ -1,7 +1,9 @@
{% extends "modal_delete_form.html" %}
{% load i18n %}
{% load inventree_extras %}
{% block pre_form_content %}
Are you sure you want to unallocate these parts?
{% trans "Are you sure you want to unallocate these parts?" %}
<br>
This will remove {{ item.quantity }} parts from build '{{ item.build.title }}'.
This will remove {% decimal item.quantity %} parts from build '{{ item.build.title }}'.
{% endblock %}

View File

@ -1,9 +1,10 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% load inventree_extras %}
{% block pre_form_content %}
{{ block.super }}
Are you sure you wish to unallocate all stock for this build?
{% trans "Are you sure you wish to unallocate all stock for this build?" %}
{% endblock %}

View File

@ -53,7 +53,7 @@ class BuildCancel(AjaxUpdateView):
model = Build
ajax_template_name = 'build/cancel.html'
ajax_form_title = 'Cancel Build'
ajax_form_title = _('Cancel Build')
context_object_name = 'build'
form_class = forms.CancelBuildForm
@ -71,12 +71,12 @@ class BuildCancel(AjaxUpdateView):
if confirm:
build.cancelBuild(request.user)
else:
form.errors['confirm_cancel'] = ['Confirm build cancellation']
form.errors['confirm_cancel'] = [_('Confirm build cancellation')]
valid = False
data = {
'form_valid': valid,
'danger': 'Build was cancelled'
'danger': _('Build was cancelled')
}
return self.renderJsonResponse(request, form, data=data)
@ -92,7 +92,7 @@ class BuildAutoAllocate(AjaxUpdateView):
model = Build
form_class = forms.ConfirmBuildForm
context_object_name = 'build'
ajax_form_title = 'Allocate Stock'
ajax_form_title = _('Allocate Stock')
ajax_template_name = 'build/auto_allocate.html'
def get_context_data(self, *args, **kwargs):
@ -105,7 +105,7 @@ class BuildAutoAllocate(AjaxUpdateView):
context['build'] = build
context['allocations'] = build.getAutoAllocations()
except Build.DoesNotExist:
context['error'] = 'No matching build found'
context['error'] = _('No matching build found')
return context
@ -124,8 +124,8 @@ class BuildAutoAllocate(AjaxUpdateView):
valid = False
if confirm is False:
form.errors['confirm'] = ['Confirm stock allocation']
form.non_field_errors = 'Check the confirmation box at the bottom of the list'
form.errors['confirm'] = [_('Confirm stock allocation')]
form.non_field_errors = _('Check the confirmation box at the bottom of the list')
else:
build.autoAllocate()
valid = True
@ -145,7 +145,7 @@ class BuildUnallocate(AjaxUpdateView):
model = Build
form_class = forms.ConfirmBuildForm
ajax_form_title = "Unallocate Stock"
ajax_form_title = _("Unallocate Stock")
ajax_template_name = "build/unallocate.html"
def post(self, request, *args, **kwargs):
@ -158,8 +158,8 @@ class BuildUnallocate(AjaxUpdateView):
valid = False
if confirm is False:
form.errors['confirm'] = ['Confirm unallocation of build stock']
form.non_field_errors = 'Check the confirmation box'
form.errors['confirm'] = [_('Confirm unallocation of build stock')]
form.non_field_errors = _('Check the confirmation box')
else:
build.unallocateStock()
valid = True
@ -182,7 +182,7 @@ class BuildComplete(AjaxUpdateView):
model = Build
form_class = forms.CompleteBuildForm
context_object_name = "build"
ajax_form_title = "Complete Build"
ajax_form_title = _("Complete Build")
ajax_template_name = "build/complete.html"
def get_form(self):
@ -255,14 +255,14 @@ class BuildComplete(AjaxUpdateView):
if confirm is False:
form.errors['confirm'] = [
'Confirm completion of build',
_('Confirm completion of build'),
]
else:
try:
location = StockLocation.objects.get(id=loc_id)
valid = True
except StockLocation.DoesNotExist:
form.errors['location'] = ['Invalid location selected']
form.errors['location'] = [_('Invalid location selected')]
serials = []
@ -306,7 +306,7 @@ class BuildComplete(AjaxUpdateView):
def get_data(self):
""" Provide feedback data back to the form """
return {
'info': 'Build marked as COMPLETE'
'info': _('Build marked as COMPLETE')
}
@ -382,7 +382,7 @@ class BuildCreate(AjaxCreateView):
model = Build
context_object_name = 'build'
form_class = forms.EditBuildForm
ajax_form_title = 'Start new Build'
ajax_form_title = _('Start new Build')
ajax_template_name = 'modal_form.html'
def get_initial(self):
@ -405,7 +405,7 @@ class BuildCreate(AjaxCreateView):
def get_data(self):
return {
'success': 'Created new build',
'success': _('Created new build'),
}
@ -415,12 +415,12 @@ class BuildUpdate(AjaxUpdateView):
model = Build
form_class = forms.EditBuildForm
context_object_name = 'build'
ajax_form_title = 'Edit Build Details'
ajax_form_title = _('Edit Build Details')
ajax_template_name = 'modal_form.html'
def get_data(self):
return {
'info': 'Edited build',
'info': _('Edited build'),
}
@ -429,7 +429,7 @@ class BuildDelete(AjaxDeleteView):
model = Build
ajax_template_name = 'build/delete_build.html'
ajax_form_title = 'Delete Build'
ajax_form_title = _('Delete Build')
class BuildItemDelete(AjaxDeleteView):
@ -439,12 +439,12 @@ class BuildItemDelete(AjaxDeleteView):
model = BuildItem
ajax_template_name = 'build/delete_build_item.html'
ajax_form_title = 'Unallocate Stock'
ajax_form_title = _('Unallocate Stock')
context_object_name = 'item'
def get_data(self):
return {
'danger': 'Removed parts from build allocation'
'danger': _('Removed parts from build allocation')
}
@ -454,7 +454,7 @@ class BuildItemCreate(AjaxCreateView):
model = BuildItem
form_class = forms.EditBuildItemForm
ajax_template_name = 'build/create_build_item.html'
ajax_form_title = 'Allocate new Part'
ajax_form_title = _('Allocate new Part')
part = None
available_stock = None
@ -570,11 +570,11 @@ class BuildItemEdit(AjaxUpdateView):
model = BuildItem
ajax_template_name = 'modal_form.html'
form_class = forms.EditBuildItemForm
ajax_form_title = 'Edit Stock Allocation'
ajax_form_title = _('Edit Stock Allocation')
def get_data(self):
return {
'info': 'Updated Build Item',
'info': _('Updated Build Item'),
}
def get_form(self):

View File

@ -5,6 +5,8 @@ Django views for interacting with common models
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
from . import models
@ -16,7 +18,7 @@ class CurrencyCreate(AjaxCreateView):
model = models.Currency
form_class = forms.CurrencyEditForm
ajax_form_title = 'Create new Currency'
ajax_form_title = _('Create new Currency')
class CurrencyEdit(AjaxUpdateView):
@ -24,12 +26,12 @@ class CurrencyEdit(AjaxUpdateView):
model = models.Currency
form_class = forms.CurrencyEditForm
ajax_form_title = 'Edit Currency'
ajax_form_title = _('Edit Currency')
class CurrencyDelete(AjaxDeleteView):
""" View for deleting an existing Currency object """
model = models.Currency
ajax_form_title = 'Delete Currency'
ajax_form_title = _('Delete Currency')
ajax_template_name = "common/delete_currency.html"

View File

@ -1,172 +0,0 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% block page_title %}
InvenTree | {{ company.name }} - {% trans "Parts" %}
{% endblock %}
{% block content %}
<div class='row'>
<div class='col-sm-6'>
<h3>{% trans "Supplier Part" %}</h3>
<div class='btn-row'>
<div class='btn-group'>
<button type='button' class='btn btn-default btn-glyph' id='edit-part' title='Edit supplier part'>
<span class='glyphicon glyphicon-edit'/>
</button>
<button type='button' class='btn btn-default btn-glyph' id='delete-part' title='Delete supplier part'>
<span class='glyphicon glyphicon-trash'/>
</button>
</div>
</div>
</div>
<div class='col-sm-6'>
<div class='media-left'>
<img class='part-thumb'
{% if part.part.image %}
src='{{ part.part.image.url }}'
{% else %}
src="{% static 'img/blank_image.png' %}"
{% endif %}/>
</div>
</div>
</div>
<hr>
<div class='row'>
<div class='col-sm-6'>
<h4>{% trans "Supplier Part Details" %}</h4>
<table class="table table-striped table-condensed">
<tr>
<td>{% trans "Internal Part" %}</td>
<td>
{% if part.part %}
<a href="{% url 'part-suppliers' part.part.id %}">{{ part.part.full_name }}</a>
{% endif %}
</td>
</tr>
<tr><td>{% trans "Supplier" %}</td><td><a href="{% url 'company-detail-parts' part.supplier.id %}">{{ part.supplier.name }}</a></td></tr>
<tr><td>{% trans "SKU" %}</td><td>{{ part.SKU }}</tr></tr>
{% if part.URL %}
<tr><td>{% trans "URL" %}</td><td><a href="{{ part.URL }}">{{ part.URL }}</a></td></tr>
{% endif %}
{% if part.description %}
<tr><td>{% trans "Description" %}</td><td>{{ part.description }}</td></tr>
{% endif %}
{% if part.manufacturer %}
<tr><td>{% trans "Manufacturer" %}</td><td>{{ part.manufacturer }}</td></tr>
<tr><td>{% trans "MPN" %}</td><td>{{ part.MPN }}</td></tr>
{% endif %}
{% if part.note %}
<tr><td>{% trans "Note" %}</td><td>{{ part.note }}</td></tr>
{% endif %}
</table>
</div>
<div class='col-sm-6'>
<h4>{% trans "Pricing Information" %}</h4>
<table class="table table-striped table-condensed">
<tr><td>{% trans "Order Multiple" %}</td><td>{{ part.multiple }}</td></tr>
{% if part.base_cost > 0 %}
<tr><td>{% trans "Base Price (Flat Fee)" %}</td><td>{{ part.base_cost }}</td></tr>
{% endif %}
<tr>
<th>{% trans "Price Breaks" %}</th>
<th>
<div style='float: right;'>
<button class='btn btn-primary' id='new-price-break' type='button'>{% trans "New Price Break" %}</button>
</div>
</th>
</tr>
<tr>
<th>{% trans "Quantity" %}</th>
<th>{% trans "Price" %}</th>
</tr>
{% if part.price_breaks.all %}
{% for pb in part.price_breaks.all %}
<tr>
<td>{{ pb.quantity }}</td>
<td>
{% if pb.currency %}{{ pb.currency.symbol }}{% endif %}
{{ pb.cost }}
{% if pb.currency %}{{ pb.currency.suffix }}{% endif %}
<div class='btn-group' style='float: right;'>
<button title='Edit Price Break' class='btn btn-default btn-sm pb-edit-button' type='button' url="{% url 'price-break-edit' pb.id %}"><span class='glyphicon glyphicon-edit'></span></button>
<button title='Delete Price Break' class='btn btn-default btn-sm pb-delete-button' type='button' url="{% url 'price-break-delete' pb.id %}"><span class='glyphicon glyphicon-trash'></span></button>
</div>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan='2'>
<span class='warning-msg'><i>{% trans "No price breaks have been added for this part" %}</i></span>
</td>
</tr>
{% endif %}
</table>
</div>
</div>
<hr>
<h4>{% trans "Purchase Orders" %}</h4>
{% include "order/po_table.html" with orders=part.purchase_orders %}
{% endblock %}
{% block js_ready %}
{{ block.super }}
$('#edit-part').click(function () {
launchModalForm(
"{% url 'supplier-part-edit' part.id %}",
{
reload: true
}
);
});
$('#delete-part').click(function() {
launchModalForm(
"{% url 'supplier-part-delete' %}?part={{ part.id }}",
{
redirect: "{% url 'company-detail-parts' part.supplier.id %}"
}
);
});
$('#new-price-break').click(function() {
launchModalForm("{% url 'price-break-create' %}",
{
reload: true,
data: {
part: {{ part.id }},
}
}
);
});
$('.pb-edit-button').click(function() {
var button = $(this);
launchModalForm(button.attr('url'),
{
reload: true,
}
);
});
$('.pb-delete-button').click(function() {
var button = $(this);
launchModalForm(button.attr('url'),
{
reload: true,
}
);
});
{% endblock %}

View File

@ -0,0 +1,68 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% block page_title %}
InvenTree | {% trans "Supplier Part" %}
{% endblock %}
{% block content %}
<div class='row'>
<div class='col-sm-6'>
<h3>{% trans "Supplier Part" %}</h3>
<div class='btn-row'>
<div class='btn-group'>
<button type='button' class='btn btn-default btn-glyph' id='edit-part' title='Edit supplier part'>
<span class='glyphicon glyphicon-edit'/>
</button>
<button type='button' class='btn btn-default btn-glyph' id='delete-part' title='Delete supplier part'>
<span class='glyphicon glyphicon-trash'/>
</button>
</div>
</div>
</div>
<div class='col-sm-6'>
<div class='media-left'>
<img class='part-thumb'
{% if part.part.image %}
src='{{ part.part.image.url }}'
{% else %}
src="{% static 'img/blank_image.png' %}"
{% endif %}/>
</div>
</div>
</div>
<hr>
<div class='container-fluid'>
{% block details %}
<!-- Particular SupplierPart page goes here ... -->
{% endblock %}
</div>
{% endblock %}
{% block js_ready %}
{{ block.super }}
$('#edit-part').click(function () {
launchModalForm(
"{% url 'supplier-part-edit' part.id %}",
{
reload: true
}
);
});
$('#delete-part').click(function() {
launchModalForm(
"{% url 'supplier-part-delete' %}?part={{ part.id }}",
{
redirect: "{% url 'company-detail-parts' part.supplier.id %}"
}
);
});
{% endblock %}

View File

@ -0,0 +1,44 @@
{% extends "company/supplier_part_base.html" %}
{% load static %}
{% load i18n %}
{% block details %}
{% include "company/supplier_part_tabs.html" with tab='details' %}
<hr>
<h4>{% trans "Supplier Part Details" %}</h4>
<table class="table table-striped table-condensed">
<tr>
<td>{% trans "Internal Part" %}</td>
<td>
{% if part.part %}
<a href="{% url 'part-suppliers' part.part.id %}">{{ part.part.full_name }}</a>
{% endif %}
</td>
</tr>
<tr><td>{% trans "Supplier" %}</td><td><a href="{% url 'company-detail-parts' part.supplier.id %}">{{ part.supplier.name }}</a></td></tr>
<tr><td>{% trans "SKU" %}</td><td>{{ part.SKU }}</tr></tr>
{% if part.URL %}
<tr><td>{% trans "URL" %}</td><td><a href="{{ part.URL }}">{{ part.URL }}</a></td></tr>
{% endif %}
{% if part.description %}
<tr><td>{% trans "Description" %}</td><td>{{ part.description }}</td></tr>
{% endif %}
{% if part.manufacturer %}
<tr><td>{% trans "Manufacturer" %}</td><td>{{ part.manufacturer }}</td></tr>
<tr><td>{% trans "MPN" %}</td><td>{{ part.MPN }}</td></tr>
{% endif %}
{% if part.note %}
<tr><td>{% trans "Note" %}</td><td>{{ part.note }}</td></tr>
{% endif %}
</table>
{% endblock %}
{% block js_ready %}
{{ block.super }}
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends "company/supplier_part_base.html" %}
{% load static %}
{% load i18n %}
{% block details %}
{% include "company/supplier_part_tabs.html" with tab='orders' %}
<hr>
<h4>{% trans "Supplier Part Orders" %}</h4>
<div id='button-bar'>
<div class='btn-group'>
<button class='btn btn-primary' type='button' id='part-order2' title='Order part'>Order Part</button>
</div>
</div>
<table class='table table-striped table-condensed po-table' id='purchase-order-table' data-toolbar='#button-bar'>
</table>
{% endblock %}
{% block js_ready %}
{{ block.super }}
loadPurchaseOrderTable($("#purchase-order-table"), {
url: "{% url 'api-po-list' %}?supplier_part={{ part.id }}",
});
{% endblock %}

View File

@ -0,0 +1,92 @@
{% extends "company/supplier_part_base.html" %}
{% load static %}
{% load i18n %}
{% load inventree_extras %}
{% block details %}
{% include "company/supplier_part_tabs.html" with tab='pricing' %}
<hr>
<h4>{% trans "Pricing Information" %}</h4>
<table class="table table-striped table-condensed">
<tr><td>{% trans "Order Multiple" %}</td><td>{{ part.multiple }}</td></tr>
{% if part.base_cost > 0 %}
<tr><td>{% trans "Base Price (Flat Fee)" %}</td><td>{{ part.base_cost }}</td></tr>
{% endif %}
<tr>
<th>{% trans "Price Breaks" %}</th>
<th>
<div style='float: right;'>
<button class='btn btn-primary' id='new-price-break' type='button'>{% trans "New Price Break" %}</button>
</div>
</th>
</tr>
<tr>
<th>{% trans "Quantity" %}</th>
<th>{% trans "Price" %}</th>
</tr>
{% if part.price_breaks.all %}
{% for pb in part.price_breaks.all %}
<tr>
<td>{% decimal pb.quantity %}</td>
<td>
{% if pb.currency %}{{ pb.currency.symbol }}{% endif %}
{% decimal pb.cost %}
{% if pb.currency %}{{ pb.currency.suffix }}{% endif %}
<div class='btn-group' style='float: right;'>
<button title='Edit Price Break' class='btn btn-default btn-sm pb-edit-button' type='button' url="{% url 'price-break-edit' pb.id %}"><span class='glyphicon glyphicon-edit'></span></button>
<button title='Delete Price Break' class='btn btn-default btn-sm pb-delete-button' type='button' url="{% url 'price-break-delete' pb.id %}"><span class='glyphicon glyphicon-trash'></span></button>
</div>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan='2'>
<span class='warning-msg'><i>{% trans "No price breaks have been added for this part" %}</i></span>
</td>
</tr>
{% endif %}
</table>
{% endblock %}
{% block js_ready %}
{{ block.super }}
$('#new-price-break').click(function() {
launchModalForm("{% url 'price-break-create' %}",
{
reload: true,
data: {
part: {{ part.id }},
}
}
);
});
$('.pb-edit-button').click(function() {
var button = $(this);
launchModalForm(button.attr('url'),
{
reload: true,
}
);
});
$('.pb-delete-button').click(function() {
var button = $(this);
launchModalForm(button.attr('url'),
{
reload: true,
}
);
});
{% endblock %}

View File

@ -0,0 +1,72 @@
{% extends "company/supplier_part_base.html" %}
{% load static %}
{% load i18n %}
{% block details %}
{% include "company/supplier_part_tabs.html" with tab='stock' %}
<hr>
<h4>{% trans "Supplier Part Stock" %}</h4>
{% include "stock_table.html" %}
{% endblock %}
{% block js_load %}
{{ block.super }}
<script type='text/javascript' src="{% static 'script/inventree/stock.js' %}"></script>
{% endblock %}
{% block js_ready %}
{{ block.super }}
loadStockTable($("#stock-table"), {
params: {
supplier_part: {{ part.id }},
location_detail: true,
part_detail: true,
},
groupByField: 'location',
buttons: ['#stock-options'],
url: "{% url 'api-stock-list' %}",
});
$("#stock-export").click(function() {
launchModalForm("{% url 'stock-export-options' %}", {
submit_text: '{% trans "Export" %}',
success: function(response) {
var url = "{% url 'stock-export' %}";
url += "?format=" + response.format;
url += "&cascade=" + response.cascade;
url += "&supplier_part={{ part.id }}";
location.href = url;
},
});
});
$("#item-create").click(function() {
launchModalForm("{% url 'stock-item-create' %}", {
reload: true,
data: {
part: {{ part.part.id }},
supplier_part: {{ part.id }},
},
secondary: [
{
field: 'location',
label: '{% trans "New Location" %}',
title: '{% trans "Create New Location" %}',
url: "{% url 'stock-location-create' %}",
}
]
});
return false;
});
{% endblock %}

View File

@ -0,0 +1,16 @@
{% load i18n %}
<ul class='nav nav-tabs'>
<li{% if tab == 'details' %} class='active'{% endif %}>
<a href="{% url 'supplier-part-detail' part.id %}">{% trans "Details" %}</a>
</li>
<li{% if tab == 'pricing' %} class='active'{% endif %}>
<a href="{% url 'supplier-part-pricing' part.id %}">{% trans "Pricing" %}</a>
</li>
<li{% if tab == 'stock' %} class='active'{% endif %}>
<a href="{% url 'supplier-part-stock' part.id %}">{% trans "Stock" %}</a>
</li>
<li {% if tab == 'orders' %} class='active'{% endif %}>
<a href="{% url 'supplier-part-orders' part.id %}">{% trans "Orders" %}</a>
</li>
</ul>

View File

@ -47,7 +47,11 @@ price_break_urls = [
]
supplier_part_detail_urls = [
url(r'edit/?', views.SupplierPartEdit.as_view(), name='supplier-part-edit'),
url(r'^edit/?', views.SupplierPartEdit.as_view(), name='supplier-part-edit'),
url(r'^pricing/', views.SupplierPartDetail.as_view(template_name='company/supplier_part_pricing.html'), name='supplier-part-pricing'),
url(r'^orders/', views.SupplierPartDetail.as_view(template_name='company/supplier_part_orders.html'), name='supplier-part-orders'),
url(r'^stock/', views.SupplierPartDetail.as_view(template_name='company/supplier_part_stock.html'), name='supplier-part-stock'),
url('^.*$', views.SupplierPartDetail.as_view(), name='supplier-part-detail'),
]

View File

@ -6,6 +6,7 @@ Django views for interacting with Company app
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.views.generic import DetailView, ListView, UpdateView
from django.urls import reverse
@ -93,12 +94,12 @@ class CompanyImage(AjaxUpdateView):
""" View for uploading an image for the Company """
model = Company
ajax_template_name = 'modal_form.html'
ajax_form_title = 'Update Company Image'
ajax_form_title = _('Update Company Image')
form_class = CompanyImageForm
def get_data(self):
return {
'success': 'Updated company image',
'success': _('Updated company image'),
}
@ -108,11 +109,11 @@ class CompanyEdit(AjaxUpdateView):
form_class = EditCompanyForm
context_object_name = 'company'
ajax_template_name = 'modal_form.html'
ajax_form_title = 'Edit Company'
ajax_form_title = _('Edit Company')
def get_data(self):
return {
'info': 'Edited company information',
'info': _('Edited company information'),
}
@ -122,11 +123,11 @@ class CompanyCreate(AjaxCreateView):
context_object_name = 'company'
form_class = EditCompanyForm
ajax_template_name = 'modal_form.html'
ajax_form_title = "Create new Company"
ajax_form_title = _("Create new Company")
def get_data(self):
return {
'success': "Created new company",
'success': _("Created new company"),
}
@ -136,19 +137,19 @@ class CompanyDelete(AjaxDeleteView):
model = Company
success_url = '/company/'
ajax_template_name = 'company/delete.html'
ajax_form_title = 'Delete Company'
ajax_form_title = _('Delete Company')
context_object_name = 'company'
def get_data(self):
return {
'danger': 'Company was deleted',
'danger': _('Company was deleted'),
}
class SupplierPartDetail(DetailView):
""" Detail view for SupplierPart """
model = SupplierPart
template_name = 'company/partdetail.html'
template_name = 'company/supplier_part_detail.html'
context_object_name = 'part'
queryset = SupplierPart.objects.all()
@ -166,7 +167,7 @@ class SupplierPartEdit(AjaxUpdateView):
context_object_name = 'part'
form_class = EditSupplierPartForm
ajax_template_name = 'modal_form.html'
ajax_form_title = 'Edit Supplier Part'
ajax_form_title = _('Edit Supplier Part')
class SupplierPartCreate(AjaxCreateView):
@ -175,7 +176,7 @@ class SupplierPartCreate(AjaxCreateView):
model = SupplierPart
form_class = EditSupplierPartForm
ajax_template_name = 'modal_form.html'
ajax_form_title = 'Create new Supplier Part'
ajax_form_title = _('Create new Supplier Part')
context_object_name = 'part'
def get_form(self):
@ -232,7 +233,7 @@ class SupplierPartDelete(AjaxDeleteView):
success_url = '/supplier/'
ajax_template_name = 'company/partdelete.html'
ajax_form_title = 'Delete Supplier Part'
ajax_form_title = _('Delete Supplier Part')
parts = []
@ -302,7 +303,7 @@ class PriceBreakCreate(AjaxCreateView):
model = SupplierPriceBreak
form_class = EditPriceBreakForm
ajax_form_title = 'Add Price Break'
ajax_form_title = _('Add Price Break')
ajax_template_name = 'modal_form.html'
def get_data(self):
@ -337,7 +338,7 @@ class PriceBreakEdit(AjaxUpdateView):
model = SupplierPriceBreak
form_class = EditPriceBreakForm
ajax_form_title = 'Edit Price Break'
ajax_form_title = _('Edit Price Break')
ajax_template_name = 'modal_form.html'
def get_form(self):
@ -352,5 +353,5 @@ class PriceBreakDelete(AjaxDeleteView):
""" View for deleting a supplier price break """
model = SupplierPriceBreak
ajax_form_title = "Delete Price Break"
ajax_form_title = _("Delete Price Break")
ajax_template_name = 'modal_delete_form.html'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@ from InvenTree.status_codes import OrderStatus
import os
from part.models import Part
from company.models import SupplierPart
from .models import PurchaseOrder, PurchaseOrderLineItem
from .serializers import POSerializer, POLineItemSerializer
@ -62,6 +63,14 @@ class POList(generics.ListCreateAPIView):
except (Part.DoesNotExist, ValueError):
pass
# Attempt to filter by supplier part
if 'supplier_part' in request.GET:
try:
supplier_part = SupplierPart.objects.get(pk=request.GET['supplier_part'])
queryset = queryset.filter(id__in=[p.id for p in supplier_part.purchase_orders()])
except (ValueError, SupplierPart.DoesNotExist):
pass
data = queryset.values(
'pk',
'supplier',

View File

@ -96,7 +96,7 @@ class PurchaseOrderCreate(AjaxCreateView):
""" View for creating a new PurchaseOrder object using a modal form """
model = PurchaseOrder
ajax_form_title = "Create Purchase Order"
ajax_form_title = _("Create Purchase Order")
form_class = order_forms.EditPurchaseOrderForm
def get_initial(self):
@ -126,7 +126,7 @@ class PurchaseOrderEdit(AjaxUpdateView):
""" View for editing a PurchaseOrder using a modal form """
model = PurchaseOrder
ajax_form_title = 'Edit Purchase Order'
ajax_form_title = _('Edit Purchase Order')
form_class = order_forms.EditPurchaseOrderForm
def get_form(self):
@ -146,7 +146,7 @@ class PurchaseOrderCancel(AjaxUpdateView):
""" View for cancelling a purchase order """
model = PurchaseOrder
ajax_form_title = 'Cancel Order'
ajax_form_title = _('Cancel Order')
ajax_template_name = 'order/order_cancel.html'
form_class = order_forms.CancelPurchaseOrderForm
@ -179,7 +179,7 @@ class PurchaseOrderIssue(AjaxUpdateView):
""" View for changing a purchase order from 'PENDING' to 'ISSUED' """
model = PurchaseOrder
ajax_form_title = 'Issue Order'
ajax_form_title = _('Issue Order')
ajax_template_name = "order/order_issue.html"
form_class = order_forms.IssuePurchaseOrderForm
@ -215,7 +215,7 @@ class PurchaseOrderComplete(AjaxUpdateView):
form_class = order_forms.CompletePurchaseOrderForm
model = PurchaseOrder
ajax_template_name = "order/order_complete.html"
ajax_form_title = "Complete Order"
ajax_form_title = _("Complete Order")
context_object_name = 'order'
def get_context_data(self):
@ -281,7 +281,7 @@ class PurchaseOrderReceive(AjaxUpdateView):
"""
form_class = order_forms.ReceivePurchaseOrderForm
ajax_form_title = "Receive Parts"
ajax_form_title = _("Receive Parts")
ajax_template_name = "order/receive_parts.html"
# Where the parts will be going (selected in POST request)
@ -445,7 +445,7 @@ class OrderParts(AjaxView):
"""
ajax_form_title = "Order Parts"
ajax_form_title = _("Order Parts")
ajax_template_name = 'order/order_wizard/select_parts.html'
# List of Parts we wish to order
@ -744,7 +744,7 @@ class POLineItemCreate(AjaxCreateView):
model = PurchaseOrderLineItem
context_object_name = 'line'
form_class = order_forms.EditPurchaseOrderLineItemForm
ajax_form_title = 'Add Line Item'
ajax_form_title = _('Add Line Item')
def post(self, request, *arg, **kwargs):
@ -859,7 +859,7 @@ class POLineItemEdit(AjaxUpdateView):
model = PurchaseOrderLineItem
form_class = order_forms.EditPurchaseOrderLineItemForm
ajax_template_name = 'modal_form.html'
ajax_form_title = 'Edit Line Item'
ajax_form_title = _('Edit Line Item')
def get_form(self):
form = super().get_form()
@ -875,10 +875,10 @@ class POLineItemDelete(AjaxDeleteView):
"""
model = PurchaseOrderLineItem
ajax_form_title = 'Delete Line Item'
ajax_form_title = _('Delete Line Item')
ajax_template_name = 'order/po_lineitem_delete.html'
def get_data(self):
return {
'danger': 'Deleted line item',
'danger': _('Deleted line item'),
}

View File

@ -131,11 +131,13 @@ class BomItemResource(ModelResource):
level = Field(attribute='level', readonly=True)
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
bom_id = Field(attribute='pk')
parent_part_id = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
parent_part_name = Field(attribute='part__full_name', readonly=True)
id = Field(attribute='sub_part', widget=widgets.ForeignKeyWidget(Part))
sub_part_id = Field(attribute='sub_part', widget=widgets.ForeignKeyWidget(Part))
sub_part_name = Field(attribute='sub_part__full_name', readonly=True)
@ -147,7 +149,12 @@ class BomItemResource(ModelResource):
report_skipped = False
clean_model_instances = True
exclude = ['checksum', ]
exclude = [
'checksum',
'id',
'part',
'sub_part',
]
class BomItemAdmin(ImportExportModelAdmin):

View File

@ -53,12 +53,18 @@ def ExportBom(part, fmt='csv', cascade=False):
bom_items = []
uids = []
def add_items(items, level):
# Add items at a given layer
for item in items:
item.level = '-' * level
# Avoid circular BOM references
if item.pk in uids:
continue
bom_items.append(item)
if item.sub_part.assembly:

View File

@ -35,6 +35,7 @@ from InvenTree import helpers
from InvenTree import validators
from InvenTree.models import InvenTreeTree
from InvenTree.fields import InvenTreeURLField
from InvenTree.helpers import decimal2string
from InvenTree.status_codes import BuildStatus, StockStatus, OrderStatus
@ -1242,11 +1243,11 @@ class BomItem(models.Model):
pmin, pmax = prange
# remove trailing zeros
pmin = pmin.normalize()
pmax = pmax.normalize()
if pmin == pmax:
return str(pmin)
return decimal2string(pmin)
# Convert to better string representation
pmin = decimal2string(pmin)
pmax = decimal2string(pmax)
return "{pmin} to {pmax}".format(pmin=pmin, pmax=pmax)

View File

@ -39,6 +39,9 @@
{
title: 'Allocated',
sortable: false,
formatter: function(value, row, index, field) {
return parseFloat(value);
},
},
{
title: 'Status',

View File

@ -1,5 +1,6 @@
{% extends "part/part_base.html" %}
{% load static %}
{% load i18n %}
{% block details %}
{% include 'part/tabs.html' with tab='stock' %}
@ -49,7 +50,7 @@
$("#stock-export").click(function() {
launchModalForm("{% url 'stock-export-options' %}", {
submit_text: "Export",
submit_text: "{% trans 'Export' %}",
success: function(response) {
var url = "{% url 'stock-export' %}";
@ -71,14 +72,14 @@
secondary: [
{
field: 'part',
label: 'New Part',
title: 'Create New Part',
label: '{% trans "New Part" %}',
title: '{% trans "Create New Part" %}',
url: "{% url 'part-create' %}",
},
{
field: 'supplier_part',
label: 'New Supplier Part',
title: 'Create new Supplier Part',
label: '{% trans "New Supplier Part" %}',
title: '{% trans "Create new Supplier Part" %}',
url: "{% url 'supplier-part-create' %}",
data: {
part: {{ part.id }}
@ -86,8 +87,8 @@
},
{
field: 'location',
label: 'New Location',
title: 'Create New Location',
label: '{% trans "New Location" %}',
title: '{% trans "Create New Location" %}',
url: "{% url 'stock-location-create' %}",
}
]

View File

@ -53,6 +53,9 @@
sortable: true,
field: 'quantity',
title: 'Uses',
formatter: function(value, row, index, field) {
return parseFloat(value);
},
}
],

View File

@ -27,7 +27,7 @@ def inrange(n, *args, **kwargs):
@register.simple_tag()
def multiply(x, y, *args, **kwargs):
""" Multiply two numbers together """
return x * y
return decimal2string(x * y)
@register.simple_tag()
@ -40,7 +40,7 @@ def add(x, y, *args, **kwargs):
def part_allocation_count(build, part, *args, **kwargs):
""" Return the total number of <part> allocated to <build> """
return build.getAllocatedQuantity(part)
return decimal2string(build.getAllocatedQuantity(part))
@register.simple_tag()

View File

@ -1,6 +1,7 @@
# Tests for the Part model
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase
@ -16,7 +17,7 @@ class TemplateTagTest(TestCase):
""" Tests for the custom template tag code """
def test_multiply(self):
self.assertEqual(inventree_extras.multiply(3, 5), 15)
self.assertEqual(int(inventree_extras.multiply(3, 5)), 15)
def test_version(self):
self.assertEqual(type(inventree_extras.inventree_version()), str)

View File

@ -72,7 +72,7 @@ class PartAttachmentCreate(AjaxCreateView):
"""
model = PartAttachment
form_class = part_forms.EditPartAttachmentForm
ajax_form_title = "Add part attachment"
ajax_form_title = _("Add part attachment")
ajax_template_name = "modal_form.html"
def get_data(self):
@ -115,7 +115,7 @@ class PartAttachmentEdit(AjaxUpdateView):
model = PartAttachment
form_class = part_forms.EditPartAttachmentForm
ajax_template_name = 'modal_form.html'
ajax_form_title = 'Edit attachment'
ajax_form_title = _('Edit attachment')
def get_data(self):
return {
@ -134,13 +134,13 @@ class PartAttachmentDelete(AjaxDeleteView):
""" View for deleting a PartAttachment """
model = PartAttachment
ajax_form_title = "Delete Part Attachment"
ajax_form_title = _("Delete Part Attachment")
ajax_template_name = "part/attachment_delete.html"
context_object_name = "attachment"
def get_data(self):
return {
'danger': 'Deleted part attachment'
'danger': _('Deleted part attachment')
}
@ -148,7 +148,7 @@ class PartSetCategory(AjaxUpdateView):
""" View for settings the part category for multiple parts at once """
ajax_template_name = 'part/set_category.html'
ajax_form_title = 'Set Part Category'
ajax_form_title = _('Set Part Category')
form_class = part_forms.SetPartCategoryForm
category = None
@ -231,7 +231,7 @@ class MakePartVariant(AjaxCreateView):
model = Part
form_class = part_forms.EditPartForm
ajax_form_title = 'Create Variant'
ajax_form_title = _('Create Variant')
ajax_template_name = 'part/variant_part.html'
def get_part_template(self):
@ -301,7 +301,7 @@ class PartDuplicate(AjaxCreateView):
model = Part
form_class = part_forms.EditPartForm
ajax_form_title = "Duplicate Part"
ajax_form_title = _("Duplicate Part")
ajax_template_name = "part/copy_part.html"
def get_data(self):
@ -592,7 +592,7 @@ class PartDetail(DetailView):
class PartQRCode(QRCodeView):
""" View for displaying a QR code for a Part object """
ajax_form_title = "Part QR Code"
ajax_form_title = _("Part QR Code")
def get_qr_data(self):
""" Generate QR code data for the Part """

View File

@ -301,6 +301,7 @@ class StockList(generics.ListCreateAPIView):
'part__category',
'part__category__name',
'part__category__description',
'supplier_part',
)
# Reduce the number of lookups we need to do for categories
@ -371,7 +372,13 @@ class StockList(generics.ListCreateAPIView):
except PartCategory.DoesNotExist:
pass
# Filter by supplier
# Filter by supplier_part ID
supplier_part_id = self.request.query_params.get('supplier_part', None)
if supplier_part_id:
stock_list = stock_list.filter(supplier_part=supplier_part_id)
# Filter by supplier ID
supplier_id = self.request.query_params.get('supplier', None)
if supplier_id:

View File

@ -35,7 +35,9 @@
<hr>
<div class='panel panel-default'>
<div class='panel-content'>
{% if item.notes %}
{{ item.notes | markdownify }}
{% endif %}
</div>
</div>

View File

@ -1,26 +0,0 @@
{% extends "stock/stock_app_base.html" %}
{% load static %}
{% block content %}
<h3>Stock list here!</h3>
<table class='table table-striped table-condensed' data-toolbar='#button-toolbar' id='tracking-table'>
</table>
{% endblock %}
{% block js_ready %}
{{ block.super }}
loadStockTrackingTable($("#tracking-table"), {
params: function(p) {
return {
ordering: '-date',
};
},
partColumn: true,
url: "{% url 'api-stock-track' %}",
});
{% endblock %}

View File

@ -24,7 +24,7 @@ from InvenTree.helpers import ExtractSerialNumbers
from decimal import Decimal, InvalidOperation
from datetime import datetime
from company.models import Company
from company.models import Company, SupplierPart
from part.models import Part
from .models import StockItem, StockLocation, StockItemTracking
@ -212,6 +212,16 @@ class StockExport(AjaxView):
except (ValueError, Company.DoesNotExist):
pass
# Check if a particular supplier_part was specified
sup_part_id = request.GET.get('supplier_part', None)
supplier_part = None
if sup_part_id:
try:
supplier_part = SupplierPart.objects.get(pk=sup_part_id)
except (ValueError, SupplierPart.DoesNotExist):
pass
# Check if a particular part was specified
part_id = request.GET.get('part', None)
part = None
@ -244,7 +254,11 @@ class StockExport(AjaxView):
if supplier:
stock_items = stock_items.filter(supplier_part__supplier=supplier)
if supplier_part:
stock_items = stock_items.filter(supplier_part=supplier_part)
# Filter out stock items that are not 'in stock'
# TODO - This might need some more thought in the future...
stock_items = stock_items.filter(customer=None)
stock_items = stock_items.filter(belongs_to=None)
@ -816,6 +830,11 @@ class StockItemCreate(AjaxCreateView):
part_id = self.request.GET.get('part', None)
loc_id = self.request.GET.get('location', None)
sup_part_id = self.request.GET.get('supplier_part', None)
part = None
location = None
supplier_part = None
# Part field has been specified
if part_id:
@ -824,14 +843,27 @@ class StockItemCreate(AjaxCreateView):
initials['part'] = part
initials['location'] = part.get_default_location()
initials['supplier_part'] = part.default_supplier
except Part.DoesNotExist:
except (ValueError, Part.DoesNotExist):
pass
# SupplierPart field has been specified
# It must match the Part, if that has been supplied
if sup_part_id:
try:
supplier_part = SupplierPart.objects.get(pk=sup_part_id)
if part is None or supplier_part.part == part:
initials['supplier_part'] = supplier_part
except (ValueError, SupplierPart.DoesNotExist):
pass
# Location has been specified
if loc_id:
try:
initials['location'] = StockLocation.objects.get(pk=loc_id)
except StockLocation.DoesNotExist:
location = StockLocation.objects.get(pk=loc_id)
initials['location'] = location
except (ValueError, StockLocation.DoesNotExist):
pass
return initials

View File

@ -1,18 +1,18 @@
{% load i18n %}
<div id='button-toolbar'>
<div class='button-toolbar container-fluid' style='float: right;'>
<button class='btn btn-default' id='stock-export' title='Export Stock Information'>Export</button>
{% if not part or part.is_template == False %}
<button class="btn btn-success" id='item-create'>New Stock Item</button>
{% endif %}
<button class='btn btn-default' id='stock-export' title='Export Stock Information'>{% trans "Export" %}</button>
<button class="btn btn-success" id='item-create'>{% trans "New Stock Item" %}</button>
<div class="dropdown" style='float: right;'>
<button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Options<span class="caret"></span></button>
<button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="#" id='multi-item-add' title='Add to selected stock items'>Add stock</a></li>
<li><a href="#" id='multi-item-remove' title='Remove from selected stock items'>Remove stock</a></li>
<li><a href="#" id='multi-item-stocktake' title='Stocktake selected stock items'>Count stock</a></li>
<li><a href='#' id='multi-item-move' title='Move selected stock items'>Move stock</a></li>
<li><a href='#' id='multi-item-order' title='Order selected items'>Order stock</a></li>
<li><a href='#' id='multi-item-delete' title='Delete selected items'>Delete Stock</a></li>
<li><a href="#" id='multi-item-add' title='Add to selected stock items'>{% trans "Add stock" %}</a></li>
<li><a href="#" id='multi-item-remove' title='Remove from selected stock items'>{% trans "Remove stock" %}</a></li>
<li><a href="#" id='multi-item-stocktake' title='Stocktake selected stock items'>{% trans "Count stock" %}</a></li>
<li><a href='#' id='multi-item-move' title='Move selected stock items'>{% trans "Move stock" %}</a></li>
<li><a href='#' id='multi-item-order' title='Order selected items'>{% trans "Order stock" %}</a></li>
<li><a href='#' id='multi-item-delete' title='Delete selected items'>{% trans "Delete Stock" %}</a></li>
</ul>
</div>
</div>

View File

@ -1,4 +1,4 @@
Django==2.2.9 # Django package
Django==2.2.10 # Django package
pillow==6.2.0 # Image manipulation
djangorestframework==3.10.3 # DRF framework
django-cors-headers==3.2.0 # CORS headers extension for DRF