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.Services.Server.SignalRs;
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
using MessagePack.Resolvers;
|
using MessagePack.Resolvers;
|
||||||
|
using Nop.Services.Tax;
|
||||||
|
|
||||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
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 IGiftCardService _giftCardService;
|
||||||
protected readonly IImportManager _importManager;
|
protected readonly IImportManager _importManager;
|
||||||
protected readonly IDateTimeHelper _dateTimeHelper;
|
protected readonly IDateTimeHelper _dateTimeHelper;
|
||||||
|
protected readonly ITaxService _taxService;
|
||||||
private static readonly char[] _separator = [','];
|
private static readonly char[] _separator = [','];
|
||||||
// ... other dependencies
|
// ... other dependencies
|
||||||
|
|
||||||
|
|
@ -121,7 +123,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
IExportManager exportManager,
|
IExportManager exportManager,
|
||||||
IGiftCardService giftCardService,
|
IGiftCardService giftCardService,
|
||||||
IImportManager importManager,
|
IImportManager importManager,
|
||||||
IDateTimeHelper dateTimeHelper)
|
IDateTimeHelper dateTimeHelper,
|
||||||
|
ITaxService taxService)
|
||||||
{
|
{
|
||||||
_logger = new Logger<CustomOrderController>(logWriters.ToArray());
|
_logger = new Logger<CustomOrderController>(logWriters.ToArray());
|
||||||
|
|
||||||
|
|
@ -147,7 +150,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
_giftCardService = giftCardService;
|
_giftCardService = giftCardService;
|
||||||
_importManager = importManager;
|
_importManager = importManager;
|
||||||
_dateTimeHelper = dateTimeHelper;
|
_dateTimeHelper = dateTimeHelper;
|
||||||
|
_taxService = taxService;
|
||||||
// ... initialize other deps
|
// ... initialize other deps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -340,6 +343,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
};
|
};
|
||||||
|
|
||||||
orderListModel.Data = sortedData.ToList();
|
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}");
|
_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}" });
|
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 ICountryService _countryService;
|
||||||
private readonly IProductService _productService;
|
private readonly IProductService _productService;
|
||||||
private readonly InnVoiceOrderService _innVoiceOrderService;
|
private readonly InnVoiceOrderService _innVoiceOrderService;
|
||||||
|
private readonly FruitBankAttributeService _fruitBankAttributeService;
|
||||||
private readonly IGenericAttributeService _genericAttributeService;
|
private readonly IGenericAttributeService _genericAttributeService;
|
||||||
private readonly FruitBankDbContext _dbContext;
|
private readonly FruitBankDbContext _dbContext;
|
||||||
|
|
||||||
|
|
@ -40,6 +41,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
ICountryService countryService,
|
ICountryService countryService,
|
||||||
IProductService productService,
|
IProductService productService,
|
||||||
InnVoiceOrderService innVoiceOrderService,
|
InnVoiceOrderService innVoiceOrderService,
|
||||||
|
FruitBankAttributeService fruitBankAttributeService,
|
||||||
IGenericAttributeService genericAttributeService,
|
IGenericAttributeService genericAttributeService,
|
||||||
FruitBankDbContext dbContext)
|
FruitBankDbContext dbContext)
|
||||||
{
|
{
|
||||||
|
|
@ -50,6 +52,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
_countryService = countryService;
|
_countryService = countryService;
|
||||||
_productService = productService;
|
_productService = productService;
|
||||||
_innVoiceOrderService = innVoiceOrderService;
|
_innVoiceOrderService = innVoiceOrderService;
|
||||||
|
_fruitBankAttributeService = fruitBankAttributeService;
|
||||||
_genericAttributeService = genericAttributeService;
|
_genericAttributeService = genericAttributeService;
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
}
|
}
|
||||||
|
|
@ -175,6 +178,12 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
var storeId = currentStore?.Id ?? 0;
|
var storeId = currentStore?.Id ?? 0;
|
||||||
|
|
||||||
// Save InnVoice order details as attributes
|
// Save InnVoice order details as attributes
|
||||||
|
//await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, string>(
|
||||||
|
// order.Id,
|
||||||
|
// "InnVoiceOrderTechId",
|
||||||
|
// response.TechId,
|
||||||
|
// storeId
|
||||||
|
//);
|
||||||
await _genericAttributeService.SaveAttributeAsync(
|
await _genericAttributeService.SaveAttributeAsync(
|
||||||
order,
|
order,
|
||||||
"InnVoiceOrderTechId",
|
"InnVoiceOrderTechId",
|
||||||
|
|
|
||||||
|
|
@ -397,6 +397,13 @@
|
||||||
Width = "150"
|
Width = "150"
|
||||||
//Render = new RenderCustom("renderColumnCustomer")
|
//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))
|
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.IsMeasurable))
|
||||||
{
|
{
|
||||||
Title = T($"FruitBank.{nameof(OrderModelExtended.IsMeasurable)}").Text,
|
Title = T($"FruitBank.{nameof(OrderModelExtended.IsMeasurable)}").Text,
|
||||||
|
|
@ -531,11 +538,18 @@
|
||||||
return `${textRenderer(row.Company)} <br /><a href="${link}">${data}</a > `;
|
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) {
|
function renderColumnIsMeasurable(data, type, row, meta) {
|
||||||
if(data === true) {
|
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) {
|
function renderColumnPickupDateAndTime(data, type, row, meta) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
@using Nop.Core.Domain.Tax;
|
@using Nop.Core.Domain.Tax;
|
||||||
@using Nop.Core.Domain.Catalog;
|
@using Nop.Core.Domain.Catalog;
|
||||||
|
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function calculateTotalGlobal(itemId, maxQuantity) {
|
function calculateTotalGlobal(itemId, maxQuantity) {
|
||||||
//var isMeasurable = $('#pvIsMeasurable'+itemId).is(':checked');
|
//var isMeasurable = $('#pvIsMeasurable'+itemId).is(':checked');
|
||||||
|
|
@ -466,12 +468,286 @@
|
||||||
}
|
}
|
||||||
@if (!Model.IsLoggedInAsVendor)
|
@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="form-group row">
|
||||||
<div class="col-md-12">
|
<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")
|
@T("Admin.Orders.Products.AddNew")
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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 FruitBank.Common.Dtos;
|
||||||
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
|
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
|
||||||
using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order;
|
using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order;
|
||||||
|
using Mango.Nop.Core.Extensions;
|
||||||
|
|
||||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
|
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();
|
//var fullName = $"{orderDto.Customer.FirstName}_{orderDto.Customer.LastName}".Trim();
|
||||||
orderModelExtended.CustomerCompany = $"{orderDto.Customer.Company} {orderDto.Customer.FirstName}_{orderDto.Customer.LastName}";
|
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)
|
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.Common;
|
||||||
using Nop.Web.Areas.Admin.Models.Orders;
|
using Nop.Web.Areas.Admin.Models.Orders;
|
||||||
using Nop.Web.Framework.Extensions;
|
using Nop.Web.Framework.Extensions;
|
||||||
|
using Nop.Web.Framework.Models.Extensions;
|
||||||
|
|
||||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Factories.MgBase;
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Factories.MgBase;
|
||||||
|
|
||||||
|
|
@ -250,10 +251,100 @@ public class MgOrderModelFactory<TOrderListModelExt, TOrderModelExt> : OrderMode
|
||||||
var preFiltered = await base.PrepareOrderListModelAsync(searchModel);
|
var preFiltered = await base.PrepareOrderListModelAsync(searchModel);
|
||||||
return preFiltered;
|
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)
|
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);
|
var extendedRows = new List<TOrderModelExt>(prefiltered.RecordsFiltered);
|
||||||
|
|
||||||
foreach (var orderModel in prefiltered.Data.ToList())
|
foreach (var orderModel in prefiltered.Data.ToList())
|
||||||
|
|
@ -271,19 +362,24 @@ public class MgOrderModelFactory<TOrderListModelExt, TOrderModelExt> : OrderMode
|
||||||
var totalRecords = prefiltered.RecordsTotal;
|
var totalRecords = prefiltered.RecordsTotal;
|
||||||
var filteredRecords = prefiltered.RecordsFiltered;
|
var filteredRecords = prefiltered.RecordsFiltered;
|
||||||
|
|
||||||
|
Console.WriteLine($"Total Records before filtering: {totalRecords}");
|
||||||
|
Console.WriteLine($"Filtered Records before filtering: {filteredRecords}");
|
||||||
|
|
||||||
//var orderListModelExtended = orderListModel.ToJson().JsonTo<TOrderListModelExt>();
|
//var orderListModelExtended = orderListModel.ToJson().JsonTo<TOrderListModelExt>();
|
||||||
if (searchModel.BillingCompany != null)
|
//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.CustomerCompany != null && x.CustomerCompany.IndexOf(searchModel.BillingCompany, StringComparison.InvariantCultureIgnoreCase) >= 0).ToList();
|
||||||
extendedRows = extendedRows.Where(x => x.CustomerId == Convert.ToInt32(searchModel.BillingCompany)).ToList();
|
// extendedRows = extendedRows.Where(x => x.CustomerId == Convert.ToInt32(searchModel.BillingCompany)).ToList();
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
var orderListModelExtended = prefiltered.CloneTo<TOrderListModelExt>();
|
var orderListModelExtended = prefiltered.CloneTo<TOrderListModelExt>();
|
||||||
orderListModelExtended.Data = extendedRows;
|
orderListModelExtended.Data = extendedRows;
|
||||||
|
orderListModelExtended.RecordsTotal = extendedRows.Count;
|
||||||
|
//orderListModelExtended.RecordsFiltered = extendedRows.Count;
|
||||||
|
|
||||||
orderListModelExtended.RecordsTotal = totalRecords;
|
|
||||||
|
//orderListModelExtended.RecordsTotal = totalRecords;
|
||||||
orderListModelExtended.RecordsFiltered = filteredRecords;
|
orderListModelExtended.RecordsFiltered = filteredRecords;
|
||||||
return orderListModelExtended;
|
return orderListModelExtended;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ using Nop.Services.Events;
|
||||||
using Nop.Web.Areas.Admin.Factories;
|
using Nop.Web.Areas.Admin.Factories;
|
||||||
using Nop.Web.Areas.Admin.Models.Catalog;
|
using Nop.Web.Areas.Admin.Models.Catalog;
|
||||||
using Nop.Web.Areas.Admin.Models.Orders;
|
using Nop.Web.Areas.Admin.Models.Orders;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Infrastructure;
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Infrastructure;
|
||||||
|
|
||||||
|
|
@ -87,6 +88,13 @@ public class PluginNopStartup : INopStartup
|
||||||
services.AddScoped<InnVoiceOrderService>();
|
services.AddScoped<InnVoiceOrderService>();
|
||||||
|
|
||||||
services.AddScoped<MeasurementService>();
|
services.AddScoped<MeasurementService>();
|
||||||
|
services.AddScoped<ReplicateService>();
|
||||||
|
|
||||||
|
services.AddHttpClient<ReplicateService>(client =>
|
||||||
|
{
|
||||||
|
client.DefaultRequestHeaders.Authorization =
|
||||||
|
new AuthenticationHeaderValue("Bearer", "r8_MUApXYIE5mRjxqy20tsGLehWBJkCzNj0Cwvrh");
|
||||||
|
});
|
||||||
|
|
||||||
//services.AddScoped<OrderListModel, OrderListModelExtended>();
|
//services.AddScoped<OrderListModel, OrderListModelExtended>();
|
||||||
//services.AddScoped<OrderModel, OrderModelExtended>();
|
//services.AddScoped<OrderModel, OrderModelExtended>();
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,11 @@ public class RouteProvider : IRouteProvider
|
||||||
pattern: "Admin/Order/Edit/{id}",
|
pattern: "Admin/Order/Edit/{id}",
|
||||||
defaults: new { controller = "CustomOrder", action = "Edit", area = AreaNames.ADMIN });
|
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(
|
endpointRouteBuilder.MapControllerRoute(
|
||||||
name: "Plugin.FruitBank.Admin.ManagementPage.ProcessShippingDocument",
|
name: "Plugin.FruitBank.Admin.ManagementPage.ProcessShippingDocument",
|
||||||
pattern: "Admin/ManagamentPage/ProcessShippingDocument/{id}",
|
pattern: "Admin/ManagamentPage/ProcessShippingDocument/{id}",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Models.Orders
|
||||||
|
|
||||||
public string CustomerCompany { get; set; }
|
public string CustomerCompany { get; set; }
|
||||||
|
|
||||||
|
public string InnvoiceTechId { get; set; }
|
||||||
|
|
||||||
public IList<OrderItemModelExtended> ItemExtendeds { get; set; }
|
public IList<OrderItemModelExtended> ItemExtendeds { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ public class CustomPriceCalculationService : PriceCalculationService
|
||||||
|
|
||||||
if (productDto.IsMeasurable)
|
if (productDto.IsMeasurable)
|
||||||
{
|
{
|
||||||
return (0, 0, 0m, []);
|
return (0, product.Price, 1000m, []);
|
||||||
//return (overriddenProductPrice.GetValueOrDefault(0), overriddenProductPrice.GetValueOrDefault(0), 0m, []);
|
//return (overriddenProductPrice.GetValueOrDefault(0), overriddenProductPrice.GetValueOrDefault(0), 0m, []);
|
||||||
}
|
}
|
||||||
//var productAttributeMappings = await _specificationAttributeService.GetProductSpecificationAttributesAsync(product.Id);
|
//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
|
@model OrderAttributesModel
|
||||||
|
|
||||||
|
|
||||||
|
@*
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-12">
|
|
||||||
<div class="card card-default mb-2">
|
</div> *@
|
||||||
<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 class="form-group row">
|
<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 card-default mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<i class="fas fa-file-invoice"></i>
|
<i class="fas fa-file-invoice"></i>
|
||||||
|
|
@ -63,22 +35,50 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-md-12 text-right">
|
<div class="col-md-12 text-right">
|
||||||
<button type="button" id="saveAttributesBtn" class="btn btn-primary">
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
<div class="col-12 col-md-6">
|
<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 card-default mb-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<i class="fas fa-file-invoice"></i>
|
<i class="fas fa-file-invoice"></i>
|
||||||
InnVoice Management
|
InnVoice Management
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<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>
|
<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;">
|
<div id="orderStatus" class="alert alert-info" style="display: none;">
|
||||||
<i class="fas fa-info-circle"></i> <span id="orderStatusMessage"></span>
|
<i class="fas fa-info-circle"></i> <span id="orderStatusMessage"></span>
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<button type="button" id="createOrderBtn" class="btn btn-success">
|
||||||
<i class="fas fa-shopping-cart"></i> Létrehozás
|
<i class="fas fa-shopping-cart"></i> Létrehozás
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue