This commit is contained in:
Loretta 2025-11-06 15:12:05 +01:00
commit 99755e49f4
12 changed files with 834 additions and 52 deletions

View File

@ -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; }
}
}
}

View File

@ -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",

View File

@ -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) {

View File

@ -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">&times;</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>

View File

@ -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)

View File

@ -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;
}

View File

@ -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>();

View File

@ -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}",

View File

@ -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; }
}
}

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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>