diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 990c25bd67..06bf758b84 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -7,6 +7,7 @@ from rest_framework.exceptions import ValidationError from django.db.models.signals import pre_delete from django.dispatch import receiver + class Company(models.Model): """ Abstract model representing an external company """ @@ -89,6 +90,10 @@ class InvenTreeTree(models.Model): return unique + @property + def has_children(self): + return self.children.count() > 0 + @property def children(self): contents = ContentType.objects.get_for_model(type(self)) @@ -185,7 +190,7 @@ class InvenTreeTree(models.Model): @receiver(pre_delete, sender=InvenTreeTree, dispatch_uid='tree_pre_delete_log') -def before_delete_tree_item(sender, intance, using, **kwargs): +def before_delete_tree_item(sender, instance, using, **kwargs): # Update each tree item below this one for child in instance.children.all(): diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index f5f5802278..bc458b112d 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -36,6 +36,7 @@ INSTALLED_APPS = [ 'rest_framework', 'simple_history', 'crispy_forms', + 'import_export', # Core django modules 'django.contrib.admin', @@ -47,9 +48,8 @@ INSTALLED_APPS = [ # InvenTree apps 'part.apps.PartConfig', - 'supplier.apps.SupplierConfig', 'stock.apps.StockConfig', - 'track.apps.TrackConfig', + 'supplier.apps.SupplierConfig', ] MIDDLEWARE = [ @@ -68,7 +68,7 @@ ROOT_URLCONF = 'InvenTree.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], + 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -81,6 +81,8 @@ TEMPLATES = [ }, ] +print(os.path.join(BASE_DIR, 'templates')) + REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'InvenTree.utils.api_exception_handler' } @@ -145,4 +147,8 @@ MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +# crispy forms use the bootstrap templates CRISPY_TEMPLATE_PACK = 'bootstrap' + +# Use database transactions when importing / exporting data +IMPORT_EXPORT_USE_TRANSACTIONS = True diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 8c20fe9282..18eb9784ec 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -1,17 +1,14 @@ from django.conf.urls import url, include from django.contrib import admin -from rest_framework.documentation import include_docs_urls - from part.urls import part_api_urls, part_cat_api_urls from part.urls import bom_api_urls from part.urls import part_urls - from stock.urls import stock_api_urls, stock_api_loc_urls from stock.urls import stock_urls -#from supplier.urls import supplier_api_urls, supplier_api_part_urls +# from supplier.urls import supplier_api_urls, supplier_api_part_urls from supplier.urls import supplier_urls from django.conf import settings @@ -19,10 +16,8 @@ from django.conf.urls.static import static from django.views.generic.base import RedirectView -from track.urls import tracking_urls - -#from project.urls import prj_urls, prj_part_urls, prj_cat_urls, prj_run_urls -#from track.urls import unique_urls, part_track_urls +# from project.urls import prj_urls, prj_part_urls, prj_cat_urls, prj_run_urls +# from track.urls import unique_urls, part_track_urls from users.urls import user_urls @@ -37,28 +32,28 @@ apipatterns = [ # Part URLs url(r'^part/', include(part_api_urls)), url(r'^part-category/', include(part_cat_api_urls)), - #url(r'^part-param/', include(part_param_urls)), - #url(r'^part-param-template/', include(part_param_template_urls)), + # url(r'^part-param/', include(part_param_urls)), + # url(r'^part-param-template/', include(part_param_template_urls)), # Part BOM URLs url(r'^bom/', include(bom_api_urls)), # Supplier URLs - #url(r'^supplier/', include(supplier_api_urls)), - #url(r'^supplier-part/', include(supplier_api_part_urls)), - #url(r'^price-break/', include(price_break_urls)), - #url(r'^manufacturer/', include(manu_urls)), - #url(r'^customer/', include(cust_urls)), + # url(r'^supplier/', include(supplier_api_urls)), + # url(r'^supplier-part/', include(supplier_api_part_urls)), + # url(r'^price-break/', include(price_break_urls)), + # url(r'^manufacturer/', include(manu_urls)), + # url(r'^customer/', include(cust_urls)), # Tracking URLs - #url(r'^track/', include(part_track_urls)), - #url(r'^unique-part/', include(unique_urls)), + # url(r'^track/', include(part_track_urls)), + # url(r'^unique-part/', include(unique_urls)), # Project URLs - #url(r'^project/', include(prj_urls)), - #url(r'^project-category/', include(prj_cat_urls)), - #url(r'^project-part/', include(prj_part_urls)), - #url(r'^project-run/', include(prj_run_urls)), + # url(r'^project/', include(prj_urls)), + # url(r'^project-category/', include(prj_cat_urls)), + # url(r'^project-part/', include(prj_part_urls)), + # url(r'^project-run/', include(prj_run_urls)), # User URLs url(r'^user/', include(user_urls)), @@ -67,13 +62,12 @@ apipatterns = [ urlpatterns = [ # API URL - #url(r'^api/', include(apipatterns)), - #url(r'^api-doc/', include_docs_urls(title='InvenTree API')), + # url(r'^api/', include(apipatterns)), + # url(r'^api-doc/', include_docs_urls(title='InvenTree API')), url(r'^part/', include(part_urls)), url(r'^stock/', include(stock_urls)), url(r'^supplier/', include(supplier_urls)), - url(r'^track/', include(tracking_urls)), url(r'^admin/', admin.site.urls), url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), @@ -87,4 +81,4 @@ if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # Send any unknown URLs to the parts page -urlpatterns += [url(r'^.*$', RedirectView.as_view(url='part/', permanent=False), name='part-index')] \ No newline at end of file +urlpatterns += [url(r'^.*$', RedirectView.as_view(url='/part/', permanent=False), name='part-index')] diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index 2e6da3a4d8..d1c025c6fb 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -1,24 +1,29 @@ from django.contrib import admin +from import_export.admin import ImportExportModelAdmin from .models import PartCategory, Part from .models import BomItem from .models import PartAttachment -class PartAdmin(admin.ModelAdmin): - list_display = ('name', 'IPN', 'description', 'stock', 'category') +class PartAdmin(ImportExportModelAdmin): + + list_display = ('name', 'IPN', 'description', 'total_stock', 'category') class PartCategoryAdmin(admin.ModelAdmin): list_display = ('name', 'pathstring', 'description') -class BomItemAdmin(admin.ModelAdmin): - list_display=('part', 'sub_part', 'quantity') + +class BomItemAdmin(ImportExportModelAdmin): + list_display = ('part', 'sub_part', 'quantity') + class PartAttachmentAdmin(admin.ModelAdmin): list_display = ('part', 'attachment') + """ class ParameterTemplateAdmin(admin.ModelAdmin): list_display = ('name', 'units', 'format') @@ -33,6 +38,6 @@ admin.site.register(PartCategory, PartCategoryAdmin) admin.site.register(BomItem, BomItemAdmin) admin.site.register(PartAttachment, PartAttachmentAdmin) -#admin.site.register(PartParameter, ParameterAdmin) -#admin.site.register(PartParameterTemplate, ParameterTemplateAdmin) -#admin.site.register(CategoryParameterLink) +# admin.site.register(PartParameter, ParameterAdmin) +# admin.site.register(PartParameterTemplate, ParameterTemplateAdmin) +# admin.site.register(CategoryParameterLink) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 417a151a72..64b82d97a1 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -8,6 +8,9 @@ from django_filters.rest_framework import FilterSet, DjangoFilterBackend from .models import PartCategory, Part, BomItem +from InvenTree.models import FilterChildren + + class PartDetail(generics.RetrieveUpdateDestroyAPIView): """ @@ -69,6 +72,7 @@ class PartParamDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly,) """ + class PartFilter(FilterSet): class Meta: @@ -174,6 +178,7 @@ class PartTemplateList(generics.ListCreateAPIView): """ + class BomItemDetail(generics.RetrieveUpdateDestroyAPIView): queryset = BomItem.objects.all() @@ -190,9 +195,6 @@ class BomItemFilter(FilterSet): class BomItemList(generics.ListCreateAPIView): - #def get_queryset(self): - # params = self.request. - queryset = BomItem.objects.all() serializer_class = BomItemSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 2042ce4aff..f986e1a0ae 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -2,7 +2,7 @@ from django import forms from crispy_forms.helper import FormHelper from crispy_forms.layout import Submit -from .models import Part, PartCategory +from .models import Part, PartCategory, BomItem class EditPartForm(forms.ModelForm): @@ -12,9 +12,7 @@ class EditPartForm(forms.ModelForm): self.helper = FormHelper() self.helper.form_id = 'id-edit-part-form' - #self.helper.form_class = 'blueForms' self.helper.form_method = 'post' - #self.helper.form_action = 'submit' self.helper.add_input(Submit('submit', 'Submit')) @@ -27,7 +25,9 @@ class EditPartForm(forms.ModelForm): 'IPN', 'URL', 'minimum_stock', + 'buildable', 'trackable', + 'purchaseable', ] @@ -38,9 +38,7 @@ class EditCategoryForm(forms.ModelForm): self.helper = FormHelper() self.helper.form_id = 'id-edit-part-form' - #self.helper.form_class = 'blueForms' self.helper.form_method = 'post' - #self.helper.form_action = 'submit' self.helper.add_input(Submit('submit', 'Submit')) @@ -50,4 +48,24 @@ class EditCategoryForm(forms.ModelForm): 'parent', 'name', 'description' - ] \ No newline at end of file + ] + + +class EditBomItemForm(forms.ModelForm): + + def __init__(self, *args, **kwargs): + super(EditBomItemForm, self).__init__(*args, **kwargs) + self.helper = FormHelper() + + self.helper.form_id = 'id-edit-part-form' + self.helper.form_method = 'post' + + self.helper.add_input(Submit('submit', 'Submit')) + + class Meta: + model = BomItem + fields = [ + 'part', + 'sub_part', + 'quantity' + ] diff --git a/InvenTree/part/migrations/0017_part_purchaseable.py b/InvenTree/part/migrations/0017_part_purchaseable.py new file mode 100644 index 0000000000..b3f6860a38 --- /dev/null +++ b/InvenTree/part/migrations/0017_part_purchaseable.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-15 14:21 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0016_auto_20180415_0316'), + ] + + operations = [ + migrations.AddField( + model_name='part', + name='purchaseable', + field=models.BooleanField(default=True), + ), + ] diff --git a/InvenTree/part/migrations/0018_part_buildable.py b/InvenTree/part/migrations/0018_part_buildable.py new file mode 100644 index 0000000000..8c4ca8d631 --- /dev/null +++ b/InvenTree/part/migrations/0018_part_buildable.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-16 12:08 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0017_part_purchaseable'), + ] + + operations = [ + migrations.AddField( + model_name='part', + name='buildable', + field=models.BooleanField(default=False), + ), + ] diff --git a/InvenTree/part/migrations/0019_auto_20180416_1249.py b/InvenTree/part/migrations/0019_auto_20180416_1249.py new file mode 100644 index 0000000000..fb2717a7b6 --- /dev/null +++ b/InvenTree/part/migrations/0019_auto_20180416_1249.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-16 12:49 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0018_part_buildable'), + ] + + operations = [ + migrations.AlterField( + model_name='part', + name='IPN', + field=models.CharField(blank=True, help_text='Internal Part Number', max_length=100), + ), + migrations.AlterField( + model_name='part', + name='URL', + field=models.URLField(blank=True, help_text='Link to extenal URL'), + ), + migrations.AlterField( + model_name='part', + name='buildable', + field=models.BooleanField(default=False, help_text='Can this part be built from other parts?'), + ), + migrations.AlterField( + model_name='part', + name='category', + field=models.ForeignKey(blank=True, help_text='Part category', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='parts', to='part.PartCategory'), + ), + migrations.AlterField( + model_name='part', + name='description', + field=models.CharField(help_text='Part description', max_length=250), + ), + migrations.AlterField( + model_name='part', + name='minimum_stock', + field=models.PositiveIntegerField(default=0, help_text='Minimum allowed stock level', validators=[django.core.validators.MinValueValidator(0)]), + ), + migrations.AlterField( + model_name='part', + name='name', + field=models.CharField(help_text='Part name (must be unique)', max_length=100, unique=True), + ), + migrations.AlterField( + model_name='part', + name='purchaseable', + field=models.BooleanField(default=True, help_text='Can this part be purchased from external suppliers?'), + ), + migrations.AlterField( + model_name='part', + name='trackable', + field=models.BooleanField(default=False, help_text='Does this part have tracking for unique items?'), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 9064cd4b40..3d2fe90628 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -from django.utils.translation import ugettext as _ from django.db import models from django.db.models import Sum from django.core.validators import MinValueValidator @@ -23,7 +22,6 @@ class PartCategory(InvenTreeTree): verbose_name = "Part Category" verbose_name_plural = "Part Categories" - @property def partcount(self): """ Return the total part count under this category @@ -37,11 +35,10 @@ class PartCategory(InvenTreeTree): return count - """ @property - def parts(self): - return self.part_set.all() - """ + def has_parts(self): + return self.parts.count() > 0 + @receiver(pre_delete, sender=PartCategory, dispatch_uid='partcategory_delete_log') def before_delete_part_category(sender, instance, using, **kwargs): @@ -85,36 +82,44 @@ class Part(models.Model): return '/part/{id}/'.format(id=self.id) # Short name of the part - name = models.CharField(max_length=100, unique=True) + name = models.CharField(max_length=100, unique=True, help_text='Part name (must be unique)') # Longer description of the part (optional) - description = models.CharField(max_length=250) + description = models.CharField(max_length=250, help_text='Part description') # Internal Part Number (optional) # Potentially multiple parts map to the same internal IPN (variants?) # So this does not have to be unique - IPN = models.CharField(max_length=100, blank=True) + IPN = models.CharField(max_length=100, blank=True, help_text='Internal Part Number') # Provide a URL for an external link - URL = models.URLField(blank=True) + URL = models.URLField(blank=True, help_text='Link to extenal URL') # Part category - all parts must be assigned to a category category = models.ForeignKey(PartCategory, related_name='parts', null=True, blank=True, - on_delete=models.DO_NOTHING) + on_delete=models.DO_NOTHING, + help_text='Part category') image = models.ImageField(upload_to=rename_part_image, max_length=255, null=True, blank=True) # Minimum "allowed" stock level - minimum_stock = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0)]) + minimum_stock = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0)], help_text='Minimum allowed stock level') # Units of quantity for this part. Default is "pcs" units = models.CharField(max_length=20, default="pcs", blank=True) + # Can this part be built? + buildable = models.BooleanField(default=False, help_text='Can this part be built from other parts?') + # Is this part "trackable"? - # Trackable parts can have unique instances which are assigned serial numbers + # Trackable parts can have unique instances + # which are assigned serial numbers (or batch numbers) # and can have their movements tracked - trackable = models.BooleanField(default=False) + trackable = models.BooleanField(default=False, help_text='Does this part have tracking for unique items?') + + # Is this part "purchaseable"? + purchaseable = models.BooleanField(default=True, help_text='Can this part be purchased from external suppliers?') def __str__(self): if self.IPN: @@ -127,10 +132,41 @@ class Part(models.Model): class Meta: verbose_name = "Part" verbose_name_plural = "Parts" - #unique_together = (("name", "category"),) @property - def stock(self): + def available_stock(self): + """ + Return the total available stock. + This subtracts stock which is already allocated + """ + + # TODO - For now, just return total stock count + # TODO - In future must take account of allocated stock + return self.total_stock + + @property + def can_build(self): + """ Return the number of units that can be build with available stock + """ + + # If this part does NOT have a BOM, result is simply the currently available stock + if not self.has_bom: + return self.available_stock + + total = None + + # Calculate the minimum number of parts that can be built using each sub-part + for item in self.bom_items.all(): + stock = item.sub_part.available_stock + n = int(1.0 * stock / item.quantity) + + if total is None or n < total: + total = n + + return total + + @property + def total_stock(self): """ Return the total stock quantity for this part. Part may be stored in multiple locations """ @@ -143,13 +179,21 @@ class Part(models.Model): return result['total'] @property - def bomItemCount(self): - return self.bom_items.all().count() - + def has_bom(self): + return self.bom_count > 0 @property - def usedInCount(self): - return self.used_in.all().count() + def bom_count(self): + return self.bom_items.count() + + @property + def used_in_count(self): + return self.used_in.count() + + @property + def supplier_count(self): + # Return the number of supplier parts available for this part + return self.supplier_parts.count() """ @property @@ -171,9 +215,10 @@ class Part(models.Model): return projects """ + def attach_file(instance, filename): - base='part_files' + base = 'part_files' # TODO - For a new PartAttachment object, PK is NULL!! @@ -182,25 +227,27 @@ def attach_file(instance, filename): return os.path.join(base, fn) + class PartAttachment(models.Model): """ A PartAttachment links a file to a part Parts can have multiple files such as datasheets, etc """ - part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='attachments') attachment = models.FileField(upload_to=attach_file, null=True, blank=True) - class BomItem(models.Model): """ A BomItem links a part to its component items. A part can have a BOM (bill of materials) which defines which parts are required (and in what quatity) to make it """ + def get_absolute_url(self): + return '/part/bom/{id}/'.format(id=self.id) + # A link to the parent part # Each part will get a reverse lookup field 'bom_items' part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='bom_items') @@ -212,7 +259,6 @@ class BomItem(models.Model): # Quantity required quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)]) - class Meta: verbose_name = "BOM Item" diff --git a/InvenTree/part/param_todo.py b/InvenTree/part/param_todo.py_todo similarity index 100% rename from InvenTree/part/param_todo.py rename to InvenTree/part/param_todo.py_todo diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index c4202b2bee..80f16b13d0 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -3,6 +3,7 @@ from rest_framework import serializers from .models import Part, PartCategory from .models import BomItem + class BomItemSerializer(serializers.ModelSerializer): class Meta: @@ -12,6 +13,7 @@ class BomItemSerializer(serializers.ModelSerializer): 'sub_part', 'quantity') + """ class PartParameterSerializer(serializers.HyperlinkedModelSerializer): " Serializer for a PartParameter @@ -27,7 +29,7 @@ class PartParameterSerializer(serializers.HyperlinkedModelSerializer): 'units') """ -#class PartSerializer(serializers.HyperlinkedModelSerializer): + class PartSerializer(serializers.ModelSerializer): """ Serializer for complete detail information of a part. Used when displaying all details of a single component. @@ -56,6 +58,7 @@ class PartCategorySerializer(serializers.HyperlinkedModelSerializer): 'parent', 'pathstring') + """ class PartTemplateSerializer(serializers.HyperlinkedModelSerializer): @@ -65,4 +68,4 @@ class PartTemplateSerializer(serializers.HyperlinkedModelSerializer): 'name', 'units', 'format') -""" \ No newline at end of file +""" diff --git a/InvenTree/part/templates/delete_obj.html b/InvenTree/part/templates/delete_obj.html index 2b85af7061..3cf123330d 100644 --- a/InvenTree/part/templates/delete_obj.html +++ b/InvenTree/part/templates/delete_obj.html @@ -12,7 +12,6 @@ Deletion title goes here

