Implement Add Stock modal
This commit is contained in:
parent
b4e561279b
commit
fbb60b455f
@ -47,7 +47,7 @@ class PartsComponentViewSet(viewsets.ModelViewSet):
|
|||||||
serializer_class = ComponentSerializer
|
serializer_class = ComponentSerializer
|
||||||
permission_classes = [permissions.DjangoModelPermissions]
|
permission_classes = [permissions.DjangoModelPermissions]
|
||||||
filter_backends = [filters.SearchFilter]
|
filter_backends = [filters.SearchFilter]
|
||||||
search_fields = ['name', 'package__name', 'manufacturer__name']
|
search_fields = ['id', 'name', 'package__name', 'manufacturer__name']
|
||||||
|
|
||||||
class PartsManufacturerViewSet(viewsets.ModelViewSet):
|
class PartsManufacturerViewSet(viewsets.ModelViewSet):
|
||||||
queryset = parts_models.Manufacturer.objects.all()
|
queryset = parts_models.Manufacturer.objects.all()
|
||||||
|
@ -72,3 +72,36 @@ class EditStockAmountForm(forms.Form):
|
|||||||
amount = -amount
|
amount = -amount
|
||||||
|
|
||||||
return stock.atomic_increment(amount)
|
return stock.atomic_increment(amount)
|
||||||
|
|
||||||
|
class AddStockForm(forms.Form):
|
||||||
|
watermark_active = forms.BooleanField(required=False)
|
||||||
|
watermark = forms.IntegerField(min_value=0, required=True, initial=0)
|
||||||
|
amount = forms.IntegerField(min_value=0, required=True, initial=1)
|
||||||
|
component_uuid = forms.UUIDField(required=True)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
|
id = cleaned_data.get('component_uuid')
|
||||||
|
if not id:
|
||||||
|
raise ValidationError('No valid component selected!')
|
||||||
|
component = None
|
||||||
|
try:
|
||||||
|
component = parts_models.Component.objects.get(id=id)
|
||||||
|
except:
|
||||||
|
raise ValidationError("Invalid component selected!")
|
||||||
|
cleaned_data['component'] = component
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
def save(self, storage):
|
||||||
|
component = self.cleaned_data.get('component')
|
||||||
|
amount = self.cleaned_data.get('amount')
|
||||||
|
watermark = -1
|
||||||
|
|
||||||
|
if self.cleaned_data.get('watermark-active'):
|
||||||
|
watermark = self.cleaned_data.get('watermark')
|
||||||
|
|
||||||
|
new_stock = parts_models.Stock.objects.create(storage=storage, component=component, watermark=watermark, amount=amount)
|
||||||
|
new_stock.save()
|
||||||
|
|
@ -13,7 +13,7 @@ from .models import Storage, Stock
|
|||||||
from .qr_parser import QrCodeValidator
|
from .qr_parser import QrCodeValidator
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from .forms import MyTestForm, AddSubStorageForm, DeleteStockForm, EditWatermarkForm, EditStockAmountForm
|
from .forms import MyTestForm, AddSubStorageForm, DeleteStockForm, EditWatermarkForm, EditStockAmountForm, AddStockForm
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
import uuid
|
import uuid
|
||||||
@ -223,6 +223,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
|
|||||||
add_storage_form.fields['responsible'].initial = self.request.user.username
|
add_storage_form.fields['responsible'].initial = self.request.user.username
|
||||||
context['add_storage_form'] = add_storage_form
|
context['add_storage_form'] = add_storage_form
|
||||||
context['delete_storage_error'] = None
|
context['delete_storage_error'] = None
|
||||||
|
context['add_stock_form'] = AddStockForm()
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -293,6 +294,24 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
|
|||||||
context = self.get_context_data(**kwargs)
|
context = self.get_context_data(**kwargs)
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
|
|
||||||
|
def handle_add_stock_post(self, request, **kwargs):
|
||||||
|
f = AddStockForm(data=request.POST)
|
||||||
|
error_occured = False
|
||||||
|
|
||||||
|
if f.is_valid():
|
||||||
|
try:
|
||||||
|
f.save(self.object)
|
||||||
|
except Exception as ex:
|
||||||
|
f.add_error('', str(ex))
|
||||||
|
error_occured = True
|
||||||
|
else:
|
||||||
|
error_occured = True
|
||||||
|
|
||||||
|
context = self.get_context_data(**kwargs)
|
||||||
|
if error_occured:
|
||||||
|
context['add_stock_form'] = f
|
||||||
|
return self.render_to_response(context)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
|
|
||||||
@ -308,4 +327,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
|
|||||||
return self.handle_amount_change_post(request, False, **kwargs)
|
return self.handle_amount_change_post(request, False, **kwargs)
|
||||||
elif 'submit-amount-increase' in request.POST:
|
elif 'submit-amount-increase' in request.POST:
|
||||||
return self.handle_amount_change_post(request, True, **kwargs)
|
return self.handle_amount_change_post(request, True, **kwargs)
|
||||||
|
elif 'submit-add-stock' in request.POST:
|
||||||
|
return self.handle_add_stock_post(request, **kwargs)
|
||||||
|
|
||||||
return super().post(request, *args, **kwargs)
|
return super().post(request, *args, **kwargs)
|
@ -1,3 +1,31 @@
|
|||||||
|
function fill_add_stock_modal_component(data) {
|
||||||
|
// component has been selected. Process it:
|
||||||
|
document.getElementById('add-stock-search').value = '';
|
||||||
|
console.log(data);
|
||||||
|
var template = document.querySelector('#add-stock-modal-component-template');
|
||||||
|
var container = document.querySelector('#add-stock-modal-component-container');
|
||||||
|
container.innerHTML = '';
|
||||||
|
var comp_elem = template.content.cloneNode(true);
|
||||||
|
|
||||||
|
if (data.ro_image) {
|
||||||
|
comp_elem.querySelector('#add-stock-cmp-img').setAttribute('src', data.ro_image);
|
||||||
|
}
|
||||||
|
var description_container = comp_elem.querySelector('#add-stock-cmp-desc-container');
|
||||||
|
var heading = document.createElement('h6');
|
||||||
|
heading.appendChild(document.createTextNode(data.name));
|
||||||
|
description_container.appendChild(heading);
|
||||||
|
if (data.package_data && data.package_data.name) {
|
||||||
|
description_container.appendChild(document.createTextNode('in '+data.package_data.name));
|
||||||
|
}
|
||||||
|
if (data.ro_manufacturer_name) {
|
||||||
|
description_container.appendChild(document.createElement('br'));
|
||||||
|
description_container.appendChild(document.createTextNode('by '+data.ro_manufacturer_name));
|
||||||
|
}
|
||||||
|
document.querySelector('#add-stock-modal-comp-uuid').value = data.id;
|
||||||
|
console.log("Selected element: "+data.id);
|
||||||
|
container.appendChild(comp_elem);
|
||||||
|
}
|
||||||
|
|
||||||
new AutocompleteCustomUi('add-stock-search', 'add-stock-search-ac-dropdown',
|
new AutocompleteCustomUi('add-stock-search', 'add-stock-search-ac-dropdown',
|
||||||
function(search, autocomplete_obj) {
|
function(search, autocomplete_obj) {
|
||||||
api_search_component(search, function(results) {
|
api_search_component(search, function(results) {
|
||||||
@ -36,9 +64,9 @@ function(search, autocomplete_obj) {
|
|||||||
node.appendChild(img_container);
|
node.appendChild(img_container);
|
||||||
node.appendChild(text_container);
|
node.appendChild(text_container);
|
||||||
|
|
||||||
test.push({'ui': node, 'data': c.url})
|
test.push({'ui': node, 'data': c})
|
||||||
}
|
}
|
||||||
autocomplete_obj.show_results(test,
|
autocomplete_obj.show_results(test,
|
||||||
function(data) {console.log(data);});
|
function(data){fill_add_stock_modal_component(data);});
|
||||||
}, function(){});
|
}, function(){});
|
||||||
});
|
});
|
@ -11,6 +11,8 @@ class AutocompleteCustomUi {
|
|||||||
this.query_callback(document.getElementById(this.text_id).value, this);
|
this.query_callback(document.getElementById(this.text_id).value, this);
|
||||||
}, autocomplete_query_delay_ms).bind(this));
|
}, autocomplete_query_delay_ms).bind(this));
|
||||||
|
|
||||||
|
this.dropdown_data = {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,15 +26,17 @@ class AutocompleteCustomUi {
|
|||||||
var ul = document.getElementById(this.dropdown_id);
|
var ul = document.getElementById(this.dropdown_id);
|
||||||
ul.innerHTML = '';
|
ul.innerHTML = '';
|
||||||
this.select_data = {};
|
this.select_data = {};
|
||||||
|
this.dropdown_data = {};
|
||||||
|
|
||||||
for (var i = 0; i < nodes_to_add.length; i++) {
|
for (var i = 0; i < nodes_to_add.length; i++) {
|
||||||
var ui = nodes_to_add[i]['ui'];
|
var ui = nodes_to_add[i]['ui'];
|
||||||
var data = nodes_to_add[i]['data']
|
var data = nodes_to_add[i]['data']
|
||||||
var dropdown_node = document.createElement('li');
|
var dropdown_node = document.createElement('li');
|
||||||
dropdown_node.dataset.ac_clicked = data;
|
dropdown_node.dataset.ac_clicked = i;
|
||||||
|
this.dropdown_data[i] = data;
|
||||||
dropdown_node.addEventListener('click',
|
dropdown_node.addEventListener('click',
|
||||||
(e) => {
|
(e) => {
|
||||||
data_clicked_callback(e.currentTarget.dataset.ac_clicked);
|
data_clicked_callback(this.dropdown_data[e.currentTarget.dataset.ac_clicked]);
|
||||||
var text_box = document.getElementById(this.text_id);
|
var text_box = document.getElementById(this.text_id);
|
||||||
var dropdown = bootstrap.Dropdown.getOrCreateInstance(text_box);
|
var dropdown = bootstrap.Dropdown.getOrCreateInstance(text_box);
|
||||||
dropdown.hide();
|
dropdown.hide();
|
||||||
|
@ -32,3 +32,7 @@ function api_search_user(search, onSuccess, onFail) {
|
|||||||
function api_search_component(search, onSuccess, onFail) {
|
function api_search_component(search, onSuccess, onFail) {
|
||||||
return api_ajax_request_without_send('GET', api_urls_v1['component-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail);
|
return api_ajax_request_without_send('GET', api_urls_v1['component-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function api_get_component_from_id(id, onSuccess, onFail) {
|
||||||
|
return api_ajax_request_without_send('GET', api_urls_v1['component-list']+`?search=${encodeURIComponent(id)}`, function(method, url, json) {onSuccess(json.results[0]);}, onFail);
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
Input context:
|
Input context:
|
||||||
- form: A stock-create-form
|
- form: A stock-create-form
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
{% load static %}
|
||||||
<div class="modal fade" id="add-stock-modal">
|
<div class="modal fade" id="add-stock-modal">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@ -14,12 +15,59 @@ Input context:
|
|||||||
<input class="form-control" autocomplete="off" data-bs-toggle="dropdown" type="search" id="add-stock-search" placeholder="Search Component" aria-label="Search for Component">
|
<input class="form-control" autocomplete="off" data-bs-toggle="dropdown" type="search" id="add-stock-search" placeholder="Search Component" aria-label="Search for Component">
|
||||||
<ul class="dropdown-menu" aria-labelledby="add-stock-search" id="add-stock-search-ac-dropdown">
|
<ul class="dropdown-menu" aria-labelledby="add-stock-search" id="add-stock-search-ac-dropdown">
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
||||||
<input type="submit" class="btn btn-primary" value="Add Stock" name="submit-add-stock">
|
|
||||||
</div>
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="add-stock-modal-component-container" class="mb-3">
|
||||||
|
<h4>No component selected.<h4>
|
||||||
|
<!-- Will be filled dynamically by selecting a component from the dropdown list -->
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="{{form.component_uuid.name}}" id="add-stock-modal-comp-uuid" value="{{form.component_uuid.value}}" required>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="add-stock-form-amount" class="form-label">Amount</label>
|
||||||
|
<input id="add-stock-form-amount" type="number" class="form-control{% if form.amount.errors %} is-invalid{% endif %}" min="0" name="{{form.amount.name}}" value="{{form.amount.value}}" required aria-describedby="add-stock-form-amount-err">
|
||||||
|
<div id="add-stock-form-amount-err" class="invalid-feedback">
|
||||||
|
{% for msg in form.amount.errors %}
|
||||||
|
{{msg}}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="add-stock-form-watermark" class="form-label">Watermark</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<input class="form-check-input mt-0" type="checkbox" {% if form.watermark_active.value %}checked{% endif %} name="{{form.watermark_active.name}}">
|
||||||
|
</div>
|
||||||
|
<input id="add-stock-form-watermark" type="number" class="form-control{% if form.watermark.errors %} is-invalid{% endif %}" name="{{form.watermark.name}}" value="{{form.watermark.value}}" min="0" required aria-describedby="add-stock-form-watermark-err">
|
||||||
|
<div id="add-stock-form-watermark-err" class="invalid-feedback">
|
||||||
|
{% for msg in form.watermark.errors %}
|
||||||
|
{{msg}}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<input type="submit" class="btn btn-primary" value="Add Stock" name="submit-add-stock">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<p class="text-danger text-center">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<template id="add-stock-modal-component-template">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img id="add-stock-cmp-img" src="{% static 'css/icons/card-image.svg' %}" style="width:64px;max-height:64px;">
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 ms-1" id="add-stock-cmp-desc-container">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -121,6 +121,7 @@
|
|||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block custom_scripts %}
|
{% block custom_scripts %}
|
||||||
|
<script type="text/javascript" src="{% static 'js/add-stock-modal.js' %}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
{% if add_storage_form.errors %}
|
{% if add_storage_form.errors %}
|
||||||
|
|
||||||
@ -133,6 +134,26 @@
|
|||||||
var d_modal = bootstrap.Modal.getOrCreateInstance(deleteStorageModal);
|
var d_modal = bootstrap.Modal.getOrCreateInstance(deleteStorageModal);
|
||||||
d_modal.show();
|
d_modal.show();
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if add_stock_form.errors %}
|
||||||
|
var uuid = "{{add_stock_form.component_uuid.value}}";
|
||||||
|
if (uuid && uuid != '' && uuid != 'None') {
|
||||||
|
api_get_component_from_id(uuid, function(component){
|
||||||
|
var add_stock_modal_div = document.querySelector('#add-stock-modal');
|
||||||
|
var add_stock_modal = bootstrap.Modal.getOrCreateInstance(add_stock_modal_div);
|
||||||
|
fill_add_stock_modal_component(component);
|
||||||
|
add_stock_modal.show();
|
||||||
|
console.log(component)
|
||||||
|
}, function(){
|
||||||
|
var add_stock_modal_div = document.querySelector('#add-stock-modal');
|
||||||
|
var add_stock_modal = bootstrap.Modal.getOrCreateInstance(add_stock_modal_div);
|
||||||
|
add_stock_modal.show();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var add_stock_modal_div = document.querySelector('#add-stock-modal');
|
||||||
|
var add_stock_modal = bootstrap.Modal.getOrCreateInstance(add_stock_modal_div);
|
||||||
|
add_stock_modal.show();
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
new AutocompleteText('{{add_storage_form.responsible.id_for_label}}', '{{add_storage_form.responsible.id_for_label}}-ac-dropdown',
|
new AutocompleteText('{{add_storage_form.responsible.id_for_label}}', '{{add_storage_form.responsible.id_for_label}}-ac-dropdown',
|
||||||
function(search, autocomplete_obj) {
|
function(search, autocomplete_obj) {
|
||||||
@ -150,7 +171,6 @@ function(search, autocomplete_obj) {
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const fallback_img_path = "{% static 'css/icons/card-image.svg' %}";
|
const fallback_img_path = "{% static 'css/icons/card-image.svg' %}";
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript" src="{% static 'js/add-stock-modal.js' %}"></script>
|
|
||||||
|
|
||||||
{% endblock custom_scripts %}
|
{% endblock custom_scripts %}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user