From 58612969744868b672da2ef2a9b386ccd8b53525 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 15 Apr 2018 21:29:24 +1000 Subject: [PATCH 01/18] Add view for BomItem model - Create BOM item (auto-add to a parent part) - Edit / delete - View details --- InvenTree/part/forms.py | 24 ++++++++- InvenTree/part/models.py | 3 ++ InvenTree/part/templates/part/bom-create.html | 5 ++ InvenTree/part/templates/part/bom-delete.html | 15 ++++++ InvenTree/part/templates/part/bom-detail.html | 18 +++++++ InvenTree/part/templates/part/bom-edit.html | 5 ++ InvenTree/part/templates/part/bom.html | 6 +++ InvenTree/part/templates/part/used_in.html | 2 +- InvenTree/part/urls.py | 14 +++++- InvenTree/part/views.py | 49 ++++++++++++++++++- 10 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 InvenTree/part/templates/part/bom-create.html create mode 100644 InvenTree/part/templates/part/bom-delete.html create mode 100644 InvenTree/part/templates/part/bom-detail.html create mode 100644 InvenTree/part/templates/part/bom-edit.html diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 2042ce4aff..379682e3dd 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): @@ -50,4 +50,26 @@ class EditCategoryForm(forms.ModelForm): 'parent', 'name', 'description' + ] + + +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_class = 'blueForms' + self.helper.form_method = 'post' + #self.helper.form_action = 'submit' + + self.helper.add_input(Submit('submit', 'Submit')) + + class Meta: + model = BomItem + fields = [ + 'part', + 'sub_part', + 'quantity' ] \ No newline at end of file diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 9064cd4b40..f08f8da4d8 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -201,6 +201,9 @@ class BomItem(models.Model): 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') 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..93eb5c2e18 --- /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..24046b5ca4 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -21,4 +21,10 @@ {% endfor %} +
+ + + +
+ {% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/used_in.html b/InvenTree/part/templates/part/used_in.html index 1168f0c26b..2b3f55ef19 100644 --- a/InvenTree/part/templates/part/used_in.html +++ b/InvenTree/part/templates/part/used_in.html @@ -13,7 +13,7 @@ This part is used to make the following parts: {% for item in part.used_in.all %} - {{ item.part.name }} + {{ item.part.name }} {{ item.part.description }} {% endfor %} diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index c3971aab91..c98cba695b 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -57,6 +57,13 @@ part_category_urls = [ url('^.*$', views.CategoryDetail.as_view(), name='category-detail'), ] +part_bom_urls = [ + url(r'^edit/?', views.BomItemEdit.as_view(), name='bom-item-edit'), + url('^delete/?', views.BomItemDelete.as_view(), name='bom-item-delete'), + + url(r'^.*$', views.BomItemDetail.as_view(), name='bom-item-detail'), +] + # URL list for part web interface part_urls = [ @@ -66,12 +73,17 @@ part_urls = [ # Create a new part url(r'^new/?', views.PartCreate.as_view(), name='part-create'), - # Individual + # Create a new BOM item + url(r'^bom/new/?', views.BomItemCreate.as_view(), name='bom-item-create'), + + # Individual part url(r'^(?P\d+)/', include(part_detail_urls)), # Part category url(r'^category/(?P\d+)/', include(part_category_urls)), + url(r'^bom/(?P\d+)/', include(part_bom_urls)), + # Top level part list (display top level parts and categories) url('', views.PartIndex.as_view(), name='part-index'), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index e0022d2593..5ade502688 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1,5 +1,6 @@ from InvenTree.models import FilterChildren -from .models import PartCategory, Part + +from .models import PartCategory, Part, BomItem from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect @@ -8,7 +9,7 @@ from django.urls import reverse from django.views.generic import DetailView, ListView from django.views.generic.edit import UpdateView, DeleteView, CreateView -from .forms import EditPartForm, EditCategoryForm +from .forms import EditPartForm, EditCategoryForm, EditBomItemForm class PartIndex(ListView): model = Part @@ -146,3 +147,47 @@ class CategoryCreate(CreateView): initials['parent'] = get_object_or_404(PartCategory, pk=parent_id) return initials + + +class BomItemDetail(DetailView): + context_object_name ='item' + queryset = BomItem.objects.all() + template_name = 'part/bom-detail.html' + + +class BomItemCreate(CreateView): + model = BomItem + form_class = EditBomItemForm + template_name = 'part/bom-create.html' + + def get_initial(self): + # Look for initial values + initials = super(BomItemCreate, self).get_initial().copy() + + # Parent part for this item? + parent_id = self.request.GET.get('parent', None) + + if parent_id: + initials['part'] = get_object_or_404(Part, pk=parent_id) + + return initials + + +class BomItemEdit(UpdateView): + model = BomItem + form_class = EditBomItemForm + template_name = 'part/bom-edit.html' + + +class BomItemDelete(DeleteView): + model = BomItem + template_name = 'part/bom-delete.html' + context_object_name = 'item' + + success_url = '/part' + + def post(self, request, *args, **kwargs): + if 'confirm' in request.POST: + return super(BomItemDelete, self).post(request, *args, **kwargs) + else: + return HttpResponseRedirect(self.get_object().get_absolute_url()) From 3c844fc77f2ed34b96321dd311e0cae1aaacda17 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 15 Apr 2018 21:47:20 +1000 Subject: [PATCH 02/18] Redirect some links --- InvenTree/part/templates/part/bom-detail.html | 4 ++-- InvenTree/static/css/inventree.css | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/InvenTree/part/templates/part/bom-detail.html b/InvenTree/part/templates/part/bom-detail.html index 93eb5c2e18..032cf03123 100644 --- a/InvenTree/part/templates/part/bom-detail.html +++ b/InvenTree/part/templates/part/bom-detail.html @@ -4,8 +4,8 @@

BOM Item

- - + +
Parent{{ item.part.name }}
Child{{ item.sub_part.name }}
Parent{{ item.part.name }}
Child{{ item.sub_part.name }}
Quantity{{ item.quantity }}
diff --git a/InvenTree/static/css/inventree.css b/InvenTree/static/css/inventree.css index dca981fc48..25c8a49b9f 100644 --- a/InvenTree/static/css/inventree.css +++ b/InvenTree/static/css/inventree.css @@ -7,6 +7,7 @@ background-color: #777; color: #fff; border-radius: 5px; + margin-left: 10px; } .part-thumb { From 55b533d3efcdc98e275137c20113a89819650b58 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 15 Apr 2018 23:27:56 +1000 Subject: [PATCH 03/18] Added edit views for stock - StockItem - StockLocation --- InvenTree/InvenTree/settings.py | 4 +- InvenTree/part/models.py | 1 - .../part/templates/part/category_delete.html | 4 +- InvenTree/part/templates/part/stock.html | 12 ++++- InvenTree/part/templates/part/supplier.html | 4 +- InvenTree/stock/models.py | 12 +++++ InvenTree/stock/templates/stock/item.html | 13 +++++- .../stock/templates/stock/item_create.html | 5 +++ .../stock/templates/stock/item_delete.html | 9 ++++ .../stock/templates/stock/item_edit.html | 5 +++ InvenTree/stock/templates/stock/location.html | 18 ++++++++ .../templates/stock/location_create.html | 5 +++ .../templates/stock/location_delete.html | 41 +++++++++++++++++ .../stock/templates/stock/location_edit.html | 5 +++ .../stock/templates/stock/stock_table.html | 2 +- InvenTree/stock/urls.py | 4 +- InvenTree/stock/views.py | 44 +++++++++++++++---- .../templates/supplier/partdetail.html | 2 +- 18 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 InvenTree/stock/templates/stock/item_create.html create mode 100644 InvenTree/stock/templates/stock/item_delete.html create mode 100644 InvenTree/stock/templates/stock/item_edit.html create mode 100644 InvenTree/stock/templates/stock/location_create.html create mode 100644 InvenTree/stock/templates/stock/location_delete.html create mode 100644 InvenTree/stock/templates/stock/location_edit.html diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index f5f5802278..c8a2be70c9 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -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' } diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index f08f8da4d8..4c2b8967f5 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -23,7 +23,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 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 }}'?
    {% for cat in category.children.all %} -
  • {{ cat.name }} - {{ cat.description }}
  • +
  • {{ cat.name }} - {{ cat.description }}
  • {% endfor %}
{% endif %} @@ -33,7 +33,7 @@ Are you sure you want to delete category '{{ category.name }}'?

    {% for part in category.parts.all %} -
  • {{ part.name }} - {{ part.description }}
  • +
  • {{ part.name }} - {{ part.description }}
  • {% endfor %}
{% endif %} diff --git a/InvenTree/part/templates/part/stock.html b/InvenTree/part/templates/part/stock.html index 49fbb9736a..825608b73f 100644 --- a/InvenTree/part/templates/part/stock.html +++ b/InvenTree/part/templates/part/stock.html @@ -10,6 +10,7 @@ Total in stock: {{ part.stock }} + @@ -18,8 +19,9 @@ Total in stock: {{ part.stock }} {% for stock in part.locations.all %} + - + {% endfor %} -
Link Quantity Location Supplier part
Click {{ stock.quantity }}{{ stock.location.name }}{{ stock.location.name }} {% if stock.supplier_part %} @@ -31,6 +33,12 @@ Total in stock: {{ part.stock }} {{ stock.notes }}
+ + + + + {% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/supplier.html b/InvenTree/part/templates/part/supplier.html index e6a26c3781..425c98b3f3 100644 --- a/InvenTree/part/templates/part/supplier.html +++ b/InvenTree/part/templates/part/supplier.html @@ -7,14 +7,14 @@ {% if part.supplier_parts.all|length > 0 %} - + {% for spart in part.supplier_parts.all %} - + {% for item in items.all %} - + diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index c83646429b..3712c6f2d6 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -52,10 +52,10 @@ stock_urls = [ url(r'^location/new/', views.StockLocationCreate.as_view(), name='stock-location-create'), + url(r'^item/new/?', views.StockItemCreate.as_view(), name='stock-item-create'), + # Individual stock items url(r'^item/(?P\d+)/', include(stock_item_detail_urls)), - url(r'^item/new/', views.StockItemCreate.as_view(), name='stock-item-create'), - url(r'^.*$', views.StockIndex.as_view(), name='stock-index'), ] \ No newline at end of file diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 7d42045a80..18c1e92dae 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -5,7 +5,7 @@ from django.urls import reverse from django.views.generic import DetailView, ListView from django.views.generic.edit import UpdateView, DeleteView, CreateView - +from part.models import Part from .models import StockItem, StockLocation from .forms import EditStockLocationForm @@ -46,35 +46,60 @@ class StockItemDetail(DetailView): class StockLocationEdit(UpdateView): model = StockLocation form_class = EditStockLocationForm - template_name = '/stock/location-edit.html' + template_name = 'stock/location_edit.html' context_object_name = 'location' class StockItemEdit(UpdateView): model = StockItem form_class = EditStockItemForm - template_name = '/stock/item-edit.html' + template_name = 'stock/item_edit.html' context_object_name = 'item' class StockLocationCreate(CreateView): model = StockLocation form_class = EditStockLocationForm - template_name = '/stock/location-create.html' + template_name = 'stock/location_create.html' context_object_name = 'location' + def get_initial(self): + initials = super(StockLocationCreate, self).get_initial().copy() + + loc_id = self.request.GET.get('location', None) + + if loc_id: + initials['parent'] = get_object_or_404(StockLocation, pk=loc_id) + + return initials + class StockItemCreate(CreateView): model = StockItem form_class = EditStockItemForm - template_name = '/stock/item-create.html' + template_name = 'stock/item_create.html' context_object_name = 'item' + def get_initial(self): + initials = super(StockItemCreate, self).get_initial().copy() + + part_id = self.request.GET.get('part', None) + loc_id = self.request.GET.get('location', None) + + if part_id: + initials['part'] = get_object_or_404(Part, pk=part_id) + + if loc_id: + initials['location'] = get_object_or_404(StockLocation, pk=loc_id) + + return initials + class StockLocationDelete(DeleteView): model = StockLocation - success_url = '/stock/' - template_name = '/stock/location-delete.html' + success_url = '/stock' + template_name = 'stock/location_delete.html' + context_object_name = 'location' def post(self, request, *args, **kwargs): if 'confirm' in request.POST: @@ -84,9 +109,10 @@ class StockLocationDelete(DeleteView): class StockItemDelete(DeleteView): - model = StockLocation + model = StockItem success_url = '/stock/' - template_name = '/stock/item-delete.html' + template_name = 'stock/item_delete.html' + context_object_name = 'item' def post(self, request, *args, **kwargs): if 'confirm' in request.POST: diff --git a/InvenTree/supplier/templates/supplier/partdetail.html b/InvenTree/supplier/templates/supplier/partdetail.html index 3c1fa315e0..ccee02fe71 100644 --- a/InvenTree/supplier/templates/supplier/partdetail.html +++ b/InvenTree/supplier/templates/supplier/partdetail.html @@ -11,7 +11,7 @@ From 8e6de1b832afe321bdcc4f3d63a8d54958f5227c Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 00:30:57 +1000 Subject: [PATCH 04/18] Add pages for part tracking - Edit / Delete / Create tracking info - Improvements to many pages --- InvenTree/part/forms.py | 1 + .../part/migrations/0017_part_purchaseable.py | 20 ++++++ InvenTree/part/models.py | 7 +++ InvenTree/part/templates/part/detail.html | 36 +++++++++-- InvenTree/part/templates/part/part_base.html | 61 +++++++++++++------ InvenTree/part/templates/part/supplier.html | 10 +-- InvenTree/part/templates/part/tabs.html | 18 ++++-- InvenTree/part/templates/part/track.html | 12 +++- InvenTree/part/templates/part/used_in.html | 2 - InvenTree/stock/forms.py | 1 + InvenTree/stock/models.py | 9 ++- InvenTree/stock/templates/stock/index.html | 6 ++ InvenTree/stock/templates/stock/item.html | 2 +- InvenTree/stock/templates/stock/loc_link.html | 2 +- .../supplier/templates/supplier/detail.html | 9 +-- .../templates/supplier/partdetail.html | 10 +-- InvenTree/supplier/views.py | 8 +++ InvenTree/track/forms.py | 28 +++++++++ InvenTree/track/models.py | 3 + InvenTree/track/templates/track/create.html | 5 ++ InvenTree/track/templates/track/delete.html | 11 ++++ InvenTree/track/templates/track/detail.html | 37 ++++++++++- InvenTree/track/templates/track/edit.html | 8 +-- InvenTree/track/templates/track/index.html | 6 ++ InvenTree/track/urls.py | 10 ++- InvenTree/track/views.py | 38 ++++++++++++ 26 files changed, 302 insertions(+), 58 deletions(-) create mode 100644 InvenTree/part/migrations/0017_part_purchaseable.py create mode 100644 InvenTree/track/forms.py create mode 100644 InvenTree/track/templates/track/create.html create mode 100644 InvenTree/track/templates/track/delete.html diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 379682e3dd..306e49bb99 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -28,6 +28,7 @@ class EditPartForm(forms.ModelForm): 'URL', 'minimum_stock', 'trackable', + 'purchaseable', ] 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/models.py b/InvenTree/part/models.py index 4c2b8967f5..cd57521dd8 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -115,6 +115,9 @@ class Part(models.Model): # and can have their movements tracked trackable = models.BooleanField(default=False) + # Is this part "purchaseable"? + purchaseable = models.BooleanField(default=True) + def __str__(self): if self.IPN: return "{name} ({ipn})".format( @@ -128,6 +131,10 @@ class Part(models.Model): verbose_name_plural = "Parts" #unique_together = (("name", "category"),) + @property + def tracked_parts(self): + return self.serials.order_by('serial') + @property def stock(self): """ Return the total stock quantity for this part. diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index dc2629ae47..3d38521dca 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -4,12 +4,40 @@ {% include 'part/tabs.html' with tab='detail' %} +
Supplier SKUSupplier URL
{{ spart.supplier.name }} {{ spart.SKU }}{{ spart.supplier.name }} {% if spart.URL %} {{ spart.URL }} diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 146943e1b0..32fca52124 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -20,6 +20,9 @@ class StockLocation(InvenTreeTree): Stock locations can be heirarchical as required """ + def get_absolute_url(self): + return '/stock/location/{id}/'.format(id=self.id) + @property def items(self): stock_list = self.stockitem_set.all() @@ -34,7 +37,16 @@ def before_delete_stock_location(sender, instance, using, **kwargs): item.location = instance.parent item.save() + # Update each child category + for child in instance.children.all(): + child.parent = instance.parent + child.save() + class StockItem(models.Model): + + def get_absolute_url(self): + return '/stock/item/{id}/'.format(id=self.id) + part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='locations') supplier_part = models.ForeignKey(SupplierPart, blank=True, null=True, on_delete=models.SET_NULL) diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index 8b7c80e5f9..1f4e63cdb4 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -4,10 +4,12 @@ {% include "stock/loc_link.html" with location=item.location %} +

Stock entry details

+ - + @@ -45,4 +47,13 @@ {% endif %}
Part{{ item.part.name }}{{ item.part.name }}
Location
+ + {% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/item_create.html b/InvenTree/stock/templates/stock/item_create.html new file mode 100644 index 0000000000..862acc859b --- /dev/null +++ b/InvenTree/stock/templates/stock/item_create.html @@ -0,0 +1,5 @@ +{% extends "create_edit_obj.html" %} + +{% block obj_title %} +Create a new stock item +{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/item_delete.html b/InvenTree/stock/templates/stock/item_delete.html new file mode 100644 index 0000000000..dfbac09c26 --- /dev/null +++ b/InvenTree/stock/templates/stock/item_delete.html @@ -0,0 +1,9 @@ +{% extends "delete_obj.html" %} + +{% block del_title %} +Are you sure you want to delete this stock item? +{% endblock %} + +{% block del_body %} +This will remove {{ item.quantity }} units of '{{ item.part.name }}' from stock. +{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/item_edit.html b/InvenTree/stock/templates/stock/item_edit.html new file mode 100644 index 0000000000..5f0de213b9 --- /dev/null +++ b/InvenTree/stock/templates/stock/item_edit.html @@ -0,0 +1,5 @@ +{% extends "create_edit_obj.html" %} + +{% block obj_title %} +Edit stock item for part '{{ item.part.name }}' +{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index 54352b0284..f68f8cb6b0 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -14,4 +14,22 @@ {% include "stock/stock_table.html" with items=location.items %} + + {% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/location_create.html b/InvenTree/stock/templates/stock/location_create.html new file mode 100644 index 0000000000..c79e09ae70 --- /dev/null +++ b/InvenTree/stock/templates/stock/location_create.html @@ -0,0 +1,5 @@ +{% extends "create_edit_obj.html" %} + +{% block obj_title %} +Create a new stock location +{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/location_delete.html b/InvenTree/stock/templates/stock/location_delete.html new file mode 100644 index 0000000000..135ed55d51 --- /dev/null +++ b/InvenTree/stock/templates/stock/location_delete.html @@ -0,0 +1,41 @@ +{% extends 'delete_obj.html' %} + +{% block del_title %} +Are you sure you want to delete stock location '{{ location.name }}'? +{% endblock %} + +{% block del_body %} +{% if location.children.all|length > 0 %} +

This location contains {{ location.children.all|length }} child locations.
+If this location is deleted, these child locations will be moved to +{% if location.parent %} +the '{{ location.parent.name }}' location. +{% else %} +the top level 'Stock' category. +{% endif %} +

+ +
    + {% for loc in location.children.all %} +
  • {{ loc.name }} - {{ loc.description}}
  • + {% endfor %} +
+{% endif %} + +{% if location.items.all|length > 0 %} +

This location contains {{ location.items.all|length }} stock items.
+{% if location.parent %} +If this location is deleted, these items will be moved to the '{{ location.parent.name }}' location. +{% else %} +If this location is deleted, these items will be deleted! +{% endif %} +

+ +
    + {% for item in location.items.all %} +
  • {{ item.part.name }} - {{ item.part.description }}{{ item.quantity }}
  • + {% endfor %} +
+{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/location_edit.html b/InvenTree/stock/templates/stock/location_edit.html new file mode 100644 index 0000000000..d2fbdbb84e --- /dev/null +++ b/InvenTree/stock/templates/stock/location_edit.html @@ -0,0 +1,5 @@ +{% extends "create_edit_obj.html" %} + +{% block obj_title %} +Edit stock location '{{ location.name }}' +{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/stock_table.html b/InvenTree/stock/templates/stock/stock_table.html index d3684ffe63..b6e92e060f 100644 --- a/InvenTree/stock/templates/stock/stock_table.html +++ b/InvenTree/stock/templates/stock/stock_table.html @@ -8,7 +8,7 @@
{{ item.part.name }}{{ item.part.name }} {{ item.quantity }} {{ item.status }} {{ item.stocktake_date }}Part {% if part.part %} - {{ part.part.name }} + {{ part.part.name }} {% endif %}
+ + + + + + + + + + + + + + + + + + + + + + + + +
Name{{ part.name }}
Description{{ part.decription }}
Category + {% if part.category %} + {{ part.category.name }} + {% endif %} +
Units{{ part.units }}
Trackable{{ part.trackable }}
Purchaseable{{ part.purchaseable }}
-Part details go here... -
- + {% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index c5202c3fa6..7cd4120f44 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -6,33 +6,54 @@ {% include "part/cat_link.html" with category=part.category %} -
-
- -
-
-

{{ part.name }}

- {% if part.description %} -

{{ part.description }}

- {% endif %} - {% if part.IPN %} -

IPN: {{ part.IPN }}

- {% endif %} - {% if part.URL %} -

{% include 'url.html' with url=part.URL %}

- {% endif %} +
+
+
+
+ +
+
+

{{ part.name }}

+ {% if part.description %} +

{{ part.description }}

+ {% endif %} +
+
+
+
+ + {% if part.IPN %} + + + + + {% endif %} + {% if part.URL %} + + + + + {% endif %} + + + + +
IPN{{ part.IPN }}
URL{{ part.URL }}
Stock{{ part.stock }}
+
+
{% block details %} {% endblock %} +
{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/supplier.html b/InvenTree/part/templates/part/supplier.html index 425c98b3f3..a4a76f55da 100644 --- a/InvenTree/part/templates/part/supplier.html +++ b/InvenTree/part/templates/part/supplier.html @@ -4,7 +4,6 @@ {% include 'part/tabs.html' with tab='suppliers' %} -{% if part.supplier_parts.all|length > 0 %} @@ -23,8 +22,11 @@ {% endfor %}
SKU
-{% else %} -There are no suppliers defined for this part, sorry! -{% endif %} + + {% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/tabs.html b/InvenTree/part/templates/part/tabs.html index 1c244c40b2..64e5089f10 100644 --- a/InvenTree/part/templates/part/tabs.html +++ b/InvenTree/part/templates/part/tabs.html @@ -1,15 +1,25 @@ \ No newline at end of file diff --git a/InvenTree/part/templates/part/track.html b/InvenTree/part/templates/part/track.html index 6ce8c8429e..01e174eef0 100644 --- a/InvenTree/part/templates/part/track.html +++ b/InvenTree/part/templates/part/track.html @@ -11,12 +11,18 @@ Part tracking for {{ part.name }} Serial Status -{% for track in part.serials.all %} +{% for track in part.tracked_parts.all %} - {{ track.serial }} - {{ track.status }} + {{ track.serial }} + {{ track.get_status_display }} {% endfor %} + + {% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/used_in.html b/InvenTree/part/templates/part/used_in.html index 2b3f55ef19..df6bdb1f24 100644 --- a/InvenTree/part/templates/part/used_in.html +++ b/InvenTree/part/templates/part/used_in.html @@ -4,8 +4,6 @@ {% include 'part/tabs.html' with tab='used' %} -This part is used to make the following parts: - diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 606b41db9f..fd124cf837 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -48,4 +48,5 @@ class EditStockItemForm(forms.ModelForm): 'supplier_part', 'location', 'quantity', + 'status' ] \ No newline at end of file diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 32fca52124..3a86e3d835 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -34,8 +34,13 @@ def before_delete_stock_location(sender, instance, using, **kwargs): # Update each part in the stock location for item in instance.items.all(): - item.location = instance.parent - item.save() + # If this location has a parent, move the child stock items to the parent + if instance.parent: + item.location = instance.parent + item.save() + # No parent location? Delete the stock items + else: + item.delete() # Update each child category for child in instance.children.all(): diff --git a/InvenTree/stock/templates/stock/index.html b/InvenTree/stock/templates/stock/index.html index 230b37604e..9d89718bb1 100644 --- a/InvenTree/stock/templates/stock/index.html +++ b/InvenTree/stock/templates/stock/index.html @@ -12,4 +12,10 @@ {% include "stock/stock_table.html" with items=items %} {% endif %} + + {% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index 1f4e63cdb4..92fb3258cb 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -37,7 +37,7 @@ {% endif %} - + {% if item.notes %} diff --git a/InvenTree/stock/templates/stock/loc_link.html b/InvenTree/stock/templates/stock/loc_link.html index 099c88e8fc..34f10f33f7 100644 --- a/InvenTree/stock/templates/stock/loc_link.html +++ b/InvenTree/stock/templates/stock/loc_link.html @@ -1,7 +1,7 @@
Part
Status{{ item.status }}{{ item.get_status_display }}
@@ -71,5 +71,6 @@ + {% endblock %} \ No newline at end of file diff --git a/InvenTree/supplier/templates/supplier/partdetail.html b/InvenTree/supplier/templates/supplier/partdetail.html index ccee02fe71..26fa19c328 100644 --- a/InvenTree/supplier/templates/supplier/partdetail.html +++ b/InvenTree/supplier/templates/supplier/partdetail.html @@ -29,12 +29,14 @@
-

+

{% endblock %} \ No newline at end of file diff --git a/InvenTree/supplier/views.py b/InvenTree/supplier/views.py index d3e03e1b66..704644bee5 100644 --- a/InvenTree/supplier/views.py +++ b/InvenTree/supplier/views.py @@ -5,6 +5,7 @@ from django.urls import reverse from django.views.generic import DetailView, ListView from django.views.generic.edit import UpdateView, DeleteView, CreateView +from part.models import Part from .models import Supplier, SupplierPart from .forms import EditSupplierForm @@ -76,9 +77,16 @@ class SupplierPartCreate(CreateView): initials = super(SupplierPartCreate, self).get_initial().copy() supplier_id = self.request.GET.get('supplier', None) + part_id = self.request.GET.get('part', None) if supplier_id: initials['supplier'] = get_object_or_404(Supplier, pk=supplier_id) + # TODO + # self.fields['supplier'].disabled = True + if part_id: + initials['part'] = get_object_or_404(Part, pk=part_id) + # TODO + # self.fields['part'].disabled = True return initials diff --git a/InvenTree/track/forms.py b/InvenTree/track/forms.py new file mode 100644 index 0000000000..0738210c56 --- /dev/null +++ b/InvenTree/track/forms.py @@ -0,0 +1,28 @@ +from django import forms +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Submit + +from .models import UniquePart + + +class EditTrackedPartForm(forms.ModelForm): + + def __init__(self, *args, **kwargs): + super(EditTrackedPartForm, self).__init__(*args, **kwargs) + 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')) + + class Meta: + model = UniquePart + fields = [ + 'part', + 'serial', + 'customer', + 'status' + ] \ No newline at end of file diff --git a/InvenTree/track/models.py b/InvenTree/track/models.py index bb13c43734..ff341aca97 100644 --- a/InvenTree/track/models.py +++ b/InvenTree/track/models.py @@ -15,6 +15,9 @@ class UniquePart(models.Model): and tracking all events in the life of a part """ + def get_absolute_url(self): + return "/track/{id}/".format(id=self.id) + class Meta: # Cannot have multiple parts with same serial number unique_together = ('part', 'serial') diff --git a/InvenTree/track/templates/track/create.html b/InvenTree/track/templates/track/create.html new file mode 100644 index 0000000000..0760c37871 --- /dev/null +++ b/InvenTree/track/templates/track/create.html @@ -0,0 +1,5 @@ +{% extends "create_edit_obj.html" %} + +{% block obj_title %} +Create a new tracked part +{% endblock %} diff --git a/InvenTree/track/templates/track/delete.html b/InvenTree/track/templates/track/delete.html new file mode 100644 index 0000000000..13490c000c --- /dev/null +++ b/InvenTree/track/templates/track/delete.html @@ -0,0 +1,11 @@ +{% extends "delete_obj.html" %} + +{% block del_title %} +Are you sure you want to delete tracking info for this part? +{% endblock %} + +{% block del_body %} + +All tracking information for part {{ track.part.name }} SN-{{ track.serial }} will be deleted. + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/track/templates/track/detail.html b/InvenTree/track/templates/track/detail.html index ce1f30ecda..1a7488be6f 100644 --- a/InvenTree/track/templates/track/detail.html +++ b/InvenTree/track/templates/track/detail.html @@ -2,8 +2,32 @@ {% block content %} -Part: {{ part.part.name }}
-Serial number: {{ part.serial }} +

Part tracking information

+ +
+ + + + + + + + + + + + + {% if part.customer %} + + + + + {% endif %} + + + + +
Part{{ part.part.name }}
Serial Number{{ part.serial }}
Creation Date{{ part.creation_date }}
Customer{{ part.customer }}
Status{{ part.get_status_display }}
{% if part.tracking_info.all|length > 0 %}

Tracking information:

@@ -23,4 +47,13 @@ Serial number: {{ part.serial }} {% endif %} + + {% endblock %} \ No newline at end of file diff --git a/InvenTree/track/templates/track/edit.html b/InvenTree/track/templates/track/edit.html index 8d0fb09346..9f23c94e9d 100644 --- a/InvenTree/track/templates/track/edit.html +++ b/InvenTree/track/templates/track/edit.html @@ -1,5 +1,5 @@ -{% extends "base.html"% } +{% extends "create_edit_obj.html" %} -{% block content %} - -{% endblock %} \ No newline at end of file +{% block obj_title %} +Edit tracked part information +{% endblock %} diff --git a/InvenTree/track/templates/track/index.html b/InvenTree/track/templates/track/index.html index b98d815a5a..94ed748a4a 100644 --- a/InvenTree/track/templates/track/index.html +++ b/InvenTree/track/templates/track/index.html @@ -30,4 +30,10 @@
{% endif %} + + {% endblock %} \ No newline at end of file diff --git a/InvenTree/track/urls.py b/InvenTree/track/urls.py index 6b3319b049..23526a48ff 100644 --- a/InvenTree/track/urls.py +++ b/InvenTree/track/urls.py @@ -24,6 +24,9 @@ unique_api_urls = [ """ track_detail_urls = [ + url(r'^edit/?', views.TrackEdit.as_view(), name='track-edit'), + url(r'^delete/?', views.TrackDelete.as_view(), name='track-delete'), + url('^.*$', views.TrackDetail.as_view(), name='track-detail'), ] @@ -31,8 +34,9 @@ tracking_urls = [ # Detail view url(r'^(?P\d+)/', include(track_detail_urls)), - # List ALL tracked items - url('', views.TrackIndex.as_view(), name='track-index'), + # Create a new tracking item + url(r'^new/?', views.TrackCreate.as_view(), name='track-create'), - url(r'^.*$', RedirectView.as_view(url='', permanent=False), name='track-index'), + # List ALL tracked items + url(r'^.*$', views.TrackIndex.as_view(), name='track-index'), ] \ No newline at end of file diff --git a/InvenTree/track/views.py b/InvenTree/track/views.py index 26a0d06ed2..6cde97c1fb 100644 --- a/InvenTree/track/views.py +++ b/InvenTree/track/views.py @@ -5,8 +5,10 @@ from django.urls import reverse from django.views.generic import DetailView, ListView from django.views.generic.edit import UpdateView, DeleteView, CreateView +from part.models import Part from .models import UniquePart, PartTrackingInfo +from .forms import EditTrackedPartForm class TrackIndex(ListView): model = UniquePart @@ -23,3 +25,39 @@ class TrackDetail(DetailView): template_name = 'track/detail.html' context_object_name='part' + +class TrackCreate(CreateView): + model = UniquePart + form_class = EditTrackedPartForm + template_name = 'track/create.html' + context_object_name = 'part' + + def get_initial(self): + initials = super(TrackCreate, self).get_initial().copy() + + part_id = self.request.GET.get('part', None) + + if part_id: + initials['part'] = get_object_or_404(Part, pk=part_id) + + return initials + + +class TrackEdit(UpdateView): + model = UniquePart + form_class = EditTrackedPartForm + template_name = 'track/edit.html' + context_obect_name = 'part' + + +class TrackDelete(DeleteView): + model = UniquePart + success_url = '/track' + template_name = 'track/delete.html' + context_object_name = 'track' + + def post(self, request, *args, **kwargs): + if 'confirm' in request.POST: + return super(TrackDelete, self).post(request, *args, **kwargs) + else: + return HttpResponseRedirect(self.get_object().get_absolute_url()) \ No newline at end of file From 1027e812bc92df752989e84b7fb6bd782773a95f Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 00:44:32 +1000 Subject: [PATCH 05/18] Use django_import_export - Allows import / export to multiple file formats - Provides admin interface - Work to be done to perform data tweaking - It would be really cool if the data fields could be associated 'intelligently' (i.e. not just based on PK, but name-lookup too). --- InvenTree/InvenTree/settings.py | 5 +++++ InvenTree/part/admin.py | 7 +++++-- InvenTree/supplier/admin.py | 9 +++++++-- requirements/base.txt | 3 ++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index c8a2be70c9..b1ce5ec324 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', @@ -147,4 +148,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/part/admin.py b/InvenTree/part/admin.py index 2e6da3a4d8..429788f4b8 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -1,10 +1,12 @@ 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): +#class PartAdmin(admin.ModelAdmin): +class PartAdmin(ImportExportModelAdmin): list_display = ('name', 'IPN', 'description', 'stock', 'category') @@ -13,7 +15,8 @@ class PartCategoryAdmin(admin.ModelAdmin): list_display = ('name', 'pathstring', 'description') -class BomItemAdmin(admin.ModelAdmin): +#class BomItemAdmin(admin.ModelAdmin): +class BomItemAdmin(ImportExportModelAdmin): list_display=('part', 'sub_part', 'quantity') class PartAttachmentAdmin(admin.ModelAdmin): diff --git a/InvenTree/supplier/admin.py b/InvenTree/supplier/admin.py index 4fe7914019..5811503041 100644 --- a/InvenTree/supplier/admin.py +++ b/InvenTree/supplier/admin.py @@ -1,13 +1,18 @@ from django.contrib import admin +from import_export.admin import ImportExportModelAdmin from .models import Supplier, SupplierPart, Customer, Manufacturer -class CompanyAdmin(admin.ModelAdmin): +class CompanyAdmin(ImportExportModelAdmin): list_display = ('name', 'website', 'contact') +class SupplierPartAdmin(ImportExportModelAdmin): + list_display = ('part', 'supplier', 'SKU') + + admin.site.register(Customer, CompanyAdmin) admin.site.register(Supplier, CompanyAdmin) admin.site.register(Manufacturer, CompanyAdmin) -admin.site.register(SupplierPart) +admin.site.register(SupplierPart, SupplierPartAdmin) diff --git a/requirements/base.txt b/requirements/base.txt index f471d3db94..5fd47a6ab2 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -4,4 +4,5 @@ django_filter==1.0.2 django-simple-history==1.8.2 coreapi==2.3.0 pygments==2.2.0 -django-crispy-forms==1.7.2 \ No newline at end of file +django-crispy-forms==1.7.2 +django-import-export==1.0.0 \ No newline at end of file From a91ff58d84e695a29d8f682daf9de8bc10f407d1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 01:02:17 +1000 Subject: [PATCH 06/18] Flakey Fixing python style errors --- InvenTree/InvenTree/models.py | 3 +- InvenTree/InvenTree/urls.py | 41 +++++++++---------- InvenTree/part/admin.py | 14 ++++--- InvenTree/part/api.py | 8 ++-- InvenTree/part/forms.py | 10 +---- InvenTree/part/models.py | 11 ++--- .../{param_todo.py => param_todo.py_todo} | 0 InvenTree/part/serializers.py | 7 +++- InvenTree/part/urls.py | 7 +--- InvenTree/part/views.py | 12 +++--- InvenTree/stock/api.py | 3 -- InvenTree/stock/forms.py | 5 +-- InvenTree/stock/models.py | 2 + InvenTree/stock/urls.py | 3 +- InvenTree/stock/views.py | 7 ++-- InvenTree/supplier/forms.py | 4 +- InvenTree/supplier/models.py | 3 +- InvenTree/supplier/urls.py | 3 +- InvenTree/supplier/views.py | 4 +- InvenTree/track/admin.py | 3 +- InvenTree/track/forms.py | 3 +- InvenTree/track/models.py | 5 +-- InvenTree/track/urls.py | 3 +- InvenTree/track/views.py | 10 ++--- 24 files changed, 74 insertions(+), 97 deletions(-) rename InvenTree/part/{param_todo.py => param_todo.py_todo} (100%) diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 990c25bd67..fc39cf9233 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 """ @@ -185,7 +186,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/urls.py b/InvenTree/InvenTree/urls.py index 8c20fe9282..202aa32447 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 @@ -21,8 +18,8 @@ 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 +34,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,8 +64,8 @@ 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)), @@ -87,4 +84,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 429788f4b8..71bdeeadd8 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -5,7 +5,7 @@ from .models import PartCategory, Part from .models import BomItem from .models import PartAttachment -#class PartAdmin(admin.ModelAdmin): + class PartAdmin(ImportExportModelAdmin): list_display = ('name', 'IPN', 'description', 'stock', 'category') @@ -15,13 +15,15 @@ class PartCategoryAdmin(admin.ModelAdmin): list_display = ('name', 'pathstring', 'description') -#class BomItemAdmin(admin.ModelAdmin): + class BomItemAdmin(ImportExportModelAdmin): - list_display=('part', 'sub_part', 'quantity') + list_display = ('part', 'sub_part', 'quantity') + class PartAttachmentAdmin(admin.ModelAdmin): list_display = ('part', 'attachment') + """ class ParameterTemplateAdmin(admin.ModelAdmin): list_display = ('name', 'units', 'format') @@ -36,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 306e49bb99..6eec8fa3c4 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -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')) @@ -39,9 +37,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')) @@ -61,10 +57,8 @@ class EditBomItemForm(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')) class Meta: @@ -73,4 +67,4 @@ class EditBomItemForm(forms.ModelForm): 'part', 'sub_part', 'quantity' - ] \ No newline at end of file + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index cd57521dd8..9bd13a0d40 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 @@ -42,6 +41,7 @@ class PartCategory(InvenTreeTree): return self.part_set.all() """ + @receiver(pre_delete, sender=PartCategory, dispatch_uid='partcategory_delete_log') def before_delete_part_category(sender, instance, using, **kwargs): @@ -129,7 +129,6 @@ class Part(models.Model): class Meta: verbose_name = "Part" verbose_name_plural = "Parts" - #unique_together = (("name", "category"),) @property def tracked_parts(self): @@ -152,7 +151,6 @@ class Part(models.Model): def bomItemCount(self): return self.bom_items.all().count() - @property def usedInCount(self): return self.used_in.all().count() @@ -177,9 +175,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!! @@ -188,19 +187,18 @@ 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 @@ -221,7 +219,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/urls.py b/InvenTree/part/urls.py index c98cba695b..6c9af81bbb 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -46,7 +46,6 @@ part_detail_urls = [ url(r'^suppliers/?', views.PartDetail.as_view(template_name='part/supplier.html'), name='part-suppliers'), # Any other URLs go to the part detail page - #url(r'^.*$', views.detail, name='part-detail'), url(r'^.*$', views.PartDetail.as_view(), name='part-detail'), ] @@ -87,11 +86,9 @@ part_urls = [ # Top level part list (display top level parts and categories) url('', views.PartIndex.as_view(), name='part-index'), - url(r'^.*$', RedirectView.as_view(url='', permanent=False), name='part-index'), + url(r'^.*$', RedirectView.as_view(url='', permanent=False), name='part-index'), ] - - """ part_param_urls = [ # Detail of a single part parameter @@ -111,5 +108,3 @@ part_param_template_urls = [ url(r'^$', views.PartTemplateList.as_view()) ] """ - - diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 5ade502688..57feb04206 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1,15 +1,13 @@ -from InvenTree.models import FilterChildren -from .models import PartCategory, Part, BomItem - -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404 from django.http import HttpResponseRedirect -from django.urls import reverse from django.views.generic import DetailView, ListView from django.views.generic.edit import UpdateView, DeleteView, CreateView from .forms import EditPartForm, EditCategoryForm, EditBomItemForm +from .models import PartCategory, Part, BomItem + class PartIndex(ListView): model = Part @@ -114,7 +112,7 @@ class CategoryDelete(DeleteView): model = PartCategory template_name = 'part/category_delete.html' context_object_name = 'category' - success_url ='/part/' + success_url = '/part/' def post(self, request, *args, **kwargs): if 'confirm' in request.POST: @@ -150,7 +148,7 @@ class CategoryCreate(CreateView): class BomItemDetail(DetailView): - context_object_name ='item' + context_object_name = 'item' queryset = BomItem.objects.all() template_name = 'part/bom-detail.html' diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 874478bf1f..99c2a246da 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -3,9 +3,6 @@ from django_filters import NumberFilter from rest_framework import generics, permissions, response - - - # from InvenTree.models import FilterChildren from .models import StockLocation, StockItem from .serializers import StockItemSerializer, StockQuantitySerializer diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index fd124cf837..e05cdfc4fa 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -14,7 +14,6 @@ class EditStockLocationForm(forms.ModelForm): 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')) @@ -36,11 +35,9 @@ class EditStockItemForm(forms.ModelForm): 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')) - class Meta: model = StockItem fields = [ @@ -49,4 +46,4 @@ class EditStockItemForm(forms.ModelForm): 'location', 'quantity', 'status' - ] \ No newline at end of file + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 3a86e3d835..c7ad5f0b9b 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -14,6 +14,7 @@ from datetime import datetime from django.db.models.signals import pre_delete from django.dispatch import receiver + class StockLocation(InvenTreeTree): """ Organization tree for StockItem objects A "StockLocation" can be considered a warehouse, or storage location @@ -47,6 +48,7 @@ def before_delete_stock_location(sender, instance, using, **kwargs): child.parent = instance.parent child.save() + class StockItem(models.Model): def get_absolute_url(self): diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index 3712c6f2d6..de398cc03d 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -1,5 +1,4 @@ from django.conf.urls import url, include -from django.views.generic.base import RedirectView from . import views from . import api @@ -58,4 +57,4 @@ stock_urls = [ url(r'^item/(?P\d+)/', include(stock_item_detail_urls)), url(r'^.*$', views.StockIndex.as_view(), name='stock-index'), -] \ No newline at end of file +] diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 18c1e92dae..cc715c0669 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -1,6 +1,5 @@ -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404 from django.http import HttpResponseRedirect -from django.urls import reverse from django.views.generic import DetailView, ListView from django.views.generic.edit import UpdateView, DeleteView, CreateView @@ -11,6 +10,7 @@ from .models import StockItem, StockLocation from .forms import EditStockLocationForm from .forms import EditStockItemForm + class StockIndex(ListView): model = StockItem template_name = 'stock/index.html' @@ -29,6 +29,7 @@ class StockIndex(ListView): return context + class StockLocationDetail(DetailView): context_object_name = 'location' template_name = 'stock/location.html' @@ -118,4 +119,4 @@ class StockItemDelete(DeleteView): if 'confirm' in request.POST: return super(StockItemDelete, self).post(request, *args, **kwargs) else: - return HttpResponseRedirect(self.get_object().get_absolute_url()) \ No newline at end of file + return HttpResponseRedirect(self.get_object().get_absolute_url()) diff --git a/InvenTree/supplier/forms.py b/InvenTree/supplier/forms.py index 96007a41c8..10167ee8d0 100644 --- a/InvenTree/supplier/forms.py +++ b/InvenTree/supplier/forms.py @@ -14,7 +14,6 @@ class EditSupplierForm(forms.ModelForm): 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')) @@ -40,7 +39,6 @@ class EditSupplierPartForm(forms.ModelForm): 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')) @@ -54,4 +52,4 @@ class EditSupplierPartForm(forms.ModelForm): 'URL', 'manufacturer', 'MPN', - ] \ No newline at end of file + ] diff --git a/InvenTree/supplier/models.py b/InvenTree/supplier/models.py index 459965f309..5eec075edc 100644 --- a/InvenTree/supplier/models.py +++ b/InvenTree/supplier/models.py @@ -46,8 +46,7 @@ class SupplierPart(models.Model): related_name='supplier_parts') supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, - related_name = 'parts') - + related_name='parts') SKU = models.CharField(max_length=100) diff --git a/InvenTree/supplier/urls.py b/InvenTree/supplier/urls.py index 81ece4a1aa..94321f734e 100644 --- a/InvenTree/supplier/urls.py +++ b/InvenTree/supplier/urls.py @@ -2,7 +2,6 @@ from django.conf.urls import url, include from django.views.generic.base import RedirectView from . import views -from . import api """ cust_urls = [ @@ -75,4 +74,4 @@ supplier_urls = [ # Redirect any other patterns url(r'^.*$', RedirectView.as_view(url='', permanent=False), name='supplier-index'), -] \ No newline at end of file +] diff --git a/InvenTree/supplier/views.py b/InvenTree/supplier/views.py index 704644bee5..c64a0b07dc 100644 --- a/InvenTree/supplier/views.py +++ b/InvenTree/supplier/views.py @@ -1,6 +1,5 @@ -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404 from django.http import HttpResponseRedirect -from django.urls import reverse from django.views.generic import DetailView, ListView from django.views.generic.edit import UpdateView, DeleteView, CreateView @@ -11,6 +10,7 @@ from .models import Supplier, SupplierPart from .forms import EditSupplierForm from .forms import EditSupplierPartForm + class SupplierIndex(ListView): model = Supplier template_name = 'supplier/index.html' diff --git a/InvenTree/track/admin.py b/InvenTree/track/admin.py index b060b5ad9e..751ad5711c 100644 --- a/InvenTree/track/admin.py +++ b/InvenTree/track/admin.py @@ -7,10 +7,9 @@ class UniquePartAdmin(admin.ModelAdmin): list_display = ('part', 'serial', 'status', 'creation_date') - class PartTrackingAdmin(admin.ModelAdmin): list_display = ('part', 'date', 'title') admin.site.register(UniquePart, UniquePartAdmin) -admin.site.register(PartTrackingInfo, PartTrackingAdmin) \ No newline at end of file +admin.site.register(PartTrackingInfo, PartTrackingAdmin) diff --git a/InvenTree/track/forms.py b/InvenTree/track/forms.py index 0738210c56..cfcc50dd4b 100644 --- a/InvenTree/track/forms.py +++ b/InvenTree/track/forms.py @@ -14,7 +14,6 @@ class EditTrackedPartForm(forms.ModelForm): 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')) @@ -25,4 +24,4 @@ class EditTrackedPartForm(forms.ModelForm): 'serial', 'customer', 'status' - ] \ No newline at end of file + ] diff --git a/InvenTree/track/models.py b/InvenTree/track/models.py index ff341aca97..d43193e392 100644 --- a/InvenTree/track/models.py +++ b/InvenTree/track/models.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -from rest_framework.exceptions import ValidationError from django.utils.translation import ugettext as _ from django.db import models @@ -55,8 +54,8 @@ class UniquePart(models.Model): status = models.IntegerField(default=PART_IN_PROGRESS, choices=PART_STATUS_CODES.items()) def __str__(self): - return "{pn} - # {sn}".format(pn = self.part.name, - sn = self.serial) + return "{pn} - # {sn}".format(pn=self.part.name, + sn=self.serial) class PartTrackingInfo(models.Model): diff --git a/InvenTree/track/urls.py b/InvenTree/track/urls.py index 23526a48ff..5f7332f417 100644 --- a/InvenTree/track/urls.py +++ b/InvenTree/track/urls.py @@ -1,5 +1,4 @@ from django.conf.urls import url, include -from django.views.generic.base import RedirectView from . import views @@ -39,4 +38,4 @@ tracking_urls = [ # List ALL tracked items url(r'^.*$', views.TrackIndex.as_view(), name='track-index'), -] \ No newline at end of file +] diff --git a/InvenTree/track/views.py b/InvenTree/track/views.py index 6cde97c1fb..5af7b57f77 100644 --- a/InvenTree/track/views.py +++ b/InvenTree/track/views.py @@ -1,15 +1,15 @@ -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404 from django.http import HttpResponseRedirect -from django.urls import reverse from django.views.generic import DetailView, ListView from django.views.generic.edit import UpdateView, DeleteView, CreateView from part.models import Part -from .models import UniquePart, PartTrackingInfo +from .models import UniquePart from .forms import EditTrackedPartForm + class TrackIndex(ListView): model = UniquePart template_name = 'track/index.html' @@ -23,7 +23,7 @@ class TrackIndex(ListView): class TrackDetail(DetailView): queryset = UniquePart.objects.all() template_name = 'track/detail.html' - context_object_name='part' + context_object_name = 'part' class TrackCreate(CreateView): @@ -60,4 +60,4 @@ class TrackDelete(DeleteView): if 'confirm' in request.POST: return super(TrackDelete, self).post(request, *args, **kwargs) else: - return HttpResponseRedirect(self.get_object().get_absolute_url()) \ No newline at end of file + return HttpResponseRedirect(self.get_object().get_absolute_url()) From a80cf0aace11c614fb14e1205773c7c653b0fc9f Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 01:14:19 +1000 Subject: [PATCH 07/18] Added some missing data on supplier pages --- InvenTree/part/templates/part/supplier.html | 5 +++++ .../supplier/templates/supplier/detail.html | 22 +++++++++++-------- .../templates/supplier/partdetail.html | 4 ++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/InvenTree/part/templates/part/supplier.html b/InvenTree/part/templates/part/supplier.html index a4a76f55da..5fbbee9bd8 100644 --- a/InvenTree/part/templates/part/supplier.html +++ b/InvenTree/part/templates/part/supplier.html @@ -8,12 +8,17 @@ SKU Supplier + MPN URL {% for spart in part.supplier_parts.all %} {{ spart.SKU }} {{ spart.supplier.name }} + + {% if spart.manufacturer %}{{ spart.manufacturer.name }}{% endif %} + {% if spart.MPN %} | {{ spart.MPN }}{% endif %} + {% if spart.URL %} {{ spart.URL }} diff --git a/InvenTree/supplier/templates/supplier/detail.html b/InvenTree/supplier/templates/supplier/detail.html index 014835fba9..f1c7a8c52a 100644 --- a/InvenTree/supplier/templates/supplier/detail.html +++ b/InvenTree/supplier/templates/supplier/detail.html @@ -6,12 +6,7 @@

{{ supplier.name }}

{{ supplier.description }}

- - - - - - +

{{ supplier.notes }}

@@ -48,21 +43,24 @@
+ - {% for part in supplier.parts.all %} + - - + {% endfor %}
SKUDescription PartManufacturer MPN URL
{{ part.SKU }}{{ part.description }} {% if part.part %} {{ part.part.name }} {% endif %} Manufacturer name goes hereMPN goes here + {% if part.manufacturer %}{{ part.manufacturer.name }}{% endif %} + {% if part.MPN %} | {{ part.MPN }}{% endif %} + {{ part.URL }}
@@ -71,6 +69,12 @@ + + + + + +
{% endblock %} \ No newline at end of file diff --git a/InvenTree/supplier/templates/supplier/partdetail.html b/InvenTree/supplier/templates/supplier/partdetail.html index 26fa19c328..fce66f5fea 100644 --- a/InvenTree/supplier/templates/supplier/partdetail.html +++ b/InvenTree/supplier/templates/supplier/partdetail.html @@ -22,8 +22,8 @@ Description{{ part.description }} {% endif %} {% if part.manufacturer %} - ManufacturerTODO - MPNTODO + Manufacturer{% if part.manufacturer %}{{ part.manufacturer.name }}{% endif %} + MPN{{ part.MPN }} {% endif %} From eb5413b4fa4af25c5bdd1739138bfed8ebd5cc1f Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 01:17:14 +1000 Subject: [PATCH 08/18] Added requirement for pillow module --- requirements/base.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/base.txt b/requirements/base.txt index 5fd47a6ab2..b39b8ebd07 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,5 @@ Django==1.11 +pillow==3.1.2 djangorestframework==3.6.2 django_filter==1.0.2 django-simple-history==1.8.2 From f7eff8ae9847f340061d81e19f551c5f2998322b Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 01:25:13 +1000 Subject: [PATCH 09/18] Added URL field to UniquePart - e.g. link to external part tracking info page --- InvenTree/part/templates/part/stock.html | 4 ---- InvenTree/track/forms.py | 1 + .../track/migrations/0005_uniquepart_url.py | 20 +++++++++++++++++++ InvenTree/track/models.py | 3 +++ InvenTree/track/templates/track/detail.html | 16 +++++++-------- 5 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 InvenTree/track/migrations/0005_uniquepart_url.py diff --git a/InvenTree/part/templates/part/stock.html b/InvenTree/part/templates/part/stock.html index 825608b73f..2d04fd9a14 100644 --- a/InvenTree/part/templates/part/stock.html +++ b/InvenTree/part/templates/part/stock.html @@ -4,10 +4,6 @@ {% include 'part/tabs.html' with tab='stock' %} -
-Total in stock: {{ part.stock }} -
- diff --git a/InvenTree/track/forms.py b/InvenTree/track/forms.py index cfcc50dd4b..7ac159a510 100644 --- a/InvenTree/track/forms.py +++ b/InvenTree/track/forms.py @@ -22,6 +22,7 @@ class EditTrackedPartForm(forms.ModelForm): fields = [ 'part', 'serial', + 'URL', 'customer', 'status' ] diff --git a/InvenTree/track/migrations/0005_uniquepart_url.py b/InvenTree/track/migrations/0005_uniquepart_url.py new file mode 100644 index 0000000000..50ea92eaa6 --- /dev/null +++ b/InvenTree/track/migrations/0005_uniquepart_url.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-15 15:21 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('track', '0004_parttrackinginfo_user'), + ] + + operations = [ + migrations.AddField( + model_name='uniquepart', + name='URL', + field=models.URLField(blank=True), + ), + ] diff --git a/InvenTree/track/models.py b/InvenTree/track/models.py index d43193e392..8df03994a1 100644 --- a/InvenTree/track/models.py +++ b/InvenTree/track/models.py @@ -28,6 +28,9 @@ class UniquePart(models.Model): serial = models.PositiveIntegerField() + # Provide a URL for an external link + URL = models.URLField(blank=True) + # createdBy = models.ForeignKey(User) customer = models.ForeignKey(Customer, blank=True, null=True) diff --git a/InvenTree/track/templates/track/detail.html b/InvenTree/track/templates/track/detail.html index 1a7488be6f..6626cd4b49 100644 --- a/InvenTree/track/templates/track/detail.html +++ b/InvenTree/track/templates/track/detail.html @@ -17,6 +17,12 @@ + {% if part.URL %} + + + + + {% endif %} {% if part.customer %} @@ -34,14 +40,8 @@
    {% for info in part.tracking_info.all %}
  • -
    -
    - {{ info.title }}{{ info.date }} -
    - {% if info.notes %} -
    {{ info.notes }}
    - {% endif %} -
    + {{ info.title }} + {% if info.note %}
    {{ info.notes }}{% endif %}{{ info.date }}
  • {% endfor %}
From 76ee150ca49ddc1ba6d1c6ff7a98581b21aa66a3 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 20:08:04 +1000 Subject: [PATCH 10/18] Moved tracking information to StockItem --- InvenTree/InvenTree/settings.py | 3 +- InvenTree/InvenTree/urls.py | 5 +- InvenTree/part/models.py | 3 +- InvenTree/part/templates/navbar.html | 1 - InvenTree/stock/forms.py | 6 +- .../migrations/0007_auto_20180416_0853.py | 81 ++++++++++++++++ InvenTree/stock/models.py | 56 ++++++++++- InvenTree/stock/templates/stock/item.html | 25 +++++ InvenTree/track/__init__.py | 0 InvenTree/track/admin.py | 15 --- InvenTree/track/api.py | 97 ------------------- InvenTree/track/apps.py | 7 -- InvenTree/track/forms.py | 28 ------ InvenTree/track/migrations/0001_initial.py | 47 --------- .../migrations/0002_auto_20180413_1440.py | 26 ----- .../migrations/0003_auto_20180415_0147.py | 31 ------ .../migrations/0004_parttrackinginfo_user.py | 23 ----- .../track/migrations/0005_uniquepart_url.py | 20 ---- InvenTree/track/migrations/__init__.py | 0 InvenTree/track/models.py | 78 --------------- InvenTree/track/serializers.py | 23 ----- InvenTree/track/templates/track/create.html | 5 - InvenTree/track/templates/track/delete.html | 11 --- InvenTree/track/templates/track/detail.html | 59 ----------- InvenTree/track/templates/track/edit.html | 5 - InvenTree/track/templates/track/index.html | 39 -------- InvenTree/track/templates/track/new.html | 5 - InvenTree/track/tests.py | 3 - InvenTree/track/urls.py | 41 -------- InvenTree/track/views.py | 63 ------------ Makefile | 1 - 31 files changed, 167 insertions(+), 640 deletions(-) create mode 100644 InvenTree/stock/migrations/0007_auto_20180416_0853.py delete mode 100644 InvenTree/track/__init__.py delete mode 100644 InvenTree/track/admin.py delete mode 100644 InvenTree/track/api.py delete mode 100644 InvenTree/track/apps.py delete mode 100644 InvenTree/track/forms.py delete mode 100644 InvenTree/track/migrations/0001_initial.py delete mode 100644 InvenTree/track/migrations/0002_auto_20180413_1440.py delete mode 100644 InvenTree/track/migrations/0003_auto_20180415_0147.py delete mode 100644 InvenTree/track/migrations/0004_parttrackinginfo_user.py delete mode 100644 InvenTree/track/migrations/0005_uniquepart_url.py delete mode 100644 InvenTree/track/migrations/__init__.py delete mode 100644 InvenTree/track/models.py delete mode 100644 InvenTree/track/serializers.py delete mode 100644 InvenTree/track/templates/track/create.html delete mode 100644 InvenTree/track/templates/track/delete.html delete mode 100644 InvenTree/track/templates/track/detail.html delete mode 100644 InvenTree/track/templates/track/edit.html delete mode 100644 InvenTree/track/templates/track/index.html delete mode 100644 InvenTree/track/templates/track/new.html delete mode 100644 InvenTree/track/tests.py delete mode 100644 InvenTree/track/urls.py delete mode 100644 InvenTree/track/views.py diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index b1ce5ec324..bc458b112d 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -48,9 +48,8 @@ INSTALLED_APPS = [ # InvenTree apps 'part.apps.PartConfig', - 'supplier.apps.SupplierConfig', 'stock.apps.StockConfig', - 'track.apps.TrackConfig', + 'supplier.apps.SupplierConfig', ] MIDDLEWARE = [ diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 202aa32447..18eb9784ec 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -16,8 +16,6 @@ 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 @@ -70,7 +68,6 @@ urlpatterns = [ 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')), @@ -84,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')] +urlpatterns += [url(r'^.*$', RedirectView.as_view(url='/part/', permanent=False), name='part-index')] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 9bd13a0d40..d9bdc50311 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -111,7 +111,8 @@ class Part(models.Model): units = models.CharField(max_length=20, default="pcs", blank=True) # 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) 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/stock/forms.py b/InvenTree/stock/forms.py index e05cdfc4fa..51655b4182 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -44,6 +44,10 @@ class EditStockItemForm(forms.ModelForm): 'part', 'supplier_part', 'location', + 'belongs_to', + 'serial', + 'batch', 'quantity', - 'status' + 'status', + 'customer' ] diff --git a/InvenTree/stock/migrations/0007_auto_20180416_0853.py b/InvenTree/stock/migrations/0007_auto_20180416_0853.py new file mode 100644 index 0000000000..0aa701198a --- /dev/null +++ b/InvenTree/stock/migrations/0007_auto_20180416_0853.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-16 08:53 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('supplier', '0006_auto_20180415_1011'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('stock', '0006_auto_20180415_0302'), + ] + + operations = [ + migrations.CreateModel( + name='StockItemTracking', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField(auto_now_add=True)), + ('title', models.CharField(max_length=250)), + ('description', models.CharField(blank=True, max_length=1024)), + ], + ), + migrations.RemoveField( + model_name='historicalstockitem', + name='history_user', + ), + migrations.RemoveField( + model_name='historicalstockitem', + name='location', + ), + migrations.RemoveField( + model_name='historicalstockitem', + name='part', + ), + migrations.RemoveField( + model_name='historicalstockitem', + name='stocktake_user', + ), + migrations.RemoveField( + model_name='historicalstockitem', + name='supplier_part', + ), + migrations.AddField( + model_name='stockitem', + name='batch', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AddField( + model_name='stockitem', + name='belongs_to', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='owned_parts', to='stock.StockItem'), + ), + migrations.AddField( + model_name='stockitem', + name='customer', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stockitems', to='supplier.Customer'), + ), + migrations.AddField( + model_name='stockitem', + name='serial', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.DeleteModel( + name='HistoricalStockItem', + ), + migrations.AddField( + model_name='stockitemtracking', + name='item', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tracking_info', to='stock.StockItem'), + ), + migrations.AddField( + model_name='stockitemtracking', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index c7ad5f0b9b..d6bd3ba867 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -3,9 +3,9 @@ from django.utils.translation import ugettext as _ from django.db import models, transaction from django.core.validators import MinValueValidator from django.contrib.auth.models import User -from simple_history.models import HistoricalRecords from supplier.models import SupplierPart +from supplier.models import Customer from part.models import Part from InvenTree.models import InvenTreeTree @@ -50,19 +50,43 @@ def before_delete_stock_location(sender, instance, using, **kwargs): class StockItem(models.Model): + """ + A 'StockItem' instance represents a quantity of physical instances of a part. + It may exist in a StockLocation, or as part of a sub-assembly installed into another StockItem + StockItems may be tracked using batch or serial numbers. + If a serial number is assigned, then StockItem cannot have a quantity other than 1 + """ def get_absolute_url(self): return '/stock/item/{id}/'.format(id=self.id) + # The 'master' copy of the part of which this stock item is an instance part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='locations') + # The 'supplier part' used in this instance. May be null if no supplier parts are defined the master part supplier_part = models.ForeignKey(SupplierPart, blank=True, null=True, on_delete=models.SET_NULL) + # Where the part is stored. If the part has been used to build another stock item, the location may not make sense location = models.ForeignKey(StockLocation, on_delete=models.DO_NOTHING, related_name='items', blank=True, null=True) + # If this StockItem belongs to another StockItem (e.g. as part of a sub-assembly) + belongs_to = models.ForeignKey('self', on_delete=models.DO_NOTHING, + related_name='owned_parts', blank=True, null=True) + + # The StockItem may be assigned to a particular customer + customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, related_name='stockitems', blank=True, null=True) + + # Optional serial number + serial = models.PositiveIntegerField(blank=True, null=True) + + # Optional batch information + batch = models.CharField(max_length=100, blank=True) + + # Quantity of this stock item. Value may be overridden by other settings quantity = models.PositiveIntegerField(validators=[MinValueValidator(0)]) + # Last time this item was updated (set automagically) updated = models.DateField(auto_now=True) # last time the stock was checked / counted @@ -97,9 +121,6 @@ class StockItem(models.Model): infinite = models.BooleanField(default=False) - # History of this item - history = HistoricalRecords() - @transaction.atomic def stocktake(self, count, user): """ Perform item stocktake. @@ -147,3 +168,30 @@ class StockItem(models.Model): n=self.quantity, part=self.part.name, loc=self.location.name) + + +class StockItemTracking(models.Model): + """ Stock tracking entry + """ + + # Stock item + item = models.ForeignKey(StockItem, on_delete=models.CASCADE, + related_name='tracking_info') + + # Date this entry was created (cannot be edited) + date = models.DateField(auto_now_add=True, editable=False) + + # Short-form title for this tracking entry + title = models.CharField(max_length=250) + + # Optional longer description + description = models.CharField(max_length=1024, blank=True) + + # Which user created this tracking entry? + user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True) + + # TODO + # image = models.ImageField(upload_to=func, max_length=255, null=True, blank=True) + + # TODO + # file = models.FileField() diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index 92fb3258cb..c7955f0d7f 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -11,10 +11,35 @@
    +{% if item.belongs_to %} + + + + +{% elif item.location %} +{% endif %} +{% if item.serial %} + + + + +{% endif %} +{% if item.batch %} + + + + +{% endif %} +{% if item.customer %} + + + + +{% endif %} diff --git a/InvenTree/track/__init__.py b/InvenTree/track/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/InvenTree/track/admin.py b/InvenTree/track/admin.py deleted file mode 100644 index 751ad5711c..0000000000 --- a/InvenTree/track/admin.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.contrib import admin - -from .models import UniquePart, PartTrackingInfo - - -class UniquePartAdmin(admin.ModelAdmin): - list_display = ('part', 'serial', 'status', 'creation_date') - - -class PartTrackingAdmin(admin.ModelAdmin): - list_display = ('part', 'date', 'title') - - -admin.site.register(UniquePart, UniquePartAdmin) -admin.site.register(PartTrackingInfo, PartTrackingAdmin) diff --git a/InvenTree/track/api.py b/InvenTree/track/api.py deleted file mode 100644 index c36f710e0d..0000000000 --- a/InvenTree/track/api.py +++ /dev/null @@ -1,97 +0,0 @@ -from django_filters.rest_framework import FilterSet, DjangoFilterBackend -from django_filters import NumberFilter - -from rest_framework import generics, permissions - -from .models import UniquePart, PartTrackingInfo -from .serializers import UniquePartSerializer, PartTrackingInfoSerializer - - -class UniquePartDetail(generics.RetrieveUpdateDestroyAPIView): - """ - - get: - Return a single UniquePart - - post: - Update a UniquePart - - delete: - Remove a UniquePart - - """ - - queryset = UniquePart.objects.all() - serializer_class = UniquePartSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - - -class UniquePartFilter(FilterSet): - # Filter based on serial number - min_sn = NumberFilter(name='serial', lookup_expr='gte') - max_sn = NumberFilter(name='serial', lookup_expr='lte') - - class Meta: - model = UniquePart - fields = ['serial', 'part', 'customer'] - - -class UniquePartList(generics.ListCreateAPIView): - """ - - get: - Return a list of all UniqueParts - (with optional query filter) - - post: - Create a new UniquePart - """ - - queryset = UniquePart.objects.all() - serializer_class = UniquePartSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - filter_backends = (DjangoFilterBackend,) - filter_class = UniquePartFilter - - -class PartTrackingDetail(generics.RetrieveUpdateDestroyAPIView): - """ - - get: - Return a single PartTrackingInfo object - - post: - Update a PartTrackingInfo object - - delete: - Remove a PartTrackingInfo object - """ - - queryset = PartTrackingInfo.objects.all() - serializer_class = PartTrackingInfoSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - - -class PartTrackingFilter(FilterSet): - - class Meta: - model = PartTrackingInfo - fields = ['part'] - - -class PartTrackingList(generics.ListCreateAPIView): - """ - - get: - Return a list of all PartTrackingInfo objects - (with optional query filter) - - post: - Create a new PartTrackingInfo object - """ - - queryset = PartTrackingInfo.objects.all() - serializer_class = PartTrackingInfoSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - filter_backends = (DjangoFilterBackend,) - filter_class = PartTrackingFilter diff --git a/InvenTree/track/apps.py b/InvenTree/track/apps.py deleted file mode 100644 index 7873ab8bfe..0000000000 --- a/InvenTree/track/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals - -from django.apps import AppConfig - - -class TrackConfig(AppConfig): - name = 'track' diff --git a/InvenTree/track/forms.py b/InvenTree/track/forms.py deleted file mode 100644 index 7ac159a510..0000000000 --- a/InvenTree/track/forms.py +++ /dev/null @@ -1,28 +0,0 @@ -from django import forms -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Submit - -from .models import UniquePart - - -class EditTrackedPartForm(forms.ModelForm): - - def __init__(self, *args, **kwargs): - super(EditTrackedPartForm, self).__init__(*args, **kwargs) - self.helper = FormHelper() - - self.helper.form_id = 'id-edit-part-form' - self.helper.form_class = 'blueForms' - self.helper.form_method = 'post' - - self.helper.add_input(Submit('submit', 'Submit')) - - class Meta: - model = UniquePart - fields = [ - 'part', - 'serial', - 'URL', - 'customer', - 'status' - ] diff --git a/InvenTree/track/migrations/0001_initial.py b/InvenTree/track/migrations/0001_initial.py deleted file mode 100644 index 8f84a6b6a4..0000000000 --- a/InvenTree/track/migrations/0001_initial.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-12 05:02 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('part', '0001_initial'), - ('supplier', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='PartTrackingInfo', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date', models.DateField(auto_now_add=True)), - ('notes', models.CharField(max_length=500)), - ], - ), - migrations.CreateModel( - name='UniquePart', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('creation_date', models.DateField(auto_now_add=True)), - ('serial', models.IntegerField()), - ('status', models.IntegerField(choices=[(0, 'In progress'), (40, 'Damaged'), (10, 'In stock'), (50, 'Destroyed'), (20, 'Shipped'), (30, 'Returned')], default=0)), - ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='supplier.Customer')), - ('part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='part.Part')), - ], - ), - migrations.AddField( - model_name='parttrackinginfo', - name='part', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tracking_info', to='track.UniquePart'), - ), - migrations.AlterUniqueTogether( - name='uniquepart', - unique_together=set([('part', 'serial')]), - ), - ] diff --git a/InvenTree/track/migrations/0002_auto_20180413_1440.py b/InvenTree/track/migrations/0002_auto_20180413_1440.py deleted file mode 100644 index bc3a2ed5ea..0000000000 --- a/InvenTree/track/migrations/0002_auto_20180413_1440.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-13 14:40 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('track', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='uniquepart', - name='part', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='serials', to='part.Part'), - ), - migrations.AlterField( - model_name='uniquepart', - name='serial', - field=models.PositiveIntegerField(), - ), - ] diff --git a/InvenTree/track/migrations/0003_auto_20180415_0147.py b/InvenTree/track/migrations/0003_auto_20180415_0147.py deleted file mode 100644 index ee74a6c253..0000000000 --- a/InvenTree/track/migrations/0003_auto_20180415_0147.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-15 01:47 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('track', '0002_auto_20180413_1440'), - ] - - operations = [ - migrations.AddField( - model_name='parttrackinginfo', - name='title', - field=models.CharField(default='tracking information', max_length=250), - preserve_default=False, - ), - migrations.AlterField( - model_name='parttrackinginfo', - name='notes', - field=models.CharField(blank=True, max_length=1024), - ), - migrations.AlterField( - model_name='uniquepart', - name='status', - field=models.IntegerField(choices=[(0, 'In progress'), (35, 'Repaired'), (40, 'Damaged'), (10, 'In stock'), (50, 'Destroyed'), (20, 'Shipped'), (30, 'Returned')], default=0), - ), - ] diff --git a/InvenTree/track/migrations/0004_parttrackinginfo_user.py b/InvenTree/track/migrations/0004_parttrackinginfo_user.py deleted file mode 100644 index 76ddb5e2a3..0000000000 --- a/InvenTree/track/migrations/0004_parttrackinginfo_user.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-15 01:50 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('track', '0003_auto_20180415_0147'), - ] - - operations = [ - migrations.AddField( - model_name='parttrackinginfo', - name='user', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/InvenTree/track/migrations/0005_uniquepart_url.py b/InvenTree/track/migrations/0005_uniquepart_url.py deleted file mode 100644 index 50ea92eaa6..0000000000 --- a/InvenTree/track/migrations/0005_uniquepart_url.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-04-15 15:21 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('track', '0004_parttrackinginfo_user'), - ] - - operations = [ - migrations.AddField( - model_name='uniquepart', - name='URL', - field=models.URLField(blank=True), - ), - ] diff --git a/InvenTree/track/migrations/__init__.py b/InvenTree/track/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/InvenTree/track/models.py b/InvenTree/track/models.py deleted file mode 100644 index 8df03994a1..0000000000 --- a/InvenTree/track/models.py +++ /dev/null @@ -1,78 +0,0 @@ -from __future__ import unicode_literals -from django.utils.translation import ugettext as _ -from django.db import models - -from django.contrib.auth.models import User - -from supplier.models import Customer -from part.models import Part - - -class UniquePart(models.Model): - """ A unique instance of a Part object. - Used for tracking parts based on serial numbers, - and tracking all events in the life of a part - """ - - def get_absolute_url(self): - return "/track/{id}/".format(id=self.id) - - class Meta: - # Cannot have multiple parts with same serial number - unique_together = ('part', 'serial') - - part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='serials') - - creation_date = models.DateField(auto_now_add=True, - editable=False) - - serial = models.PositiveIntegerField() - - # Provide a URL for an external link - URL = models.URLField(blank=True) - - # createdBy = models.ForeignKey(User) - - customer = models.ForeignKey(Customer, blank=True, null=True) - - # Part status types - PART_IN_PROGRESS = 0 - PART_IN_STOCK = 10 - PART_SHIPPED = 20 - PART_RETURNED = 30 - PART_REPAIRED = 35 - PART_DAMAGED = 40 - PART_DESTROYED = 50 - - PART_STATUS_CODES = { - PART_IN_PROGRESS: _("In progress"), - PART_IN_STOCK: _("In stock"), - PART_SHIPPED: _("Shipped"), - PART_RETURNED: _("Returned"), - PART_REPAIRED: _("Repaired"), - PART_DAMAGED: _("Damaged"), - PART_DESTROYED: _("Destroyed") - } - - status = models.IntegerField(default=PART_IN_PROGRESS, choices=PART_STATUS_CODES.items()) - - def __str__(self): - return "{pn} - # {sn}".format(pn=self.part.name, - sn=self.serial) - - -class PartTrackingInfo(models.Model): - """ Single data-point in the life of a UniquePart - Each time something happens to the UniquePart, - a new PartTrackingInfo object should be created. - """ - - part = models.ForeignKey(UniquePart, on_delete=models.CASCADE, related_name='tracking_info') - - date = models.DateField(auto_now_add=True, editable=False) - - title = models.CharField(max_length=250) - - notes = models.CharField(max_length=1024, blank=True) - - user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True) diff --git a/InvenTree/track/serializers.py b/InvenTree/track/serializers.py deleted file mode 100644 index 34f15e227d..0000000000 --- a/InvenTree/track/serializers.py +++ /dev/null @@ -1,23 +0,0 @@ -from rest_framework import serializers - -from .models import UniquePart, PartTrackingInfo - - -class UniquePartSerializer(serializers.HyperlinkedModelSerializer): - - class Meta: - model = UniquePart - fields = ['url', - 'part', - 'creation_date', - 'serial', - # 'createdBy', - 'customer', - 'status'] - - -class PartTrackingInfoSerializer(serializers.HyperlinkedModelSerializer): - - class Meta: - model = PartTrackingInfo - fields = '__all__' diff --git a/InvenTree/track/templates/track/create.html b/InvenTree/track/templates/track/create.html deleted file mode 100644 index 0760c37871..0000000000 --- a/InvenTree/track/templates/track/create.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "create_edit_obj.html" %} - -{% block obj_title %} -Create a new tracked part -{% endblock %} diff --git a/InvenTree/track/templates/track/delete.html b/InvenTree/track/templates/track/delete.html deleted file mode 100644 index 13490c000c..0000000000 --- a/InvenTree/track/templates/track/delete.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "delete_obj.html" %} - -{% block del_title %} -Are you sure you want to delete tracking info for this part? -{% endblock %} - -{% block del_body %} - -All tracking information for part {{ track.part.name }} SN-{{ track.serial }} will be deleted. - -{% endblock %} \ No newline at end of file diff --git a/InvenTree/track/templates/track/detail.html b/InvenTree/track/templates/track/detail.html deleted file mode 100644 index 6626cd4b49..0000000000 --- a/InvenTree/track/templates/track/detail.html +++ /dev/null @@ -1,59 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -

    Part tracking information

    - -
    LinkCreation Date {{ part.creation_date }}
    URL{{ part.URL }}
    CustomerPart {{ item.part.name }}
    Belongs To{{ item.belongs_to }}
    Location {{ item.location.name }}
    Serial{{ item.serial }}
    Batch{{ item.batch }}
    Customer{{ item.customer.name }}
    Quantity {{ item.quantity }}
    - - - - - - - - - - - - - {% if part.URL %} - - - - - {% endif %} - {% if part.customer %} - - - - - {% endif %} - - - - -
    Part{{ part.part.name }}
    Serial Number{{ part.serial }}
    Creation Date{{ part.creation_date }}
    URL{{ part.URL }}
    Customer{{ part.customer }}
    Status{{ part.get_status_display }}
    - -{% if part.tracking_info.all|length > 0 %} -

    Tracking information:

    -
      -{% for info in part.tracking_info.all %} -
    • - {{ info.title }} - {% if info.note %}
      {{ info.notes }}{% endif %}{{ info.date }} -
    • -{% endfor %} -
    -{% endif %} - - - -{% endblock %} \ No newline at end of file diff --git a/InvenTree/track/templates/track/edit.html b/InvenTree/track/templates/track/edit.html deleted file mode 100644 index 9f23c94e9d..0000000000 --- a/InvenTree/track/templates/track/edit.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "create_edit_obj.html" %} - -{% block obj_title %} -Edit tracked part information -{% endblock %} diff --git a/InvenTree/track/templates/track/index.html b/InvenTree/track/templates/track/index.html deleted file mode 100644 index 94ed748a4a..0000000000 --- a/InvenTree/track/templates/track/index.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -

    Part Tracking

    - - - -{% if is_paginated %} - -{% endif %} - - - -{% endblock %} \ No newline at end of file diff --git a/InvenTree/track/templates/track/new.html b/InvenTree/track/templates/track/new.html deleted file mode 100644 index 9cd5dfea90..0000000000 --- a/InvenTree/track/templates/track/new.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -{% endblock %} \ No newline at end of file diff --git a/InvenTree/track/tests.py b/InvenTree/track/tests.py deleted file mode 100644 index a79ca8be56..0000000000 --- a/InvenTree/track/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -# from django.test import TestCase - -# Create your tests here. diff --git a/InvenTree/track/urls.py b/InvenTree/track/urls.py deleted file mode 100644 index 5f7332f417..0000000000 --- a/InvenTree/track/urls.py +++ /dev/null @@ -1,41 +0,0 @@ -from django.conf.urls import url, include - -from . import views - -""" -TODO - Implement JSON API for part serial number tracking -part_track_api_urls = [ - url(r'^(?P[0-9]+)/?$', api.PartTrackingDetail.as_view(), name='parttrackinginfo-detail'), - - url(r'^\?.*/?$', api.PartTrackingList.as_view()), - url(r'^$', api.PartTrackingList.as_view()) -] - -unique_api_urls = [ - - # Detail for a single unique part - url(r'^(?P[0-9]+)/?$', api.UniquePartDetail.as_view(), name='uniquepart-detail'), - - # List all unique parts, with optional filters - url(r'^\?.*/?$', api.UniquePartList.as_view()), - url(r'^$', api.UniquePartList.as_view()), -] -""" - -track_detail_urls = [ - url(r'^edit/?', views.TrackEdit.as_view(), name='track-edit'), - url(r'^delete/?', views.TrackDelete.as_view(), name='track-delete'), - - url('^.*$', views.TrackDetail.as_view(), name='track-detail'), -] - -tracking_urls = [ - # Detail view - url(r'^(?P\d+)/', include(track_detail_urls)), - - # Create a new tracking item - url(r'^new/?', views.TrackCreate.as_view(), name='track-create'), - - # List ALL tracked items - url(r'^.*$', views.TrackIndex.as_view(), name='track-index'), -] diff --git a/InvenTree/track/views.py b/InvenTree/track/views.py deleted file mode 100644 index 5af7b57f77..0000000000 --- a/InvenTree/track/views.py +++ /dev/null @@ -1,63 +0,0 @@ -from django.shortcuts import get_object_or_404 -from django.http import HttpResponseRedirect - -from django.views.generic import DetailView, ListView -from django.views.generic.edit import UpdateView, DeleteView, CreateView - -from part.models import Part -from .models import UniquePart - -from .forms import EditTrackedPartForm - - -class TrackIndex(ListView): - model = UniquePart - template_name = 'track/index.html' - context_object_name = 'parts' - paginate_by = 50 - - def get_queryset(self): - return UniquePart.objects.order_by('part__name', 'serial') - - -class TrackDetail(DetailView): - queryset = UniquePart.objects.all() - template_name = 'track/detail.html' - context_object_name = 'part' - - -class TrackCreate(CreateView): - model = UniquePart - form_class = EditTrackedPartForm - template_name = 'track/create.html' - context_object_name = 'part' - - def get_initial(self): - initials = super(TrackCreate, self).get_initial().copy() - - part_id = self.request.GET.get('part', None) - - if part_id: - initials['part'] = get_object_or_404(Part, pk=part_id) - - return initials - - -class TrackEdit(UpdateView): - model = UniquePart - form_class = EditTrackedPartForm - template_name = 'track/edit.html' - context_obect_name = 'part' - - -class TrackDelete(DeleteView): - model = UniquePart - success_url = '/track' - template_name = 'track/delete.html' - context_object_name = 'track' - - def post(self, request, *args, **kwargs): - if 'confirm' in request.POST: - return super(TrackDelete, self).post(request, *args, **kwargs) - else: - return HttpResponseRedirect(self.get_object().get_absolute_url()) diff --git a/Makefile b/Makefile index ac9cb51062..e2a51d7779 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,6 @@ migrate: python InvenTree/manage.py makemigrations part python InvenTree/manage.py makemigrations stock python InvenTree/manage.py makemigrations supplier - python InvenTree/manage.py makemigrations track python InvenTree/manage.py migrate --run-syncdb python InvenTree/manage.py check From 09b357976d7483ab0b5e44bf279d5999606b2265 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 20:17:58 +1000 Subject: [PATCH 11/18] Admin for part tracking - Added 'has_tracking_info' function for StockItem --- InvenTree/stock/admin.py | 6 +++++- InvenTree/stock/models.py | 4 ++++ InvenTree/stock/templates/stock/item.html | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index 99d70ff9f4..a3e5c188f4 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from simple_history.admin import SimpleHistoryAdmin from .models import StockLocation, StockItem - +from .models import StockItemTracking class LocationAdmin(admin.ModelAdmin): list_display = ('name', 'pathstring', 'description') @@ -12,5 +12,9 @@ class StockItemAdmin(SimpleHistoryAdmin): list_display = ('part', 'quantity', 'location', 'status', 'updated') +class StockTrackingAdmin(admin.ModelAdmin): + list_display = ('item', 'date', 'title') + admin.site.register(StockLocation, LocationAdmin) admin.site.register(StockItem, StockItemAdmin) +admin.site.register(StockItemTracking, StockTrackingAdmin) \ No newline at end of file diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index d6bd3ba867..689a04523c 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -121,6 +121,10 @@ class StockItem(models.Model): infinite = models.BooleanField(default=False) + @property + def has_tracking_info(self): + return self.tracking_info.all().count() > 0 + @transaction.atomic def stocktake(self, count, user): """ Perform item stocktake. diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index c7955f0d7f..7266e692f4 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -72,6 +72,21 @@ {% endif %} +{% if item.has_tracking_info %} +

    Stock Tracking

    +
      +{% for track in item.tracking_info.all %} +
    • + {{ track.title }} + {% if track.description %} +

      {{ track.description }} + {% endif %} + {{ track.date }} +
    • +{% endfor %} +
    +{% endif %} +
    From 9191d1ae219540371cf6c6b6d25cc320c19b9e57 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 20:46:21 +1000 Subject: [PATCH 12/18] PEP fixes --- InvenTree/stock/admin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index a3e5c188f4..b8d55d44c7 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -4,6 +4,7 @@ from simple_history.admin import SimpleHistoryAdmin from .models import StockLocation, StockItem from .models import StockItemTracking + class LocationAdmin(admin.ModelAdmin): list_display = ('name', 'pathstring', 'description') @@ -15,6 +16,7 @@ class StockItemAdmin(SimpleHistoryAdmin): class StockTrackingAdmin(admin.ModelAdmin): list_display = ('item', 'date', 'title') + admin.site.register(StockLocation, LocationAdmin) admin.site.register(StockItem, StockItemAdmin) -admin.site.register(StockItemTracking, StockTrackingAdmin) \ No newline at end of file +admin.site.register(StockItemTracking, StockTrackingAdmin) From 202a8e65b92df8d202ca778cd645e3b58a04d219 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 21:07:57 +1000 Subject: [PATCH 13/18] Added URL field to stock item --- InvenTree/stock/forms.py | 3 ++- .../stock/migrations/0008_stockitem_url.py | 20 +++++++++++++++++++ InvenTree/stock/models.py | 3 +++ InvenTree/stock/templates/stock/item.html | 6 ++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 InvenTree/stock/migrations/0008_stockitem_url.py diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 51655b4182..c7defebaa3 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -49,5 +49,6 @@ class EditStockItemForm(forms.ModelForm): 'batch', 'quantity', 'status', - 'customer' + 'customer', + 'URL', ] diff --git a/InvenTree/stock/migrations/0008_stockitem_url.py b/InvenTree/stock/migrations/0008_stockitem_url.py new file mode 100644 index 0000000000..93e79fb4e9 --- /dev/null +++ b/InvenTree/stock/migrations/0008_stockitem_url.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-16 11:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0007_auto_20180416_0853'), + ] + + operations = [ + migrations.AddField( + model_name='stockitem', + name='URL', + field=models.URLField(blank=True, max_length=125), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 689a04523c..8d0f01a409 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -80,6 +80,9 @@ class StockItem(models.Model): # Optional serial number serial = models.PositiveIntegerField(blank=True, null=True) + # Optional URL to link to external resource + URL = models.URLField(max_length=125, blank=True) + # Optional batch information batch = models.CharField(max_length=100, blank=True) diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index 7266e692f4..65394d623d 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -44,6 +44,12 @@ Quantity {{ item.quantity }} +{% if item.URL %} + + URL + {{ item.URL }} + +{% endif %} {% if item.supplier_part %} Supplier Part From e43439ef5b6bd0b89c394fbdf1a08cb40d7e4963 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Apr 2018 21:49:38 +1000 Subject: [PATCH 14/18] Function to calculate how many parts can be made with current stock --- InvenTree/part/models.py | 38 +++++++++++++++++--- InvenTree/part/templates/part/bom.html | 2 +- InvenTree/part/templates/part/delete.html | 4 +-- InvenTree/part/templates/part/part_base.html | 10 ++++-- InvenTree/part/templates/part/tabs.html | 8 ++--- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index d9bdc50311..bf421cef3b 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -132,8 +132,34 @@ class Part(models.Model): verbose_name_plural = "Parts" @property - def tracked_parts(self): - return self.serials.order_by('serial') + def available_stock(self): + """ + Return the total available stock. + This subtracts stock which is already allocated + """ + + # TODO - For now, just return total stock count + return self.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 + + 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 stock(self): @@ -149,11 +175,15 @@ class Part(models.Model): return result['total'] @property - def bomItemCount(self): + def has_bom(self): + return self.bom_item_count > 0 + + @property + def bom_item_count(self): return self.bom_items.all().count() @property - def usedInCount(self): + def used_in_count(self): return self.used_in.all().count() """ diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index 24046b5ca4..31ae8919bd 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -15,7 +15,7 @@ {{ sub_part.name }} {{ sub_part.description }} - {{ bom_item.quantity }} + {{ bom_item.quantity }}{{ bom_item.sub_part.available_stock }} {% endwith %} {% endfor %} 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:

      {% for child in part.used_in.all %}
    • {{ child.part.name }} - {{ child.part.description }}
    • diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 7cd4120f44..7df825c3bb 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -40,9 +40,15 @@ {% endif %} - Stock - {{ part.stock }} + Available Stock + {{ part.available_stock }} + {% if part.has_bom %} + + Can Build + {{ part.can_build }} + + {% endif %}
    diff --git a/InvenTree/part/templates/part/tabs.html b/InvenTree/part/templates/part/tabs.html index 64e5089f10..073460e58a 100644 --- a/InvenTree/part/templates/part/tabs.html +++ b/InvenTree/part/templates/part/tabs.html @@ -1,11 +1,11 @@