diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomDashboardController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomDashboardController.cs new file mode 100644 index 0000000..eea1d99 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomDashboardController.cs @@ -0,0 +1,150 @@ +using ExCSS; +using Microsoft.AspNetCore.Mvc; +using Nop.Core; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Services.Common; +using Nop.Services.Configuration; +using Nop.Services.Localization; +using Nop.Services.Messages; +using Nop.Services.Security; +using Nop.Web.Areas.Admin.Controllers; +using Nop.Web.Areas.Admin.Factories; +using Nop.Web.Areas.Admin.Models.Common; +using Nop.Web.Areas.Admin.Models.Home; +using Nop.Web.Framework.Models.DataTables; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers +{ + public partial class CustomDashboardController : BaseAdminController + { + #region Fields + + protected readonly AdminAreaSettings _adminAreaSettings; + protected readonly ICommonModelFactory _commonModelFactory; + protected readonly IHomeModelFactory _homeModelFactory; + protected readonly ILocalizationService _localizationService; + protected readonly INotificationService _notificationService; + protected readonly IPermissionService _permissionService; + protected readonly ISettingService _settingService; + protected readonly IGenericAttributeService _genericAttributeService; + protected readonly IWorkContext _workContext; + + #endregion + + #region Ctor + + public CustomDashboardController(AdminAreaSettings adminAreaSettings, + ICommonModelFactory commonModelFactory, + IHomeModelFactory homeModelFactory, + ILocalizationService localizationService, + INotificationService notificationService, + IPermissionService permissionService, + ISettingService settingService, + IGenericAttributeService genericAttributeService, + IWorkContext workContext) + { + _adminAreaSettings = adminAreaSettings; + _commonModelFactory = commonModelFactory; + _homeModelFactory = homeModelFactory; + _localizationService = localizationService; + _notificationService = notificationService; + _permissionService = permissionService; + _settingService = settingService; + _workContext = workContext; + _genericAttributeService = genericAttributeService; + } + + #endregion + + #region Methods + + public virtual async Task Index() + { + //display a warning to a store owner if there are some error + var customer = await _workContext.GetCurrentCustomerAsync(); + var hideCard = await _genericAttributeService.GetAttributeAsync(customer, NopCustomerDefaults.HideConfigurationStepsAttribute); + var closeCard = await _genericAttributeService.GetAttributeAsync(customer, NopCustomerDefaults.CloseConfigurationStepsAttribute); + + if ((hideCard || closeCard) && await _permissionService.AuthorizeAsync(StandardPermission.System.MANAGE_MAINTENANCE)) + { + var warnings = await _commonModelFactory.PrepareSystemWarningModelsAsync(); + if (warnings.Any(warning => warning.Level == SystemWarningLevel.Fail || warning.Level == SystemWarningLevel.Warning)) + { + var locale = await _localizationService.GetResourceAsync("Admin.System.Warnings.Errors"); + _notificationService.WarningNotification(string.Format(locale, Url.Action("Warnings", "Common")), false); //do not encode URLs + } + } + + //progress of localization + var currentLanguage = await _workContext.GetWorkingLanguageAsync(); + var progress = await _genericAttributeService.GetAttributeAsync(currentLanguage, NopCommonDefaults.LanguagePackProgressAttribute); + if (!string.IsNullOrEmpty(progress)) + { + var locale = await _localizationService.GetResourceAsync("Admin.Configuration.LanguagePackProgressMessage"); + _notificationService.SuccessNotification(string.Format(locale, progress, NopLinksDefaults.OfficialSite.Translations), false); + await _genericAttributeService.SaveAttributeAsync(currentLanguage, NopCommonDefaults.LanguagePackProgressAttribute, string.Empty); + } + + //prepare model + var model = await _homeModelFactory.PrepareDashboardModelAsync(new DashboardModel()); + + //return View(model); + return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Index.cshtml", model); + } + + [HttpPost] + public virtual async Task NopCommerceNewsHideAdv() + { + _adminAreaSettings.HideAdvertisementsOnAdminArea = !_adminAreaSettings.HideAdvertisementsOnAdminArea; + await _settingService.SaveSettingAsync(_adminAreaSettings); + + return Content("Setting changed"); + } + + public virtual async Task GetPopularSearchTerm() + { + var model = new DataTablesModel(); + model = await _homeModelFactory.PreparePopularSearchTermReportModelAsync(model); + return PartialView("Table", model); + } + + public virtual async Task GetBestsellersBriefReportByAmount() + { + var model = new DataTablesModel(); + model = await _homeModelFactory.PrepareBestsellersBriefReportByAmountModelAsync(model); + return PartialView("Table", model); + } + + public virtual async Task GetBestsellersBriefReportByQuantity() + { + var model = new DataTablesModel(); + model = await _homeModelFactory.PrepareBestsellersBriefReportByQuantityModelAsync(model); + return PartialView("Table", model); + } + + public virtual async Task GetLatestOrders() + { + var model = new DataTablesModel(); + model = await _homeModelFactory.PrepareLatestOrdersModelAsync(model); + return PartialView("Table", model); + } + + public virtual async Task GetOrderIncomplete() + { + var model = new DataTablesModel(); + model = await _homeModelFactory.PrepareOrderIncompleteModelAsync(model); + return PartialView("Table", model); + } + + public virtual async Task GetOrderAverage() + { + var model = new DataTablesModel(); + model = await _homeModelFactory.PrepareOrderAverageModelAsync(model); + return PartialView("Table", model); + } + + #endregion + } +} + diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs new file mode 100644 index 0000000..ba30f4f --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Mvc; +using Nop.Services.Orders; +using Nop.Web.Areas.Admin.Controllers; +using Nop.Web.Framework.Mvc.Filters; +using Nop.Web.Framework; +using Nop.Web.Areas.Admin.Models.Orders; +using Nop.Services.Security; +using Nop.Plugin.Misc.FruitBankPlugin.Factories; +using Nop.Web.Areas.Admin.Factories; +using Nop.Plugin.Misc.FruitBankPlugin.Models; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers +{ + [Area(AreaNames.ADMIN)] + [AuthorizeAdmin] + public class CustomOrderController : BaseAdminController + { + private readonly IOrderService _orderService; + private readonly IOrderModelFactory _orderModelFactory; + private readonly IPermissionService _permissionService; + // ... other dependencies + + public CustomOrderController(IOrderService orderService, IOrderModelFactory orderModelFactory, IPermissionService permissionService) + { + _orderService = orderService; + _orderModelFactory = orderModelFactory; + _permissionService = permissionService; + // ... initialize other deps + } + + [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)] + public virtual async Task List(List orderStatuses = null, List paymentStatuses = null, List shippingStatuses = null) + { + //prepare model + var model = await _orderModelFactory.PrepareOrderSearchModelAsync(new OrderSearchModel + { + OrderStatusIds = orderStatuses, + PaymentStatusIds = paymentStatuses, + ShippingStatusIds = shippingStatuses, + }); + + return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/List.cshtml", model); + } + + [HttpPost] + [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)] + public virtual async Task OrderList(OrderSearchModel searchModel) + { + //prepare model + var model = await _orderModelFactory.PrepareOrderListModelAsync(searchModel); + + + Console.WriteLine($"Total: {model.RecordsTotal}, Data Count: {model.Data.Count()}"); + foreach (var item in model.Data.Take(3)) + { + Console.WriteLine($"Order: {item.Id}, {item.CustomOrderNumber}"); + } + var valami = Json(model); + Console.WriteLine(valami); + return valami; + } + + public virtual IActionResult Test() + { + // Your custom logic here + // This will use your custom List.cshtml view + return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/Test.cshtml"); + } + + //[HttpPost] + //[CheckPermission(Nop.Services.Security.StandardPermission.Orders.ORDERS_VIEW)] + //public virtual async Task OrderList(OrderSearchModel searchModel) + //{ + // //prepare model + // var model = await _orderModelFactory.PrepareOrderListModelAsync(searchModel); + + // return Json(model); + //} + } +} + + diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/InvoiceController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/InvoiceController.cs new file mode 100644 index 0000000..0273ff2 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/InvoiceController.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Mvc; +using Nop.Web.Areas.Admin.Controllers; +using Nop.Web.Framework.Mvc.Filters; +using Nop.Web.Framework; +using Nop.Services.Security; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers +{ + [Area(AreaNames.ADMIN)] + [AuthorizeAdmin] + public class InvoiceController : BaseAdminController + { + private readonly IPermissionService _permissionService; + + public InvoiceController(IPermissionService permissionService) + { + _permissionService = permissionService; + } + + public async Task List() + { + if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) + return AccessDeniedView(); + + return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Invoice/List.cshtml"); + } + } +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ShipmentController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ShipmentController.cs new file mode 100644 index 0000000..324d67d --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ShipmentController.cs @@ -0,0 +1,149 @@ +using Microsoft.AspNetCore.Mvc; +using Nop.Web.Areas.Admin.Controllers; +using Nop.Web.Framework.Mvc.Filters; +using Nop.Web.Framework; +using Nop.Services.Security; +using Microsoft.AspNetCore.Http; +using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models; +using Nop.Services.Messages; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers +{ + [Area(AreaNames.ADMIN)] + [AuthorizeAdmin] + public class ShipmentController : BaseAdminController + { + private readonly IPermissionService _permissionService; + protected readonly INotificationService _notificationService; + + public ShipmentController(IPermissionService permissionService, INotificationService notificationService) + { + _permissionService = permissionService; + _notificationService = notificationService; + } + + public async Task List() + { + if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) + return AccessDeniedView(); + + return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipment/List.cshtml"); + } + + [HttpGet] + public async Task Create() + { + if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) + return AccessDeniedView(); + + var model = new CreateShipmentModel + { + ShipmentDate = DateTime.Now + }; + + return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipment/Create.cshtml", model); + } + + [HttpPost] + public async Task Create(CreateShipmentModel model) + { + if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) + return AccessDeniedView(); + + if (!ModelState.IsValid) + { + return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipment/Create.cshtml", model); + } + + try + { + // TODO: Save shipment to database + // var shipment = new Shipment + // { + // Name = model.ShipmentName, + // Description = model.Description, + // ShipmentDate = model.ShipmentDate, + // TrackingNumber = model.TrackingNumber, + // CreatedOnUtc = DateTime.UtcNow + // }; + // await _shipmentService.InsertShipmentAsync(shipment); + + _notificationService.SuccessNotification("Shipment created successfully"); + return RedirectToAction("List"); + } + catch (Exception ex) + { + _notificationService.ErrorNotification($"Error creating shipment: {ex.Message}"); + return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipment/Create.cshtml", model); + } + } + + [HttpPost] + public async Task UploadFile(IFormFile file) + { + try + { + if (file == null || file.Length == 0) + return Json(new FileUploadResult { Success = false, ErrorMessage = "No file selected" }); + + // Validate file type (PDF only) + if (!file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)) + return Json(new FileUploadResult { Success = false, ErrorMessage = "Only PDF files are allowed" }); + + // Validate file size (e.g., max 10MB) + if (file.Length > 10 * 1024 * 1024) + return Json(new FileUploadResult { Success = false, ErrorMessage = "File size must be less than 10MB" }); + + // Create upload directory if it doesn't exist + var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "shipments"); + Directory.CreateDirectory(uploadsPath); + + // Generate unique filename + var fileName = $"{Guid.NewGuid()}_{file.FileName}"; + var filePath = Path.Combine(uploadsPath, fileName); + + // Save file + using (var stream = new FileStream(filePath, FileMode.Create)) + { + await file.CopyToAsync(stream); + } + + return Json(new FileUploadResult + { + Success = true, + FileName = file.FileName, + FilePath = $"/uploads/shipments/{fileName}" + }); + } + catch (Exception ex) + { + return Json(new FileUploadResult { Success = false, ErrorMessage = ex.Message }); + } + } + + [HttpPost] + public IActionResult DeleteUploadedFile(string filePath) + { + try + { + if (string.IsNullOrEmpty(filePath)) + return Json(new { success = false, message = "Invalid file path" }); + + var fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", filePath.TrimStart('/')); + + if (System.IO.File.Exists(fullPath)) + { + System.IO.File.Delete(fullPath); + return Json(new { success = true }); + } + + return Json(new { success = false, message = "File not found" }); + } + catch (Exception ex) + { + return Json(new { success = false, message = ex.Message }); + } + } + + } +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/CreateShipmentModel.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/CreateShipmentModel.cs new file mode 100644 index 0000000..d4ccfd8 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/CreateShipmentModel.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Http; +using Nop.Web.Framework.Models; +using System.ComponentModel.DataAnnotations; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models +{ + public record CreateShipmentModel : BaseNopModel + { + [Required] + public string ShipmentName { get; set; } + + public string Description { get; set; } + + [Required] + public DateTime ShipmentDate { get; set; } = DateTime.Now; + + public string TrackingNumber { get; set; } + + public List UploadedFiles { get; set; } = new(); + } + + public class FileUploadResult + { + public bool Success { get; set; } + public string FileName { get; set; } + public string FilePath { get; set; } + public string ErrorMessage { get; set; } + } +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/DashboardModel.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/DashboardModel.cs new file mode 100644 index 0000000..1273ecf --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/DashboardModel.cs @@ -0,0 +1,15 @@ +using Nop.Web.Framework.Models; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models; + +/// +/// Represents a dashboard model +/// +public partial record DashboardModel : BaseNopModel +{ + #region Properties + + public bool IsLoggedInAsVendor { get; set; } + + #endregion +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Index.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Index.cshtml new file mode 100644 index 0000000..959a392 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Index.cshtml @@ -0,0 +1,129 @@ +@model DashboardModel +@inject IPermissionService permissionService +@using Nop.Services.Security +@{ + //page title + ViewBag.PageTitle = T("Admin.Dashboard").Text; + + var canManageOrders = await permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_VIEW); + var canManageCustomers = await permissionService.AuthorizeAsync(StandardPermission.Customers.CUSTOMERS_VIEW); + var canManageProducts = await permissionService.AuthorizeAsync(StandardPermission.Catalog.PRODUCTS_VIEW); + var canManageReturnRequests = await permissionService.AuthorizeAsync(StandardPermission.Orders.RETURN_REQUESTS_VIEW); + + //close configuration steps value + const string closeCardAttributeName = "CloseConfigurationSteps"; + var closeConfigurationStepsCard = await genericAttributeService.GetAttributeAsync(await workContext.GetCurrentCustomerAsync(), closeCardAttributeName); + + //active menu item (system name) + NopHtml.SetActiveMenuItemSystemName("Dashboard"); +} + + + + +
+

+ Fruit Bank @T("Admin.Dashboard") +

+
+
+
+
+
+
+ @if (!closeConfigurationStepsCard) + { +
+
+ @await Html.PartialAsync("~/Areas/Admin/Views/Home/_ConfigurationSteps.cshtml") +
+
+ } + + @await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardTop, additionalData = Model }) + @if (!Model.IsLoggedInAsVendor) + { +
+
+ @await Component.InvokeAsync(typeof(NopCommerceNewsViewComponent)) +
+
+ } + + @await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardNewsAfter, additionalData = Model }) + @if (!Model.IsLoggedInAsVendor && canManageOrders && canManageCustomers && canManageProducts && canManageReturnRequests) + { +
+
+ @await Component.InvokeAsync(typeof(CommonStatisticsViewComponent)) +
+
+ } + @await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardCommonstatisticsAfter, additionalData = Model }) + @if (!Model.IsLoggedInAsVendor && (canManageOrders || canManageCustomers)) + { +
+ @if (!Model.IsLoggedInAsVendor && canManageOrders) + { +
+ @await Html.PartialAsync("~/Areas/Admin/Views/Home/_OrderStatistics.cshtml") +
+ } + @if (!Model.IsLoggedInAsVendor && canManageCustomers) + { +
+ @await Html.PartialAsync("~/Areas/Admin/Views/Home/_CustomerStatistics.cshtml") +
+ } +
+ } + @await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardCustomerorderchartsAfter, additionalData = Model }) + @if (!Model.IsLoggedInAsVendor && canManageOrders) + { +
+
+ @await Html.PartialAsync("~/Areas/Admin/Views/Home/_OrderAverageReport.cshtml") +
+
+ @await Html.PartialAsync("~/Areas/Admin/Views/Home/_OrderIncompleteReport.cshtml") +
+
+ } + @await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardOrderreportsAfter, additionalData = Model }) + @if (!Model.IsLoggedInAsVendor && (canManageOrders || canManageProducts)) + { +
+ @if (canManageOrders) + { +
+ @await Html.PartialAsync("~/Areas/Admin/Views/Home/_LatestOrders.cshtml") +
+ } +
+ @if (canManageProducts) + { + @await Html.PartialAsync("~/Areas/Admin/Views/Home/_PopularSearchTermsReport.cshtml") + } +
+
+ } + @await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardLatestordersSearchtermsAfter, additionalData = Model }) + @if (canManageOrders) + { +
+
+ @await Html.PartialAsync("~/Areas/Admin/Views/Home/_BestsellersBriefReportByQuantity.cshtml") +
+
+ @await Html.PartialAsync("~/Areas/Admin/Views/Home/_BestsellersBriefReportByAmount.cshtml") +
+
+ } + @await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardBottom, additionalData = Model }) +
+
+
+
+
+ + diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Invoice/List.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Invoice/List.cshtml new file mode 100644 index 0000000..0b8e403 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Invoice/List.cshtml @@ -0,0 +1,32 @@ +@{ + // Layout = "_AdminLayout"; + ViewBag.PageTitle = "Invoice"; + NopHtml.SetActiveMenuItemSystemName("Invoices"); +} + +
+

+ + Invoices +

+
+ +
+
+
+
+
+
+

+ Shipment Management +

+
+
+

This is our custom Invoices page.

+

Add shipment functionality here.

+
+
+
+
+
+
\ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml new file mode 100644 index 0000000..ebc6b19 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml @@ -0,0 +1,668 @@ +@model OrderSearchModel + +@inject IStoreService storeService +@using Nop.Plugin.Misc.FruitBankPlugin.Models +@using Nop.Services.Stores +@using Nop.Web.Areas.Admin.Components +@using Nop.Web.Areas.Admin.Models.Orders +@using Nop.Web.Framework.Infrastructure +@using Nop.Web.Framework.Models.DataTables +@using static Nop.Services.Common.NopLinksDefaults + +@{ + //page title + ViewBag.PageTitle = T("Admin.Orders").Text; + //active menu item (system name) + NopHtml.SetActiveMenuItemSystemName("Orders"); +} + +@{ + const string hideSearchBlockAttributeName = "OrdersPage.HideSearchBlock"; + var hideSearchBlock = await genericAttributeService.GetAttributeAsync(await workContext.GetCurrentCustomerAsync(), hideSearchBlockAttributeName); +} + +@if (Model.LicenseCheckModel.BlockPages != true) +{ +
+
+

+ @T("Admin.Orders") +

+
+
+ + + +
+ +
+ + + +
+ @await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.OrderListButtons, additionalData = Model }) +
+
+
+
+
+
+ + +
+
+

