Merge branch '4.80' of https://git.aycode.com/Adam/Mango.Nop.Plugins into 4.80
This commit is contained in:
commit
bc7a5e0be5
|
|
@ -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)
|
private static OrderNote CreateOrderNote(int orderId, bool displayToCustomer, string note)
|
||||||
{
|
{
|
||||||
return new OrderNote
|
return new OrderNote
|
||||||
|
|
@ -878,7 +920,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
if (string.IsNullOrWhiteSpace(term) || term.Length < 2)
|
if (string.IsNullOrWhiteSpace(term) || term.Length < 2)
|
||||||
return Json(new List<object>());
|
return Json(new List<object>());
|
||||||
|
|
||||||
const int maxResults = 15;
|
const int maxResults = 30;
|
||||||
|
|
||||||
// Search products by name or SKU
|
// Search products by name or SKU
|
||||||
var products = await _productService.SearchProductsAsync(
|
var products = await _productService.SearchProductsAsync(
|
||||||
|
|
@ -892,7 +934,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
foreach (var product in products)
|
foreach (var product in products)
|
||||||
{
|
{
|
||||||
var productDto = productDtosById[product.Id];
|
var productDto = productDtosById[product.Id];
|
||||||
|
if (productDto != null)
|
||||||
|
{
|
||||||
|
if(productDto.AvailableQuantity > 0)
|
||||||
|
{
|
||||||
result.Add(new
|
result.Add(new
|
||||||
{
|
{
|
||||||
label = $"{product.Name} [RENDELHETŐ: {(product.StockQuantity + productDto.IncomingQuantity)}] [ÁR: {product.Price}]",
|
label = $"{product.Name} [RENDELHETŐ: {(product.StockQuantity + productDto.IncomingQuantity)}] [ÁR: {product.Price}]",
|
||||||
|
|
@ -903,6 +948,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
incomingQuantity = productDto.IncomingQuantity,
|
incomingQuantity = productDto.IncomingQuantity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@{
|
@{
|
||||||
Layout = "_ConfigurePlugin";
|
Layout = "_ConfigurePlugin";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -13,6 +13,21 @@
|
||||||
<h4>Voice Recorder</h4>
|
<h4>Voice Recorder</h4>
|
||||||
<p>Click the button below to start recording your voice message.</p>
|
<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="form-group row">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<button type="button" id="recordButton" class="btn btn-primary">
|
<button type="button" id="recordButton" class="btn btn-primary">
|
||||||
|
|
@ -70,9 +85,29 @@
|
||||||
const responseMessage = document.getElementById('responseMessage');
|
const responseMessage = document.getElementById('responseMessage');
|
||||||
const transcriptionResult = document.getElementById('transcriptionResult');
|
const transcriptionResult = document.getElementById('transcriptionResult');
|
||||||
const transcriptionText = document.getElementById('transcriptionText');
|
const transcriptionText = document.getElementById('transcriptionText');
|
||||||
|
const permissionInstructions = document.getElementById('permissionInstructions');
|
||||||
|
|
||||||
recordButton.addEventListener('click', async () => {
|
recordButton.addEventListener('click', async () => {
|
||||||
try {
|
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 });
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
|
|
||||||
mediaRecorder = new MediaRecorder(stream);
|
mediaRecorder = new MediaRecorder(stream);
|
||||||
|
|
@ -103,7 +138,35 @@
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error accessing microphone:', 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
|
// Get the antiforgery token
|
||||||
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
|
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',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'RequestVerificationToken': token
|
'RequestVerificationToken': token
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,9 @@
|
||||||
<td style="width: 80px;" class="text-center">
|
<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 />
|
<input type="hidden" name="pvAverageWeightDifference@(item.Id)" id="pvAverageWeightDifference@(item.Id)" value="@(item.AverageWeightDifference.ToString())" disabled />
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
|
||||||
orderItemModelExtended.ProductStockQuantity = orderItemDto.ProductDto!.StockQuantity;
|
orderItemModelExtended.ProductStockQuantity = orderItemDto.ProductDto!.StockQuantity;
|
||||||
orderItemModelExtended.ProductIncomingQuantity = orderItemDto.ProductDto.IncomingQuantity;
|
orderItemModelExtended.ProductIncomingQuantity = orderItemDto.ProductDto.IncomingQuantity;
|
||||||
orderItemModelExtended.ProductAvailableQuantity = orderItemDto.ProductDto.AvailableQuantity;
|
orderItemModelExtended.ProductAvailableQuantity = orderItemDto.ProductDto.AvailableQuantity;
|
||||||
|
orderItemModelExtended.ProductAverageWeight = orderItemDto.ProductDto.AverageWeight;
|
||||||
orderItemModelExtended.AverageWeight = orderItemDto.AverageWeight;
|
orderItemModelExtended.AverageWeight = orderItemDto.AverageWeight;
|
||||||
orderItemModelExtended.AverageWeightIsValid = orderItemDto.AverageWeightIsValid;
|
orderItemModelExtended.AverageWeightIsValid = orderItemDto.AverageWeightIsValid;
|
||||||
orderItemModelExtended.AverageWeightDifference = orderItemDto.AverageWeightDifference;
|
orderItemModelExtended.AverageWeightDifference = orderItemDto.AverageWeightDifference;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Models.Orders
|
||||||
public int ProductStockQuantity { get; set; }
|
public int ProductStockQuantity { get; set; }
|
||||||
public int ProductIncomingQuantity { get; set; }
|
public int ProductIncomingQuantity { get; set; }
|
||||||
public int ProductAvailableQuantity { get; set; }
|
public int ProductAvailableQuantity { get; set; }
|
||||||
|
public double ProductAverageWeight { get; set; }
|
||||||
public double AverageWeight { get; set; }
|
public double AverageWeight { get; set; }
|
||||||
public bool AverageWeightIsValid { get; set; }
|
public bool AverageWeightIsValid { get; set; }
|
||||||
public double AverageWeightDifference { get; set; }
|
public double AverageWeightDifference { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -38,16 +38,21 @@
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="form-group row">
|
<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">
|
<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
|
<i class="fa fa-redo"></i> Újramérés engedélyezése
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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">
|
<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
|
<i class="fas fa-paper-plane"></i> Üzenet küldése
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -161,6 +166,40 @@
|
||||||
</div>
|
</div>
|
||||||
</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">×</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>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|
@ -614,5 +653,90 @@
|
||||||
$("#notificationMessage").val("");
|
$("#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>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue