This commit is contained in:
Adam 2025-10-24 12:03:00 +02:00
commit 015f42bef9
16 changed files with 690 additions and 145 deletions

File diff suppressed because one or more lines are too long

View File

@ -35,6 +35,12 @@ public class CustomOrderSignalREndpoint(FruitBankDbContext ctx, IWorkContext wor
return await ctx.OrderDtos.GetAllByOrderStatus(OrderStatus.Pending).ToListAsync(); return await ctx.OrderDtos.GetAllByOrderStatus(OrderStatus.Pending).ToListAsync();
} }
[SignalR(SignalRTags.GetPendingOrderDtosForMeasuring)]
public async Task<List<OrderDto>> GetPendingOrderDtosForMeasuring()
{
return await ctx.OrderDtos.GetAllForMeasuring().ToListAsync();
}
[SignalR(SignalRTags.GetAllOrderDtoByIds)] [SignalR(SignalRTags.GetAllOrderDtoByIds)]
public async Task<List<OrderDto>> GetAllOrderDtoByIds(int[] orderIds) public async Task<List<OrderDto>> GetAllOrderDtoByIds(int[] orderIds)
{ {

View File

@ -0,0 +1,82 @@
@model OrderModel
@{
//page title
ViewBag.PageTitle = T("Admin.Orders.EditOrderDetails").Text + "ANYÁD";
//active menu item (system name)
NopHtml.SetActiveMenuItemSystemName("Orders");
}
@{
const string hideInfoBlockAttributeName = "OrderPage.HideInfoBlock";
var customer = await workContext.GetCurrentCustomerAsync();
var hideInfoBlock = await genericAttributeService.GetAttributeAsync<bool>(customer, hideInfoBlockAttributeName);
const string hideBillingAndShippingBlockAttributeName = "OrderPage.HideBillingAndShippingBlock";
var hideBillingAndShippingBlock = await genericAttributeService.GetAttributeAsync<bool>(customer, hideBillingAndShippingBlockAttributeName);
const string hideProductsBlockAttributeName = "OrderPage.HideProductsBlock";
var hideProductsBlock = await genericAttributeService.GetAttributeAsync<bool>(customer, hideProductsBlockAttributeName);
const string hideNotesBlockAttributeName = "OrderPage.HideNotesBlock";
var hideNotesBlock = await genericAttributeService.GetAttributeAsync<bool>(customer, hideNotesBlockAttributeName);
}
<form asp-controller="Order" asp-action="Edit" method="post" id="order-form">
<div class="content-header clearfix">
<h1 class="float-left">
@T("Admin.Orders.EditOrderDetails") - @Model.CustomOrderNumber
<small>
<i class="fas fa-arrow-circle-left"></i>
<a asp-action="List">@T("Admin.Orders.BackToList")</a>
</small>
</h1>
<div class="float-right">
<a asp-action="PdfInvoice" asp-route-orderId="@Model.Id" class="btn btn-info">
<i class="far fa-file-pdf"></i>
@T("Admin.Orders.PdfInvoice")
</a>
@if (!Model.IsLoggedInAsVendor)
{
<span id="order-delete" class="btn btn-danger">
<i class="far fa-trash-can"></i>
@T("Admin.Common.Delete")
</span>
}
<button type="submit" id="btnRefreshPage" style="display: none"></button>
<script>
$(function() {
$('#btnRefreshPage').click(function () {
//refresh pageed
location.reload();
});
});
</script>
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.OrderDetailsButtons, additionalData = Model })
</div>
</div>
<div asp-validation-summary="All"></div>
<section class="content">
<div class="container-fluid">
<div class="form-horizontal">
<nop-cards id="order-cards">
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.OrderDetailsBlock, additionalData = Model })
<nop-card asp-name="order-info" asp-icon="fas fa-info" asp-title="@T("Admin.Orders.Info")" asp-hide-block-attribute-name="@hideInfoBlockAttributeName" asp-hide="@hideInfoBlock" asp-advanced="false">@await Html.PartialAsync("_OrderDetails.Info", Model)</nop-card>
<nop-card asp-name="order-billing-shipping" asp-icon="fas fa-truck" asp-title="@T("Admin.Orders.BillingShippingInfo")" asp-hide-block-attribute-name="@hideBillingAndShippingBlockAttributeName" asp-hide="@hideBillingAndShippingBlock" asp-advanced="false">@await Html.PartialAsync("_OrderDetails.BillingShipping", Model)</nop-card>
<nop-card asp-name="order-products" asp-icon="fas fa-table-list" asp-title="@T("Admin.Orders.Products")" asp-hide-block-attribute-name="@hideProductsBlockAttributeName" asp-hide="@hideProductsBlock" asp-advanced="true">@await Html.PartialAsync("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/_CustomOrderDetails.Products.cshtml", Model)</nop-card>
@if (!Model.IsLoggedInAsVendor)
{
<nop-card asp-name="order-notes" asp-icon="far fa-sticky-note" asp-title="@T("Admin.Orders.OrderNotes")" asp-hide-block-attribute-name="@hideNotesBlockAttributeName" asp-hide="@hideNotesBlock" asp-advanced="true">@await Html.PartialAsync("_OrderDetails.Notes", Model)</nop-card>
}
</nop-cards>
</div>
</div>
</section>
</form>
<nop-delete-confirmation asp-model-id="@Model.Id" asp-button-id="order-delete" />

View File

@ -752,8 +752,8 @@
<thead> <thead>
<tr> <tr>
<th>Product</th> <th>Product</th>
<th style="width: 100px;">Quantity</th> <th style="width: 100px;">Mennyisség</th>
<th style="width: 120px;">Price</th> <th style="width: 120px;">Egységár</th>
<th style="width: 50px;"></th> <th style="width: 50px;"></th>
</tr> </tr>
</thead> </thead>

View File

@ -0,0 +1,399 @@
@model OrderModel
@using Nop.Core.Domain.Tax;
@using Nop.Core.Domain.Catalog;
<div class="card-body">
<div class="form-group row">
<div class="col-md-12" style="overflow-x: auto;">ANYÁD!!!!!
@foreach (var item in Model.Items)
{
<script>
$(function() {
toggleOrderItemEdit@(item.Id)(false);
// Attach event listeners for automatic calculation
setupAutoCalculation@(item.Id)();
});
</script>
<script>
// Function to automatically calculate and update total price
function calculateTotal@(item.Id)() {
// Get values
var unitPriceInclTax = parseFloat($('#pvUnitPriceInclTax@(item.Id)').val()) || 0;
var unitPriceExclTax = parseFloat($('#pvUnitPriceExclTax@(item.Id)').val()) || 0;
var quantity = parseFloat($('#pvQuantity@(item.Id)').val()) || 0;
var discountInclTax = parseFloat($('#pvDiscountInclTax@(item.Id)').val()) || 0;
var discountExclTax = parseFloat($('#pvDiscountExclTax@(item.Id)').val()) || 0;
// Calculate totals
var totalInclTax = (unitPriceInclTax * quantity) - discountInclTax;
var totalExclTax = (unitPriceExclTax * quantity) - discountExclTax;
// Update total price fields
$('#pvPriceInclTax@(item.Id)').val(totalInclTax.toFixed(0));
$('#pvPriceExclTax@(item.Id)').val(totalExclTax.toFixed(0));
}
// Function to setup event listeners for automatic calculation
function setupAutoCalculation@(item.Id)() {
// Attach change and input events to all relevant fields
$('#pvUnitPriceInclTax@(item.Id), #pvUnitPriceExclTax@(item.Id), #pvQuantity@(item.Id), #pvDiscountInclTax@(item.Id), #pvDiscountExclTax@(item.Id)').on('input change', function() {
calculateTotal@(item.Id)();
});
}
function toggleOrderItemEdit@(item.Id)(editMode) {
if (editMode) {
$('#pnlEditPvUnitPrice@(item.Id)').showElement();
$('#pnlEditPvQuantity@(item.Id)').showElement();
$('#pnlEditPvDiscount@(item.Id)').showElement();
$('#pnlEditPvPrice@(item.Id)').showElement();
$('#btnSaveOrderItem@(item.Id)').showElement();
$('#btnCancelOrderItem@(item.Id)').showElement();
$('#pvUnitPriceInclTax@(item.Id)').prop("disabled", false);
$('#pvUnitPriceExclTax@(item.Id)').prop("disabled", false);
$('#pvQuantity@(item.Id)').prop("disabled", false);
$('#pvDiscountInclTax@(item.Id)').prop("disabled", false);
$('#pvDiscountExclTax@(item.Id)').prop("disabled", false);
$('#pvPriceInclTax@(item.Id)').prop("disabled", false);
$('#pvPriceExclTax@(item.Id)').prop("disabled", false);
$('#btnEditOrderItem@(item.Id)').hideElement();
$('#btnDeleteOrderItem@(item.Id)').hideElement();
} else {
$('#pnlEditPvUnitPrice@(item.Id)').hideElement();
$('#pnlEditPvQuantity@(item.Id)').hideElement();
$('#pnlEditPvDiscount@(item.Id)').hideElement();
$('#pnlEditPvPrice@(item.Id)').hideElement();
$('#btnSaveOrderItem@(item.Id)').hideElement();
$('#btnCancelOrderItem@(item.Id)').hideElement();
$('#pvUnitPriceInclTax@(item.Id)').prop("disabled", true);
$('#pvUnitPriceExclTax@(item.Id)').prop("disabled", true);
$('#pvQuantity@(item.Id)').prop("disabled", true);
$('#pvDiscountInclTax@(item.Id)').prop("disabled", true);
$('#pvDiscountExclTax@(item.Id)').prop("disabled", true);
$('#pvPriceInclTax@(item.Id)').prop("disabled", true);
$('#pvPriceExclTax@(item.Id)').prop("disabled", true);
$('#btnEditOrderItem@(item.Id)').showElement();
$('#btnDeleteOrderItem@(item.Id)').showElement();
}
}
</script>
}
<table class="table table-hover table-bordered">
<col />
<col />
@if (Model.HasDownloadableProducts)
{
<col />
}
<col />
<col />
<col />
<col />
@if (!Model.IsLoggedInAsVendor)
{
<col />
}
<thead>
<tr>
<th>
@T("Admin.Orders.Products.Picture")
</th>
<th>
@T("Admin.Orders.Products.ProductName")
</th>
@if (Model.HasDownloadableProducts)
{
<th>
@T("Admin.Orders.Products.Download")
</th>
}
<th>
@T("Admin.Orders.Products.Price")
</th>
<th>
@T("Admin.Orders.Products.Quantity")
</th>
<th>
@T("Admin.Orders.Products.Discount")
</th>
<th>
@T("Admin.Orders.Products.Total")
</th>
@if (!Model.IsLoggedInAsVendor)
{
<th>
@T("Admin.Common.Edit")
</th>
}
</tr>
</thead>
<tbody>
@foreach (var item in Model.Items)
{
<tr>
<td class="text-center preview">
<img src="@item.PictureThumbnailUrl" alt="" title="" />
</td>
<td style="width: 25%;" class="text-left">
<em><a asp-controller="Product" asp-action="Edit" asp-route-id="@item.ProductId">@item.ProductName</a></em>
@if (!string.IsNullOrEmpty(item.AttributeInfo))
{
<p>
@Html.Raw(item.AttributeInfo)
</p>
}
@if (!string.IsNullOrEmpty(item.RecurringInfo))
{
<p>
@Html.Raw(item.RecurringInfo)
</p>
}
@if (!string.IsNullOrEmpty(item.RentalInfo))
{
<p>
@Html.Raw(item.RentalInfo)
</p>
}
@if (!string.IsNullOrEmpty(item.Sku))
{
<p>
<strong>@T("Admin.Orders.Products.SKU")</strong><text>:</text>
@item.Sku
</p>
}
@if (!string.IsNullOrEmpty(item.VendorName))
{
<p>
<strong>@T("Admin.Orders.Products.Vendor")</strong><text>:</text>
@item.VendorName
</p>
}
@if (item.ReturnRequests.Count > 0)
{
<p>
@T("Admin.Orders.Products.ReturnRequests")<text>:</text>
@for (var i = 0; i < item.ReturnRequests.Count; i++)
{
var returnRequest = item.ReturnRequests[i];
<a asp-controller="ReturnRequest" asp-action="Edit" asp-route-id="@returnRequest.Id">@returnRequest.CustomNumber</a>
if (i != item.ReturnRequests.Count - 1)
{
<text>, </text>
}
}
</p>
}
</td>
@if (Model.HasDownloadableProducts)
{
<td style="width: 15%;" class="text-center">
@if (item.IsDownload)
{
<a asp-controller="Download" asp-action="DownloadFile" asp-route-orderItemId="@item.Id">
@T("Admin.Orders.Products.Download.Download")
</a>
}
else
{
@T("Admin.Orders.Products.Download.NotAvailable")
}
</td>
}
<td style="width: 15%;" class="text-center">
@if (Model.AllowCustomersToSelectTaxDisplayType)
{
<div>@Html.Raw(item.UnitPriceInclTax)</div>
<div>@Html.Raw(item.UnitPriceExclTax)</div>
}
else
{
switch (Model.TaxDisplayType)
{
case TaxDisplayType.ExcludingTax:
{
@Html.Raw(item.UnitPriceExclTax)
}
break;
case TaxDisplayType.IncludingTax:
{
@Html.Raw(item.UnitPriceInclTax)
}
break;
default:
break;
}
}
<div id="pnlEditPvUnitPrice@(item.Id)">
<div class="form-group row">
<div class="col-md-5">
@T("Admin.Orders.Products.Edit.InclTax")
</div>
<div class="col-md-7">
<input name="pvUnitPriceInclTax@(item.Id)" type="text" value="@item.UnitPriceInclTaxValue" id="pvUnitPriceInclTax@(item.Id)" class="form-control input-sm" />
</div>
</div>
<div class="form-group row">
<div class="col-md-5">
@T("Admin.Orders.Products.Edit.ExclTax")
</div>
<div class="col-md-7">
<input name="pvUnitPriceExclTax@(item.Id)" type="text" value="@item.UnitPriceExclTaxValue" id="pvUnitPriceExclTax@(item.Id)" class="form-control input-sm" />
</div>
</div>
</div>
</td>
<td style="width: 10%;" class="text-center">
<div>@item.Quantity</div>
<div id="pnlEditPvQuantity@(item.Id)">
<div class="form-group row">
<div class="col-md-8 offset-md-2">
<input name="pvQuantity@(item.Id)" type="text" value="@item.Quantity" id="pvQuantity@(item.Id)" class="form-control input-sm" />
</div>
</div>
</div>
</td>
<td style="width: 15%;" class="text-center">
@if (Model.AllowCustomersToSelectTaxDisplayType)
{
<div>@Html.Raw(item.DiscountInclTax)</div>
<div>@Html.Raw(item.DiscountExclTax)</div>
}
else
{
switch (Model.TaxDisplayType)
{
case TaxDisplayType.ExcludingTax:
{
@Html.Raw(item.DiscountExclTax)
}
break;
case TaxDisplayType.IncludingTax:
{
@Html.Raw(item.DiscountInclTax)
}
break;
default:
break;
}
}
<div id="pnlEditPvDiscount@(item.Id)">
<div class="form-group row">
<div class="col-md-5">
@T("Admin.Orders.Products.Edit.InclTax")
</div>
<div class="col-md-7">
<input name="pvDiscountInclTax@(item.Id)" type="text" value="@item.DiscountInclTaxValue" id="pvDiscountInclTax@(item.Id)" class="form-control input-sm" />
</div>
</div>
<div class="form-group row">
<div class="col-md-5">
@T("Admin.Orders.Products.Edit.ExclTax")
</div>
<div class="col-md-7">
<input name="pvDiscountExclTax@(item.Id)" type="text" value="@item.DiscountExclTaxValue" id="pvDiscountExclTax@(item.Id)" class="form-control input-sm" />
</div>
</div>
</div>
</td>
<td style="width: 15%;" class="text-center">
@if (Model.AllowCustomersToSelectTaxDisplayType)
{
<div>@Html.Raw(item.SubTotalInclTax)</div>
<div>@Html.Raw(item.SubTotalExclTax)</div>
}
else
{
switch (Model.TaxDisplayType)
{
case TaxDisplayType.ExcludingTax:
{
@Html.Raw(item.SubTotalExclTax)
}
break;
case TaxDisplayType.IncludingTax:
{
@Html.Raw(item.SubTotalInclTax)
}
break;
default:
break;
}
}
<div id="pnlEditPvPrice@(item.Id)">
<div class="form-group row">
<div class="col-md-5">
@T("Admin.Orders.Products.Edit.InclTax")
</div>
<div class="col-md-7">
<input name="pvPriceInclTax@(item.Id)" type="text" value="@item.SubTotalInclTaxValue" id="pvPriceInclTax@(item.Id)" class="form-control input-sm" />
</div>
</div>
<div class="form-group row">
<div class="col-md-5">
@T("Admin.Orders.Products.Edit.ExclTax")
</div>
<div class="col-md-7">
<input name="pvPriceExclTax@(item.Id)" type="text" value="@item.SubTotalExclTaxValue" id="pvPriceExclTax@(item.Id)" class="form-control input-sm" />
</div>
</div>
</div>
</td>
@if (!Model.IsLoggedInAsVendor)
{
<td style="width: 15%;" class="text-center">
<button type="submit" class="btn btn-default" name="btnEditOrderItem@(item.Id)" onclick="toggleOrderItemEdit@(item.Id)(true);return false;" id="btnEditOrderItem@(item.Id)">
<i class="fas fa-pencil"></i>
@T("Admin.Common.Edit")
</button>
<button type="submit" class="btn btn-default" name="btnDeleteOrderItem@(item.Id)" id="btnDeleteOrderItem@(item.Id)">
<i class="far fa-trash-can"></i>
@T("Admin.Common.Delete")
</button>
<nop-action-confirmation asp-button-id="@("btnDeleteOrderItem" + item.Id)" />
<button type="submit" class="btn btn-default" name="btnSaveOrderItem@(item.Id)" id="btnSaveOrderItem@(item.Id)">
<i class="far fa-floppy-disk"></i>
@T("Admin.Common.Save")
</button>
<nop-action-confirmation asp-button-id="@("btnSaveOrderItem" + item.Id)" />
<button type="submit" class="btn btn-default" name="btnCancelOrderItem@(item.Id)" onclick="toggleOrderItemEdit@(item.Id)(false);return false;" id="btnCancelOrderItem@(item.Id)">
<i class="fas fa-times"></i>
@T("Admin.Common.Cancel")
</button>
</td>
}
</tr>
}
</tbody>
</table>
</div>
</div>
@if (!string.IsNullOrEmpty(Model.CheckoutAttributeInfo) && !Model.IsLoggedInAsVendor)
{
<div class="form-group row">
<div class="col-md-12">
@Html.Raw(Model.CheckoutAttributeInfo)
</div>
</div>
}
@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", "Order", new { orderId = Model.Id }))'); return false;" class="btn btn-primary">
@T("Admin.Orders.Products.AddNew")
</button>
</div>
</div>
}
</div>

View File

@ -75,3 +75,4 @@
@* @using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Components *@ @* @using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Components *@
@using DevExtreme.AspNet.Mvc @using DevExtreme.AspNet.Mvc
@using Nop.Web.Areas.Admin.Models.Orders;

View File

@ -2,22 +2,24 @@
using AyCode.Core.Extensions; using AyCode.Core.Extensions;
using AyCode.Core.Loggers; using AyCode.Core.Loggers;
using AyCode.Utils.Extensions; using AyCode.Utils.Extensions;
using FruitBank.Common.Dtos;
using FruitBank.Common.Entities; using FruitBank.Common.Entities;
using FruitBank.Common.Interfaces; using FruitBank.Common.Interfaces;
using FruitBank.Common.Models; using FruitBank.Common.Models;
using Mango.Nop.Core.Extensions;
using Mango.Nop.Core.Loggers;
using Mango.Nop.Core.Repositories; using Mango.Nop.Core.Repositories;
using Nop.Core; using Nop.Core;
using Nop.Core.Caching; using Nop.Core.Caching;
using Nop.Core.Domain.Catalog; using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers; using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Events;
using Nop.Data; using Nop.Data;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer.Interfaces; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer.Interfaces;
using Nop.Plugin.Misc.FruitBankPlugin.Services; using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Catalog; using Nop.Services.Catalog;
using FruitBank.Common.Dtos; using Nop.Services.Security;
using Mango.Nop.Core.Extensions;
using Mango.Nop.Core.Loggers;
using Nop.Core.Domain.Orders;
using WebMarkupMin.Core.Loggers; using WebMarkupMin.Core.Loggers;
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
@ -38,6 +40,7 @@ public class FruitBankDbContext : MgDbContextBase,
private readonly IStoreContext _storeContext; private readonly IStoreContext _storeContext;
private readonly IProductService _productService; private readonly IProductService _productService;
private readonly IStaticCacheManager _staticCacheManager; private readonly IStaticCacheManager _staticCacheManager;
protected readonly IEventPublisher _eventPublisher;
public ProductDtoDbTable ProductDtos { get; set; } public ProductDtoDbTable ProductDtos { get; set; }
@ -55,7 +58,6 @@ public class FruitBankDbContext : MgDbContextBase,
public FilesDbTable Files { get; set; } public FilesDbTable Files { get; set; }
public ShippingDocumentToFilesDbTable ShippingDocumentToFiles { get; set; } public ShippingDocumentToFilesDbTable ShippingDocumentToFiles { get; set; }
public IRepository<Product> Products { get; set; }
public IRepository<Customer> Customers { get; set; } public IRepository<Customer> Customers { get; set; }
public IRepository<CustomerRole> CustomerRoles { get; set; } public IRepository<CustomerRole> CustomerRoles { get; set; }
public IRepository<CustomerCustomerRoleMapping> CustomerRoleMappings { get; set; } public IRepository<CustomerCustomerRoleMapping> CustomerRoleMappings { get; set; }
@ -65,12 +67,15 @@ public class FruitBankDbContext : MgDbContextBase,
ShippingItemPalletDbTable shippingItemPalletDbTable, FilesDbTable filesDbTable, ShippingDocumentToFilesDbTable shippingDocumentToFilesDbTable, ShippingItemPalletDbTable shippingItemPalletDbTable, FilesDbTable filesDbTable, ShippingDocumentToFilesDbTable shippingDocumentToFilesDbTable,
ProductDtoDbTable productDtoDbTable, OrderDtoDbTable orderDtoDbTable, OrderItemDtoDbTable orderItemDtoDbTable, OrderItemPalletDbTable orderItemPalletDbTable, ProductDtoDbTable productDtoDbTable, OrderDtoDbTable orderDtoDbTable, OrderItemDtoDbTable orderItemDtoDbTable, OrderItemPalletDbTable orderItemPalletDbTable,
IProductService productService, IStaticCacheManager staticCacheManager, IProductService productService, IStaticCacheManager staticCacheManager,
IRepository<Order> orderRepository,
IRepository<OrderItem> orderItemRepository,
IRepository<Product> productRepository, IRepository<Product> productRepository,
IRepository<Customer> customerRepository, IRepository<Customer> customerRepository,
IRepository<CustomerCustomerRoleMapping> customerCustomerRoleMappingRepository, IRepository<CustomerCustomerRoleMapping> customerCustomerRoleMappingRepository,
IRepository<CustomerRole> customerRoleRepository, IRepository<CustomerRole> customerRoleRepository,IEventPublisher eventPublisher,
IEnumerable<IAcLogWriterBase> logWriters) : base(dataProvider, lockService, new Logger<FruitBankDbContext>(logWriters.ToArray())) IEnumerable<IAcLogWriterBase> logWriters) : base(productRepository, orderRepository, orderItemRepository, dataProvider, lockService, new Logger<FruitBankDbContext>(logWriters.ToArray()))
{ {
_eventPublisher = eventPublisher;
_storeContext = storeContext; _storeContext = storeContext;
_productService = productService; _productService = productService;
_staticCacheManager = staticCacheManager; _staticCacheManager = staticCacheManager;
@ -411,6 +416,8 @@ public class FruitBankDbContext : MgDbContextBase,
if (orderDto == null) return null; if (orderDto == null) return null;
if (!orderDto.IsMeasuredAndValid() || orderDto.OrderStatus == OrderStatus.Complete) return null; //throw new Exception($"SetOrderDtoToComplete; orderDto.IsMeasured == false; {orderDto}"); if (!orderDto.IsMeasuredAndValid() || orderDto.OrderStatus == OrderStatus.Complete) return null; //throw new Exception($"SetOrderDtoToComplete; orderDto.IsMeasured == false; {orderDto}");
var prevOrderStatus = orderDto.OrderStatus;
orderDto.OrderStatus = OrderStatus.Complete; orderDto.OrderStatus = OrderStatus.Complete;
await OrderDtos.UpdateAsync(orderDto); await OrderDtos.UpdateAsync(orderDto);
@ -435,6 +442,9 @@ public class FruitBankDbContext : MgDbContextBase,
(orderItemDto.ProductId, -(orderItemDto.NetWeight-gaNetWeight), orderItemDto.IsMeasurable, true); (orderItemDto.ProductId, -(orderItemDto.NetWeight-gaNetWeight), orderItemDto.IsMeasurable, true);
} }
var order = Orders.GetById(orderDto.Id);
await _eventPublisher.PublishAsync(new OrderStatusChangedEvent(order, prevOrderStatus));
return orderDto; return orderDto;
} }

