Merge branch '4.80' of https://git.aycode.com/Adam/Mango.Nop.Plugins into 4.80
This commit is contained in:
commit
99755e49f4
|
|
@ -48,6 +48,7 @@ using System.Xml.Serialization;
|
|||
using AyCode.Services.Server.SignalRs;
|
||||
using AyCode.Core.Extensions;
|
||||
using MessagePack.Resolvers;
|
||||
using Nop.Services.Tax;
|
||||
|
||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||
{
|
||||
|
|
@ -76,6 +77,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
protected readonly IGiftCardService _giftCardService;
|
||||
protected readonly IImportManager _importManager;
|
||||
protected readonly IDateTimeHelper _dateTimeHelper;
|
||||
protected readonly ITaxService _taxService;
|
||||
private static readonly char[] _separator = [','];
|
||||
// ... other dependencies
|
||||
|
||||
|
|
@ -121,7 +123,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
IExportManager exportManager,
|
||||
IGiftCardService giftCardService,
|
||||
IImportManager importManager,
|
||||
IDateTimeHelper dateTimeHelper)
|
||||
IDateTimeHelper dateTimeHelper,
|
||||
ITaxService taxService)
|
||||
{
|
||||
_logger = new Logger<CustomOrderController>(logWriters.ToArray());
|
||||
|
||||
|
|
@ -147,7 +150,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
_giftCardService = giftCardService;
|
||||
_importManager = importManager;
|
||||
_dateTimeHelper = dateTimeHelper;
|
||||
|
||||
_taxService = taxService;
|
||||
// ... initialize other deps
|
||||
}
|
||||
|
||||
|
|
@ -340,6 +343,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
};
|
||||
|
||||
orderListModel.Data = sortedData.ToList();
|
||||
orderListModel.RecordsTotal = orderListModel.Data.Count();
|
||||
//orderListModel.Draw = searchModel.Draw;
|
||||
|
||||
Console.WriteLine($"Sorted Data Count: {orderListModel.Data.Count()}");
|
||||
Console.WriteLine($"Total Records: {orderListModel.RecordsTotal}");
|
||||
Console.WriteLine($"Filtered Records: {orderListModel.RecordsFiltered}");
|
||||
Console.WriteLine($"Draw: {orderListModel.Draw}");
|
||||
|
||||
|
||||
_logger.Detail($"Sorted by {searchModel.SortColumn} {searchModel.SortColumnDirection}");
|
||||
}
|
||||
|
|
@ -1117,6 +1128,184 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
return Json(new { success = false, message = $"Hiba történt: {ex.Message}" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
//[IgnoreAntiforgeryToken]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> FruitBankAddProductToOrder(int orderId, string productsJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info($"AddProductToOrder - OrderId: {orderId}, ProductsJson: {productsJson}");
|
||||
|
||||
if (!await _permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE))
|
||||
{
|
||||
return Json(new { success = false, message = "Access denied" });
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(productsJson))
|
||||
{
|
||||
return Json(new { success = false, message = "No products data received" });
|
||||
}
|
||||
|
||||
var order = await _orderService.GetOrderByIdAsync(orderId);
|
||||
if (order == null || order.Deleted)
|
||||
{
|
||||
return Json(new { success = false, message = "Order not found" });
|
||||
}
|
||||
|
||||
// Deserialize products
|
||||
var products = JsonConvert.DeserializeObject<List<AddProductModel>>(productsJson);
|
||||
|
||||
if (products == null || !products.Any())
|
||||
{
|
||||
return Json(new { success = false, message = "No products to add" });
|
||||
}
|
||||
|
||||
var productDtosByOrderItemId = await _dbContext.ProductDtos.GetAllByIds(products.Select(x => x.Id).ToArray()).ToDictionaryAsync(k => k.Id, v => v);
|
||||
var customer = await _customerService.GetCustomerByIdAsync(order.CustomerId);
|
||||
var store = await _storeContext.GetCurrentStoreAsync();
|
||||
var admin = await _workContext.GetCurrentCustomerAsync();
|
||||
string errorMessage = "";
|
||||
|
||||
var transactionSuccess = await _dbContext.TransactionSafeAsync(async _ =>
|
||||
{
|
||||
|
||||
|
||||
// Add each product to the order
|
||||
foreach (var productModel in products)
|
||||
{
|
||||
var product = await _productService.GetProductByIdAsync(productModel.Id);
|
||||
if (product == null)
|
||||
{
|
||||
_logger.Warning($"Product with ID {productModel.Id} not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate stock
|
||||
var stockQuantity = await _productService.GetTotalStockQuantityAsync(product);
|
||||
var productDto = productDtosByOrderItemId[productModel.Id];
|
||||
var isMeasurable = productDto.IsMeasurable;
|
||||
|
||||
//if (stockQuantity < productModel.Quantity)
|
||||
//{
|
||||
// return Json(new
|
||||
// {
|
||||
// success = false,
|
||||
// message = $"Product '{product.Name}' has insufficient stock. Available: {stockQuantity}, Requested: {productModel.Quantity}"
|
||||
// });
|
||||
//}
|
||||
|
||||
if ((product.StockQuantity + productDto.IncomingQuantity) - productModel.Quantity < 0)
|
||||
{
|
||||
errorMessage = $"Nem elérhető készleten!";
|
||||
var errorText = $"((product.StockQuantity + productDto.IncomingQuantity) - item.Quantity < 0); productId: {product.Id}; (product.StockQuantity + productDto.IncomingQuantity) - item.Quantity: {(product.StockQuantity + productDto.IncomingQuantity) - productModel.Quantity}";
|
||||
_logger.Error($"{errorText}");
|
||||
throw new Exception($"{errorText}");
|
||||
|
||||
}
|
||||
|
||||
// Get or calculate price
|
||||
var unitPrice = productModel.Price > 0
|
||||
? productModel.Price
|
||||
: (await _priceCalculationService.GetFinalPriceAsync(product, customer, store)).finalPrice;
|
||||
|
||||
// Calculate tax
|
||||
var (unitPriceInclTaxValue, _) = await _taxService.GetProductPriceAsync(product, unitPrice, true, customer);
|
||||
var (unitPriceExclTaxValue, _) = await _taxService.GetProductPriceAsync(product, unitPrice, false, customer);
|
||||
|
||||
// Create order item
|
||||
var orderItem = new OrderItem
|
||||
{
|
||||
OrderItemGuid = Guid.NewGuid(),
|
||||
OrderId = order.Id,
|
||||
ProductId = product.Id,
|
||||
UnitPriceInclTax = unitPriceInclTaxValue,
|
||||
UnitPriceExclTax = unitPriceExclTaxValue,
|
||||
PriceInclTax = unitPriceInclTaxValue * productModel.Quantity,
|
||||
PriceExclTax = unitPriceExclTaxValue * productModel.Quantity,
|
||||
OriginalProductCost = await _priceCalculationService.GetProductCostAsync(product, null),
|
||||
Quantity = productModel.Quantity,
|
||||
DiscountAmountInclTax = decimal.Zero,
|
||||
DiscountAmountExclTax = decimal.Zero,
|
||||
DownloadCount = 0,
|
||||
IsDownloadActivated = false,
|
||||
LicenseDownloadId = 0,
|
||||
ItemWeight = product.Weight * productModel.Quantity,
|
||||
RentalStartDateUtc = null,
|
||||
RentalEndDateUtc = null
|
||||
};
|
||||
|
||||
await _orderService.InsertOrderItemAsync(orderItem);
|
||||
|
||||
// Adjust inventory
|
||||
await _productService.AdjustInventoryAsync(
|
||||
product,
|
||||
-productModel.Quantity,
|
||||
orderItem.AttributesXml,
|
||||
string.Format(await _localizationService.GetResourceAsync("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id));
|
||||
}
|
||||
|
||||
// Update order totals
|
||||
var orderSubtotalInclTax = decimal.Zero;
|
||||
var orderSubtotalExclTax = decimal.Zero;
|
||||
|
||||
var orderItems = await _orderService.GetOrderItemsAsync(order.Id);
|
||||
foreach (var item in orderItems)
|
||||
{
|
||||
orderSubtotalInclTax += item.PriceInclTax;
|
||||
orderSubtotalExclTax += item.PriceExclTax;
|
||||
}
|
||||
|
||||
order.OrderSubtotalInclTax = orderSubtotalInclTax;
|
||||
order.OrderSubtotalExclTax = orderSubtotalExclTax;
|
||||
order.OrderTotal = orderSubtotalInclTax + order.OrderShippingInclTax + order.PaymentMethodAdditionalFeeInclTax - order.OrderDiscount;
|
||||
|
||||
await _orderService.UpdateOrderAsync(order);
|
||||
|
||||
// Add order note
|
||||
await _orderService.InsertOrderNoteAsync(new OrderNote
|
||||
{
|
||||
OrderId = order.Id,
|
||||
Note = $"Products added to order by {admin.FirstName} {admin.LastName}, (Id: {admin.Id})",
|
||||
DisplayToCustomer = false,
|
||||
CreatedOnUtc = DateTime.UtcNow
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if(transactionSuccess)
|
||||
{
|
||||
_logger.Info($"Successfully added {products.Count} products to order {orderId}");
|
||||
|
||||
return Json(new { success = true, message = "Products added successfully" });
|
||||
}
|
||||
else {
|
||||
return Json(new { success = false, message = errorMessage });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Error adding products to order {orderId}, {ex.Message}");
|
||||
return Json(new { success = false, message = $"Error: {ex.Message}" });
|
||||
}
|
||||
}
|
||||
|
||||
// Helper model for deserialization
|
||||
public class AddProductModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Sku { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public int StockQuantity { get; set; }
|
||||
public int IncomingQuantity { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
private readonly ICountryService _countryService;
|
||||
private readonly IProductService _productService;
|
||||
private readonly InnVoiceOrderService _innVoiceOrderService;
|
||||
private readonly FruitBankAttributeService _fruitBankAttributeService;
|
||||
private readonly IGenericAttributeService _genericAttributeService;
|
||||
private readonly FruitBankDbContext _dbContext;
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
ICountryService countryService,
|
||||
IProductService productService,
|
||||
InnVoiceOrderService innVoiceOrderService,
|
||||
FruitBankAttributeService fruitBankAttributeService,
|
||||
IGenericAttributeService genericAttributeService,
|
||||
FruitBankDbContext dbContext)
|
||||
{
|
||||
|
|
@ -50,6 +52,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
_countryService = countryService;
|
||||
_productService = productService;
|
||||
_innVoiceOrderService = innVoiceOrderService;
|
||||
_fruitBankAttributeService = fruitBankAttributeService;
|
||||
_genericAttributeService = genericAttributeService;
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
|
@ -175,6 +178,12 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
var storeId = currentStore?.Id ?? 0;
|
||||
|
||||
// Save InnVoice order details as attributes
|
||||
//await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, string>(
|
||||
// order.Id,
|
||||
// "InnVoiceOrderTechId",
|
||||
// response.TechId,
|
||||
// storeId
|
||||
//);
|
||||
await _genericAttributeService.SaveAttributeAsync(
|
||||
order,
|
||||
"InnVoiceOrderTechId",
|
||||
|
|
|
|||
|
|
@ -397,6 +397,13 @@
|
|||
Width = "150"
|
||||
//Render = new RenderCustom("renderColumnCustomer")
|
||||
});
|
||||
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.InnvoiceTechId))
|
||||
{
|
||||
Title = "Innvoiceba beküldve",
|
||||
Width = "150",
|
||||
Render = new RenderCustom("renderColumnInnvoiceTechId"),
|
||||
ClassName = NopColumnClassDefaults.CenterAll
|
||||
});
|
||||
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.IsMeasurable))
|
||||
{
|
||||
Title = T($"FruitBank.{nameof(OrderModelExtended.IsMeasurable)}").Text,
|
||||
|
|
@ -531,11 +538,18 @@
|
|||
return `${textRenderer(row.Company)} <br /><a href="${link}">${data}</a > `;
|
||||
}
|
||||
|
||||
function renderColumnInnvoiceTechId(data, type, row, meta) {
|
||||
if(data != null) {
|
||||
return '<span class="badge badge-success" disabled>Igen</span>';
|
||||
}
|
||||
return '<span class="badge badge-warning" disabled>Nem</span>';
|
||||
}
|
||||
|
||||
function renderColumnIsMeasurable(data, type, row, meta) {
|
||||
if(data === true) {
|
||||
return '<span class="badge badge-warning" disabled>Yes</span>';
|
||||
return '<input type="checkbox" name="IsMeasurable" checked disabled>';
|
||||
}
|
||||
return '<span class="badge badge-secondary" disabled>No</span>';
|
||||
return '<input type="checkbox" name="IsMeasurable" disabled>';
|
||||
}
|
||||
|
||||
function renderColumnPickupDateAndTime(data, type, row, meta) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
@using Nop.Core.Domain.Tax;
|
||||
@using Nop.Core.Domain.Catalog;
|
||||
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<script>
|
||||
function calculateTotalGlobal(itemId, maxQuantity) {
|
||||
//var isMeasurable = $('#pvIsMeasurable'+itemId).is(':checked');
|
||||
|
|
@ -466,12 +468,286 @@
|
|||
}
|
||||
@if (!Model.IsLoggedInAsVendor)
|
||||
{
|
||||
@* <div class="form-group row">
|
||||
<div class="col-md-12">
|
||||
<button type="submit" id="btnAddNewProduct" name="btnAddNewProduct" onclick="javascript:setLocation('@(Url.Action("AddProductToOrder", "CustomOrder", new { orderId = Model.Id }))'); return false;" class="btn btn-primary">
|
||||
@T("Admin.Orders.Products.AddNew")
|
||||
</button>
|
||||
</div>
|
||||
</div> *@
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12">
|
||||
<button type="submit" id="btnAddNewProduct" name="btnAddNewProduct" onclick="javascript:setLocation('@(Url.Action("AddProductToOrder", "Order", new { orderId = Model.Id }))'); return false;" class="btn btn-primary">
|
||||
<button type="button" id="btnAddNewProduct" name="btnAddNewProduct" class="btn btn-primary" data-toggle="modal" data-target="#add-product-to-order-window">
|
||||
@T("Admin.Orders.Products.AddNew")
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@* Add Product to Order Modal *@
|
||||
<div id="add-product-to-order-window" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="add-product-to-order-title">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="add-product-to-order-title">@T("Admin.Orders.Products.AddNew")</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="add-product-form-container">
|
||||
@* @Html.AntiForgeryToken() *@
|
||||
<div class="form-horizontal">
|
||||
<div class="modal-body">
|
||||
<!-- Product Selection -->
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3">
|
||||
<div class="label-wrapper">
|
||||
<label class="col-form-label">
|
||||
@T("Admin.Orders.Fields.Product")
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="add-product-search" autocomplete="off" class="form-control" placeholder="Type product name or SKU..." />
|
||||
<span class="field-validation-error" id="add-product-error" style="display:none;">Please select at least one product</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected Products List -->
|
||||
<div id="add-products-section" style="display:none;">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12">
|
||||
<label class="col-form-label"><strong>Products to Add:</strong></label>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered" id="add-products-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th style="width: 100px;">Mennyiség</th>
|
||||
<th style="width: 120px;">Egységár</th>
|
||||
<th style="width: 50px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="add-products-body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
@T("Admin.Common.Cancel")
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" id="add-product-submit">
|
||||
@T("Admin.Orders.Products.AddNew")
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Fix z-index for autocomplete dropdown in modal */
|
||||
.ui-autocomplete {
|
||||
z-index: 1060 !important;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Ensure the modal backdrop doesn't interfere */
|
||||
.modal-backdrop {
|
||||
z-index: 1040 !important;
|
||||
}
|
||||
|
||||
/* Ensure the modal itself has correct z-index */
|
||||
#add-product-to-order-window {
|
||||
z-index: 1050 !important;
|
||||
}
|
||||
|
||||
#add-products-table input[type="number"] {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
#add-products-table input[type="text"] {
|
||||
width: 100px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
var addProducts = [];
|
||||
|
||||
// Product autocomplete for adding to existing order
|
||||
$('#add-product-search').autocomplete({
|
||||
delay: 500,
|
||||
minLength: 2,
|
||||
source: '@Url.Action("ProductSearchAutoComplete", "CustomOrder")',
|
||||
select: function(event, ui) {
|
||||
addProductToList(ui.item);
|
||||
$('#add-product-search').val('');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Add product to list
|
||||
function addProductToList(product) {
|
||||
var existingProduct = addProducts.find(p => p.id === product.value);
|
||||
if (existingProduct) {
|
||||
alert('This product is already in the list.');
|
||||
return;
|
||||
}
|
||||
|
||||
var productItem = {
|
||||
id: product.value,
|
||||
name: product.label,
|
||||
sku: product.sku || '',
|
||||
quantity: 1,
|
||||
stockQuantity: product.stockQuantity,
|
||||
incomingQuantity: product.incomingQuantity,
|
||||
price: product.price || 0
|
||||
};
|
||||
|
||||
addProducts.push(productItem);
|
||||
renderAddProductsList();
|
||||
}
|
||||
|
||||
// Render products list
|
||||
function renderAddProductsList() {
|
||||
var tbody = $('#add-products-body');
|
||||
tbody.empty();
|
||||
|
||||
if (addProducts.length === 0) {
|
||||
$('#add-products-section').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
$('#add-products-section').show();
|
||||
|
||||
addProducts.forEach(function(product, index) {
|
||||
var row = $('<tr>');
|
||||
|
||||
var nameCell = $('<td>').html(
|
||||
'<strong>' + product.name + '</strong>' +
|
||||
(product.sku ? '<br><small>SKU: ' + product.sku + '</small>' : '')
|
||||
);
|
||||
|
||||
var quantityCell = $('<td>').html(
|
||||
'<input type="number" class="form-control form-control-sm quantity-input" min="1" max="' +
|
||||
(product.stockQuantity + product.incomingQuantity) + '" value="' + product.quantity +
|
||||
'" data-index="' + index + '" />'
|
||||
);
|
||||
|
||||
var priceCell = $('<td>').html(
|
||||
'<input type="text" class="form-control form-control-sm price-input" value="' +
|
||||
product.price + '" data-index="' + index + '" />'
|
||||
);
|
||||
|
||||
var removeCell = $('<td class="text-center">').html(
|
||||
'<button type="button" class="btn btn-sm btn-danger remove-product-btn" data-index="' +
|
||||
index + '"><i class="fas fa-trash"></i></button>'
|
||||
);
|
||||
|
||||
row.append(nameCell).append(quantityCell).append(priceCell).append(removeCell);
|
||||
tbody.append(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Update quantity
|
||||
$(document).on('change', '#add-products-body .quantity-input', function() {
|
||||
var index = $(this).data('index');
|
||||
var newQuantity = parseInt($(this).val());
|
||||
if (newQuantity > 0) {
|
||||
addProducts[index].quantity = newQuantity;
|
||||
} else {
|
||||
$(this).val(addProducts[index].quantity);
|
||||
}
|
||||
});
|
||||
|
||||
// Update price
|
||||
$(document).on('change', '#add-products-body .price-input', function() {
|
||||
var index = $(this).data('index');
|
||||
var newPrice = parseFloat($(this).val());
|
||||
if (!isNaN(newPrice) && newPrice >= 0) {
|
||||
addProducts[index].price = newPrice;
|
||||
} else {
|
||||
$(this).val(addProducts[index].price);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove product
|
||||
$(document).on('click', '#add-products-body .remove-product-btn', function() {
|
||||
var index = $(this).data('index');
|
||||
addProducts.splice(index, 1);
|
||||
renderAddProductsList();
|
||||
});
|
||||
|
||||
// Submit button click handler
|
||||
$('#add-product-submit').on('click', function() {
|
||||
// Validate
|
||||
if (addProducts.length === 0) {
|
||||
$('#add-product-error').show();
|
||||
alert('Please add at least one product');
|
||||
return false;
|
||||
}
|
||||
|
||||
$('#add-product-error').hide();
|
||||
|
||||
// Show loading state
|
||||
var $btn = $(this);
|
||||
var originalText = $btn.text();
|
||||
$btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Adding...');
|
||||
|
||||
// Get anti-forgery token
|
||||
// var token = $('#add-product-form-container input[name="__RequestVerificationToken"]').val();
|
||||
var token = $('input[name="__RequestVerificationToken"]').val();
|
||||
|
||||
// Submit via AJAX
|
||||
$.ajax({
|
||||
url: '@Url.Action("FruitBankAddProductToOrder", "CustomOrder")',
|
||||
type: 'POST',
|
||||
data: {
|
||||
orderId: @Model.Id,
|
||||
productsJson: JSON.stringify(addProducts),
|
||||
__RequestVerificationToken: token
|
||||
},
|
||||
success: function(response) {
|
||||
// Close modal
|
||||
$('#add-product-to-order-window').modal('hide');
|
||||
|
||||
// Show success message (you can customize this)
|
||||
// alert('Products added successfully!');
|
||||
if(!response.success) {
|
||||
// alert('Products added successfully!');
|
||||
alert('Nem sikerült hozzáadni: ' + response.message);
|
||||
}
|
||||
// Reload the page to show updated order
|
||||
location.reload();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', xhr.responseText);
|
||||
alert('Error adding products: ' + (xhr.responseText || error));
|
||||
|
||||
// Reset button
|
||||
$btn.prop('disabled', false).text(originalText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Reset form when modal is closed
|
||||
$('#add-product-to-order-window').on('hidden.bs.modal', function() {
|
||||
$('#add-product-search').val('');
|
||||
$('#add-product-error').hide();
|
||||
addProducts = [];
|
||||
renderAddProductsList();
|
||||
|
||||
// Reset button state
|
||||
$('#add-product-submit').prop('disabled', false).text('@T("Admin.Orders.Products.AddNew")');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ using Nop.Plugin.Misc.FruitBankPlugin.Factories.MgBase;
|
|||
using FruitBank.Common.Dtos;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order;
|
||||
using Mango.Nop.Core.Extensions;
|
||||
|
||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
|
||||
{
|
||||
|
|
@ -184,6 +185,15 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
|
|||
|
||||
//var fullName = $"{orderDto.Customer.FirstName}_{orderDto.Customer.LastName}".Trim();
|
||||
orderModelExtended.CustomerCompany = $"{orderDto.Customer.Company} {orderDto.Customer.FirstName}_{orderDto.Customer.LastName}";
|
||||
var genericAttributes = orderDto.GenericAttributes;
|
||||
if(genericAttributes != null)
|
||||
{
|
||||
var innvoiceTechIdAttribute = genericAttributes.FirstOrDefault(ga => ga.Key == "InnVoiceOrderTechId");
|
||||
if (innvoiceTechIdAttribute != null)
|
||||
{
|
||||
orderModelExtended.InnvoiceTechId = innvoiceTechIdAttribute.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<OrderModel> PrepareOrderModelAsync(OrderModel model, Order order, bool excludeProperties = false)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ using Nop.Web.Areas.Admin.Factories;
|
|||
using Nop.Web.Areas.Admin.Models.Common;
|
||||
using Nop.Web.Areas.Admin.Models.Orders;
|
||||
using Nop.Web.Framework.Extensions;
|
||||
using Nop.Web.Framework.Models.Extensions;
|
||||
|
||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Factories.MgBase;
|
||||
|
||||
|
|
@ -250,10 +251,100 @@ public class MgOrderModelFactory<TOrderListModelExt, TOrderModelExt> : OrderMode
|
|||
var preFiltered = await base.PrepareOrderListModelAsync(searchModel);
|
||||
return preFiltered;
|
||||
}
|
||||
|
||||
public async Task<OrderListModel> PrepareOrderListModelAsync(OrderSearchModel searchModel, int customerId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(searchModel);
|
||||
|
||||
//get parameters to filter orders
|
||||
var orderStatusIds = (searchModel.OrderStatusIds?.Contains(0) ?? true) ? null : searchModel.OrderStatusIds.ToList();
|
||||
var paymentStatusIds = (searchModel.PaymentStatusIds?.Contains(0) ?? true) ? null : searchModel.PaymentStatusIds.ToList();
|
||||
var shippingStatusIds = (searchModel.ShippingStatusIds?.Contains(0) ?? true) ? null : searchModel.ShippingStatusIds.ToList();
|
||||
var currentVendor = await _workContext.GetCurrentVendorAsync();
|
||||
if (currentVendor != null)
|
||||
searchModel.VendorId = currentVendor.Id;
|
||||
var startDateValue = !searchModel.StartDate.HasValue ? null
|
||||
: (DateTime?)_dateTimeHelper.ConvertToUtcTime(searchModel.StartDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync());
|
||||
var endDateValue = !searchModel.EndDate.HasValue ? null
|
||||
: (DateTime?)_dateTimeHelper.ConvertToUtcTime(searchModel.EndDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync()).AddDays(1);
|
||||
var product = await _productService.GetProductByIdAsync(searchModel.ProductId);
|
||||
var filterByProductId = product != null && (currentVendor == null || product.VendorId == currentVendor.Id)
|
||||
? searchModel.ProductId : 0;
|
||||
|
||||
//get orders
|
||||
var orders = await _orderService.SearchOrdersAsync(storeId: searchModel.StoreId,
|
||||
vendorId: searchModel.VendorId,
|
||||
customerId: customerId,
|
||||
productId: filterByProductId,
|
||||
warehouseId: searchModel.WarehouseId,
|
||||
paymentMethodSystemName: searchModel.PaymentMethodSystemName,
|
||||
createdFromUtc: startDateValue,
|
||||
createdToUtc: endDateValue,
|
||||
osIds: orderStatusIds,
|
||||
psIds: paymentStatusIds,
|
||||
ssIds: shippingStatusIds,
|
||||
billingPhone: searchModel.BillingPhone,
|
||||
billingEmail: searchModel.BillingEmail,
|
||||
billingLastName: searchModel.BillingLastName,
|
||||
billingCountryId: searchModel.BillingCountryId,
|
||||
orderNotes: searchModel.OrderNotes,
|
||||
pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize);
|
||||
|
||||
//prepare list model
|
||||
var model = await new OrderListModel().PrepareToGridAsync(searchModel, orders, () =>
|
||||
{
|
||||
//fill in model values from the entity
|
||||
return orders.SelectAwait(async order =>
|
||||
{
|
||||
var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);
|
||||
|
||||
//fill in model values from the entity
|
||||
var orderModel = new OrderModel
|
||||
{
|
||||
Id = order.Id,
|
||||
OrderStatusId = order.OrderStatusId,
|
||||
PaymentStatusId = order.PaymentStatusId,
|
||||
ShippingStatusId = order.ShippingStatusId,
|
||||
CustomerEmail = billingAddress.Email,
|
||||
CustomerFullName = $"{billingAddress.FirstName} {billingAddress.LastName}",
|
||||
CustomerId = order.CustomerId,
|
||||
CustomOrderNumber = order.CustomOrderNumber
|
||||
};
|
||||
|
||||
//convert dates to the user time
|
||||
orderModel.CreatedOn = await _dateTimeHelper.ConvertToUserTimeAsync(order.CreatedOnUtc, DateTimeKind.Utc);
|
||||
|
||||
//fill in additional values (not existing in the entity)
|
||||
orderModel.StoreName = (await _storeService.GetStoreByIdAsync(order.StoreId))?.Name ?? "Deleted";
|
||||
orderModel.OrderStatus = await _localizationService.GetLocalizedEnumAsync(order.OrderStatus);
|
||||
orderModel.PaymentStatus = await _localizationService.GetLocalizedEnumAsync(order.PaymentStatus);
|
||||
orderModel.ShippingStatus = await _localizationService.GetLocalizedEnumAsync(order.ShippingStatus);
|
||||
orderModel.OrderTotal = await _priceFormatter.FormatPriceAsync(order.OrderTotal, true, false);
|
||||
|
||||
return orderModel;
|
||||
});
|
||||
});
|
||||
|
||||
return model;
|
||||
}
|
||||
public virtual async Task<TOrderListModelExt> PrepareOrderListModelExtendedAsync(OrderSearchModelExtended searchModel, Func<OrderListModel, TOrderModelExt, Task> dataItemCopiedCallback)
|
||||
{
|
||||
var prefiltered = await PrepareOrderListModelAsync(searchModel);
|
||||
|
||||
var customerCompany = searchModel.BillingCompany;
|
||||
var customer = await _customerService.GetCustomerByIdAsync(Convert.ToInt32(customerCompany));
|
||||
//var customer = customers.FirstOrDefault(c => c.Company != null && c.Company.Equals(customerCompany, StringComparison.InvariantCultureIgnoreCase));
|
||||
//var customer = customers.FirstOrDefault(c => c.Company != null && c.Company.Equals(customerCompany, StringComparison.InvariantCultureIgnoreCase));
|
||||
OrderListModel prefiltered;
|
||||
if (customer != null)
|
||||
{
|
||||
|
||||
prefiltered = await PrepareOrderListModelAsync(searchModel, customer.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
prefiltered = await PrepareOrderListModelAsync(searchModel);
|
||||
|
||||
}
|
||||
|
||||
var extendedRows = new List<TOrderModelExt>(prefiltered.RecordsFiltered);
|
||||
|
||||
foreach (var orderModel in prefiltered.Data.ToList())
|
||||
|
|
@ -271,19 +362,24 @@ public class MgOrderModelFactory<TOrderListModelExt, TOrderModelExt> : OrderMode
|
|||
var totalRecords = prefiltered.RecordsTotal;
|
||||
var filteredRecords = prefiltered.RecordsFiltered;
|
||||
|
||||
Console.WriteLine($"Total Records before filtering: {totalRecords}");
|
||||
Console.WriteLine($"Filtered Records before filtering: {filteredRecords}");
|
||||
|
||||
//var orderListModelExtended = orderListModel.ToJson().JsonTo<TOrderListModelExt>();
|
||||
if (searchModel.BillingCompany != null)
|
||||
{
|
||||
//extendedRows = extendedRows.Where(x => x.CustomerCompany != null && x.CustomerCompany.IndexOf(searchModel.BillingCompany, StringComparison.InvariantCultureIgnoreCase) >= 0).ToList();
|
||||
extendedRows = extendedRows.Where(x => x.CustomerId == Convert.ToInt32(searchModel.BillingCompany)).ToList();
|
||||
}
|
||||
//if (searchModel.BillingCompany != null)
|
||||
//{
|
||||
// //extendedRows = extendedRows.Where(x => x.CustomerCompany != null && x.CustomerCompany.IndexOf(searchModel.BillingCompany, StringComparison.InvariantCultureIgnoreCase) >= 0).ToList();
|
||||
// extendedRows = extendedRows.Where(x => x.CustomerId == Convert.ToInt32(searchModel.BillingCompany)).ToList();
|
||||
//}
|
||||
|
||||
|
||||
var orderListModelExtended = prefiltered.CloneTo<TOrderListModelExt>();
|
||||
orderListModelExtended.Data = extendedRows;
|
||||
orderListModelExtended.RecordsTotal = extendedRows.Count;
|
||||
//orderListModelExtended.RecordsFiltered = extendedRows.Count;
|
||||
|
||||
orderListModelExtended.RecordsTotal = totalRecords;
|
||||
|
||||
//orderListModelExtended.RecordsTotal = totalRecords;
|
||||
orderListModelExtended.RecordsFiltered = filteredRecords;
|
||||
return orderListModelExtended;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ using Nop.Services.Events;
|
|||
using Nop.Web.Areas.Admin.Factories;
|
||||
using Nop.Web.Areas.Admin.Models.Catalog;
|
||||
using Nop.Web.Areas.Admin.Models.Orders;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Infrastructure;
|
||||
|
||||
|
|
@ -87,6 +88,13 @@ public class PluginNopStartup : INopStartup
|
|||
services.AddScoped<InnVoiceOrderService>();
|
||||
|
||||
services.AddScoped<MeasurementService>();
|
||||
services.AddScoped<ReplicateService>();
|
||||
|
||||
services.AddHttpClient<ReplicateService>(client =>
|
||||
{
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Bearer", "r8_MUApXYIE5mRjxqy20tsGLehWBJkCzNj0Cwvrh");
|
||||
});
|
||||
|
||||
//services.AddScoped<OrderListModel, OrderListModelExtended>();
|
||||
//services.AddScoped<OrderModel, OrderModelExtended>();
|
||||
|
|
|
|||
|
|
@ -147,6 +147,11 @@ public class RouteProvider : IRouteProvider
|
|||
pattern: "Admin/Order/Edit/{id}",
|
||||
defaults: new { controller = "CustomOrder", action = "Edit", area = AreaNames.ADMIN });
|
||||
|
||||
endpointRouteBuilder.MapControllerRoute(
|
||||
name: "Plugin.FruitBank.Admin.Order.AddProduct",
|
||||
pattern: "Admin/CustomOrder/FruitBankAddProductToOrder",
|
||||
defaults: new { controller = "CustomOrder", action = "FruitBankAddProductToOrder", area = AreaNames.ADMIN });
|
||||
|
||||
endpointRouteBuilder.MapControllerRoute(
|
||||
name: "Plugin.FruitBank.Admin.ManagementPage.ProcessShippingDocument",
|
||||
pattern: "Admin/ManagamentPage/ProcessShippingDocument/{id}",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Models.Orders
|
|||
|
||||
public string CustomerCompany { get; set; }
|
||||
|
||||
public string InnvoiceTechId { get; set; }
|
||||
|
||||
public IList<OrderItemModelExtended> ItemExtendeds { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ public class CustomPriceCalculationService : PriceCalculationService
|
|||
|
||||
if (productDto.IsMeasurable)
|
||||
{
|
||||
return (0, 0, 0m, []);
|
||||
return (0, product.Price, 1000m, []);
|
||||
//return (overriddenProductPrice.GetValueOrDefault(0), overriddenProductPrice.GetValueOrDefault(0), 0m, []);
|
||||
}
|
||||
//var productAttributeMappings = await _specificationAttributeService.GetProductSpecificationAttributesAsync(product.Id);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
{
|
||||
public class ReplicateService
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
|
||||
public ReplicateService(HttpClient http)
|
||||
{
|
||||
_http = http;
|
||||
}
|
||||
public async Task<string> GenerateImageAsync(string prompt, bool removeBackground)
|
||||
{
|
||||
return await GenerateImageAsync("https://api.replicate.com/v1/models/black-forest-labs/flux-schnell/predictions", prompt, removeBackground);
|
||||
}
|
||||
public async Task<string> GenerateLogoAsync(string prompt, bool removeBackground)
|
||||
{
|
||||
return await GenerateImageAsync("https://api.replicate.com/v1/models/google/imagen-4-fast/predictions", prompt, removeBackground);
|
||||
}
|
||||
|
||||
public async Task<string> GenerateImageAsync(string apiUrl, string prompt, bool removeBackground)
|
||||
{
|
||||
var request = new
|
||||
{
|
||||
input = new { prompt = prompt, aspect_ratio = "1:1", output_format = "jpg" }
|
||||
|
||||
};
|
||||
|
||||
var createResponse = await _http.PostAsJsonAsync(apiUrl, request);
|
||||
createResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var createJson = await createResponse.Content.ReadFromJsonAsync<JsonElement>();
|
||||
if (!createJson.TryGetProperty("id", out var idProp))
|
||||
throw new Exception("Replicate response missing prediction ID.");
|
||||
|
||||
string predictionId = idProp.GetString();
|
||||
string status = "";
|
||||
JsonElement finalJson;
|
||||
|
||||
for (int attempt = 0; attempt < 30; attempt++)
|
||||
{
|
||||
var getResponse = await _http.GetAsync($"https://api.replicate.com/v1/predictions/{predictionId}");
|
||||
getResponse.EnsureSuccessStatusCode();
|
||||
|
||||
finalJson = await getResponse.Content.ReadFromJsonAsync<JsonElement>();
|
||||
status = finalJson.GetProperty("status").GetString();
|
||||
|
||||
if (status == "succeeded")
|
||||
{
|
||||
var output = finalJson.GetProperty("output");
|
||||
|
||||
if (output.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var imageUrl = output.GetString();
|
||||
if (removeBackground)
|
||||
{
|
||||
return await RemoveBackgroundAsync(imageUrl);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return imageUrl;
|
||||
}
|
||||
}
|
||||
else if (output.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
var length = output.GetArrayLength();
|
||||
var imageUrl = output[0].ToString();
|
||||
if (removeBackground)
|
||||
{
|
||||
return await RemoveBackgroundAsync(imageUrl);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return "Replicate response succeeded but no output image URL found.";
|
||||
}
|
||||
else if (status == "failed")
|
||||
{
|
||||
return "Replicate prediction failed.";
|
||||
}
|
||||
|
||||
await Task.Delay(2500);
|
||||
}
|
||||
|
||||
return "Timeout waiting for Replicate prediction to complete.";
|
||||
}
|
||||
|
||||
public async Task<string> RemoveBackgroundAsync(string imageUrl)
|
||||
{
|
||||
var request = new
|
||||
{
|
||||
version = "a029dff38972b5fda4ec5d75d7d1cd25aeff621d2cf4946a41055d7db66b80bc",
|
||||
input = new { image = imageUrl }
|
||||
};
|
||||
|
||||
var createResponse = await _http.PostAsJsonAsync("https://api.replicate.com/v1/predictions", request);
|
||||
createResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var createJson = await createResponse.Content.ReadFromJsonAsync<JsonElement>();
|
||||
if (!createJson.TryGetProperty("id", out var idProp))
|
||||
throw new Exception("Replicate response missing prediction ID.");
|
||||
|
||||
string predictionId = idProp.GetString();
|
||||
string status = "";
|
||||
JsonElement finalJson;
|
||||
|
||||
for (int attempt = 0; attempt < 20; attempt++)
|
||||
{
|
||||
var getResponse = await _http.GetAsync($"https://api.replicate.com/v1/predictions/{predictionId}");
|
||||
getResponse.EnsureSuccessStatusCode();
|
||||
|
||||
finalJson = await getResponse.Content.ReadFromJsonAsync<JsonElement>();
|
||||
status = finalJson.GetProperty("status").GetString();
|
||||
|
||||
if (status == "succeeded")
|
||||
{
|
||||
var output = finalJson.GetProperty("output");
|
||||
|
||||
if (output.ValueKind == JsonValueKind.String)
|
||||
return output.GetString();
|
||||
|
||||
return "Replicate response succeeded but no output image URL found.";
|
||||
}
|
||||
else if (status == "failed")
|
||||
{
|
||||
return "Replicate prediction failed.";
|
||||
}
|
||||
|
||||
await Task.Delay(1500);
|
||||
}
|
||||
|
||||
return "Timeout waiting for Replicate prediction to complete.";
|
||||
}
|
||||
|
||||
public async Task<JsonElement> AnalyzeImageAsync(string imageUrl)
|
||||
{
|
||||
var body = new
|
||||
{
|
||||
version = "a524caeaa23495bc9edc805ab08ab5fe943afd3febed884a4f3747aa32e9cd61",
|
||||
input = new
|
||||
{
|
||||
image = imageUrl
|
||||
}
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Post,
|
||||
"https://api.replicate.com/v1/predictions")
|
||||
{
|
||||
Content = JsonContent.Create(body)
|
||||
};
|
||||
|
||||
// This makes Replicate wait until the prediction is done
|
||||
request.Headers.Add("Prefer", "wait");
|
||||
|
||||
var response = await _http.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
return json.ValueKind == JsonValueKind.Undefined
|
||||
? throw new Exception("Empty response from Replicate.")
|
||||
: json;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,42 +2,14 @@
|
|||
@model OrderAttributesModel
|
||||
|
||||
|
||||
|
||||
@*
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<div class="card card-default mb-2">
|
||||
<div class="card-header">
|
||||
<h4><i class="fas fa-cogs"></i> Üzenet küldése</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12">
|
||||
<label for="notificationMessage">Üzenet szövege:</label>
|
||||
<textarea id="notificationMessage" class="form-control" rows="3" placeholder="Írja be az üzenetet..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12">
|
||||
<div id="notificationStatus" class="alert" style="display: none;">
|
||||
<i class="fas fa-info-circle"></i> <span id="notificationStatusMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button type="button" id="sendNotificationBtn" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane"></i> Üzenet küldése
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> *@
|
||||
|
||||
<div class="form-group row">
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="card card-default mb-2">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-file-invoice"></i>
|
||||
|
|
@ -63,22 +35,50 @@
|
|||
<div class="form-group row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button type="button" id="saveAttributesBtn" class="btn btn-primary">
|
||||
<i class="fa fa-save"></i> Save Attributes
|
||||
<i class="fa fa-save"></i> Mentés
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="card card-default mb-3">
|
||||
<div class="card-header">
|
||||
<h4><i class="fas fa-cogs"></i> Üzenet küldése</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<label for="notificationMessage">Üzenet szövege:</label>
|
||||
<textarea id="notificationMessage" class="form-control" rows="3" placeholder="Írja be az üzenetet..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<div id="notificationStatus" class="alert" style="display: none;">
|
||||
<i class="fas fa-info-circle"></i> <span id="notificationStatusMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-12 text-right">
|
||||
<button type="button" id="sendNotificationBtn" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane"></i> Üzenet küldése
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="card card-default mb-3">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-file-invoice"></i>
|
||||
InnVoice Management
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="col-6">
|
||||
<div class="col-12">
|
||||
<h5><i class="fas fa-shopping-cart"></i> Megrendelés beküldése Innvoice-ba</h5>
|
||||
<div id="orderStatus" class="alert alert-info" style="display: none;">
|
||||
<i class="fas fa-info-circle"></i> <span id="orderStatusMessage"></span>
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 text-right float-end">
|
||||
<div class="col-12 text-right float-end">
|
||||
<button type="button" id="createOrderBtn" class="btn btn-success">
|
||||
<i class="fas fa-shopping-cart"></i> Létrehozás
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Reference in New Issue