mirror of
https://github.com/grocy/grocy.git
synced 2026-04-05 12:26:15 +02:00
This *absolute commit monster* does the following things: - Introduce gulp to build javascript and css files. This includes moving node_modules out of the public/ folder. Use `gulp --tasks` to get a list of all tasks; however some of them are automatically generated. Use `gulp live` to watch for changes and automatically recompile what's needed. - Upgrade to yarn2 - Upgrade FullCalendar to 4.4.2 I know that 5.x is the current version, but two major version upgrades are too much right now. Also v5 would break any custom css as they renamed a bunch of classes. - Move Styles to sass (Most) global styles are now included in one sass file. This also means that we now compile our own bootstrap. - Javascript is now in strict mode Because everything is a module now, `use strict` is now in effect for all javascript files. There are probably still some parts left where implicit variable declarations were used. - grocy*.js were split up in modules. `window.Grocy` is now an instance of GrocyClass. API-wise nothing has changed (albeit some functions were added regarding Undo actions) At the Moment, this leaks a whole bunch of functions into window (that was easier than tracking those down). - FindObjectIn... style functions were removed. Array.prototype.find and Array.prototype.filter suffice. - Use babel to preprocess javascript. - Use rollup to bundle javascript. rollup bundles and tree-shakes es6 javascript bundles. It also allows to "import" css files and generate css files specific to this javascript file. This is used in viewjs scripts, for example when importing FullCalendar, to generate an associated viewcss file. - Use postcss to post-process css files. postcss uses autoprefixer to handle browser compatiblity. Internally this uses the package `browserslist`; and is currently configured to the default setting. - Minify assets when building in production `gulp publish` builds all assets in production mode, that is, the assets get minified. This includes javascript as well as css files. - css bundling concatCss is used to pull @imports of non-sass-files into one grocy.css - animate.css is now in the main bundle animate.css was used in so many places that it is now located in the main style bundle.
395 lines
13 KiB
JavaScript
Executable File
395 lines
13 KiB
JavaScript
Executable File
var stockOverviewTable = $('#stock-overview-table').DataTable({
|
|
'order': [[5, 'asc']],
|
|
'columnDefs': [
|
|
{ 'orderable': false, 'targets': 0 },
|
|
{ 'searchable': false, "targets": 0 },
|
|
{ 'searchable': false, "targets": 0 },
|
|
{ 'visible': false, 'targets': 6 },
|
|
{ 'visible': false, 'targets': 7 },
|
|
{ 'visible': false, 'targets': 8 },
|
|
{ 'visible': false, 'targets': 2 },
|
|
{ 'visible': false, 'targets': 4 },
|
|
{ 'visible': false, 'targets': 9 },
|
|
{ 'visible': false, 'targets': 10 },
|
|
{ 'visible': false, 'targets': 11 },
|
|
{ 'visible': false, 'targets': 12 },
|
|
{ 'visible': false, 'targets': 13 },
|
|
{ "type": "num", "targets": 3 },
|
|
{ "type": "html-num-fmt", "targets": 9 },
|
|
{ "type": "html-num-fmt", "targets": 10 },
|
|
{ "type": "html", "targets": 5 },
|
|
{ "type": "html", "targets": 11 },
|
|
{ "type": "html-num-fmt", "targets": 12 },
|
|
{ "type": "num", "targets": 13 }
|
|
].concat($.fn.dataTable.defaults.columnDefs)
|
|
});
|
|
|
|
$('#stock-overview-table tbody').removeClass("d-none");
|
|
stockOverviewTable.columns.adjust().draw();
|
|
|
|
$("#location-filter").on("change", function()
|
|
{
|
|
var value = $(this).val();
|
|
if (value === "all")
|
|
{
|
|
value = "";
|
|
}
|
|
else
|
|
{
|
|
value = "xx" + value + "xx";
|
|
}
|
|
|
|
stockOverviewTable.column(6).search(value).draw();
|
|
});
|
|
|
|
$("#product-group-filter").on("change", function()
|
|
{
|
|
var value = $(this).val();
|
|
if (value === "all")
|
|
{
|
|
value = "";
|
|
}
|
|
else
|
|
{
|
|
value = "xx" + value + "xx";
|
|
}
|
|
|
|
stockOverviewTable.column(8).search(value).draw();
|
|
});
|
|
|
|
$("#status-filter").on("change", function()
|
|
{
|
|
var value = $(this).val();
|
|
if (value === "all")
|
|
{
|
|
value = "";
|
|
}
|
|
|
|
// Transfer CSS classes of selected element to dropdown element (for background)
|
|
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
|
|
|
|
stockOverviewTable.column(7).search(value).draw();
|
|
});
|
|
|
|
$(".status-filter-message").on("click", function()
|
|
{
|
|
var value = $(this).data("status-filter");
|
|
$("#status-filter").val(value);
|
|
$("#status-filter").trigger("change");
|
|
});
|
|
|
|
$("#clear-filter-button").on("click", function()
|
|
{
|
|
$("#search").val("");
|
|
$("#status-filter").val("all");
|
|
$("#product-group-filter").val("all");
|
|
$("#location-filter").val("all");
|
|
stockOverviewTable.column(6).search("").draw();
|
|
stockOverviewTable.column(7).search("").draw();
|
|
stockOverviewTable.column(8).search("").draw();
|
|
stockOverviewTable.search("").draw();
|
|
});
|
|
|
|
$("#search").on("keyup", Delay(function()
|
|
{
|
|
var value = $(this).val();
|
|
if (value === "all")
|
|
{
|
|
value = "";
|
|
}
|
|
|
|
stockOverviewTable.search(value).draw();
|
|
}, 200));
|
|
|
|
$(document).on('click', '.stockentry-grocycode-product-label-print', function(e)
|
|
{
|
|
e.preventDefault();
|
|
document.activeElement.blur();
|
|
|
|
var productId = $(e.currentTarget).attr('data-product-id');
|
|
Grocy.Api.Get('stock/products/' + productId + '/printlabel', function(labelData)
|
|
{
|
|
if (Grocy.Webhooks.labelprinter !== undefined)
|
|
{
|
|
Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, labelData);
|
|
}
|
|
});
|
|
});
|
|
|
|
$(document).on('click', '.product-consume-button', function(e)
|
|
{
|
|
e.preventDefault();
|
|
|
|
// Remove the focus from the current button
|
|
// to prevent that the tooltip stays until clicked anywhere else
|
|
document.activeElement.blur();
|
|
|
|
Grocy.FrontendHelpers.BeginUiBusy();
|
|
|
|
var productId = $(e.currentTarget).attr('data-product-id');
|
|
var consumeAmount = $(e.currentTarget).attr('data-consume-amount');
|
|
var originalTotalStockAmount = $(e.currentTarget).attr('data-original-total-stock-amount');
|
|
var wasSpoiled = $(e.currentTarget).hasClass("product-consume-button-spoiled");
|
|
|
|
Grocy.Api.Post('stock/products/' + productId + '/consume', { 'amount': consumeAmount, 'spoiled': wasSpoiled, 'allow_subproduct_substitution': true },
|
|
function(bookingResponse)
|
|
{
|
|
Grocy.Api.Get('stock/products/' + productId,
|
|
function(result)
|
|
{
|
|
if (result.product.enable_tare_weight_handling == 1)
|
|
{
|
|
var toastMessage = __t('Removed %1$s of %2$s from stock', parseFloat(originalTotalStockAmount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural), result.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="Grocy.UndoStockTransaction(\'' + bookingResponse[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
|
|
}
|
|
else
|
|
{
|
|
var toastMessage = __t('Removed %1$s of %2$s from stock', parseFloat(consumeAmount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural), result.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="Grocy.UndoStockTransaction(\'' + bookingResponse[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
|
|
}
|
|
|
|
if (wasSpoiled)
|
|
{
|
|
toastMessage += " (" + __t("Spoiled") + ")";
|
|
}
|
|
|
|
Grocy.FrontendHelpers.EndUiBusy();
|
|
toastr.success(toastMessage);
|
|
RefreshStatistics();
|
|
RefreshProductRow(productId);
|
|
},
|
|
function(xhr)
|
|
{
|
|
Grocy.FrontendHelpers.EndUiBusy();
|
|
console.error(xhr);
|
|
}
|
|
);
|
|
},
|
|
function(xhr)
|
|
{
|
|
Grocy.FrontendHelpers.EndUiBusy();
|
|
console.error(xhr);
|
|
}
|
|
);
|
|
});
|
|
|
|
$(document).on('click', '.product-open-button', function(e)
|
|
{
|
|
e.preventDefault();
|
|
|
|
// Remove the focus from the current button
|
|
// to prevent that the tooltip stays until clicked anywhere else
|
|
document.activeElement.blur();
|
|
|
|
Grocy.FrontendHelpers.BeginUiBusy();
|
|
|
|
var productId = $(e.currentTarget).attr('data-product-id');
|
|
var productName = $(e.currentTarget).attr('data-product-name');
|
|
var productQuName = $(e.currentTarget).attr('data-product-qu-name');
|
|
var amount = $(e.currentTarget).attr('data-open-amount');
|
|
var button = $(e.currentTarget);
|
|
|
|
Grocy.Api.Post('stock/products/' + productId + '/open', { 'amount': amount, 'allow_subproduct_substitution': true },
|
|
function(bookingResponse)
|
|
{
|
|
Grocy.Api.Get('stock/products/' + productId,
|
|
function(result)
|
|
{
|
|
if (result.stock_amount == result.stock_amount_opened)
|
|
{
|
|
button.addClass("disabled");
|
|
}
|
|
|
|
Grocy.FrontendHelpers.EndUiBusy();
|
|
toastr.success(__t('Marked %1$s of %2$s as opened', parseFloat(amount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + productQuName, productName) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="Grocy.UndoStockTransaction(\'' + bookingResponse[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>');
|
|
RefreshStatistics();
|
|
RefreshProductRow(productId);
|
|
},
|
|
function(xhr)
|
|
{
|
|
Grocy.FrontendHelpers.EndUiBusy();
|
|
console.error(xhr);
|
|
}
|
|
);
|
|
},
|
|
function(xhr)
|
|
{
|
|
Grocy.FrontendHelpers.EndUiBusy();
|
|
console.error(xhr);
|
|
}
|
|
);
|
|
});
|
|
|
|
$(document).on("click", ".product-name-cell", function(e)
|
|
{
|
|
Grocy.Components.ProductCard.Refresh($(e.currentTarget).attr("data-product-id"));
|
|
$("#stockoverview-productcard-modal").modal("show");
|
|
});
|
|
|
|
function RefreshStatistics()
|
|
{
|
|
Grocy.Api.Get('stock',
|
|
function(result)
|
|
{
|
|
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
|
|
{
|
|
$("#info-current-stock").text(__n(result.length, '%s Product', '%s Products'));
|
|
}
|
|
else
|
|
{
|
|
var valueSum = 0;
|
|
result.forEach(element =>
|
|
{
|
|
valueSum += parseFloat(element.value);
|
|
});
|
|
$("#info-current-stock").text(__n(result.length, '%s Product', '%s Products') + ", " + __t('%s total value', valueSum.toLocaleString(undefined, { style: "currency", currency: Grocy.Currency })));
|
|
}
|
|
},
|
|
function(xhr)
|
|
{
|
|
console.error(xhr);
|
|
}
|
|
);
|
|
|
|
var nextXDays = $("#info-duesoon-products").data("next-x-days");
|
|
Grocy.Api.Get('stock/volatile?due_soon_days=' + nextXDays,
|
|
function(result)
|
|
{
|
|
$("#info-duesoon-products").html('<span class="d-block d-md-none">' + result.due_products.length + ' <i class="fas fa-clock"></i></span><span class="d-none d-md-block">' + __n(result.due_products.length, '%s product is due', '%s products are due') + ' ' + __n(nextXDays, 'within the next day', 'within the next %s days') + '</span>');
|
|
$("#info-overdue-products").html('<span class="d-block d-md-none">' + result.overdue_products.length + ' <i class="fas fa-times-circle"></i></span><span class="d-none d-md-block">' + __n(result.overdue_products.length, '%s product is overdue', '%s products are overdue') + '</span>');
|
|
$("#info-expired-products").html('<span class="d-block d-md-none">' + result.expired_products.length + ' <i class="fas fa-times-circle"></i></span><span class="d-none d-md-block">' + __n(result.expired_products.length, '%s product is expired', '%s products are expired') + '</span>');
|
|
$("#info-missing-products").html('<span class="d-block d-md-none">' + result.missing_products.length + ' <i class="fas fa-exclamation-circle"></i></span><span class="d-none d-md-block">' + __n(result.missing_products.length, '%s product is below defined min. stock amount', '%s products are below defined min. stock amount') + '</span>');
|
|
},
|
|
function(xhr)
|
|
{
|
|
console.error(xhr);
|
|
}
|
|
);
|
|
}
|
|
RefreshStatistics();
|
|
|
|
function RefreshProductRow(productId)
|
|
{
|
|
productId = productId.toString();
|
|
|
|
Grocy.Api.Get('stock/products/' + productId,
|
|
function(result)
|
|
{
|
|
// Also refresh the parent product, if any
|
|
if (result.product.parent_product_id !== null && !result.product.parent_product_id.toString().isEmpty())
|
|
{
|
|
RefreshProductRow(result.product.parent_product_id);
|
|
}
|
|
|
|
var productRow = $('#product-' + productId + '-row');
|
|
var dueSoonThreshold = moment().add($("#info-duesoon-products").data("next-x-days"), "days");
|
|
var now = moment();
|
|
var nextDueDate = moment(result.next_due_date);
|
|
|
|
productRow.removeClass("table-warning");
|
|
productRow.removeClass("table-danger");
|
|
productRow.removeClass("table-secondary");
|
|
productRow.removeClass("table-info");
|
|
productRow.removeClass("d-none");
|
|
productRow.removeAttr("style");
|
|
if (now.isAfter(nextDueDate))
|
|
{
|
|
if (result.product.due_type == 1)
|
|
{
|
|
productRow.addClass("table-secondary");
|
|
}
|
|
else
|
|
{
|
|
productRow.addClass("table-danger");
|
|
}
|
|
}
|
|
else if (nextDueDate.isBefore(dueSoonThreshold))
|
|
{
|
|
productRow.addClass("table-warning");
|
|
}
|
|
|
|
if (result.stock_amount == 0 && result.stock_amount_aggregated == 0 && result.product.min_stock_amount == 0)
|
|
{
|
|
animateCSS("#product-" + productId + "-row", "fadeOut", function()
|
|
{
|
|
$("#product-" + productId + "-row").tooltip("hide");
|
|
$("#product-" + productId + "-row").addClass("d-none");
|
|
});
|
|
}
|
|
else
|
|
{
|
|
animateCSS("#product-" + productId + "-row td:not(:first)", "shake");
|
|
|
|
$('#product-' + productId + '-qu-name').text(__n(result.stock_amount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural));
|
|
$('#product-' + productId + '-amount').text(result.stock_amount);
|
|
$('#product-' + productId + '-consume-all-button').attr('data-consume-amount', result.stock_amount);
|
|
$('#product-' + productId + '-value').text(result.stock_value);
|
|
$('#product-' + productId + '-next-due-date').text(result.next_due_date);
|
|
$('#product-' + productId + '-next-due-date-timeago').attr('datetime', result.next_due_date);
|
|
|
|
var openedAmount = result.stock_amount_opened || 0;
|
|
if (openedAmount > 0)
|
|
{
|
|
$('#product-' + productId + '-opened-amount').text(__t('%s opened', openedAmount));
|
|
}
|
|
else
|
|
{
|
|
$('#product-' + productId + '-opened-amount').text("");
|
|
}
|
|
|
|
if (result.stock_amount == 0 && result.product.min_stock_amount > 0)
|
|
{
|
|
productRow.addClass("table-info");
|
|
}
|
|
}
|
|
|
|
$('#product-' + productId + '-next-due-date').text(result.next_due_date);
|
|
$('#product-' + productId + '-next-due-date-timeago').attr('datetime', result.next_due_date + ' 23:59:59');
|
|
|
|
if (result.stock_amount_opened > 0)
|
|
{
|
|
$('#product-' + productId + '-opened-amount').text(__t('%s opened', result.stock_amount_opened));
|
|
}
|
|
else
|
|
{
|
|
$('#product-' + productId + '-opened-amount').text("");
|
|
}
|
|
|
|
if (parseInt(result.is_aggregated_amount) === 1)
|
|
{
|
|
$('#product-' + productId + '-amount-aggregated').text(result.stock_amount_aggregated);
|
|
|
|
if (result.stock_amount_opened_aggregated > 0)
|
|
{
|
|
$('#product-' + productId + '-opened-amount-aggregated').text(__t('%s opened', result.stock_amount_opened_aggregated));
|
|
}
|
|
else
|
|
{
|
|
$('#product-' + productId + '-opened-amount-aggregated').text("");
|
|
}
|
|
}
|
|
|
|
// Needs to be delayed because of the animation above the date-text would be wrong if fired immediately...
|
|
setTimeout(function()
|
|
{
|
|
RefreshContextualTimeago("#product-" + productId + "-row");
|
|
RefreshLocaleNumberDisplay("#product-" + productId + "-row");
|
|
}, 600);
|
|
},
|
|
function(xhr)
|
|
{
|
|
Grocy.FrontendHelpers.EndUiBusy();
|
|
console.error(xhr);
|
|
}
|
|
);
|
|
}
|
|
|
|
$(window).on("message", function(e)
|
|
{
|
|
var data = e.originalEvent.data;
|
|
|
|
if (data.Message === "ProductChanged")
|
|
{
|
|
RefreshProductRow(data.Payload);
|
|
RefreshStatistics();
|
|
}
|
|
});
|