View File

@ -33,5 +33,8 @@ public class OrderDtoDbTable : MgDtoDbTableBase<OrderDto, Order>
public IQueryable<OrderDto> GetAllByOrderStatus(OrderStatus orderStatus, bool loadRelations = true) public IQueryable<OrderDto> GetAllByOrderStatus(OrderStatus orderStatus, bool loadRelations = true)
=> GetAll(loadRelations).Where(o => o.OrderStatusId == (int)orderStatus); => GetAll(loadRelations).Where(o => o.OrderStatusId == (int)orderStatus);
public IQueryable<OrderDto> GetAllForMeasuring(bool loadRelations = true)
=> GetAllByOrderStatus(OrderStatus.Pending, loadRelations).Where(o => o.DateOfReceipt != null);
public IQueryable<OrderDto> GetAllByIds(IEnumerable<int> orderIds, bool loadRelations = true) => GetAll(loadRelations).Where(o => orderIds.Contains(o.Id)); public IQueryable<OrderDto> GetAllByIds(IEnumerable<int> orderIds, bool loadRelations = true) => GetAll(loadRelations).Where(o => orderIds.Contains(o.Id));
} }

View File

@ -10,11 +10,12 @@ using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Services; using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Events; using Nop.Services.Events;
using Mango.Nop.Core.Extensions; using Mango.Nop.Core.Extensions;
using Nop.Core.Domain.Orders;
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.EventConsumers; namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.EventConsumers;
public class FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBankDbContext ctx, FruitBankAttributeService fruitBankAttributeService, IEnumerable<IAcLogWriterBase> logWriters) : public class FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBankDbContext ctx, FruitBankAttributeService fruitBankAttributeService, IEnumerable<IAcLogWriterBase> logWriters) :
MgEventConsumer(httpContextAcc, logWriters), MgEventConsumerBase(ctx, httpContextAcc, logWriters),
IConsumer<EntityDeletedEvent<Shipping>>, IConsumer<EntityDeletedEvent<Shipping>>,
IConsumer<EntityInsertedEvent<ShippingItem>>, IConsumer<EntityInsertedEvent<ShippingItem>>,
IConsumer<EntityUpdatedEvent<ShippingItem>>, IConsumer<EntityUpdatedEvent<ShippingItem>>,
@ -24,13 +25,16 @@ public class FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBa
IConsumer<EntityDeletedEvent<ShippingDocument>>, IConsumer<EntityDeletedEvent<ShippingDocument>>,
IConsumer<EntityInsertedEvent<ShippingItemPallet>>, IConsumer<EntityInsertedEvent<ShippingItemPallet>>,
IConsumer<EntityUpdatedEvent<ShippingItemPallet>>, IConsumer<EntityUpdatedEvent<ShippingItemPallet>>,
IConsumer<EntityDeletedEvent<ShippingItemPallet>> IConsumer<EntityDeletedEvent<ShippingItemPallet>>,
IConsumer<EntityInsertedEvent<OrderItem>>,
IConsumer<EntityUpdatedEvent<OrderItem>>
{ {
public override async Task HandleEventAsync(EntityUpdatedEvent<Product> eventMessage) public override async Task HandleEventAsync(EntityUpdatedEvent<Product> eventMessage)
{ {
var product = eventMessage.Entity; var product = await CheckAndUpdateProductManageInventoryMethodToManageStock(eventMessage.Entity);
var saveProductCustomAttributesResult = await SaveProductCustomAttributesAsync(eventMessage.Entity); var saveProductCustomAttributesResult = await SaveProductCustomAttributesAsync(product);
//var isMeasurableProduct = await fruitBankAttributeService.IsMeasurableEntityAsync<Product>(product.Id); //var isMeasurableProduct = await fruitBankAttributeService.IsMeasurableEntityAsync<Product>(product.Id);
@ -52,7 +56,9 @@ public class FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBa
public override async Task HandleEventAsync(EntityInsertedEvent<Product> eventMessage) public override async Task HandleEventAsync(EntityInsertedEvent<Product> eventMessage)
{ {
await SaveProductCustomAttributesAsync(eventMessage.Entity); //TODO: ez ide miért kell? - J. var product = await CheckAndUpdateProductManageInventoryMethodToManageStock(eventMessage.Entity);
await SaveProductCustomAttributesAsync(product); //TODO: ez ide miért kell? - J.
await base.HandleEventAsync(eventMessage); await base.HandleEventAsync(eventMessage);
} }
@ -124,7 +130,7 @@ public class FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBa
{ {
return; return;
Logger.Info($"HandleEventAsync EntityInsertedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}"); Logger.Info($"HandleEventAsync->EntityInsertedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}");
await UpdateShippingItemMeasuringValuesAsync(eventMessage.Entity); await UpdateShippingItemMeasuringValuesAsync(eventMessage.Entity);
} }
@ -132,13 +138,13 @@ public class FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBa
{ {
return; return;
Logger.Info($"HandleEventAsync EntityUpdatedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}"); Logger.Info($"HandleEventAsync->EntityUpdatedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}");
await UpdateShippingItemMeasuringValuesAsync(eventMessage.Entity); await UpdateShippingItemMeasuringValuesAsync(eventMessage.Entity);
} }
public async Task HandleEventAsync(EntityDeletedEvent<ShippingItemPallet> eventMessage) public async Task HandleEventAsync(EntityDeletedEvent<ShippingItemPallet> eventMessage)
{ {
Logger.Info($"HandleEventAsync EntityDeletedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}"); Logger.Info($"HandleEventAsync->EntityDeletedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}");
await UpdateShippingItemMeasuringValuesAsync(eventMessage.Entity); await UpdateShippingItemMeasuringValuesAsync(eventMessage.Entity);
} }
@ -151,7 +157,7 @@ public class FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBa
public async Task HandleEventAsync(EntityInsertedEvent<ShippingItem> eventMessage) public async Task HandleEventAsync(EntityInsertedEvent<ShippingItem> eventMessage)
{ {
Logger.Info($"HandleEventAsync EntityInsertedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}"); Logger.Info($"HandleEventAsync->EntityInsertedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}");
await UpdateShippingDocumentIsAllMeasuredAsync(eventMessage.Entity); await UpdateShippingDocumentIsAllMeasuredAsync(eventMessage.Entity);
} }
@ -160,7 +166,7 @@ public class FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBa
public async Task HandleEventAsync(EntityUpdatedEvent<ShippingItem> eventMessage) public async Task HandleEventAsync(EntityUpdatedEvent<ShippingItem> eventMessage)
{ {
Logger.Info($"HandleEventAsync EntityUpdatedEvent<ShippingItem>; id: {eventMessage.Entity.Id}"); Logger.Info($"HandleEventAsync->EntityUpdatedEvent<ShippingItem>; id: {eventMessage.Entity.Id}");
var shippingItem = eventMessage.Entity; var shippingItem = eventMessage.Entity;
//var isMeasured = shippingItem.IsValidMeasuringValues(); //var isMeasured = shippingItem.IsValidMeasuringValues();
@ -189,14 +195,14 @@ public class FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBa
public async Task HandleEventAsync(EntityInsertedEvent<ShippingDocument> eventMessage) public async Task HandleEventAsync(EntityInsertedEvent<ShippingDocument> eventMessage)
{ {
Logger.Info($"HandleEventAsync EntityInsertedEvent<ShippingDocument>; id: {eventMessage.Entity.Id}"); Logger.Info($"HandleEventAsync->EntityInsertedEvent<ShippingDocument>; id: {eventMessage.Entity.Id}");
await UpdateShippingIsAllMeasuredAsync(eventMessage.Entity); await UpdateShippingIsAllMeasuredAsync(eventMessage.Entity);
} }
public async Task HandleEventAsync(EntityUpdatedEvent<ShippingDocument> eventMessage) public async Task HandleEventAsync(EntityUpdatedEvent<ShippingDocument> eventMessage)
{ {
Logger.Info($"HandleEventAsync EntityUpdatedEvent<ShippingDocument>; id: {eventMessage.Entity.Id}"); Logger.Info($"HandleEventAsync->EntityUpdatedEvent<ShippingDocument>; id: {eventMessage.Entity.Id}");
await UpdateShippingIsAllMeasuredAsync(eventMessage.Entity); await UpdateShippingIsAllMeasuredAsync(eventMessage.Entity);
} }
@ -219,24 +225,48 @@ public class FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBa
public async Task HandleEventAsync(EntityDeletedEvent<Shipping> eventMessage) public async Task HandleEventAsync(EntityDeletedEvent<Shipping> eventMessage)
{ {
Logger.Info($"HandleEventAsync EntityDeletedEvent<Shipping>; id: {eventMessage.Entity.Id}"); Logger.Info($"HandleEventAsync->EntityDeletedEvent<Shipping>; id: {eventMessage.Entity.Id}");
await ctx.ShippingDocuments.DeleteAsync(sd => sd.ShippingId == eventMessage.Entity.Id, true); await ctx.ShippingDocuments.DeleteAsync(sd => sd.ShippingId == eventMessage.Entity.Id, true);
} }
public async Task HandleEventAsync(EntityDeletedEvent<ShippingDocument> eventMessage) public async Task HandleEventAsync(EntityDeletedEvent<ShippingDocument> eventMessage)
{ {
Logger.Info($"HandleEventAsync EntityDeletedEvent<ShippingDocument>; id: {eventMessage.Entity.Id}"); Logger.Info($"HandleEventAsync->EntityDeletedEvent<ShippingDocument>; id: {eventMessage.Entity.Id}");
await ctx.ShippingItems.DeleteAsync(si => si.ShippingDocumentId == eventMessage.Entity.Id, true); await ctx.ShippingItems.DeleteAsync(si => si.ShippingDocumentId == eventMessage.Entity.Id, true);
} }
public async Task HandleEventAsync(EntityDeletedEvent<ShippingItem> eventMessage) public async Task HandleEventAsync(EntityDeletedEvent<ShippingItem> eventMessage)
{ {
Logger.Info($"HandleEventAsync EntityDeletedEvent<ShippingItem>; id: {eventMessage.Entity.Id}"); Logger.Info($"HandleEventAsync->EntityDeletedEvent<ShippingItem>; id: {eventMessage.Entity.Id}");
await ctx.ShippingItemPallets.DeleteAsync(sp => sp.ShippingItemId == eventMessage.Entity.Id, false); await ctx.ShippingItemPallets.DeleteAsync(sp => sp.ShippingItemId == eventMessage.Entity.Id, false);
} }
public async Task HandleEventAsync(EntityUpdatedEvent<OrderItem> eventMessage) => await CheckAndUpdateOrderItemFinalPrices(eventMessage.Entity);
public async Task HandleEventAsync(EntityInsertedEvent<OrderItem> eventMessage) => await CheckAndUpdateOrderItemFinalPrices(eventMessage.Entity);
private async Task CheckAndUpdateOrderItemFinalPrices(OrderItem orderItem)
{
Logger.Info($"HandleEventAsync->CheckAndUpdateOrderItemFinalPrices; orderItem.Id: {orderItem.Id}");
var finalPriceInclTax = decimal.Round(orderItem.UnitPriceInclTax * orderItem.Quantity, 0);
var finalPriceExclTax = decimal.Round(orderItem.UnitPriceExclTax * orderItem.Quantity, 0);
if (orderItem.PriceInclTax != finalPriceInclTax || orderItem.PriceExclTax != finalPriceExclTax)
{
Logger.Error($"HandleEventAsync->CheckAndUpdateOrderItemFinalPrices; " +
$"orderItem.PriceInclTax({orderItem.PriceInclTax}) != finalPriceInclTax({finalPriceInclTax}) || " +
$"orderItem.PriceExclTax({orderItem.PriceExclTax}) != finalPriceExclTax({finalPriceExclTax})");
orderItem.PriceInclTax = finalPriceInclTax;
orderItem.PriceExclTax = finalPriceExclTax;
await ctx.OrderItems.UpdateAsync(orderItem, false);
}
}
#endregion Delete #endregion Delete
} }

View File

@ -176,7 +176,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
//orderModelExtended.IsMeasurable = await ShouldMarkAsNeedsMeasurementAsync(orderModel); //orderModelExtended.IsMeasurable = await ShouldMarkAsNeedsMeasurementAsync(orderModel);
//orderModelExtended.DateOfReceipt = await GetPickupDateTimeAsync(orderModel); //orderModelExtended.DateOfReceipt = await GetPickupDateTimeAsync(orderModel);
Console.WriteLine(orderModelExtended.Id); //Console.WriteLine(orderModelExtended.Id);
}); });
return orderListModelExtended; return orderListModelExtended;

View File

@ -9,6 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="Areas\Admin\Views\Order\Edit.cshtml" />
<None Remove="logo.jpg" /> <None Remove="logo.jpg" />
<None Remove="plugin.json" /> <None Remove="plugin.json" />
<None Remove="Views\_ViewImports.cshtml" /> <None Remove="Views\_ViewImports.cshtml" />
@ -40,6 +41,16 @@
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Areas\Admin\Views\Order\Edit.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Areas\Admin\Views\Order\_CustomOrderDetails.Products.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Areas\Admin\Views\Product\List.cshtml"> <Content Include="Areas\Admin\Views\Product\List.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile> <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,18 @@
using System.Text.Json; using AyCode.Core.Loggers;
using System.Text; using Mango.Nop.Core.Loggers;
using Microsoft.Extensions.Configuration; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Models; using Nop.Plugin.Misc.FruitBankPlugin.Models;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Configuration; using Nop.Services.Configuration;
using System.Text;
using System.Text.Json;
#nullable enable
namespace Nop.Plugin.Misc.FruitBankPlugin.Services namespace Nop.Plugin.Misc.FruitBankPlugin.Services
{ {
public class CerebrasAPIService : IAIAPIService public class CerebrasAPIService : IAIAPIService
{ {
private readonly ILogger _logger;
private readonly ISettingService _settingService; private readonly ISettingService _settingService;
private readonly FruitBankSettings _fruitBankSettings; private readonly FruitBankSettings _fruitBankSettings;
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
@ -18,23 +22,20 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
private const string CerebrasEndpoint = "https://api.cerebras.ai/v1/chat/completions"; private const string CerebrasEndpoint = "https://api.cerebras.ai/v1/chat/completions";
public CerebrasAPIService(ISettingService settingService, HttpClient httpClient) public CerebrasAPIService(ISettingService settingService, HttpClient httpClient, IEnumerable<IAcLogWriterBase> logWriters)
{ {
_logger = new Logger<CerebrasAPIService>(logWriters.ToArray());
_settingService = settingService; _settingService = settingService;
_fruitBankSettings = _settingService.LoadSetting<FruitBankSettings>(); _fruitBankSettings = _settingService.LoadSetting<FruitBankSettings>();
_httpClient = httpClient; _httpClient = httpClient;
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {GetApiKey()}"); _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {GetApiKey()}");
} }
public string GetApiKey() => public string GetApiKey() => _fruitBankSettings.ApiKey; //_configuration?.GetSection("Cerebras")?.GetValue<string>("ApiKey") ?? string.Empty;
_fruitBankSettings.ApiKey;
//_configuration?.GetSection("Cerebras")?.GetValue<string>("ApiKey") ?? string.Empty;
public string GetModelName() => _fruitBankSettings.ModelName; //_configuration?.GetSection("Cerebras")?.GetValue<string>("Model") ?? string.Empty;
public string GetModelName() =>
_fruitBankSettings.ModelName;
//_configuration?.GetSection("Cerebras")?.GetValue<string>("Model") ?? string.Empty;
public void RegisterCallback(Action<string, string> callback, Action<string> onCompleteCallback, Action<string, string> onErrorCallback) public void RegisterCallback(Action<string, string> callback, Action<string> onCompleteCallback, Action<string, string> onErrorCallback)
{ {
@ -44,27 +45,26 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
} }
public async Task<string> GetSimpleResponseAsync(string systemMessage, string userMessage, string? assistantMessage = null) public async Task<string?> GetSimpleResponseAsync(string systemMessage, string userMessage, string? assistantMessage = null)
{ {
var modelName = GetModelName();
string modelName = GetModelName();
var requestBody = new CerebrasAIChatRequest var requestBody = new CerebrasAIChatRequest
{ {
Model = modelName, Model = modelName,
Temperature = 0.2, Temperature = 0.2,
Messages = assistantMessage == null || assistantMessage == string.Empty Messages = string.IsNullOrEmpty(assistantMessage)
? new[] ?
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
} ]
: new[] :
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "assistant", Content = assistantMessage }, new AIChatMessage { Role = "assistant", Content = assistantMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
}, ],
Stream = false Stream = false
}; };
@ -78,12 +78,13 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
using var response = await _httpClient.PostAsync(CerebrasEndpoint, requestContent); using var response = await _httpClient.PostAsync(CerebrasEndpoint, requestContent);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync(); await using var responseStream = await response.Content.ReadAsStreamAsync();
using var document = await JsonDocument.ParseAsync(responseStream); using var document = await JsonDocument.ParseAsync(responseStream);
var inputTokens = document.RootElement.GetProperty("usage").GetProperty("prompt_tokens").GetInt32(); var inputTokens = document.RootElement.GetProperty("usage").GetProperty("prompt_tokens").GetInt32();
var outputTokens = document.RootElement.GetProperty("usage").GetProperty("completion_tokens").GetInt32(); var outputTokens = document.RootElement.GetProperty("usage").GetProperty("completion_tokens").GetInt32();
var sum = inputTokens + outputTokens; var sum = inputTokens + outputTokens;
Console.WriteLine($"USAGE STATS - Tokens: {inputTokens.ToString()} + {outputTokens.ToString()} = {sum.ToString()}");
_logger.Info($"USAGE STATS - Tokens: {inputTokens.ToString()} + {outputTokens.ToString()} = {sum.ToString()}");
return document.RootElement return document.RootElement
.GetProperty("choices")[0] .GetProperty("choices")[0]
@ -95,24 +96,24 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
public async Task<string> GetStreamedResponseAsync(string sessionId, string systemMessage, string userMessage, string? assistantMessage = null) public async Task<string> GetStreamedResponseAsync(string sessionId, string systemMessage, string userMessage, string? assistantMessage = null)
{ {
string modelName = GetModelName(); var modelName = GetModelName();
var requestBody = new CerebrasAIChatRequest var requestBody = new CerebrasAIChatRequest
{ {
Model = modelName, Model = modelName,
Temperature = 0.2, Temperature = 0.2,
Messages = assistantMessage == null Messages = assistantMessage == null
? new[] ?
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
} ]
: new[] :
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "assistant", Content = assistantMessage }, new AIChatMessage { Role = "assistant", Content = assistantMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
}, ],
Stream = true Stream = true
}; };
@ -122,16 +123,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
}); });
var requestContent = new StringContent(requestJson, Encoding.UTF8, "application/json"); var requestContent = new StringContent(requestJson, Encoding.UTF8, "application/json");
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, CerebrasEndpoint) using var httpRequest = new HttpRequestMessage(HttpMethod.Post, CerebrasEndpoint);
{ httpRequest.Content = requestContent;
Content = requestContent
};
using var response = await _httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead); using var response = await _httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var stringBuilder = new StringBuilder(); var stringBuilder = new StringBuilder();
using var responseStream = await response.Content.ReadAsStreamAsync(); await using var responseStream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(responseStream); using var reader = new StreamReader(responseStream);
try try

