mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
SupplierPart availability (#3148)
* Adds new fields to the SupplierPart model: - available - availability_updated * Allow availability_updated field to be blank * Revert "Remove stat context variables" This reverts commit0989c308d0
. * Increment API version * Adds availability information to the SupplierPart API serializer - If the 'available' field is updated, the current date is added to the availability_updated field * Add 'available' field to SupplierPart table * More JS refactoring * Add unit testing for specifying availability via the API * Display availability data on the SupplierPart detail page * Add ability to set 'available' quantity from the SupplierPart detail page * Revert "Revert "Remove stat context variables"" This reverts commit3f98037f79
.
This commit is contained in:
parent
a8a543755f
commit
258957c14c
@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 59
|
INVENTREE_API_VERSION = 60
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Increment this 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
|
||||||
|
|
||||||
|
v60 -> 2022-06-08 : https://github.com/inventree/InvenTree/pull/3148
|
||||||
|
- Add availability data fields to the SupplierPart model
|
||||||
|
|
||||||
v59 -> 2022-06-07 : https://github.com/inventree/InvenTree/pull/3154
|
v59 -> 2022-06-07 : https://github.com/inventree/InvenTree/pull/3154
|
||||||
- Adds further improvements to BulkDelete mixin class
|
- Adds further improvements to BulkDelete mixin class
|
||||||
- Fixes multiple bugs in custom OPTIONS metadata implementation
|
- Fixes multiple bugs in custom OPTIONS metadata implementation
|
||||||
|
24
InvenTree/company/migrations/0044_auto_20220607_2204.py
Normal file
24
InvenTree/company/migrations/0044_auto_20220607_2204.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-06-07 22:04
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('company', '0043_manufacturerpartattachment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='supplierpart',
|
||||||
|
name='availability_updated',
|
||||||
|
field=models.DateTimeField(blank=True, help_text='Date of last update of availability data', null=True, verbose_name='Availability Updated'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='supplierpart',
|
||||||
|
name='available',
|
||||||
|
field=models.DecimalField(decimal_places=3, default=0, help_text='Quantity available from supplier', max_digits=10, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Available'),
|
||||||
|
),
|
||||||
|
]
|
@ -1,6 +1,7 @@
|
|||||||
"""Company database model definitions."""
|
"""Company database model definitions."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -528,6 +529,25 @@ class SupplierPart(models.Model):
|
|||||||
# TODO - Reimplement lead-time as a charfield with special validation (pattern matching).
|
# TODO - Reimplement lead-time as a charfield with special validation (pattern matching).
|
||||||
# lead_time = models.DurationField(blank=True, null=True)
|
# lead_time = models.DurationField(blank=True, null=True)
|
||||||
|
|
||||||
|
available = models.DecimalField(
|
||||||
|
max_digits=10, decimal_places=3, default=0,
|
||||||
|
validators=[MinValueValidator(0)],
|
||||||
|
verbose_name=_('Available'),
|
||||||
|
help_text=_('Quantity available from supplier'),
|
||||||
|
)
|
||||||
|
|
||||||
|
availability_updated = models.DateTimeField(
|
||||||
|
null=True, blank=True, verbose_name=_('Availability Updated'),
|
||||||
|
help_text=_('Date of last update of availability data'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_available_quantity(self, quantity):
|
||||||
|
"""Update the available quantity for this SupplierPart"""
|
||||||
|
|
||||||
|
self.available = quantity
|
||||||
|
self.availability_updated = datetime.now()
|
||||||
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def manufacturer_string(self):
|
def manufacturer_string(self):
|
||||||
"""Format a MPN string for this SupplierPart.
|
"""Format a MPN string for this SupplierPart.
|
||||||
|
@ -209,6 +209,10 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize this serializer with extra detail fields as required"""
|
"""Initialize this serializer with extra detail fields as required"""
|
||||||
|
|
||||||
|
# Check if 'available' quantity was supplied
|
||||||
|
self.has_available_quantity = 'available' in kwargs.get('data', {})
|
||||||
|
|
||||||
part_detail = kwargs.pop('part_detail', True)
|
part_detail = kwargs.pop('part_detail', True)
|
||||||
supplier_detail = kwargs.pop('supplier_detail', True)
|
supplier_detail = kwargs.pop('supplier_detail', True)
|
||||||
manufacturer_detail = kwargs.pop('manufacturer_detail', True)
|
manufacturer_detail = kwargs.pop('manufacturer_detail', True)
|
||||||
@ -242,6 +246,8 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
model = SupplierPart
|
model = SupplierPart
|
||||||
fields = [
|
fields = [
|
||||||
|
'available',
|
||||||
|
'availability_updated',
|
||||||
'description',
|
'description',
|
||||||
'link',
|
'link',
|
||||||
'manufacturer',
|
'manufacturer',
|
||||||
@ -260,11 +266,34 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
|
|||||||
'supplier_detail',
|
'supplier_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
read_only_fields = [
|
||||||
|
'availability_updated',
|
||||||
|
]
|
||||||
|
|
||||||
|
def update(self, supplier_part, data):
|
||||||
|
"""Custom update functionality for the serializer"""
|
||||||
|
|
||||||
|
available = data.pop('available', None)
|
||||||
|
|
||||||
|
response = super().update(supplier_part, data)
|
||||||
|
|
||||||
|
if available is not None and self.has_available_quantity:
|
||||||
|
supplier_part.update_available_quantity(available)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
"""Extract manufacturer data and process ManufacturerPart."""
|
"""Extract manufacturer data and process ManufacturerPart."""
|
||||||
|
|
||||||
|
# Extract 'available' quantity from the serializer
|
||||||
|
available = validated_data.pop('available', None)
|
||||||
|
|
||||||
# Create SupplierPart
|
# Create SupplierPart
|
||||||
supplier_part = super().create(validated_data)
|
supplier_part = super().create(validated_data)
|
||||||
|
|
||||||
|
if available is not None and self.has_available_quantity:
|
||||||
|
supplier_part.update_available_quantity(available)
|
||||||
|
|
||||||
# Get ManufacturerPart raw data (unvalidated)
|
# Get ManufacturerPart raw data (unvalidated)
|
||||||
manufacturer = self.initial_data.get('manufacturer', None)
|
manufacturer = self.initial_data.get('manufacturer', None)
|
||||||
MPN = self.initial_data.get('MPN', None)
|
MPN = self.initial_data.get('MPN', None)
|
||||||
|
@ -30,18 +30,32 @@
|
|||||||
{% url 'admin:company_supplierpart_change' part.pk as url %}
|
{% url 'admin:company_supplierpart_change' part.pk as url %}
|
||||||
{% include "admin_button.html" with url=url %}
|
{% include "admin_button.html" with url=url %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if roles.purchase_order.add %}
|
{% if roles.purchase_order.change or roles.purchase_order.add or roles.purchase_order.delete %}
|
||||||
<button type='button' class='btn btn-outline-secondary' id='order-part' title='{% trans "Order part" %}'>
|
<div class='btn-group'>
|
||||||
<span class='fas fa-shopping-cart'></span>
|
<button id='supplier-part-actions' title='{% trans "Supplier part actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'>
|
||||||
</button>
|
<span class='fas fa-tools'></span> <span class='caret'></span>
|
||||||
{% endif %}
|
</button>
|
||||||
<button type='button' class='btn btn-outline-secondary' id='edit-part' title='{% trans "Edit supplier part" %}'>
|
<ul class='dropdown-menu'>
|
||||||
<span class='fas fa-edit icon-green'/>
|
{% if roles.purchase_order.add %}
|
||||||
</button>
|
<li><a class='dropdown-item' href='#' id='order-part' title='{% trans "Order Part" %}'>
|
||||||
{% if roles.purchase_order.delete %}
|
<span class='fas fa-shopping-cart'></span> {% trans "Order Part" %}
|
||||||
<button type='button' class='btn btn-outline-secondary' id='delete-part' title='{% trans "Delete supplier part" %}'>
|
</a></li>
|
||||||
<span class='fas fa-trash-alt icon-red'/>
|
{% endif %}
|
||||||
</button>
|
{% if roles.purchase_order.change %}
|
||||||
|
<li><a class='dropdown-item' href='#' id='update-part-availability' title='{% trans "Update Availability" %}'>
|
||||||
|
<span class='fas fa-building'></span> {% trans "Update Availability" %}
|
||||||
|
</a></li>
|
||||||
|
<li><a class='dropdown-item' href='#' id='edit-part' title='{% trans "Edit Supplier Part" %}'>
|
||||||
|
<span class='fas fa-edit icon-green'></span> {% trans "Edit Supplier Part" %}
|
||||||
|
</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if roles.purchase_order.delete %}
|
||||||
|
<li><a class='dropdown-item' href='#' id='delete-part' title='{% trans "Delete Supplier Part" %}'>
|
||||||
|
<span class='fas fa-trash-alt icon-red'></span> {% trans "Delete Supplier Part" %}
|
||||||
|
</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock actions %}
|
{% endblock actions %}
|
||||||
|
|
||||||
@ -74,6 +88,13 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<td>{{ part.description }}{% include "clip.html"%}</td>
|
<td>{{ part.description }}{% include "clip.html"%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if part.availability_updated %}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>{% trans "Available" %}</td>
|
||||||
|
<td>{% decimal part.available %}<span class='badge bg-dark rounded-pill float-right'>{% render_date part.availability_updated %}</span></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% endblock details %}
|
{% endblock details %}
|
||||||
@ -351,6 +372,20 @@ $('#order-part, #order-part2').click(function() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
{% if roles.purchase_order.change %}
|
||||||
|
|
||||||
|
$('#update-part-availability').click(function() {
|
||||||
|
editSupplierPart({{ part.pk }}, {
|
||||||
|
fields: {
|
||||||
|
available: {},
|
||||||
|
},
|
||||||
|
title: '{% trans "Update Part Availability" %}',
|
||||||
|
onSuccess: function() {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$('#edit-part').click(function () {
|
$('#edit-part').click(function () {
|
||||||
|
|
||||||
editSupplierPart({{ part.pk }}, {
|
editSupplierPart({{ part.pk }}, {
|
||||||
@ -360,6 +395,8 @@ $('#edit-part').click(function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
$('#delete-part').click(function() {
|
$('#delete-part').click(function() {
|
||||||
inventreeGet(
|
inventreeGet(
|
||||||
'{% url "api-supplier-part-detail" part.pk %}',
|
'{% url "api-supplier-part-detail" part.pk %}',
|
||||||
|
@ -6,7 +6,7 @@ from rest_framework import status
|
|||||||
|
|
||||||
from InvenTree.api_tester import InvenTreeAPITestCase
|
from InvenTree.api_tester import InvenTreeAPITestCase
|
||||||
|
|
||||||
from .models import Company
|
from .models import Company, SupplierPart
|
||||||
|
|
||||||
|
|
||||||
class CompanyTest(InvenTreeAPITestCase):
|
class CompanyTest(InvenTreeAPITestCase):
|
||||||
@ -146,6 +146,7 @@ class ManufacturerTest(InvenTreeAPITestCase):
|
|||||||
'location',
|
'location',
|
||||||
'company',
|
'company',
|
||||||
'manufacturer_part',
|
'manufacturer_part',
|
||||||
|
'supplier_part',
|
||||||
]
|
]
|
||||||
|
|
||||||
roles = [
|
roles = [
|
||||||
@ -238,3 +239,111 @@ class ManufacturerTest(InvenTreeAPITestCase):
|
|||||||
url = reverse('api-manufacturer-part-detail', kwargs={'pk': manufacturer_part_id})
|
url = reverse('api-manufacturer-part-detail', kwargs={'pk': manufacturer_part_id})
|
||||||
response = self.get(url)
|
response = self.get(url)
|
||||||
self.assertEqual(response.data['MPN'], 'PART_NUMBER')
|
self.assertEqual(response.data['MPN'], 'PART_NUMBER')
|
||||||
|
|
||||||
|
|
||||||
|
class SupplierPartTest(InvenTreeAPITestCase):
|
||||||
|
"""Unit tests for the SupplierPart API endpoints"""
|
||||||
|
|
||||||
|
fixtures = [
|
||||||
|
'category',
|
||||||
|
'part',
|
||||||
|
'location',
|
||||||
|
'company',
|
||||||
|
'manufacturer_part',
|
||||||
|
'supplier_part',
|
||||||
|
]
|
||||||
|
|
||||||
|
roles = [
|
||||||
|
'part.add',
|
||||||
|
'part.change',
|
||||||
|
'part.add',
|
||||||
|
'purchase_order.change',
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_supplier_part_list(self):
|
||||||
|
"""Test the SupplierPart API list functionality"""
|
||||||
|
url = reverse('api-supplier-part-list')
|
||||||
|
|
||||||
|
# Return *all* SupplierParts
|
||||||
|
response = self.get(url, {}, expected_code=200)
|
||||||
|
|
||||||
|
self.assertEqual(len(response.data), SupplierPart.objects.count())
|
||||||
|
|
||||||
|
# Filter by Supplier reference
|
||||||
|
for supplier in Company.objects.filter(is_supplier=True):
|
||||||
|
response = self.get(url, {'supplier': supplier.pk}, expected_code=200)
|
||||||
|
self.assertEqual(len(response.data), supplier.supplied_parts.count())
|
||||||
|
|
||||||
|
# Filter by Part reference
|
||||||
|
expected = {
|
||||||
|
1: 4,
|
||||||
|
25: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
for pk, n in expected.items():
|
||||||
|
response = self.get(url, {'part': pk}, expected_code=200)
|
||||||
|
self.assertEqual(len(response.data), n)
|
||||||
|
|
||||||
|
def test_available(self):
|
||||||
|
"""Tests for updating the 'available' field"""
|
||||||
|
|
||||||
|
url = reverse('api-supplier-part-list')
|
||||||
|
|
||||||
|
# Should fail when sending an invalid 'available' field
|
||||||
|
response = self.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'part': 1,
|
||||||
|
'supplier': 2,
|
||||||
|
'SKU': 'QQ',
|
||||||
|
'available': 'not a number',
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('A valid number is required', str(response.data))
|
||||||
|
|
||||||
|
# Create a SupplierPart without specifying available quantity
|
||||||
|
response = self.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'part': 1,
|
||||||
|
'supplier': 2,
|
||||||
|
'SKU': 'QQ',
|
||||||
|
},
|
||||||
|
expected_code=201
|
||||||
|
)
|
||||||
|
|
||||||
|
sp = SupplierPart.objects.get(pk=response.data['pk'])
|
||||||
|
|
||||||
|
self.assertIsNone(sp.availability_updated)
|
||||||
|
self.assertEqual(sp.available, 0)
|
||||||
|
|
||||||
|
# Now, *update* the availabile quantity via the API
|
||||||
|
self.patch(
|
||||||
|
reverse('api-supplier-part-detail', kwargs={'pk': sp.pk}),
|
||||||
|
{
|
||||||
|
'available': 1234,
|
||||||
|
},
|
||||||
|
expected_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
sp.refresh_from_db()
|
||||||
|
self.assertIsNotNone(sp.availability_updated)
|
||||||
|
self.assertEqual(sp.available, 1234)
|
||||||
|
|
||||||
|
# We should also be able to create a SupplierPart with initial 'available' quantity
|
||||||
|
response = self.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'part': 1,
|
||||||
|
'supplier': 2,
|
||||||
|
'SKU': 'QQQ',
|
||||||
|
'available': 999,
|
||||||
|
},
|
||||||
|
expected_code=201,
|
||||||
|
)
|
||||||
|
|
||||||
|
sp = SupplierPart.objects.get(pk=response.data['pk'])
|
||||||
|
self.assertEqual(sp.available, 999)
|
||||||
|
self.assertIsNotNone(sp.availability_updated)
|
||||||
|
@ -1010,7 +1010,7 @@ function loadBomTable(table, options={}) {
|
|||||||
can_build = available / row.quantity;
|
can_build = available / row.quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return +can_build.toFixed(2);
|
return formatDecimal(can_build, 2);
|
||||||
},
|
},
|
||||||
sorter: function(valA, valB, rowA, rowB) {
|
sorter: function(valA, valB, rowA, rowB) {
|
||||||
// Function to sort the "can build" quantity
|
// Function to sort the "can build" quantity
|
||||||
|
@ -795,7 +795,7 @@ function sumAllocationsForBomRow(bom_row, allocations) {
|
|||||||
quantity += allocation.quantity;
|
quantity += allocation.quantity;
|
||||||
});
|
});
|
||||||
|
|
||||||
return parseFloat(quantity).toFixed(15);
|
return formatDecimal(quantity, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1490,8 +1490,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
|||||||
|
|
||||||
// Store the required quantity in the row data
|
// Store the required quantity in the row data
|
||||||
// Prevent weird rounding issues
|
// Prevent weird rounding issues
|
||||||
row.required = parseFloat(quantity.toFixed(15));
|
row.required = formatDecimal(quantity, 15);
|
||||||
|
|
||||||
return row.required;
|
return row.required;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2043,7 +2042,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the quantity sent to the form field is correctly formatted
|
// Ensure the quantity sent to the form field is correctly formatted
|
||||||
remaining = parseFloat(remaining.toFixed(15));
|
remaining = formatDecimal(remaining, 15);
|
||||||
|
|
||||||
// We only care about entries which are not yet fully allocated
|
// We only care about entries which are not yet fully allocated
|
||||||
if (remaining > 0) {
|
if (remaining > 0) {
|
||||||
|
@ -189,14 +189,16 @@ function createSupplierPart(options={}) {
|
|||||||
|
|
||||||
function editSupplierPart(part, options={}) {
|
function editSupplierPart(part, options={}) {
|
||||||
|
|
||||||
var fields = supplierPartFields();
|
var fields = options.fields || supplierPartFields();
|
||||||
|
|
||||||
// Hide the "part" field
|
// Hide the "part" field
|
||||||
fields.part.hidden = true;
|
if (fields.part) {
|
||||||
|
fields.part.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
constructForm(`/api/company/part/${part}/`, {
|
constructForm(`/api/company/part/${part}/`, {
|
||||||
fields: fields,
|
fields: fields,
|
||||||
title: '{% trans "Edit Supplier Part" %}',
|
title: options.title || '{% trans "Edit Supplier Part" %}',
|
||||||
onSuccess: options.onSuccess
|
onSuccess: options.onSuccess
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -952,6 +954,21 @@ function loadSupplierPartTable(table, url, options) {
|
|||||||
title: '{% trans "Packaging" %}',
|
title: '{% trans "Packaging" %}',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'available',
|
||||||
|
title: '{% trans "Available" %}',
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(value, row) {
|
||||||
|
if (row.availability_updated) {
|
||||||
|
var html = formatDecimal(value);
|
||||||
|
var date = renderDate(row.availability_updated, {showTime: true});
|
||||||
|
html += `<span class='fas fa-info-circle float-right' title='{% trans "Last Updated" %}: ${date}'></span>`;
|
||||||
|
return html;
|
||||||
|
} else {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'actions',
|
field: 'actions',
|
||||||
title: '',
|
title: '',
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
blankImage,
|
blankImage,
|
||||||
deleteButton,
|
deleteButton,
|
||||||
editButton,
|
editButton,
|
||||||
|
formatDecimal,
|
||||||
imageHoverIcon,
|
imageHoverIcon,
|
||||||
makeIconBadge,
|
makeIconBadge,
|
||||||
makeIconButton,
|
makeIconButton,
|
||||||
@ -34,6 +35,13 @@ function deleteButton(url, text='{% trans "Delete" %}') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Format a decimal (floating point) number, to strip trailing zeros
|
||||||
|
*/
|
||||||
|
function formatDecimal(number, places=5) {
|
||||||
|
return +parseFloat(number).toFixed(places);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function blankImage() {
|
function blankImage() {
|
||||||
return `/static/img/blank_image.png`;
|
return `/static/img/blank_image.png`;
|
||||||
}
|
}
|
||||||
|
@ -1191,7 +1191,7 @@ function noResultBadge() {
|
|||||||
|
|
||||||
function formatDate(row) {
|
function formatDate(row) {
|
||||||
// Function for formatting date field
|
// Function for formatting date field
|
||||||
var html = row.date;
|
var html = renderDate(row.date);
|
||||||
|
|
||||||
if (row.user_detail) {
|
if (row.user_detail) {
|
||||||
html += `<span class='badge badge-right rounded-pill bg-secondary'>${row.user_detail.username}</span>`;
|
html += `<span class='badge badge-right rounded-pill bg-secondary'>${row.user_detail.username}</span>`;
|
||||||
@ -1707,13 +1707,13 @@ function loadStockTable(table, options) {
|
|||||||
val = '# ' + row.serial;
|
val = '# ' + row.serial;
|
||||||
} else if (row.quantity != available) {
|
} else if (row.quantity != available) {
|
||||||
// Some quantity is available, show available *and* quantity
|
// Some quantity is available, show available *and* quantity
|
||||||
var ava = +parseFloat(available).toFixed(5);
|
var ava = formatDecimal(available);
|
||||||
var tot = +parseFloat(row.quantity).toFixed(5);
|
var tot = formatDecimal(row.quantity);
|
||||||
|
|
||||||
val = `${ava} / ${tot}`;
|
val = `${ava} / ${tot}`;
|
||||||
} else {
|
} else {
|
||||||
// Format floating point numbers with this one weird trick
|
// Format floating point numbers with this one weird trick
|
||||||
val = +parseFloat(value).toFixed(5);
|
val = formatDecimal(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var html = renderLink(val, `/stock/item/${row.pk}/`);
|
var html = renderLink(val, `/stock/item/${row.pk}/`);
|
||||||
|
Loading…
Reference in New Issue
Block a user