diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html
index 5f9502d20e..fa3e35235e 100644
--- a/InvenTree/part/templates/part/detail.html
+++ b/InvenTree/part/templates/part/detail.html
@@ -63,7 +63,7 @@
{% if roles.part.add %}
{% endif %}
@@ -468,6 +468,10 @@
// Load the "stocktake" tab
onPanelLoad('stocktake', function() {
loadPartStocktakeTable({{ part.pk }});
+
+ $('#btn-stocktake').click(function() {
+ performStocktake({{ part.pk }});
+ });
});
// Load the "suppliers" tab
diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js
index d8c3f74478..eb8ddbaf73 100644
--- a/InvenTree/templates/js/translated/part.js
+++ b/InvenTree/templates/js/translated/part.js
@@ -37,7 +37,9 @@
loadPartVariantTable,
loadRelatedPartsTable,
loadSimplePartTable,
+ partDetail,
partStockLabel,
+ performStocktake,
toggleStar,
validateBom,
*/
@@ -679,7 +681,133 @@ function makePartIcons(part) {
}
return html;
+}
+
+/*
+ * Render part information for a table view
+ *
+ * part: JSON part object
+ * options:
+ * icons: Display part icons
+ * thumb: Display part thumbnail
+ * link: Display URL
+ */
+function partDetail(part, options={}) {
+
+ var html = '';
+
+ var name = part.full_name;
+
+ if (options.thumb) {
+ html += imageHoverIcon(part.thumbnail || part.image);
+ }
+
+ if (options.link) {
+ var url = `/part/${part.pk}/`;
+ html += renderLink(shortenString(name), url);
+ } else {
+ html += shortenString(name);
+ }
+
+ if (options.icons) {
+ html += makePartIcons(part);
+ }
+
+ return html;
+}
+
+
+/*
+ * Guide user through "stocktake" process
+ */
+function performStocktake(partId, options={}) {
+
+ // Helper function for formatting a StockItem row
+ function buildStockItemRow(item) {
+
+ // Part detail
+ var part = partDetail(item.part_detail, {
+ thumb: true,
+ });
+
+ // Location detail
+ var location = locationDetail(item);
+
+ // Quantity detail
+ var quantity = item.quantity;
+
+ if (item.serial && item.quantity == 1) {
+ quantity = `{% trans "Serial" %}: ${item.serial}`
+ }
+
+ // Last update
+ var updated = item.updated || item.stocktake_date;
+
+ // Actions
+ var actions = '';
+
+ return `
+
+ ${part} |
+ ${location} |
+ ${quantity} |
+ ${renderDate(updated)} |
+ ${actions} |
+
`;
+ }
+
+ // First, load stock information for the part
+ inventreeGet(
+ '{% url "api-stock-list" %}',
+ {
+ part: partId,
+ in_stock: true,
+ location_detail: true,
+ part_detail: true,
+ include_variants: true,
+ },
+ {
+ success: function(response) {
+ var html = '';
+
+ html += `
+
+
+
+ {% trans "Stock Item" %} |
+ {% trans "Location" %} |
+ {% trans "Quantity" %} |
+ {% trans "Updated" %} |
+ |
+
+
+
+ `;
+
+ response.forEach(function(item) {
+ html += buildStockItemRow(item);
+ });
+
+ html += `
`;
+
+ constructForm(`/api/part/stocktake/`, {
+ preFormContent: html,
+ method: 'POST',
+ title: '{% trans "Part Stocktake" %}',
+ confirm: true,
+ fields: {
+ part: {
+ value: partId,
+ hidden: true,
+ },
+ quantity: {},
+ note: {},
+ }
+ });
+ }
+ }
+ );
}
diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js
index 76d6f3ec5f..62272e9487 100644
--- a/InvenTree/templates/js/translated/stock.js
+++ b/InvenTree/templates/js/translated/stock.js
@@ -1736,15 +1736,11 @@ function loadStockTable(table, options) {
switchable: params['part_detail'],
formatter: function(value, row) {
- var url = `/part/${row.part}/`;
- var thumb = row.part_detail.thumbnail;
- var name = row.part_detail.full_name;
-
- var html = imageHoverIcon(thumb) + renderLink(shortenString(name), url);
-
- html += makePartIcons(row.part_detail);
-
- return html;
+ return partDetail(row.part_detail, {
+ thumb: true,
+ link: true,
+ icons: true,
+ });
}
};