mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree
This commit is contained in:
commit
728896be6c
@ -8,7 +8,14 @@ from .models import Build
|
|||||||
|
|
||||||
class BuildAdmin(admin.ModelAdmin):
|
class BuildAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
list_display = ('status', )
|
list_display = ('part',
|
||||||
|
'status',
|
||||||
|
'batch',
|
||||||
|
'quantity',
|
||||||
|
'creation_date',
|
||||||
|
'completion_date',
|
||||||
|
'title',
|
||||||
|
'notes',
|
||||||
|
)
|
||||||
|
|
||||||
admin.site.register(Build, BuildAdmin)
|
admin.site.register(Build, BuildAdmin)
|
||||||
|
48
InvenTree/build/migrations/0004_auto_20180417_0657.py
Normal file
48
InvenTree/build/migrations/0004_auto_20180417_0657.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.12 on 2018-04-17 06:57
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0019_auto_20180416_1249'),
|
||||||
|
('build', '0003_build_part'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BuildOutput',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('quantity', models.PositiveIntegerField(default=1, help_text='Number of parts to build', validators=[django.core.validators.MinValueValidator(1)])),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='build',
|
||||||
|
name='part',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='build',
|
||||||
|
name='quantity',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='build',
|
||||||
|
name='status',
|
||||||
|
field=models.PositiveIntegerField(choices=[(40, 'Cancelled'), (10, 'Pending'), (20, 'Allocated'), (50, 'Complete'), (30, 'Holding')], default=10, validators=[django.core.validators.MinValueValidator(0)]),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='buildoutput',
|
||||||
|
name='build',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='outputs', to='build.Build'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='buildoutput',
|
||||||
|
name='part',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.Part'),
|
||||||
|
),
|
||||||
|
]
|
20
InvenTree/build/migrations/0005_buildoutput_batch.py
Normal file
20
InvenTree/build/migrations/0005_buildoutput_batch.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.12 on 2018-04-17 08:29
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('build', '0004_auto_20180417_0657'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='buildoutput',
|
||||||
|
name='batch',
|
||||||
|
field=models.CharField(blank=True, help_text='Batch code for this build output', max_length=100),
|
||||||
|
),
|
||||||
|
]
|
66
InvenTree/build/migrations/0006_auto_20180417_0933.py
Normal file
66
InvenTree/build/migrations/0006_auto_20180417_0933.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.12 on 2018-04-17 09:33
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0022_auto_20180417_0819'),
|
||||||
|
('build', '0005_buildoutput_batch'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='buildoutput',
|
||||||
|
name='build',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='buildoutput',
|
||||||
|
name='part',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='build',
|
||||||
|
name='batch',
|
||||||
|
field=models.CharField(blank=True, help_text='Batch code for this build output', max_length=100, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='build',
|
||||||
|
name='completion_date',
|
||||||
|
field=models.DateField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='build',
|
||||||
|
name='creation_date',
|
||||||
|
field=models.DateField(auto_now=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='build',
|
||||||
|
name='notes',
|
||||||
|
field=models.CharField(blank=True, max_length=500),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='build',
|
||||||
|
name='part',
|
||||||
|
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.Part'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='build',
|
||||||
|
name='quantity',
|
||||||
|
field=models.PositiveIntegerField(default=1, help_text='Number of parts to build', validators=[django.core.validators.MinValueValidator(1)]),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='build',
|
||||||
|
name='title',
|
||||||
|
field=models.CharField(default='Build title', help_text='Brief description of the build', max_length=100),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='BuildOutput',
|
||||||
|
),
|
||||||
|
]
|
21
InvenTree/build/migrations/0007_auto_20180417_1025.py
Normal file
21
InvenTree/build/migrations/0007_auto_20180417_1025.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.12 on 2018-04-17 10:25
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('build', '0006_auto_20180417_0933'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='build',
|
||||||
|
name='status',
|
||||||
|
field=models.PositiveIntegerField(choices=[(40, 'Complete'), (10, 'Pending'), (20, 'Holding'), (30, 'Cancelled')], default=10, validators=[django.core.validators.MinValueValidator(0)]),
|
||||||
|
),
|
||||||
|
]
|
@ -1,11 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
from InvenTree.helpers import ChoiceEnum
|
|
||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
|
|
||||||
class Build(models.Model):
|
class Build(models.Model):
|
||||||
@ -14,26 +14,65 @@ class Build(models.Model):
|
|||||||
Parts are then taken from stock
|
Parts are then taken from stock
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class BUILD_STATUS(ChoiceEnum):
|
# Build status codes
|
||||||
# The build is 'pending' - no action taken yet
|
PENDING = 10 # Build is pending / active
|
||||||
Pending = 10
|
HOLDING = 20 # Build is currently being held
|
||||||
|
CANCELLED = 30 # Build was cancelled
|
||||||
|
COMPLETE = 40 # Build is complete
|
||||||
|
|
||||||
# The parts required for this build have been allocated
|
BUILD_STATUS_CODES = {
|
||||||
Allocated = 20
|
PENDING : _("Pending"),
|
||||||
|
HOLDING : _("Holding"),
|
||||||
|
CANCELLED : _("Cancelled"),
|
||||||
|
COMPLETE : _("Complete"),
|
||||||
|
}
|
||||||
|
|
||||||
# The build has been cancelled (parts unallocated)
|
batch = models.CharField(max_length=100, blank=True, null=True,
|
||||||
Cancelled = 30
|
help_text='Batch code for this build output')
|
||||||
|
|
||||||
# The build is complete!
|
|
||||||
Complete = 40
|
|
||||||
|
|
||||||
# Status of the build
|
# Status of the build
|
||||||
status = models.PositiveIntegerField(default=BUILD_STATUS.Pending.value,
|
status = models.PositiveIntegerField(default=PENDING,
|
||||||
choices=BUILD_STATUS.choices())
|
choices=BUILD_STATUS_CODES.items(),
|
||||||
|
validators=[MinValueValidator(0)])
|
||||||
|
|
||||||
|
|
||||||
|
# Date the build model was 'created'
|
||||||
|
creation_date = models.DateField(auto_now=True, editable=False)
|
||||||
|
|
||||||
|
# Date the build was 'completed'
|
||||||
|
completion_date = models.DateField(null=True, blank=True)
|
||||||
|
|
||||||
|
# Brief build title
|
||||||
|
title = models.CharField(max_length=100, help_text='Brief description of the build')
|
||||||
|
|
||||||
|
# A reference to the part being built
|
||||||
|
# Only 'buildable' parts can be selected
|
||||||
part = models.ForeignKey(Part, on_delete=models.CASCADE,
|
part = models.ForeignKey(Part, on_delete=models.CASCADE,
|
||||||
related_name='builds')
|
related_name='builds',
|
||||||
|
limit_choices_to={'buildable': True},
|
||||||
|
)
|
||||||
|
|
||||||
|
# How many parts to build?
|
||||||
quantity = models.PositiveIntegerField(default=1,
|
quantity = models.PositiveIntegerField(default=1,
|
||||||
validators=[MinValueValidator(1)],
|
validators=[MinValueValidator(1)],
|
||||||
help_text='Number of parts to build')
|
help_text='Number of parts to build')
|
||||||
|
|
||||||
|
# Notes can be attached to each build output
|
||||||
|
notes = models.CharField(max_length=500, blank=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_active(self):
|
||||||
|
""" Is this build active?
|
||||||
|
An active build is either:
|
||||||
|
- Pending
|
||||||
|
- Holding
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.status in [
|
||||||
|
self.PENDING,
|
||||||
|
self.HOLDING
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_complete(self):
|
||||||
|
return self.status == self.COMPLETE
|
||||||
|
@ -24,10 +24,13 @@ class EditPartForm(forms.ModelForm):
|
|||||||
'description',
|
'description',
|
||||||
'IPN',
|
'IPN',
|
||||||
'URL',
|
'URL',
|
||||||
|
'default_location',
|
||||||
|
'default_supplier',
|
||||||
'minimum_stock',
|
'minimum_stock',
|
||||||
'buildable',
|
'buildable',
|
||||||
'trackable',
|
'trackable',
|
||||||
'purchaseable',
|
'purchaseable',
|
||||||
|
'salable',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
20
InvenTree/part/migrations/0020_part_salable.py
Normal file
20
InvenTree/part/migrations/0020_part_salable.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.12 on 2018-04-17 08:06
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0019_auto_20180416_1249'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='part',
|
||||||
|
name='salable',
|
||||||
|
field=models.BooleanField(default=False, help_text='Can this part be sold to customers?'),
|
||||||
|
),
|
||||||
|
]
|
22
InvenTree/part/migrations/0021_part_default_location.py
Normal file
22
InvenTree/part/migrations/0021_part_default_location.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.12 on 2018-04-17 08:12
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('stock', '0010_stockitem_build'),
|
||||||
|
('part', '0020_part_salable'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='part',
|
||||||
|
name='default_location',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='Where is this item normally stored?', null=True, on_delete=django.db.models.deletion.SET_NULL, to='stock.StockLocation'),
|
||||||
|
),
|
||||||
|
]
|
27
InvenTree/part/migrations/0022_auto_20180417_0819.py
Normal file
27
InvenTree/part/migrations/0022_auto_20180417_0819.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.12 on 2018-04-17 08:19
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('supplier', '0007_auto_20180416_1253'),
|
||||||
|
('part', '0021_part_default_location'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='part',
|
||||||
|
name='default_supplier',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='Default supplier part', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_parts', to='supplier.SupplierPart'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='part',
|
||||||
|
name='default_location',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='Where is this item normally stored?', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_parts', to='stock.StockLocation'),
|
||||||
|
),
|
||||||
|
]
|
@ -1,15 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
from InvenTree.models import InvenTreeTree
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
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 InvenTree.models import InvenTreeTree
|
||||||
|
# from stock.models import StockLocation
|
||||||
|
|
||||||
|
|
||||||
class PartCategory(InvenTreeTree):
|
class PartCategory(InvenTreeTree):
|
||||||
""" PartCategory provides hierarchical organization of Part objects.
|
""" PartCategory provides hierarchical organization of Part objects.
|
||||||
@ -103,6 +106,18 @@ class Part(models.Model):
|
|||||||
|
|
||||||
image = models.ImageField(upload_to=rename_part_image, max_length=255, null=True, blank=True)
|
image = models.ImageField(upload_to=rename_part_image, max_length=255, null=True, blank=True)
|
||||||
|
|
||||||
|
default_location = models.ForeignKey('stock.StockLocation', on_delete=models.SET_NULL,
|
||||||
|
blank=True, null=True,
|
||||||
|
help_text='Where is this item normally stored?',
|
||||||
|
related_name='default_parts')
|
||||||
|
|
||||||
|
# Default supplier part
|
||||||
|
default_supplier = models.ForeignKey('supplier.SupplierPart',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
blank=True, null=True,
|
||||||
|
help_text='Default supplier part',
|
||||||
|
related_name='default_parts')
|
||||||
|
|
||||||
# Minimum "allowed" stock level
|
# Minimum "allowed" stock level
|
||||||
minimum_stock = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0)], help_text='Minimum allowed stock level')
|
minimum_stock = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0)], help_text='Minimum allowed stock level')
|
||||||
|
|
||||||
@ -121,6 +136,9 @@ class Part(models.Model):
|
|||||||
# Is this part "purchaseable"?
|
# 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?")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.IPN:
|
if self.IPN:
|
||||||
return "{name} ({ipn})".format(
|
return "{name} ({ipn})".format(
|
||||||
@ -140,9 +158,11 @@ class Part(models.Model):
|
|||||||
This subtracts stock which is already allocated
|
This subtracts stock which is already allocated
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO - For now, just return total stock count
|
total = self.total_stock
|
||||||
# TODO - In future must take account of allocated stock
|
|
||||||
return self.total_stock
|
total -= self.allocation_count
|
||||||
|
|
||||||
|
return max(total, 0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def can_build(self):
|
def can_build(self):
|
||||||
@ -163,8 +183,68 @@ class Part(models.Model):
|
|||||||
if total is None or n < total:
|
if total is None or n < total:
|
||||||
total = n
|
total = n
|
||||||
|
|
||||||
|
return max(total, 0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def active_builds(self):
|
||||||
|
""" Return a list of outstanding builds.
|
||||||
|
Builds marked as 'complete' or 'cancelled' are ignored
|
||||||
|
"""
|
||||||
|
|
||||||
|
return [b for b in self.builds.all() if b.is_active]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inactive_builds(self):
|
||||||
|
""" Return a list of inactive builds
|
||||||
|
"""
|
||||||
|
|
||||||
|
return [b for b in self.builds.all() if not b.is_active]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def quantity_being_built(self):
|
||||||
|
""" Return the current number of parts currently being built
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sum([b.quantity for b in self.active_builds])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def allocated_builds(self):
|
||||||
|
""" Return list of builds to which this part is allocated
|
||||||
|
"""
|
||||||
|
|
||||||
|
builds = []
|
||||||
|
|
||||||
|
for item in self.used_in.all():
|
||||||
|
for build in item.part.active_builds:
|
||||||
|
builds.append(build)
|
||||||
|
|
||||||
|
return builds
|
||||||
|
|
||||||
|
@property
|
||||||
|
def allocated_build_count(self):
|
||||||
|
""" Return the total number of this that are allocated for builds
|
||||||
|
"""
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
for item in self.used_in.all():
|
||||||
|
for build in item.part.active_builds:
|
||||||
|
n = build.quantity * item.quantity
|
||||||
|
total += n
|
||||||
|
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
@property
|
||||||
|
def allocation_count(self):
|
||||||
|
""" Return true if any of this part is allocated
|
||||||
|
- To another build
|
||||||
|
- To a customer order
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sum([
|
||||||
|
self.allocated_build_count,
|
||||||
|
])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_stock(self):
|
def total_stock(self):
|
||||||
""" Return the total stock quantity for this part.
|
""" Return the total stock quantity for this part.
|
||||||
|
29
InvenTree/part/templates/part/allocation.html
Normal file
29
InvenTree/part/templates/part/allocation.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{% extends "part/part_base.html" %}
|
||||||
|
|
||||||
|
{% block details %}
|
||||||
|
|
||||||
|
{% include "part/tabs.html" with tab="allocation" %}
|
||||||
|
|
||||||
|
<h3>Part Allocation</h3>
|
||||||
|
|
||||||
|
{% if part.allocated_build_count > 0 %}
|
||||||
|
<h4>Allocated to Part Builds</h4>
|
||||||
|
<table class='table table-striped'>
|
||||||
|
<tr>
|
||||||
|
<th>Build</th>
|
||||||
|
<th>Making</th>
|
||||||
|
<th>Allocted</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
{% for build in part.allocated_builds %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></td>
|
||||||
|
<td><a href="{% url 'part-detail' build.part.id %}">{{ build.part.name }}</a></td>
|
||||||
|
<td>Quantity</td>
|
||||||
|
<td>{% include "part/build_status.html" with build=build %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -4,10 +4,31 @@
|
|||||||
|
|
||||||
{% include 'part/tabs.html' with tab='build' %}
|
{% include 'part/tabs.html' with tab='build' %}
|
||||||
|
|
||||||
<h3>Build Part</h3>
|
<h3>Part Builds</h3>
|
||||||
|
|
||||||
TODO
|
<table class='table table-striped'>
|
||||||
<br><br>
|
|
||||||
You can build {{ part.can_build }} of this part with current stock.
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Quantity</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Completion Date</th>
|
||||||
|
</tr>
|
||||||
|
{% if part.active_builds|length > 0 %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4"><b>Active Builds</b></td>
|
||||||
|
</tr>
|
||||||
|
{% include "part/build_list.html" with builds=part.active_builds %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if part.inactive_builds|length > 0 %}
|
||||||
|
<tr><td colspan="4"></td></tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4"><b>Inactive Builds</b></td>
|
||||||
|
</tr>
|
||||||
|
{% include "part/build_list.html" with builds=part.inactive_builds %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
10
InvenTree/part/templates/part/build_list.html
Normal file
10
InvenTree/part/templates/part/build_list.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% for build in builds %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></td>
|
||||||
|
<td>{{ build.quantity }}</td>
|
||||||
|
<td>
|
||||||
|
{% include "part/build_status.html" with build=build %}
|
||||||
|
</td>
|
||||||
|
<td>{% if build.completion_date %}{{ build.completion_date }}{% endif %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
11
InvenTree/part/templates/part/build_status.html
Normal file
11
InvenTree/part/templates/part/build_status.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% if build.status == build.PENDING %}
|
||||||
|
<span class='label label-info'>
|
||||||
|
{% elif build.status == build.HOLDING %}
|
||||||
|
<span class='label label-warning'>
|
||||||
|
{% elif build.status == build.CANCELLED %}
|
||||||
|
<span class='label label-danger'>
|
||||||
|
{% elif build.status == build.COMPLETE %}
|
||||||
|
<span class='label label-success'>
|
||||||
|
{% endif %}
|
||||||
|
{{ build.get_status_display }}
|
||||||
|
</span>
|
@ -23,21 +23,37 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if part.default_location %}
|
||||||
|
<tr>
|
||||||
|
<td>Default Location</td>
|
||||||
|
<td>{{ part.default_location.pathstring }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.default_supplier %}
|
||||||
|
<tr>
|
||||||
|
<td>Default Supplier</td>
|
||||||
|
<td>{{ part.default_supplier.supplier.name }} | {{ part.default_supplier.SKU }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Units</td>
|
<td>Units</td>
|
||||||
<td>{{ part.units }}</td>
|
<td>{{ part.units }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Buildable</td>
|
<td>Buildable</td>
|
||||||
<td>{{ part.buildable }}</td>
|
<td>{% include "yesnolabel.html" with value=part.buildable %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Trackable</td>
|
<td>Trackable</td>
|
||||||
<td>{{ part.trackable }}</td>
|
<td>{% include "yesnolabel.html" with value=part.trackable %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Purchaseable</td>
|
<td>Purchaseable</td>
|
||||||
<td>{{ part.purchaseable }}</td>
|
<td>{% include "yesnolabel.html" with value=part.purchaseable %}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Salable</td>
|
||||||
|
<td>{% include "yesnolabel.html" with value=part.salable %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if part.minimum_stock > 0 %}
|
{% if part.minimum_stock > 0 %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -22,11 +22,6 @@
|
|||||||
{% if part.description %}
|
{% if part.description %}
|
||||||
<p><i>{{ part.description }}</i></p>
|
<p><i>{{ part.description }}</i></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<table class="table table-striped">
|
|
||||||
{% if part.IPN %}
|
{% if part.IPN %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>IPN</td>
|
<td>IPN</td>
|
||||||
@ -39,15 +34,22 @@
|
|||||||
<td><a href="{{ part.URL }}">{{ part.URL }}</a></td>
|
<td><a href="{{ part.URL }}">{{ part.URL }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<h4>Stock Status - {{ part.available_stock }} available</h4>
|
||||||
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Available Stock</td>
|
<td>In Stock</td>
|
||||||
<td>
|
<td>
|
||||||
{% if part.available_stock == 0 %}
|
{% if part.stock == 0 %}
|
||||||
<span class='label label-danger'>{{ part.available_stock }}</span>
|
<span class='label label-danger'>{{ part.total_stock }}</span>
|
||||||
{% elif part.available_stock < part.minimum_stock %}
|
{% elif part.stock < part.minimum_stock %}
|
||||||
<span class='label label-warning'>{{ part.available_stock }}</span>
|
<span class='label label-warning'>{{ part.total_stock }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ part.available_stock }}
|
{{ part.total_stock }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -62,6 +64,23 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if part.quantity_being_built > 0 %}
|
||||||
|
<tr>
|
||||||
|
<td>Underway</td>
|
||||||
|
<td>{{ part.quantity_being_built }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if part.allocation_count > 0 %}
|
||||||
|
<tr>
|
||||||
|
<td>Allocated</td>
|
||||||
|
{% if part.allocation_count > part.total_stock %}
|
||||||
|
<td><span class='label label-danger'>{{ part.allocation_count }}</span>
|
||||||
|
{% else %}
|
||||||
|
{{ part.allocation_count }} {{ part.total_stock }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,10 @@
|
|||||||
{% if part.used_in_count > 0 %}
|
{% if part.used_in_count > 0 %}
|
||||||
<li{% ifequal tab 'used' %} class="active"{% endifequal %}><a href="{% url 'part-used-in' part.id %}">Used In{% if part.used_in_count > 0 %}<span class="badge">{{ part.used_in_count }}</span>{% endif %}</a></li>
|
<li{% ifequal tab 'used' %} class="active"{% endifequal %}><a href="{% url 'part-used-in' part.id %}">Used In{% if part.used_in_count > 0 %}<span class="badge">{{ part.used_in_count }}</span>{% endif %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li{% ifequal tab 'stock' %} class="active"{% endifequal %}><a href="{% url 'part-stock' part.id %}">Stock <span class="badge">{{ part.available_stock }}</span></a></li>
|
<li{% ifequal tab 'stock' %} class="active"{% endifequal %}><a href="{% url 'part-stock' part.id %}">Stock <span class="badge">{{ part.total_stock }}</span></a></li>
|
||||||
|
{% if part.allocation_count > 0 %}
|
||||||
|
<li{% ifequal tab 'allocation' %} class="active"{% endifequal %}><a href="{% url 'part-allocation' part.id %}">Allocated <span class="badge">{{ part.allocation_count }}</span></a></li>
|
||||||
|
{% endif %}
|
||||||
{% if part.purchaseable %}
|
{% if part.purchaseable %}
|
||||||
<li{% ifequal tab 'suppliers' %} class="active"{% endifequal %}><a href="{% url 'part-suppliers' part.id %}">Suppliers
|
<li{% ifequal tab 'suppliers' %} class="active"{% endifequal %}><a href="{% url 'part-suppliers' part.id %}">Suppliers
|
||||||
<span class="badge">{{ part.supplier_count }}<span>
|
<span class="badge">{{ part.supplier_count }}<span>
|
||||||
|
@ -9,11 +9,13 @@
|
|||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Part</th>
|
<th>Part</th>
|
||||||
|
<th>Uses</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for item in part.used_in.all %}
|
{% for item in part.used_in.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'part-bom' item.part.id %}">{{ item.part.name }}</a></td>
|
<td><a href="{% url 'part-bom' item.part.id %}">{{ item.part.name }}</a></td>
|
||||||
|
<td>{{ item.quantity }}</td>
|
||||||
<td>{{ item.part.description }}</td>
|
<td>{{ item.part.description }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
5
InvenTree/part/templates/yesnolabel.html
Normal file
5
InvenTree/part/templates/yesnolabel.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% if value %}
|
||||||
|
<span class='label label-success'>Yes</span>
|
||||||
|
{% else %}
|
||||||
|
<span class='label label-warning'>No</span>
|
||||||
|
{% endif %}
|
@ -44,6 +44,7 @@ part_detail_urls = [
|
|||||||
url(r'^build/?', views.PartDetail.as_view(template_name='part/build.html'), name='part-build'),
|
url(r'^build/?', views.PartDetail.as_view(template_name='part/build.html'), name='part-build'),
|
||||||
url(r'^stock/?', views.PartDetail.as_view(template_name='part/stock.html'), name='part-stock'),
|
url(r'^stock/?', views.PartDetail.as_view(template_name='part/stock.html'), name='part-stock'),
|
||||||
url(r'^used/?', views.PartDetail.as_view(template_name='part/used_in.html'), name='part-used-in'),
|
url(r'^used/?', views.PartDetail.as_view(template_name='part/used_in.html'), name='part-used-in'),
|
||||||
|
url(r'^allocation/?', views.PartDetail.as_view(template_name='part/allocation.html'), name='part-allocation'),
|
||||||
url(r'^suppliers/?', views.PartDetail.as_view(template_name='part/supplier.html'), name='part-suppliers'),
|
url(r'^suppliers/?', views.PartDetail.as_view(template_name='part/supplier.html'), name='part-suppliers'),
|
||||||
|
|
||||||
# Any other URLs go to the part detail page
|
# Any other URLs go to the part detail page
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
@ -88,7 +88,11 @@ class StockItemCreate(CreateView):
|
|||||||
loc_id = self.request.GET.get('location', None)
|
loc_id = self.request.GET.get('location', None)
|
||||||
|
|
||||||
if part_id:
|
if part_id:
|
||||||
initials['part'] = get_object_or_404(Part, pk=part_id)
|
part = get_object_or_404(Part, pk=part_id)
|
||||||
|
if part:
|
||||||
|
initials['part'] = get_object_or_404(Part, pk=part_id)
|
||||||
|
initials['location'] = part.default_location
|
||||||
|
initials['supplier_part'] = part.default_supplier
|
||||||
|
|
||||||
if loc_id:
|
if loc_id:
|
||||||
initials['location'] = get_object_or_404(StockLocation, pk=loc_id)
|
initials['location'] = get_object_or_404(StockLocation, pk=loc_id)
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
Django==1.11
|
Django>=1.11
|
||||||
pillow==3.1.2
|
pillow>=5.0.0
|
||||||
djangorestframework==3.6.2
|
djangorestframework>=3.6.2
|
||||||
django_filter==1.0.2
|
django_filter>=1.0.2
|
||||||
django-simple-history==1.8.2
|
django-simple-history>=1.8.2
|
||||||
coreapi==2.3.0
|
coreapi>=2.3.0
|
||||||
pygments==2.2.0
|
pygments>=2.2.0
|
||||||
django-crispy-forms==1.7.2
|
django-crispy-forms>=1.7.2
|
||||||
django-import-export==1.0.0
|
django-import-export>=1.0.0
|
Loading…
Reference in New Issue
Block a user