RTTTTTTTTTT

+
+
+ + + @{ + var gridModel = new DataTablesModel + { + Name = "orders-grid", + UrlRead = new DataUrl("OrderList", "CustomOrder", null), + SearchButtonId = "search-orders", + Length = Model.PageSize, + LengthMenu = Model.AvailablePageSizes, + FooterCallback = !Model.IsLoggedInAsVendor ? "ordersfootercallback" : null, + FooterColumns = !Model.IsLoggedInAsVendor ? 10 : 0, + Filters = new List + { + new FilterParameter(nameof(Model.StartDate), typeof(DateTime?)), + new FilterParameter(nameof(Model.EndDate), typeof(DateTime?)), + new FilterParameter(nameof(Model.OrderStatusIds)), + new FilterParameter(nameof(Model.PaymentStatusIds)), + new FilterParameter(nameof(Model.ShippingStatusIds)), + new FilterParameter(nameof(Model.StoreId)), + new FilterParameter(nameof(Model.VendorId)), + new FilterParameter(nameof(Model.WarehouseId)), + new FilterParameter(nameof(Model.BillingEmail)), + new FilterParameter(nameof(Model.BillingPhone)), + new FilterParameter(nameof(Model.BillingLastName)), + new FilterParameter(nameof(Model.BillingCountryId)), + new FilterParameter(nameof(Model.PaymentMethodSystemName)), + new FilterParameter(nameof(Model.ProductId)), + new FilterParameter(nameof(Model.OrderNotes)) + } + }; + gridModel.ColumnCollection = new List + { + new ColumnProperty(nameof(OrderModel.Id)) + { + IsMasterCheckBox = true, + Render = new RenderCheckBox("checkbox_orders"), + ClassName = NopColumnClassDefaults.CenterAll, + Width = "50" + }, + new ColumnProperty(nameof(OrderModel.CustomOrderNumber)) + { + Title = T("Admin.Orders.Fields.CustomOrderNumber").Text, + Width = "80" + } + }; + gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.NeedsMeasurement)) + { + Title = "Needs Measurement", + Width = "100", + Render = new RenderCustom("renderColumnNeedsMeasurement"), + ClassName = NopColumnClassDefaults.CenterAll + }); + + //a vendor does not have access to this functionality + if (!Model.IsLoggedInAsVendor) + { + gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.OrderStatus)) + { + Title = T("Admin.Orders.Fields.OrderStatus").Text, + Width = "100", + Render = new RenderCustom("renderColumnOrderStatus") + }); + } + gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.PaymentStatus)) + { + Title = T("Admin.Orders.Fields.PaymentStatus").Text, + Width = "150" + }); + //a vendor does not have access to this functionality + if (!Model.IsLoggedInAsVendor) + { + gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.ShippingStatus)) + { + Title = T("Admin.Orders.Fields.ShippingStatus").Text, + Width = "150" + }); + } + gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.CustomerEmail)) + { + Title = T("Admin.Orders.Fields.Customer").Text, + Render = new RenderCustom("renderColumnCustomer") + }); + gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.StoreName)) + { + Title = T("Admin.Orders.Fields.Store").Text, + Width = "100", + Visible = (await storeService.GetAllStoresAsync()).Count > 1 + }); + gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.CreatedOn)) + { + Title = T("Admin.Orders.Fields.CreatedOn").Text, + Width = "120", + Render = new RenderDate() + }); + //a vendor does not have access to this functionality + if (!Model.IsLoggedInAsVendor) + { + gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.OrderTotal)) + { + Title = T("Admin.Orders.Fields.OrderTotal").Text, + Width = "100", + }); + } + gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.Id)) + { + Title = T("Admin.Common.View").Text, + Width = "50", + ClassName = NopColumnClassDefaults.Button, + Render = new RenderButtonView(new DataUrl("~/Admin/Order/Edit")) + }); + var orderSummaryColumnNumber = 8; + } + @await Html.PartialAsync("Table", gridModel) + + +
+
+
+
+
+
+
+} + + + + +@*export selected (XML). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@ +
+ +
+ + + + +@*export selected (Excel). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@ +
+ +
+ + + + +@*Print packaging slips selected (XML). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@ +
+ +
+ + + + +@*import orders form*@ + + diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/Test.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/Test.cshtml new file mode 100644 index 0000000..48930f0 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/Test.cshtml @@ -0,0 +1,4 @@ +@{ + Layout = "_AdminLayout"; +} +

TEST VIEW FROM PLUGIN

\ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Shipment/Create.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Shipment/Create.cshtml new file mode 100644 index 0000000..514f3fa --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Shipment/Create.cshtml @@ -0,0 +1,362 @@ +@model Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.CreateShipmentModel +@{ + // Layout = "_AdminLayout"; + ViewBag.PageTitle = "Create Shipment"; + NopHtml.SetActiveMenuItemSystemName("Shipments.Create"); +} + +
+
+

+ + Create New Shipment +

+
+ + + + Back to List + +
+
+ +
+
+
+
+ +
+
+
Shipment Information
+
+
+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+
+
+ + +
+
+
+ + Upload Documents (PDF only) +
+
+
+ +
+
+ +

Drag & Drop PDF files here

+

or click to select files

+ + +
+
+ + + + + +
+
Uploaded Files:
+
+ +
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Shipment/List.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Shipment/List.cshtml new file mode 100644 index 0000000..86e4c05 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Shipment/List.cshtml @@ -0,0 +1,32 @@ +@{ + // Layout = "_AdminLayout"; + ViewBag.PageTitle = "Shipments"; + NopHtml.SetActiveMenuItemSystemName("Shipments"); +} + +
+

+ + Shipments +

+
+ +
+
+
+
+
+
+

+ Shipment Management +

+
+
+

This is our custom Shipments page.

+

Add shipment functionality here.

+
+
+
+
+
+
\ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewImports.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewImports.cshtml index 47e0479..ad5d40c 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewImports.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewImports.cshtml @@ -2,11 +2,72 @@ @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Nop.Web.Framework -@inject INopHtmlHelper NopHtml +@inject INopHtmlHelper NopHtml +@inject IGenericAttributeService genericAttributeService +@inject IWorkContext workContext @using Microsoft.AspNetCore.Mvc.ViewFeatures +@using Nop.Core +@using Nop.Services.Common @using Nop.Web.Framework.UI @using Nop.Web.Framework.Extensions @using System.Text.Encodings.Web @using Nop.Services.Events -@using Nop.Web.Framework.Events \ No newline at end of file +@using Nop.Web.Framework.Events + + + + +@using System.Globalization; +@using System.Net; +@using Microsoft.AspNetCore.Routing +@using Microsoft.AspNetCore.StaticFiles +@using Microsoft.Extensions.Primitives +@using Nop.Core.Domain.Common +@using Nop.Core.Events +@using Nop.Core.Infrastructure +@using static Nop.Services.Common.NopLinksDefaults +@using Nop.Web.Areas.Admin.Components +@using Nop.Web.Areas.Admin.Models.Affiliates +@using Nop.Web.Areas.Admin.Models.Blogs +@using Nop.Web.Areas.Admin.Models.Catalog +@using Nop.Web.Areas.Admin.Models.Cms +@using Nop.Web.Areas.Admin.Models.Common +@using Nop.Web.Areas.Admin.Models.Customers +@using Nop.Web.Areas.Admin.Models.Directory +@using Nop.Web.Areas.Admin.Models.Discounts +@using Nop.Web.Areas.Admin.Models.ExternalAuthentication +@using Nop.Web.Areas.Admin.Models.Forums +@using Nop.Web.Areas.Admin.Models.Home +@using Nop.Web.Areas.Admin.Models.Localization +@using Nop.Web.Areas.Admin.Models.Logging +@using Nop.Web.Areas.Admin.Models.Messages +@using Nop.Web.Areas.Admin.Models.MultiFactorAuthentication +@using Nop.Web.Areas.Admin.Models.News +@using Nop.Web.Areas.Admin.Models.Orders +@using Nop.Web.Areas.Admin.Models.Payments +@using Nop.Web.Areas.Admin.Models.Plugins +@using Nop.Web.Areas.Admin.Models.Plugins.Marketplace +@using Nop.Web.Areas.Admin.Models.Polls +@using Nop.Web.Areas.Admin.Models.Reports +@using Nop.Web.Areas.Admin.Models.Security +@using Nop.Web.Areas.Admin.Models.Settings +@using Nop.Web.Areas.Admin.Models.Shipping +@using Nop.Web.Areas.Admin.Models.ShoppingCart +@using Nop.Web.Areas.Admin.Models.Stores +@using Nop.Web.Areas.Admin.Models.Tasks +@using Nop.Web.Areas.Admin.Models.Tax +@using Nop.Web.Areas.Admin.Models.Templates +@using Nop.Web.Areas.Admin.Models.Topics +@using Nop.Web.Areas.Admin.Models.Vendors +@using Nop.Web.Components +@using Nop.Web.Extensions +@using Nop.Web.Framework +@using Nop.Web.Framework.Infrastructure +@using Nop.Web.Framework.Models +@using Nop.Web.Framework.Models.Cms +@using Nop.Web.Framework.Models.DataTables +@using Nop.Web.Framework.Mvc.Routing +@using Nop.Web.Framework.Security.Captcha +@using Nop.Web.Framework.Security.Honeypot +@using Nop.Web.Framework.Themes \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewStart.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewStart.cshtml new file mode 100644 index 0000000..0297c66 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_AdminLayout"; +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Factories/CustomOrderModelFactory.cs b/Nop.Plugin.Misc.AIPlugin/Factories/CustomOrderModelFactory.cs new file mode 100644 index 0000000..280c2c5 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Factories/CustomOrderModelFactory.cs @@ -0,0 +1,215 @@ +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Core; +using Nop.Plugin.Misc.FruitBankPlugin.Services; +using Nop.Services.Affiliates; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Configuration; +using Nop.Services.Customers; +using Nop.Services.Directory; +using Nop.Services.Discounts; +using Nop.Services.Helpers; +using Nop.Services.Localization; +using Nop.Services.Media; +using Nop.Services.Orders; +using Nop.Services.Payments; +using Nop.Services.Security; +using Nop.Services.Seo; +using Nop.Services.Shipping; +using Nop.Services.Stores; +using Nop.Services.Tax; +using Nop.Services.Vendors; +using Nop.Web.Areas.Admin.Factories; +using Nop.Web.Areas.Admin.Models.Orders; +using Nop.Plugin.Misc.FruitBankPlugin.Models; +using System.Reflection; +using Nop.Plugin.Misc.FruitBankPlugin.Helpers; +using Microsoft.AspNetCore.Mvc.TagHelpers; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Factories +{ + public class CustomOrderModelFactory : OrderModelFactory + { + private readonly IOrderMeasurementService _orderMeasurementService; + + public CustomOrderModelFactory( + IOrderMeasurementService orderMeasurementService, + AddressSettings addressSettings, + CatalogSettings catalogSettings, + CurrencySettings currencySettings, + IActionContextAccessor actionContextAccessor, + IAddressModelFactory addressModelFactory, + IAddressService addressService, + IAffiliateService affiliateService, + IBaseAdminModelFactory baseAdminModelFactory, + ICountryService countryService, + ICurrencyService currencyService, + ICustomerService customerService, + IDateTimeHelper dateTimeHelper, + IDiscountService discountService, + IDownloadService downloadService, + IEncryptionService encryptionService, + IGiftCardService giftCardService, + ILocalizationService localizationService, + IMeasureService measureService, + IOrderProcessingService orderProcessingService, + IOrderReportService orderReportService, + IOrderService orderService, + IPaymentPluginManager paymentPluginManager, + IPaymentService paymentService, + IPictureService pictureService, + IPriceCalculationService priceCalculationService, + IPriceFormatter priceFormatter, + IProductAttributeService productAttributeService, + IProductService productService, + IReturnRequestService returnRequestService, + IRewardPointService rewardPointService, + ISettingService settingService, + IShipmentService shipmentService, + IShippingService shippingService, + IStateProvinceService stateProvinceService, + IStoreService storeService, + ITaxService taxService, + IUrlHelperFactory urlHelperFactory, + IVendorService vendorService, + IWorkContext workContext, + MeasureSettings measureSettings, + NopHttpClient nopHttpClient, + OrderSettings orderSettings, + ShippingSettings shippingSettings, + IUrlRecordService urlRecordService, + TaxSettings taxSettings + ) : base(addressSettings, + catalogSettings, + currencySettings, + actionContextAccessor, + addressModelFactory, + addressService, + affiliateService, + baseAdminModelFactory, + countryService, + currencyService, + customerService, + dateTimeHelper, + discountService, + downloadService, + encryptionService, + giftCardService, + localizationService, + measureService, + orderProcessingService, + orderReportService, + orderService, + paymentPluginManager, + paymentService, + pictureService, + priceCalculationService, + priceFormatter, + productAttributeService, + productService, + returnRequestService, + rewardPointService, + settingService, + shipmentService, + shippingService, + stateProvinceService, + storeService, + taxService, + urlHelperFactory, + vendorService, + workContext, + measureSettings, + nopHttpClient, + orderSettings, + shippingSettings, + urlRecordService, + taxSettings + ) + { + _orderMeasurementService = orderMeasurementService; + } + + public override async Task PrepareOrderSearchModelAsync(OrderSearchModel searchModel) + { + // let base prepare default model first + var baseModel = await base.PrepareOrderSearchModelAsync(searchModel); + + //foreach (var order in baseModel.order) + + + // create derived/extended instance + //var extended = new OrderSearchModelExtended(); + + // copy all public instance properties from baseModel to extended + //CopyModelHelper.CopyPublicProperties(baseModel, extended); + + // try to obtain NeedsMeasurement from the incoming searchModel (if it's extended) + //bool? needsMeasurement = null; + //var prop = searchModel?.GetType().GetProperty("NeedsMeasurement", BindingFlags.Public | BindingFlags.Instance); + //if (prop != null && prop.PropertyType == typeof(bool?)) + //{ + // needsMeasurement = (bool?)prop.GetValue(searchModel); + //} + + //extended.NeedsMeasurement = needsMeasurement; + + // return the extended object (it's assignable to OrderSearchModel) + //return extended; + return baseModel; + } + + public override async Task PrepareOrderListModelAsync(OrderSearchModel searchModel) + { + // get the default model first + var baseModel = await base.PrepareOrderListModelAsync(searchModel); + + var extendedRows = new List(); + + foreach (var order in baseModel.Data) + { + var extendedOrder = new OrderModelExtended(); + CopyModelHelper.CopyPublicProperties(order, extendedOrder); + extendedOrder.NeedsMeasurement = await ShouldMarkAsNeedsMeasurementAsync(order); + Console.WriteLine(extendedOrder.Id); + extendedRows.Add(extendedOrder); + } + + //var model = new OrderListModel + //{ + // Data = extendedRows.Cast().ToList(), + // RecordsTotal = baseModel.RecordsTotal + //}; + + var model = new OrderListModel(); + CopyModelHelper.CopyPublicProperties(baseModel, model); + model.Data = extendedRows.ToList(); // Different cast approach + + return model; + } + + // example async custom logic + private async Task ShouldMarkAsNeedsMeasurementAsync(OrderModel order) + { + // TODO: your logic (e.g. check if order has products that need measuring) + if (order == null) + return false; + + var fullOrder = await _orderService.GetOrderByIdAsync(order.Id); + if (fullOrder != null) + { + return await _orderMeasurementService.IsPendingMeasurementAsync(fullOrder); + } + return await Task.FromResult(false); + } + + + } +} + diff --git a/Nop.Plugin.Misc.AIPlugin/Filters/PendingMeasurementCheckoutFilter.cs b/Nop.Plugin.Misc.AIPlugin/Filters/PendingMeasurementCheckoutFilter.cs index 25521f9..b0fa135 100644 --- a/Nop.Plugin.Misc.AIPlugin/Filters/PendingMeasurementCheckoutFilter.cs +++ b/Nop.Plugin.Misc.AIPlugin/Filters/PendingMeasurementCheckoutFilter.cs @@ -39,6 +39,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Filters if (order != null && await _orderMeasurementService.IsPendingMeasurementAsync(order)) { + order.OrderStatus = OrderStatus.Processing; + order.PaymentStatus = Core.Domain.Payments.PaymentStatus.Pending; context.Result = new RedirectToRouteResult(new { controller = "Checkout", diff --git a/Nop.Plugin.Misc.AIPlugin/FruitBankPlugin.cs b/Nop.Plugin.Misc.AIPlugin/FruitBankPlugin.cs index 8620b58..0ae795d 100644 --- a/Nop.Plugin.Misc.AIPlugin/FruitBankPlugin.cs +++ b/Nop.Plugin.Misc.AIPlugin/FruitBankPlugin.cs @@ -67,7 +67,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin ApiKey = string.Empty }; await _settingService.SaveSettingAsync(settings); - + await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.ShipmentsList", "Shipment", "EN"); + await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.ShipmentsList", "Shipment", "HU"); await base.InstallAsync(); } @@ -113,48 +114,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin if (!await _permissionService.AuthorizeAsync(StandardPermission.Configuration.MANAGE_PLUGINS)) return; - var configurationItem = rootNode.ChildNodes.FirstOrDefault(node => node.SystemName.Equals("Configuration")); - if (configurationItem is null) - return; - - var shippingItem = configurationItem.ChildNodes.FirstOrDefault(node => node.SystemName.Equals("Shipping")); - var widgetsItem = configurationItem.ChildNodes.FirstOrDefault(node => node.SystemName.Equals("Widgets")); - if (shippingItem is null && widgetsItem is null) - return; - - var index = shippingItem is not null ? configurationItem.ChildNodes.IndexOf(shippingItem) : -1; - if (index < 0) - index = widgetsItem is not null ? configurationItem.ChildNodes.IndexOf(widgetsItem) : -1; - if (index < 0) - return; - - configurationItem.ChildNodes.Insert(index + 1, new AdminMenuItem - { - - Visible = true, - SystemName = "AI plugins", - Title = await _localizationService.GetResourceAsync("Plugins.Misc.SignalRApi.Menu.AI"), - IconClass = "far fa-dot-circle", - ChildNodes = new List - { - new() - { - // SystemName = "FruitBankPlugin.Configure", - // Title = "AI Assistant", - // Url = $"{_webHelper.GetStoreLocation()}Admin/FruitBankPluginAdmin/Configure", - // Visible = true - Visible = true, - SystemName = PluginDescriptor.SystemName, - Title = PluginDescriptor.FriendlyName, - IconClass = "far fa-circle", - Url = _adminMenu.GetMenuItemUrl("FruitBankPlugin", "Configure"), - //Url = "Admin/SignalRApi/Configure", - //ControllerName = "SignalRApi", - //ActionName = "Configure", - //RouteValues = new RouteValueDictionary { { "area", AreaNames.ADMIN } } - } - } - }); } public override string GetConfigurationPageUrl() diff --git a/Nop.Plugin.Misc.AIPlugin/Helpers/CopyModelHelper.cs b/Nop.Plugin.Misc.AIPlugin/Helpers/CopyModelHelper.cs new file mode 100644 index 0000000..9af2d64 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Helpers/CopyModelHelper.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Helpers +{ + public static class CopyModelHelper + { + public static void CopyPublicProperties(TSource src, TDestination dest) + { + if (src == null || dest == null) return; + + var srcProps = typeof(TSource) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.CanRead); + + foreach (var sp in srcProps) + { + var dp = typeof(TDestination).GetProperty(sp.Name, BindingFlags.Public | BindingFlags.Instance); + if (dp == null || !dp.CanWrite) continue; + dp.SetValue(dest, sp.GetValue(src)); + } + } + } +} diff --git a/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs b/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs index 654d95d..a6865e8 100644 --- a/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs +++ b/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs @@ -12,11 +12,19 @@ using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Nop.Core.Domain.Orders; using Nop.Core.Infrastructure; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; +using Nop.Plugin.Misc.FruitBankPlugin.Factories; using Nop.Plugin.Misc.FruitBankPlugin.Filters; +using Nop.Plugin.Misc.FruitBankPlugin.Models; using Nop.Plugin.Misc.FruitBankPlugin.Services; using Nop.Services.Catalog; +using Nop.Services.Common; +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 FruitBankDataController = Nop.Plugin.Misc.FruitBankPlugin.Controllers.FruitBankDataController; namespace Nop.Plugin.Misc.FruitBankPlugin.Infrastructure; @@ -56,8 +64,14 @@ public class PluginNopStartup : INopStartup //services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped, EventConsumer>(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddControllersWithViews(options => { options.Filters.AddService(); diff --git a/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs b/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs index 9cb3d70..e308580 100644 --- a/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs +++ b/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs @@ -22,10 +22,58 @@ public class RouteProvider : IRouteProvider defaults: new { controller = "FruitBankPluginAdmin", action = "Configure", area = AreaNames.ADMIN }); //endpointRouteBuilder.MapHub("/fbhub");//.RequireCors("AllowBlazorClient"); + + endpointRouteBuilder.MapControllerRoute( + name: "Plugin.FruitBank.Admin.Order.List", + pattern: "Admin/Order/List", + defaults: new { controller = "CustomOrder", action = "List", area = AreaNames.ADMIN } + //constraints: new { area = AreaNames.ADMIN } + ); + + endpointRouteBuilder.MapControllerRoute( + name: "Plugin.FruitBank.Admin.Order.OrderList", + pattern: "Admin/Order/OrderList", + defaults: new { controller = "CustomOrder", action = "OrderList", area = AreaNames.ADMIN }); + + endpointRouteBuilder.MapControllerRoute( + name: "Plugin.FruitBank.Admin.Order.Test", + pattern: "Admin/Order/Test", + defaults: new { controller = "CustomOrder", action = "Test", area = AreaNames.ADMIN } + //constraints: new { area = AreaNames.ADMIN } + ); + + endpointRouteBuilder.MapControllerRoute( + name: "Plugin.FruitBank.Admin.Index", + pattern: "Admin", + defaults: new { controller = "CustomDashboard", action = "Index", area = AreaNames.ADMIN } + //constraints: new { area = AreaNames.ADMIN } + ); + + endpointRouteBuilder.MapControllerRoute( + name: "Plugin.FruitBank.Admin.Shipment.List", + pattern: "Admin/Shipment/List", + defaults: new { controller = "Shipment", action = "List", area = AreaNames.ADMIN } + ); + + endpointRouteBuilder.MapControllerRoute( + name: "Plugin.FruitBank.Admin.Invoices.List", + pattern: "Admin/Invoices/List", + defaults: new { controller = "Invoice", action = "List", area = AreaNames.ADMIN } + ); + + endpointRouteBuilder.MapControllerRoute( + name: "Plugin.FruitBank.Admin.Shipment.Create", + pattern: "Admin/Shipment/Create", + defaults: new { controller = "Shipment", action = "Create", area = AreaNames.ADMIN }); + + endpointRouteBuilder.MapControllerRoute( + name: "Plugin.FruitBank.Admin.Shipment.UploadFile", + pattern: "Admin/Shipment/UploadFile", + defaults: new { controller = "Shipment", action = "UploadFile", area = AreaNames.ADMIN }); } /// /// Gets a priority of route provider /// - public int Priority => 0; + public int Priority => 3000; } \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Models/OrderListModelExtended.cs b/Nop.Plugin.Misc.AIPlugin/Models/OrderListModelExtended.cs new file mode 100644 index 0000000..79217ee --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Models/OrderListModelExtended.cs @@ -0,0 +1,10 @@ +using Nop.Web.Areas.Admin.Models.Orders; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Models +{ + public partial record OrderListModelExtended : OrderListModel + { + public bool? NeedsMeasurement { get; set; } + } +} + diff --git a/Nop.Plugin.Misc.AIPlugin/Models/OrderModelExtended.cs b/Nop.Plugin.Misc.AIPlugin/Models/OrderModelExtended.cs new file mode 100644 index 0000000..afef1c0 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Models/OrderModelExtended.cs @@ -0,0 +1,10 @@ +using Nop.Web.Areas.Admin.Models.Orders; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Models +{ + public partial record OrderModelExtended : OrderModel + { + public bool NeedsMeasurement { get; set; } + } +} + diff --git a/Nop.Plugin.Misc.AIPlugin/Models/OrderSearchModelExtended.cs b/Nop.Plugin.Misc.AIPlugin/Models/OrderSearchModelExtended.cs new file mode 100644 index 0000000..7d46408 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Models/OrderSearchModelExtended.cs @@ -0,0 +1,10 @@ +using Nop.Web.Areas.Admin.Models.Orders; + +namespace Nop.Plugin.Misc.FruitBankPlugin.Models +{ + public partial record OrderSearchModelExtended : OrderSearchModel + { + public bool? NeedsMeasurement { get; set; } + } +} + diff --git a/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.FruitBankPlugin.csproj b/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.FruitBankPlugin.csproj index fa3c373..d8f0e53 100644 --- a/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.FruitBankPlugin.csproj +++ b/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.FruitBankPlugin.csproj @@ -20,6 +20,16 @@ + + true + PreserveNewest + Always + + + true + PreserveNewest + Always + PreserveNewest @@ -33,7 +43,7 @@ PreserveNewest - PreserveNewest + Always PreserveNewest @@ -55,9 +65,8 @@ - - - + + @@ -138,6 +147,21 @@ Always + + Always + + + Always + + + Always + + + Always + + + Always + Always diff --git a/Nop.Plugin.Misc.AIPlugin/Services/EventConsumer.cs b/Nop.Plugin.Misc.AIPlugin/Services/EventConsumer.cs index f75d45a..c124fd2 100644 --- a/Nop.Plugin.Misc.AIPlugin/Services/EventConsumer.cs +++ b/Nop.Plugin.Misc.AIPlugin/Services/EventConsumer.cs @@ -1,32 +1,56 @@ using System.Linq; +using Nop.Core; using Nop.Core.Domain.Catalog; using Nop.Core.Domain.Orders; using Nop.Services.Catalog; using Nop.Services.Common; using Nop.Services.Events; +using Nop.Services.Localization; using Nop.Services.Orders; +using Nop.Services.Plugins; +using Nop.Web.Framework.Events; +using Nop.Web.Framework.Menu; +using Nop.Web.Models.Sitemap; -namespace Nop.Plugin.YourCompany.Measurement.Services +namespace Nop.Plugin.Misc.FruitBankPlugin.Services { - public class OrderPlacedConsumer : IConsumer + public class EventConsumer : BaseAdminMenuCreatedEventConsumer, IConsumer, IConsumer { private readonly IGenericAttributeService _genericAttributeService; private readonly IProductService _productService; + private readonly ISpecificationAttributeService _specificationAttributeService; private readonly IOrderService _orderService; private readonly IProductAttributeService _productAttributeService; + private readonly IWorkContext _workContext; + private readonly IStoreContext _storeContext; + private readonly IAdminMenu _adminMenu; + private readonly ILocalizationService _localizationService; - public OrderPlacedConsumer( + public EventConsumer( IGenericAttributeService genericAttributeService, IProductService productService, - IOrderService orderService, - IProductAttributeService productAttributeService) + ISpecificationAttributeService specificationAttributeService, + IOrderService orderService, + IProductAttributeService productAttributeService, + IPluginManager pluginManager, + IWorkContext workContext, + IStoreContext storeContext, + IAdminMenu adminMenu, + ILocalizationService localizationService) : base(pluginManager) { _genericAttributeService = genericAttributeService; _productService = productService; + _specificationAttributeService = specificationAttributeService; _orderService = orderService; _productAttributeService = productAttributeService; + _workContext = workContext; + _storeContext = storeContext; + _adminMenu = adminMenu; + _localizationService = localizationService; } + protected override string PluginSystemName => "Misc.FruitBankPlugin"; + public async Task HandleEventAsync(OrderPlacedEvent eventMessage) { var order = eventMessage.Order; @@ -41,31 +65,159 @@ namespace Nop.Plugin.YourCompany.Measurement.Services // akár egy product attribute is lehet, vagy egy saját extension metódus if (product != null) { - var productAttributeMappings = await _productAttributeService.GetProductAttributeMappingsByProductIdAsync(product.Id); - //Product Attributes - foreach (var pam in productAttributeMappings) + //var productAttributeMappings = await _productAttributeService.GetProductAttributeMappingsByProductIdAsync(product.Id); + ////Product Attributes + //foreach (var pam in productAttributeMappings) + //{ + // var attributes = await _productAttributeService.GetProductAttributeValuesAsync(pam.Id); + // foreach (var attr in attributes) + // { + // // you can check for specific attribute by its name or id + // if (attr.Name == "NeedsToBeMeasured" && attr.IsPreSelected) + // { + // requiresMeasurement = true; + // break; + // } + // } + //} + + var productSpecAttributes = await _specificationAttributeService.GetProductSpecificationAttributesAsync(product.Id); + + foreach (var specAttribute in productSpecAttributes) { - var attributes = await _productAttributeService.GetProductAttributeValuesAsync(pam.Id); - foreach (var attr in attributes) + // Get the specification attribute + var specificationAttribute = await _specificationAttributeService + .GetSpecificationAttributeByIdAsync(specAttribute.Id); + + // Get the specification attribute option + var specificationAttributeOption = await _specificationAttributeService + .GetSpecificationAttributeOptionByIdAsync(specAttribute.SpecificationAttributeOptionId); + + System.Diagnostics.Debug.WriteLine($"Spec Attribute: {specificationAttribute.Name}, Option: {specificationAttributeOption.Name}"); + + // Check if this is your "NeedsToBeMeasured" specification attribute + if (specificationAttribute.Name == "Measureable" && + specificationAttributeOption.Name == "Yes") // or whatever value you set { - // you can check for specific attribute by its name or id - if (attr.Name == "NeedsToBeMeasured" && attr.IsPreSelected) - { - requiresMeasurement = true; - break; - } + requiresMeasurement = true; + break; } } - + } } if (requiresMeasurement) { + var store = await _storeContext.GetCurrentStoreAsync(); // itt adjuk hozzá a GenericAttribute flag-et az orderhez await _genericAttributeService.SaveAttributeAsync(order, - "PendingMeasurement", true); + "PendingMeasurement", true, store.Id); + // status pending + // paymentstatus pending } } + + public async Task HandleEventAsync(AdminMenuCreatedEvent eventMessage) + { + var rootNode = eventMessage.RootMenuItem; + + + var shipmentsListMenuItem = new AdminMenuItem + { + Visible = true, + SystemName = "FruitBank", + Title = await _localizationService.GetResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.ShipmentsList"), // You can localize this with await _localizationService.GetResourceAsync("...") + IconClass = "fas fa-shipping-fast", + Url = _adminMenu.GetMenuItemUrl("Shipment", "List") + }; + + + var createShipmentMenuItem = new AdminMenuItem + { + Visible = true, + SystemName = "Shipments.Create", + Title = "Create Shipment", + IconClass = "far fa-circle", + Url = _adminMenu.GetMenuItemUrl("Shipment", "Create") + }; + + // Create a new top-level menu item + var shipmentsMenuItem = new AdminMenuItem + { + Visible = true, + SystemName = "FruitBank", + Title = await _localizationService.GetResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.Shipments"), // You can localize this with await _localizationService.GetResourceAsync("...") + IconClass = "fas fa-shipping-fast", + //Url = _adminMenu.GetMenuItemUrl("Shipment", "List") + ChildNodes = new[] { shipmentsListMenuItem, createShipmentMenuItem } + }; + + var shipmentConfigurationItem = rootNode; + shipmentConfigurationItem.ChildNodes.Insert(2, shipmentsMenuItem); + + + var invoiceListMenuItem = new AdminMenuItem + { + Visible = true, + SystemName = "FruitBank", + Title = await _localizationService.GetResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.InvoicesList"), // You can localize this with await _localizationService.GetResourceAsync("...") + IconClass = "fas fa-file-invoice", + Url = _adminMenu.GetMenuItemUrl("Invoices", "List") + }; + + // Create a new top-level menu item + var invoicesMenuItem = new AdminMenuItem + { + Visible = true, + SystemName = "FruitBank", + Title = await _localizationService.GetResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.Invoices"), // You can localize this with await _localizationService.GetResourceAsync("...") + IconClass = "fas fa-file-invoice", + //Url = _adminMenu.GetMenuItemUrl("Shipment", "List") + ChildNodes = new[] { invoiceListMenuItem } + }; + + var invoiceConfigurationItem = rootNode; + invoiceConfigurationItem.ChildNodes.Insert(2, invoicesMenuItem); + + + var configurationItem = rootNode.ChildNodes.FirstOrDefault(node => node.SystemName.Equals("Configuration")); + if (configurationItem is null) + return; + + var shippingItem = configurationItem.ChildNodes.FirstOrDefault(node => node.SystemName.Equals("Shipping")); + var widgetsItem = configurationItem.ChildNodes.FirstOrDefault(node => node.SystemName.Equals("Widgets")); + if (shippingItem is null && widgetsItem is null) + return; + + var index = shippingItem is not null ? configurationItem.ChildNodes.IndexOf(shippingItem) : -1; + if (index < 0) + index = widgetsItem is not null ? configurationItem.ChildNodes.IndexOf(widgetsItem) : -1; + if (index < 0) + return; + + configurationItem.ChildNodes.Insert(index + 1, new AdminMenuItem + { + + Visible = true, + SystemName = "FruitBank", + Title = await _localizationService.GetResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.AI"), + IconClass = "far fa-dot-circle", + ChildNodes = new List + { + new() + { + + Visible = true, + SystemName = "FruitBank", + Title = await _localizationService.GetResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.Configure"), + IconClass = "far fa-circle", + Url = _adminMenu.GetMenuItemUrl("FruitBankPlugin", "Configure"), + + } + } + }); + + } } }