mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 13:05:42 +00:00
Moving 'supplier' to 'company'
This commit is contained in:
0
InvenTree/company/__init__.py
Normal file
0
InvenTree/company/__init__.py
Normal file
21
InvenTree/company/admin.py
Normal file
21
InvenTree/company/admin.py
Normal file
@ -0,0 +1,21 @@
|
||||
from django.contrib import admin
|
||||
from import_export.admin import ImportExportModelAdmin
|
||||
|
||||
from .models import Company, SupplierPart
|
||||
from .models import SupplierOrder
|
||||
|
||||
class CompanyAdmin(ImportExportModelAdmin):
|
||||
list_display = ('name', 'website', 'contact')
|
||||
|
||||
|
||||
class SupplierPartAdmin(ImportExportModelAdmin):
|
||||
list_display = ('part', 'supplier', 'SKU')
|
||||
|
||||
|
||||
class SupplierOrderAdmin(admin.ModelAdmin):
|
||||
list_display = ('internal_ref', 'supplier', 'issued_date', 'delivery_date', 'status')
|
||||
|
||||
|
||||
admin.site.register(Company, CompanyAdmin)
|
||||
admin.site.register(SupplierPart, SupplierPartAdmin)
|
||||
admin.site.register(SupplierOrder, SupplierOrderAdmin)
|
208
InvenTree/company/api.py
Normal file
208
InvenTree/company/api.py
Normal file
@ -0,0 +1,208 @@
|
||||
from django_filters.rest_framework import FilterSet, DjangoFilterBackend
|
||||
|
||||
from rest_framework import generics, permissions
|
||||
|
||||
from .models import Supplier, SupplierPart, SupplierPriceBreak
|
||||
from .models import Manufacturer, Customer
|
||||
from .serializers import SupplierSerializer
|
||||
from .serializers import SupplierPartSerializer
|
||||
from .serializers import SupplierPriceBreakSerializer
|
||||
from .serializers import ManufacturerSerializer
|
||||
from .serializers import CustomerSerializer
|
||||
|
||||
|
||||
class ManufacturerDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
|
||||
get:
|
||||
Return a single Manufacturer
|
||||
|
||||
post:
|
||||
Update a Manufacturer
|
||||
|
||||
delete:
|
||||
Remove a Manufacturer
|
||||
|
||||
"""
|
||||
|
||||
queryset = Manufacturer.objects.all()
|
||||
serializer_class = ManufacturerSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class ManufacturerList(generics.ListCreateAPIView):
|
||||
"""
|
||||
|
||||
get:
|
||||
Return a list of all Manufacturers
|
||||
|
||||
post:
|
||||
Create a new Manufacturer
|
||||
|
||||
"""
|
||||
|
||||
queryset = Manufacturer.objects.all()
|
||||
serializer_class = ManufacturerSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class CustomerDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
|
||||
get:
|
||||
Return a single Customer
|
||||
|
||||
post:
|
||||
Update a Customer
|
||||
|
||||
delete:
|
||||
Remove a Customer
|
||||
|
||||
"""
|
||||
|
||||
queryset = Customer.objects.all()
|
||||
serializer_class = CustomerSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class CustomerList(generics.ListCreateAPIView):
|
||||
"""
|
||||
|
||||
get:
|
||||
Return a list of all Cutstomers
|
||||
|
||||
post:
|
||||
Create a new Customer
|
||||
|
||||
"""
|
||||
|
||||
queryset = Customer.objects.all()
|
||||
serializer_class = CustomerSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class SupplierDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
|
||||
get:
|
||||
Return a single Supplier
|
||||
|
||||
post:
|
||||
Update a supplier
|
||||
|
||||
delete:
|
||||
Remove a supplier
|
||||
|
||||
"""
|
||||
|
||||
queryset = Supplier.objects.all()
|
||||
serializer_class = SupplierSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class SupplierList(generics.ListCreateAPIView):
|
||||
"""
|
||||
|
||||
get:
|
||||
Return a list of all Suppliers
|
||||
|
||||
post:
|
||||
Create a new Supplier
|
||||
|
||||
"""
|
||||
|
||||
queryset = Supplier.objects.all()
|
||||
serializer_class = SupplierSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
|
||||
get:
|
||||
Return a single SupplierPart
|
||||
|
||||
post:
|
||||
Update a SupplierPart
|
||||
|
||||
delete:
|
||||
Remove a SupplierPart
|
||||
|
||||
"""
|
||||
|
||||
queryset = SupplierPart.objects.all()
|
||||
serializer_class = SupplierPartSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class SupplierPartFilter(FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = SupplierPart
|
||||
fields = ['supplier', 'part', 'manufacturer']
|
||||
|
||||
|
||||
class SupplierPartList(generics.ListCreateAPIView):
|
||||
"""
|
||||
|
||||
get:
|
||||
List all SupplierParts
|
||||
(with optional query filters)
|
||||
|
||||
post:
|
||||
Create a new SupplierPart
|
||||
|
||||
"""
|
||||
|
||||
queryset = SupplierPart.objects.all()
|
||||
serializer_class = SupplierPartSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
filter_class = SupplierPartFilter
|
||||
|
||||
|
||||
class SupplierPriceBreakDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
|
||||
get:
|
||||
Return a single SupplierPriceBreak
|
||||
|
||||
post:
|
||||
Update a SupplierPriceBreak
|
||||
|
||||
delete:
|
||||
Remove a SupplierPriceBreak
|
||||
|
||||
"""
|
||||
|
||||
queryset = SupplierPriceBreak.objects.all()
|
||||
serializer_class = SupplierPriceBreakSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class PriceBreakFilter(FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = SupplierPriceBreak
|
||||
fields = ['part']
|
||||
|
||||
|
||||
class SupplierPriceBreakList(generics.ListCreateAPIView):
|
||||
"""
|
||||
|
||||
get:
|
||||
Return a list of all SupplierPriceBreaks
|
||||
(with optional query filters)
|
||||
|
||||
post:
|
||||
Create a new SupplierPriceBreak
|
||||
|
||||
"""
|
||||
|
||||
queryset = SupplierPriceBreak.objects.all()
|
||||
serializer_class = SupplierPriceBreakSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
filter_class = PriceBreakFilter
|
7
InvenTree/company/apps.py
Normal file
7
InvenTree/company/apps.py
Normal file
@ -0,0 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CompanyConfig(AppConfig):
|
||||
name = 'company'
|
78
InvenTree/company/forms.py
Normal file
78
InvenTree/company/forms.py
Normal file
@ -0,0 +1,78 @@
|
||||
from django import forms
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Submit
|
||||
|
||||
from .models import Company, SupplierPart
|
||||
from .models import SupplierOrder
|
||||
|
||||
|
||||
class EditSupplierOrderForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditSupplierOrderForm, self).__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
|
||||
self.helper.form_id = 'id-edit-part-form'
|
||||
self.helper.form_class = 'blueForms'
|
||||
self.helper.form_method = 'post'
|
||||
|
||||
self.helper.add_input(Submit('submit', 'Submit'))
|
||||
|
||||
class Meta:
|
||||
model = SupplierOrder
|
||||
fields = [
|
||||
'internal_ref',
|
||||
'supplier',
|
||||
'notes',
|
||||
'issued_date',
|
||||
]
|
||||
|
||||
|
||||
class EditCompanyForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditCompanyForm, self).__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
|
||||
self.helper.form_id = 'id-edit-part-form'
|
||||
self.helper.form_class = 'blueForms'
|
||||
self.helper.form_method = 'post'
|
||||
|
||||
self.helper.add_input(Submit('submit', 'Submit'))
|
||||
|
||||
class Meta:
|
||||
model = Company
|
||||
fields = [
|
||||
'name',
|
||||
'description',
|
||||
'website',
|
||||
'address',
|
||||
'phone',
|
||||
'email',
|
||||
'contact',
|
||||
'notes'
|
||||
]
|
||||
|
||||
|
||||
class EditSupplierPartForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditSupplierPartForm, self).__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
|
||||
self.helper.form_id = 'id-edit-part-form'
|
||||
self.helper.form_class = 'blueForms'
|
||||
self.helper.form_method = 'post'
|
||||
|
||||
self.helper.add_input(Submit('submit', 'Submit'))
|
||||
|
||||
class Meta:
|
||||
model = SupplierPart
|
||||
fields = [
|
||||
'supplier',
|
||||
'SKU',
|
||||
'part',
|
||||
'description',
|
||||
'URL',
|
||||
'manufacturer',
|
||||
'MPN',
|
||||
]
|
103
InvenTree/company/migrations/0001_initial.py
Normal file
103
InvenTree/company/migrations/0001_initial.py
Normal file
@ -0,0 +1,103 @@
|
||||
# -*- 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='Customer',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
('address', models.CharField(blank=True, max_length=200)),
|
||||
('phone', models.CharField(blank=True, max_length=50)),
|
||||
('email', models.EmailField(blank=True, max_length=254)),
|
||||
('contact', models.CharField(blank=True, max_length=100)),
|
||||
('notes', models.CharField(blank=True, max_length=500)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Manufacturer',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
('address', models.CharField(blank=True, max_length=200)),
|
||||
('phone', models.CharField(blank=True, max_length=50)),
|
||||
('email', models.EmailField(blank=True, max_length=254)),
|
||||
('contact', models.CharField(blank=True, max_length=100)),
|
||||
('notes', models.CharField(blank=True, max_length=500)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Supplier',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
('address', models.CharField(blank=True, max_length=200)),
|
||||
('phone', models.CharField(blank=True, max_length=50)),
|
||||
('email', models.EmailField(blank=True, max_length=254)),
|
||||
('contact', models.CharField(blank=True, max_length=100)),
|
||||
('notes', models.CharField(blank=True, max_length=500)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SupplierPart',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('SKU', models.CharField(max_length=100)),
|
||||
('MPN', models.CharField(blank=True, max_length=100)),
|
||||
('URL', models.URLField(blank=True)),
|
||||
('description', models.CharField(blank=True, max_length=250)),
|
||||
('single_price', models.DecimalField(decimal_places=3, default=0, max_digits=10)),
|
||||
('base_cost', models.DecimalField(decimal_places=3, default=0, max_digits=10)),
|
||||
('packaging', models.CharField(blank=True, max_length=50)),
|
||||
('multiple', models.PositiveIntegerField(default=1, validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('minimum', models.PositiveIntegerField(default=1, validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('lead_time', models.DurationField(blank=True, null=True)),
|
||||
('manufacturer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='supplier.Manufacturer')),
|
||||
('part', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='part.Part')),
|
||||
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='supplier.Supplier')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SupplierPriceBreak',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('cost', models.DecimalField(decimal_places=3, max_digits=10)),
|
||||
('part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='price_breaks', to='supplier.SupplierPart')),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='supplierpricebreak',
|
||||
unique_together=set([('part', 'quantity')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='supplierpart',
|
||||
unique_together=set([('part', 'supplier', 'SKU')]),
|
||||
),
|
||||
]
|
21
InvenTree/company/migrations/0002_auto_20180412_0622.py
Normal file
21
InvenTree/company/migrations/0002_auto_20180412_0622.py
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-12 06:22
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='supplierpart',
|
||||
name='part',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='supplier_parts', to='part.Part'),
|
||||
),
|
||||
]
|
30
InvenTree/company/migrations/0003_auto_20180414_0540.py
Normal file
30
InvenTree/company/migrations/0003_auto_20180414_0540.py
Normal file
@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-14 05:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0002_auto_20180412_0622'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customer',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=500),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='manufacturer',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=500),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supplier',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=500),
|
||||
),
|
||||
]
|
21
InvenTree/company/migrations/0004_auto_20180414_0624.py
Normal file
21
InvenTree/company/migrations/0004_auto_20180414_0624.py
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-14 06:24
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0003_auto_20180414_0540'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='supplierpart',
|
||||
name='supplier',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parts', to='supplier.Supplier'),
|
||||
),
|
||||
]
|
30
InvenTree/company/migrations/0005_auto_20180415_0255.py
Normal file
30
InvenTree/company/migrations/0005_auto_20180415_0255.py
Normal file
@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-15 02:55
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0004_auto_20180414_0624'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customer',
|
||||
name='description',
|
||||
field=models.CharField(max_length=500),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='manufacturer',
|
||||
name='description',
|
||||
field=models.CharField(max_length=500),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='description',
|
||||
field=models.CharField(max_length=500),
|
||||
),
|
||||
]
|
26
InvenTree/company/migrations/0006_auto_20180415_1011.py
Normal file
26
InvenTree/company/migrations/0006_auto_20180415_1011.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-15 10:11
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0005_auto_20180415_0255'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='supplierpart',
|
||||
name='manufacturer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='supplier.Manufacturer'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplierpart',
|
||||
name='part',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplier_parts', to='part.Part'),
|
||||
),
|
||||
]
|
36
InvenTree/company/migrations/0007_auto_20180416_1253.py
Normal file
36
InvenTree/company/migrations/0007_auto_20180416_1253.py
Normal file
@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-16 12:53
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0006_auto_20180415_1011'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='supplierpart',
|
||||
name='MPN',
|
||||
field=models.CharField(blank=True, help_text='Manufacturer part number', max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplierpart',
|
||||
name='SKU',
|
||||
field=models.CharField(help_text='Supplier stock keeping unit', max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplierpart',
|
||||
name='manufacturer',
|
||||
field=models.ForeignKey(blank=True, help_text='Manufacturer', null=True, on_delete=django.db.models.deletion.SET_NULL, to='supplier.Manufacturer'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplierpart',
|
||||
name='part',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supplier_parts', to='part.Part'),
|
||||
),
|
||||
]
|
18
InvenTree/company/migrations/0008_delete_customer.py
Normal file
18
InvenTree/company/migrations/0008_delete_customer.py
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-17 13:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0007_auto_20180416_1253'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Customer',
|
||||
),
|
||||
]
|
42
InvenTree/company/migrations/0009_auto_20180417_1411.py
Normal file
42
InvenTree/company/migrations/0009_auto_20180417_1411.py
Normal file
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-17 14:11
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0008_delete_customer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SupplierOrder',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('internal_ref', models.CharField(max_length=25, unique=True)),
|
||||
('created_date', models.DateField(auto_now_add=True)),
|
||||
('issued_date', models.DateField(blank=True, help_text='Date the purchase order was issued')),
|
||||
('notes', models.TextField(blank=True, help_text='Order notes')),
|
||||
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='supplier.Supplier')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SupplierOrderLineItem',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('line_number', models.PositiveIntegerField(default=1)),
|
||||
('quantity', models.PositiveIntegerField(default=1)),
|
||||
('notes', models.TextField(blank=True)),
|
||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='supplier.SupplierOrder')),
|
||||
('part', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='supplier.SupplierPart')),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='supplierorderlineitem',
|
||||
unique_together=set([('order', 'line_number'), ('order', 'part')]),
|
||||
),
|
||||
]
|
25
InvenTree/company/migrations/0009_auto_20180417_1516.py
Normal file
25
InvenTree/company/migrations/0009_auto_20180417_1516.py
Normal file
@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-17 15:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0008_delete_customer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='manufacturer',
|
||||
name='notes',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='notes',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
]
|
20
InvenTree/company/migrations/0010_auto_20180417_1420.py
Normal file
20
InvenTree/company/migrations/0010_auto_20180417_1420.py
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-17 14:20
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0009_auto_20180417_1411'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='supplierorder',
|
||||
name='issued_date',
|
||||
field=models.DateField(blank=True, help_text='Date the purchase order was issued', null=True),
|
||||
),
|
||||
]
|
61
InvenTree/company/migrations/0011_auto_20180417_1436.py
Normal file
61
InvenTree/company/migrations/0011_auto_20180417_1436.py
Normal file
@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-17 14:36
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0010_auto_20180417_1420'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='manufacturer',
|
||||
name='address',
|
||||
field=models.CharField(blank=True, help_text='Company address', max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='manufacturer',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Company naem', max_length=100, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='manufacturer',
|
||||
name='notes',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='manufacturer',
|
||||
name='website',
|
||||
field=models.URLField(blank=True, help_text='Company website URL'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='address',
|
||||
field=models.CharField(blank=True, help_text='Company address', max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Company naem', max_length=100, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='notes',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='website',
|
||||
field=models.URLField(blank=True, help_text='Company website URL'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplierorder',
|
||||
name='supplier',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='supplier.Supplier'),
|
||||
),
|
||||
]
|
25
InvenTree/company/migrations/0012_auto_20180417_1447.py
Normal file
25
InvenTree/company/migrations/0012_auto_20180417_1447.py
Normal file
@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-17 14:47
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0011_auto_20180417_1436'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='supplierorder',
|
||||
name='delivery_date',
|
||||
field=models.DateField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supplierorder',
|
||||
name='status',
|
||||
field=models.PositiveIntegerField(choices=[(40, 'Cancelled'), (10, 'Pending'), (20, 'Placed'), (50, 'Lost'), (30, 'Received')], default=10),
|
||||
),
|
||||
]
|
16
InvenTree/company/migrations/0013_merge_20180417_1517.py
Normal file
16
InvenTree/company/migrations/0013_merge_20180417_1517.py
Normal file
@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-17 15:17
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0009_auto_20180417_1516'),
|
||||
('supplier', '0012_auto_20180417_1447'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
31
InvenTree/company/migrations/0014_auto_20180417_1520.py
Normal file
31
InvenTree/company/migrations/0014_auto_20180417_1520.py
Normal file
@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-17 15:20
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0022_auto_20180417_0819'),
|
||||
('supplier', '0013_merge_20180417_1517'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='supplierorderlineitem',
|
||||
old_name='part',
|
||||
new_name='supplier_part',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supplierorderlineitem',
|
||||
name='internal_part',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='part.Part'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='supplierorderlineitem',
|
||||
unique_together=set([('order', 'line_number'), ('order', 'internal_part'), ('order', 'supplier_part')]),
|
||||
),
|
||||
]
|
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-17 15:22
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('supplier', '0014_auto_20180417_1520'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='supplierorderlineitem',
|
||||
name='received',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
0
InvenTree/company/migrations/__init__.py
Normal file
0
InvenTree/company/migrations/__init__.py
Normal file
202
InvenTree/company/models.py
Normal file
202
InvenTree/company/models.py
Normal file
@ -0,0 +1,202 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from django.db import models
|
||||
from django.core.validators import MinValueValidator
|
||||
|
||||
from part.models import Part
|
||||
|
||||
class Company(models.Model):
|
||||
""" Abstract model representing an external company
|
||||
"""
|
||||
|
||||
name = models.CharField(max_length=100, unique=True,
|
||||
help_text='Company naem')
|
||||
|
||||
description = models.CharField(max_length=500)
|
||||
|
||||
website = models.URLField(blank=True, help_text='Company website URL')
|
||||
|
||||
address = models.CharField(max_length=200,
|
||||
blank=True, help_text='Company address')
|
||||
|
||||
phone = models.CharField(max_length=50,
|
||||
blank=True)
|
||||
|
||||
email = models.EmailField(blank=True)
|
||||
|
||||
contact = models.CharField(max_length=100,
|
||||
blank=True)
|
||||
|
||||
notes = models.TextField(blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "/company/{id}/".format(id=self.id)
|
||||
|
||||
@property
|
||||
def part_count(self):
|
||||
return self.parts.count()
|
||||
|
||||
@property
|
||||
def has_parts(self):
|
||||
return self.part_count > 0
|
||||
|
||||
@property
|
||||
def order_count(self):
|
||||
return self.orders.count()
|
||||
|
||||
@property
|
||||
def has_orders(self):
|
||||
return self.order_count > 0
|
||||
|
||||
|
||||
class SupplierPart(models.Model):
|
||||
""" Represents a unique part as provided by a Supplier
|
||||
Each SupplierPart is identified by a MPN (Manufacturer Part Number)
|
||||
Each SupplierPart is also linked to a Part object
|
||||
- A Part may be available from multiple suppliers
|
||||
"""
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "/supplier/part/{id}/".format(id=self.id)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('part', 'supplier', 'SKU')
|
||||
|
||||
# Link to an actual part
|
||||
# The part will have a field 'supplier_parts' which links to the supplier part options
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE,
|
||||
related_name='supplier_parts')
|
||||
|
||||
supplier = models.ForeignKey(Company, on_delete=models.CASCADE,
|
||||
related_name='parts')
|
||||
|
||||
SKU = models.CharField(max_length=100, help_text='Supplier stock keeping unit')
|
||||
|
||||
manufacturer = models.CharField(max_length=100, blank=True, help_text='Manufacturer')
|
||||
|
||||
MPN = models.CharField(max_length=100, blank=True, help_text='Manufacturer part number')
|
||||
|
||||
URL = models.URLField(blank=True)
|
||||
|
||||
description = models.CharField(max_length=250, blank=True)
|
||||
|
||||
# Default price for a single unit
|
||||
single_price = models.DecimalField(max_digits=10, decimal_places=3, default=0)
|
||||
|
||||
# Base charge added to order independent of quantity e.g. "Reeling Fee"
|
||||
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0)
|
||||
|
||||
# packaging that the part is supplied in, e.g. "Reel"
|
||||
packaging = models.CharField(max_length=50, blank=True)
|
||||
|
||||
# multiple that the part is provided in
|
||||
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
|
||||
|
||||
# Mimumum number required to order
|
||||
minimum = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
|
||||
|
||||
# lead time for parts that cannot be delivered immediately
|
||||
lead_time = models.DurationField(blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return "{sku} - {supplier}".format(
|
||||
sku=self.SKU,
|
||||
supplier=self.supplier.name)
|
||||
|
||||
|
||||
class SupplierPriceBreak(models.Model):
|
||||
""" Represents a quantity price break for a SupplierPart
|
||||
- Suppliers can offer discounts at larger quantities
|
||||
- SupplierPart(s) may have zero-or-more associated SupplierPriceBreak(s)
|
||||
"""
|
||||
|
||||
part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='price_breaks')
|
||||
quantity = models.PositiveIntegerField(validators=[MinValueValidator(0)])
|
||||
cost = models.DecimalField(max_digits=10, decimal_places=3)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("part", "quantity")
|
||||
|
||||
def __str__(self):
|
||||
return "{mpn} - {cost}{currency} @ {quan}".format(
|
||||
mpn=self.part.MPN,
|
||||
cost=self.cost,
|
||||
currency=self.currency if self.currency else '',
|
||||
quan=self.quantity)
|
||||
|
||||
|
||||
class SupplierOrder(models.Model):
|
||||
"""
|
||||
An order of parts from a supplier, made up of multiple line items
|
||||
"""
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "/supplier/order/{id}/".format(id=self.id)
|
||||
|
||||
# Interal reference for this order
|
||||
internal_ref = models.CharField(max_length=25, unique=True)
|
||||
|
||||
supplier = models.ForeignKey(Company, on_delete=models.CASCADE,
|
||||
related_name='orders')
|
||||
|
||||
created_date = models.DateField(auto_now_add=True, editable=False)
|
||||
|
||||
issued_date = models.DateField(blank=True, null=True, help_text="Date the purchase order was issued")
|
||||
|
||||
notes = models.TextField(blank=True, help_text="Order notes")
|
||||
|
||||
def __str__(self):
|
||||
return "PO {ref} ({status})".format(ref=self.internal_ref,
|
||||
status=self.get_status_display)
|
||||
|
||||
PENDING = 10 # Order is pending (not yet placed)
|
||||
PLACED = 20 # Order has been placed
|
||||
RECEIVED = 30 # Order has been received
|
||||
CANCELLED = 40 # Order was cancelled
|
||||
LOST = 50 # Order was lost
|
||||
|
||||
ORDER_STATUS_CODES = {PENDING: _("Pending"),
|
||||
PLACED: _("Placed"),
|
||||
CANCELLED: _("Cancelled"),
|
||||
RECEIVED: _("Received"),
|
||||
LOST: _("Lost")
|
||||
}
|
||||
|
||||
status = models.PositiveIntegerField(default=PENDING,
|
||||
choices=ORDER_STATUS_CODES.items())
|
||||
|
||||
delivery_date = models.DateField(blank=True, null=True)
|
||||
|
||||
|
||||
|
||||
class SupplierOrderLineItem(models.Model):
|
||||
"""
|
||||
A line item in a supplier order, corresponding to some quantity of part
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
unique_together = [
|
||||
('order', 'line_number'),
|
||||
('order', 'supplier_part'),
|
||||
('order', 'internal_part'),
|
||||
]
|
||||
|
||||
order = models.ForeignKey(SupplierOrder, on_delete=models.CASCADE)
|
||||
|
||||
line_number = models.PositiveIntegerField(default=1)
|
||||
|
||||
internal_part = models.ForeignKey(Part, null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
||||
supplier_part = models.ForeignKey(SupplierPart, null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
||||
quantity = models.PositiveIntegerField(default=1)
|
||||
|
||||
notes = models.TextField(blank=True)
|
||||
|
||||
received = models.BooleanField(default=False)
|
55
InvenTree/company/serializers.py
Normal file
55
InvenTree/company/serializers.py
Normal file
@ -0,0 +1,55 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from part.models import Part
|
||||
|
||||
from .models import Company, SupplierPart, SupplierPriceBreak
|
||||
|
||||
|
||||
class CompanySerializer(serializers.HyperlinkedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Company
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class SupplierPartSerializer(serializers.ModelSerializer):
|
||||
|
||||
price_breaks = serializers.HyperlinkedRelatedField(many=True,
|
||||
read_only=True,
|
||||
view_name='supplierpricebreak-detail')
|
||||
|
||||
part = serializers.HyperlinkedRelatedField(view_name='part-detail',
|
||||
queryset=Part.objects.all())
|
||||
|
||||
supplier = serializers.HyperlinkedRelatedField(view_name='supplier-detail',
|
||||
queryset=Supplier.objects.all())
|
||||
|
||||
manufacturer = serializers.HyperlinkedRelatedField(view_name='manufacturer-detail',
|
||||
queryset=Manufacturer.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = SupplierPart
|
||||
fields = ['url',
|
||||
'part',
|
||||
'supplier',
|
||||
'SKU',
|
||||
'manufacturer',
|
||||
'MPN',
|
||||
'URL',
|
||||
'description',
|
||||
'single_price',
|
||||
'packaging',
|
||||
'multiple',
|
||||
'minimum',
|
||||
'price_breaks',
|
||||
'lead_time']
|
||||
|
||||
|
||||
class SupplierPriceBreakSerializer(serializers.HyperlinkedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = SupplierPriceBreak
|
||||
fields = ['url',
|
||||
'part',
|
||||
'quantity',
|
||||
'cost']
|
5
InvenTree/company/templates/company/create.html
Normal file
5
InvenTree/company/templates/company/create.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "create_edit_obj.html" %}
|
||||
|
||||
{% block obj_title %}
|
||||
Create a new supplier
|
||||
{% endblock %}
|
17
InvenTree/company/templates/company/delete.html
Normal file
17
InvenTree/company/templates/company/delete.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends "delete_obj.html" %}
|
||||
|
||||
{% block del_title %}
|
||||
Are you sure you want to delete supplier '{{ supplier.name }}'?
|
||||
{% endblock %}
|
||||
|
||||
{% block del_body %}
|
||||
{% if supplier.part_count > 0 %}
|
||||
<p>There are {{ supplier.part_count }} parts sourced from this supplier.<br>
|
||||
If this supplier is deleted, these supplier part entries will also be deleted.</p>
|
||||
<ul class='list-group'>
|
||||
{% for part in supplier.parts.all %}
|
||||
<li class='list-group-item'><b>{{ part.SKU }}</b><br><i>Part - {{ part.part.name }}</i></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
46
InvenTree/company/templates/company/detail.html
Normal file
46
InvenTree/company/templates/company/detail.html
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends "supplier/supplier_base.html" %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include "supplier/tabs.html" with tab='parts' %}
|
||||
|
||||
<h3>Supplier Parts</h3>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>SKU</th>
|
||||
<th>Description</th>
|
||||
<th>Parent Part</th>
|
||||
<th>MPN</th>
|
||||
<th>URL</th>
|
||||
</tr>
|
||||
{% for part in supplier.parts.all %}
|
||||
<tr>
|
||||
<td><a href="{% url 'supplier-part-detail' part.id %}">{{ part.SKU }}</a></td>
|
||||
<td>{{ part.description }}</td>
|
||||
<td>
|
||||
{% if part.part %}
|
||||
<a href="{% url 'part-suppliers' part.part.id %}">{{ part.part.name }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if part.manufacturer %}{{ part.manufacturer.name }}{% endif %}
|
||||
{% if part.MPN %} | {{ part.MPN }}{% endif %}
|
||||
</td>
|
||||
<td>{{ part.URL }}</td>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<div class='container-fluid'>
|
||||
<a href="{% url 'supplier-part-create' %}?supplier={{ supplier.id }}">
|
||||
<button class="btn btn-success">New Supplier Part</button>
|
||||
</a>
|
||||
<a href="{% url 'supplier-edit' supplier.id %}">
|
||||
<button class="btn btn-info">Edit supplier details</button>
|
||||
</a>
|
||||
<a href="{% url 'supplier-delete' supplier.id %}">
|
||||
<button class="btn btn-danger">Delete supplier</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
5
InvenTree/company/templates/company/edit.html
Normal file
5
InvenTree/company/templates/company/edit.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "create_edit_obj.html" %}
|
||||
|
||||
{% block obj_title %}
|
||||
Edit details for supplier '{{ supplier.name }}'
|
||||
{% endblock %}
|
28
InvenTree/company/templates/company/index.html
Normal file
28
InvenTree/company/templates/company/index.html
Normal file
@ -0,0 +1,28 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>Suppliers</h3>
|
||||
<ul class='list-group'>
|
||||
{% for supplier in suppliers %}
|
||||
<li class='list-group-item'>
|
||||
<b><a href="{% url 'supplier-detail' supplier.id %}">{{ supplier.name }}</a></b>
|
||||
<br>
|
||||
{{ supplier.description }}
|
||||
{% if supplier.website %}
|
||||
<a href="{{ supplier.website }}">- {{ supplier.website }}</a>
|
||||
{% endif %}
|
||||
<span class="badge">
|
||||
{{ supplier.parts.all|length }}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class='container-fluid'>
|
||||
<a href="{% url 'supplier-create' %}">
|
||||
<button class="btn btn-success">New Supplier</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
5
InvenTree/company/templates/company/order_create.html
Normal file
5
InvenTree/company/templates/company/order_create.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "create_edit_obj.html" %}
|
||||
|
||||
{% block obj_title %}
|
||||
Create a new supplier purchase order
|
||||
{% endblock %}
|
49
InvenTree/company/templates/company/order_detail.html
Normal file
49
InvenTree/company/templates/company/order_detail.html
Normal file
@ -0,0 +1,49 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>Supplier Order Details</h3>
|
||||
|
||||
<table class='table table-striped'>
|
||||
<tr>
|
||||
<td>Reference</td>
|
||||
<td>{{ order.internal_ref }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Supplier</td>
|
||||
<td>
|
||||
{% if order.supplier %}
|
||||
<a href="{% url 'supplier-detail-orders' order.supplier.id %}">{{ order.supplier.name }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td>{% include "supplier/order_status.html" with order=order %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Created Date</td>
|
||||
<td>{{ order.created_date }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Issued Date</td>
|
||||
<td>{{ order.issued_date }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Delivered Date</td>
|
||||
<td>{{ order.delivery_date }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
{% if order.notes %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><b>Notes</b></div>
|
||||
<div class="panel-body">{{ order.notes }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>TODO</h2>
|
||||
Here we list all the line ites which exist under this order...
|
||||
|
||||
{% endblock %}
|
13
InvenTree/company/templates/company/order_status.html
Normal file
13
InvenTree/company/templates/company/order_status.html
Normal file
@ -0,0 +1,13 @@
|
||||
{% if order.status == order.PENDING %}
|
||||
<span class='label label-info'>
|
||||
{% elif order.status == order.PLACED %}
|
||||
<span class='label label-primary'>
|
||||
{% elif order.status == order.RECEIVED %}
|
||||
<span class='label label-success'>
|
||||
{% elif order.status == order.CANCELLED %}
|
||||
<span class='label label-warning'>
|
||||
{% else %}
|
||||
<span class='label label-danger'>
|
||||
{% endif %}
|
||||
{{ order.get_status_display }}
|
||||
</span>
|
31
InvenTree/company/templates/company/orders.html
Normal file
31
InvenTree/company/templates/company/orders.html
Normal file
@ -0,0 +1,31 @@
|
||||
{% extends "supplier/supplier_base.html" %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include "supplier/tabs.html" with tab='order' %}
|
||||
|
||||
<h3>Supplier Orders</h3>
|
||||
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Reference</th>
|
||||
<th>Issued</th>
|
||||
<th>Delivery</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
{% for order in supplier.orders.all %}
|
||||
<tr>
|
||||
<td><a href="{% url 'supplier-order-detail' order.id %}">{{ order.internal_ref }}</a></td>
|
||||
<td>{% if order.issued_date %}{{ order.issued_date }}{% endif %}</td>
|
||||
<td>{% if order.delivery_date %}{{ order.delivery_date }}{% endif %}</td>
|
||||
<td>{% include "supplier/order_status.html" with order=order %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<div class='container-fluid'>
|
||||
<a href="{% url 'supplier-order-create' %}?supplier={{ supplier.id }}">
|
||||
<button class="btn btn-success">New Order</button>
|
||||
</a>
|
||||
|
||||
{% endblock %}
|
5
InvenTree/company/templates/company/partcreate.html
Normal file
5
InvenTree/company/templates/company/partcreate.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "create_edit_obj.html" %}
|
||||
|
||||
{% block obj_title %}
|
||||
Create a new supplier part
|
||||
{% endblock %}
|
5
InvenTree/company/templates/company/partdelete.html
Normal file
5
InvenTree/company/templates/company/partdelete.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends 'delete_obj.html' %}
|
||||
|
||||
{% block del_title %}
|
||||
Are you sure you want to delete this supplier part?
|
||||
{% endblock %}
|
42
InvenTree/company/templates/company/partdetail.html
Normal file
42
InvenTree/company/templates/company/partdetail.html
Normal file
@ -0,0 +1,42 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>Supplier part information</h3>
|
||||
|
||||
<table class="table">
|
||||
<tr><td>SKU</td><td>{{ part.SKU }}</tr></tr>
|
||||
<tr><td>Supplier</td><td><a href="{% url 'supplier-detail' part.supplier.id %}">{{ part.supplier.name }}</a></td></tr>
|
||||
<tr>
|
||||
<td>Parent Part</td>
|
||||
<td>
|
||||
{% if part.part %}
|
||||
<a href="{% url 'part-suppliers' part.part.id %}">{{ part.part.name }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if part.URL %}
|
||||
<tr><td><URL></td><td><a href="{{ part.URL }}">{{ part.URL }}</a></td></tr>
|
||||
{% endif %}
|
||||
{% if part.description %}
|
||||
<tr><td>Description</td><td>{{ part.description }}</td></tr>
|
||||
{% endif %}
|
||||
{% if part.manufacturer %}
|
||||
<tr><td>Manufacturer</td><td>{% if part.manufacturer %}{{ part.manufacturer.name }}{% endif %}</td></tr>
|
||||
<tr><td>MPN</td><td>{{ part.MPN }}</td></tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
<br>
|
||||
|
||||
<div class='container-fluid'>
|
||||
<a href="{% url 'supplier-part-edit' part.id %}">
|
||||
<button class="btn btn-info">Edit supplier part</button>
|
||||
</a>
|
||||
<a href="{% url 'supplier-part-delete' part.id %}">
|
||||
<button class="btn btn-danger">Delete supplier part</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
5
InvenTree/company/templates/company/partedit.html
Normal file
5
InvenTree/company/templates/company/partedit.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "create_edit_obj.html" %}
|
||||
|
||||
{% block obj_title %}
|
||||
Edit supplier part '{{ part.SKU }}'
|
||||
{% endblock %}
|
46
InvenTree/company/templates/company/supplier_base.html
Normal file
46
InvenTree/company/templates/company/supplier_base.html
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h3>{{ supplier.name }}</h3>
|
||||
<p>{{ supplier.description }}</p>
|
||||
<p>{{ supplier.notes }}</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<table class="table">
|
||||
{% if supplier.website %}
|
||||
<tr>
|
||||
<td>Website</td><td><a href="{{ supplier.website }}">{{ supplier.website }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if supplier.address %}
|
||||
<tr>
|
||||
<td>Address</td><td>{{ supplier.address }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if supplier.phone %}
|
||||
<tr>
|
||||
<td>Phone</td><td>{{ supplier.phone }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if supplier.email %}
|
||||
<tr>
|
||||
<td>Email</td><td>{{ supplier.email }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if supplier.contact %}
|
||||
<tr>
|
||||
<td>Contact</td><td>{{ supplier.contact }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
6
InvenTree/company/templates/company/tabs.html
Normal file
6
InvenTree/company/templates/company/tabs.html
Normal file
@ -0,0 +1,6 @@
|
||||
<ul class='nav nav-tabs'>
|
||||
<li{% if tab == 'parts' %} class='active'{% endif %}>
|
||||
<a href="{% url 'supplier-detail' supplier.id %}">Parts <span class='badge'>{{ supplier.part_count }}</span></a></li>
|
||||
<li{% if tab == 'order' %} class='active'{% endif %}>
|
||||
<a href="{% url 'supplier-detail-orders' supplier.id %}">Orders <span class="badge">{{ supplier.order_count }}</span></a></li>
|
||||
</ul>
|
3
InvenTree/company/tests.py
Normal file
3
InvenTree/company/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
# from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
101
InvenTree/company/urls.py
Normal file
101
InvenTree/company/urls.py
Normal file
@ -0,0 +1,101 @@
|
||||
from django.conf.urls import url, include
|
||||
from django.views.generic.base import RedirectView
|
||||
|
||||
from . import views
|
||||
|
||||
"""
|
||||
cust_urls = [
|
||||
# Customer detail
|
||||
url(r'^(?P<pk>[0-9]+)/?$', api.CustomerDetail.as_view(), name='customer-detail'),
|
||||
|
||||
# List customers
|
||||
url(r'^\?.*/?$', api.CustomerList.as_view()),
|
||||
url(r'^$', api.CustomerList.as_view())
|
||||
]
|
||||
|
||||
manu_urls = [
|
||||
# Manufacturer detail
|
||||
url(r'^(?P<pk>[0-9]+)/?$', api.ManufacturerDetail.as_view(), name='manufacturer-detail'),
|
||||
|
||||
# List manufacturers
|
||||
url(r'^\?.*/?$', api.ManufacturerList.as_view()),
|
||||
url(r'^$', api.ManufacturerList.as_view())
|
||||
]
|
||||
|
||||
supplier_api_part_urls = [
|
||||
url(r'^(?P<pk>[0-9]+)/?$', api.SupplierPartDetail.as_view(), name='supplierpart-detail'),
|
||||
|
||||
url(r'^\?.*/?$', api.SupplierPartList.as_view()),
|
||||
url(r'^$', api.SupplierPartList.as_view())
|
||||
]
|
||||
|
||||
price_break_urls = [
|
||||
url(r'^(?P<pk>[0-9]+)/?$', api.SupplierPriceBreakDetail.as_view(), name='supplierpricebreak-detail'),
|
||||
|
||||
url(r'^\?.*/?$', api.SupplierPriceBreakList.as_view()),
|
||||
url(r'^$', api.SupplierPriceBreakList.as_view())
|
||||
]
|
||||
|
||||
supplier_api_urls = [
|
||||
|
||||
# Display details of a supplier
|
||||
url(r'^(?P<pk>[0-9]+)/$', api.SupplierDetail.as_view(), name='supplier-detail'),
|
||||
|
||||
# List suppliers
|
||||
url(r'^\?.*/?$', api.SupplierList.as_view()),
|
||||
url(r'^$', api.SupplierList.as_view())
|
||||
]
|
||||
"""
|
||||
|
||||
company_detail_urls = [
|
||||
url(r'edit/?', views.CompanyEdit.as_view(), name='company-edit'),
|
||||
url(r'delete/?', views.CompanyDelete.as_view(), name='company-delete'),
|
||||
|
||||
url(r'orders/?', views.CompanyDetail.as_view(template_name='supplier/orders.html'), name='company-detail-orders'),
|
||||
|
||||
url(r'^.*$', views.CompanyDetail.as_view(), name='company-detail'),
|
||||
]
|
||||
|
||||
supplier_part_detail_urls = [
|
||||
url(r'edit/?', views.SupplierPartEdit.as_view(), name='supplier-part-edit'),
|
||||
url(r'delete/?', views.SupplierPartDelete.as_view(), name='supplier-part-delete'),
|
||||
|
||||
url('^.*$', views.SupplierPartDetail.as_view(), name='supplier-part-detail'),
|
||||
]
|
||||
|
||||
supplier_part_urls = [
|
||||
url(r'^new/?', views.SupplierPartCreate.as_view(), name='supplier-part-create'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/', include(supplier_part_detail_urls)),
|
||||
]
|
||||
|
||||
"""
|
||||
supplier_order_detail_urls = [
|
||||
|
||||
|
||||
url('^.*$', views.SupplierOrderDetail.as_view(), name='supplier-order-detail'),
|
||||
]
|
||||
|
||||
supplier_order_urls = [
|
||||
url(r'^new/?', views.SupplierOrderCreate.as_view(), name='supplier-order-create'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/', include(supplier_order_detail_urls)),
|
||||
]
|
||||
"""
|
||||
|
||||
company_urls = [
|
||||
|
||||
|
||||
url(r'supplier_part/', include(supplier_part_urls)),
|
||||
|
||||
#url(r'order/', include(supplier_order_urls)),
|
||||
|
||||
#url(r'new/?', views.SupplierCreate.as_view(), name='supplier-create'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/', include(company_detail_urls)),
|
||||
|
||||
url(r'', views.CompanyIndex.as_view(), name='company-index'),
|
||||
|
||||
# Redirect any other patterns
|
||||
url(r'^.*$', RedirectView.as_view(url='', permanent=False), name='company-index'),
|
||||
]
|
129
InvenTree/company/views.py
Normal file
129
InvenTree/company/views.py
Normal file
@ -0,0 +1,129 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic.edit import UpdateView, DeleteView, CreateView
|
||||
|
||||
from part.models import Part
|
||||
from .models import Company
|
||||
from .models import SupplierPart
|
||||
from .models import SupplierOrder
|
||||
|
||||
from .forms import EditCompanyForm
|
||||
from .forms import EditSupplierPartForm
|
||||
from .forms import EditSupplierOrderForm
|
||||
|
||||
class SupplierOrderDetail(DetailView):
|
||||
context_object_name = 'order'
|
||||
model = SupplierOrder
|
||||
template_name = 'company/order_detail.html'
|
||||
queryset = SupplierOrder.objects.all()
|
||||
|
||||
|
||||
class SupplierOrderCreate(CreateView):
|
||||
model = SupplierOrder
|
||||
form_class = EditSupplierOrderForm
|
||||
context_object_name = 'supplier'
|
||||
template_name = 'company/order_create.html'
|
||||
|
||||
def get_initial(self):
|
||||
initials = super(SupplierOrderCreate, self).get_initial().copy()
|
||||
|
||||
s_id = self.request.GET.get('supplier', None)
|
||||
|
||||
if s_id:
|
||||
initials['supplier'] = get_object_or_404(Supplier, pk=s_id)
|
||||
|
||||
return initials
|
||||
|
||||
|
||||
class CompanyIndex(ListView):
|
||||
model = Company
|
||||
template_name = 'company/index.html'
|
||||
context_object_name = 'companies'
|
||||
paginate_by = 50
|
||||
|
||||
def get_queryset(self):
|
||||
return Supplier.objects.order_by('name')
|
||||
|
||||
|
||||
class CompanyDetail(DetailView):
|
||||
context_obect_name = 'company'
|
||||
template_name = 'company/detail.html'
|
||||
queryset = Company.objects.all()
|
||||
model = Company
|
||||
|
||||
|
||||
class CompanyEdit(UpdateView):
|
||||
model = Company
|
||||
form_class = EditCompanyForm
|
||||
template_name = 'company/edit.html'
|
||||
context_object_name = 'supplier'
|
||||
|
||||
|
||||
class CompanyCreate(CreateView):
|
||||
model = Company
|
||||
form_class = EditCompanyForm
|
||||
template_name = "company/create.html"
|
||||
|
||||
|
||||
class CompanyDelete(DeleteView):
|
||||
model = Company
|
||||
success_url = '/company/'
|
||||
template_name = 'company/delete.html'
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if 'confirm' in request.POST:
|
||||
return super(CompanyDelete, self).post(request, *args, **kwargs)
|
||||
else:
|
||||
return HttpResponseRedirect(self.get_object().get_absolute_url())
|
||||
|
||||
|
||||
class SupplierPartDetail(DetailView):
|
||||
model = SupplierPart
|
||||
template_name = 'company/partdetail.html'
|
||||
context_object_name = 'part'
|
||||
queryset = SupplierPart.objects.all()
|
||||
|
||||
|
||||
class SupplierPartEdit(UpdateView):
|
||||
model = SupplierPart
|
||||
template_name = 'company/partedit.html'
|
||||
context_object_name = 'part'
|
||||
form_class = EditSupplierPartForm
|
||||
|
||||
|
||||
class SupplierPartCreate(CreateView):
|
||||
model = SupplierPart
|
||||
form_class = EditSupplierPartForm
|
||||
template_name = 'company/partcreate.html'
|
||||
context_object_name = 'part'
|
||||
|
||||
def get_initial(self):
|
||||
initials = super(SupplierPartCreate, self).get_initial().copy()
|
||||
|
||||
supplier_id = self.request.GET.get('supplier', None)
|
||||
part_id = self.request.GET.get('part', None)
|
||||
|
||||
if supplier_id:
|
||||
initials['supplier'] = get_object_or_404(Supplier, pk=supplier_id)
|
||||
# TODO
|
||||
# self.fields['supplier'].disabled = True
|
||||
if part_id:
|
||||
initials['part'] = get_object_or_404(Part, pk=part_id)
|
||||
# TODO
|
||||
# self.fields['part'].disabled = True
|
||||
|
||||
return initials
|
||||
|
||||
|
||||
class SupplierPartDelete(DeleteView):
|
||||
model = SupplierPart
|
||||
success_url = '/supplier/'
|
||||
template_name = 'company/partdelete.html'
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if 'confirm' in request.POST:
|
||||
return super(SupplierPartDelete, self).post(request, *args, **kwargs)
|
||||
else:
|
||||
return HttpResponseRedirect(self.get_object().get_absolute_url())
|
Reference in New Issue
Block a user