mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Added skeleton for 'build' app
This commit is contained in:
parent
86b3092b5e
commit
fa23767150
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,7 +6,7 @@ __pycache__/
|
|||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
env/
|
env/
|
||||||
build/
|
./build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
downloads/
|
downloads/
|
||||||
|
18
InvenTree/InvenTree/helpers.py
Normal file
18
InvenTree/InvenTree/helpers.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import inspect
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceEnum(Enum):
|
||||||
|
""" Helper class to provide enumerated choice values for integer fields
|
||||||
|
"""
|
||||||
|
# http://blog.richard.do/index.php/2014/02/how-to-use-enums-for-django-field-choices/
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def choices(cls):
|
||||||
|
# get all members of the class
|
||||||
|
members = inspect.getmembers(cls, lambda m: not(inspect.isroutine(m)))
|
||||||
|
# filter down to just properties
|
||||||
|
props = [m for m in members if not(m[0][:2] == '__')]
|
||||||
|
# format into django choice tuple
|
||||||
|
choices = tuple([(str(p[1].value), p[0]) for p in props])
|
||||||
|
return choices
|
@ -50,6 +50,7 @@ INSTALLED_APPS = [
|
|||||||
'part.apps.PartConfig',
|
'part.apps.PartConfig',
|
||||||
'stock.apps.StockConfig',
|
'stock.apps.StockConfig',
|
||||||
'supplier.apps.SupplierConfig',
|
'supplier.apps.SupplierConfig',
|
||||||
|
'build.apps.BuildConfig',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -11,6 +11,8 @@ 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 supplier.urls import supplier_urls
|
||||||
|
|
||||||
|
from build.urls import build_urls
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
@ -68,6 +70,7 @@ urlpatterns = [
|
|||||||
url(r'^part/', include(part_urls)),
|
url(r'^part/', include(part_urls)),
|
||||||
url(r'^stock/', include(stock_urls)),
|
url(r'^stock/', include(stock_urls)),
|
||||||
url(r'^supplier/', include(supplier_urls)),
|
url(r'^supplier/', include(supplier_urls)),
|
||||||
|
url(r'^build/', include(build_urls)),
|
||||||
|
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
|
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
|
14
InvenTree/build/admin.py
Normal file
14
InvenTree/build/admin.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Build
|
||||||
|
|
||||||
|
|
||||||
|
class BuildAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
list_display = ('status', )
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Build, BuildAdmin)
|
@ -1,7 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class ProjectConfig(AppConfig):
|
class BuildConfig(AppConfig):
|
||||||
name = 'project'
|
name = 'build'
|
40
InvenTree/build/migrations/0001_initial.py
Normal file
40
InvenTree/build/migrations/0001_initial.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-04-16 14:03
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0019_auto_20180416_1249'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Build',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('status', models.PositiveIntegerField(choices=[(b'20', b'Allocated'), (b'30', b'Cancelled'), (b'40', b'Complete'), (b'10', b'Pending')], default=10)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BuildOutput',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('part_name', models.CharField(max_length=255)),
|
||||||
|
('quantity', models.PositiveIntegerField(default=1, help_text='Number of parts to build', validators=[django.core.validators.MinValueValidator(1)])),
|
||||||
|
('build', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='outputs', to='build.Build')),
|
||||||
|
('part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.Part')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='buildoutput',
|
||||||
|
unique_together=set([('part', 'build')]),
|
||||||
|
),
|
||||||
|
]
|
36
InvenTree/build/migrations/0002_auto_20180416_1423.py
Normal file
36
InvenTree/build/migrations/0002_auto_20180416_1423.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-04-16 14:23
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('build', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='buildoutput',
|
||||||
|
unique_together=set([]),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='buildoutput',
|
||||||
|
name='build',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='buildoutput',
|
||||||
|
name='part',
|
||||||
|
),
|
||||||
|
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.DeleteModel(
|
||||||
|
name='BuildOutput',
|
||||||
|
),
|
||||||
|
]
|
23
InvenTree/build/migrations/0003_build_part.py
Normal file
23
InvenTree/build/migrations/0003_build_part.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-04-16 14:28
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0019_auto_20180416_1249'),
|
||||||
|
('build', '0002_auto_20180416_1423'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
]
|
39
InvenTree/build/models.py
Normal file
39
InvenTree/build/models.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
|
from InvenTree.helpers import ChoiceEnum
|
||||||
|
|
||||||
|
from part.models import Part
|
||||||
|
|
||||||
|
class Build(models.Model):
|
||||||
|
""" A Build object organises the creation of new parts from the component parts
|
||||||
|
It uses the part BOM to generate new parts.
|
||||||
|
Parts are then taken from stock
|
||||||
|
"""
|
||||||
|
|
||||||
|
class BUILD_STATUS(ChoiceEnum):
|
||||||
|
# The build is 'pending' - no action taken yet
|
||||||
|
Pending = 10
|
||||||
|
|
||||||
|
# The parts required for this build have been allocated
|
||||||
|
Allocated = 20
|
||||||
|
|
||||||
|
# The build has been cancelled (parts unallocated)
|
||||||
|
Cancelled = 30
|
||||||
|
|
||||||
|
# The build is complete!
|
||||||
|
Complete = 40
|
||||||
|
|
||||||
|
# Status of the build
|
||||||
|
status = models.PositiveIntegerField(default=BUILD_STATUS.Pending.value,
|
||||||
|
choices=BUILD_STATUS.choices())
|
||||||
|
|
||||||
|
part = models.ForeignKey(Part, on_delete=models.CASCADE,
|
||||||
|
related_name='builds')
|
||||||
|
|
||||||
|
quantity = models.PositiveIntegerField(default=1,
|
||||||
|
validators=[MinValueValidator(1)],
|
||||||
|
help_text='Number of parts to build')
|
3
InvenTree/build/templates/build/detail.html
Normal file
3
InvenTree/build/templates/build/detail.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{% include "base.html" %}
|
||||||
|
|
||||||
|
Build detail for Build No. {{ build.id }}.
|
7
InvenTree/build/templates/build/index.html
Normal file
7
InvenTree/build/templates/build/index.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>Builds</h3>
|
||||||
|
|
||||||
|
{% endblock %}
|
6
InvenTree/build/tests.py
Normal file
6
InvenTree/build/tests.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
17
InvenTree/build/urls.py
Normal file
17
InvenTree/build/urls.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from django.conf.urls import url, include
|
||||||
|
from django.views.generic.base import RedirectView
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
build_detail_urls = [
|
||||||
|
|
||||||
|
url(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
|
||||||
|
]
|
||||||
|
|
||||||
|
build_urls = [
|
||||||
|
# url(r'new/?', views.BuildCreate.as_view(), name='build-create'),
|
||||||
|
|
||||||
|
url(r'^(?P<pk>\d+)/', include(build_detail_urls)),
|
||||||
|
|
||||||
|
url(r'.*$', views.BuildIndex.as_view(), name='build-index'),
|
||||||
|
]
|
22
InvenTree/build/views.py
Normal file
22
InvenTree/build/views.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
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 .models import Build
|
||||||
|
|
||||||
|
|
||||||
|
class BuildIndex(ListView):
|
||||||
|
model = Build
|
||||||
|
template_name = 'build/index.html'
|
||||||
|
context_object_name = 'builds'
|
||||||
|
|
||||||
|
|
||||||
|
class BuildDetail(DetailView):
|
||||||
|
model = Build
|
||||||
|
template_name = 'build/detail.html'
|
||||||
|
context_object_name = 'build'
|
@ -85,9 +85,7 @@ part_urls = [
|
|||||||
url(r'^bom/(?P<pk>\d+)/', include(part_bom_urls)),
|
url(r'^bom/(?P<pk>\d+)/', include(part_bom_urls)),
|
||||||
|
|
||||||
# Top level part list (display top level parts and categories)
|
# Top level part list (display top level parts and categories)
|
||||||
url('', views.PartIndex.as_view(), name='part-index'),
|
url(r'^.*$', views.PartIndex.as_view(), name='part-index'),
|
||||||
|
|
||||||
url(r'^.*$', RedirectView.as_view(url='', permanent=False), name='part-index'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from .models import ProjectCategory, Project, ProjectPart, ProjectRun
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectCategoryAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'pathstring', 'description')
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'description', 'category')
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectPartAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('part', 'project', 'quantity', 'output')
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectRunAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('project', 'quantity', 'run_date')
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(ProjectCategory, ProjectCategoryAdmin)
|
|
||||||
admin.site.register(Project, ProjectAdmin)
|
|
||||||
admin.site.register(ProjectPart, ProjectPartAdmin)
|
|
||||||
admin.site.register(ProjectRun, ProjectRunAdmin)
|
|
@ -1,72 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11 on 2018-04-12 05:02
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import django.core.validators
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('part', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Project',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=100)),
|
|
||||||
('description', models.CharField(blank=True, max_length=500)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ProjectCategory',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=100)),
|
|
||||||
('description', models.CharField(blank=True, max_length=250)),
|
|
||||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='project.ProjectCategory')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Project Category',
|
|
||||||
'verbose_name_plural': 'Project Categories',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ProjectPart',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('quantity', models.PositiveIntegerField(default=1, validators=[django.core.validators.MinValueValidator(0)])),
|
|
||||||
('output', models.BooleanField(default=False)),
|
|
||||||
('part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='part.Part')),
|
|
||||||
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='project.Project')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ProjectRun',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('quantity', models.PositiveIntegerField(default=1, validators=[django.core.validators.MinValueValidator(0)])),
|
|
||||||
('run_date', models.DateField(blank=True, null=True)),
|
|
||||||
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='project.Project')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='category',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='project.ProjectCategory'),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='projectpart',
|
|
||||||
unique_together=set([('part', 'project')]),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='project',
|
|
||||||
unique_together=set([('name', 'category')]),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,96 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
# from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from InvenTree.models import InvenTreeTree
|
|
||||||
from part.models import Part
|
|
||||||
from django.core.validators import MinValueValidator
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectCategory(InvenTreeTree):
|
|
||||||
""" ProjectCategory provides hierarchical organization of Project objects.
|
|
||||||
Each ProjectCategory can contain zero-or-more child categories,
|
|
||||||
and in turn can have zero-or-one parent category.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Project Category"
|
|
||||||
verbose_name_plural = "Project Categories"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def projects(self):
|
|
||||||
return self.project_set.all()
|
|
||||||
|
|
||||||
|
|
||||||
class Project(models.Model):
|
|
||||||
""" A Project takes multiple Part objects.
|
|
||||||
A project can output zero-or-more Part objects
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = models.CharField(max_length=100)
|
|
||||||
description = models.CharField(max_length=500, blank=True)
|
|
||||||
category = models.ForeignKey(ProjectCategory, on_delete=models.CASCADE, related_name='projects')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = ('name', 'category')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectPart(models.Model):
|
|
||||||
""" A project part associates a single part with a project
|
|
||||||
The quantity of parts required for a single-run of that project is stored.
|
|
||||||
The overage is the number of extra parts that are generally used for a single run.
|
|
||||||
"""
|
|
||||||
|
|
||||||
part = models.ForeignKey(Part, on_delete=models.CASCADE)
|
|
||||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
|
||||||
quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = ('part', 'project')
|
|
||||||
|
|
||||||
"""
|
|
||||||
# TODO - Add overage model fields
|
|
||||||
|
|
||||||
# Overage types
|
|
||||||
OVERAGE_PERCENT = 0
|
|
||||||
OVERAGE_ABSOLUTE = 1
|
|
||||||
|
|
||||||
OVARAGE_CODES = {
|
|
||||||
OVERAGE_PERCENT: _("Percent"),
|
|
||||||
OVERAGE_ABSOLUTE: _("Absolute")
|
|
||||||
}
|
|
||||||
|
|
||||||
overage = models.FloatField(default=0)
|
|
||||||
overage_type = models.PositiveIntegerField(
|
|
||||||
default=OVERAGE_ABSOLUTE,
|
|
||||||
choices=OVARAGE_CODES.items(),
|
|
||||||
validators=[MinValueValidator(0)])
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Set if the part is generated by the project,
|
|
||||||
# rather than being consumed by the project
|
|
||||||
output = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{quan} x {name}".format(
|
|
||||||
name=self.part.name,
|
|
||||||
quan=self.quantity)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectRun(models.Model):
|
|
||||||
""" A single run of a particular project.
|
|
||||||
Tracks the number of 'units' made in the project.
|
|
||||||
Provides functionality to update stock,
|
|
||||||
based on both:
|
|
||||||
a) Parts used (project inputs)
|
|
||||||
b) Parts produced (project outputs)
|
|
||||||
"""
|
|
||||||
|
|
||||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
|
||||||
quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
|
|
||||||
|
|
||||||
run_date = models.DateField(blank=True, null=True)
|
|
@ -1,47 +0,0 @@
|
|||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from .models import ProjectCategory, Project, ProjectPart, ProjectRun
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectPartSerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ProjectPart
|
|
||||||
fields = ('url',
|
|
||||||
'part',
|
|
||||||
'project',
|
|
||||||
'quantity',
|
|
||||||
'output')
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Project
|
|
||||||
fields = ('url',
|
|
||||||
'name',
|
|
||||||
'description',
|
|
||||||
'category')
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectCategorySerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ProjectCategory
|
|
||||||
fields = ('url',
|
|
||||||
'name',
|
|
||||||
'description',
|
|
||||||
'parent',
|
|
||||||
'pathstring')
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectRunSerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ProjectRun
|
|
||||||
fields = ('url',
|
|
||||||
'project',
|
|
||||||
'quantity',
|
|
||||||
'run_date')
|
|
||||||
|
|
||||||
read_only_fields = ('run_date',)
|
|
@ -1,3 +0,0 @@
|
|||||||
# from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
@ -1,39 +0,0 @@
|
|||||||
from django.conf.urls import url
|
|
||||||
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
prj_part_urls = [
|
|
||||||
# Detail of a single project part
|
|
||||||
url(r'^(?P<pk>[0-9]+)/?$', views.ProjectPartDetail.as_view(), name='projectpart-detail'),
|
|
||||||
|
|
||||||
# List project parts, with optional filters
|
|
||||||
url(r'^\?.*/?$', views.ProjectPartsList.as_view()),
|
|
||||||
url(r'^$', views.ProjectPartsList.as_view())
|
|
||||||
]
|
|
||||||
|
|
||||||
prj_cat_urls = [
|
|
||||||
# Detail of a single project category
|
|
||||||
url(r'^(?P<pk>[0-9]+)/?$', views.ProjectCategoryDetail.as_view(), name='projectcategory-detail'),
|
|
||||||
|
|
||||||
# List of project categories, with filters
|
|
||||||
url(r'^\?.*/?$', views.ProjectCategoryList.as_view()),
|
|
||||||
url(r'^$', views.ProjectCategoryList.as_view())
|
|
||||||
]
|
|
||||||
|
|
||||||
prj_urls = [
|
|
||||||
# Individual project URL
|
|
||||||
url(r'^(?P<pk>[0-9]+)/?$', views.ProjectDetail.as_view(), name='project-detail'),
|
|
||||||
|
|
||||||
# List of all projects
|
|
||||||
url(r'^\?.*/?$', views.ProjectList.as_view()),
|
|
||||||
url(r'^$', views.ProjectList.as_view())
|
|
||||||
]
|
|
||||||
|
|
||||||
prj_run_urls = [
|
|
||||||
# Individual project URL
|
|
||||||
url(r'^(?P<pk>[0-9]+)/?$', views.ProjectRunDetail.as_view(), name='projectrun-detail'),
|
|
||||||
|
|
||||||
# List of all projects
|
|
||||||
url(r'^\?.*/?$', views.ProjectRunList.as_view()),
|
|
||||||
url(r'^$', views.ProjectRunList.as_view())
|
|
||||||
]
|
|
@ -1,183 +0,0 @@
|
|||||||
from django_filters.rest_framework import FilterSet, DjangoFilterBackend
|
|
||||||
|
|
||||||
from rest_framework import generics, permissions
|
|
||||||
from InvenTree.models import FilterChildren
|
|
||||||
from .models import ProjectCategory, Project, ProjectPart, ProjectRun
|
|
||||||
from .serializers import ProjectSerializer
|
|
||||||
from .serializers import ProjectCategorySerializer
|
|
||||||
from .serializers import ProjectPartSerializer
|
|
||||||
from .serializers import ProjectRunSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
|
|
||||||
"""
|
|
||||||
|
|
||||||
get:
|
|
||||||
Return a single Project object
|
|
||||||
|
|
||||||
post:
|
|
||||||
Update a Project
|
|
||||||
|
|
||||||
delete:
|
|
||||||
Remove a Project
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = Project.objects.all()
|
|
||||||
serializer_class = ProjectSerializer
|
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectFilter(FilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Project
|
|
||||||
fields = ['category']
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectList(generics.ListCreateAPIView):
|
|
||||||
"""
|
|
||||||
|
|
||||||
get:
|
|
||||||
Return a list of all Project objects
|
|
||||||
(with optional query filters)
|
|
||||||
|
|
||||||
post:
|
|
||||||
Create a new Project
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = Project.objects.all()
|
|
||||||
serializer_class = ProjectSerializer
|
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
|
||||||
filter_backends = (DjangoFilterBackend,)
|
|
||||||
filter_class = ProjectFilter
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectCategoryDetail(generics.RetrieveUpdateAPIView):
|
|
||||||
"""
|
|
||||||
|
|
||||||
get:
|
|
||||||
Return a single ProjectCategory object
|
|
||||||
|
|
||||||
post:
|
|
||||||
Update a ProjectCategory
|
|
||||||
|
|
||||||
delete:
|
|
||||||
Remove a ProjectCategory
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = ProjectCategory.objects.all()
|
|
||||||
serializer_class = ProjectCategorySerializer
|
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectCategoryList(generics.ListCreateAPIView):
|
|
||||||
"""
|
|
||||||
|
|
||||||
get:
|
|
||||||
Return a list of all ProjectCategory objects
|
|
||||||
|
|
||||||
post:
|
|
||||||
Create a new ProjectCategory
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
params = self.request.query_params
|
|
||||||
|
|
||||||
categories = ProjectCategory.objects.all()
|
|
||||||
|
|
||||||
categories = FilterChildren(categories, params.get('parent', None))
|
|
||||||
|
|
||||||
return categories
|
|
||||||
|
|
||||||
serializer_class = ProjectCategorySerializer
|
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectPartFilter(FilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ProjectPart
|
|
||||||
fields = ['project', 'part']
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectPartsList(generics.ListCreateAPIView):
|
|
||||||
"""
|
|
||||||
|
|
||||||
get:
|
|
||||||
Return a list of all ProjectPart objects
|
|
||||||
|
|
||||||
post:
|
|
||||||
Create a new ProjectPart
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
serializer_class = ProjectPartSerializer
|
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
|
||||||
queryset = ProjectPart.objects.all()
|
|
||||||
filter_backends = (DjangoFilterBackend,)
|
|
||||||
filter_class = ProjectPartFilter
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectPartDetail(generics.RetrieveUpdateDestroyAPIView):
|
|
||||||
"""
|
|
||||||
|
|
||||||
get:
|
|
||||||
Return a single ProjectPart object
|
|
||||||
|
|
||||||
post:
|
|
||||||
Update a ProjectPart
|
|
||||||
|
|
||||||
delete:
|
|
||||||
Remove a ProjectPart
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = ProjectPart.objects.all()
|
|
||||||
serializer_class = ProjectPartSerializer
|
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectRunDetail(generics.RetrieveUpdateDestroyAPIView):
|
|
||||||
"""
|
|
||||||
|
|
||||||
get:
|
|
||||||
Return a single ProjectRun
|
|
||||||
|
|
||||||
post:
|
|
||||||
Update a ProjectRun
|
|
||||||
|
|
||||||
delete:
|
|
||||||
Remove a ProjectRun
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = ProjectRun.objects.all()
|
|
||||||
serializer_class = ProjectRunSerializer
|
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectRunFilter(FilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ProjectRun
|
|
||||||
fields = ['project']
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectRunList(generics.ListCreateAPIView):
|
|
||||||
"""
|
|
||||||
|
|
||||||
get:
|
|
||||||
Return a list of all ProjectRun objects
|
|
||||||
|
|
||||||
post:
|
|
||||||
Create a new ProjectRun object
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = ProjectRun.objects.all()
|
|
||||||
serializer_class = ProjectRunSerializer
|
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
|
||||||
filter_backends = (DjangoFilterBackend,)
|
|
||||||
filter_class = ProjectRunFilter
|
|
22
InvenTree/stock/migrations/0010_stockitem_build.py
Normal file
22
InvenTree/stock/migrations/0010_stockitem_build.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2018-04-16 14:03
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('build', '0001_initial'),
|
||||||
|
('stock', '0009_auto_20180416_1253'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='stockitem',
|
||||||
|
name='build',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='build.Build'),
|
||||||
|
),
|
||||||
|
]
|
@ -3,16 +3,16 @@ 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
|
||||||
|
from django.db.models.signals import pre_delete
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from supplier.models import SupplierPart
|
from supplier.models import SupplierPart
|
||||||
from supplier.models import Customer
|
from supplier.models import Customer
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from InvenTree.models import InvenTreeTree
|
from InvenTree.models import InvenTreeTree
|
||||||
|
from build.models import Build
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from django.db.models.signals import pre_delete
|
|
||||||
from django.dispatch import receiver
|
|
||||||
|
|
||||||
|
|
||||||
class StockLocation(InvenTreeTree):
|
class StockLocation(InvenTreeTree):
|
||||||
@ -95,6 +95,9 @@ class StockItem(models.Model):
|
|||||||
batch = models.CharField(max_length=100, blank=True,
|
batch = models.CharField(max_length=100, blank=True,
|
||||||
help_text='Batch code for this stock item')
|
help_text='Batch code for this stock item')
|
||||||
|
|
||||||
|
# If this part was produced by a build, point to that build here
|
||||||
|
build = models.ForeignKey(Build, on_delete=models.SET_NULL, blank=True, null=True)
|
||||||
|
|
||||||
# Quantity of this stock item. Value may be overridden by other settings
|
# Quantity of this stock item. Value may be overridden by other settings
|
||||||
quantity = models.PositiveIntegerField(validators=[MinValueValidator(0)])
|
quantity = models.PositiveIntegerField(validators=[MinValueValidator(0)])
|
||||||
|
|
||||||
@ -108,7 +111,6 @@ class StockItem(models.Model):
|
|||||||
|
|
||||||
review_needed = models.BooleanField(default=False)
|
review_needed = models.BooleanField(default=False)
|
||||||
|
|
||||||
# Stock status types
|
|
||||||
ITEM_OK = 10
|
ITEM_OK = 10
|
||||||
ITEM_ATTENTION = 50
|
ITEM_ATTENTION = 50
|
||||||
ITEM_DAMAGED = 55
|
ITEM_DAMAGED = 55
|
||||||
|
1
Makefile
1
Makefile
@ -18,6 +18,7 @@ migrate:
|
|||||||
python InvenTree/manage.py makemigrations part
|
python InvenTree/manage.py makemigrations part
|
||||||
python InvenTree/manage.py makemigrations stock
|
python InvenTree/manage.py makemigrations stock
|
||||||
python InvenTree/manage.py makemigrations supplier
|
python InvenTree/manage.py makemigrations supplier
|
||||||
|
python InvenTree/manage.py makemigrations build
|
||||||
python InvenTree/manage.py migrate --run-syncdb
|
python InvenTree/manage.py migrate --run-syncdb
|
||||||
python InvenTree/manage.py check
|
python InvenTree/manage.py check
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user