View File

@ -53,8 +53,6 @@ public class CustomPriceCalculationService : PriceCalculationService
} }
//decimal? overriddenProductPrice = null
public override async Task<(decimal priceWithoutDiscounts, decimal finalPrice, decimal appliedDiscountAmount, List<Discount> appliedDiscounts)> GetFinalPriceAsync( public override async Task<(decimal priceWithoutDiscounts, decimal finalPrice, decimal appliedDiscountAmount, List<Discount> appliedDiscounts)> GetFinalPriceAsync(
Product product, Customer customer, Store store, decimal? overriddenProductPrice, decimal additionalCharge = 0, bool includeDiscounts = true, Product product, Customer customer, Store store, decimal? overriddenProductPrice, decimal additionalCharge = 0, bool includeDiscounts = true,
int quantity = 1, DateTime? rentalStartDate = null, DateTime? rentalEndDate = null) int quantity = 1, DateTime? rentalStartDate = null, DateTime? rentalEndDate = null)

View File

@ -2,7 +2,7 @@
namespace Nop.Plugin.Misc.FruitBankPlugin.Services; namespace Nop.Plugin.Misc.FruitBankPlugin.Services;
public class LockService : MgLockService, ILockService public class LockService : MgLockServiceBase, ILockService
{ {
public LockService() : this(new SemaphoreSlim(1)) public LockService() : this(new SemaphoreSlim(1))
{} {}

View File

@ -1,12 +1,11 @@
using Microsoft.Extensions.Configuration; using Nop.Plugin.Misc.FruitBankPlugin.Models;
using Nop.Plugin.Misc.FruitBankPlugin.Models;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Configuration; using Nop.Services.Configuration;
using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using AyCode.Utils.Extensions;
#nullable enable
namespace Nop.Plugin.Misc.FruitBankPlugin.Services namespace Nop.Plugin.Misc.FruitBankPlugin.Services
{ {
public class OpenAIApiService : IAIAPIService public class OpenAIApiService : IAIAPIService
@ -45,29 +44,30 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
} }
#region === CHAT (TEXT INPUT) === #region === CHAT (TEXT INPUT) ===
public async Task<string?> GetSimpleResponseAsync(string systemMessage, string userMessage, string? assistantMessage = null) public async Task<string?> GetSimpleResponseAsync(string systemMessage, string userMessage, string? assistantMessage = null)
{ {
string modelName = GetModelName(); var modelName = GetModelName();
StringContent requestContent = new(""); StringContent requestContent;
if (modelName == "gpt-4.1-mini" || modelName == "gpt-4o-mini" || modelName == "gpt-4.1-nano" || modelName == "gpt-5-nano") if (modelName is "gpt-4.1-mini" or "gpt-4o-mini" or "gpt-4.1-nano" or "gpt-5-nano")
{ {
var requestBody = new OpenAIGpt4MiniAIChatRequest var requestBody = new OpenAIGpt4MiniAIChatRequest
{ {
Model = modelName, Model = modelName,
Temperature = 0.2, Temperature = 0.2,
Messages = assistantMessage == null || assistantMessage == string.Empty Messages = string.IsNullOrEmpty(assistantMessage)
? new[] ?
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
} ]
: new[] :
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "assistant", Content = assistantMessage }, new AIChatMessage { Role = "assistant", Content = assistantMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
}, ],
Stream = false Stream = false
}; };
@ -84,18 +84,18 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
{ {
Model = modelName, Model = modelName,
Temperature = 1, Temperature = 1,
Messages = assistantMessage == null || assistantMessage == string.Empty Messages = string.IsNullOrEmpty(assistantMessage)
? new[] ?
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
} ]
: new[] :
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "assistant", Content = assistantMessage }, new AIChatMessage { Role = "assistant", Content = assistantMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
}, ],
ReasoningEffort = "minimal", ReasoningEffort = "minimal",
Verbosity = "high", Verbosity = "high",
Stream = false Stream = false
@ -134,12 +134,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
return null; return null;
} }
} }
#endregion #endregion
#region === CHAT (STREAMING) === #region === CHAT (STREAMING) ===
public async Task<string> GetStreamedResponseAsync(string sessionId, string systemMessage, string userMessage, string? assistantMessage = null) public async Task<string> GetStreamedResponseAsync(string sessionId, string systemMessage, string userMessage, string? assistantMessage = null)
{ {
string modelName = GetModelName(); var modelName = GetModelName();
StringContent requestContent = new(""); StringContent requestContent = new("");
if (modelName == "gpt-4.1-mini" || modelName == "gpt-4o-mini" || modelName == "gpt-4.1-nano" || modelName == "gpt-5-nano") if (modelName == "gpt-4.1-mini" || modelName == "gpt-4o-mini" || modelName == "gpt-4.1-nano" || modelName == "gpt-5-nano")
@ -148,18 +150,18 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
{ {
Model = modelName, Model = modelName,
Temperature = 0.2, Temperature = 0.2,
Messages = assistantMessage == null || assistantMessage == string.Empty Messages = string.IsNullOrEmpty(assistantMessage)
? new[] ?
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
} ]
: new[] :
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "assistant", Content = assistantMessage }, new AIChatMessage { Role = "assistant", Content = assistantMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
}, ],
Stream = true Stream = true
}; };
@ -176,18 +178,18 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
{ {
Model = modelName, Model = modelName,
Temperature = 1, Temperature = 1,
Messages = assistantMessage == null || assistantMessage == string.Empty Messages = string.IsNullOrEmpty(assistantMessage)
? new[] ?
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
} ]
: new[] :
{ [
new AIChatMessage { Role = "system", Content = systemMessage }, new AIChatMessage { Role = "system", Content = systemMessage },
new AIChatMessage { Role = "assistant", Content = assistantMessage }, new AIChatMessage { Role = "assistant", Content = assistantMessage },
new AIChatMessage { Role = "user", Content = userMessage } new AIChatMessage { Role = "user", Content = userMessage }
}, ],
Stream = true Stream = true
}; };
@ -200,7 +202,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
} }
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, OpenAiEndpoint); using var httpRequest = new HttpRequestMessage(HttpMethod.Post, OpenAiEndpoint);
httpRequest.Content = requestContent; httpRequest.Content = new StringContent("");
using var response = await _httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead); using var response = await _httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
@ -216,7 +218,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
var line = await reader.ReadLineAsync(); var line = await reader.ReadLineAsync();
if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data: ")) continue; if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data: ")) continue;
var jsonResponse = line.Substring(6); var jsonResponse = line[6..];
if (jsonResponse == "[DONE]") if (jsonResponse == "[DONE]")
{ {
@ -259,9 +261,11 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
return stringBuilder.ToString(); return stringBuilder.ToString();
} }
#endregion #endregion
#region === IMAGE GENERATION === #region === IMAGE GENERATION ===
public async Task<string?> GenerateImageAsync(string prompt) public async Task<string?> GenerateImageAsync(string prompt)
{ {
var request = new HttpRequestMessage(HttpMethod.Post, OpenAiImageEndpoint); var request = new HttpRequestMessage(HttpMethod.Post, OpenAiImageEndpoint);
@ -294,6 +298,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
return $"data:image/png;base64,{base64Image}"; return $"data:image/png;base64,{base64Image}";
} }
#endregion #endregion
#region === PDF ANALYSIS (NEW) === #region === PDF ANALYSIS (NEW) ===
@ -301,16 +306,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
private async Task EnsureAssistantAndVectorStoreAsync() private async Task EnsureAssistantAndVectorStoreAsync()
{ {
// Find or create vector store // Find or create vector store
if (_vectorStoreId == null) _vectorStoreId ??= await FindOrCreateVectorStoreAsync("pdf-analysis-store");
{
_vectorStoreId = await FindOrCreateVectorStoreAsync("pdf-analysis-store");
}
// Find or create assistant // Find or create assistant
if (_assistantId == null) _assistantId ??= await FindOrCreateAssistantAsync("PDF and Image Analyzer Assistant");
{
_assistantId = await FindOrCreateAssistantAsync("PDF and Image Analyzer Assistant");
}
} }
//TEMPORARY: Cleanup all assistants (for testing purposes) - A. //TEMPORARY: Cleanup all assistants (for testing purposes) - A.
@ -375,7 +374,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
return await GetAssistantResponseAsync(threadId); return await GetAssistantResponseAsync(threadId);
} }
private bool IsImageFile(string fileName) private static bool IsImageFile(string fileName)
{ {
var extension = Path.GetExtension(fileName).ToLowerInvariant(); var extension = Path.GetExtension(fileName).ToLowerInvariant();
return extension == ".jpg" || extension == ".jpeg" || extension == ".png" || extension == ".gif" || extension == ".webp"; return extension == ".jpg" || extension == ".jpeg" || extension == ".png" || extension == ".gif" || extension == ".webp";
@ -507,7 +506,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
{ {
const int pollIntervalMs = 1000; const int pollIntervalMs = 1000;
const int maxAttempts = 60; // 1 minute timeout const int maxAttempts = 60; // 1 minute timeout
int attempts = 0; var attempts = 0;
while (attempts < maxAttempts) while (attempts < maxAttempts)
{ {
@ -560,7 +559,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
return firstMessage ?? "No response"; return firstMessage ?? "No response";
} }
private HttpRequestMessage CreateAssistantRequest(HttpMethod method, string url, object? body = null) private static HttpRequestMessage CreateAssistantRequest(HttpMethod method, string url, object? body = null)
{ {
var request = new HttpRequestMessage(method, url); var request = new HttpRequestMessage(method, url);
request.Headers.Add("OpenAI-Beta", "assistants=v2"); request.Headers.Add("OpenAI-Beta", "assistants=v2");
@ -577,7 +576,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
return request; return request;
} }
private async Task EnsureSuccessAsync(HttpResponseMessage response, string operation) private static async Task EnsureSuccessAsync(HttpResponseMessage response, string operation)
{ {
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
@ -588,8 +587,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
} }
} }
private async Task<string> FindOrCreateVectorStoreAsync(string name) private async Task<string> FindOrCreateVectorStoreAsync(string name)
{ {
// List existing vector stores // List existing vector stores
@ -602,14 +599,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
using var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); using var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
var stores = json.RootElement.GetProperty("data"); var stores = json.RootElement.GetProperty("data");
foreach (var store in stores.EnumerateArray()) //var getString = stores.EnumerateArray().FirstOrDefault(store => store.GetProperty("name").GetString() == name)?.GetProperty("id").GetString();
{ //if (!getString.IsNullOrWhiteSpace()) return getString;
if (store.GetProperty("name").GetString() == name)
foreach (var store in stores.EnumerateArray().Where(store => store.GetProperty("name").GetString() == name))
{ {
return store.GetProperty("id").GetString()!; return store.GetProperty("id").GetString()!;
} }
} }
}
// Create new if not found // Create new if not found
var createBody = new { name = name }; var createBody = new { name = name };
@ -633,14 +630,11 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
using var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); using var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
var assistants = json.RootElement.GetProperty("data"); var assistants = json.RootElement.GetProperty("data");
foreach (var assistant in assistants.EnumerateArray()) foreach (var assistant in assistants.EnumerateArray().Where(assistant => assistant.GetProperty("name").GetString() == name))
{
if (assistant.GetProperty("name").GetString() == name)
{ {
return assistant.GetProperty("id").GetString()!; return assistant.GetProperty("id").GetString()!;
} }
} }
}
// Create new if not found // Create new if not found
var assistantBody = new var assistantBody = new
@ -660,8 +654,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
} }
#endregion #endregion
} }
} }