diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 6ce09ad104..fe22396242 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -51,6 +51,7 @@ INSTALLED_APPS = [ 'stock.apps.StockConfig', 'supplier.apps.SupplierConfig', 'build.apps.BuildConfig', + 'customer_orders.apps.CustomerOrdersConfig' ] MIDDLEWARE = [ diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index da9e2573a9..2fbfba3e0d 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -13,6 +13,8 @@ from supplier.urls import supplier_urls from build.urls import build_urls +from customer_orders.urls import customer_orders_urls + from django.conf import settings from django.conf.urls.static import static @@ -71,6 +73,7 @@ urlpatterns = [ url(r'^stock/', include(stock_urls)), url(r'^supplier/', include(supplier_urls)), url(r'^build/', include(build_urls)), + url(r'^customer-orders/', include(customer_orders_urls)), url(r'^admin/', admin.site.urls), url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), diff --git a/InvenTree/build/migrations/0004_auto_20180417_2127.py b/InvenTree/build/migrations/0004_auto_20180417_2127.py new file mode 100644 index 0000000000..3898af57ea --- /dev/null +++ b/InvenTree/build/migrations/0004_auto_20180417_2127.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-17 11:27 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('build', '0003_build_part'), + ] + + operations = [ + migrations.AlterField( + model_name='build', + name='status', + field=models.PositiveIntegerField(choices=[('20', 'Allocated'), ('30', 'Cancelled'), ('40', 'Complete'), ('10', 'Pending')], default=10), + ), + ] diff --git a/InvenTree/customer_orders/__init__.py b/InvenTree/customer_orders/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/InvenTree/customer_orders/admin.py b/InvenTree/customer_orders/admin.py new file mode 100644 index 0000000000..f2919066ca --- /dev/null +++ b/InvenTree/customer_orders/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin +from import_export.admin import ImportExportModelAdmin + +from .models import CustomerOrder, CustomerOrderLine + + +class CustomerOrderAdmin(admin.ModelAdmin): + pass + +class CustomerOrderLineAdmin(admin.ModelAdmin): + pass + + +admin.site.register(CustomerOrder, CustomerOrderAdmin) +admin.site.register(CustomerOrderLine, CustomerOrderLineAdmin) \ No newline at end of file diff --git a/InvenTree/customer_orders/apps.py b/InvenTree/customer_orders/apps.py new file mode 100644 index 0000000000..7196157c41 --- /dev/null +++ b/InvenTree/customer_orders/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CustomerOrdersConfig(AppConfig): + name = 'customer_orders' diff --git a/InvenTree/customer_orders/migrations/0001_initial.py b/InvenTree/customer_orders/migrations/0001_initial.py new file mode 100644 index 0000000000..64f76be5ff --- /dev/null +++ b/InvenTree/customer_orders/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-17 11:27 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('supplier', '0007_auto_20180416_1253'), + ] + + operations = [ + migrations.CreateModel( + name='CustomerOrder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('customer', models.ForeignKey(blank=True, help_text='Customer that placed this order', null=True, on_delete=django.db.models.deletion.SET_NULL, to='supplier.Customer')), + ], + ), + migrations.CreateModel( + name='CustomerOrderLine', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('line_number', models.PositiveIntegerField(default=0)), + ('customer_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='customer_orders.CustomerOrder')), + ], + ), + migrations.AlterUniqueTogether( + name='customerorderline', + unique_together=set([('customer_order', 'line_number')]), + ), + ] diff --git a/InvenTree/customer_orders/migrations/0002_auto_20180417_2205.py b/InvenTree/customer_orders/migrations/0002_auto_20180417_2205.py new file mode 100644 index 0000000000..44de420e53 --- /dev/null +++ b/InvenTree/customer_orders/migrations/0002_auto_20180417_2205.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-17 12:05 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0019_auto_20180416_1249'), + ('customer_orders', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='customerorder', + name='created_date', + field=models.DateField(auto_now_add=True, default=django.utils.timezone.now, help_text='Date order entered in system'), + preserve_default=False, + ), + migrations.AddField( + model_name='customerorder', + name='customer_ref', + field=models.CharField(blank=True, default='', max_length=100), + ), + migrations.AddField( + model_name='customerorder', + name='internal_ref', + field=models.CharField(default=0, max_length=100, unique=True), + preserve_default=False, + ), + migrations.AddField( + model_name='customerorder', + name='issued_date', + field=models.DateField(blank=True, default=django.utils.timezone.now, help_text='Date order issued by customer'), + preserve_default=False, + ), + migrations.AddField( + model_name='customerorder', + name='notes', + field=models.TextField(blank=True, default='', help_text='Order notes'), + ), + migrations.AddField( + model_name='customerorderline', + name='notes', + field=models.TextField(blank=True, help_text='Line notes'), + ), + migrations.AddField( + model_name='customerorderline', + name='part', + field=models.ForeignKey(default=0, help_text='Part', on_delete=django.db.models.deletion.CASCADE, to='part.Part'), + preserve_default=False, + ), + migrations.AddField( + model_name='customerorderline', + name='quantity', + field=models.IntegerField(default=1, help_text='Quantity of part'), + preserve_default=False, + ), + migrations.AlterField( + model_name='customerorderline', + name='customer_order', + field=models.ForeignKey(help_text='Order this line belongs to', on_delete=django.db.models.deletion.CASCADE, to='customer_orders.CustomerOrder'), + ), + migrations.AlterField( + model_name='customerorderline', + name='line_number', + field=models.PositiveIntegerField(default=0, help_text='Line number'), + ), + ] diff --git a/InvenTree/customer_orders/migrations/__init__.py b/InvenTree/customer_orders/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/InvenTree/customer_orders/models.py b/InvenTree/customer_orders/models.py new file mode 100644 index 0000000000..93171fe2a7 --- /dev/null +++ b/InvenTree/customer_orders/models.py @@ -0,0 +1,64 @@ +from django.db import models + +from supplier.models import Customer +from part.models import Part + +import datetime + + +class CustomerOrder(models.Model): + """ + An order from a customer, made up of multiple 'lines' + """ + # Reference 'number' internal to company, must be unique + internal_ref = models.CharField(max_length=100, unique=True) + + # TODO: Should the Customer model move to the customer_orders app? + # Orders can exist even if the customer doesn't in the database + customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, + blank=True, null=True, help_text="Customer that placed this order") + + # Reference from customer, if provided + customer_ref = models.CharField(max_length=100, blank=True, default="") + + # TODO: Should the customer and customer_ref together be unique? + + # Date the order was entered into system + created_date = models.DateField(auto_now_add=True, blank=True, help_text="Date order entered " + "in system") + + # Date the order was issued on the paperwork, if provided + issued_date = models.DateField(blank=True, help_text="Date order issued by customer") + + # Order notes + notes = models.TextField(blank=True, default="", help_text="Order notes") + + +class CustomerOrderLine(models.Model): + """ + A line on an order from a customer, corresponding to some quantity of some parts (hopefully just one part per line + in a sane world, but maybe not). + + The line describes the Part ordered, but something needs to associate the StockItem assigned, possibly that will + be the StockItem itself. + """ + + class Meta: + unique_together = [ + ('customer_order', 'line_number') + ] + + # Point to a specific customer order + customer_order = models.ForeignKey(CustomerOrder, on_delete=models.CASCADE, help_text="Order this line belongs to") + + line_number = models.PositiveIntegerField(default=0, help_text="Line number") + + # TODO: for now, each line corresponds to some quantity of some part, but in future we might want more flexibility + part = models.ForeignKey(Part, blank=True, help_text="Part") + + # TODO: should quantity field here somehow related to quantity field of related part? Views will handle this, right? + quantity = models.IntegerField(blank=True, help_text="Quantity of part") + + # Line notes + notes = models.TextField(blank=True, help_text="Line notes") + diff --git a/InvenTree/customer_orders/templates/customer_orders/index.html b/InvenTree/customer_orders/templates/customer_orders/index.html new file mode 100644 index 0000000000..4c49b9d019 --- /dev/null +++ b/InvenTree/customer_orders/templates/customer_orders/index.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} + +{% if customer_orders.all|length > 0 %} +
Internal Ref | +Customer | +Customer Ref | +
---|---|---|
{{ order.internal_ref }} | +{{ order.customer }} | +{{ order.customer_ref }} | +