extra features, after publish, before upgrade

This commit is contained in:
Adam 2025-12-11 18:30:56 +01:00
parent 805c5e2299
commit 61d3b2089c
6 changed files with 256 additions and 18 deletions

View File

@ -765,6 +765,48 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
// };
//}
/// <summary>
/// Add a note to an order that will be displayed in the external application
/// </summary>
[HttpPost]
public async Task<IActionResult> AddOrderNote(int orderId, string note)
{
if (!await _permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE))
return Json(new { success = false, message = "Access denied" });
if (orderId <= 0)
{
return Json(new { success = false, message = "Invalid order ID" });
}
if (string.IsNullOrWhiteSpace(note))
{
return Json(new { success = false, message = "Note text is required" });
}
try
{
// Create and insert the order note
await InsertOrderNoteAsync(orderId, displayToCustomer: false, note);
return Json(new
{
success = true,
message = "Order note added successfully"
});
}
catch (Exception ex)
{
return Json(new
{
success = false,
message = $"Error adding order note: {ex.Message}"
});
}
}
private static OrderNote CreateOrderNote(int orderId, bool displayToCustomer, string note)
{
return new OrderNote
@ -878,7 +920,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
if (string.IsNullOrWhiteSpace(term) || term.Length < 2)
return Json(new List<object>());
const int maxResults = 15;
const int maxResults = 30;
// Search products by name or SKU
var products = await _productService.SearchProductsAsync(
@ -892,16 +934,21 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
foreach (var product in products)
{
var productDto = productDtosById[product.Id];
result.Add(new
if (productDto != null)
{
label = $"{product.Name} [RENDELHETŐ: {(product.StockQuantity + productDto.IncomingQuantity)}] [ÁR: {product.Price}]",
value = product.Id,
sku = product.Sku,
price = product.Price,
stockQuantity = product.StockQuantity,
incomingQuantity = productDto.IncomingQuantity,
});
if(productDto.AvailableQuantity > 0)
{
result.Add(new
{
label = $"{product.Name} [RENDELHETŐ: {(product.StockQuantity + productDto.IncomingQuantity)}] [ÁR: {product.Price}]",
value = product.Id,
sku = product.Sku,
price = product.Price,
stockQuantity = product.StockQuantity,
incomingQuantity = productDto.IncomingQuantity,
});
}
}
}
return Json(result);

View File

@ -1,4 +1,4 @@
@{
@{
Layout = "_ConfigurePlugin";
}
@ -13,6 +13,21 @@
<h4>Voice Recorder</h4>
<p>Click the button below to start recording your voice message.</p>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i> <strong>Important:</strong> When you click "Start Recording", your browser will ask for permission to use your microphone. Please click "Allow" to enable voice recording.
</div>
<div id="permissionInstructions" class="alert alert-warning" style="display:none;">
<strong><i class="fas fa-exclamation-triangle"></i> Microphone Permission Required</strong>
<p class="mb-2">Your browser blocked microphone access. To enable it:</p>
<ul class="mb-2">
<li><strong>Chrome/Edge:</strong> Click the camera/microphone icon in the address bar (or the lock icon) → Site settings → Allow Microphone</li>
<li><strong>Firefox:</strong> Click the microphone icon in the address bar → Click the X to remove the block → Refresh the page</li>
<li><strong>Safari:</strong> Safari menu → Settings for This Website → Microphone → Allow</li>
</ul>
<p class="mb-0">After allowing permission, <strong>refresh this page</strong> and try again.</p>
</div>
<div class="form-group row">
<div class="col-md-9">
<button type="button" id="recordButton" class="btn btn-primary">
@ -70,9 +85,29 @@
const responseMessage = document.getElementById('responseMessage');
const transcriptionResult = document.getElementById('transcriptionResult');
const transcriptionText = document.getElementById('transcriptionText');
const permissionInstructions = document.getElementById('permissionInstructions');
recordButton.addEventListener('click', async () => {
try {
// Hide permission instructions if shown
permissionInstructions.style.display = 'none';
// Check if we're on HTTPS or localhost
const isSecure = window.location.protocol === 'https:' ||
window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1';
if (!isSecure) {
showMessage('Error: Microphone access requires HTTPS connection. Please access this page via HTTPS.', 'danger');
return;
}
// Check if getUserMedia is supported
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
showMessage('Error: Your browser does not support audio recording. Please use a modern browser like Chrome, Firefox, or Edge.', 'danger');
return;
}
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
@ -103,7 +138,35 @@
} catch (error) {
console.error('Error accessing microphone:', error);
showMessage('Error: Could not access microphone. Please check permissions.', 'danger');
let errorMessage = 'Error: Could not access microphone. ';
let showInstructions = false;
if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
errorMessage = 'Microphone access was denied. Please see the instructions below to enable microphone access.';
showInstructions = true;
} else if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
errorMessage += 'No microphone found. Please connect a microphone and try again.';
} else if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
errorMessage += 'Microphone is already in use by another application. Please close other applications using the microphone.';
} else if (error.name === 'OverconstrainedError' || error.name === 'ConstraintNotSatisfiedError') {
errorMessage += 'Microphone does not meet the required constraints.';
} else if (error.name === 'NotSupportedError') {
errorMessage += 'This page must be accessed via HTTPS to use the microphone.';
} else if (error.name === 'TypeError') {
errorMessage += 'This page must be accessed via HTTPS to use the microphone.';
} else {
errorMessage += error.message || 'Unknown error occurred.';
}
showMessage(errorMessage, 'danger');
// Show permission instructions if needed
if (showInstructions) {
permissionInstructions.style.display = 'block';
// Scroll to instructions
permissionInstructions.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
});
@ -133,7 +196,7 @@
// Get the antiforgery token
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
const response = await fetch('@Url.Action("ReceiveVoiceRecording", "FruitBankAudio")', {
const response = await fetch('@Url.Action("ReceiveVoiceRecording", "FruitBankAdmin")', {
method: 'POST',
headers: {
'RequestVerificationToken': token

View File

@ -355,7 +355,9 @@
<td style="width: 80px;" class="text-center">
<span class="">Eltérés: @item.AverageWeightDifference (KG), Mért átlag: @item.AverageWeight (KG/rekesz)</span>
<span class="">Eltérés: @item.AverageWeightDifference (KG)</span>
<span class="">Mért átlag: @item.AverageWeight (KG/rekesz)</span>
<span class="">Elvárt: @item.ProductAverageWeight</span>
<input type="hidden" name="pvAverageWeightDifference@(item.Id)" id="pvAverageWeightDifference@(item.Id)" value="@(item.AverageWeightDifference.ToString())" disabled />
</td>

View File

@ -228,6 +228,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
orderItemModelExtended.ProductStockQuantity = orderItemDto.ProductDto!.StockQuantity;
orderItemModelExtended.ProductIncomingQuantity = orderItemDto.ProductDto.IncomingQuantity;
orderItemModelExtended.ProductAvailableQuantity = orderItemDto.ProductDto.AvailableQuantity;
orderItemModelExtended.ProductAverageWeight = orderItemDto.ProductDto.AverageWeight;
orderItemModelExtended.AverageWeight = orderItemDto.AverageWeight;
orderItemModelExtended.AverageWeightIsValid = orderItemDto.AverageWeightIsValid;
orderItemModelExtended.AverageWeightDifference = orderItemDto.AverageWeightDifference;

View File

@ -14,6 +14,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Models.Orders
public int ProductStockQuantity { get; set; }
public int ProductIncomingQuantity { get; set; }
public int ProductAvailableQuantity { get; set; }
public double ProductAverageWeight { get; set; }
public double AverageWeight { get; set; }
public bool AverageWeightIsValid { get; set; }
public double AverageWeightDifference { get; set; }

View File

@ -29,7 +29,7 @@
</div>
<div class="col-md-6">
<nop-editor asp-for="DateOfReceipt" asp-template="" />
</div>
</div>
<div class="col-md-3 text-right">
<button type="button" id="saveAttributesBtn" class="btn btn-primary">
<i class="fa fa-save"></i> Mentés
@ -38,16 +38,21 @@
</div>
<hr />
<div class="form-group row">
<div class="col-md-6">
<div class="col-md-4">
<button type="button" class="btn btn-warning btn-block" data-toggle="modal" data-target="#allowRevisionModal">
<i class="fa fa-redo"></i> Újramérés engedélyezése
</button>
</div>
<div class="col-md-6">
<div class="col-md-4">
<button type="button" class="btn btn-primary btn-block" data-toggle="modal" data-target="#sendMessageModal">
<i class="fas fa-paper-plane"></i> Üzenet küldése
</button>
</div>
<div class="col-md-4">
<button type="button" class="btn btn-info btn-block" data-toggle="modal" data-target="#addOrderNoteModal">
<i class="fas fa-sticky-note"></i> Jegyzet hozzáadása
</button>
</div>
</div>
</div>
</div>
@ -60,7 +65,7 @@
</div>
<div class="card-body">
<div class="col-12">
<div id="orderStatus" class="alert alert-info" style="display: none;">
<i class="fas fa-info-circle"></i> <span id="orderStatusMessage"></span>
</div>
@ -161,6 +166,40 @@
</div>
</div>
<!-- Add Order Note Modal -->
<div class="modal fade" id="addOrderNoteModal" tabindex="-1" role="dialog" aria-labelledby="addOrderNoteModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addOrderNoteModalLabel">
<i class="fas fa-sticky-note"></i> Jegyzet hozzáadása
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="orderNoteText"><strong>Jegyzet szövege:</strong></label>
<textarea id="orderNoteText" class="form-control" rows="4" placeholder="Írja be a jegyzetet a kollégák számára..."></textarea>
<small class="form-text text-muted">Ez a jegyzet megjelenik az external alkalmazásban a kollégák számára.</small>
</div>
<div id="orderNoteStatus" class="alert" style="display: none; margin-top: 15px;">
<i class="fas fa-info-circle"></i> <span id="orderNoteStatusMessage"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
<i class="fa fa-times"></i> Mégse
</button>
<button type="button" id="addOrderNoteBtn" class="btn btn-primary">
<i class="fas fa-save"></i> Jegyzet mentése
</button>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
@ -614,5 +653,90 @@
$("#notificationMessage").val("");
});
// ========== ORDER NOTE MANAGEMENT ==========
var addOrderNoteUrl = '@Url.Action("AddOrderNote", "CustomOrder")';
// Add Order Note (in modal)
$("#addOrderNoteBtn").click(function (e) {
e.preventDefault();
e.stopPropagation();
var noteText = $("#orderNoteText").val().trim();
if (!noteText) {
showOrderNoteStatus("Kérjük, adjon meg egy jegyzetet!", "warning");
return false;
}
var btn = $(this);
btn.prop("disabled", true).html('<i class="fas fa-spinner fa-spin"></i> Mentés...');
showOrderNoteStatus("Jegyzet mentése folyamatban...", "info");
// Add asterisk (*) at the beginning of the message
var noteWithAsterisk = "* " + noteText;
$.ajax({
type: "POST",
url: addOrderNoteUrl,
data: {
orderId: @Model.OrderId,
note: noteWithAsterisk,
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
},
dataType: 'json',
success: function (response) {
console.log("Order Note Response:", response);
btn.prop("disabled", false).html('<i class="fas fa-save"></i> Jegyzet mentése');
if (response.success) {
showOrderNoteStatus("Jegyzet sikeresen mentve!", "success");
// Close modal after 1.5 seconds
setTimeout(function() {
$('#addOrderNoteModal').modal('hide');
$("#orderNoteText").val(""); // Clear the textbox
}, 1500);
} else {
showOrderNoteStatus("Hiba: " + (response.message || "Ismeretlen hiba"), "danger");
}
},
error: function (xhr, status, error) {
console.error("Order Note AJAX Error:", xhr, status, error);
btn.prop("disabled", false).html('<i class="fas fa-save"></i> Jegyzet mentése');
var errorMessage = "Hiba a jegyzet mentése közben";
if (xhr.responseJSON && xhr.responseJSON.message) {
errorMessage = xhr.responseJSON.message;
} else if (xhr.responseText) {
try {
var errorObj = JSON.parse(xhr.responseText);
errorMessage = errorObj.message || errorMessage;
} catch (e) {
errorMessage = "Hiba: " + xhr.status + " - " + xhr.statusText;
}
}
showOrderNoteStatus(errorMessage, "danger");
}
});
return false;
});
function showOrderNoteStatus(message, type) {
var statusDiv = $("#orderNoteStatus");
statusDiv.removeClass("alert-info alert-success alert-warning alert-danger")
.addClass("alert-" + type);
$("#orderNoteStatusMessage").text(message);
statusDiv.show();
}
// Clear order note status when modal is closed
$('#addOrderNoteModal').on('hidden.bs.modal', function () {
$("#orderNoteStatus").hide();
$("#orderNoteText").val("");
});
});
</script>