mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'inventree:master' into matmair/issue2250
This commit is contained in:
commit
75aeab48b8
@ -19,6 +19,7 @@ def add_default_reference(apps, schema_editor):
|
|||||||
build.save()
|
build.save()
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
|
if count > 0:
|
||||||
print(f"\nUpdated build reference for {count} existing BuildOrder objects")
|
print(f"\nUpdated build reference for {count} existing BuildOrder objects")
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
fields:
|
fields:
|
||||||
name: Zerg Corp
|
name: Zerg Corp
|
||||||
description: We eat the competition
|
description: We eat the competition
|
||||||
|
is_customer: False
|
||||||
|
|
||||||
- model: company.company
|
- model: company.company
|
||||||
pk: 4
|
pk: 4
|
||||||
|
@ -163,6 +163,23 @@ class StockTransfer(StockAdjustView):
|
|||||||
serializer_class = StockSerializers.StockTransferSerializer
|
serializer_class = StockSerializers.StockTransferSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class StockAssign(generics.CreateAPIView):
|
||||||
|
"""
|
||||||
|
API endpoint for assigning stock to a particular customer
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = StockItem.objects.all()
|
||||||
|
serializer_class = StockSerializers.StockAssignmentSerializer
|
||||||
|
|
||||||
|
def get_serializer_context(self):
|
||||||
|
|
||||||
|
ctx = super().get_serializer_context()
|
||||||
|
|
||||||
|
ctx['request'] = self.request
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class StockLocationList(generics.ListCreateAPIView):
|
class StockLocationList(generics.ListCreateAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for list view of StockLocation objects:
|
API endpoint for list view of StockLocation objects:
|
||||||
@ -1174,6 +1191,7 @@ stock_api_urls = [
|
|||||||
url(r'^add/', StockAdd.as_view(), name='api-stock-add'),
|
url(r'^add/', StockAdd.as_view(), name='api-stock-add'),
|
||||||
url(r'^remove/', StockRemove.as_view(), name='api-stock-remove'),
|
url(r'^remove/', StockRemove.as_view(), name='api-stock-remove'),
|
||||||
url(r'^transfer/', StockTransfer.as_view(), name='api-stock-transfer'),
|
url(r'^transfer/', StockTransfer.as_view(), name='api-stock-transfer'),
|
||||||
|
url(r'^assign/', StockAssign.as_view(), name='api-stock-assign'),
|
||||||
|
|
||||||
# StockItemAttachment API endpoints
|
# StockItemAttachment API endpoints
|
||||||
url(r'^attachment/', include([
|
url(r'^attachment/', include([
|
||||||
|
@ -21,20 +21,6 @@ from part.models import Part
|
|||||||
from .models import StockLocation, StockItem, StockItemTracking
|
from .models import StockLocation, StockItem, StockItemTracking
|
||||||
|
|
||||||
|
|
||||||
class AssignStockItemToCustomerForm(HelperForm):
|
|
||||||
"""
|
|
||||||
Form for manually assigning a StockItem to a Customer
|
|
||||||
|
|
||||||
TODO: This could be a simple API driven form!
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = StockItem
|
|
||||||
fields = [
|
|
||||||
'customer',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ReturnStockItemForm(HelperForm):
|
class ReturnStockItemForm(HelperForm):
|
||||||
"""
|
"""
|
||||||
Form for manually returning a StockItem into stock
|
Form for manually returning a StockItem into stock
|
||||||
|
@ -28,6 +28,8 @@ from .models import StockItemTestResult
|
|||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
from common.settings import currency_code_default, currency_code_mappings
|
from common.settings import currency_code_default, currency_code_mappings
|
||||||
|
|
||||||
|
import company.models
|
||||||
from company.serializers import SupplierPartSerializer
|
from company.serializers import SupplierPartSerializer
|
||||||
|
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
@ -537,6 +539,127 @@ class StockTrackingSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class StockAssignmentItemSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
Serializer for a single StockItem with in StockAssignment request.
|
||||||
|
|
||||||
|
Here, the particular StockItem is being assigned (manually) to a customer
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
- item: StockItem object
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = [
|
||||||
|
'item',
|
||||||
|
]
|
||||||
|
|
||||||
|
item = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=StockItem.objects.all(),
|
||||||
|
many=False,
|
||||||
|
allow_null=False,
|
||||||
|
required=True,
|
||||||
|
label=_('Stock Item'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_item(self, item):
|
||||||
|
|
||||||
|
# The item must currently be "in stock"
|
||||||
|
if not item.in_stock:
|
||||||
|
raise ValidationError(_("Item must be in stock"))
|
||||||
|
|
||||||
|
# The base part must be "salable"
|
||||||
|
if not item.part.salable:
|
||||||
|
raise ValidationError(_("Part must be salable"))
|
||||||
|
|
||||||
|
# The item must not be allocated to a sales order
|
||||||
|
if item.sales_order_allocations.count() > 0:
|
||||||
|
raise ValidationError(_("Item is allocated to a sales order"))
|
||||||
|
|
||||||
|
# The item must not be allocated to a build order
|
||||||
|
if item.allocations.count() > 0:
|
||||||
|
raise ValidationError(_("Item is allocated to a build order"))
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
class StockAssignmentSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
Serializer for assigning one (or more) stock items to a customer.
|
||||||
|
|
||||||
|
This is a manual assignment process, separate for (for example) a Sales Order
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = [
|
||||||
|
'items',
|
||||||
|
'customer',
|
||||||
|
'notes',
|
||||||
|
]
|
||||||
|
|
||||||
|
items = StockAssignmentItemSerializer(
|
||||||
|
many=True,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
customer = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=company.models.Company.objects.all(),
|
||||||
|
many=False,
|
||||||
|
allow_null=False,
|
||||||
|
required=True,
|
||||||
|
label=_('Customer'),
|
||||||
|
help_text=_('Customer to assign stock items'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_customer(self, customer):
|
||||||
|
|
||||||
|
if customer and not customer.is_customer:
|
||||||
|
raise ValidationError(_('Selected company is not a customer'))
|
||||||
|
|
||||||
|
return customer
|
||||||
|
|
||||||
|
notes = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
allow_blank=True,
|
||||||
|
label=_('Notes'),
|
||||||
|
help_text=_('Stock assignment notes'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
|
||||||
|
data = super().validate(data)
|
||||||
|
|
||||||
|
items = data.get('items', [])
|
||||||
|
|
||||||
|
if len(items) == 0:
|
||||||
|
raise ValidationError(_("A list of stock items must be provided"))
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
|
||||||
|
request = self.context['request']
|
||||||
|
|
||||||
|
user = getattr(request, 'user', None)
|
||||||
|
|
||||||
|
data = self.validated_data
|
||||||
|
|
||||||
|
items = data['items']
|
||||||
|
customer = data['customer']
|
||||||
|
notes = data.get('notes', '')
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for item in items:
|
||||||
|
|
||||||
|
stock_item = item['item']
|
||||||
|
|
||||||
|
stock_item.allocateToCustomer(
|
||||||
|
customer,
|
||||||
|
user=user,
|
||||||
|
notes=notes,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StockAdjustmentItemSerializer(serializers.Serializer):
|
class StockAdjustmentItemSerializer(serializers.Serializer):
|
||||||
"""
|
"""
|
||||||
Serializer for a single StockItem within a stock adjument request.
|
Serializer for a single StockItem within a stock adjument request.
|
||||||
|
@ -568,11 +568,19 @@ $("#stock-convert").click(function() {
|
|||||||
|
|
||||||
{% if item.in_stock %}
|
{% if item.in_stock %}
|
||||||
$("#stock-assign-to-customer").click(function() {
|
$("#stock-assign-to-customer").click(function() {
|
||||||
launchModalForm("{% url 'stock-item-assign' item.id %}",
|
|
||||||
|
inventreeGet('{% url "api-stock-detail" item.pk %}', {}, {
|
||||||
|
success: function(response) {
|
||||||
|
assignStockToCustomer(
|
||||||
|
[response],
|
||||||
{
|
{
|
||||||
reload: true,
|
success: function() {
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#stock-move").click(function() {
|
$("#stock-move").click(function() {
|
||||||
|
@ -16,8 +16,9 @@ from InvenTree.status_codes import StockStatus
|
|||||||
from InvenTree.api_tester import InvenTreeAPITestCase
|
from InvenTree.api_tester import InvenTreeAPITestCase
|
||||||
|
|
||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeSetting
|
||||||
|
import company.models
|
||||||
from .models import StockItem, StockLocation
|
import part.models
|
||||||
|
from stock.models import StockItem, StockLocation
|
||||||
|
|
||||||
|
|
||||||
class StockAPITestCase(InvenTreeAPITestCase):
|
class StockAPITestCase(InvenTreeAPITestCase):
|
||||||
@ -732,3 +733,112 @@ class StockTestResultTest(StockAPITestCase):
|
|||||||
|
|
||||||
# Check that an attachment has been uploaded
|
# Check that an attachment has been uploaded
|
||||||
self.assertIsNotNone(response.data['attachment'])
|
self.assertIsNotNone(response.data['attachment'])
|
||||||
|
|
||||||
|
|
||||||
|
class StockAssignTest(StockAPITestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for the stock assignment API endpoint,
|
||||||
|
where stock items are manually assigned to a customer
|
||||||
|
"""
|
||||||
|
|
||||||
|
URL = reverse('api-stock-assign')
|
||||||
|
|
||||||
|
def test_invalid(self):
|
||||||
|
|
||||||
|
# Test with empty data
|
||||||
|
response = self.post(
|
||||||
|
self.URL,
|
||||||
|
data={},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('This field is required', str(response.data['items']))
|
||||||
|
self.assertIn('This field is required', str(response.data['customer']))
|
||||||
|
|
||||||
|
# Test with an invalid customer
|
||||||
|
response = self.post(
|
||||||
|
self.URL,
|
||||||
|
data={
|
||||||
|
'customer': 999,
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('object does not exist', str(response.data['customer']))
|
||||||
|
|
||||||
|
# Test with a company which is *not* a customer
|
||||||
|
response = self.post(
|
||||||
|
self.URL,
|
||||||
|
data={
|
||||||
|
'customer': 3,
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('company is not a customer', str(response.data['customer']))
|
||||||
|
|
||||||
|
# Test with an empty items list
|
||||||
|
response = self.post(
|
||||||
|
self.URL,
|
||||||
|
data={
|
||||||
|
'items': [],
|
||||||
|
'customer': 4,
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('A list of stock items must be provided', str(response.data))
|
||||||
|
|
||||||
|
stock_item = StockItem.objects.create(
|
||||||
|
part=part.models.Part.objects.get(pk=1),
|
||||||
|
status=StockStatus.DESTROYED,
|
||||||
|
quantity=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.post(
|
||||||
|
self.URL,
|
||||||
|
data={
|
||||||
|
'items': [
|
||||||
|
{
|
||||||
|
'item': stock_item.pk,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'customer': 4,
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('Item must be in stock', str(response.data['items'][0]))
|
||||||
|
|
||||||
|
def test_valid(self):
|
||||||
|
|
||||||
|
stock_items = []
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
|
||||||
|
stock_item = StockItem.objects.create(
|
||||||
|
part=part.models.Part.objects.get(pk=25),
|
||||||
|
quantity=i + 5,
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_items.append({
|
||||||
|
'item': stock_item.pk
|
||||||
|
})
|
||||||
|
|
||||||
|
customer = company.models.Company.objects.get(pk=4)
|
||||||
|
|
||||||
|
self.assertEqual(customer.assigned_stock.count(), 0)
|
||||||
|
|
||||||
|
response = self.post(
|
||||||
|
self.URL,
|
||||||
|
data={
|
||||||
|
'items': stock_items,
|
||||||
|
'customer': 4,
|
||||||
|
},
|
||||||
|
expected_code=201,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.data['customer'], 4)
|
||||||
|
|
||||||
|
# 5 stock items should now have been assigned to this customer
|
||||||
|
self.assertEqual(customer.assigned_stock.count(), 5)
|
||||||
|
@ -23,7 +23,6 @@ stock_item_detail_urls = [
|
|||||||
url(r'^delete/', views.StockItemDelete.as_view(), name='stock-item-delete'),
|
url(r'^delete/', views.StockItemDelete.as_view(), name='stock-item-delete'),
|
||||||
url(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'),
|
url(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'),
|
||||||
url(r'^delete_test_data/', views.StockItemDeleteTestData.as_view(), name='stock-item-delete-test-data'),
|
url(r'^delete_test_data/', views.StockItemDeleteTestData.as_view(), name='stock-item-delete-test-data'),
|
||||||
url(r'^assign/', views.StockItemAssignToCustomer.as_view(), name='stock-item-assign'),
|
|
||||||
url(r'^return/', views.StockItemReturnToStock.as_view(), name='stock-item-return'),
|
url(r'^return/', views.StockItemReturnToStock.as_view(), name='stock-item-return'),
|
||||||
url(r'^install/', views.StockItemInstall.as_view(), name='stock-item-install'),
|
url(r'^install/', views.StockItemInstall.as_view(), name='stock-item-install'),
|
||||||
|
|
||||||
|
@ -294,39 +294,6 @@ class StockLocationQRCode(QRCodeView):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class StockItemAssignToCustomer(AjaxUpdateView):
|
|
||||||
"""
|
|
||||||
View for manually assigning a StockItem to a Customer
|
|
||||||
"""
|
|
||||||
|
|
||||||
model = StockItem
|
|
||||||
ajax_form_title = _("Assign to Customer")
|
|
||||||
context_object_name = "item"
|
|
||||||
form_class = StockForms.AssignStockItemToCustomerForm
|
|
||||||
|
|
||||||
def validate(self, item, form, **kwargs):
|
|
||||||
|
|
||||||
customer = form.cleaned_data.get('customer', None)
|
|
||||||
|
|
||||||
if not customer:
|
|
||||||
form.add_error('customer', _('Customer must be specified'))
|
|
||||||
|
|
||||||
def save(self, item, form, **kwargs):
|
|
||||||
"""
|
|
||||||
Assign the stock item to the customer.
|
|
||||||
"""
|
|
||||||
|
|
||||||
customer = form.cleaned_data.get('customer', None)
|
|
||||||
|
|
||||||
if customer:
|
|
||||||
item = item.allocateToCustomer(
|
|
||||||
customer,
|
|
||||||
user=self.request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
item.clearAllocations()
|
|
||||||
|
|
||||||
|
|
||||||
class StockItemReturnToStock(AjaxUpdateView):
|
class StockItemReturnToStock(AjaxUpdateView):
|
||||||
"""
|
"""
|
||||||
View for returning a stock item (which is assigned to a customer) to stock.
|
View for returning a stock item (which is assigned to a customer) to stock.
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* exported
|
/* exported
|
||||||
|
assignStockToCustomer,
|
||||||
createNewStockItem,
|
createNewStockItem,
|
||||||
createStockLocation,
|
createStockLocation,
|
||||||
duplicateStockItem,
|
duplicateStockItem,
|
||||||
@ -533,13 +534,166 @@ function exportStock(params={}) {
|
|||||||
url += `&${key}=${params[key]}`;
|
url += `&${key}=${params[key]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(url);
|
|
||||||
location.href = url;
|
location.href = url;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign multiple stock items to a customer
|
||||||
|
*/
|
||||||
|
function assignStockToCustomer(items, options={}) {
|
||||||
|
|
||||||
|
// Generate HTML content for the form
|
||||||
|
var html = `
|
||||||
|
<table class='table table-striped table-condensed' id='stock-assign-table'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Part" %}</th>
|
||||||
|
<th>{% trans "Stock Item" %}</th>
|
||||||
|
<th>{% trans "Location" %}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
for (var idx = 0; idx < items.length; idx++) {
|
||||||
|
|
||||||
|
var item = items[idx];
|
||||||
|
|
||||||
|
var pk = item.pk;
|
||||||
|
|
||||||
|
var part = item.part_detail;
|
||||||
|
|
||||||
|
var thumbnail = thumbnailImage(part.thumbnail || part.image);
|
||||||
|
|
||||||
|
var status = stockStatusDisplay(item.status, {classes: 'float-right'});
|
||||||
|
|
||||||
|
var quantity = '';
|
||||||
|
|
||||||
|
if (item.serial && item.quantity == 1) {
|
||||||
|
quantity = `{% trans "Serial" %}: ${item.serial}`;
|
||||||
|
} else {
|
||||||
|
quantity = `{% trans "Quantity" %}: ${item.quantity}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
quantity += status;
|
||||||
|
|
||||||
|
var location = locationDetail(item, false);
|
||||||
|
|
||||||
|
var buttons = `<div class='btn-group' role='group'>`;
|
||||||
|
|
||||||
|
buttons += makeIconButton(
|
||||||
|
'fa-times icon-red',
|
||||||
|
'button-stock-item-remove',
|
||||||
|
pk,
|
||||||
|
'{% trans "Remove row" %}',
|
||||||
|
);
|
||||||
|
|
||||||
|
buttons += '</div>';
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<tr id='stock_item_${pk}' class='stock-item'row'>
|
||||||
|
<td id='part_${pk}'>${thumbnail} ${part.full_name}</td>
|
||||||
|
<td id='stock_${pk}'>
|
||||||
|
<div id='div_id_items_item_${pk}'>
|
||||||
|
${quantity}
|
||||||
|
<div id='errors-items_item_${pk}'></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td id='location_${pk}'>${location}</td>
|
||||||
|
<td id='buttons_${pk}'>${buttons}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</tbody></table>`;
|
||||||
|
|
||||||
|
constructForm('{% url "api-stock-assign" %}', {
|
||||||
|
method: 'POST',
|
||||||
|
preFormContent: html,
|
||||||
|
fields: {
|
||||||
|
'customer': {
|
||||||
|
value: options.customer,
|
||||||
|
filters: {
|
||||||
|
is_customer: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'notes': {},
|
||||||
|
},
|
||||||
|
confirm: true,
|
||||||
|
confirmMessage: '{% trans "Confirm stock assignment" %}',
|
||||||
|
title: '{% trans "Assign Stock to Customer" %}',
|
||||||
|
afterRender: function(fields, opts) {
|
||||||
|
// Add button callbacks to remove rows
|
||||||
|
$(opts.modal).find('.button-stock-item-remove').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
$(opts.modal).find(`#stock_item_${pk}`).remove();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSubmit: function(fields, opts) {
|
||||||
|
|
||||||
|
// Extract data elements from the form
|
||||||
|
var data = {
|
||||||
|
customer: getFormFieldValue('customer', {}, opts),
|
||||||
|
notes: getFormFieldValue('notes', {}, opts),
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
var item_pk_values = [];
|
||||||
|
|
||||||
|
items.forEach(function(item) {
|
||||||
|
var pk = item.pk;
|
||||||
|
|
||||||
|
// Does the row exist in the form?
|
||||||
|
var row = $(opts.modal).find(`#stock_item_${pk}`);
|
||||||
|
|
||||||
|
if (row.exists()) {
|
||||||
|
item_pk_values.push(pk);
|
||||||
|
|
||||||
|
data.items.push({
|
||||||
|
item: pk,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
opts.nested = {
|
||||||
|
'items': item_pk_values,
|
||||||
|
};
|
||||||
|
|
||||||
|
inventreePut(
|
||||||
|
'{% url "api-stock-assign" %}',
|
||||||
|
data,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
success: function(response) {
|
||||||
|
$(opts.modal).modal('hide');
|
||||||
|
|
||||||
|
if (options.success) {
|
||||||
|
options.success(response);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
switch (xhr.status) {
|
||||||
|
case 400:
|
||||||
|
handleFormErrors(xhr.responseJSON, fields, opts);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$(opts.modal).modal('hide');
|
||||||
|
showApiError(xhr, opts.url);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform stock adjustments
|
* Perform stock adjustments
|
||||||
*/
|
*/
|
||||||
@ -777,7 +931,7 @@ function adjustStock(action, items, options={}) {
|
|||||||
// Does the row exist in the form?
|
// Does the row exist in the form?
|
||||||
var row = $(opts.modal).find(`#stock_item_${pk}`);
|
var row = $(opts.modal).find(`#stock_item_${pk}`);
|
||||||
|
|
||||||
if (row) {
|
if (row.exists()) {
|
||||||
|
|
||||||
item_pk_values.push(pk);
|
item_pk_values.push(pk);
|
||||||
|
|
||||||
@ -1098,7 +1252,7 @@ function locationDetail(row, showLink=true) {
|
|||||||
// StockItem has been assigned to a sales order
|
// StockItem has been assigned to a sales order
|
||||||
text = '{% trans "Assigned to Sales Order" %}';
|
text = '{% trans "Assigned to Sales Order" %}';
|
||||||
url = `/order/sales-order/${row.sales_order}/`;
|
url = `/order/sales-order/${row.sales_order}/`;
|
||||||
} else if (row.location) {
|
} else if (row.location && row.location_detail) {
|
||||||
text = row.location_detail.pathstring;
|
text = row.location_detail.pathstring;
|
||||||
url = `/stock/location/${row.location}/`;
|
url = `/stock/location/${row.location}/`;
|
||||||
} else {
|
} else {
|
||||||
@ -1721,6 +1875,17 @@ function loadStockTable(table, options) {
|
|||||||
stockAdjustment('move');
|
stockAdjustment('move');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#multi-item-assign').click(function() {
|
||||||
|
|
||||||
|
var items = $(table).bootstrapTable('getSelections');
|
||||||
|
|
||||||
|
assignStockToCustomer(items, {
|
||||||
|
success: function() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$('#multi-item-order').click(function() {
|
$('#multi-item-order').click(function() {
|
||||||
var selections = $(table).bootstrapTable('getSelections');
|
var selections = $(table).bootstrapTable('getSelections');
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
<li><a class='dropdown-item' href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'><span class='fas fa-check-circle'></span> {% trans "Count stock" %}</a></li>
|
<li><a class='dropdown-item' href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'><span class='fas fa-check-circle'></span> {% trans "Count stock" %}</a></li>
|
||||||
<li><a class='dropdown-item' href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'><span class='fas fa-exchange-alt'></span> {% trans "Move stock" %}</a></li>
|
<li><a class='dropdown-item' href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'><span class='fas fa-exchange-alt'></span> {% trans "Move stock" %}</a></li>
|
||||||
<li><a class='dropdown-item' href='#' id='multi-item-order' title='{% trans "Order selected items" %}'><span class='fas fa-shopping-cart'></span> {% trans "Order stock" %}</a></li>
|
<li><a class='dropdown-item' href='#' id='multi-item-order' title='{% trans "Order selected items" %}'><span class='fas fa-shopping-cart'></span> {% trans "Order stock" %}</a></li>
|
||||||
|
<li><a class='dropdown-item' href='#' id='multi-item-assign' title='{% trans "Assign to customer" %}'><span class='fas fa-user-tie'></span> {% trans "Assign to customer" %}</a></li>
|
||||||
<li><a class='dropdown-item' href='#' id='multi-item-set-status' title='{% trans "Change status" %}'><span class='fas fa-exclamation-circle'></span> {% trans "Change stock status" %}</a></li>
|
<li><a class='dropdown-item' href='#' id='multi-item-set-status' title='{% trans "Change status" %}'><span class='fas fa-exclamation-circle'></span> {% trans "Change stock status" %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if roles.stock.delete %}
|
{% if roles.stock.delete %}
|
||||||
|
Loading…
Reference in New Issue
Block a user