Merge branch 'PO'

This commit is contained in:
Oliver 2018-04-18 01:17:07 +10:00
commit 24f4c95060
22 changed files with 497 additions and 57 deletions

View File

@ -15,16 +15,19 @@ class Company(models.Model):
class Meta:
abstract = True
name = models.CharField(max_length=100, unique=True)
name = models.CharField(max_length=100, unique=True,
help_text='Company naem')
description = models.CharField(max_length=500)
website = models.URLField(blank=True)
website = models.URLField(blank=True, help_text='Company website URL')
address = models.CharField(max_length=200,
blank=True)
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,

View File

@ -1,4 +1,4 @@
{% include "base.html" %}
{% extends "base.html" %}
{% block content %}
<h3>Build Details</h3>

View File

@ -0,0 +1,35 @@
# -*- 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
class Migration(migrations.Migration):
dependencies = [
('customer', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='customer',
name='address',
field=models.CharField(blank=True, help_text='Company address', max_length=200),
),
migrations.AlterField(
model_name='customer',
name='name',
field=models.CharField(help_text='Company naem', max_length=100, unique=True),
),
migrations.AlterField(
model_name='customer',
name='notes',
field=models.TextField(blank=True),
),
migrations.AlterField(
model_name='customer',
name='website',
field=models.URLField(blank=True, help_text='Company website URL'),
),
]

View File

@ -1,23 +1,31 @@
<ul class="nav nav-tabs">
<li{% ifequal tab 'detail' %} class="active"{% endifequal %}><a href="{% url 'part-detail' part.id %}">Details</a></li>
<li{% ifequal tab 'detail' %} class="active"{% endifequal %}>
<a href="{% url 'part-detail' part.id %}">Details</a></li>
{% if part.buildable %}
<li{% ifequal tab 'bom' %} class="active"{% endifequal %}><a href="{% url 'part-bom' part.id %}">BOM<span class="badge">{{ part.bom_count }}</span></a></li>
<li{% ifequal tab 'build' %} class="active"{% endifequal %}><a href="{% url 'part-build' part.id %}">Build<span class='badge'>{{ part.can_build }}</span></a></li>
<li{% ifequal tab 'bom' %} class="active"{% endifequal %}>
<a href="{% url 'part-bom' part.id %}">BOM<span class="badge">{{ part.bom_count }}</span></a></li>
<li{% ifequal tab 'build' %} class="active"{% endifequal %}>
<a href="{% url 'part-build' part.id %}">Build<span class='badge'>{{ part.can_build }}</span></a></li>
{% endif %}
{% if part.used_in_count > 0 %}
<li{% ifequal tab 'used' %} class="active"{% endifequal %}><a href="{% url 'part-used-in' part.id %}">Used In{% if part.used_in_count > 0 %}<span class="badge">{{ part.used_in_count }}</span>{% endif %}</a></li>
<li{% ifequal tab 'used' %} class="active"{% endifequal %}>
<a href="{% url 'part-used-in' part.id %}">Used In{% if part.used_in_count > 0 %}<span class="badge">{{ part.used_in_count }}</span>{% endif %}</a></li>
{% endif %}
<li{% ifequal tab 'stock' %} class="active"{% endifequal %}><a href="{% url 'part-stock' part.id %}">Stock <span class="badge">{{ part.total_stock }}</span></a></li>
<li{% ifequal tab 'stock' %} class="active"{% endifequal %}>
<a href="{% url 'part-stock' part.id %}">Stock <span class="badge">{{ part.total_stock }}</span></a></li>
{% if part.allocation_count > 0 %}
<li{% ifequal tab 'allocation' %} class="active"{% endifequal %}><a href="{% url 'part-allocation' part.id %}">Allocated <span class="badge">{{ part.allocation_count }}</span></a></li>
<li{% ifequal tab 'allocation' %} class="active"{% endifequal %}>
<a href="{% url 'part-allocation' part.id %}">Allocated <span class="badge">{{ part.allocation_count }}</span></a></li>
{% endif %}
{% if part.purchaseable %}
<li{% ifequal tab 'suppliers' %} class="active"{% endifequal %}><a href="{% url 'part-suppliers' part.id %}">Suppliers
<li{% ifequal tab 'suppliers' %} class="active"{% endifequal %}>
<a href="{% url 'part-suppliers' part.id %}">Suppliers
<span class="badge">{{ part.supplier_count }}<span>
</a></li>
{% endif %}
{% if part.trackable %}
<li{% ifequal tab 'track' %} class="active"{% endifequal %}><a href="{% url 'part-track' part.id %}">Tracking
<li{% ifequal tab 'track' %} class="active"{% endifequal %}>
<a href="{% url 'part-track' part.id %}">Tracking
{% if parts.serials.all|length > 0 %}
<span class="badge">{{ part.serials.all|length }}</span>
{% endif %}

View File

