mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
14217ff648
@ -238,6 +238,18 @@ class AjaxCreateView(AjaxMixin, CreateView):
|
|||||||
- Handles form validation via AJAX POST requests
|
- Handles form validation via AJAX POST requests
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def pre_save(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Hook for doing something before the form is validated
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_save(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Hook for doing something with the created object after it is saved
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
""" Creates form with initial data, and renders JSON response """
|
""" Creates form with initial data, and renders JSON response """
|
||||||
|
|
||||||
@ -255,26 +267,29 @@ class AjaxCreateView(AjaxMixin, CreateView):
|
|||||||
- Return status info (success / failure)
|
- Return status info (success / failure)
|
||||||
"""
|
"""
|
||||||
self.request = request
|
self.request = request
|
||||||
form = self.get_form()
|
self.form = self.get_form()
|
||||||
|
|
||||||
# Extra JSON data sent alongside form
|
# Extra JSON data sent alongside form
|
||||||
data = {
|
data = {
|
||||||
'form_valid': form.is_valid(),
|
'form_valid': self.form.is_valid(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.is_valid():
|
if self.form.is_valid():
|
||||||
obj = form.save()
|
|
||||||
|
self.pre_save()
|
||||||
|
self.object = self.form.save()
|
||||||
|
self.post_save()
|
||||||
|
|
||||||
# Return the PK of the newly-created object
|
# Return the PK of the newly-created object
|
||||||
data['pk'] = obj.pk
|
data['pk'] = self.object.pk
|
||||||
data['text'] = str(obj)
|
data['text'] = str(object)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data['url'] = obj.get_absolute_url()
|
data['url'] = self.object.get_absolute_url()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.renderJsonResponse(request, form, data)
|
return self.renderJsonResponse(request, self.form, data)
|
||||||
|
|
||||||
|
|
||||||
class AjaxUpdateView(AjaxMixin, UpdateView):
|
class AjaxUpdateView(AjaxMixin, UpdateView):
|
||||||
|
@ -8,8 +8,11 @@ from __future__ import unicode_literals
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from mptt.fields import TreeNodeChoiceField
|
||||||
|
|
||||||
from InvenTree.forms import HelperForm
|
from InvenTree.forms import HelperForm
|
||||||
|
|
||||||
|
from stock.models import StockLocation
|
||||||
from .models import PurchaseOrder, PurchaseOrderLineItem
|
from .models import PurchaseOrder, PurchaseOrderLineItem
|
||||||
|
|
||||||
|
|
||||||
@ -35,6 +38,17 @@ class CancelPurchaseOrderForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ReceivePurchaseOrderForm(HelperForm):
|
||||||
|
|
||||||
|
location = TreeNodeChoiceField(queryset=StockLocation.objects.all(), required=True, help_text=_('Receive parts to this location'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PurchaseOrder
|
||||||
|
fields = [
|
||||||
|
'location',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EditPurchaseOrderForm(HelperForm):
|
class EditPurchaseOrderForm(HelperForm):
|
||||||
""" Form for editing a PurchaseOrder object """
|
""" Form for editing a PurchaseOrder object """
|
||||||
|
|
||||||
|
@ -108,14 +108,12 @@ InvenTree | {{ order }}
|
|||||||
<th data-sortable='true'>Received</th>
|
<th data-sortable='true'>Received</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<th>Note</th>
|
<th>Note</th>
|
||||||
{% if order.status == OrderStatus.PENDING %}
|
|
||||||
<th></th>
|
<th></th>
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for line in order.lines.all %}
|
{% for line in order.lines.all %}
|
||||||
<tr>
|
<tr{% if order.status == OrderStatus.PLACED %} class={% if line.received < line.quantity %}'rowinvalid'{% else %}'rowvalid'{% endif %}{% endif %}>
|
||||||
<td>
|
<td>
|
||||||
{{ forloop.counter }}
|
{{ forloop.counter }}
|
||||||
</td>
|
</td>
|
||||||
@ -137,18 +135,23 @@ InvenTree | {{ order }}
|
|||||||
<td>
|
<td>
|
||||||
{{ line.notes }}
|
{{ line.notes }}
|
||||||
</td>
|
</td>
|
||||||
{% if order.status == OrderStatus.PENDING %}
|
|
||||||
<td>
|
<td>
|
||||||
<div class='btn-group'>
|
<div class='btn-group'>
|
||||||
|
{% if order.status == OrderStatus.PENDING %}
|
||||||
<button class='btn btn-default btn-glyph' line='{{ line.id }}' id='edit-line-item-{{ line.id }} title='Edit line item' onclick='editPurchaseOrderLineItem()'>
|
<button class='btn btn-default btn-glyph' line='{{ line.id }}' id='edit-line-item-{{ line.id }} title='Edit line item' onclick='editPurchaseOrderLineItem()'>
|
||||||
<span url="{% url 'po-line-item-edit' line.id %}" line='{{ line.id }}' class='glyphicon glyphicon-edit'></span>
|
<span url="{% url 'po-line-item-edit' line.id %}" line='{{ line.id }}' class='glyphicon glyphicon-edit'></span>
|
||||||
</button>
|
</button>
|
||||||
<button class='btn btn-default btn-glyph' line='{{ line.id }}' id='remove-line-item-{{ line.id }' title='Remove line item' type='button' onclick='removePurchaseOrderLineItem()'>
|
<button class='btn btn-default btn-glyph' line='{{ line.id }}' id='remove-line-item-{{ line.id }' title='Remove line item' type='button' onclick='removePurchaseOrderLineItem()'>
|
||||||
<span url="{% url 'po-line-item-delete' line.id %}" line='{{ line.id }}' class='glyphicon glyphicon-remove'></span>
|
<span url="{% url 'po-line-item-delete' line.id %}" line='{{ line.id }}' class='glyphicon glyphicon-remove'></span>
|
||||||
</button>
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if order.status == OrderStatus.PLACED and line.received < line.quantity %}
|
||||||
|
<button class='btn btn-default btn-glyph line-receive' pk='{{ line.pk }}' title='Receive item(s)'>
|
||||||
|
<span class='glyphicon glyphicon-check'></span>
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -187,9 +190,31 @@ $("#cancel-order").click(function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#po-lines-table").on('click', ".line-receive", function() {
|
||||||
|
|
||||||
|
var button = $(this);
|
||||||
|
|
||||||
|
console.log('clicked! ' + button.attr('pk'));
|
||||||
|
|
||||||
|
launchModalForm("{% url 'purchase-order-receive' order.id %}", {
|
||||||
|
reload: true,
|
||||||
|
data: {
|
||||||
|
line: button.attr('pk')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$("#receive-order").click(function() {
|
$("#receive-order").click(function() {
|
||||||
launchModalForm("{% url 'purchase-order-receive' order.id %}", {
|
launchModalForm("{% url 'purchase-order-receive' order.id %}", {
|
||||||
reload: true,
|
reload: true,
|
||||||
|
secondary: [
|
||||||
|
{
|
||||||
|
field: 'location',
|
||||||
|
label: 'New Location',
|
||||||
|
title: 'Create new stock location',
|
||||||
|
url: "{% url 'stock-location-create' %}",
|
||||||
|
},
|
||||||
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,23 +8,6 @@ Receive outstanding parts for <b>{{ order }}</b> - <i>{{ order.description }}</i
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
<div class='control-group'>
|
|
||||||
<label class='control-label requiredField'>Location</label>
|
|
||||||
<div class='controls'>
|
|
||||||
<select class='select' name='receive_location'>
|
|
||||||
<option value=''>---------</option>
|
|
||||||
{% for loc in locations %}
|
|
||||||
<option value='{{ loc.id }}' {% if destination.id == loc.id %}selected='selected'{% endif %}>{{ loc.pathstring }} - {{ loc.description }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
{% if not destination %}
|
|
||||||
<span class='help-inline'>Select location to receive parts</span>
|
|
||||||
{% else %}
|
|
||||||
<p class='help-block'>Location of received parts</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class='control-label'>Parts</label>
|
<label class='control-label'>Parts</label>
|
||||||
<p class='help-block'>Select parts to receive against this order.</p>
|
<p class='help-block'>Select parts to receive against this order.</p>
|
||||||
|
|
||||||
@ -64,6 +47,8 @@ Receive outstanding parts for <b>{{ order }}</b> - <i>{{ order.description }}</i
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{% crispy form %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -91,6 +91,12 @@ class PurchaseOrderCreate(AjaxCreateView):
|
|||||||
|
|
||||||
return initials
|
return initials
|
||||||
|
|
||||||
|
def post_save(self, **kwargs):
|
||||||
|
# Record the user who created this purchase order
|
||||||
|
|
||||||
|
self.object.created_by = self.request.user
|
||||||
|
self.object.save()
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderEdit(AjaxUpdateView):
|
class PurchaseOrderEdit(AjaxUpdateView):
|
||||||
""" View for editing a PurchaseOrder using a modal form """
|
""" View for editing a PurchaseOrder using a modal form """
|
||||||
@ -206,7 +212,7 @@ class PurchaseOrderExport(AjaxView):
|
|||||||
return DownloadFile(filedata, filename)
|
return DownloadFile(filedata, filename)
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderReceive(AjaxView):
|
class PurchaseOrderReceive(AjaxUpdateView):
|
||||||
""" View for receiving parts which are outstanding against a PurchaseOrder.
|
""" View for receiving parts which are outstanding against a PurchaseOrder.
|
||||||
|
|
||||||
Any parts which are outstanding are listed.
|
Any parts which are outstanding are listed.
|
||||||
@ -214,6 +220,7 @@ class PurchaseOrderReceive(AjaxView):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
form_class = order_forms.ReceivePurchaseOrderForm
|
||||||
ajax_form_title = "Receive Parts"
|
ajax_form_title = "Receive Parts"
|
||||||
ajax_template_name = "order/receive_parts.html"
|
ajax_template_name = "order/receive_parts.html"
|
||||||
|
|
||||||
@ -225,12 +232,34 @@ class PurchaseOrderReceive(AjaxView):
|
|||||||
ctx = {
|
ctx = {
|
||||||
'order': self.order,
|
'order': self.order,
|
||||||
'lines': self.lines,
|
'lines': self.lines,
|
||||||
'locations': StockLocation.objects.all(),
|
|
||||||
'destination': self.destination,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
def get_lines(self):
|
||||||
|
"""
|
||||||
|
Extract particular line items from the request,
|
||||||
|
or default to *all* pending line items if none are provided
|
||||||
|
"""
|
||||||
|
|
||||||
|
lines = None
|
||||||
|
|
||||||
|
if 'line' in self.request.GET:
|
||||||
|
line_id = self.request.GET.get('line')
|
||||||
|
|
||||||
|
try:
|
||||||
|
lines = PurchaseOrderLineItem.objects.filter(pk=line_id)
|
||||||
|
except (PurchaseOrderLineItem.DoesNotExist, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# TODO - Option to pass multiple lines?
|
||||||
|
|
||||||
|
# No lines specified - default selection
|
||||||
|
if lines is None:
|
||||||
|
lines = self.order.pending_line_items()
|
||||||
|
|
||||||
|
return lines
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
""" Respond to a GET request. Determines which parts are outstanding,
|
""" Respond to a GET request. Determines which parts are outstanding,
|
||||||
and presents a list of these parts to the user.
|
and presents a list of these parts to the user.
|
||||||
@ -239,13 +268,13 @@ class PurchaseOrderReceive(AjaxView):
|
|||||||
self.request = request
|
self.request = request
|
||||||
self.order = get_object_or_404(PurchaseOrder, pk=self.kwargs['pk'])
|
self.order = get_object_or_404(PurchaseOrder, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
self.lines = self.order.pending_line_items()
|
self.lines = self.get_lines()
|
||||||
|
|
||||||
for line in self.lines:
|
for line in self.lines:
|
||||||
# Pre-fill the remaining quantity
|
# Pre-fill the remaining quantity
|
||||||
line.receive_quantity = line.remaining()
|
line.receive_quantity = line.remaining()
|
||||||
|
|
||||||
return self.renderJsonResponse(request)
|
return self.renderJsonResponse(request, form=self.get_form())
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
""" Respond to a POST request. Data checking and error handling.
|
""" Respond to a POST request. Data checking and error handling.
|
||||||
@ -260,8 +289,8 @@ class PurchaseOrderReceive(AjaxView):
|
|||||||
self.destination = None
|
self.destination = None
|
||||||
|
|
||||||
# Extract the destination for received parts
|
# Extract the destination for received parts
|
||||||
if 'receive_location' in request.POST:
|
if 'location' in request.POST:
|
||||||
pk = request.POST['receive_location']
|
pk = request.POST['location']
|
||||||
try:
|
try:
|
||||||
self.destination = StockLocation.objects.get(id=pk)
|
self.destination = StockLocation.objects.get(id=pk)
|
||||||
except (StockLocation.DoesNotExist, ValueError):
|
except (StockLocation.DoesNotExist, ValueError):
|
||||||
@ -316,7 +345,7 @@ class PurchaseOrderReceive(AjaxView):
|
|||||||
'success': 'Items marked as received',
|
'success': 'Items marked as received',
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.renderJsonResponse(request, data=data)
|
return self.renderJsonResponse(request, data=data, form=self.get_form())
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def receive_parts(self):
|
def receive_parts(self):
|
||||||
|
@ -129,13 +129,17 @@ class StockItem(models.Model):
|
|||||||
else:
|
else:
|
||||||
add_note = False
|
add_note = False
|
||||||
|
|
||||||
|
user = kwargs.pop('user', None)
|
||||||
|
|
||||||
|
add_note = add_note and kwargs.pop('note', True)
|
||||||
|
|
||||||
super(StockItem, self).save(*args, **kwargs)
|
super(StockItem, self).save(*args, **kwargs)
|
||||||
|
|
||||||
if add_note:
|
if add_note:
|
||||||
# This StockItem is being saved for the first time
|
# This StockItem is being saved for the first time
|
||||||
self.addTransactionNote(
|
self.addTransactionNote(
|
||||||
'Created stock item',
|
'Created stock item',
|
||||||
None,
|
user,
|
||||||
notes="Created new stock item for part '{p}'".format(p=str(self.part)),
|
notes="Created new stock item for part '{p}'".format(p=str(self.part)),
|
||||||
system=True
|
system=True
|
||||||
)
|
)
|
||||||
@ -466,7 +470,8 @@ class StockItem(models.Model):
|
|||||||
if location:
|
if location:
|
||||||
new_item.location = location
|
new_item.location = location
|
||||||
|
|
||||||
new_item.save()
|
# The item already has a transaction history, don't create a new note
|
||||||
|
new_item.save(user=user, note=False)
|
||||||
|
|
||||||
# Copy entire transaction history
|
# Copy entire transaction history
|
||||||
new_item.copyHistoryFrom(self)
|
new_item.copyHistoryFrom(self)
|
||||||
|
@ -792,6 +792,8 @@ class StockItemCreate(AjaxCreateView):
|
|||||||
|
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
|
||||||
valid = form.is_valid()
|
valid = form.is_valid()
|
||||||
|
|
||||||
if valid:
|
if valid:
|
||||||
@ -850,7 +852,7 @@ class StockItemCreate(AjaxCreateView):
|
|||||||
URL=data.get('URL'),
|
URL=data.get('URL'),
|
||||||
)
|
)
|
||||||
|
|
||||||
item.save()
|
item.save(user=request.user)
|
||||||
|
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
form.errors['serial_numbers'] = e.messages
|
form.errors['serial_numbers'] = e.messages
|
||||||
@ -861,11 +863,15 @@ class StockItemCreate(AjaxCreateView):
|
|||||||
# We need to call _post_clean() here because it is prevented in the form implementation
|
# We need to call _post_clean() here because it is prevented in the form implementation
|
||||||
form.clean()
|
form.clean()
|
||||||
form._post_clean()
|
form._post_clean()
|
||||||
form.save()
|
|
||||||
|
item = form.save(commit=False)
|
||||||
|
item.save(user=request.user)
|
||||||
|
|
||||||
data = {
|
data['pk'] = item.pk
|
||||||
'form_valid': valid,
|
data['url'] = item.get_absolute_url()
|
||||||
}
|
data['success'] = _("Created new stock item")
|
||||||
|
|
||||||
|
data['form_valid'] = valid
|
||||||
|
|
||||||
return self.renderJsonResponse(request, form, data=data)
|
return self.renderJsonResponse(request, form, data=data)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user