Add a "sales_order" reference to the Build model

- If a build order is made to fulfil a sales order
- Add sales_order filtering to the Build API
- Pass initial information through to the BuildCreate view
This commit is contained in:
Oliver Walters 2020-04-25 13:15:45 +10:00
parent b351976ae9
commit d5f3498238
8 changed files with 96 additions and 29 deletions

View File

@ -38,6 +38,8 @@ class BuildList(generics.ListCreateAPIView):
] ]
filter_fields = [ filter_fields = [
'part',
'sales_order',
] ]
def get_queryset(self): def get_queryset(self):
@ -48,12 +50,6 @@ class BuildList(generics.ListCreateAPIView):
build_list = super().get_queryset() build_list = super().get_queryset()
# Filter by part
part = self.request.query_params.get('part', None)
if part is not None:
build_list = build_list.filter(part=part)
# Filter by build status? # Filter by build status?
status = self.request.query_params.get('status', None) status = self.request.query_params.get('status', None)

View File

@ -22,6 +22,7 @@ class EditBuildForm(HelperForm):
fields = [ fields = [
'title', 'title',
'part', 'part',
'sales_order',
'quantity', 'quantity',
'take_from', 'take_from',
'batch', 'batch',

View File

@ -0,0 +1,20 @@
# Generated by Django 3.0.5 on 2020-04-24 22:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('order', '0029_auto_20200423_1042'),
('build', '0011_auto_20200406_0123'),
]
operations = [
migrations.AddField(
model_name='build',
name='sales_order',
field=models.ForeignKey(blank=True, help_text='SalesOrder to which this build is allocated', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds', to='order.SalesOrder'),
),
]

View File

@ -33,6 +33,7 @@ class Build(models.Model):
part: The part to be built (from component BOM items) part: The part to be built (from component BOM items)
title: Brief title describing the build (required) title: Brief title describing the build (required)
quantity: Number of units to be built quantity: Number of units to be built
sales_order: References to a SalesOrder object for which this Build is required (e.g. the output of this build will be used to fulfil a sales order)
take_from: Location to take stock from to make this build (if blank, can take from anywhere) take_from: Location to take stock from to make this build (if blank, can take from anywhere)
status: Build status code status: Build status code
batch: Batch code transferred to build parts (optional) batch: Batch code transferred to build parts (optional)
@ -51,24 +52,37 @@ class Build(models.Model):
title = models.CharField( title = models.CharField(
blank=False, blank=False,
max_length=100, max_length=100,
help_text=_('Brief description of the build')) help_text=_('Brief description of the build')
)
part = models.ForeignKey('part.Part', on_delete=models.CASCADE, part = models.ForeignKey(
related_name='builds', 'part.Part',
limit_choices_to={ on_delete=models.CASCADE,
'is_template': False, related_name='builds',
'assembly': True, limit_choices_to={
'active': True, 'is_template': False,
'virtual': False, 'assembly': True,
}, 'active': True,
help_text=_('Select part to build'), 'virtual': False,
) },
help_text=_('Select part to build'),
)
take_from = models.ForeignKey('stock.StockLocation', on_delete=models.SET_NULL, sales_order = models.ForeignKey(
related_name='sourcing_builds', 'order.SalesOrder',
null=True, blank=True, on_delete=models.SET_NULL,
help_text=_('Select location to take stock from for this build (leave blank to take from any stock location)') related_name='builds',
) null=True, blank=True,
help_text=_('SalesOrder to which this build is allocated')
)
take_from = models.ForeignKey(
'stock.StockLocation',
on_delete=models.SET_NULL,
related_name='sourcing_builds',
null=True, blank=True,
help_text=_('Select location to take stock from for this build (leave blank to take from any stock location)')
)
quantity = models.PositiveIntegerField( quantity = models.PositiveIntegerField(
default=1, default=1,

View File

@ -39,6 +39,7 @@ class BuildSerializer(InvenTreeModelSerializer):
'completion_date', 'completion_date',
'part', 'part',
'part_detail', 'part_detail',
'sales_order',
'quantity', 'quantity',
'status', 'status',
'status_text', 'status_text',

View File

@ -8,6 +8,14 @@
InvenTree | {% trans "Build" %} - {{ build }} InvenTree | {% trans "Build" %} - {{ build }}
{% endblock %} {% endblock %}
{% block pre_content %}
{% if build.sales_order %}
<div class='alert alert-block alert-info'>
{% trans "This build is allocated to Sales Order" %} <b><a href="{% url 'so-detail' build.sales_order.id %}">{{ build.sales_order }}</a></b>
</div>
{% endif %}
{% endblock %}
{% block thumbnail %} {% block thumbnail %}
<img class="part-thumb" <img class="part-thumb"
{% if build.part.image %} {% if build.part.image %}
@ -54,7 +62,7 @@ src="{% static 'img/blank_image.png' %}"
</tr> </tr>
<tr> <tr>
<td><span class='fas fa-shapes'></span></td> <td><span class='fas fa-shapes'></span></td>
<td>Part</td> <td>{% trans "Part" %}</td>
<td><a href="{% url 'part-detail' build.part.id %}">{{ build.part.full_name }}</a></td> <td><a href="{% url 'part-detail' build.part.id %}">{{ build.part.full_name }}</a></td>
</tr> </tr>
<tr> <tr>
@ -67,6 +75,13 @@ src="{% static 'img/blank_image.png' %}"
<td>{% trans "Status" %}</td> <td>{% trans "Status" %}</td>
<td>{% build_status_label build.status %}</td> <td>{% build_status_label build.status %}</td>
</tr> </tr>
{% if build.sales_order %}
<tr>
<td><span class='fas fa-dolly'></span></td>
<td>{% trans "Sales Order" %}</td>
<td><a href="{% url 'so-detail' build.sales_order.id %}">{{ build.sales_order }}</a></td>
</tr>
{% endif %}
<tr> <tr>
<td><span class='fas fa-dollar-sign'></span></td> <td><span class='fas fa-dollar-sign'></span></td>
<td>{% trans "BOM Price" %}</td> <td>{% trans "BOM Price" %}</td>

View File

@ -393,13 +393,13 @@ class BuildCreate(AjaxCreateView):
initials = super(BuildCreate, self).get_initial().copy() initials = super(BuildCreate, self).get_initial().copy()
part_id = self.request.GET.get('part', None) # User has provided a Part ID
initials['part'] = self.request.GET.get('part', None)
if part_id: # User has provided a SalesOrder ID
try: initials['sales_order'] = self.request.GET.get('sales_order', None)
initials['part'] = Part.objects.get(pk=part_id)
except Part.DoesNotExist: initials['quantity'] = self.request.GET.get('quantity', 1)
pass
return initials return initials

View File

@ -6,6 +6,9 @@
{% load static %} {% load static %}
{% block details %} {% block details %}
{% include "order/so_tabs.html" with tab='details' %}
<hr> <hr>
<h4>{% trans "Sales Order Items" %}</h4> <h4>{% trans "Sales Order Items" %}</h4>
@ -45,6 +48,7 @@ $("#so-lines-table").inventreeTable({
part_detail: true, part_detail: true,
allocations: true, allocations: true,
}, },
uniqueId: 'pk',
url: "{% url 'api-so-line-list' %}", url: "{% url 'api-so-line-list' %}",
onPostBody: setupCallbacks, onPostBody: setupCallbacks,
detailViewByClick: true, detailViewByClick: true,
@ -242,12 +246,28 @@ function setupCallbacks() {
}); });
table.find(".button-build").click(function() { table.find(".button-build").click(function() {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
// Extract the row data from the table!
var idx = $(this).closest('tr').attr('data-index');
var row = table.bootstrapTable('getData')[idx];
console.log('Row ' + idx + ' - ' + row.pk + ', ' + row.quantity);
var quantity = 1;
if (row.allocated < row.quantity) {
quantity = row.quantity - row.allocated;
}
launchModalForm(`/build/new/`, { launchModalForm(`/build/new/`, {
follow: true, follow: true,
data: { data: {
part: pk, part: pk,
sales_order: {{ order.id }},
quantity: quantity,
}, },
}); });
}); });