mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'master' of github.com:inventree/InvenTree into part_main_details
This commit is contained in:
commit
2703ae520e
@ -8,12 +8,16 @@ import re
|
|||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
|
|
||||||
INVENTREE_SW_VERSION = "0.3.0"
|
INVENTREE_SW_VERSION = "0.3.1"
|
||||||
|
|
||||||
INVENTREE_API_VERSION = 7
|
INVENTREE_API_VERSION = 8
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Increment thi API version number whenever there is a significant change to the API that any clients need to know about
|
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||||
|
|
||||||
|
v8 -> 2021-07-19
|
||||||
|
- Refactors the API interface for SupplierPart and ManufacturerPart models
|
||||||
|
- ManufacturerPart objects can no longer be created via the SupplierPart API endpoint
|
||||||
|
|
||||||
v7 -> 2021-07-03
|
v7 -> 2021-07-03
|
||||||
- Introduced the concept of "API forms" in https://github.com/inventree/InvenTree/pull/1716
|
- Introduced the concept of "API forms" in https://github.com/inventree/InvenTree/pull/1716
|
||||||
|
@ -233,6 +233,13 @@ class InvenTreeSetting(models.Model):
|
|||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'PART_CREATE_INITIAL': {
|
||||||
|
'name': _('Create initial stock'),
|
||||||
|
'description': _('Create initial stock on part creation'),
|
||||||
|
'default': False,
|
||||||
|
'validator': bool,
|
||||||
|
},
|
||||||
|
|
||||||
'PART_INTERNAL_PRICE': {
|
'PART_INTERNAL_PRICE': {
|
||||||
'name': _('Internal Prices'),
|
'name': _('Internal Prices'),
|
||||||
'description': _('Enable internal prices for parts'),
|
'description': _('Enable internal prices for parts'),
|
||||||
|
@ -6,13 +6,12 @@ Django Forms for interacting with Company app
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from InvenTree.forms import HelperForm
|
from InvenTree.forms import HelperForm
|
||||||
from InvenTree.fields import InvenTreeMoneyField, RoundingDecimalFormField
|
from InvenTree.fields import RoundingDecimalFormField
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
import django.forms
|
import django.forms
|
||||||
|
|
||||||
from .models import Company
|
from .models import Company
|
||||||
from .models import SupplierPart
|
|
||||||
from .models import SupplierPriceBreak
|
from .models import SupplierPriceBreak
|
||||||
|
|
||||||
|
|
||||||
@ -34,67 +33,6 @@ class CompanyImageDownloadForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class EditSupplierPartForm(HelperForm):
|
|
||||||
""" Form for editing a SupplierPart object """
|
|
||||||
|
|
||||||
field_prefix = {
|
|
||||||
'link': 'fa-link',
|
|
||||||
'SKU': 'fa-hashtag',
|
|
||||||
'note': 'fa-pencil-alt',
|
|
||||||
}
|
|
||||||
|
|
||||||
single_pricing = InvenTreeMoneyField(
|
|
||||||
label=_('Single Price'),
|
|
||||||
help_text=_('Single quantity price'),
|
|
||||||
decimal_places=4,
|
|
||||||
max_digits=19,
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
manufacturer = django.forms.ChoiceField(
|
|
||||||
required=False,
|
|
||||||
help_text=_('Select manufacturer'),
|
|
||||||
choices=[],
|
|
||||||
)
|
|
||||||
|
|
||||||
MPN = django.forms.CharField(
|
|
||||||
required=False,
|
|
||||||
help_text=_('Manufacturer Part Number'),
|
|
||||||
max_length=100,
|
|
||||||
label=_('MPN'),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = SupplierPart
|
|
||||||
fields = [
|
|
||||||
'part',
|
|
||||||
'supplier',
|
|
||||||
'SKU',
|
|
||||||
'manufacturer',
|
|
||||||
'MPN',
|
|
||||||
'description',
|
|
||||||
'link',
|
|
||||||
'note',
|
|
||||||
'single_pricing',
|
|
||||||
# 'base_cost',
|
|
||||||
# 'multiple',
|
|
||||||
'packaging',
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_manufacturer_choices(self):
|
|
||||||
""" Returns tuples for all manufacturers """
|
|
||||||
empty_choice = [('', '----------')]
|
|
||||||
|
|
||||||
manufacturers = [(manufacturer.id, manufacturer.name) for manufacturer in Company.objects.filter(is_manufacturer=True)]
|
|
||||||
|
|
||||||
return empty_choice + manufacturers
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
self.fields['manufacturer'].choices = self.get_manufacturer_choices()
|
|
||||||
|
|
||||||
|
|
||||||
class EditPriceBreakForm(HelperForm):
|
class EditPriceBreakForm(HelperForm):
|
||||||
""" Form for creating / editing a supplier price break """
|
""" Form for creating / editing a supplier price break """
|
||||||
|
|
||||||
|
@ -9,9 +9,7 @@ import os
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.utils import IntegrityError
|
|
||||||
from django.db.models import Sum, Q, UniqueConstraint
|
from django.db.models import Sum, Q, UniqueConstraint
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
@ -475,57 +473,6 @@ class SupplierPart(models.Model):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('supplier-part-detail', kwargs={'pk': self.id})
|
return reverse('supplier-part-detail', kwargs={'pk': self.id})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
""" Overriding save method to process the linked ManufacturerPart
|
|
||||||
"""
|
|
||||||
|
|
||||||
if 'manufacturer' in kwargs:
|
|
||||||
manufacturer_id = kwargs.pop('manufacturer')
|
|
||||||
|
|
||||||
try:
|
|
||||||
manufacturer = Company.objects.get(pk=int(manufacturer_id))
|
|
||||||
except (ValueError, Company.DoesNotExist):
|
|
||||||
manufacturer = None
|
|
||||||
else:
|
|
||||||
manufacturer = None
|
|
||||||
if 'MPN' in kwargs:
|
|
||||||
MPN = kwargs.pop('MPN')
|
|
||||||
else:
|
|
||||||
MPN = None
|
|
||||||
|
|
||||||
if manufacturer or MPN:
|
|
||||||
if not self.manufacturer_part:
|
|
||||||
# Create ManufacturerPart
|
|
||||||
manufacturer_part = ManufacturerPart.create(part=self.part,
|
|
||||||
manufacturer=manufacturer,
|
|
||||||
mpn=MPN,
|
|
||||||
description=self.description)
|
|
||||||
self.manufacturer_part = manufacturer_part
|
|
||||||
else:
|
|
||||||
# Update ManufacturerPart (if ID exists)
|
|
||||||
try:
|
|
||||||
manufacturer_part_id = self.manufacturer_part.id
|
|
||||||
except AttributeError:
|
|
||||||
manufacturer_part_id = None
|
|
||||||
|
|
||||||
if manufacturer_part_id:
|
|
||||||
try:
|
|
||||||
(manufacturer_part, created) = ManufacturerPart.objects.update_or_create(part=self.part,
|
|
||||||
manufacturer=manufacturer,
|
|
||||||
MPN=MPN)
|
|
||||||
except IntegrityError:
|
|
||||||
manufacturer_part = None
|
|
||||||
raise ValidationError(f'ManufacturerPart linked to {self.part} from manufacturer {manufacturer.name}'
|
|
||||||
f'with part number {MPN} already exists!')
|
|
||||||
|
|
||||||
if manufacturer_part:
|
|
||||||
self.manufacturer_part = manufacturer_part
|
|
||||||
|
|
||||||
self.clean()
|
|
||||||
self.validate_unique()
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('part', 'supplier', 'SKU')
|
unique_together = ('part', 'supplier', 'SKU')
|
||||||
|
|
||||||
|
@ -96,7 +96,9 @@ class CompanySerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ManufacturerPartSerializer(InvenTreeModelSerializer):
|
class ManufacturerPartSerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for ManufacturerPart object """
|
"""
|
||||||
|
Serializer for ManufacturerPart object
|
||||||
|
"""
|
||||||
|
|
||||||
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
|
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
|
||||||
|
|
||||||
@ -106,8 +108,8 @@ class ManufacturerPartSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
part_detail = kwargs.pop('part_detail', False)
|
part_detail = kwargs.pop('part_detail', True)
|
||||||
manufacturer_detail = kwargs.pop('manufacturer_detail', False)
|
manufacturer_detail = kwargs.pop('manufacturer_detail', True)
|
||||||
prettify = kwargs.pop('pretty', False)
|
prettify = kwargs.pop('pretty', False)
|
||||||
|
|
||||||
super(ManufacturerPartSerializer, self).__init__(*args, **kwargs)
|
super(ManufacturerPartSerializer, self).__init__(*args, **kwargs)
|
||||||
@ -229,25 +231,6 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
|
|||||||
'supplier_detail',
|
'supplier_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
""" Extract manufacturer data and process ManufacturerPart """
|
|
||||||
|
|
||||||
# Create SupplierPart
|
|
||||||
supplier_part = super().create(validated_data)
|
|
||||||
|
|
||||||
# Get ManufacturerPart raw data (unvalidated)
|
|
||||||
manufacturer_id = self.initial_data.get('manufacturer', None)
|
|
||||||
MPN = self.initial_data.get('MPN', None)
|
|
||||||
|
|
||||||
if manufacturer_id and MPN:
|
|
||||||
kwargs = {
|
|
||||||
'manufacturer': manufacturer_id,
|
|
||||||
'MPN': MPN,
|
|
||||||
}
|
|
||||||
supplier_part.save(**kwargs)
|
|
||||||
|
|
||||||
return supplier_part
|
|
||||||
|
|
||||||
|
|
||||||
class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
|
class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for SupplierPriceBreak object """
|
""" Serializer for SupplierPriceBreak object """
|
||||||
|
@ -267,16 +267,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("#stock-export").click(function() {
|
$("#stock-export").click(function() {
|
||||||
launchModalForm("{% url 'stock-export-options' %}", {
|
exportStock({
|
||||||
submit_text: '{% trans "Export" %}',
|
supplier: {{ company.id }}
|
||||||
success: function(response) {
|
|
||||||
var url = "{% url 'stock-export' %}";
|
|
||||||
|
|
||||||
url += "?format=" + response.format;
|
|
||||||
url += "&supplier={{ company.id }}";
|
|
||||||
|
|
||||||
location.href = url;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -284,22 +276,8 @@
|
|||||||
|
|
||||||
$("#manufacturer-part-create").click(function () {
|
$("#manufacturer-part-create").click(function () {
|
||||||
|
|
||||||
constructForm('{% url "api-manufacturer-part-list" %}', {
|
createManufacturerPart({
|
||||||
fields: {
|
manufacturer: {{ company.pk }},
|
||||||
part: {},
|
|
||||||
manufacturer: {
|
|
||||||
value: {{ company.pk }},
|
|
||||||
},
|
|
||||||
MPN: {
|
|
||||||
icon: 'fa-hashtag',
|
|
||||||
},
|
|
||||||
description: {},
|
|
||||||
link: {
|
|
||||||
icon: 'fa-link',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
method: 'POST',
|
|
||||||
title: '{% trans "Add Manufacturer Part" %}',
|
|
||||||
onSuccess: function() {
|
onSuccess: function() {
|
||||||
$("#part-table").bootstrapTable("refresh");
|
$("#part-table").bootstrapTable("refresh");
|
||||||
}
|
}
|
||||||
@ -350,27 +328,15 @@
|
|||||||
|
|
||||||
{% if company.is_supplier %}
|
{% if company.is_supplier %}
|
||||||
|
|
||||||
|
function reloadSupplierPartTable() {
|
||||||
|
$('#supplier-part-table').bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
|
||||||
$("#supplier-part-create").click(function () {
|
$("#supplier-part-create").click(function () {
|
||||||
launchModalForm(
|
|
||||||
"{% url 'supplier-part-create' %}",
|
createSupplierPart({
|
||||||
{
|
supplier: {{ company.pk }},
|
||||||
data: {
|
onSuccess: reloadSupplierPartTable,
|
||||||
supplier: {{ company.id }},
|
|
||||||
},
|
|
||||||
reload: true,
|
|
||||||
secondary: [
|
|
||||||
{
|
|
||||||
field: 'part',
|
|
||||||
label: '{% trans "New Part" %}',
|
|
||||||
title: '{% trans "Create new Part" %}',
|
|
||||||
url: "{% url 'part-create' %}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'supplier',
|
|
||||||
label: "{% trans 'New Supplier' %}",
|
|
||||||
title: "{% trans 'Create new Supplier' %}",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -390,22 +356,27 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
$("#multi-part-delete").click(function() {
|
$("#multi-part-delete").click(function() {
|
||||||
var selections = $("#part-table").bootstrapTable("getSelections");
|
var selections = $("#supplier-part-table").bootstrapTable("getSelections");
|
||||||
|
|
||||||
var parts = [];
|
var requests = [];
|
||||||
|
|
||||||
selections.forEach(function(item) {
|
showQuestionDialog(
|
||||||
parts.push(item.pk);
|
'{% trans "Delete Supplier Parts?" %}',
|
||||||
|
'{% trans "All selected supplier parts will be deleted" %}',
|
||||||
|
{
|
||||||
|
accept: function() {
|
||||||
|
selections.forEach(function(part) {
|
||||||
|
var url = `/api/company/part/${part.pk}/`;
|
||||||
|
|
||||||
|
requests.push(inventreeDelete(url));
|
||||||
});
|
});
|
||||||
|
|
||||||
var url = "{% url 'supplier-part-delete' %}"
|
$.when.apply($, requests).then(function() {
|
||||||
|
$('#supplier-part-table').bootstrapTable('refresh');
|
||||||
launchModalForm(url, {
|
|
||||||
data: {
|
|
||||||
parts: parts,
|
|
||||||
},
|
|
||||||
reload: true,
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#multi-part-order").click(function() {
|
$("#multi-part-order").click(function() {
|
||||||
|
@ -178,21 +178,15 @@ $('#parameter-create').click(function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function reloadSupplierPartTable() {
|
||||||
|
$('#supplier-table').bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
|
||||||
$('#supplier-create').click(function () {
|
$('#supplier-create').click(function () {
|
||||||
launchModalForm(
|
createSupplierPart({
|
||||||
"{% url 'supplier-part-create' %}",
|
manufacturer_part: {{ part.pk }},
|
||||||
{
|
part: {{ part.part.pk }},
|
||||||
reload: true,
|
onSuccess: reloadSupplierPartTable,
|
||||||
data: {
|
|
||||||
manufacturer_part: {{ part.id }}
|
|
||||||
},
|
|
||||||
secondary: [
|
|
||||||
{
|
|
||||||
field: 'supplier',
|
|
||||||
label: '{% trans "New Supplier" %}',
|
|
||||||
title: '{% trans "Create new supplier" %}',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -200,18 +194,25 @@ $("#supplier-part-delete").click(function() {
|
|||||||
|
|
||||||
var selections = $("#supplier-table").bootstrapTable("getSelections");
|
var selections = $("#supplier-table").bootstrapTable("getSelections");
|
||||||
|
|
||||||
var parts = [];
|
var requests = [];
|
||||||
|
|
||||||
selections.forEach(function(item) {
|
showQuestionDialog(
|
||||||
parts.push(item.pk);
|
'{% trans "Delete Supplier Parts?" %}',
|
||||||
|
'{% trans "All selected supplier parts will be deleted" %}',
|
||||||
|
{
|
||||||
|
accept: function() {
|
||||||
|
selections.forEach(function(part) {
|
||||||
|
var url = `/api/company/part/${part.pk}/`;
|
||||||
|
|
||||||
|
requests.push(inventreeDelete(url));
|
||||||
});
|
});
|
||||||
|
|
||||||
launchModalForm("{% url 'supplier-part-delete' %}", {
|
$.when.apply($, requests).then(function() {
|
||||||
data: {
|
reloadSupplierPartTable();
|
||||||
parts: parts,
|
|
||||||
},
|
|
||||||
reload: true,
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#multi-parameter-delete").click(function() {
|
$("#multi-parameter-delete").click(function() {
|
||||||
@ -296,29 +297,19 @@ $('#order-part, #order-part2').click(function() {
|
|||||||
|
|
||||||
$('#edit-part').click(function () {
|
$('#edit-part').click(function () {
|
||||||
|
|
||||||
constructForm('{% url "api-manufacturer-part-detail" part.pk %}', {
|
editManufacturerPart({{ part.pk }}, {
|
||||||
fields: {
|
onSuccess: function() {
|
||||||
part: {},
|
location.reload();
|
||||||
manufacturer: {},
|
}
|
||||||
MPN: {
|
|
||||||
icon: 'fa-hashtag',
|
|
||||||
},
|
|
||||||
description: {},
|
|
||||||
link: {
|
|
||||||
icon: 'fa-link',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: '{% trans "Edit Manufacturer Part" %}',
|
|
||||||
reload: true,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#delete-part').click(function() {
|
$('#delete-part').click(function() {
|
||||||
|
|
||||||
constructForm('{% url "api-manufacturer-part-detail" part.pk %}', {
|
deleteManufacturerPart({{ part.pk }}, {
|
||||||
method: 'DELETE',
|
onSuccess: function() {
|
||||||
title: '{% trans "Delete Manufacturer Part" %}',
|
window.location.href = "{% url 'company-detail' part.manufacturer.id %}";
|
||||||
redirect: "{% url 'company-detail' part.manufacturer.id %}",
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if company.is_supplier or company.is_manufacturer %}
|
{% if company.is_supplier %}
|
||||||
<li class='list-group-item' title='{% trans "Supplied Parts" %}'>
|
<li class='list-group-item' title='{% trans "Supplied Parts" %}'>
|
||||||
<a href='#' id='select-supplier-parts' class='nav-toggle'>
|
<a href='#' id='select-supplier-parts' class='nav-toggle'>
|
||||||
<span class='fas fa-building sidebar-icon'></span>
|
<span class='fas fa-building sidebar-icon'></span>
|
||||||
|
@ -284,18 +284,11 @@ loadStockTable($("#stock-table"), {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("#stock-export").click(function() {
|
$("#stock-export").click(function() {
|
||||||
launchModalForm("{% url 'stock-export-options' %}", {
|
|
||||||
submit_text: '{% trans "Export" %}',
|
|
||||||
success: function(response) {
|
|
||||||
var url = "{% url 'stock-export' %}";
|
|
||||||
|
|
||||||
url += "?format=" + response.format;
|
exportStock({
|
||||||
url += "&cascade=" + response.cascade;
|
supplier_part: {{ part.pk }},
|
||||||
url += "&supplier_part={{ part.id }}";
|
|
||||||
|
|
||||||
location.href = url;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#item-create").click(function() {
|
$("#item-create").click(function() {
|
||||||
@ -327,21 +320,21 @@ $('#order-part, #order-part2').click(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('#edit-part').click(function () {
|
$('#edit-part').click(function () {
|
||||||
launchModalForm(
|
|
||||||
"{% url 'supplier-part-edit' part.id %}",
|
editSupplierPart({{ part.pk }}, {
|
||||||
{
|
onSuccess: function() {
|
||||||
reload: true
|
location.reload();
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#delete-part').click(function() {
|
$('#delete-part').click(function() {
|
||||||
launchModalForm(
|
|
||||||
"{% url 'supplier-part-delete' %}?part={{ part.id }}",
|
deleteSupplierPart({{ part.pk }}, {
|
||||||
{
|
onSuccess: function() {
|
||||||
redirect: "{% url 'company-detail' part.supplier.id %}"
|
window.location.href = "{% url 'company-detail' part.supplier.id %}";
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
attachNavCallbacks({
|
attachNavCallbacks({
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
{% extends "modal_form.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block pre_form_content %}
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
{% if part %}
|
|
||||||
<div class='alert alert-block alert-info'>
|
|
||||||
{% include "hover_image.html" with image=part.image %}
|
|
||||||
{{ part.full_name}}
|
|
||||||
<br>
|
|
||||||
<i>{{ part.description }}</i>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,31 +0,0 @@
|
|||||||
{% extends "modal_delete_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block pre_form_content %}
|
|
||||||
{% trans "Are you sure you want to delete the following Supplier Parts?" %}
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block form_data %}
|
|
||||||
<table class='table table-striped table-condensed'>
|
|
||||||
{% for part in parts %}
|
|
||||||
<tr>
|
|
||||||
<input type='hidden' name='supplier-part-{{ part.id}}' value='supplier-part-{{ part.id }}'/>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
{% include "hover_image.html" with image=part.part.image %}
|
|
||||||
{{ part.part.full_name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% include "hover_image.html" with image=part.supplier.image %}
|
|
||||||
{{ part.supplier.name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ part.SKU }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -218,14 +218,27 @@ class ManufacturerTest(InvenTreeAPITestCase):
|
|||||||
def test_supplier_part_create(self):
|
def test_supplier_part_create(self):
|
||||||
url = reverse('api-supplier-part-list')
|
url = reverse('api-supplier-part-list')
|
||||||
|
|
||||||
# Create supplier part
|
# Create a manufacturer part
|
||||||
|
response = self.post(
|
||||||
|
reverse('api-manufacturer-part-list'),
|
||||||
|
{
|
||||||
|
'part': 1,
|
||||||
|
'manufacturer': 7,
|
||||||
|
'MPN': 'PART_NUMBER',
|
||||||
|
},
|
||||||
|
expected_code=201
|
||||||
|
)
|
||||||
|
|
||||||
|
pk = response.data['pk']
|
||||||
|
|
||||||
|
# Create a supplier part (associated with the new manufacturer part)
|
||||||
data = {
|
data = {
|
||||||
'part': 1,
|
'part': 1,
|
||||||
'supplier': 1,
|
'supplier': 1,
|
||||||
'SKU': 'SKU_TEST',
|
'SKU': 'SKU_TEST',
|
||||||
'manufacturer': 7,
|
'manufacturer_part': pk,
|
||||||
'MPN': 'PART_NUMBER',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(url, data, format='json')
|
response = self.client.post(url, data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
@ -10,9 +10,6 @@ from django.urls import reverse
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
from .models import ManufacturerPart
|
|
||||||
from .models import SupplierPart
|
|
||||||
|
|
||||||
|
|
||||||
class CompanyViewTestBase(TestCase):
|
class CompanyViewTestBase(TestCase):
|
||||||
|
|
||||||
@ -75,108 +72,6 @@ class CompanyViewTestBase(TestCase):
|
|||||||
return json_data, form_errors
|
return json_data, form_errors
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartViewTests(CompanyViewTestBase):
|
|
||||||
"""
|
|
||||||
Tests for the SupplierPart views.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_supplier_part_create(self):
|
|
||||||
"""
|
|
||||||
Test the SupplierPartCreate view.
|
|
||||||
|
|
||||||
This view allows some additional functionality,
|
|
||||||
specifically it allows the user to create a single-quantity price break
|
|
||||||
automatically, when saving the new SupplierPart model.
|
|
||||||
"""
|
|
||||||
|
|
||||||
url = reverse('supplier-part-create')
|
|
||||||
|
|
||||||
# First check that we can GET the form
|
|
||||||
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# How many supplier parts are already in the database?
|
|
||||||
n = SupplierPart.objects.all().count()
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'part': 1,
|
|
||||||
'supplier': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
# SKU is required! (form should fail)
|
|
||||||
(response, errors) = self.post(url, data, valid=False)
|
|
||||||
|
|
||||||
self.assertIsNotNone(errors.get('SKU', None))
|
|
||||||
|
|
||||||
data['SKU'] = 'TEST-ME-123'
|
|
||||||
|
|
||||||
(response, errors) = self.post(url, data, valid=True)
|
|
||||||
|
|
||||||
# Check that the SupplierPart was created!
|
|
||||||
self.assertEqual(n + 1, SupplierPart.objects.all().count())
|
|
||||||
|
|
||||||
# Check that it was created *without* a price-break
|
|
||||||
supplier_part = SupplierPart.objects.get(pk=response['pk'])
|
|
||||||
|
|
||||||
self.assertEqual(supplier_part.price_breaks.count(), 0)
|
|
||||||
|
|
||||||
# Duplicate SKU is prohibited
|
|
||||||
(response, errors) = self.post(url, data, valid=False)
|
|
||||||
|
|
||||||
self.assertIsNotNone(errors.get('__all__', None))
|
|
||||||
|
|
||||||
# Add with a different SKU, *and* a single-quantity price
|
|
||||||
data['SKU'] = 'TEST-ME-1234'
|
|
||||||
data['single_pricing_0'] = '123.4'
|
|
||||||
data['single_pricing_1'] = 'CAD'
|
|
||||||
|
|
||||||
(response, errors) = self.post(url, data, valid=True)
|
|
||||||
|
|
||||||
pk = response.get('pk')
|
|
||||||
|
|
||||||
# Check that *another* SupplierPart was created
|
|
||||||
self.assertEqual(n + 2, SupplierPart.objects.all().count())
|
|
||||||
|
|
||||||
supplier_part = SupplierPart.objects.get(pk=pk)
|
|
||||||
|
|
||||||
# Check that a price-break has been created!
|
|
||||||
self.assertEqual(supplier_part.price_breaks.count(), 1)
|
|
||||||
|
|
||||||
price_break = supplier_part.price_breaks.first()
|
|
||||||
|
|
||||||
self.assertEqual(price_break.quantity, 1)
|
|
||||||
|
|
||||||
def test_supplier_part_delete(self):
|
|
||||||
"""
|
|
||||||
Test the SupplierPartDelete view
|
|
||||||
"""
|
|
||||||
|
|
||||||
url = reverse('supplier-part-delete')
|
|
||||||
|
|
||||||
# Get form using 'part' argument
|
|
||||||
response = self.client.get(url, {'part': '1'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# Get form using 'parts' argument
|
|
||||||
response = self.client.get(url + '?parts[]=1&parts[]=2', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# POST to delete two parts
|
|
||||||
n = SupplierPart.objects.count()
|
|
||||||
response = self.client.post(
|
|
||||||
url,
|
|
||||||
{
|
|
||||||
'supplier-part-2': 'supplier-part-2',
|
|
||||||
'supplier-part-3': 'supplier-part-3',
|
|
||||||
'confirm_delete': True
|
|
||||||
},
|
|
||||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
self.assertEqual(n - 2, SupplierPart.objects.count())
|
|
||||||
|
|
||||||
|
|
||||||
class CompanyViewTest(CompanyViewTestBase):
|
class CompanyViewTest(CompanyViewTestBase):
|
||||||
"""
|
"""
|
||||||
Tests for various 'Company' views
|
Tests for various 'Company' views
|
||||||
@ -187,36 +82,3 @@ class CompanyViewTest(CompanyViewTestBase):
|
|||||||
|
|
||||||
response = self.client.get(reverse('company-index'))
|
response = self.client.get(reverse('company-index'))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
class ManufacturerPartViewTests(CompanyViewTestBase):
|
|
||||||
"""
|
|
||||||
Tests for the ManufacturerPart views.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_supplier_part_create(self):
|
|
||||||
"""
|
|
||||||
Test that the SupplierPartCreate view creates Manufacturer Part.
|
|
||||||
"""
|
|
||||||
|
|
||||||
url = reverse('supplier-part-create')
|
|
||||||
|
|
||||||
# First check that we can GET the form
|
|
||||||
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# How many manufacturer parts are already in the database?
|
|
||||||
n = ManufacturerPart.objects.all().count()
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'part': 1,
|
|
||||||
'supplier': 1,
|
|
||||||
'SKU': 'SKU_TEST',
|
|
||||||
'manufacturer': 6,
|
|
||||||
'MPN': 'MPN_TEST',
|
|
||||||
}
|
|
||||||
|
|
||||||
(response, errors) = self.post(url, data, valid=True)
|
|
||||||
|
|
||||||
# Check that the ManufacturerPart was created!
|
|
||||||
self.assertEqual(n + 1, ManufacturerPart.objects.all().count())
|
|
||||||
|
@ -192,18 +192,14 @@ class ManufacturerPartSimpleTest(TestCase):
|
|||||||
SKU='SKU_TEST',
|
SKU='SKU_TEST',
|
||||||
)
|
)
|
||||||
|
|
||||||
kwargs = {
|
supplier_part.save()
|
||||||
'manufacturer': manufacturer.id,
|
|
||||||
'MPN': 'MPN_TEST',
|
|
||||||
}
|
|
||||||
supplier_part.save(**kwargs)
|
|
||||||
|
|
||||||
def test_exists(self):
|
def test_exists(self):
|
||||||
self.assertEqual(ManufacturerPart.objects.count(), 5)
|
self.assertEqual(ManufacturerPart.objects.count(), 4)
|
||||||
|
|
||||||
# Check that manufacturer part was created from supplier part creation
|
# Check that manufacturer part was created from supplier part creation
|
||||||
manufacturer_parts = ManufacturerPart.objects.filter(manufacturer=1)
|
manufacturer_parts = ManufacturerPart.objects.filter(manufacturer=1)
|
||||||
self.assertEqual(manufacturer_parts.count(), 2)
|
self.assertEqual(manufacturer_parts.count(), 1)
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
# Remove a part
|
# Remove a part
|
||||||
|
@ -35,16 +35,6 @@ manufacturer_part_urls = [
|
|||||||
])),
|
])),
|
||||||
]
|
]
|
||||||
|
|
||||||
supplier_part_detail_urls = [
|
supplier_part_urls = [
|
||||||
url(r'^edit/?', views.SupplierPartEdit.as_view(), name='supplier-part-edit'),
|
|
||||||
|
|
||||||
url('^.*$', views.SupplierPartDetail.as_view(template_name='company/supplier_part.html'), name='supplier-part-detail'),
|
url('^.*$', views.SupplierPartDetail.as_view(template_name='company/supplier_part.html'), name='supplier-part-detail'),
|
||||||
]
|
]
|
||||||
|
|
||||||
supplier_part_urls = [
|
|
||||||
url(r'^new/?', views.SupplierPartCreate.as_view(), name='supplier-part-create'),
|
|
||||||
|
|
||||||
url(r'delete/', views.SupplierPartDelete.as_view(), name='supplier-part-delete'),
|
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/', include(supplier_part_detail_urls)),
|
|
||||||
]
|
|
||||||
|
@ -10,31 +10,22 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.forms import HiddenInput
|
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
|
||||||
from moneyed import CURRENCIES
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import requests
|
import requests
|
||||||
import io
|
import io
|
||||||
|
|
||||||
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
from InvenTree.views import AjaxUpdateView
|
||||||
from InvenTree.helpers import str2bool
|
|
||||||
from InvenTree.views import InvenTreeRoleMixin
|
from InvenTree.views import InvenTreeRoleMixin
|
||||||
|
|
||||||
from .models import Company
|
from .models import Company
|
||||||
from .models import ManufacturerPart
|
from .models import ManufacturerPart
|
||||||
from .models import SupplierPart
|
from .models import SupplierPart
|
||||||
|
|
||||||
from part.models import Part
|
|
||||||
|
|
||||||
from .forms import EditSupplierPartForm
|
|
||||||
from .forms import CompanyImageDownloadForm
|
from .forms import CompanyImageDownloadForm
|
||||||
|
|
||||||
import common.models
|
|
||||||
import common.settings
|
|
||||||
|
|
||||||
|
|
||||||
class CompanyIndex(InvenTreeRoleMixin, ListView):
|
class CompanyIndex(InvenTreeRoleMixin, ListView):
|
||||||
""" View for displaying list of companies
|
""" View for displaying list of companies
|
||||||
@ -231,272 +222,3 @@ class SupplierPartDetail(DetailView):
|
|||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartEdit(AjaxUpdateView):
|
|
||||||
""" Update view for editing SupplierPart """
|
|
||||||
|
|
||||||
model = SupplierPart
|
|
||||||
context_object_name = 'part'
|
|
||||||
form_class = EditSupplierPartForm
|
|
||||||
ajax_template_name = 'modal_form.html'
|
|
||||||
ajax_form_title = _('Edit Supplier Part')
|
|
||||||
|
|
||||||
def save(self, supplier_part, form, **kwargs):
|
|
||||||
""" Process ManufacturerPart data """
|
|
||||||
|
|
||||||
manufacturer = form.cleaned_data.get('manufacturer', None)
|
|
||||||
MPN = form.cleaned_data.get('MPN', None)
|
|
||||||
kwargs = {'manufacturer': manufacturer,
|
|
||||||
'MPN': MPN,
|
|
||||||
}
|
|
||||||
supplier_part.save(**kwargs)
|
|
||||||
|
|
||||||
def get_form(self):
|
|
||||||
form = super().get_form()
|
|
||||||
|
|
||||||
supplier_part = self.get_object()
|
|
||||||
|
|
||||||
# Hide Manufacturer fields
|
|
||||||
form.fields['manufacturer'].widget = HiddenInput()
|
|
||||||
form.fields['MPN'].widget = HiddenInput()
|
|
||||||
|
|
||||||
# It appears that hiding a MoneyField fails validation
|
|
||||||
# Therefore the idea to set the value before hiding
|
|
||||||
if form.is_valid():
|
|
||||||
form.cleaned_data['single_pricing'] = supplier_part.unit_pricing
|
|
||||||
# Hide the single-pricing field (only for creating a new SupplierPart!)
|
|
||||||
form.fields['single_pricing'].widget = HiddenInput()
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
""" Fetch data from ManufacturerPart """
|
|
||||||
|
|
||||||
initials = super(SupplierPartEdit, self).get_initial().copy()
|
|
||||||
|
|
||||||
supplier_part = self.get_object()
|
|
||||||
|
|
||||||
if supplier_part.manufacturer_part:
|
|
||||||
if supplier_part.manufacturer_part.manufacturer:
|
|
||||||
initials['manufacturer'] = supplier_part.manufacturer_part.manufacturer.id
|
|
||||||
initials['MPN'] = supplier_part.manufacturer_part.MPN
|
|
||||||
|
|
||||||
return initials
|
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartCreate(AjaxCreateView):
|
|
||||||
""" Create view for making new SupplierPart """
|
|
||||||
|
|
||||||
model = SupplierPart
|
|
||||||
form_class = EditSupplierPartForm
|
|
||||||
ajax_template_name = 'company/supplier_part_create.html'
|
|
||||||
ajax_form_title = _('Create new Supplier Part')
|
|
||||||
context_object_name = 'part'
|
|
||||||
|
|
||||||
def validate(self, part, form):
|
|
||||||
|
|
||||||
single_pricing = form.cleaned_data.get('single_pricing', None)
|
|
||||||
|
|
||||||
if single_pricing:
|
|
||||||
# TODO - What validation steps can be performed on the single_pricing field?
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_context_data(self):
|
|
||||||
"""
|
|
||||||
Supply context data to the form
|
|
||||||
"""
|
|
||||||
|
|
||||||
ctx = super().get_context_data()
|
|
||||||
|
|
||||||
# Add 'part' object
|
|
||||||
form = self.get_form()
|
|
||||||
|
|
||||||
part = form['part'].value()
|
|
||||||
|
|
||||||
try:
|
|
||||||
part = Part.objects.get(pk=part)
|
|
||||||
except (ValueError, Part.DoesNotExist):
|
|
||||||
part = None
|
|
||||||
|
|
||||||
ctx['part'] = part
|
|
||||||
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
def save(self, form):
|
|
||||||
"""
|
|
||||||
If single_pricing is defined, add a price break for quantity=1
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Save the supplier part object
|
|
||||||
supplier_part = super().save(form)
|
|
||||||
|
|
||||||
# Process manufacturer data
|
|
||||||
manufacturer = form.cleaned_data.get('manufacturer', None)
|
|
||||||
MPN = form.cleaned_data.get('MPN', None)
|
|
||||||
kwargs = {'manufacturer': manufacturer,
|
|
||||||
'MPN': MPN,
|
|
||||||
}
|
|
||||||
supplier_part.save(**kwargs)
|
|
||||||
|
|
||||||
single_pricing = form.cleaned_data.get('single_pricing', None)
|
|
||||||
|
|
||||||
if single_pricing:
|
|
||||||
|
|
||||||
supplier_part.add_price_break(1, single_pricing)
|
|
||||||
|
|
||||||
return supplier_part
|
|
||||||
|
|
||||||
def get_form(self):
|
|
||||||
""" Create Form instance to create a new SupplierPart object.
|
|
||||||
Hide some fields if they are not appropriate in context
|
|
||||||
"""
|
|
||||||
form = super(AjaxCreateView, self).get_form()
|
|
||||||
|
|
||||||
if form.initial.get('part', None):
|
|
||||||
# Hide the part field
|
|
||||||
form.fields['part'].widget = HiddenInput()
|
|
||||||
|
|
||||||
if form.initial.get('manufacturer', None):
|
|
||||||
# Hide the manufacturer field
|
|
||||||
form.fields['manufacturer'].widget = HiddenInput()
|
|
||||||
# Hide the MPN field
|
|
||||||
form.fields['MPN'].widget = HiddenInput()
|
|
||||||
|
|
||||||
return form
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
""" Provide initial data for new SupplierPart:
|
|
||||||
|
|
||||||
- If 'supplier_id' provided, pre-fill supplier field
|
|
||||||
- If 'part_id' provided, pre-fill part field
|
|
||||||
"""
|
|
||||||
initials = super(SupplierPartCreate, self).get_initial().copy()
|
|
||||||
|
|
||||||
manufacturer_id = self.get_param('manufacturer')
|
|
||||||
supplier_id = self.get_param('supplier')
|
|
||||||
part_id = self.get_param('part')
|
|
||||||
manufacturer_part_id = self.get_param('manufacturer_part')
|
|
||||||
|
|
||||||
supplier = None
|
|
||||||
|
|
||||||
if supplier_id:
|
|
||||||
try:
|
|
||||||
supplier = Company.objects.get(pk=supplier_id)
|
|
||||||
initials['supplier'] = supplier
|
|
||||||
except (ValueError, Company.DoesNotExist):
|
|
||||||
supplier = None
|
|
||||||
|
|
||||||
if manufacturer_id:
|
|
||||||
try:
|
|
||||||
initials['manufacturer'] = Company.objects.get(pk=manufacturer_id)
|
|
||||||
except (ValueError, Company.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
if manufacturer_part_id:
|
|
||||||
try:
|
|
||||||
# Get ManufacturerPart instance information
|
|
||||||
manufacturer_part_obj = ManufacturerPart.objects.get(pk=manufacturer_part_id)
|
|
||||||
initials['part'] = Part.objects.get(pk=manufacturer_part_obj.part.id)
|
|
||||||
initials['manufacturer'] = manufacturer_part_obj.manufacturer.id
|
|
||||||
initials['MPN'] = manufacturer_part_obj.MPN
|
|
||||||
except (ValueError, ManufacturerPart.DoesNotExist, Part.DoesNotExist, Company.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
if part_id:
|
|
||||||
try:
|
|
||||||
initials['part'] = Part.objects.get(pk=part_id)
|
|
||||||
except (ValueError, Part.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Initial value for single pricing
|
|
||||||
if supplier:
|
|
||||||
currency_code = supplier.currency_code
|
|
||||||
else:
|
|
||||||
currency_code = common.settings.currency_code_default()
|
|
||||||
|
|
||||||
currency = CURRENCIES.get(currency_code, None)
|
|
||||||
|
|
||||||
if currency_code:
|
|
||||||
initials['single_pricing'] = ('', currency)
|
|
||||||
|
|
||||||
return initials
|
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartDelete(AjaxDeleteView):
|
|
||||||
""" Delete view for removing a SupplierPart.
|
|
||||||
|
|
||||||
SupplierParts can be deleted using a variety of 'selectors'.
|
|
||||||
|
|
||||||
- ?part=<pk> -> Delete a single SupplierPart object
|
|
||||||
- ?parts=[] -> Delete a list of SupplierPart objects
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
success_url = '/supplier/'
|
|
||||||
ajax_template_name = 'company/supplier_part_delete.html'
|
|
||||||
ajax_form_title = _('Delete Supplier Part')
|
|
||||||
|
|
||||||
role_required = 'purchase_order.delete'
|
|
||||||
|
|
||||||
parts = []
|
|
||||||
|
|
||||||
def get_context_data(self):
|
|
||||||
ctx = {}
|
|
||||||
|
|
||||||
ctx['parts'] = self.parts
|
|
||||||
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
def get_parts(self):
|
|
||||||
""" Determine which SupplierPart object(s) the user wishes to delete.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.parts = []
|
|
||||||
|
|
||||||
# User passes a single SupplierPart ID
|
|
||||||
if 'part' in self.request.GET:
|
|
||||||
try:
|
|
||||||
self.parts.append(SupplierPart.objects.get(pk=self.request.GET.get('part')))
|
|
||||||
except (ValueError, SupplierPart.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif 'parts[]' in self.request.GET:
|
|
||||||
|
|
||||||
part_id_list = self.request.GET.getlist('parts[]')
|
|
||||||
|
|
||||||
self.parts = SupplierPart.objects.filter(id__in=part_id_list)
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
self.request = request
|
|
||||||
self.get_parts()
|
|
||||||
|
|
||||||
return self.renderJsonResponse(request, form=self.get_form())
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
""" Handle the POST action for deleting supplier parts.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.request = request
|
|
||||||
self.parts = []
|
|
||||||
|
|
||||||
for item in self.request.POST:
|
|
||||||
if item.startswith('supplier-part-'):
|
|
||||||
pk = item.replace('supplier-part-', '')
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.parts.append(SupplierPart.objects.get(pk=pk))
|
|
||||||
except (ValueError, SupplierPart.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
confirm = str2bool(self.request.POST.get('confirm_delete', False))
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'form_valid': confirm,
|
|
||||||
}
|
|
||||||
|
|
||||||
if confirm:
|
|
||||||
for part in self.parts:
|
|
||||||
part.delete()
|
|
||||||
|
|
||||||
return self.renderJsonResponse(self.request, data=data, form=self.get_form())
|
|
||||||
|
@ -132,7 +132,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
purchase_price_string = serializers.CharField(source='purchase_price', read_only=True)
|
purchase_price_string = serializers.CharField(source='purchase_price', read_only=True)
|
||||||
|
|
||||||
destination = LocationBriefSerializer(source='get_destination', read_only=True)
|
destination_detail = LocationBriefSerializer(source='get_destination', read_only=True)
|
||||||
|
|
||||||
purchase_price_currency = serializers.ChoiceField(
|
purchase_price_currency = serializers.ChoiceField(
|
||||||
choices=currency_code_mappings(),
|
choices=currency_code_mappings(),
|
||||||
@ -156,6 +156,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
'purchase_price_currency',
|
'purchase_price_currency',
|
||||||
'purchase_price_string',
|
'purchase_price_string',
|
||||||
'destination',
|
'destination',
|
||||||
|
'destination_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,6 +170,7 @@ $("#edit-order").click(function() {
|
|||||||
supplier: {
|
supplier: {
|
||||||
},
|
},
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
supplier_reference: {},
|
||||||
description: {},
|
description: {},
|
||||||
target_date: {
|
target_date: {
|
||||||
icon: 'fa-calendar-alt',
|
icon: 'fa-calendar-alt',
|
||||||
|
@ -401,8 +401,15 @@ $("#po-table").inventreeTable({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'destination.pathstring',
|
field: 'destination',
|
||||||
title: '{% trans "Destination" %}',
|
title: '{% trans "Destination" %}',
|
||||||
|
formatter: function(value, row) {
|
||||||
|
if (value) {
|
||||||
|
return renderLink(row.destination_detail.pathstring, `/stock/location/${value}/`);
|
||||||
|
} else {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'notes',
|
field: 'notes',
|
||||||
|
@ -163,6 +163,7 @@ $("#edit-order").click(function() {
|
|||||||
customer: {
|
customer: {
|
||||||
},
|
},
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
customer_reference: {},
|
||||||
description: {},
|
description: {},
|
||||||
target_date: {
|
target_date: {
|
||||||
icon: 'fa-calendar-alt',
|
icon: 'fa-calendar-alt',
|
||||||
|
@ -118,9 +118,17 @@ class CategoryList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
ordering_fields = [
|
ordering_fields = [
|
||||||
'name',
|
'name',
|
||||||
|
'level',
|
||||||
|
'tree_id',
|
||||||
|
'lft',
|
||||||
]
|
]
|
||||||
|
|
||||||
ordering = 'name'
|
# Use hierarchical ordering by default
|
||||||
|
ordering = [
|
||||||
|
'tree_id',
|
||||||
|
'lft',
|
||||||
|
'name'
|
||||||
|
]
|
||||||
|
|
||||||
search_fields = [
|
search_fields = [
|
||||||
'name',
|
'name',
|
||||||
|
@ -217,6 +217,11 @@ class EditPartForm(HelperForm):
|
|||||||
label=_('Include parent categories parameter templates'),
|
label=_('Include parent categories parameter templates'),
|
||||||
widget=forms.HiddenInput())
|
widget=forms.HiddenInput())
|
||||||
|
|
||||||
|
initial_stock = forms.IntegerField(required=False,
|
||||||
|
initial=0,
|
||||||
|
label=_('Initial stock amount'),
|
||||||
|
help_text=_('Create stock for this part'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Part
|
model = Part
|
||||||
fields = [
|
fields = [
|
||||||
@ -238,6 +243,7 @@ class EditPartForm(HelperForm):
|
|||||||
'default_expiry',
|
'default_expiry',
|
||||||
'units',
|
'units',
|
||||||
'minimum_stock',
|
'minimum_stock',
|
||||||
|
'initial_stock',
|
||||||
'component',
|
'component',
|
||||||
'assembly',
|
'assembly',
|
||||||
'is_template',
|
'is_template',
|
||||||
|
@ -32,6 +32,8 @@ class CategorySerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
parts = serializers.IntegerField(source='item_count', read_only=True)
|
parts = serializers.IntegerField(source='item_count', read_only=True)
|
||||||
|
|
||||||
|
level = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PartCategory
|
model = PartCategory
|
||||||
fields = [
|
fields = [
|
||||||
@ -40,10 +42,11 @@ class CategorySerializer(InvenTreeModelSerializer):
|
|||||||
'description',
|
'description',
|
||||||
'default_location',
|
'default_location',
|
||||||
'default_keywords',
|
'default_keywords',
|
||||||
'pathstring',
|
'level',
|
||||||
'url',
|
|
||||||
'parent',
|
'parent',
|
||||||
'parts',
|
'parts',
|
||||||
|
'pathstring',
|
||||||
|
'url',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -370,6 +370,16 @@
|
|||||||
sub_part_detail: true,
|
sub_part_detail: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load the BOM table data in the pricing view
|
||||||
|
loadBomTable($("#bom-pricing-table"), {
|
||||||
|
editable: {{ editing_enabled }},
|
||||||
|
bom_url: "{% url 'api-bom-list' %}",
|
||||||
|
part_url: "{% url 'api-part-list' %}",
|
||||||
|
parent_id: {{ part.id }} ,
|
||||||
|
sub_part_detail: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
linkButtonsToSelection($("#bom-table"),
|
linkButtonsToSelection($("#bom-table"),
|
||||||
[
|
[
|
||||||
"#bom-item-delete",
|
"#bom-item-delete",
|
||||||
@ -634,17 +644,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("#stock-export").click(function() {
|
$("#stock-export").click(function() {
|
||||||
launchModalForm("{% url 'stock-export-options' %}", {
|
|
||||||
submit_text: "{% trans 'Export' %}",
|
|
||||||
success: function(response) {
|
|
||||||
var url = "{% url 'stock-export' %}";
|
|
||||||
|
|
||||||
url += "?format=" + response.format;
|
exportStock({
|
||||||
url += "&cascade=" + response.cascade;
|
part: {{ part.pk }}
|
||||||
url += "&part={{ part.id }}";
|
|
||||||
|
|
||||||
location.href = url;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -801,26 +803,15 @@
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#supplier-create').click(function () {
|
function reloadSupplierPartTable() {
|
||||||
launchModalForm(
|
$('#supplier-part-table').bootstrapTable('refresh');
|
||||||
"{% url 'supplier-part-create' %}",
|
|
||||||
{
|
|
||||||
reload: true,
|
|
||||||
data: {
|
|
||||||
part: {{ part.id }}
|
|
||||||
},
|
|
||||||
secondary: [
|
|
||||||
{
|
|
||||||
field: 'supplier',
|
|
||||||
label: '{% trans "New Supplier" %}',
|
|
||||||
title: '{% trans "Create new supplier" %}',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'manufacturer',
|
|
||||||
label: '{% trans "New Manufacturer" %}',
|
|
||||||
title: '{% trans "Create new manufacturer" %}',
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
|
$('#supplier-create').click(function () {
|
||||||
|
|
||||||
|
createSupplierPart({
|
||||||
|
part: {{ part.pk }},
|
||||||
|
onSuccess: reloadSupplierPartTable,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -828,18 +819,25 @@
|
|||||||
|
|
||||||
var selections = $("#supplier-part-table").bootstrapTable("getSelections");
|
var selections = $("#supplier-part-table").bootstrapTable("getSelections");
|
||||||
|
|
||||||
var parts = [];
|
var requests = [];
|
||||||
|
|
||||||
selections.forEach(function(item) {
|
showQuestionDialog(
|
||||||
parts.push(item.pk);
|
'{% trans "Delete Supplier Parts?" %}',
|
||||||
|
'{% trans "All selected supplier parts will be deleted" %}',
|
||||||
|
{
|
||||||
|
accept: function() {
|
||||||
|
selections.forEach(function(part) {
|
||||||
|
var url = `/api/company/part/${part.pk}/`;
|
||||||
|
|
||||||
|
requests.push(inventreeDelete(url));
|
||||||
});
|
});
|
||||||
|
|
||||||
launchModalForm("{% url 'supplier-part-delete' %}", {
|
$.when.apply($, requests).then(function() {
|
||||||
data: {
|
reloadSupplierPartTable();
|
||||||
parts: parts,
|
|
||||||
},
|
|
||||||
reload: true,
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
loadSupplierPartTable(
|
loadSupplierPartTable(
|
||||||
@ -884,19 +882,8 @@
|
|||||||
|
|
||||||
$('#manufacturer-create').click(function () {
|
$('#manufacturer-create').click(function () {
|
||||||
|
|
||||||
constructForm('{% url "api-manufacturer-part-list" %}', {
|
createManufacturerPart({
|
||||||
fields: {
|
part: {{ part.pk }},
|
||||||
part: {
|
|
||||||
value: {{ part.pk }},
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
manufacturer: {},
|
|
||||||
MPN: {},
|
|
||||||
description: {},
|
|
||||||
link: {},
|
|
||||||
},
|
|
||||||
method: 'POST',
|
|
||||||
title: '{% trans "Add Manufacturer Part" %}',
|
|
||||||
onSuccess: function() {
|
onSuccess: function() {
|
||||||
$("#manufacturer-part-table").bootstrapTable("refresh");
|
$("#manufacturer-part-table").bootstrapTable("refresh");
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@
|
|||||||
<div class='panel-content'>
|
<div class='panel-content'>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
<table class='table table-bom table-condensed' data-toolbar="#button-toolbar" id='bom-table'></table>
|
<table class='table table-bom table-condensed' data-toolbar="#button-toolbar" id='bom-pricing-table'></table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if part.bom_count > 0 %}
|
{% if part.bom_count > 0 %}
|
||||||
|
@ -44,7 +44,7 @@ from common.files import FileManager
|
|||||||
from common.views import FileManagementFormView, FileManagementAjaxView
|
from common.views import FileManagementFormView, FileManagementAjaxView
|
||||||
from common.forms import UploadFileForm, MatchFieldForm
|
from common.forms import UploadFileForm, MatchFieldForm
|
||||||
|
|
||||||
from stock.models import StockLocation
|
from stock.models import StockItem, StockLocation
|
||||||
|
|
||||||
import common.settings as inventree_settings
|
import common.settings as inventree_settings
|
||||||
|
|
||||||
@ -487,6 +487,10 @@ class PartCreate(AjaxCreateView):
|
|||||||
if not inventree_settings.stock_expiry_enabled():
|
if not inventree_settings.stock_expiry_enabled():
|
||||||
form.fields['default_expiry'].widget = HiddenInput()
|
form.fields['default_expiry'].widget = HiddenInput()
|
||||||
|
|
||||||
|
# Hide the "initial stock amount" field if the feature is not enabled
|
||||||
|
if not InvenTreeSetting.get_setting('PART_CREATE_INITIAL'):
|
||||||
|
form.fields['initial_stock'].widget = HiddenInput()
|
||||||
|
|
||||||
# Hide the default_supplier field (there are no matching supplier parts yet!)
|
# Hide the default_supplier field (there are no matching supplier parts yet!)
|
||||||
form.fields['default_supplier'].widget = HiddenInput()
|
form.fields['default_supplier'].widget = HiddenInput()
|
||||||
|
|
||||||
@ -547,6 +551,14 @@ class PartCreate(AjaxCreateView):
|
|||||||
# Save part and pass category template settings
|
# Save part and pass category template settings
|
||||||
part.save(**{'add_category_templates': add_category_templates})
|
part.save(**{'add_category_templates': add_category_templates})
|
||||||
|
|
||||||
|
# Add stock if set
|
||||||
|
init_stock = int(request.POST.get('initial_stock', 0))
|
||||||
|
if init_stock:
|
||||||
|
stock = StockItem(part=part,
|
||||||
|
quantity=init_stock,
|
||||||
|
location=part.default_location)
|
||||||
|
stock.save()
|
||||||
|
|
||||||
data['pk'] = part.pk
|
data['pk'] = part.pk
|
||||||
data['text'] = str(part)
|
data['text'] = str(part)
|
||||||
|
|
||||||
|
@ -357,7 +357,8 @@ class TestReport(ReportTemplateBase):
|
|||||||
'serial': stock_item.serial,
|
'serial': stock_item.serial,
|
||||||
'part': stock_item.part,
|
'part': stock_item.part,
|
||||||
'results': stock_item.testResultMap(include_installed=self.include_installed),
|
'results': stock_item.testResultMap(include_installed=self.include_installed),
|
||||||
'result_list': stock_item.testResultList(include_installed=self.include_installed)
|
'result_list': stock_item.testResultList(include_installed=self.include_installed),
|
||||||
|
'installed_items': stock_item.get_installed_items(cascade=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,6 +56,10 @@ content: "{% trans 'Stock Item Test Report' %}";
|
|||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block pre_page_content %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
|
|
||||||
<div class='container'>
|
<div class='container'>
|
||||||
@ -80,6 +84,7 @@ content: "{% trans 'Stock Item Test Report' %}";
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if resul_list|length > 0 %}
|
||||||
<h3>{% trans "Test Results" %}</h3>
|
<h3>{% trans "Test Results" %}</h3>
|
||||||
|
|
||||||
<table class='table test-table'>
|
<table class='table test-table'>
|
||||||
@ -112,5 +117,37 @@ content: "{% trans 'Stock Item Test Report' %}";
|
|||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if installed_items|length > 0 %}
|
||||||
|
<h3>{% trans "Installed Items" %}</h3>
|
||||||
|
|
||||||
|
<table class='table test-table'>
|
||||||
|
<thead>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for sub_item in installed_items %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img src='{% part_image sub_item.part %}' class='part-img' style='max-width: 24px; max-height: 24px;'>
|
||||||
|
{{ sub_item.part.full_name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if sub_item.serialized %}
|
||||||
|
{% trans "Serial" %}: {{ sub_item.serial }}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Quantity" %}: {% decimal sub_item.quantity %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block post_page_content %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -363,6 +363,15 @@ class StockLocationList(generics.ListCreateAPIView):
|
|||||||
ordering_fields = [
|
ordering_fields = [
|
||||||
'name',
|
'name',
|
||||||
'items',
|
'items',
|
||||||
|
'level',
|
||||||
|
'tree_id',
|
||||||
|
'lft',
|
||||||
|
]
|
||||||
|
|
||||||
|
ordering = [
|
||||||
|
'tree_id',
|
||||||
|
'lft',
|
||||||
|
'name',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ from django.core.exceptions import ValidationError
|
|||||||
|
|
||||||
from mptt.fields import TreeNodeChoiceField
|
from mptt.fields import TreeNodeChoiceField
|
||||||
|
|
||||||
from InvenTree.helpers import GetExportFormats
|
|
||||||
from InvenTree.forms import HelperForm
|
from InvenTree.forms import HelperForm
|
||||||
from InvenTree.fields import RoundingDecimalFormField
|
from InvenTree.fields import RoundingDecimalFormField
|
||||||
from InvenTree.fields import DatePickerFormField
|
from InvenTree.fields import DatePickerFormField
|
||||||
@ -226,33 +225,6 @@ class TestReportFormatForm(HelperForm):
|
|||||||
template = forms.ChoiceField(label=_('Template'), help_text=_('Select test report template'))
|
template = forms.ChoiceField(label=_('Template'), help_text=_('Select test report template'))
|
||||||
|
|
||||||
|
|
||||||
class ExportOptionsForm(HelperForm):
|
|
||||||
""" Form for selecting stock export options """
|
|
||||||
|
|
||||||
file_format = forms.ChoiceField(label=_('File Format'), help_text=_('Select output file format'))
|
|
||||||
|
|
||||||
include_sublocations = forms.BooleanField(required=False, initial=True, label=_('Include sublocations'), help_text=_("Include stock items in sub locations"))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = StockLocation
|
|
||||||
fields = [
|
|
||||||
'file_format',
|
|
||||||
'include_sublocations',
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_format_choices(self):
|
|
||||||
""" File format choices """
|
|
||||||
|
|
||||||
choices = [(x, x.upper()) for x in GetExportFormats()]
|
|
||||||
|
|
||||||
return choices
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
self.fields['file_format'].choices = self.get_format_choices()
|
|
||||||
|
|
||||||
|
|
||||||
class InstallStockForm(HelperForm):
|
class InstallStockForm(HelperForm):
|
||||||
"""
|
"""
|
||||||
Form for manually installing a stock item into another stock item
|
Form for manually installing a stock item into another stock item
|
||||||
|
@ -260,12 +260,15 @@ class LocationSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
items = serializers.IntegerField(source='item_count', read_only=True)
|
items = serializers.IntegerField(source='item_count', read_only=True)
|
||||||
|
|
||||||
|
level = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = StockLocation
|
model = StockLocation
|
||||||
fields = [
|
fields = [
|
||||||
'pk',
|
'pk',
|
||||||
'url',
|
'url',
|
||||||
'name',
|
'name',
|
||||||
|
'level',
|
||||||
'description',
|
'description',
|
||||||
'parent',
|
'parent',
|
||||||
'pathstring',
|
'pathstring',
|
||||||
|
@ -227,20 +227,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
$("#stock-export").click(function() {
|
$("#stock-export").click(function() {
|
||||||
launchModalForm("{% url 'stock-export-options' %}", {
|
|
||||||
submit_text: '{% trans "Export" %}',
|
|
||||||
success: function(response) {
|
|
||||||
var url = "{% url 'stock-export' %}";
|
|
||||||
|
|
||||||
url += "?format=" + response.format;
|
|
||||||
url += "&cascade=" + response.cascade;
|
|
||||||
|
|
||||||
|
exportStock({
|
||||||
{% if location %}
|
{% if location %}
|
||||||
url += "&location={{ location.id }}";
|
location: {{ location.pk }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
location.href = url;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -56,7 +56,6 @@ stock_urls = [
|
|||||||
|
|
||||||
url(r'^track/', include(stock_tracking_urls)),
|
url(r'^track/', include(stock_tracking_urls)),
|
||||||
|
|
||||||
url(r'^export-options/?', views.StockExportOptions.as_view(), name='stock-export-options'),
|
|
||||||
url(r'^export/?', views.StockExport.as_view(), name='stock-export'),
|
url(r'^export/?', views.StockExport.as_view(), name='stock-export'),
|
||||||
|
|
||||||
# Individual stock items
|
# Individual stock items
|
||||||
|
@ -378,38 +378,6 @@ class StockItemDeleteTestData(AjaxUpdateView):
|
|||||||
return self.renderJsonResponse(request, form, data)
|
return self.renderJsonResponse(request, form, data)
|
||||||
|
|
||||||
|
|
||||||
class StockExportOptions(AjaxView):
|
|
||||||
""" Form for selecting StockExport options """
|
|
||||||
|
|
||||||
model = StockLocation
|
|
||||||
ajax_form_title = _('Stock Export Options')
|
|
||||||
form_class = StockForms.ExportOptionsForm
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
|
|
||||||
self.request = request
|
|
||||||
|
|
||||||
fmt = request.POST.get('file_format', 'csv').lower()
|
|
||||||
cascade = str2bool(request.POST.get('include_sublocations', False))
|
|
||||||
|
|
||||||
# Format a URL to redirect to
|
|
||||||
url = reverse('stock-export')
|
|
||||||
|
|
||||||
url += '?format=' + fmt
|
|
||||||
url += '&cascade=' + str(cascade)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'form_valid': True,
|
|
||||||
'format': fmt,
|
|
||||||
'cascade': cascade
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.renderJsonResponse(self.request, self.form_class(), data=data)
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
return self.renderJsonResponse(request, self.form_class())
|
|
||||||
|
|
||||||
|
|
||||||
class StockExport(AjaxView):
|
class StockExport(AjaxView):
|
||||||
""" Export stock data from a particular location.
|
""" Export stock data from a particular location.
|
||||||
Returns a file containing stock information for that location.
|
Returns a file containing stock information for that location.
|
||||||
@ -471,11 +439,10 @@ class StockExport(AjaxView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if location:
|
if location:
|
||||||
# CHeck if locations should be cascading
|
# Check if locations should be cascading
|
||||||
cascade = str2bool(request.GET.get('cascade', True))
|
cascade = str2bool(request.GET.get('cascade', True))
|
||||||
stock_items = location.get_stock_items(cascade)
|
stock_items = location.get_stock_items(cascade)
|
||||||
else:
|
else:
|
||||||
cascade = True
|
|
||||||
stock_items = StockItem.objects.all()
|
stock_items = StockItem.objects.all()
|
||||||
|
|
||||||
if part:
|
if part:
|
||||||
|
@ -23,7 +23,8 @@
|
|||||||
{% include "InvenTree/settings/setting.html" with key="PART_SHOW_PRICE_IN_FORMS" icon="fa-dollar-sign" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_SHOW_PRICE_IN_FORMS" icon="fa-dollar-sign" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_SHOW_RELATED" icon="fa-random" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_SHOW_RELATED" icon="fa-random" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_RECENT_COUNT" icon="fa-clock" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_RECENT_COUNT" icon="fa-clock" %}
|
||||||
<tr><td colspan='5 '></td></tr>
|
{% include "InvenTree/settings/setting.html" with key="PART_CREATE_INITIAL" icon="fa-boxes" %}
|
||||||
|
<tr><td colspan='5'></td></tr>
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_TEMPLATE" icon="fa-clone" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_TEMPLATE" icon="fa-clone" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_ASSEMBLY" icon="fa-tools" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_ASSEMBLY" icon="fa-tools" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_COMPONENT" icon="fa-th"%}
|
{% include "InvenTree/settings/setting.html" with key="PART_COMPONENT" icon="fa-th"%}
|
||||||
|
@ -1,6 +1,142 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
|
||||||
|
function manufacturerPartFields() {
|
||||||
|
|
||||||
|
return {
|
||||||
|
part: {},
|
||||||
|
manufacturer: {},
|
||||||
|
MPN: {
|
||||||
|
icon: 'fa-hashtag',
|
||||||
|
},
|
||||||
|
description: {},
|
||||||
|
link: {
|
||||||
|
icon: 'fa-link',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createManufacturerPart(options={}) {
|
||||||
|
|
||||||
|
var fields = manufacturerPartFields();
|
||||||
|
|
||||||
|
if (options.part) {
|
||||||
|
fields.part.value = options.part;
|
||||||
|
fields.part.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.manufacturer) {
|
||||||
|
fields.manufacturer.value = options.manufacturer;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructForm('{% url "api-manufacturer-part-list" %}', {
|
||||||
|
fields: fields,
|
||||||
|
method: 'POST',
|
||||||
|
title: '{% trans "Add Manufacturer Part" %}',
|
||||||
|
onSuccess: options.onSuccess
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function editManufacturerPart(part, options={}) {
|
||||||
|
|
||||||
|
var url = `/api/company/part/manufacturer/${part}/`;
|
||||||
|
|
||||||
|
constructForm(url, {
|
||||||
|
fields: manufacturerPartFields(),
|
||||||
|
title: '{% trans "Edit Manufacturer Part" %}',
|
||||||
|
onSuccess: options.onSuccess
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteManufacturerPart(part, options={}) {
|
||||||
|
|
||||||
|
constructForm(`/api/company/part/manufacturer/${part}/`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
title: '{% trans "Delete Manufacturer Part" %}',
|
||||||
|
onSuccess: options.onSuccess,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function supplierPartFields() {
|
||||||
|
|
||||||
|
return {
|
||||||
|
part: {},
|
||||||
|
supplier: {},
|
||||||
|
SKU: {
|
||||||
|
icon: 'fa-hashtag',
|
||||||
|
},
|
||||||
|
manufacturer_part: {
|
||||||
|
filters: {
|
||||||
|
part_detail: true,
|
||||||
|
manufacturer_detail: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description: {},
|
||||||
|
link: {
|
||||||
|
icon: 'fa-link',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
icon: 'fa-pencil-alt',
|
||||||
|
},
|
||||||
|
packaging: {
|
||||||
|
icon: 'fa-box',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Launch a form to create a new ManufacturerPart
|
||||||
|
*/
|
||||||
|
function createSupplierPart(options={}) {
|
||||||
|
|
||||||
|
var fields = supplierPartFields();
|
||||||
|
|
||||||
|
if (options.part) {
|
||||||
|
fields.manufacturer_part.filters.part = options.part;
|
||||||
|
fields.part.hidden = true;
|
||||||
|
fields.part.value = options.part;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.supplier) {
|
||||||
|
fields.supplier.value = options.supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.manufacturer_part) {
|
||||||
|
fields.manufacturer_part.value = options.manufacturer_part;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructForm('{% url "api-supplier-part-list" %}', {
|
||||||
|
fields: fields,
|
||||||
|
method: 'POST',
|
||||||
|
title: '{% trans "Add Supplier Part" %}',
|
||||||
|
onSuccess: options.onSuccess,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function editSupplierPart(part, options={}) {
|
||||||
|
|
||||||
|
constructForm(`/api/company/part/${part}/`, {
|
||||||
|
fields: supplierPartFields(),
|
||||||
|
title: '{% trans "Edit Supplier Part" %}',
|
||||||
|
onSuccess: options.onSuccess
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function deleteSupplierPart(part, options={}) {
|
||||||
|
|
||||||
|
constructForm(`/api/company/part/${part}/`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
title: '{% trans "Delete Supplier Part" %}',
|
||||||
|
onSuccess: options.onSuccess,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Returns a default form-set for creating / editing a Company object
|
// Returns a default form-set for creating / editing a Company object
|
||||||
function companyFormFields(options={}) {
|
function companyFormFields(options={}) {
|
||||||
|
|
||||||
@ -323,8 +459,52 @@ function loadManufacturerPartTable(table, url, options) {
|
|||||||
title: '{% trans "Description" %}',
|
title: '{% trans "Description" %}',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
switchable: true,
|
switchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
title: '',
|
||||||
|
sortable: false,
|
||||||
|
switchable: false,
|
||||||
|
formatter: function(value, row) {
|
||||||
|
var pk = row.pk;
|
||||||
|
|
||||||
|
var html = `<div class='btn-group float-right' role='group'>`;
|
||||||
|
|
||||||
|
html += makeIconButton('fa-edit icon-blue', 'button-manufacturer-part-edit', pk, '{% trans "Edit manufacturer part" %}');
|
||||||
|
html += makeIconButton('fa-trash-alt icon-red', 'button-manufacturer-part-delete', pk, '{% trans "Delete manufacturer part" %}');
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
onPostBody: function() {
|
||||||
|
// Callbacks
|
||||||
|
$(table).find('.button-manufacturer-part-edit').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
editManufacturerPart(
|
||||||
|
pk,
|
||||||
|
{
|
||||||
|
onSuccess: function() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(table).find('.button-manufacturer-part-delete').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
deleteManufacturerPart(
|
||||||
|
pk,
|
||||||
|
{
|
||||||
|
onSuccess: function() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,7 +750,51 @@ function loadSupplierPartTable(table, url, options) {
|
|||||||
field: 'packaging',
|
field: 'packaging',
|
||||||
title: '{% trans "Packaging" %}',
|
title: '{% trans "Packaging" %}',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
title: '',
|
||||||
|
sortable: false,
|
||||||
|
switchable: false,
|
||||||
|
formatter: function(value, row) {
|
||||||
|
var pk = row.pk;
|
||||||
|
|
||||||
|
var html = `<div class='btn-group float-right' role='group'>`;
|
||||||
|
|
||||||
|
html += makeIconButton('fa-edit icon-blue', 'button-supplier-part-edit', pk, '{% trans "Edit supplier part" %}');
|
||||||
|
html += makeIconButton('fa-trash-alt icon-red', 'button-supplier-part-delete', pk, '{% trans "Delete supplier part" %}');
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
onPostBody: function() {
|
||||||
|
// Callbacks
|
||||||
|
$(table).find('.button-supplier-part-edit').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
editSupplierPart(
|
||||||
|
pk,
|
||||||
|
{
|
||||||
|
onSuccess: function() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(table).find('.button-supplier-part-delete').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
deleteSupplierPart(
|
||||||
|
pk,
|
||||||
|
{
|
||||||
|
onSuccess: function() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -252,6 +252,11 @@ function constructDeleteForm(fields, options) {
|
|||||||
*/
|
*/
|
||||||
function constructForm(url, options) {
|
function constructForm(url, options) {
|
||||||
|
|
||||||
|
// An "empty" form will be defined locally
|
||||||
|
if (url == null) {
|
||||||
|
constructFormBody({}, options);
|
||||||
|
}
|
||||||
|
|
||||||
// Save the URL
|
// Save the URL
|
||||||
options.url = url;
|
options.url = url;
|
||||||
|
|
||||||
@ -378,6 +383,11 @@ function constructFormBody(fields, options) {
|
|||||||
fields[field].placeholder = field_options.placeholder;
|
fields[field].placeholder = field_options.placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Choices
|
||||||
|
if (field_options.choices) {
|
||||||
|
fields[field].choices = field_options.choices;
|
||||||
|
}
|
||||||
|
|
||||||
// Field prefix
|
// Field prefix
|
||||||
if (field_options.prefix) {
|
if (field_options.prefix) {
|
||||||
fields[field].prefix = field_options.prefix;
|
fields[field].prefix = field_options.prefix;
|
||||||
@ -1113,7 +1123,7 @@ function initializeRelatedField(name, field, options) {
|
|||||||
var pk = field.value;
|
var pk = field.value;
|
||||||
var url = `${field.api_url}/${pk}/`.replace('//', '/');
|
var url = `${field.api_url}/${pk}/`.replace('//', '/');
|
||||||
|
|
||||||
inventreeGet(url, {}, {
|
inventreeGet(url, field.filters || {}, {
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
setRelatedFieldData(name, data, options);
|
setRelatedFieldData(name, data, options);
|
||||||
}
|
}
|
||||||
@ -1211,6 +1221,9 @@ function renderModelData(name, model, data, parameters, options) {
|
|||||||
case 'partparametertemplate':
|
case 'partparametertemplate':
|
||||||
renderer = renderPartParameterTemplate;
|
renderer = renderPartParameterTemplate;
|
||||||
break;
|
break;
|
||||||
|
case 'manufacturerpart':
|
||||||
|
renderer = renderManufacturerPart;
|
||||||
|
break;
|
||||||
case 'supplierpart':
|
case 'supplierpart':
|
||||||
renderer = renderSupplierPart;
|
renderer = renderSupplierPart;
|
||||||
break;
|
break;
|
||||||
|
@ -765,6 +765,9 @@ function attachSecondaryModal(modal, options) {
|
|||||||
function attachSecondaries(modal, secondaries) {
|
function attachSecondaries(modal, secondaries) {
|
||||||
/* Attach a provided list of secondary modals */
|
/* Attach a provided list of secondary modals */
|
||||||
|
|
||||||
|
// 2021-07-18 - Secondary modals will be disabled for now, until they are re-implemented in the "API forms" architecture
|
||||||
|
return;
|
||||||
|
|
||||||
for (var i = 0; i < secondaries.length; i++) {
|
for (var i = 0; i < secondaries.length; i++) {
|
||||||
attachSecondaryModal(modal, secondaries[i]);
|
attachSecondaryModal(modal, secondaries[i]);
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,9 @@ function renderStockItem(name, data, parameters, options) {
|
|||||||
// Renderer for "StockLocation" model
|
// Renderer for "StockLocation" model
|
||||||
function renderStockLocation(name, data, parameters, options) {
|
function renderStockLocation(name, data, parameters, options) {
|
||||||
|
|
||||||
var html = `<span>${data.name}</span>`;
|
var level = '- '.repeat(data.level);
|
||||||
|
|
||||||
|
var html = `<span>${level}${data.pathstring}</span>`;
|
||||||
|
|
||||||
if (data.description) {
|
if (data.description) {
|
||||||
html += ` - <i>${data.description}</i>`;
|
html += ` - <i>${data.description}</i>`;
|
||||||
@ -75,10 +77,6 @@ function renderStockLocation(name, data, parameters, options) {
|
|||||||
|
|
||||||
html += `<span class='float-right'>{% trans "Location ID" %}: ${data.pk}</span>`;
|
html += `<span class='float-right'>{% trans "Location ID" %}: ${data.pk}</span>`;
|
||||||
|
|
||||||
if (data.pathstring) {
|
|
||||||
html += `<p><small>${data.pathstring}</small></p>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +152,9 @@ function renderOwner(name, data, parameters, options) {
|
|||||||
// Renderer for "PartCategory" model
|
// Renderer for "PartCategory" model
|
||||||
function renderPartCategory(name, data, parameters, options) {
|
function renderPartCategory(name, data, parameters, options) {
|
||||||
|
|
||||||
var html = `<span><b>${data.name}</b></span>`;
|
var level = '- '.repeat(data.level);
|
||||||
|
|
||||||
|
var html = `<span>${level}${data.pathstring}</span>`;
|
||||||
|
|
||||||
if (data.description) {
|
if (data.description) {
|
||||||
html += ` - <i>${data.description}</i>`;
|
html += ` - <i>${data.description}</i>`;
|
||||||
@ -162,10 +162,6 @@ function renderPartCategory(name, data, parameters, options) {
|
|||||||
|
|
||||||
html += `<span class='float-right'>{% trans "Category ID" %}: ${data.pk}</span>`;
|
html += `<span class='float-right'>{% trans "Category ID" %}: ${data.pk}</span>`;
|
||||||
|
|
||||||
if (data.pathstring) {
|
|
||||||
html += `<p><small>${data.pathstring}</small></p>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +174,35 @@ function renderPartParameterTemplate(name, data, parameters, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Rendered for "SupplierPart" model
|
// Renderer for "ManufacturerPart" model
|
||||||
|
function renderManufacturerPart(name, data, parameters, options) {
|
||||||
|
|
||||||
|
var manufacturer_image = null;
|
||||||
|
var part_image = null;
|
||||||
|
|
||||||
|
if (data.manufacturer_detail) {
|
||||||
|
manufacturer_image = data.manufacturer_detail.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.part_detail) {
|
||||||
|
part_image = data.part_detail.thumbnail || data.part_detail.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
html += select2Thumbnail(manufacturer_image);
|
||||||
|
html += select2Thumbnail(part_image);
|
||||||
|
|
||||||
|
html += ` <span><b>${data.manufacturer_detail.name}</b> - ${data.MPN}</span>`;
|
||||||
|
html += ` - <i>${data.part_detail.full_name}</i>`;
|
||||||
|
|
||||||
|
html += `<span class='float-right'>{% trans "Manufacturer Part ID" %}: ${data.pk}</span>`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Renderer for "SupplierPart" model
|
||||||
function renderSupplierPart(name, data, parameters, options) {
|
function renderSupplierPart(name, data, parameters, options) {
|
||||||
|
|
||||||
var supplier_image = null;
|
var supplier_image = null;
|
||||||
|
@ -14,6 +14,7 @@ function createSalesOrder(options={}) {
|
|||||||
customer: {
|
customer: {
|
||||||
value: options.customer,
|
value: options.customer,
|
||||||
},
|
},
|
||||||
|
customer_reference: {},
|
||||||
description: {},
|
description: {},
|
||||||
target_date: {
|
target_date: {
|
||||||
icon: 'fa-calendar-alt',
|
icon: 'fa-calendar-alt',
|
||||||
@ -44,6 +45,7 @@ function createPurchaseOrder(options={}) {
|
|||||||
supplier: {
|
supplier: {
|
||||||
value: options.supplier,
|
value: options.supplier,
|
||||||
},
|
},
|
||||||
|
supplier_reference: {},
|
||||||
description: {},
|
description: {},
|
||||||
target_date: {
|
target_date: {
|
||||||
icon: 'fa-calendar-alt',
|
icon: 'fa-calendar-alt',
|
||||||
|
@ -20,6 +20,55 @@ function stockStatusCodes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Export stock table
|
||||||
|
*/
|
||||||
|
function exportStock(params={}) {
|
||||||
|
|
||||||
|
constructFormBody({}, {
|
||||||
|
title: '{% trans "Export Stock" %}',
|
||||||
|
fields: {
|
||||||
|
format: {
|
||||||
|
label: '{% trans "Format" %}',
|
||||||
|
help_text: '{% trans "Select file format" %}',
|
||||||
|
required: true,
|
||||||
|
type: 'choice',
|
||||||
|
value: 'csv',
|
||||||
|
choices: [
|
||||||
|
{ value: 'csv', display_name: 'CSV' },
|
||||||
|
{ value: 'tsv', display_name: 'TSV' },
|
||||||
|
{ value: 'xls', display_name: 'XLS' },
|
||||||
|
{ value: 'xlsx', display_name: 'XLSX' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sublocations: {
|
||||||
|
label: '{% trans "Include Sublocations" %}',
|
||||||
|
help_text: '{% trans "Include stock items in sublocations" %}',
|
||||||
|
type: 'boolean',
|
||||||
|
value: 'true',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSubmit: function(fields, form_options) {
|
||||||
|
|
||||||
|
var format = getFormFieldValue('format', fields['format'], form_options);
|
||||||
|
var cascade = getFormFieldValue('sublocations', fields['sublocations'], form_options);
|
||||||
|
|
||||||
|
// Hide the modal
|
||||||
|
$(form_options.modal).modal('hide');
|
||||||
|
|
||||||
|
var url = `{% url "stock-export" %}?format=${format}&cascade=${cascade}`;
|
||||||
|
|
||||||
|
for (var key in params) {
|
||||||
|
url += `&${key}=${params[key]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(url);
|
||||||
|
location.href = url;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform stock adjustments
|
* Perform stock adjustments
|
||||||
*/
|
*/
|
||||||
@ -1616,27 +1665,6 @@ function createNewStockItem(options) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
options.secondary = [
|
|
||||||
{
|
|
||||||
field: 'part',
|
|
||||||
label: '{% trans "New Part" %}',
|
|
||||||
title: '{% trans "Create New Part" %}',
|
|
||||||
url: "{% url 'part-create' %}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'supplier_part',
|
|
||||||
label: '{% trans "New Supplier Part" %}',
|
|
||||||
title: '{% trans "Create new Supplier Part" %}',
|
|
||||||
url: "{% url 'supplier-part-create' %}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'location',
|
|
||||||
label: '{% trans "New Location" %}',
|
|
||||||
title: '{% trans "Create New Location" %}',
|
|
||||||
url: "{% url 'stock-location-create' %}",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
launchModalForm("{% url 'stock-item-create' %}", options);
|
launchModalForm("{% url 'stock-item-create' %}", options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
|
||||||
|
function reloadtable(table) {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function editButton(url, text='Edit') {
|
function editButton(url, text='Edit') {
|
||||||
return "<button class='btn btn-success edit-button btn-sm' type='button' url='" + url + "'>" + text + "</button>";
|
return "<button class='btn btn-success edit-button btn-sm' type='button' url='" + url + "'>" + text + "</button>";
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user