mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
142a7659bd
@ -225,7 +225,7 @@ class Build(models.Model):
|
|||||||
'Removed {n} items to build {m} x {part}'.format(
|
'Removed {n} items to build {m} x {part}'.format(
|
||||||
n=item.quantity,
|
n=item.quantity,
|
||||||
m=self.quantity,
|
m=self.quantity,
|
||||||
part=self.part.name
|
part=self.part.full_name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -370,7 +370,7 @@ class BuildItem(models.Model):
|
|||||||
|
|
||||||
if self.stock_item is not None and self.stock_item.part is not None:
|
if self.stock_item is not None and self.stock_item.part is not None:
|
||||||
if self.stock_item.part not in self.build.part.required_parts():
|
if self.stock_item.part not in self.build.part.required_parts():
|
||||||
errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'".format(p=self.build.part.name))]
|
errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'".format(p=self.build.part.full_name))]
|
||||||
|
|
||||||
if self.stock_item is not None and self.quantity > self.stock_item.quantity:
|
if self.stock_item is not None and self.quantity > self.stock_item.quantity:
|
||||||
errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})".format(
|
errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})".format(
|
||||||
|
@ -44,7 +44,7 @@ class BuildItemSerializer(InvenTreeModelSerializer):
|
|||||||
""" Serializes a BuildItem object """
|
""" Serializes a BuildItem object """
|
||||||
|
|
||||||
part = serializers.IntegerField(source='stock_item.part.pk', read_only=True)
|
part = serializers.IntegerField(source='stock_item.part.pk', read_only=True)
|
||||||
part_name = serializers.CharField(source='stock_item.part.name', read_only=True)
|
part_name = serializers.CharField(source='stock_item.part.full_name', read_only=True)
|
||||||
stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True)
|
stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -14,7 +14,7 @@ InvenTree | Allocate Parts
|
|||||||
<div class='col-sm-6'>
|
<div class='col-sm-6'>
|
||||||
|
|
||||||
<h4><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></h4>
|
<h4><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></h4>
|
||||||
{{ build.quantity }} x {{ build.part.name }}
|
{{ build.quantity }} x {{ build.part.lonname }}
|
||||||
</div>
|
</div>
|
||||||
<div class='col-sm-6'>
|
<div class='col-sm-6'>
|
||||||
<div class='btn-group' style='float: right;'>
|
<div class='btn-group' style='float: right;'>
|
||||||
@ -47,7 +47,7 @@ InvenTree | Allocate Parts
|
|||||||
loadAllocationTable(
|
loadAllocationTable(
|
||||||
$("#allocate-table-id-{{ bom_item.sub_part.id }}"),
|
$("#allocate-table-id-{{ bom_item.sub_part.id }}"),
|
||||||
{{ bom_item.sub_part.id }},
|
{{ bom_item.sub_part.id }},
|
||||||
"{{ bom_item.sub_part.name }}",
|
"{{ bom_item.sub_part.full_name }}",
|
||||||
"{% url 'api-build-item-list' %}?build={{ build.id }}&part={{ bom_item.sub_part.id }}",
|
"{% url 'api-build-item-list' %}?build={{ build.id }}&part={{ bom_item.sub_part.id }}",
|
||||||
{% multiply build.quantity bom_item.quantity %},
|
{% multiply build.quantity bom_item.quantity %},
|
||||||
$("#new-item-{{ bom_item.sub_part.id }}")
|
$("#new-item-{{ bom_item.sub_part.id }}")
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<img class='hover-img-large' src="{% if item.sub_part.image %}{{ item.sub_part.image.url }}{% endif %}">
|
<img class='hover-img-large' src="{% if item.sub_part.image %}{{ item.sub_part.image.url }}{% endif %}">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ item.sub_part.name }}<br>
|
{{ item.sub_part.full_name }}<br>
|
||||||
<small><i>{{ item.sub_part.description }}</i></small>
|
<small><i>{{ item.sub_part.description }}</i></small>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
<b>Build: {{ build.title }}</b> - {{ build.quantity }} x {{ build.part.name }}
|
<b>Build: {{ build.title }}</b> - {{ build.quantity }} x {{ build.part.full_name }}
|
||||||
<br><br>
|
<br><br>
|
||||||
Automatically allocate stock to this build?
|
Automatically allocate stock to this build?
|
||||||
<hr>
|
<hr>
|
||||||
@ -27,7 +27,7 @@ Automatically allocate stock to this build?
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ item.stock_item.part.name }}<br>
|
{{ item.stock_item.part.full_name }}<br>
|
||||||
<i>{{ item.stock_item.part.description }}</i>
|
<i>{{ item.stock_item.part.description }}</i>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.quantity }}</td>
|
<td>{{ item.quantity }}</td>
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
{% for build in builds %}
|
{% for build in builds %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></td>
|
<td><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></td>
|
||||||
<td><a href="{% url 'part-build' build.part.id %}">{{ build.part.name }}</a></td>
|
<td><a href="{% url 'part-build' build.part.id %}">{{ build.part.full_name }}</a></td>
|
||||||
<td>{{ build.quantity }}</td>
|
<td>{{ build.quantity }}</td>
|
||||||
<td>{% include "build_status.html" with build=build %}
|
<td>{% include "build_status.html" with build=build %}
|
||||||
{% if completed %}
|
{% if completed %}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "modal_form.html" %}
|
{% extends "modal_form.html" %}
|
||||||
|
|
||||||
{% block pre_form_content %}
|
{% block pre_form_content %}
|
||||||
<b>Build: {{ build.title }}</b> - {{ build.quantity }} x {{ build.part.name }}
|
<b>Build: {{ build.title }}</b> - {{ build.quantity }} x {{ build.part.full_name }}
|
||||||
<br>
|
<br>
|
||||||
Are you sure you want to mark this build as complete?
|
Are you sure you want to mark this build as complete?
|
||||||
<hr>
|
<hr>
|
||||||
@ -24,7 +24,7 @@ The following items will be removed from stock:
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ item.stock_item.part.name }}<br>
|
{{ item.stock_item.part.full_name }}<br>
|
||||||
<i>{{ item.stock_item.part.description }}</i>
|
<i>{{ item.stock_item.part.description }}</i>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.quantity }}</td>
|
<td>{{ item.quantity }}</td>
|
||||||
@ -42,7 +42,7 @@ The following items will be created:
|
|||||||
<img class='hover-img-thumb' src='{{ build.part.image.url }}'>
|
<img class='hover-img-thumb' src='{{ build.part.image.url }}'>
|
||||||
<img class='hover-img-large' src='{{ build.part.image.url }}'>
|
<img class='hover-img-large' src='{{ build.part.image.url }}'>
|
||||||
</a>
|
</a>
|
||||||
{{ build.quantity }} x {{ build.part.name }}
|
{{ build.quantity }} x {{ build.part.full_name }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -11,7 +11,7 @@ InvenTree | Build - {{ build }}
|
|||||||
<div class='col-sm-6'>
|
<div class='col-sm-6'>
|
||||||
<h3>Build Details</h3>
|
<h3>Build Details</h3>
|
||||||
<p><b>{{ build.title }}</b>{% include "build_status.html" with build=build %}</p>
|
<p><b>{{ build.title }}</b>{% include "build_status.html" with build=build %}</p>
|
||||||
<p>Building {{ build.quantity }} × {{ build.part.name }}</p>
|
<p>Building {{ build.quantity }} × {{ build.part.full_name }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class='col-sm-6'>
|
<div class='col-sm-6'>
|
||||||
<h3>
|
<h3>
|
||||||
@ -40,7 +40,7 @@ InvenTree | Build - {{ build }}
|
|||||||
<td>Title</td><td>{{ build.title }}</td>
|
<td>Title</td><td>{{ build.title }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Part</td><td><a href="{% url 'part-build' build.part.id %}">{{ build.part.name }}</a></td>
|
<td>Part</td><td><a href="{% url 'part-build' build.part.id %}">{{ build.part.full_name }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Quantity</td><td>{{ build.quantity }}</td>
|
<td>Quantity</td><td>{{ build.quantity }}</td>
|
||||||
@ -109,7 +109,7 @@ InvenTree | Build - {{ build }}
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for item in build.required_parts %}
|
{% for item in build.required_parts %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'part-detail' item.part.id %}">{{ item.part.name }}</a></td>
|
<td><a href="{% url 'part-detail' item.part.id %}">{{ item.part.full_name }}</a></td>
|
||||||
<td>{{ item.quantity }}</td>
|
<td>{{ item.quantity }}</td>
|
||||||
<td>{{ item.part.total_stock }}</td>
|
<td>{{ item.part.total_stock }}</td>
|
||||||
<td>{{ item.allocated }}</td>
|
<td>{{ item.allocated }}</td>
|
||||||
|
@ -7,7 +7,7 @@ Are you sure you want to delete company '{{ company.name }}'?
|
|||||||
If this supplier is deleted, these supplier part entries will also be deleted.</p>
|
If this supplier is deleted, these supplier part entries will also be deleted.</p>
|
||||||
<ul class='list-group'>
|
<ul class='list-group'>
|
||||||
{% for part in company.parts.all %}
|
{% for part in company.parts.all %}
|
||||||
<li class='list-group-item'><b>{{ part.SKU }}</b> - <i>{{ part.part.name }}</i></li>
|
<li class='list-group-item'><b>{{ part.SKU }}</b> - <i>{{ part.part.full_name }}</i></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: 'part_detail.name',
|
field: 'part_detail.full_name',
|
||||||
title: 'Part',
|
title: 'Part',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
return imageHoverIcon(row.part_detail.image_url) + renderLink(value, '/part/' + row.part + '/suppliers/');
|
return imageHoverIcon(row.part_detail.image_url) + renderLink(value, '/part/' + row.part + '/suppliers/');
|
||||||
@ -74,7 +74,18 @@
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
field: 'MPN',
|
field: 'MPN',
|
||||||
title: 'MPN',
|
title: 'MPN',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'URL',
|
||||||
|
title: 'URL',
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
if (value) {
|
||||||
|
return renderLink(value, value);
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
],
|
],
|
||||||
url: "{% url 'api-part-supplier-list' %}"
|
url: "{% url 'api-part-supplier-list' %}"
|
||||||
});
|
});
|
||||||
|
@ -37,7 +37,7 @@ InvenTree | {{ company.name }} - Parts
|
|||||||
<td>Internal Part</td>
|
<td>Internal Part</td>
|
||||||
<td>
|
<td>
|
||||||
{% if part.part %}
|
{% if part.part %}
|
||||||
<a href="{% url 'part-suppliers' part.part.id %}">{{ part.part.name }}</a>
|
<a href="{% url 'part-suppliers' part.part.id %}">{{ part.part.full_name }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -9,7 +9,7 @@ from .models import BomItem
|
|||||||
|
|
||||||
class PartAdmin(ImportExportModelAdmin):
|
class PartAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
list_display = ('long_name', 'IPN', 'description', 'total_stock', 'category')
|
list_display = ('full_name', 'description', 'total_stock', 'category')
|
||||||
|
|
||||||
|
|
||||||
class PartCategoryAdmin(ImportExportModelAdmin):
|
class PartCategoryAdmin(ImportExportModelAdmin):
|
||||||
|
@ -24,6 +24,21 @@ class PartImageForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BomValidateForm(HelperForm):
|
||||||
|
""" Simple confirmation form for BOM validation.
|
||||||
|
User is presented with a single checkbox input,
|
||||||
|
to confirm that the BOM for this part is valid
|
||||||
|
"""
|
||||||
|
|
||||||
|
validate = forms.BooleanField(required=False, initial=False, help_text='Confirm that the BOM is correct')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Part
|
||||||
|
fields = [
|
||||||
|
'validate'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class BomExportForm(HelperForm):
|
class BomExportForm(HelperForm):
|
||||||
|
|
||||||
# TODO - Define these choices somewhere else, and import them here
|
# TODO - Define these choices somewhere else, and import them here
|
||||||
|
31
InvenTree/part/migrations/0022_auto_20190512_1246.py
Normal file
31
InvenTree/part/migrations/0022_auto_20190512_1246.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 2.2 on 2019-05-12 02:46
|
||||||
|
|
||||||
|
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),
|
||||||
|
('part', '0021_auto_20190510_2220'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='part',
|
||||||
|
name='bom_checked_by',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='boms_checked', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='part',
|
||||||
|
name='bom_checked_date',
|
||||||
|
field=models.DateField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='part',
|
||||||
|
name='bom_checksum',
|
||||||
|
field=models.CharField(blank=True, help_text='Stored BOM checksum', max_length=128),
|
||||||
|
),
|
||||||
|
]
|
@ -16,7 +16,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
from django.contrib.staticfiles.templatetags.staticfiles import static
|
from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||||
@ -24,7 +24,9 @@ from django.contrib.auth.models import User
|
|||||||
from django.db.models.signals import pre_delete
|
from django.db.models.signals import pre_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from fuzzywuzzy import fuzz
|
from fuzzywuzzy import fuzz
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from InvenTree import helpers
|
from InvenTree import helpers
|
||||||
from InvenTree import validators
|
from InvenTree import validators
|
||||||
@ -202,14 +204,30 @@ class Part(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{n} - {d}".format(n=self.long_name, d=self.description)
|
return "{n} - {d}".format(n=self.full_name, d=self.description)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def long_name(self):
|
def full_name(self):
|
||||||
name = self.name
|
""" Format a 'full name' for this Part.
|
||||||
|
|
||||||
|
- IPN (if not null)
|
||||||
|
- Part name
|
||||||
|
- Part variant (if not null)
|
||||||
|
|
||||||
|
Elements are joined by the | character
|
||||||
|
"""
|
||||||
|
|
||||||
|
elements = []
|
||||||
|
|
||||||
|
if self.IPN:
|
||||||
|
elements.append(self.IPN)
|
||||||
|
|
||||||
|
elements.append(self.name)
|
||||||
|
|
||||||
if self.variant:
|
if self.variant:
|
||||||
name += " | " + self.variant
|
elements.append(self.variant)
|
||||||
return name
|
|
||||||
|
return ' | '.join(elements)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
""" Return the web URL for viewing this part """
|
""" Return the web URL for viewing this part """
|
||||||
@ -284,23 +302,23 @@ class Part(models.Model):
|
|||||||
|
|
||||||
consumable = models.BooleanField(default=True, help_text='Can this part be used to build other parts?')
|
consumable = models.BooleanField(default=True, help_text='Can this part be used to build other parts?')
|
||||||
|
|
||||||
# Is this part "trackable"?
|
|
||||||
# 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, help_text='Does this part have tracking for unique items?')
|
trackable = models.BooleanField(default=False, help_text='Does this part have tracking for unique items?')
|
||||||
|
|
||||||
# Is this part "purchaseable"?
|
|
||||||
purchaseable = models.BooleanField(default=True, help_text='Can this part be purchased from external suppliers?')
|
purchaseable = models.BooleanField(default=True, help_text='Can this part be purchased from external suppliers?')
|
||||||
|
|
||||||
# Can this part be sold to customers?
|
|
||||||
salable = models.BooleanField(default=False, help_text="Can this part be sold to customers?")
|
salable = models.BooleanField(default=False, help_text="Can this part be sold to customers?")
|
||||||
|
|
||||||
# Is this part active?
|
|
||||||
active = models.BooleanField(default=True, help_text='Is this part active?')
|
active = models.BooleanField(default=True, help_text='Is this part active?')
|
||||||
|
|
||||||
notes = models.TextField(blank=True)
|
notes = models.TextField(blank=True)
|
||||||
|
|
||||||
|
bom_checksum = models.CharField(max_length=128, blank=True, help_text='Stored BOM checksum')
|
||||||
|
|
||||||
|
bom_checked_by = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True,
|
||||||
|
related_name='boms_checked')
|
||||||
|
|
||||||
|
bom_checked_date = models.DateField(blank=True, null=True)
|
||||||
|
|
||||||
def format_barcode(self):
|
def format_barcode(self):
|
||||||
""" Return a JSON string for formatting a barcode for this Part object """
|
""" Return a JSON string for formatting a barcode for this Part object """
|
||||||
|
|
||||||
@ -451,13 +469,59 @@ class Part(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def bom_count(self):
|
def bom_count(self):
|
||||||
|
""" Return the number of items contained in the BOM for this part """
|
||||||
return self.bom_items.count()
|
return self.bom_items.count()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def used_in_count(self):
|
def used_in_count(self):
|
||||||
|
""" Return the number of part BOMs that this part appears in """
|
||||||
return self.used_in.count()
|
return self.used_in.count()
|
||||||
|
|
||||||
|
def get_bom_hash(self):
|
||||||
|
""" Return a checksum hash for the BOM for this part.
|
||||||
|
Used to determine if the BOM has changed (and needs to be signed off!)
|
||||||
|
|
||||||
|
For hash is calculated from the following fields of each BOM item:
|
||||||
|
|
||||||
|
- Part.full_name (if the part name changes, the BOM checksum is invalidated)
|
||||||
|
- quantity
|
||||||
|
- Note field
|
||||||
|
|
||||||
|
returns a string representation of a hash object which can be compared with a stored value
|
||||||
|
"""
|
||||||
|
|
||||||
|
hash = hashlib.md5('bom seed'.encode())
|
||||||
|
|
||||||
|
for item in self.bom_items.all():
|
||||||
|
hash.update(str(item.sub_part.full_name).encode())
|
||||||
|
hash.update(str(item.quantity).encode())
|
||||||
|
hash.update(str(item.note).encode())
|
||||||
|
|
||||||
|
return str(hash.digest())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_bom_valid(self):
|
||||||
|
""" Check if the BOM is 'valid' - if the calculated checksum matches the stored value
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.get_bom_hash() == self.bom_checksum
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def validate_bom(self, user):
|
||||||
|
""" Validate the BOM (mark the BOM as validated by the given User.
|
||||||
|
|
||||||
|
- Calculates and stores the hash for the BOM
|
||||||
|
- Saves the current date and the checking user
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.bom_checksum = self.get_bom_hash()
|
||||||
|
self.bom_checked_by = user
|
||||||
|
self.bom_checked_date = datetime.now().date()
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
def required_parts(self):
|
def required_parts(self):
|
||||||
|
""" Return a list of parts required to make this part (list of BOM items) """
|
||||||
parts = []
|
parts = []
|
||||||
for bom in self.bom_items.all():
|
for bom in self.bom_items.all():
|
||||||
parts.append(bom.sub_part)
|
parts.append(bom.sub_part)
|
||||||
@ -465,7 +529,7 @@ class Part(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def supplier_count(self):
|
def supplier_count(self):
|
||||||
# Return the number of supplier parts available for this part
|
""" Return the number of supplier parts available for this part """
|
||||||
return self.supplier_parts.count()
|
return self.supplier_parts.count()
|
||||||
|
|
||||||
def export_bom(self, **kwargs):
|
def export_bom(self, **kwargs):
|
||||||
@ -480,7 +544,7 @@ class Part(models.Model):
|
|||||||
for it in self.bom_items.all():
|
for it in self.bom_items.all():
|
||||||
line = []
|
line = []
|
||||||
|
|
||||||
line.append(it.sub_part.name)
|
line.append(it.sub_part.full_name)
|
||||||
line.append(it.sub_part.description)
|
line.append(it.sub_part.description)
|
||||||
line.append(it.quantity)
|
line.append(it.quantity)
|
||||||
line.append(it.note)
|
line.append(it.note)
|
||||||
@ -611,8 +675,8 @@ class BomItem(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{n} x {child} to make {parent}".format(
|
return "{n} x {child} to make {parent}".format(
|
||||||
parent=self.part.name,
|
parent=self.part.full_name,
|
||||||
child=self.sub_part.name,
|
child=self.sub_part.full_name,
|
||||||
n=self.quantity)
|
n=self.quantity)
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,8 +40,7 @@ class PartBriefSerializer(serializers.ModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'pk',
|
'pk',
|
||||||
'url',
|
'url',
|
||||||
'name',
|
'full_name',
|
||||||
'variant',
|
|
||||||
'description',
|
'description',
|
||||||
'available_stock',
|
'available_stock',
|
||||||
'image_url',
|
'image_url',
|
||||||
@ -63,6 +62,7 @@ class PartSerializer(serializers.ModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'pk',
|
'pk',
|
||||||
'url', # Link to the part detail page
|
'url', # Link to the part detail page
|
||||||
|
'full_name',
|
||||||
'name',
|
'name',
|
||||||
'variant',
|
'variant',
|
||||||
'image_url',
|
'image_url',
|
||||||
@ -86,7 +86,7 @@ class PartSerializer(serializers.ModelSerializer):
|
|||||||
class PartStarSerializer(InvenTreeModelSerializer):
|
class PartStarSerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for a PartStar object """
|
""" Serializer for a PartStar object """
|
||||||
|
|
||||||
partname = serializers.CharField(source='part.name', read_only=True)
|
partname = serializers.CharField(source='part.full_name', read_only=True)
|
||||||
username = serializers.CharField(source='user.username', read_only=True)
|
username = serializers.CharField(source='user.username', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -145,6 +145,7 @@ class SupplierPartSerializer(serializers.ModelSerializer):
|
|||||||
'SKU',
|
'SKU',
|
||||||
'manufacturer',
|
'manufacturer',
|
||||||
'MPN',
|
'MPN',
|
||||||
|
'URL',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
{% for allocation in part.build_allocation %}
|
{% for allocation in part.build_allocation %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'build-detail' allocation.build.id %}">{{ allocation.build.title }}</a></td>
|
<td><a href="{% url 'build-detail' allocation.build.id %}">{{ allocation.build.title }}</a></td>
|
||||||
<td>{{ allocation.build.quantity }} × <a href="{% url 'part-detail' allocation.build.part.id %}">{{ allocation.build.part.name }}</a></td>
|
<td>{{ allocation.build.quantity }} × <a href="{% url 'part-detail' allocation.build.part.id %}">{{ allocation.build.part.full_name }}</a></td>
|
||||||
<td>{{ allocation.quantity }}</td>
|
<td>{{ allocation.quantity }}</td>
|
||||||
<td>{% include "build_status.html" with build=allocation.build %}</td>
|
<td>{% include "build_status.html" with build=allocation.build %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -4,6 +4,6 @@ Deleting this entry will remove the BOM row from the following part:
|
|||||||
|
|
||||||
<ul class='list-group'>
|
<ul class='list-group'>
|
||||||
<li class='list-group-item'>
|
<li class='list-group-item'>
|
||||||
<b>{{ item.part.name }}</b> - <i>{{ item.part.description }}</i>
|
<b>{{ item.part.full_name }}</b> - <i>{{ item.part.description }}</i>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
<h3>BOM Item</h3>
|
<h3>BOM Item</h3>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<tr><td>Parent</td><td><a href="{% url 'part-bom' item.part.id %}">{{ item.part.name }}</a></td></tr>
|
<tr><td>Parent</td><td><a href="{% url 'part-bom' item.part.id %}">{{ item.part.full_name }}</a></td></tr>
|
||||||
<tr><td>Child</td><td><a href="{% url 'part-used-in' item.sub_part.id %}">{{ item.sub_part.name }}</a></td></tr>
|
<tr><td>Child</td><td><a href="{% url 'part-used-in' item.sub_part.id %}">{{ item.sub_part.full_name }}</a></td></tr>
|
||||||
<tr><td>Quantity</td><td>{{ item.quantity }}</td></tr>
|
<tr><td>Quantity</td><td>{{ item.quantity }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -11,6 +11,21 @@
|
|||||||
|
|
||||||
<h3>Bill of Materials</h3>
|
<h3>Bill of Materials</h3>
|
||||||
|
|
||||||
|
{% if part.bom_checked_date %}
|
||||||
|
{% if part.is_bom_valid %}
|
||||||
|
<div class='alert alert-block alert-info'>
|
||||||
|
{% else %}
|
||||||
|
<div class='alert alert-block alert-danger'>
|
||||||
|
The BOM for <i>{{ part.full_name }}</i> has changed, and must be validated.<br>
|
||||||
|
{% endif %}
|
||||||
|
The BOM for <i>{{ part.full_name }}</i> was last checked by {{ part.bom_checked_by }} on {{ part.bom_checked_date }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class='alert alert-danger alert-block'>
|
||||||
|
<b>The BOM for <i>{{ part.full_name }}</i> has not been validated.</b>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div id='button-toolbar'>
|
<div id='button-toolbar'>
|
||||||
{% if editing_enabled %}
|
{% if editing_enabled %}
|
||||||
<div class='btn-group' style='float: right;'>
|
<div class='btn-group' style='float: right;'>
|
||||||
@ -24,6 +39,9 @@
|
|||||||
<span class='caret'></span>
|
<span class='caret'></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class='dropdown-menu'>
|
<ul class='dropdown-menu'>
|
||||||
|
{% if part.is_bom_valid == False %}
|
||||||
|
<li><a href='#' id='validate-bom' title='Validate BOM'>Validate BOM</a></li>
|
||||||
|
{% endif %}
|
||||||
<li><a href='#' id='edit-bom' title='Edit BOM'>Edit BOM</a></li>
|
<li><a href='#' id='edit-bom' title='Edit BOM'>Edit BOM</a></li>
|
||||||
<li><a href='#' id='export-bom' title='Export BOM'>Export BOM</a></li>
|
<li><a href='#' id='export-bom' title='Export BOM'>Export BOM</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -72,6 +90,15 @@
|
|||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
|
$("#validate-bom").click(function() {
|
||||||
|
launchModalForm(
|
||||||
|
"{% url 'bom-validate' part.id %}",
|
||||||
|
{
|
||||||
|
reload: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
$("#edit-bom").click(function () {
|
$("#edit-bom").click(function () {
|
||||||
location.href = "{% url 'part-bom' part.id %}?edit=True";
|
location.href = "{% url 'part-bom' part.id %}?edit=True";
|
||||||
});
|
});
|
||||||
|
5
InvenTree/part/templates/part/bom_validate.html
Normal file
5
InvenTree/part/templates/part/bom_validate.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% extends "modal_form.html" %}
|
||||||
|
|
||||||
|
{% block pre_form_content %}
|
||||||
|
Confirm that the Bill of Materials (BOM) is valid for:<br><i>{{ part.full_name }}</i>
|
||||||
|
{% endblock %}
|
@ -27,7 +27,7 @@ the top level 'Parts' category.
|
|||||||
</p>
|
</p>
|
||||||
<ul class='list-group'>
|
<ul class='list-group'>
|
||||||
{% for part in category.parts.all %}
|
{% for part in category.parts.all %}
|
||||||
<li class='list-group-item'><b>{{ part.long_name }}</b> - <i>{{ part.description }}</i></li>
|
<li class='list-group-item'><b>{{ part.full_name }}</b> - <i>{{ part.description }}</i></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
@ -10,7 +10,7 @@
|
|||||||
<ul class='list-group'>
|
<ul class='list-group'>
|
||||||
{% for match in matches %}
|
{% for match in matches %}
|
||||||
<li class='list-group-item list-group-item-condensed'>
|
<li class='list-group-item list-group-item-condensed'>
|
||||||
{{ match.part.name }} - <i>{{ match.part.description }}</i> ({{ match.ratio }}%)
|
{{ match.part.full_name }} - <i>{{ match.part.description }}</i> ({{ match.ratio }}%)
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
{% if part.active %}
|
{% if part.active %}
|
||||||
<li><a href='#' id='duplicate-part' title='Duplicate Part'>Duplicate</a></li>
|
<li><a href='#' id='duplicate-part' title='Duplicate Part'>Duplicate</a></li>
|
||||||
<li><a href="#" id='edit-part' title='Edit part'>Edit</a></li>
|
<li><a href="#" id='edit-part' title='Edit part'>Edit</a></li>
|
||||||
<li><a href='#' id='stocktake-part' title='Stocktake'>Stocktake</a></li>
|
|
||||||
<hr>
|
<hr>
|
||||||
<li><a href="#" id='deactivate-part' title='Deactivate part'>Deactivate</a></li>
|
<li><a href="#" id='deactivate-part' title='Deactivate part'>Deactivate</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -34,7 +33,7 @@
|
|||||||
<table class='table table-striped'>
|
<table class='table table-striped'>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Part name</td>
|
<td>Part name</td>
|
||||||
<td>{{ part.long_name }}</td>
|
<td>{{ part.full_name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
@ -46,6 +45,12 @@
|
|||||||
<td>{{ part.IPN }}</td>
|
<td>{{ part.IPN }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if part.URL %}
|
||||||
|
<tr>
|
||||||
|
<td>URL</td>
|
||||||
|
<td><a href="{{ part.URL }}">{{ part.URL }}</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Category</td>
|
<td>Category</td>
|
||||||
<td>
|
<td>
|
||||||
@ -147,7 +152,7 @@
|
|||||||
$('#activate-part').click(function() {
|
$('#activate-part').click(function() {
|
||||||
showQuestionDialog(
|
showQuestionDialog(
|
||||||
'Activate Part?',
|
'Activate Part?',
|
||||||
'Are you sure you wish to reactivate {{ part.long_name }}?',
|
'Are you sure you wish to reactivate {{ part.full_name }}?',
|
||||||
{
|
{
|
||||||
accept_text: 'Activate',
|
accept_text: 'Activate',
|
||||||
accept: function() {
|
accept: function() {
|
||||||
@ -169,7 +174,7 @@
|
|||||||
$('#deactivate-part').click(function() {
|
$('#deactivate-part').click(function() {
|
||||||
showQuestionDialog(
|
showQuestionDialog(
|
||||||
'Deactivate Part?',
|
'Deactivate Part?',
|
||||||
`Are you sure you wish to deactivate {{ part.long_name }}?<br>
|
`Are you sure you wish to deactivate {{ part.full_name }}?<br>
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
accept_text: 'Deactivate',
|
accept_text: 'Deactivate',
|
||||||
@ -198,16 +203,4 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#stocktake-part').click(function() {
|
|
||||||
adjustStock({
|
|
||||||
action: 'stocktake',
|
|
||||||
query: {
|
|
||||||
part: {{ part.id }},
|
|
||||||
},
|
|
||||||
success: function() {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
{% if part %}
|
{% if part %}
|
||||||
InvenTree | Part - {{ part.long_name }}
|
InvenTree | Part - {{ part.full_name }}
|
||||||
{% elif category %}
|
{% elif category %}
|
||||||
InvenTree | Part Category - {{ category }}
|
InvenTree | Part Category - {{ category }}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
{% if part.active == False %}
|
{% if part.active == False %}
|
||||||
<div class='alert alert-danger' style='display: block;'>
|
<div class='alert alert-danger' style='display: block;'>
|
||||||
This part ({{ part.long_name }}) is not active:
|
This part ({{ part.full_name }}) is not active:
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<h4>
|
<h4>
|
||||||
{{ part.long_name }}
|
{{ part.full_name }}
|
||||||
</h4>
|
</h4>
|
||||||
{% if part.variant %}
|
{% if part.variant %}
|
||||||
<p>Variant: {{ part.variant }}</p>
|
<p>Variant: {{ part.variant }}</p>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
Are you sure you want to delete part '{{ part.long_name }}'?
|
Are you sure you want to delete part '{{ part.full_name }}'?
|
||||||
|
|
||||||
{% if part.used_in_count %}
|
{% if part.used_in_count %}
|
||||||
<p>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:
|
<p>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:
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{% for child in part.used_in.all %}
|
{% for child in part.used_in.all %}
|
||||||
<li class='list-group-item'>{{ child.part.name }} - {{ child.part.description }}</li>
|
<li class='list-group-item'>{{ child.part.full_name }} - {{ child.part.description }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -30,5 +30,5 @@ Are you sure you want to delete part '{{ part.long_name }}'?
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if part.serials.all|length > 0 %}
|
{% if part.serials.all|length > 0 %}
|
||||||
<p>There are {{ part.serials.all|length }} unique parts tracked for '{{ part.long_name }}'. Deleting this part will permanently remove this tracking information.</p>
|
<p>There are {{ part.serials.all|length }} unique parts tracked for '{{ part.full_name }}'. Deleting this part will permanently remove this tracking information.</p>
|
||||||
{% endif %}
|
{% endif %}
|
@ -48,7 +48,7 @@
|
|||||||
$("#supplier-table").bootstrapTable({
|
$("#supplier-table").bootstrapTable({
|
||||||
sortable: true,
|
sortable: true,
|
||||||
search: true,
|
search: true,
|
||||||
formatNoMatches: function() { return "No supplier parts available for {{ part.long_name }}"; },
|
formatNoMatches: function() { return "No supplier parts available for {{ part.full_name }}"; },
|
||||||
queryParams: function(p) {
|
queryParams: function(p) {
|
||||||
return {
|
return {
|
||||||
part: {{ part.id }}
|
part: {{ part.id }}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.buildable %}
|
{% if part.buildable %}
|
||||||
<li{% ifequal tab 'bom' %} class="active"{% endifequal %}>
|
<li{% ifequal tab 'bom' %} class="active"{% endifequal %}>
|
||||||
<a href="{% url 'part-bom' part.id %}">BOM<span class="badge">{{ part.bom_count }}</span></a></li>
|
<a href="{% url 'part-bom' part.id %}">BOM<span class="badge{% if part.is_bom_valid == False %} badge-alert{% endif %}">{{ part.bom_count }}</span></a></li>
|
||||||
<li{% ifequal tab 'build' %} class="active"{% endifequal %}>
|
<li{% ifequal tab 'build' %} class="active"{% endifequal %}>
|
||||||
<a href="{% url 'part-build' part.id %}">Build<span class='badge'>{{ part.active_builds|length }}</span></a></li>
|
<a href="{% url 'part-build' part.id %}">Build<span class='badge'>{{ part.active_builds|length }}</span></a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% include 'part/tabs.html' with tab='track' %}
|
{% include 'part/tabs.html' with tab='track' %}
|
||||||
|
|
||||||
Part tracking for {{ part.long_name }}
|
Part tracking for {{ part.full_name }}
|
||||||
|
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
$("#used-table").bootstrapTable({
|
$("#used-table").bootstrapTable({
|
||||||
sortable: true,
|
sortable: true,
|
||||||
search: true,
|
search: true,
|
||||||
formatNoMatches: function() { return "{{ part.long_name }} is not used to make any other parts"; },
|
formatNoMatches: function() { return "{{ part.full_name }} is not used to make any other parts"; },
|
||||||
queryParams: function(p) {
|
queryParams: function(p) {
|
||||||
return {
|
return {
|
||||||
sub_part: {{ part.id }}
|
sub_part: {{ part.id }}
|
||||||
@ -43,7 +43,7 @@
|
|||||||
field: 'part_detail',
|
field: 'part_detail',
|
||||||
title: 'Part',
|
title: 'Part',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
return imageHoverIcon(row.part_detail.image_url) + renderLink(value.name, value.url + 'bom/');
|
return imageHoverIcon(row.part_detail.image_url) + renderLink(value.full_name, value.url + 'bom/');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -35,6 +35,7 @@ part_detail_urls = [
|
|||||||
url(r'^edit/?', views.PartEdit.as_view(), name='part-edit'),
|
url(r'^edit/?', views.PartEdit.as_view(), name='part-edit'),
|
||||||
url(r'^delete/?', views.PartDelete.as_view(), name='part-delete'),
|
url(r'^delete/?', views.PartDelete.as_view(), name='part-delete'),
|
||||||
url(r'^bom-export/?', views.BomDownload.as_view(), name='bom-export'),
|
url(r'^bom-export/?', views.BomDownload.as_view(), name='bom-export'),
|
||||||
|
url(r'^validate-bom/', views.BomValidate.as_view(), name='bom-validate'),
|
||||||
|
|
||||||
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
||||||
url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'),
|
url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'),
|
||||||
|
@ -331,12 +331,50 @@ class PartEdit(AjaxUpdateView):
|
|||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
||||||
|
class BomValidate(AjaxUpdateView):
|
||||||
|
""" Modal form view for validating a part BOM """
|
||||||
|
|
||||||
|
model = Part
|
||||||
|
ajax_form_title = "Validate BOM"
|
||||||
|
ajax_template_name = 'part/bom_validate.html'
|
||||||
|
context_object_name = 'part'
|
||||||
|
form_class = part_forms.BomValidateForm
|
||||||
|
|
||||||
|
def get_context(self):
|
||||||
|
return {
|
||||||
|
'part': self.get_object(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
form = self.get_form()
|
||||||
|
|
||||||
|
return self.renderJsonResponse(request, form, context=self.get_context())
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
form = self.get_form()
|
||||||
|
part = self.get_object()
|
||||||
|
|
||||||
|
confirmed = str2bool(request.POST.get('validate', False))
|
||||||
|
|
||||||
|
if confirmed:
|
||||||
|
part.validate_bom(request.user)
|
||||||
|
else:
|
||||||
|
form.errors['validate'] = ['Confirm that the BOM is valid']
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'form_valid': confirmed
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.renderJsonResponse(request, form, data, context=self.get_context())
|
||||||
|
|
||||||
|
|
||||||
class BomExport(AjaxView):
|
class BomExport(AjaxView):
|
||||||
|
|
||||||
model = Part
|
model = Part
|
||||||
ajax_form_title = 'Export BOM'
|
ajax_form_title = 'Export BOM'
|
||||||
ajax_template_name = 'part/bom_export.html'
|
ajax_template_name = 'part/bom_export.html'
|
||||||
context_object_name = 'part'
|
|
||||||
form_class = part_forms.BomExportForm
|
form_class = part_forms.BomExportForm
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
@ -345,17 +383,6 @@ class BomExport(AjaxView):
|
|||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
form = self.form_class()
|
form = self.form_class()
|
||||||
|
|
||||||
"""
|
|
||||||
part = self.get_object()
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'part': part
|
|
||||||
}
|
|
||||||
|
|
||||||
if request.is_ajax():
|
|
||||||
passs
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.renderJsonResponse(request, form)
|
return self.renderJsonResponse(request, form)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
color: #ffcc00;
|
color: #ffcc00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* CSS overrides for treeview */
|
/* CSS overrides for treeview */
|
||||||
.expand-icon {
|
.expand-icon {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@ -97,6 +98,10 @@
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-alert {
|
||||||
|
background-color: #f33;
|
||||||
|
}
|
||||||
|
|
||||||
.part-thumb {
|
.part-thumb {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
@ -219,6 +224,10 @@
|
|||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
|
@ -93,7 +93,7 @@ function loadBomTable(table, options) {
|
|||||||
title: 'Part',
|
title: 'Part',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
return imageHoverIcon(value.image_url) + renderLink(value.name, value.url);
|
return imageHoverIcon(value.image_url) + renderLink(value.full_name, value.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -123,11 +123,7 @@ function loadPartTable(table, url, options={}) {
|
|||||||
title: 'Part',
|
title: 'Part',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
var name = row.name;
|
var name = row.full_name;
|
||||||
|
|
||||||
if (row.variant) {
|
|
||||||
name = name + " | " + row.variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
var display = imageHoverIcon(row.image_url) + renderLink(name, row.url);
|
var display = imageHoverIcon(row.image_url) + renderLink(name, row.url);
|
||||||
if (!row.active) {
|
if (!row.active) {
|
||||||
|
@ -393,7 +393,7 @@ function loadStockTable(table, options) {
|
|||||||
visible: false,
|
visible: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'part.name',
|
field: 'part.full_name',
|
||||||
title: 'Part',
|
title: 'Part',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
@ -180,7 +180,7 @@ class StockItem(models.Model):
|
|||||||
reverse('api-stock-detail', kwargs={'pk': self.id}),
|
reverse('api-stock-detail', kwargs={'pk': self.id}),
|
||||||
{
|
{
|
||||||
'part_id': self.part.id,
|
'part_id': self.part.id,
|
||||||
'part_name': self.part.name
|
'part_name': self.part.full_name
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -464,7 +464,7 @@ class StockItem(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
s = '{n} x {part}'.format(
|
s = '{n} x {part}'.format(
|
||||||
n=self.quantity,
|
n=self.quantity,
|
||||||
part=self.part.name)
|
part=self.part.full_name)
|
||||||
|
|
||||||
if self.location:
|
if self.location:
|
||||||
s += ' @ {loc}'.format(loc=self.location.name)
|
s += ' @ {loc}'.format(loc=self.location.name)
|
||||||
|
@ -32,7 +32,7 @@ class StockItemSerializerBrief(serializers.ModelSerializer):
|
|||||||
""" Brief serializers for a StockItem """
|
""" Brief serializers for a StockItem """
|
||||||
|
|
||||||
location_name = serializers.CharField(source='location', read_only=True)
|
location_name = serializers.CharField(source='location', read_only=True)
|
||||||
part_name = serializers.CharField(source='part.name', read_only=True)
|
part_name = serializers.CharField(source='part.full_name', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = StockItem
|
model = StockItem
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div class='row'>
|
<div class='row'>
|
||||||
<div class='col-sm-6'>
|
<div class='col-sm-6'>
|
||||||
<h3>Stock Item Details</h3>
|
<h3>Stock Item Details</h3>
|
||||||
<p><i>{{ item.quantity }} × {{ item.part.name }}</i></p>
|
<p><i>{{ item.quantity }} × {{ item.part.full_name }}</i></p>
|
||||||
<p>
|
<p>
|
||||||
<div class='btn-group'>
|
<div class='btn-group'>
|
||||||
{% include "qr_button.html" %}
|
{% include "qr_button.html" %}
|
||||||
@ -39,7 +39,7 @@
|
|||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Part</td>
|
<td>Part</td>
|
||||||
<td><a href="{% url 'part-stock' item.part.id %}">{{ item.part.name }}</td>
|
<td><a href="{% url 'part-stock' item.part.id %}">{{ item.part.full_name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if item.belongs_to %}
|
{% if item.belongs_to %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -2,4 +2,4 @@ Are you sure you want to delete this stock item?
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
This will remove <b>{{ item.quantity }}</b> units of <b>{{ item.part.name }}</b> from stock.
|
This will remove <b>{{ item.quantity }}</b> units of <b>{{ item.part.full_name }}</b> from stock.
|
||||||
|
@ -30,7 +30,7 @@ If this location is deleted, these items will be moved to the top level 'Stock'
|
|||||||
|
|
||||||
<ul class='list-group'>
|
<ul class='list-group'>
|
||||||
{% for item in location.stock_items.all %}
|
{% for item in location.stock_items.all %}
|
||||||
<li class='list-group-item'><b>{{ item.part.name }}</b> - <i>{{ item.part.description }}</i><span class='badge'>{{ item.quantity }}</span></li>
|
<li class='list-group-item'><b>{{ item.part.full_name }}</b> - <i>{{ item.part.description }}</i><span class='badge'>{{ item.quantity }}</span></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% for part in parts %}
|
{% for part in parts %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'part-detail' part.id %}">{{ part.long_name }}</a></td>
|
<td><a href="{% url 'part-detail' part.id %}">{{ part.full_name }}</a></td>
|
||||||
<td>{{ part.description }}</td>
|
<td>{{ part.description }}</td>
|
||||||
<td>{{ part.total_stock }}</td>
|
<td>{{ part.total_stock }}</td>
|
||||||
<td>{{ part.allocation_count }}</td>
|
<td>{{ part.allocation_count }}</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user