@ -2,7 +2,7 @@ from django.contrib import admin
from import_export.admin import ImportExportModelAdmin
from .models import Supplier, SupplierPart, Manufacturer
from .models import SupplierOrder
class SupplierAdmin(ImportExportModelAdmin):
list_display = ('name', 'website', 'contact')
@ -16,6 +16,11 @@ 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(Supplier, SupplierAdmin)
admin.site.register(Manufacturer, ManufacturerAdmin)
admin.site.register(SupplierPart, SupplierPartAdmin)
admin.site.register(SupplierOrder, SupplierOrderAdmin)

View File

@ -3,6 +3,29 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from .models import Supplier, 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 EditSupplierForm(forms.ModelForm):

View 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')]),
),
]

View 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),
),
]

View 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'),
),
]

View 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),
),
]

View File

@ -1,5 +1,8 @@
# -*- 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
@ -103,3 +106,69 @@ class SupplierPriceBreak(models.Model):
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(Supplier, 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', 'part'),
]
order = models.ForeignKey(SupplierOrder, on_delete=models.CASCADE)
line_number = models.PositiveIntegerField(default=1)
part = models.ForeignKey(SupplierPart, null=True, blank=True, on_delete=models.SET_NULL)
quantity = models.PositiveIntegerField(default=1)
notes = models.TextField(blank=True)

View File

@ -1,45 +1,10 @@
{% extends "base.html" %}
{% extends "supplier/supplier_base.html" %}
{% block content %}
{% block details %}
<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>
{% include "supplier/tabs.html" with tab='parts' %}
<h4>Supplier Parts</h3>
<h3>Supplier Parts</h3>
<table class="table table-striped">
<tr>
<th>SKU</th>
@ -77,4 +42,5 @@
</a>
</div>
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends "create_edit_obj.html" %}
{% block obj_title %}
Create a new supplier purchase order
{% endblock %}

View File

@ -0,0 +1,50 @@
{% 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>
<h2>TODO</h2>
Here we list all the line ites which exist under this order...
{% endif %}
{% endblock %}

View 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>

View File

@ -0,0 +1,26 @@
{% 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>
{% endblock %}

View 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 %}

View 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</a></li>
<li{% if tab == 'order' %} class='active'{% endif %}>
<a href="{% url 'supplier-detail-orders' supplier.id %}">Orders</a></li>
</ul>

View File

@ -51,6 +51,8 @@ supplier_detail_urls = [
url(r'edit/?', views.SupplierEdit.as_view(), name='supplier-edit'),
url(r'delete/?', views.SupplierDelete.as_view(), name='supplier-delete'),
url(r'orders/?', views.SupplierDetail.as_view(template_name='supplier/orders.html'), name='supplier-detail-orders'),
url(r'^.*$', views.SupplierDetail.as_view(), name='supplier-detail'),
]
@ -61,10 +63,30 @@ supplier_part_detail_urls = [
url('^.*$', views.SupplierPartDetail.as_view(), name='supplier-part-detail'),
]
supplier_urls = [
url(r'part/(?P<pk>\d+)/', include(supplier_part_detail_urls)),
supplier_part_urls = [
url(r'^new/?', views.SupplierPartCreate.as_view(), name='supplier-part-create'),
url(r'part/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)),
]
supplier_urls = [
url(r'part/', include(supplier_part_urls)),
url(r'order/', include(supplier_order_urls)),
url(r'new/?', views.SupplierCreate.as_view(), name='supplier-create'),

View File

@ -6,9 +6,24 @@ from django.views.generic.edit import UpdateView, DeleteView, CreateView
from part.models import Part
from .models import Supplier, SupplierPart
from .models import SupplierOrder
from .forms import EditSupplierForm
from .forms import EditSupplierPartForm
from .forms import EditSupplierOrderForm
class SupplierOrderDetail(DetailView):
context_object_name = 'order'
model = SupplierOrder
template_name = 'supplier/order_detail.html'
queryset = SupplierOrder.objects.all()
class SupplierOrderCreate(CreateView):
model = SupplierOrder
form_class = EditSupplierOrderForm
context_object_name = 'supplier'
template_name = 'supplier/order_create.html'
class SupplierIndex(ListView):

View File

@ -33,7 +33,7 @@ InvenTree
{% include "navbar.html" %}
<div class="container-fluid inventree-content">
<div class="container container-fluid inventree-content">
{% block content %}
<!-- Each view fills in here.. -->
{% endblock %}

View File

@ -10,7 +10,7 @@
<li><a href="{% url 'stock-index' %}">Stock</a></li>
<li><a href="{% url 'build-index' %}">Build</a></li>
<li><a href="{% url 'supplier-index' %}">Suppliers</a></li>
<li><a href="{% url 'customer-order-index' %}">Customer Orders</a></li>
<li><a href="{% url 'customer-index' %}">Customers</a></li>
</ul>
</div>
</nav>