mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree into order-modal-show-price
This commit is contained in:
commit
b503c62464
@ -33,6 +33,7 @@ def canAppAccessDatabase():
|
|||||||
'createsuperuser',
|
'createsuperuser',
|
||||||
'wait_for_db',
|
'wait_for_db',
|
||||||
'prerender',
|
'prerender',
|
||||||
|
'rebuild',
|
||||||
'collectstatic',
|
'collectstatic',
|
||||||
'makemessages',
|
'makemessages',
|
||||||
'compilemessages',
|
'compilemessages',
|
||||||
|
@ -4,9 +4,10 @@ Provides system status functionality checks.
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django_q.models import Success
|
from django_q.models import Success
|
||||||
from django_q.monitor import Stat
|
from django_q.monitor import Stat
|
||||||
@ -34,7 +35,7 @@ def is_worker_running(**kwargs):
|
|||||||
Check to see if we have a result within the last 20 minutes
|
Check to see if we have a result within the last 20 minutes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
now = datetime.now()
|
now = timezone.now()
|
||||||
past = now - timedelta(minutes=20)
|
past = now - timedelta(minutes=20)
|
||||||
|
|
||||||
results = Success.objects.filter(
|
results = Success.objects.filter(
|
||||||
|
@ -20,9 +20,12 @@ v4 -> 2021-06-01
|
|||||||
- BOM items can now accept "variant stock" to be assigned against them
|
- BOM items can now accept "variant stock" to be assigned against them
|
||||||
- Many slight API tweaks were needed to get this to work properly!
|
- Many slight API tweaks were needed to get this to work properly!
|
||||||
|
|
||||||
|
v5 -> 2021-06-21
|
||||||
|
- Adds API interface for manufacturer part parameters
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
INVENTREE_API_VERSION = 4
|
INVENTREE_API_VERSION = 5
|
||||||
|
|
||||||
|
|
||||||
def inventreeInstanceName():
|
def inventreeInstanceName():
|
||||||
|
@ -11,6 +11,7 @@ import import_export.widgets as widgets
|
|||||||
from .models import Company
|
from .models import Company
|
||||||
from .models import SupplierPart
|
from .models import SupplierPart
|
||||||
from .models import SupplierPriceBreak
|
from .models import SupplierPriceBreak
|
||||||
|
from .models import ManufacturerPart, ManufacturerPartParameter
|
||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
|
|
||||||
@ -71,6 +72,92 @@ class SupplierPartAdmin(ImportExportModelAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartResource(ModelResource):
|
||||||
|
"""
|
||||||
|
Class for managing ManufacturerPart data import/export
|
||||||
|
"""
|
||||||
|
|
||||||
|
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
|
||||||
|
|
||||||
|
part_name = Field(attribute='part__full_name', readonly=True)
|
||||||
|
|
||||||
|
manufacturer = Field(attribute='manufacturer', widget=widgets.ForeignKeyWidget(Company))
|
||||||
|
|
||||||
|
manufacturer_name = Field(attribute='manufacturer__name', readonly=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ManufacturerPart
|
||||||
|
skip_unchanged = True
|
||||||
|
report_skipped = True
|
||||||
|
clean_model_instances = True
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartParameterInline(admin.TabularInline):
|
||||||
|
"""
|
||||||
|
Inline for editing ManufacturerPartParameter objects,
|
||||||
|
directly from the ManufacturerPart admin view.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = ManufacturerPartParameter
|
||||||
|
|
||||||
|
|
||||||
|
class SupplierPartInline(admin.TabularInline):
|
||||||
|
"""
|
||||||
|
Inline for the SupplierPart model
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = SupplierPart
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartAdmin(ImportExportModelAdmin):
|
||||||
|
"""
|
||||||
|
Admin class for ManufacturerPart model
|
||||||
|
"""
|
||||||
|
|
||||||
|
resource_class = ManufacturerPartResource
|
||||||
|
|
||||||
|
list_display = ('part', 'manufacturer', 'MPN')
|
||||||
|
|
||||||
|
search_fields = [
|
||||||
|
'manufacturer__name',
|
||||||
|
'part__name',
|
||||||
|
'MPN',
|
||||||
|
]
|
||||||
|
|
||||||
|
inlines = [
|
||||||
|
SupplierPartInline,
|
||||||
|
ManufacturerPartParameterInline,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartParameterResource(ModelResource):
|
||||||
|
"""
|
||||||
|
Class for managing ManufacturerPartParameter data import/export
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ManufacturerPartParameter
|
||||||
|
skip_unchanged = True
|
||||||
|
report_skipped = True
|
||||||
|
clean_model_instance = True
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartParameterAdmin(ImportExportModelAdmin):
|
||||||
|
"""
|
||||||
|
Admin class for ManufacturerPartParameter model
|
||||||
|
"""
|
||||||
|
|
||||||
|
resource_class = ManufacturerPartParameterResource
|
||||||
|
|
||||||
|
list_display = ('manufacturer_part', 'name', 'value')
|
||||||
|
|
||||||
|
search_fields = [
|
||||||
|
'manufacturer_part__manufacturer__name',
|
||||||
|
'name',
|
||||||
|
'value'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SupplierPriceBreakResource(ModelResource):
|
class SupplierPriceBreakResource(ModelResource):
|
||||||
""" Class for managing SupplierPriceBreak data import/export """
|
""" Class for managing SupplierPriceBreak data import/export """
|
||||||
|
|
||||||
@ -103,3 +190,6 @@ class SupplierPriceBreakAdmin(ImportExportModelAdmin):
|
|||||||
admin.site.register(Company, CompanyAdmin)
|
admin.site.register(Company, CompanyAdmin)
|
||||||
admin.site.register(SupplierPart, SupplierPartAdmin)
|
admin.site.register(SupplierPart, SupplierPartAdmin)
|
||||||
admin.site.register(SupplierPriceBreak, SupplierPriceBreakAdmin)
|
admin.site.register(SupplierPriceBreak, SupplierPriceBreakAdmin)
|
||||||
|
|
||||||
|
admin.site.register(ManufacturerPart, ManufacturerPartAdmin)
|
||||||
|
admin.site.register(ManufacturerPartParameter, ManufacturerPartParameterAdmin)
|
||||||
|
@ -15,11 +15,11 @@ from django.db.models import Q
|
|||||||
from InvenTree.helpers import str2bool
|
from InvenTree.helpers import str2bool
|
||||||
|
|
||||||
from .models import Company
|
from .models import Company
|
||||||
from .models import ManufacturerPart
|
from .models import ManufacturerPart, ManufacturerPartParameter
|
||||||
from .models import SupplierPart, SupplierPriceBreak
|
from .models import SupplierPart, SupplierPriceBreak
|
||||||
|
|
||||||
from .serializers import CompanySerializer
|
from .serializers import CompanySerializer
|
||||||
from .serializers import ManufacturerPartSerializer
|
from .serializers import ManufacturerPartSerializer, ManufacturerPartParameterSerializer
|
||||||
from .serializers import SupplierPartSerializer, SupplierPriceBreakSerializer
|
from .serializers import SupplierPartSerializer, SupplierPriceBreakSerializer
|
||||||
|
|
||||||
|
|
||||||
@ -175,6 +175,86 @@ class ManufacturerPartDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
serializer_class = ManufacturerPartSerializer
|
serializer_class = ManufacturerPartSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartParameterList(generics.ListCreateAPIView):
|
||||||
|
"""
|
||||||
|
API endpoint for list view of ManufacturerPartParamater model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = ManufacturerPartParameter.objects.all()
|
||||||
|
serializer_class = ManufacturerPartParameterSerializer
|
||||||
|
|
||||||
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
|
||||||
|
# Do we wish to include any extra detail?
|
||||||
|
try:
|
||||||
|
params = self.request.query_params
|
||||||
|
|
||||||
|
optional_fields = [
|
||||||
|
'manufacturer_part_detail',
|
||||||
|
]
|
||||||
|
|
||||||
|
for key in optional_fields:
|
||||||
|
kwargs[key] = str2bool(params.get(key, None))
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
kwargs['context'] = self.get_serializer_context()
|
||||||
|
|
||||||
|
return self.serializer_class(*args, **kwargs)
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
"""
|
||||||
|
Custom filtering for the queryset
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
|
||||||
|
params = self.request.query_params
|
||||||
|
|
||||||
|
# Filter by manufacturer?
|
||||||
|
manufacturer = params.get('manufacturer', None)
|
||||||
|
|
||||||
|
if manufacturer is not None:
|
||||||
|
queryset = queryset.filter(manufacturer_part__manufacturer=manufacturer)
|
||||||
|
|
||||||
|
# Filter by part?
|
||||||
|
part = params.get('part', None)
|
||||||
|
|
||||||
|
if part is not None:
|
||||||
|
queryset = queryset.filter(manufacturer_part__part=part)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
filter_backends = [
|
||||||
|
DjangoFilterBackend,
|
||||||
|
filters.SearchFilter,
|
||||||
|
filters.OrderingFilter,
|
||||||
|
]
|
||||||
|
|
||||||
|
filter_fields = [
|
||||||
|
'name',
|
||||||
|
'value',
|
||||||
|
'units',
|
||||||
|
'manufacturer_part',
|
||||||
|
]
|
||||||
|
|
||||||
|
search_fields = [
|
||||||
|
'name',
|
||||||
|
'value',
|
||||||
|
'units',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartParameterDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""
|
||||||
|
API endpoint for detail view of ManufacturerPartParameter model
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = ManufacturerPartParameter.objects.all()
|
||||||
|
serializer_class = ManufacturerPartParameterSerializer
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartList(generics.ListCreateAPIView):
|
class SupplierPartList(generics.ListCreateAPIView):
|
||||||
""" API endpoint for list view of SupplierPart object
|
""" API endpoint for list view of SupplierPart object
|
||||||
|
|
||||||
@ -249,7 +329,7 @@ class SupplierPartList(generics.ListCreateAPIView):
|
|||||||
params = self.request.query_params
|
params = self.request.query_params
|
||||||
kwargs['part_detail'] = str2bool(params.get('part_detail', None))
|
kwargs['part_detail'] = str2bool(params.get('part_detail', None))
|
||||||
kwargs['supplier_detail'] = str2bool(params.get('supplier_detail', None))
|
kwargs['supplier_detail'] = str2bool(params.get('supplier_detail', None))
|
||||||
kwargs['manufacturer_detail'] = str2bool(self.params.get('manufacturer_detail', None))
|
kwargs['manufacturer_detail'] = str2bool(params.get('manufacturer_detail', None))
|
||||||
kwargs['pretty'] = str2bool(params.get('pretty', None))
|
kwargs['pretty'] = str2bool(params.get('pretty', None))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
@ -316,6 +396,13 @@ class SupplierPriceBreakList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
manufacturer_part_api_urls = [
|
manufacturer_part_api_urls = [
|
||||||
|
|
||||||
|
url(r'^parameter/', include([
|
||||||
|
url(r'^(?P<pk>\d+)/', ManufacturerPartParameterDetail.as_view(), name='api-manufacturer-part-parameter-detail'),
|
||||||
|
|
||||||
|
# Catch anything else
|
||||||
|
url(r'^.*$', ManufacturerPartParameterList.as_view(), name='api-manufacturer-part-parameter-list'),
|
||||||
|
])),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/?', ManufacturerPartDetail.as_view(), name='api-manufacturer-part-detail'),
|
url(r'^(?P<pk>\d+)/?', ManufacturerPartDetail.as_view(), name='api-manufacturer-part-detail'),
|
||||||
|
|
||||||
# Catch anything else
|
# Catch anything else
|
||||||
|
@ -16,7 +16,7 @@ from djmoney.forms.fields import MoneyField
|
|||||||
|
|
||||||
from common.settings import currency_code_default
|
from common.settings import currency_code_default
|
||||||
|
|
||||||
from .models import Company
|
from .models import Company, ManufacturerPartParameter
|
||||||
from .models import ManufacturerPart
|
from .models import ManufacturerPart
|
||||||
from .models import SupplierPart
|
from .models import SupplierPart
|
||||||
from .models import SupplierPriceBreak
|
from .models import SupplierPriceBreak
|
||||||
@ -105,6 +105,21 @@ class EditManufacturerPartForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EditManufacturerPartParameterForm(HelperForm):
|
||||||
|
"""
|
||||||
|
Form for creating / editing a ManufacturerPartParameter object
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ManufacturerPartParameter
|
||||||
|
fields = [
|
||||||
|
'manufacturer_part',
|
||||||
|
'name',
|
||||||
|
'value',
|
||||||
|
'units',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EditSupplierPartForm(HelperForm):
|
class EditSupplierPartForm(HelperForm):
|
||||||
""" Form for editing a SupplierPart object """
|
""" Form for editing a SupplierPart object """
|
||||||
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 3.2.4 on 2021-06-20 07:48
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('company', '0037_supplierpart_update_3'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ManufacturerPartParameter',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(help_text='Parameter name', max_length=500, verbose_name='Name')),
|
||||||
|
('value', models.CharField(help_text='Parameter value', max_length=500, verbose_name='Value')),
|
||||||
|
('units', models.CharField(blank=True, help_text='Parameter units', max_length=64, null=True, verbose_name='Units')),
|
||||||
|
('manufacturer_part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parameters', to='company.manufacturerpart', verbose_name='Manufacturer Part')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('manufacturer_part', 'name')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -371,6 +371,47 @@ class ManufacturerPart(models.Model):
|
|||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartParameter(models.Model):
|
||||||
|
"""
|
||||||
|
A ManufacturerPartParameter represents a key:value parameter for a MnaufacturerPart.
|
||||||
|
|
||||||
|
This is used to represent parmeters / properties for a particular manufacturer part.
|
||||||
|
|
||||||
|
Each parameter is a simple string (text) value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('manufacturer_part', 'name')
|
||||||
|
|
||||||
|
manufacturer_part = models.ForeignKey(
|
||||||
|
ManufacturerPart,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='parameters',
|
||||||
|
verbose_name=_('Manufacturer Part'),
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=500,
|
||||||
|
blank=False,
|
||||||
|
verbose_name=_('Name'),
|
||||||
|
help_text=_('Parameter name')
|
||||||
|
)
|
||||||
|
|
||||||
|
value = models.CharField(
|
||||||
|
max_length=500,
|
||||||
|
blank=False,
|
||||||
|
verbose_name=_('Value'),
|
||||||
|
help_text=_('Parameter value')
|
||||||
|
)
|
||||||
|
|
||||||
|
units = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True, null=True,
|
||||||
|
verbose_name=_('Units'),
|
||||||
|
help_text=_('Parameter units')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SupplierPart(models.Model):
|
class SupplierPart(models.Model):
|
||||||
""" Represents a unique part as provided by a Supplier
|
""" Represents a unique part as provided by a Supplier
|
||||||
Each SupplierPart is identified by a SKU (Supplier Part Number)
|
Each SupplierPart is identified by a SKU (Supplier Part Number)
|
||||||
|
@ -7,7 +7,7 @@ from rest_framework import serializers
|
|||||||
from sql_util.utils import SubqueryCount
|
from sql_util.utils import SubqueryCount
|
||||||
|
|
||||||
from .models import Company
|
from .models import Company
|
||||||
from .models import ManufacturerPart
|
from .models import ManufacturerPart, ManufacturerPartParameter
|
||||||
from .models import SupplierPart, SupplierPriceBreak
|
from .models import SupplierPart, SupplierPriceBreak
|
||||||
|
|
||||||
from InvenTree.serializers import InvenTreeModelSerializer
|
from InvenTree.serializers import InvenTreeModelSerializer
|
||||||
@ -124,6 +124,35 @@ class ManufacturerPartSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartParameterSerializer(InvenTreeModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for the ManufacturerPartParameter model
|
||||||
|
"""
|
||||||
|
|
||||||
|
manufacturer_part_detail = ManufacturerPartSerializer(source='manufacturer_part', many=False, read_only=True)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
man_detail = kwargs.pop('manufacturer_part_detail', False)
|
||||||
|
|
||||||
|
super(ManufacturerPartParameterSerializer, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if not man_detail:
|
||||||
|
self.fields.pop('manufacturer_part_detail')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ManufacturerPartParameter
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
'pk',
|
||||||
|
'manufacturer_part',
|
||||||
|
'manufacturer_part_detail',
|
||||||
|
'name',
|
||||||
|
'value',
|
||||||
|
'units',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartSerializer(InvenTreeModelSerializer):
|
class SupplierPartSerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for SupplierPart object """
|
""" Serializer for SupplierPart object """
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block heading %}
|
{% block heading %}
|
||||||
{% trans "Supplier Parts" %}
|
{% trans "Suppliers" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block details %}
|
{% block details %}
|
||||||
@ -30,9 +30,44 @@
|
|||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block post_content_panels %}
|
||||||
|
|
||||||
|
<div class='panel panel-default panel-inventree'>
|
||||||
|
<div class='panel-heading'>
|
||||||
|
<h4>{% trans "Parameters" %}</h4>
|
||||||
|
</div>
|
||||||
|
<div class='panel-content'>
|
||||||
|
<div id='parameter-toolbar'>
|
||||||
|
<div class='btn-group'>
|
||||||
|
<button class='btn btn-success' id='parameter-create'>
|
||||||
|
<span class='fas fa-plus-circle'></span> {% trans "New Parameter" %}
|
||||||
|
</button>
|
||||||
|
<div id='param-dropdown' class='btn-group'>
|
||||||
|
<!-- TODO -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed' id='parameter-table' data-toolbar='#parameter-toolbar'></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
|
$('#parameter-create').click(function() {
|
||||||
|
launchModalForm(
|
||||||
|
"{% url 'manufacturer-part-parameter-create' %}",
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
manufacturer_part: {{ part.id }},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
$('#supplier-create').click(function () {
|
$('#supplier-create').click(function () {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
"{% url 'supplier-part-create' %}",
|
"{% url 'supplier-part-create' %}",
|
||||||
@ -84,6 +119,16 @@ loadSupplierPartTable(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
loadManufacturerPartParameterTable(
|
||||||
|
"#parameter-table",
|
||||||
|
"{% url 'api-manufacturer-part-parameter-list' %}",
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
manufacturer_part: {{ part.id }},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
linkButtonsToSelection($("#supplier-table"), ['#supplier-part-options'])
|
linkButtonsToSelection($("#supplier-table"), ['#supplier-part-options'])
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -53,20 +53,25 @@ price_break_urls = [
|
|||||||
url(r'^(?P<pk>\d+)/delete/', views.PriceBreakDelete.as_view(), name='price-break-delete'),
|
url(r'^(?P<pk>\d+)/delete/', views.PriceBreakDelete.as_view(), name='price-break-delete'),
|
||||||
]
|
]
|
||||||
|
|
||||||
manufacturer_part_detail_urls = [
|
|
||||||
url(r'^edit/?', views.ManufacturerPartEdit.as_view(), name='manufacturer-part-edit'),
|
|
||||||
|
|
||||||
url(r'^suppliers/', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_suppliers.html'), name='manufacturer-part-suppliers'),
|
|
||||||
|
|
||||||
url('^.*$', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_suppliers.html'), name='manufacturer-part-detail'),
|
|
||||||
]
|
|
||||||
|
|
||||||
manufacturer_part_urls = [
|
manufacturer_part_urls = [
|
||||||
url(r'^new/?', views.ManufacturerPartCreate.as_view(), name='manufacturer-part-create'),
|
url(r'^new/?', views.ManufacturerPartCreate.as_view(), name='manufacturer-part-create'),
|
||||||
|
|
||||||
url(r'delete/', views.ManufacturerPartDelete.as_view(), name='manufacturer-part-delete'),
|
url(r'^delete/', views.ManufacturerPartDelete.as_view(), name='manufacturer-part-delete'),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/', include(manufacturer_part_detail_urls)),
|
# URLs for ManufacturerPartParameter views (create / edit / delete)
|
||||||
|
url(r'^parameter/', include([
|
||||||
|
url(r'^new/', views.ManufacturerPartParameterCreate.as_view(), name='manufacturer-part-parameter-create'),
|
||||||
|
url(r'^(?P<pk>\d)/', include([
|
||||||
|
url(r'^edit/', views.ManufacturerPartParameterEdit.as_view(), name='manufacturer-part-parameter-edit'),
|
||||||
|
url(r'^delete/', views.ManufacturerPartParameterDelete.as_view(), name='manufacturer-part-parameter-delete'),
|
||||||
|
])),
|
||||||
|
])),
|
||||||
|
|
||||||
|
url(r'^(?P<pk>\d+)/', include([
|
||||||
|
url(r'^edit/?', views.ManufacturerPartEdit.as_view(), name='manufacturer-part-edit'),
|
||||||
|
url(r'^suppliers/', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_suppliers.html'), name='manufacturer-part-suppliers'),
|
||||||
|
url('^.*$', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_suppliers.html'), name='manufacturer-part-detail'),
|
||||||
|
])),
|
||||||
]
|
]
|
||||||
|
|
||||||
supplier_part_detail_urls = [
|
supplier_part_detail_urls = [
|
||||||
|
@ -23,14 +23,14 @@ from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
|||||||
from InvenTree.helpers import str2bool
|
from InvenTree.helpers import str2bool
|
||||||
from InvenTree.views import InvenTreeRoleMixin
|
from InvenTree.views import InvenTreeRoleMixin
|
||||||
|
|
||||||
from .models import Company
|
from .models import Company, ManufacturerPartParameter
|
||||||
from .models import ManufacturerPart
|
from .models import ManufacturerPart
|
||||||
from .models import SupplierPart
|
from .models import SupplierPart
|
||||||
from .models import SupplierPriceBreak
|
from .models import SupplierPriceBreak
|
||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
|
|
||||||
from .forms import EditCompanyForm
|
from .forms import EditCompanyForm, EditManufacturerPartParameterForm
|
||||||
from .forms import CompanyImageForm
|
from .forms import CompanyImageForm
|
||||||
from .forms import EditManufacturerPartForm
|
from .forms import EditManufacturerPartForm
|
||||||
from .forms import EditSupplierPartForm
|
from .forms import EditSupplierPartForm
|
||||||
@ -504,6 +504,66 @@ class ManufacturerPartDelete(AjaxDeleteView):
|
|||||||
return self.renderJsonResponse(self.request, data=data, form=self.get_form())
|
return self.renderJsonResponse(self.request, data=data, form=self.get_form())
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartParameterCreate(AjaxCreateView):
|
||||||
|
"""
|
||||||
|
View for creating a new ManufacturerPartParameter object
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = ManufacturerPartParameter
|
||||||
|
form_class = EditManufacturerPartParameterForm
|
||||||
|
ajax_form_title = _('Add Manufacturer Part Parameter')
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
|
||||||
|
form = super().get_form()
|
||||||
|
|
||||||
|
# Hide the manufacturer_part field if specified
|
||||||
|
if form.initial.get('manufacturer_part', None):
|
||||||
|
form.fields['manufacturer_part'].widget = HiddenInput()
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
|
||||||
|
initials = super().get_initial().copy()
|
||||||
|
|
||||||
|
manufacturer_part = self.get_param('manufacturer_part')
|
||||||
|
|
||||||
|
if manufacturer_part:
|
||||||
|
try:
|
||||||
|
initials['manufacturer_part'] = ManufacturerPartParameter.objects.get(pk=manufacturer_part)
|
||||||
|
except (ValueError, ManufacturerPartParameter.DoesNotExist):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return initials
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartParameterEdit(AjaxUpdateView):
|
||||||
|
"""
|
||||||
|
View for editing a ManufacturerPartParameter object
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = ManufacturerPartParameter
|
||||||
|
form_class = EditManufacturerPartParameterForm
|
||||||
|
ajax_form_title = _('Edit Manufacturer Part Parameter')
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
|
||||||
|
form = super().get_form()
|
||||||
|
|
||||||
|
form.fields['manufacturer_part'].widget = HiddenInput()
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartParameterDelete(AjaxDeleteView):
|
||||||
|
"""
|
||||||
|
View for deleting a ManufacturerPartParameter object
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = ManufacturerPartParameter
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartDetail(DetailView):
|
class SupplierPartDetail(DetailView):
|
||||||
""" Detail view for SupplierPart """
|
""" Detail view for SupplierPart """
|
||||||
model = SupplierPart
|
model = SupplierPart
|
||||||
@ -563,6 +623,7 @@ class SupplierPartEdit(AjaxUpdateView):
|
|||||||
supplier_part = self.get_object()
|
supplier_part = self.get_object()
|
||||||
|
|
||||||
if supplier_part.manufacturer_part:
|
if supplier_part.manufacturer_part:
|
||||||
|
if supplier_part.manufacturer_part.manufacturer:
|
||||||
initials['manufacturer'] = supplier_part.manufacturer_part.manufacturer.id
|
initials['manufacturer'] = supplier_part.manufacturer_part.manufacturer.id
|
||||||
initials['MPN'] = supplier_part.manufacturer_part.MPN
|
initials['MPN'] = supplier_part.manufacturer_part.MPN
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-hashtag'></span></td>
|
<td><span class='fas fa-hashtag'></span></td>
|
||||||
<td>{% trans "Order Reference" %}</td>
|
<td>{% trans "Order Reference" %}</td>
|
||||||
<td>{{ order.reference }}{% include "clip.html"%}</td>
|
<td>{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}{{ order.reference }}{% include "clip.html"%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-info'></span></td>
|
<td><span class='fas fa-info'></span></td>
|
||||||
|
@ -77,7 +77,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-hashtag'></span></td>
|
<td><span class='fas fa-hashtag'></span></td>
|
||||||
<td>{% trans "Order Reference" %}</td>
|
<td>{% trans "Order Reference" %}</td>
|
||||||
<td>{{ order.reference }}{% include "clip.html"%}</td>
|
<td>{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}{{ order.reference }}{% include "clip.html"%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-info'></span></td>
|
<td><span class='fas fa-info'></span></td>
|
||||||
|
@ -111,6 +111,13 @@ class PartCategoryResource(ModelResource):
|
|||||||
PartCategory.objects.rebuild()
|
PartCategory.objects.rebuild()
|
||||||
|
|
||||||
|
|
||||||
|
class PartCategoryInline(admin.TabularInline):
|
||||||
|
"""
|
||||||
|
Inline for PartCategory model
|
||||||
|
"""
|
||||||
|
model = PartCategory
|
||||||
|
|
||||||
|
|
||||||
class PartCategoryAdmin(ImportExportModelAdmin):
|
class PartCategoryAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
resource_class = PartCategoryResource
|
resource_class = PartCategoryResource
|
||||||
@ -119,6 +126,10 @@ class PartCategoryAdmin(ImportExportModelAdmin):
|
|||||||
|
|
||||||
search_fields = ('name', 'description')
|
search_fields = ('name', 'description')
|
||||||
|
|
||||||
|
inlines = [
|
||||||
|
PartCategoryInline,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class PartRelatedAdmin(admin.ModelAdmin):
|
class PartRelatedAdmin(admin.ModelAdmin):
|
||||||
''' Class to manage PartRelated objects '''
|
''' Class to manage PartRelated objects '''
|
||||||
|
@ -706,6 +706,7 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
'creation_date',
|
'creation_date',
|
||||||
'IPN',
|
'IPN',
|
||||||
'in_stock',
|
'in_stock',
|
||||||
|
'category',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Default ordering
|
# Default ordering
|
||||||
|
@ -380,7 +380,6 @@ class Part(MPTTModel):
|
|||||||
previous.image.delete(save=False)
|
previous.image.delete(save=False)
|
||||||
|
|
||||||
self.clean()
|
self.clean()
|
||||||
self.validate_unique()
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@ -672,6 +671,8 @@ class Part(MPTTModel):
|
|||||||
|
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
|
self.validate_unique()
|
||||||
|
|
||||||
if self.trackable:
|
if self.trackable:
|
||||||
for part in self.get_used_in().all():
|
for part in self.get_used_in().all():
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<h4>{% trans 'Stock Pricing' %}<i class="fas fa-info-circle" title="Shows the purchase prices of stock for this part.
|
<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>
|
The part single price is the current purchase price for that supplier part."></i></h4>
|
||||||
{% if price_history|length > 1 %}
|
{% if price_history|length > 0 %}
|
||||||
<div style="max-width: 99%; min-height: 300px">
|
<div style="max-width: 99%; min-height: 300px">
|
||||||
<canvas id="StockPriceChart"></canvas>
|
<canvas id="StockPriceChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,6 +5,7 @@ over and above the built-in Django tags.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings as djangosettings
|
from django.conf import settings as djangosettings
|
||||||
@ -114,6 +115,14 @@ def inventree_title(*args, **kwargs):
|
|||||||
return version.inventreeInstanceTitle()
|
return version.inventreeInstanceTitle()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag()
|
||||||
|
def python_version(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Return the current python version
|
||||||
|
"""
|
||||||
|
return sys.version.split(' ')[0]
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def inventree_version(*args, **kwargs):
|
def inventree_version(*args, **kwargs):
|
||||||
""" Return InvenTree version string """
|
""" Return InvenTree version string """
|
||||||
|
@ -814,7 +814,7 @@ class PartPricingView(PartDetail):
|
|||||||
part = self.get_part()
|
part = self.get_part()
|
||||||
# Stock history
|
# Stock history
|
||||||
if part.total_stock > 1:
|
if part.total_stock > 1:
|
||||||
ret = []
|
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) # .order_by('purchase_order__date')
|
||||||
stock = stock.prefetch_related('purchase_order', 'supplier_part')
|
stock = stock.prefetch_related('purchase_order', 'supplier_part')
|
||||||
|
|
||||||
@ -841,9 +841,9 @@ class PartPricingView(PartDetail):
|
|||||||
line['date'] = stock_item.purchase_order.issue_date.strftime('%d.%m.%Y')
|
line['date'] = stock_item.purchase_order.issue_date.strftime('%d.%m.%Y')
|
||||||
else:
|
else:
|
||||||
line['date'] = stock_item.tracking_info.first().date.strftime('%d.%m.%Y')
|
line['date'] = stock_item.tracking_info.first().date.strftime('%d.%m.%Y')
|
||||||
ret.append(line)
|
price_history.append(line)
|
||||||
|
|
||||||
ctx['price_history'] = ret
|
ctx['price_history'] = price_history
|
||||||
|
|
||||||
# BOM Information for Pie-Chart
|
# BOM Information for Pie-Chart
|
||||||
if part.has_bom:
|
if part.has_bom:
|
||||||
|
@ -44,6 +44,13 @@ class LocationResource(ModelResource):
|
|||||||
StockLocation.objects.rebuild()
|
StockLocation.objects.rebuild()
|
||||||
|
|
||||||
|
|
||||||
|
class LocationInline(admin.TabularInline):
|
||||||
|
"""
|
||||||
|
Inline for sub-locations
|
||||||
|
"""
|
||||||
|
model = StockLocation
|
||||||
|
|
||||||
|
|
||||||
class LocationAdmin(ImportExportModelAdmin):
|
class LocationAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
resource_class = LocationResource
|
resource_class = LocationResource
|
||||||
@ -52,6 +59,10 @@ class LocationAdmin(ImportExportModelAdmin):
|
|||||||
|
|
||||||
search_fields = ('name', 'description')
|
search_fields = ('name', 'description')
|
||||||
|
|
||||||
|
inlines = [
|
||||||
|
LocationInline,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockItemResource(ModelResource):
|
class StockItemResource(ModelResource):
|
||||||
""" Class for managing StockItem data import/export """
|
""" Class for managing StockItem data import/export """
|
||||||
|
76
InvenTree/stock/migrations/0064_auto_20210621_1724.py
Normal file
76
InvenTree/stock/migrations/0064_auto_20210621_1724.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Generated by Django 3.2.4 on 2021-06-21 07:24
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def extract_purchase_price(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Find instances of StockItem which do *not* have a purchase price set,
|
||||||
|
but which point to a PurchaseOrder where there *is* a purchase price set.
|
||||||
|
|
||||||
|
Then, assign *that* purchase price to original StockItem.
|
||||||
|
|
||||||
|
This is to address an issue where older versions of InvenTree
|
||||||
|
did not correctly copy purchase price information cross to the StockItem objects.
|
||||||
|
|
||||||
|
Current InvenTree version (as of 2021-06-21) copy this information across correctly,
|
||||||
|
so this one-time data migration should suffice.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Required database models
|
||||||
|
StockItem = apps.get_model('stock', 'stockitem')
|
||||||
|
PurchaseOrder = apps.get_model('order', 'purchaseorder')
|
||||||
|
PurchaseOrderLineItem = apps.get_model('order', 'purchaseorderlineitem')
|
||||||
|
Part = apps.get_model('part', 'part')
|
||||||
|
|
||||||
|
# Find all the StockItem objects without a purchase_price which point to a PurchaseOrder
|
||||||
|
items = StockItem.objects.filter(purchase_price=None).exclude(purchase_order=None)
|
||||||
|
|
||||||
|
print(f"Found {items.count()} stock items with missing purchase price information")
|
||||||
|
|
||||||
|
update_count = 0
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
|
||||||
|
part_id = item.part
|
||||||
|
|
||||||
|
po = item.purchase_order
|
||||||
|
|
||||||
|
# Look for a matching PurchaseOrderLineItem (with a price)
|
||||||
|
lines = PurchaseOrderLineItem.objects.filter(part__part=part_id, order=po)
|
||||||
|
|
||||||
|
if lines.exists():
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if line.purchase_price is not None:
|
||||||
|
|
||||||
|
# Copy pricing information across
|
||||||
|
item.purchase_price = line.purchase_price
|
||||||
|
item.purchases_price_currency = line.purchase_price_currency
|
||||||
|
|
||||||
|
print(f"- Updating supplier price for {item.part.name} - {item.purchase_price} {item.purchase_price_currency}")
|
||||||
|
|
||||||
|
update_count += 1
|
||||||
|
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
print(f"Updated pricing for {update_count} stock items")
|
||||||
|
|
||||||
|
def reverse_operation(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
DO NOTHING!
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('stock', '0063_auto_20210511_2343'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(extract_purchase_price, reverse_code=reverse_operation)
|
||||||
|
]
|
@ -81,6 +81,7 @@ class StockItemSerializer(InvenTreeModelSerializer):
|
|||||||
'belongs_to',
|
'belongs_to',
|
||||||
'build',
|
'build',
|
||||||
'customer',
|
'customer',
|
||||||
|
'purchase_order',
|
||||||
'sales_order',
|
'sales_order',
|
||||||
'supplier_part',
|
'supplier_part',
|
||||||
'supplier_part__supplier',
|
'supplier_part__supplier',
|
||||||
@ -163,6 +164,10 @@ class StockItemSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
purchase_price = serializers.SerializerMethodField()
|
purchase_price = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
purchase_order_reference = serializers.CharField(source='purchase_order.reference', read_only=True)
|
||||||
|
|
||||||
|
sales_order_reference = serializers.CharField(source='sales_order.reference', read_only=True)
|
||||||
|
|
||||||
def get_purchase_price(self, obj):
|
def get_purchase_price(self, obj):
|
||||||
""" Return purchase_price (Money field) as string (includes currency) """
|
""" Return purchase_price (Money field) as string (includes currency) """
|
||||||
|
|
||||||
@ -208,10 +213,13 @@ class StockItemSerializer(InvenTreeModelSerializer):
|
|||||||
'packaging',
|
'packaging',
|
||||||
'part',
|
'part',
|
||||||
'part_detail',
|
'part_detail',
|
||||||
|
'purchase_order',
|
||||||
|
'purchase_order_reference',
|
||||||
'pk',
|
'pk',
|
||||||
'quantity',
|
'quantity',
|
||||||
'required_tests',
|
'required_tests',
|
||||||
'sales_order',
|
'sales_order',
|
||||||
|
'sales_order_reference',
|
||||||
'serial',
|
'serial',
|
||||||
'stale',
|
'stale',
|
||||||
'status',
|
'status',
|
||||||
|
@ -325,7 +325,7 @@
|
|||||||
<td><a href="{% url 'po-detail' item.purchase_order.id %}">{{ item.purchase_order }}</a></td>
|
<td><a href="{% url 'po-detail' item.purchase_order.id %}">{{ item.purchase_order }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if item.purchase_price %}
|
{% if item.purchase_price != None %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-dollar-sign'></span></td>
|
<td><span class='fas fa-dollar-sign'></span></td>
|
||||||
<td>{% trans "Purchase Price" %}</td>
|
<td>{% trans "Purchase Price" %}</td>
|
||||||
@ -350,7 +350,12 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-industry'></span></td>
|
<td><span class='fas fa-industry'></span></td>
|
||||||
<td>{% trans "Manufacturer" %}</td>
|
<td>{% trans "Manufacturer" %}</td>
|
||||||
|
{% if item.supplier_part.manufacturer_part.manufacturer %}
|
||||||
<td><a href="{% url 'company-detail' item.supplier_part.manufacturer_part.manufacturer.id %}">{{ item.supplier_part.manufacturer_part.manufacturer.name }}</a></td>
|
<td><a href="{% url 'company-detail' item.supplier_part.manufacturer_part.manufacturer.id %}">{{ item.supplier_part.manufacturer_part.manufacturer.name }}</a></td>
|
||||||
|
{% else %}
|
||||||
|
<td><i>{% trans "No manufacturer set" %}</i></td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-hashtag'></span></td>
|
<td><span class='fas fa-hashtag'></span></td>
|
||||||
|
@ -34,6 +34,11 @@
|
|||||||
<td>{% trans "API Version" %}</td>
|
<td>{% trans "API Version" %}</td>
|
||||||
<td>{% inventree_api_version %}{% include "clip.html" %}</td>
|
<td>{% inventree_api_version %}{% include "clip.html" %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-hashtag'></span></td>
|
||||||
|
<td>{% trans "Python Version" %}</td>
|
||||||
|
<td>{% python_version %}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-hashtag'></span></td>
|
<td><span class='fas fa-hashtag'></span></td>
|
||||||
<td>{% trans "Django Version" %}</td>
|
<td>{% trans "Django Version" %}</td>
|
||||||
|
@ -126,7 +126,7 @@ function loadManufacturerPartTable(table, url, options) {
|
|||||||
queryParams: filters,
|
queryParams: filters,
|
||||||
name: 'manufacturerparts',
|
name: 'manufacturerparts',
|
||||||
groupBy: false,
|
groupBy: false,
|
||||||
formatNoMatches: function() { return "{% trans "No manufacturer parts found" %}"; },
|
formatNoMatches: function() { return '{% trans "No manufacturer parts found" %}'; },
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
checkbox: true,
|
checkbox: true,
|
||||||
@ -199,6 +199,107 @@ function loadManufacturerPartTable(table, url, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadManufacturerPartParameterTable(table, url, options) {
|
||||||
|
/*
|
||||||
|
* Load table of ManufacturerPartParameter objects
|
||||||
|
*/
|
||||||
|
|
||||||
|
var params = options.params || {};
|
||||||
|
|
||||||
|
// Load filters
|
||||||
|
var filters = loadTableFilters("manufacturer-part-parameters");
|
||||||
|
|
||||||
|
// Overwrite explicit parameters
|
||||||
|
for (var key in params) {
|
||||||
|
filters[key] = params[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupFilterList("manufacturer-part-parameters", $(table));
|
||||||
|
|
||||||
|
$(table).inventreeTable({
|
||||||
|
url: url,
|
||||||
|
method: 'get',
|
||||||
|
original: params,
|
||||||
|
queryParams: filters,
|
||||||
|
name: 'manufacturerpartparameters',
|
||||||
|
groupBy: false,
|
||||||
|
formatNoMatches: function() { return '{% trans "No parameters found" %}'; },
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
checkbox: true,
|
||||||
|
switchable: false,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '{% trans "Name" %}',
|
||||||
|
switchable: false,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'value',
|
||||||
|
title: '{% trans "Value" %}',
|
||||||
|
switchable: false,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'units',
|
||||||
|
title: '{% trans "Units" %}',
|
||||||
|
switchable: true,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
title: '',
|
||||||
|
switchable: false,
|
||||||
|
sortable: false,
|
||||||
|
formatter: function(value, row) {
|
||||||
|
|
||||||
|
var pk = row.pk;
|
||||||
|
|
||||||
|
var html = `<div class='btn-group float-right' role='group'>`;
|
||||||
|
|
||||||
|
html += makeIconButton('fa-edit icon-blue', 'button-parameter-edit', pk, '{% trans "Edit parameter" %}');
|
||||||
|
html += makeIconButton('fa-trash-alt icon-red', 'button-parameter-delete', pk, '{% trans "Delete parameter" %}');
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onPostBody: function() {
|
||||||
|
// Setup callback functions
|
||||||
|
$(table).find('.button-parameter-edit').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
launchModalForm(
|
||||||
|
`/manufacturer-part/parameter/${pk}/edit/`,
|
||||||
|
{
|
||||||
|
success: function() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
$(table).find('.button-parameter-delete').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
launchModalForm(
|
||||||
|
`/manufacturer-part/parameter/${pk}/delete/`,
|
||||||
|
{
|
||||||
|
success: function() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function loadSupplierPartTable(table, url, options) {
|
function loadSupplierPartTable(table, url, options) {
|
||||||
/*
|
/*
|
||||||
* Load supplier part table
|
* Load supplier part table
|
||||||
@ -224,7 +325,7 @@ function loadSupplierPartTable(table, url, options) {
|
|||||||
queryParams: filters,
|
queryParams: filters,
|
||||||
name: 'supplierparts',
|
name: 'supplierparts',
|
||||||
groupBy: false,
|
groupBy: false,
|
||||||
formatNoMatches: function() { return "{% trans "No supplier parts found" %}"; },
|
formatNoMatches: function() { return '{% trans "No supplier parts found" %}'; },
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
checkbox: true,
|
checkbox: true,
|
||||||
@ -260,7 +361,7 @@ function loadSupplierPartTable(table, url, options) {
|
|||||||
{
|
{
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: 'supplier',
|
field: 'supplier',
|
||||||
title: "{% trans "Supplier" %}",
|
title: '{% trans "Supplier" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
if (value) {
|
if (value) {
|
||||||
var name = row.supplier_detail.name;
|
var name = row.supplier_detail.name;
|
||||||
@ -276,7 +377,7 @@ function loadSupplierPartTable(table, url, options) {
|
|||||||
{
|
{
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: 'SKU',
|
field: 'SKU',
|
||||||
title: "{% trans "Supplier Part" %}",
|
title: '{% trans "Supplier Part" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
return renderLink(value, `/supplier-part/${row.pk}/`);
|
return renderLink(value, `/supplier-part/${row.pk}/`);
|
||||||
}
|
}
|
||||||
|
@ -447,6 +447,7 @@ function loadPartTable(table, url, options={}) {
|
|||||||
|
|
||||||
columns.push({
|
columns.push({
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
sortName: 'category',
|
||||||
field: 'category_detail',
|
field: 'category_detail',
|
||||||
title: '{% trans "Category" %}',
|
title: '{% trans "Category" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
@ -660,6 +660,27 @@ function loadStockTable(table, options) {
|
|||||||
title: '{% trans "Last Updated" %}',
|
title: '{% trans "Last Updated" %}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'purchase_order',
|
||||||
|
title: '{% trans "Purchase Order" %}',
|
||||||
|
formatter: function(value, row) {
|
||||||
|
if (!value) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
var link = `/order/purchase-order/${row.purchase_order}/`;
|
||||||
|
var text = `${row.purchase_order}`;
|
||||||
|
|
||||||
|
if (row.purchase_order_reference) {
|
||||||
|
|
||||||
|
var prefix = '{% settings_value "PURCHASEORDER_REFERENCE_PREFIX" %}';
|
||||||
|
|
||||||
|
text = prefix + row.purchase_order_reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderLink(text, link);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'purchase_price',
|
field: 'purchase_price',
|
||||||
title: '{% trans "Purchase Price" %}',
|
title: '{% trans "Purchase Price" %}',
|
||||||
|
@ -43,6 +43,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block pre_content_panels %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_panels %}
|
{% block content_panels %}
|
||||||
<div class='panel panel-default panel-inventree'>
|
<div class='panel panel-default panel-inventree'>
|
||||||
<div class='panel-heading'>
|
<div class='panel-heading'>
|
||||||
@ -63,6 +66,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block post_content_panels %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
|
@ -85,6 +85,7 @@ class RuleSet(models.Model):
|
|||||||
'part_partstar',
|
'part_partstar',
|
||||||
'company_supplierpart',
|
'company_supplierpart',
|
||||||
'company_manufacturerpart',
|
'company_manufacturerpart',
|
||||||
|
'company_manufacturerpartparameter',
|
||||||
],
|
],
|
||||||
'stock_location': [
|
'stock_location': [
|
||||||
'stock_stocklocation',
|
'stock_stocklocation',
|
||||||
@ -116,6 +117,8 @@ class RuleSet(models.Model):
|
|||||||
'order_purchaseorderattachment',
|
'order_purchaseorderattachment',
|
||||||
'order_purchaseorderlineitem',
|
'order_purchaseorderlineitem',
|
||||||
'company_supplierpart',
|
'company_supplierpart',
|
||||||
|
'company_manufacturerpart',
|
||||||
|
'company_manufacturerpartparameter',
|
||||||
],
|
],
|
||||||
'sales_order': [
|
'sales_order': [
|
||||||
'company_company',
|
'company_company',
|
||||||
|
23
RELEASE.md
Normal file
23
RELEASE.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
## Release Checklist
|
||||||
|
|
||||||
|
Checklist of steps to perform at each code release
|
||||||
|
|
||||||
|
### Update Version String
|
||||||
|
|
||||||
|
Update `INVENTREE_SW_VERSION` in [version.py](https://github.com/inventree/InvenTree/blob/master/InvenTree/InvenTree/version.py)
|
||||||
|
|
||||||
|
### Increment API Version
|
||||||
|
|
||||||
|
If the API has changed, ensure that the API version number is incremented.
|
||||||
|
|
||||||
|
### Translation Files
|
||||||
|
|
||||||
|
Merge the crowdin translation updates into master branch
|
||||||
|
|
||||||
|
### Documentation Release
|
||||||
|
|
||||||
|
Create new release for the [inventree documentation](https://github.com/inventree/inventree-docs)
|
||||||
|
|
||||||
|
### Python Library Release
|
||||||
|
|
||||||
|
Create new release for the [inventree python library](https://github.com/inventree/inventree-python)
|
18
tasks.py
18
tasks.py
@ -251,12 +251,15 @@ def content_excludes():
|
|||||||
"contenttypes",
|
"contenttypes",
|
||||||
"sessions.session",
|
"sessions.session",
|
||||||
"auth.permission",
|
"auth.permission",
|
||||||
|
"authtoken.token",
|
||||||
"error_report.error",
|
"error_report.error",
|
||||||
"admin.logentry",
|
"admin.logentry",
|
||||||
"django_q.schedule",
|
"django_q.schedule",
|
||||||
"django_q.task",
|
"django_q.task",
|
||||||
"django_q.ormq",
|
"django_q.ormq",
|
||||||
"users.owner",
|
"users.owner",
|
||||||
|
"exchange.rate",
|
||||||
|
"exchange.exchangebackend",
|
||||||
]
|
]
|
||||||
|
|
||||||
output = ""
|
output = ""
|
||||||
@ -362,6 +365,21 @@ def import_records(c, filename='data.json'):
|
|||||||
|
|
||||||
print("Data import completed")
|
print("Data import completed")
|
||||||
|
|
||||||
|
|
||||||
|
@task
|
||||||
|
def delete_data(c, force=False):
|
||||||
|
"""
|
||||||
|
Delete all database records!
|
||||||
|
|
||||||
|
Warning: This will REALLY delete all records in the database!!
|
||||||
|
"""
|
||||||
|
|
||||||
|
if force:
|
||||||
|
manage(c, 'flush --noinput')
|
||||||
|
else:
|
||||||
|
manage(c, 'flush')
|
||||||
|
|
||||||
|
|
||||||
@task(post=[rebuild])
|
@task(post=[rebuild])
|
||||||
def import_fixtures(c):
|
def import_fixtures(c):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user