This is a permanent action and cannot be undone.

{% block del_body %} -Deletion body goes here {% endblock %}
{% csrf_token %} diff --git a/InvenTree/part/templates/navbar.html b/InvenTree/part/templates/navbar.html index b71ddf4ffc..db8cae6aa7 100644 --- a/InvenTree/part/templates/navbar.html +++ b/InvenTree/part/templates/navbar.html @@ -9,7 +9,6 @@
  • Parts
  • Stock
  • Suppliers
  • -
  • Tracking
  • \ No newline at end of file diff --git a/InvenTree/part/templates/part/bom-create.html b/InvenTree/part/templates/part/bom-create.html new file mode 100644 index 0000000000..7db3161c84 --- /dev/null +++ b/InvenTree/part/templates/part/bom-create.html @@ -0,0 +1,5 @@ +{% extends 'create_edit_obj.html' %} + +{% block obj_title %} +Create a new BOM item +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/bom-delete.html b/InvenTree/part/templates/part/bom-delete.html new file mode 100644 index 0000000000..4cf9c8524e --- /dev/null +++ b/InvenTree/part/templates/part/bom-delete.html @@ -0,0 +1,15 @@ +{% extends "delete_obj.html" %} + +{% block del_title %} + Are you sure you want to delete this BOM item? +{% endblock %} + +{% block del_body %} + Deleting this entry will remove the BOM row from the following part: + + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/bom-detail.html b/InvenTree/part/templates/part/bom-detail.html new file mode 100644 index 0000000000..032cf03123 --- /dev/null +++ b/InvenTree/part/templates/part/bom-detail.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block content %} + +

    BOM Item

    + + + + +
    Parent{{ item.part.name }}
    Child{{ item.sub_part.name }}
    Quantity{{ item.quantity }}
    + +
    + + + +
    + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/bom-edit.html b/InvenTree/part/templates/part/bom-edit.html new file mode 100644 index 0000000000..91a503004c --- /dev/null +++ b/InvenTree/part/templates/part/bom-edit.html @@ -0,0 +1,5 @@ +{% extends 'create_edit_obj.html' %} + +{% block obj_title %} +Edit details for BOM item +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index f3db81216f..0e9e28b6b1 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -4,21 +4,31 @@ {% include 'part/tabs.html' with tab='bom' %} +

    Bill of Materials

    + + {% for bom_item in part.bom_items.all %} {% with sub_part=bom_item.sub_part %} - + + {% endwith %} {% endfor %}
    Part Description QuantityEdit
    {{ sub_part.name }} {{ sub_part.description }}{{ bom_item.quantity }}{{ bom_item.quantity }}Edit
    +
    + + + +
    + {% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/build.html b/InvenTree/part/templates/part/build.html new file mode 100644 index 0000000000..0c90630bc1 --- /dev/null +++ b/InvenTree/part/templates/part/build.html @@ -0,0 +1,13 @@ +{% extends "part/part_base.html" %} + +{% block details %} + +{% include 'part/tabs.html' with tab='build' %} + +

    Build Part

    + +TODO +

    +You can build {{ part.can_build }} of this part with current stock. + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/category_delete.html b/InvenTree/part/templates/part/category_delete.html index 960edbbaa1..7814317356 100644 --- a/InvenTree/part/templates/part/category_delete.html +++ b/InvenTree/part/templates/part/category_delete.html @@ -18,7 +18,7 @@ Are you sure you want to delete category '{{ category.name }}'? {% endif %} @@ -33,7 +33,7 @@ Are you sure you want to delete category '{{ category.name }}'?

    {% endif %} diff --git a/InvenTree/part/templates/part/category_detail.html b/InvenTree/part/templates/part/category_detail.html index ed9fc8f996..7e91c82a59 100644 --- a/InvenTree/part/templates/part/category_detail.html +++ b/InvenTree/part/templates/part/category_detail.html @@ -11,9 +11,15 @@ {{ category.description }}

    +{% if category.has_children %} +

    Subcategories

    {% include "part/category_subcategories.html" with children=category.children.all %} +{% endif %} +{% if category.has_parts %} +

    Parts

    {% include "part/category_parts.html" with parts=category.parts.all %} +{% endif %}
    diff --git a/InvenTree/part/templates/part/category_parts.html b/InvenTree/part/templates/part/category_parts.html index b6b5a8578d..6b5321e476 100644 --- a/InvenTree/part/templates/part/category_parts.html +++ b/InvenTree/part/templates/part/category_parts.html @@ -1,5 +1,3 @@ -{% if parts|length > 0 %} -Parts: @@ -11,5 +9,4 @@ Parts: {% endfor %} -
    Part{{ part.description }}
    -{% endif %} \ No newline at end of file + \ No newline at end of file diff --git a/InvenTree/part/templates/part/category_subcategories.html b/InvenTree/part/templates/part/category_subcategories.html index b6bd9ee6e4..6b7f6e56a2 100644 --- a/InvenTree/part/templates/part/category_subcategories.html +++ b/InvenTree/part/templates/part/category_subcategories.html @@ -1,5 +1,3 @@ -{% if children|length > 0 %} -Subcategories:
      {% for child in children %}
    • @@ -10,5 +8,4 @@ Subcategories: {{ child.partcount }}
    • {% endfor %} -
    -{% endif %} \ No newline at end of file + \ No newline at end of file diff --git a/InvenTree/part/templates/part/delete.html b/InvenTree/part/templates/part/delete.html index df28dc82d8..5e536508d9 100644 --- a/InvenTree/part/templates/part/delete.html +++ b/InvenTree/part/templates/part/delete.html @@ -7,8 +7,8 @@ {% block del_body %} - {% if part.usedInCount > 0 %} -

    This part is used in BOMs for {{ part.usedInCount }} other parts. If you delete this part, the BOMs for the following parts will be updated: + {% if part.used_in_count %} +

    This part is used in BOMs for {{ part.used_in_count }} other parts. If you delete this part, the BOMs for the following parts will be updated: