This commit is contained in:
Loretta 2025-09-29 13:36:20 +02:00
commit 6e4e8f99e0
25 changed files with 2284 additions and 68 deletions

View File

@ -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<IActionResult> Index()
{
//display a warning to a store owner if there are some error
var customer = await _workContext.GetCurrentCustomerAsync();
var hideCard = await _genericAttributeService.GetAttributeAsync<bool>(customer, NopCustomerDefaults.HideConfigurationStepsAttribute);
var closeCard = await _genericAttributeService.GetAttributeAsync<bool>(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<string>(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<IActionResult> NopCommerceNewsHideAdv()
{
_adminAreaSettings.HideAdvertisementsOnAdminArea = !_adminAreaSettings.HideAdvertisementsOnAdminArea;
await _settingService.SaveSettingAsync(_adminAreaSettings);
return Content("Setting changed");
}
public virtual async Task<IActionResult> GetPopularSearchTerm()
{
var model = new DataTablesModel();
model = await _homeModelFactory.PreparePopularSearchTermReportModelAsync(model);
return PartialView("Table", model);
}
public virtual async Task<IActionResult> GetBestsellersBriefReportByAmount()
{
var model = new DataTablesModel();
model = await _homeModelFactory.PrepareBestsellersBriefReportByAmountModelAsync(model);
return PartialView("Table", model);
}
public virtual async Task<IActionResult> GetBestsellersBriefReportByQuantity()
{
var model = new DataTablesModel();
model = await _homeModelFactory.PrepareBestsellersBriefReportByQuantityModelAsync(model);
return PartialView("Table", model);
}
public virtual async Task<IActionResult> GetLatestOrders()
{
var model = new DataTablesModel();
model = await _homeModelFactory.PrepareLatestOrdersModelAsync(model);
return PartialView("Table", model);
}
public virtual async Task<IActionResult> GetOrderIncomplete()
{
var model = new DataTablesModel();
model = await _homeModelFactory.PrepareOrderIncompleteModelAsync(model);
return PartialView("Table", model);
}
public virtual async Task<IActionResult> GetOrderAverage()
{
var model = new DataTablesModel();
model = await _homeModelFactory.PrepareOrderAverageModelAsync(model);
return PartialView("Table", model);
}
#endregion
}
}

View File

@ -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<IActionResult> List(List<int> orderStatuses = null, List<int> paymentStatuses = null, List<int> 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<IActionResult> 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<IActionResult> OrderList(OrderSearchModel searchModel)
//{
// //prepare model
// var model = await _orderModelFactory.PrepareOrderListModelAsync(searchModel);
// return Json(model);
//}
}
}

View File

@ -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<IActionResult> List()
{
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return AccessDeniedView();
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Invoice/List.cshtml");
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,15 @@
using Nop.Web.Framework.Models;
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models;
/// <summary>
/// Represents a dashboard model
/// </summary>
public partial record DashboardModel : BaseNopModel
{
#region Properties
public bool IsLoggedInAsVendor { get; set; }
#endregion
}

View File

@ -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<bool>(await workContext.GetCurrentCustomerAsync(), closeCardAttributeName);
//active menu item (system name)
NopHtml.SetActiveMenuItemSystemName("Dashboard");
}
<link rel="stylesheet" href="~/lib_npm/ionicons/css/ionicons.min.css" />
<script src="~/js/admin.table.js" asp-location="Footer"></script>
<div class="content-header">
<h1>
Fruit Bank @T("Admin.Dashboard")
</h1>
</div>
<section class="content">
<div class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@if (!closeConfigurationStepsCard)
{
<div class="row">
<div class="col-md-12">
@await Html.PartialAsync("~/Areas/Admin/Views/Home/_ConfigurationSteps.cshtml")
</div>
</div>
}
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardTop, additionalData = Model })
@if (!Model.IsLoggedInAsVendor)
{
<div class="row">
<div class="col-md-12">
@await Component.InvokeAsync(typeof(NopCommerceNewsViewComponent))
</div>
</div>
}
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardNewsAfter, additionalData = Model })
@if (!Model.IsLoggedInAsVendor && canManageOrders && canManageCustomers && canManageProducts && canManageReturnRequests)
{
<div class="row">
<div class="col-md-12">
@await Component.InvokeAsync(typeof(CommonStatisticsViewComponent))
</div>
</div>
}
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardCommonstatisticsAfter, additionalData = Model })
@if (!Model.IsLoggedInAsVendor && (canManageOrders || canManageCustomers))
{
<div class="row">
@if (!Model.IsLoggedInAsVendor && canManageOrders)
{
<div class="col-md-6">
@await Html.PartialAsync("~/Areas/Admin/Views/Home/_OrderStatistics.cshtml")
</div>
}
@if (!Model.IsLoggedInAsVendor && canManageCustomers)
{
<div class="col-md-6">
@await Html.PartialAsync("~/Areas/Admin/Views/Home/_CustomerStatistics.cshtml")
</div>
}
</div>
}
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardCustomerorderchartsAfter, additionalData = Model })
@if (!Model.IsLoggedInAsVendor && canManageOrders)
{
<div class="row">
<div class="col-md-8">
@await Html.PartialAsync("~/Areas/Admin/Views/Home/_OrderAverageReport.cshtml")
</div>
<div class="col-md-4">
@await Html.PartialAsync("~/Areas/Admin/Views/Home/_OrderIncompleteReport.cshtml")
</div>
</div>
}
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardOrderreportsAfter, additionalData = Model })
@if (!Model.IsLoggedInAsVendor && (canManageOrders || canManageProducts))
{
<div class="row">
@if (canManageOrders)
{
<div class="col-md-8">
@await Html.PartialAsync("~/Areas/Admin/Views/Home/_LatestOrders.cshtml")
</div>
}
<div class="col-md-4">
@if (canManageProducts)
{
@await Html.PartialAsync("~/Areas/Admin/Views/Home/_PopularSearchTermsReport.cshtml")
}
</div>
</div>
}
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardLatestordersSearchtermsAfter, additionalData = Model })
@if (canManageOrders)
{
<div class="row">
<div class="col-md-6">
@await Html.PartialAsync("~/Areas/Admin/Views/Home/_BestsellersBriefReportByQuantity.cshtml")
</div>
<div class="col-md-6">
@await Html.PartialAsync("~/Areas/Admin/Views/Home/_BestsellersBriefReportByAmount.cshtml")
</div>
</div>
}
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.DashboardBottom, additionalData = Model })
</div>
</div>
</div>
</div>
</section>
<nop-alert asp-alert-id="loadOrderStatisticsAlert" asp-alert-message="@T("Admin.SalesReport.OrderStatistics.Alert.FailedLoad")" />
<nop-alert asp-alert-id="loadCustomerStatisticsAlert" asp-alert-message="@T("Admin.Reports.Customers.CustomerStatistics.Alert.FailedLoad")" />

View File

@ -0,0 +1,32 @@
@{
// Layout = "_AdminLayout";
ViewBag.PageTitle = "Invoice";
NopHtml.SetActiveMenuItemSystemName("Invoices");
}
<div class="content-header clearfix">
<h1 class="float-left">
<i class="fas fa-shipping-fast"></i>
Invoices
</h1>
</div>
<section class="content">
<div class="container-fluid">
<div class="form-horizontal">
<div class="cards-group">
<div class="card card-default">
<div class="card-header">
<h3 class="card-title">
Shipment Management
</h3>
</div>
<div class="card-body">
<p>This is our custom Invoices page.</p>
<p>Add shipment functionality here.</p>
</div>
</div>
</div>
</div>
</div>
</section>

View File

@ -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<bool>(await workContext.GetCurrentCustomerAsync(), hideSearchBlockAttributeName);
}
@if (Model.LicenseCheckModel.BlockPages != true)
{
<form asp-controller="Order" asp-action="List" method="post">
<div class="content-header clearfix">
<h1 class="float-left">
@T("Admin.Orders")
</h1>
<div class="float-right">
<div class="btn-group">
<button type="button" class="btn btn-success">
<i class="fas fa-download"></i>
@T("Admin.Common.Export")
</button>
<button type="button" class="btn btn-success dropdown-toggle dropdown-icon" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">&nbsp;</span>
</button>
<ul class="dropdown-menu" role="menu">
<li class="dropdown-item">
<button asp-action="ExportXml" type="submit" name="exportxml-all">
<i class="far fa-file-code"></i>
@T("Admin.Common.ExportToXml.All")
</button>
</li>
<li class="dropdown-item">
<button type="button" id="exportxml-selected">
<i class="far fa-file-code"></i>
@T("Admin.Common.ExportToXml.Selected")
</button>
</li>
<li class="dropdown-divider"></li>
<li class="dropdown-item">
<button asp-action="ExportExcel" type="submit" name="exportexcel-all">
<i class="far fa-file-excel"></i>
@T("Admin.Common.ExportToExcel.All")
</button>
</li>
<li class="dropdown-item">
<button type="button" id="exportexcel-selected">
<i class="far fa-file-excel"></i>
@T("Admin.Common.ExportToExcel.Selected")
</button>
</li>
</ul>
</div>
<button type="button" name="importexcel" class="btn bg-olive" data-toggle="modal" data-target="#importexcel-window">
<i class="fas fa-upload"></i>
@T("Admin.Common.Import")
</button>
<div class="btn-group">
<button type="button" class="btn btn-info">
<i class="far fa-file-pdf"></i>
@T("Admin.Orders.PdfInvoices")
</button>
<button type="button" class="btn btn-info dropdown-toggle dropdown-icon" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">&nbsp;</span>
</button>
<ul class="dropdown-menu" role="menu">
<li class="dropdown-item">
<button asp-action="PdfInvoice" type="submit" name="pdf-invoice-all">
@T("Admin.Orders.PdfInvoices.All")
</button>
</li>
<li class="dropdown-item">
<button type="button" id="pdf-invoice-selected">
@T("Admin.Orders.PdfInvoices.Selected")
</button>
</li>
</ul>
</div>
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.OrderListButtons, additionalData = Model })
</div>
</div>
<section class="content">
<div class="container-fluid">
<div class="form-horizontal">
<div class="cards-group">
<div class="card card-default card-search">
<div class="card-body">
<div class="row search-row @(!hideSearchBlock ? "opened" : "")" data-hideAttribute="@hideSearchBlockAttributeName">
<div class="search-text">@T("Admin.Common.Search")</div>
<div class="icon-search"><i class="fas fa-magnifying-glass" aria-hidden="true"></i></div>
<div class="icon-collapse"><i class="far fa-angle-@(!hideSearchBlock ? "up" : "down")" aria-hidden="true"></i></div>
</div>
<div class="search-body @(hideSearchBlock ? "closed" : "")">
<div class="row">
<div class="col-md-5">
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="StartDate" />
</div>
<div class="col-md-8">
<nop-editor asp-for="StartDate" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="EndDate" />
</div>
<div class="col-md-8">
<nop-editor asp-for="EndDate" />
</div>
</div>
<div class="form-group row" @(Model.AvailableWarehouses.SelectionIsNotPossible() ? Html.Raw("style=\"display:none\"") : null)>
<div class="col-md-4">
<nop-label asp-for="WarehouseId" />
</div>
<div class="col-md-8">
<nop-select asp-for="WarehouseId" asp-items="Model.AvailableWarehouses" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="ProductId" />
</div>
<div class="col-md-8">
<input type="text" id="search-product-name" autocomplete="off" class="form-control" />
<span id="search-product-friendly-name"></span>
<button type="button" id="search-product-clear" class="btn bg-gray" style="display: none; margin-top: 5px;">@T("Admin.Common.Clear")</button>
<input asp-for="ProductId" autocomplete="off" style="display: none;" />
<script>
$(function() {
$('#search-product-name').autocomplete({
delay: 500,
minLength: 3,
source: '@Url.Action("SearchAutoComplete", "SearchComplete")',
select: function(event, ui) {
$('#@Html.IdFor(model => model.ProductId)').val(ui.item.productid);
$('#search-product-friendly-name').text(ui.item.label);
$('#search-product-clear').show();
return false;
}
});
//remove button
$('#search-product-clear').click(function() {
$('#@Html.IdFor(model => model.ProductId)').val('0');
$('#search-product-friendly-name').text('');
$('#search-product-clear').hide();
return false;
});
});
</script>
</div>
</div>
<div class="form-group row" @(Model.IsLoggedInAsVendor ? Html.Raw("style='display: none;'") : null)>
<div class="col-md-4">
<nop-label asp-for="OrderStatusIds" />
</div>
<div class="col-md-8">
<nop-select asp-for="OrderStatusIds" asp-items="Model.AvailableOrderStatuses" asp-multiple="true" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="PaymentStatusIds" />
</div>
<div class="col-md-8">
<nop-select asp-for="PaymentStatusIds" asp-items="Model.AvailablePaymentStatuses" asp-multiple="true" />
</div>
</div>
<div class="form-group row" @(Model.IsLoggedInAsVendor ? Html.Raw("style='display: none;'") : null)>
<div class="col-md-4">
<nop-label asp-for="ShippingStatusIds" />
</div>
<div class="col-md-8">
<nop-select asp-for="ShippingStatusIds" asp-items="Model.AvailableShippingStatuses" asp-multiple="true" />
</div>
</div>
</div>
<div class="col-md-7">
<div class="form-group row" @(Model.HideStoresList ? Html.Raw("style=\"display:none\"") : null)>
<div class="col-md-4">
<nop-label asp-for="StoreId" />
</div>
<div class="col-md-8">
<nop-select asp-for="StoreId" asp-items="Model.AvailableStores" />
</div>
</div>
<div class="form-group row" @(Model.AvailableVendors.SelectionIsNotPossible() || Model.IsLoggedInAsVendor ? Html.Raw("style='display: none;'") : null)>
<div class="col-md-4">
<nop-label asp-for="VendorId" />
</div>
<div class="col-md-8">
<nop-select asp-for="VendorId" asp-items="Model.AvailableVendors" />
</div>
</div>
@if (Model.BillingPhoneEnabled)
{
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="BillingPhone" />
</div>
<div class="col-md-8">
<nop-editor asp-for="BillingPhone" />
</div>
</div>
}
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="BillingEmail" />
</div>
<div class="col-md-8">
<nop-editor asp-for="BillingEmail" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="BillingLastName" />
</div>
<div class="col-md-8">
<nop-editor asp-for="BillingLastName" />
</div>
</div>
<div class="form-group row" @(Model.AvailableCountries.SelectionIsNotPossible() ? Html.Raw("style=\"display:none\"") : null)>
<div class="col-md-4">
<nop-label asp-for="BillingCountryId" />
</div>
<div class="col-md-8">
<nop-select asp-for="BillingCountryId" asp-items="Model.AvailableCountries" />
</div>
</div>
<div class="form-group row" @(Model.AvailablePaymentMethods.SelectionIsNotPossible() || Model.IsLoggedInAsVendor ? Html.Raw("style='display: none;'") : null)>
<div class="col-md-4">
<nop-label asp-for="PaymentMethodSystemName" />
</div>
<div class="col-md-8">
<nop-select asp-for="PaymentMethodSystemName" asp-items="Model.AvailablePaymentMethods" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="OrderNotes" />
</div>
<div class="col-md-8">
<nop-editor asp-for="OrderNotes" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="GoDirectlyToCustomOrderNumber" />
</div>
<div class="col-md-8">
<div class="input-group input-group-short">
<nop-editor asp-for="GoDirectlyToCustomOrderNumber" />
<span class="input-group-append">
<button type="submit" id="go-to-order-by-number" name="go-to-order-by-number" class="btn btn-info btn-flat">
@T("Admin.Common.Go")
</button>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="text-center col-12">
<button type="button" id="search-orders" class="btn btn-primary btn-search">
<i class="fas fa-magnifying-glass"></i>
@T("Admin.Common.Search")
</button>
</div>
</div>
</div>
</div>
</div>
<div class="card card-default">
<div class="card-header">
<h1>RTTTTTTTTTT</h1>
</div>
<div class="card-body">
<nop-doc-reference asp-string-resource="@T("Admin.Documentation.Reference.Orders", Docs.Orders + Utm.OnAdmin)" />
@{
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<FilterParameter>
{
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<ColumnProperty>
{
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)
<script>
function renderColumnOrderStatus(data, type, row, meta) {
try {
console.log("OrderStatus render - data:", data, "row:", row);
if (!row.OrderStatusId) {
console.error("OrderStatusId is missing from row data");
return data || '';
}
var color;
switch (row.OrderStatusId) {
case 10: color = 'yellow'; break;
case 20: color = 'blue'; break;
case 30: color = 'green'; break;
case 40: color = 'red'; break;
default: color = 'gray';
}
return '<span class="grid-report-item ' + color + '">' + data + '</span>';
} catch (e) {
console.error("Error in renderColumnOrderStatus:", e);
return data || '';
}
}
function renderColumnCustomer(data, type, row, meta) {
console.log("Hello World 2");
var link = '@Url.Content("~/Admin/Customer/Edit/")' + row.CustomerId;
var textRenderer = $.fn.dataTable.render.text().display;
return `${textRenderer(row.CustomerFullName)} <br /><a href="${link}">${data}</a > `;
}
function renderColumnNeedsMeasurement(data, type, row, meta) {
if(data === true) {
return '<span class="badge badge-warning">Yes</span>';
}
return '<span class="badge badge-secondary">No</span>';
}
$(function() {
$("#@Html.IdFor(model => model.GoDirectlyToCustomOrderNumber)").keydown(
function(event) {
if (event.keyCode === 13) {
$("#go-to-order-by-number").trigger("click");
return false;
}
});
});
function ordersfootercallback(tfoot, data, start, end, display) {
//update order totals summary
var postData = {
StartDate: $('#@Html.IdFor(model => model.StartDate)').val(),
EndDate: $('#@Html.IdFor(model => model.EndDate)').val(),
OrderStatusIds: $('#@Html.IdFor(model => model.OrderStatusIds)').val(),
PaymentStatusIds: $('#@Html.IdFor(model => model.PaymentStatusIds)').val(),
ShippingStatusIds: $('#@Html.IdFor(model => model.ShippingStatusIds)').val(),
StoreId: $('#@Html.IdFor(model => model.StoreId)').val(),
VendorId: $('#@Html.IdFor(model => model.VendorId)').val(),
WarehouseId: $('#@Html.IdFor(model => model.WarehouseId)').val(),
BillingEmail: $('#@Html.IdFor(model => model.BillingEmail)').val(),
BillingPhone: $('#@Html.IdFor(model => model.BillingPhone)').val(),
BillingLastName: $('#@Html.IdFor(model => model.BillingLastName)').val(),
BillingCountryId: $('#@Html.IdFor(model => model.BillingCountryId)').val(),
PaymentMethodSystemName: $('#@Html.IdFor(model => model.PaymentMethodSystemName)').val(),
ProductId: $('#@Html.IdFor(model => model.ProductId)').val(),
OrderNotes: $('#@Html.IdFor(model => model.OrderNotes)').val()
};
addAntiForgeryToken(postData);
$.ajax({
cache: false,
type: "POST",
url: "@(Url.Action("ReportAggregates", "Order"))",
data: postData,
success: function (data, textStatus, jqXHR) {
if (data) {
for (var key in data) {
var reportSummary = '<div><strong>@T("Admin.Orders.Report.Summary").Text</strong></div>' +
'<div>@T("Admin.Orders.Report.Profit").Text <span>' + data['AggregatorProfit'] +'</span></div>' +
'<div>@T("Admin.Orders.Report.Shipping").Text <span>' + data['AggregatorShipping'] + '</span></div>' +
'<div>@T("Admin.Orders.Report.Tax").Text <span>' + data['AggregatorTax'] + '</span></div>' +
'<div>@T("Admin.Orders.Report.Total").Text <span>' + data['AggregatorTotal'] + '</span></div>'
var orderTotalsColumn = $('#orders-grid').DataTable().column(@(orderSummaryColumnNumber));
$(orderTotalsColumn.footer()).html(reportSummary);
}
}
}
});
}
</script>
</div>
</div>
</div>
</div>
</div>
</section>
</form>
}
<script>
$(function() {
var displayModal = @((Model.LicenseCheckModel.DisplayWarning == true || Model.LicenseCheckModel?.BlockPages == true).ToString().ToLower());
if (displayModal){
$('#license-window').modal({
backdrop: 'static',
keyboard: false
});
$('#license-window').on('shown.bs.modal', function (event) {
var modalCloseEl = $(this).find('button.close');
var closeTextEl = $('span', modalCloseEl);
var startFrom = 5;
closeTextEl.text(startFrom);
const timer = setInterval(function() {
if (startFrom-- > 0)
closeTextEl.text(startFrom);
}, 1000);
setTimeout(function() {
closeTextEl.html('&times;');
modalCloseEl.on('click', function() {
$('#license-window').modal('hide')
});
clearInterval(timer);
}, startFrom*1000);
});
}
});
</script>
<div id="license-window" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
@Html.Raw(Model.LicenseCheckModel?.WarningText)
</div>
</div>
</div>
@*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*@
<form asp-controller="Order" asp-action="ExportXmlSelected" method="post" id="export-xml-selected-form">
<input type="hidden" id="selectedIds" name="selectedIds" value="" />
</form>
<script>
$(function() {
$('#exportxml-selected').click(function (e) {
e.preventDefault();
var ids = selectedIds.join(",");
if (!ids) {
$('#exportXmlSelected-info').text("@T("Admin.Orders.NoOrders")");
$("#exportXmlSelected").trigger("click");
}
else {
$('#export-xml-selected-form #selectedIds').val(ids);
$('#export-xml-selected-form').submit();
updateTable('#orders-grid');
}
return false;
});
});
</script>
<nop-alert asp-alert-id="exportXmlSelected" />
@*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*@
<form asp-controller="Order" asp-action="ExportExcelSelected" method="post" id="export-excel-selected-form">
<input type="hidden" id="selectedIds" name="selectedIds" value="" />
</form>
<script>
$(function() {
$('#exportexcel-selected').click(function (e) {
e.preventDefault();
var ids = selectedIds.join(",");
if (!ids) {
$('#exportExcelSelected-info').text("@T("Admin.Orders.NoOrders")");
$("#exportExcelSelected").trigger("click");
}
else {
$('#export-excel-selected-form #selectedIds').val(ids);
$('#export-excel-selected-form').submit();
updateTable('#orders-grid');
}
return false;
});
});
</script>
<nop-alert asp-alert-id="exportExcelSelected" />
@*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*@
<form asp-controller="Order" asp-action="PdfInvoiceSelected" method="post" id="pdf-invoice-selected-form">
<input type="hidden" id="selectedIds" name="selectedIds" value="" />
</form>
<script>
$(function() {
$('#pdf-invoice-selected').click(function (e) {
e.preventDefault();
var ids = selectedIds.join(",");
if (!ids) {
$('#pdfInvoiceSelected-info').text("@T("Admin.Orders.NoOrders")");
$("#pdfInvoiceSelected").trigger("click");
}
else {
$('#pdf-invoice-selected-form #selectedIds').val(ids);
$('#pdf-invoice-selected-form').submit();
updateTable('#orders-grid');
}
return false;
});
});
</script>
<nop-alert asp-alert-id="pdfInvoiceSelected" />
@*import orders form*@
<div id="importexcel-window" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="importexcel-window-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="importexcel-window-title">@T("Admin.Common.ImportFromExcel")</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<form asp-controller="Order" asp-action="ImportFromXlsx" method="post" enctype="multipart/form-data">
<div class="form-horizontal">
<div class="modal-body">
<ul class="common-list">
<li>
<em>@T("Admin.Orders.List.ImportFromExcelTip")</em>
</li>
<li>
<em>@T("Admin.Common.ImportFromExcel.ManyRecordsWarning")</em>
</li>
</ul>
<div class="form-group row">
<div class="col-md-2">
<div class="label-wrapper">
<label class="col-form-label">
@T("Admin.Common.ExcelFile")
</label>
</div>
</div>
<div class="col-md-10">
<input type="file" id="importexcelfile" name="importexcelfile" class="form-control" />
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">
@T("Admin.Common.ImportFromExcel")
</button>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,4 @@
@{
Layout = "_AdminLayout";
}
<h1>TEST VIEW FROM PLUGIN</h1>

View File

@ -0,0 +1,362 @@
@model Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.CreateShipmentModel
@{
// Layout = "_AdminLayout";
ViewBag.PageTitle = "Create Shipment";
NopHtml.SetActiveMenuItemSystemName("Shipments.Create");
}
<form asp-action="Create" method="post" enctype="multipart/form-data">
<div class="content-header clearfix">
<h1 class="float-left">
<i class="fas fa-plus"></i>
Create New Shipment
</h1>
<div class="float-right">
<button type="submit" name="save" class="btn btn-primary">
<i class="far fa-save"></i>
Save
</button>
<a asp-action="List" class="btn btn-default">
<i class="fas fa-arrow-left"></i>
Back to List
</a>
</div>
</div>
<section class="content">
<div class="container-fluid">
<div class="form-horizontal">
<div class="cards-group">
<!-- Basic Information -->
<div class="card card-default">
<div class="card-header">
<div class="card-title">Shipment Information</div>
</div>
<div class="card-body">
<div class="form-group row">
<div class="col-md-3">
<nop-label asp-for="ShipmentName" />
</div>
<div class="col-md-9">
<nop-editor asp-for="ShipmentName" required="true" />
<span asp-validation-for="ShipmentName"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-label asp-for="Description" />
</div>
<div class="col-md-9">
<textarea asp-for="Description" class="form-control" rows="3"></textarea>
<span asp-validation-for="Description"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-label asp-for="ShipmentDate" />
</div>
<div class="col-md-9">
<nop-editor asp-for="ShipmentDate" asp-template="DateTime" required="true" />
<span asp-validation-for="ShipmentDate"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-label asp-for="TrackingNumber" />
</div>
<div class="col-md-9">
<nop-editor asp-for="TrackingNumber" />
<span asp-validation-for="TrackingNumber"></span>
</div>
</div>
</div>
</div>
<!-- File Upload Section -->
<div class="card card-default">
<div class="card-header">
<div class="card-title">
<i class="fas fa-file-pdf"></i>
Upload Documents (PDF only)
</div>
</div>
<div class="card-body">
<!-- Drag & Drop Zone -->
<div id="dropZone" class="drop-zone">
<div class="drop-zone-content">
<i class="fas fa-cloud-upload-alt fa-3x text-muted"></i>
<h4>Drag & Drop PDF files here</h4>
<p class="text-muted">or click to select files</p>
<input type="file" id="fileInput" multiple accept=".pdf" style="display: none;">
<button type="button" id="selectFilesBtn" class="btn btn-outline-primary">
<i class="fas fa-folder-open"></i>
Select Files
</button>
</div>
</div>
<!-- Upload Progress -->
<div id="uploadProgress" style="display: none;">
<div class="progress mb-3">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
</div>
<p id="uploadStatus" class="text-center"></p>
</div>
<!-- Uploaded Files List -->
<div id="uploadedFiles" class="mt-3">
<h5>Uploaded Files:</h5>
<div id="filesList" class="list-group">
<!-- Uploaded files will appear here -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</form>
<style>
.drop-zone {
border: 2px dashed #dee2e6;
border-radius: 10px;
padding: 40px;
text-align: center;
transition: all 0.3s ease;
background-color: #f8f9fa;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.drop-zone:hover,
.drop-zone.drag-over {
border-color: #007bff;
background-color: rgba(0, 123, 255, 0.05);
}
.drop-zone-content {
pointer-events: none;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
margin-bottom: 5px;
border: 1px solid #dee2e6;
border-radius: 5px;
background-color: #fff;
}
.file-item .file-info {
display: flex;
align-items: center;
}
.file-item .file-info i {
margin-right: 10px;
color: #dc3545;
}
.file-item .file-actions button {
margin-left: 5px;
}
.upload-error {
border-color: #dc3545;
background-color: rgba(220, 53, 69, 0.05);
}
</style>
<script>
$(document).ready(function() {
let uploadedFiles = [];
// File input and drop zone elements
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const selectFilesBtn = document.getElementById('selectFilesBtn');
// Click to select files
selectFilesBtn.addEventListener('click', () => fileInput.click());
dropZone.addEventListener('click', (e) => {
if (e.target === dropZone || e.target.closest('.drop-zone-content')) {
fileInput.click();
}
});
// File input change
fileInput.addEventListener('change', handleFiles);
// Drag and drop events
dropZone.addEventListener('dragover', handleDragOver);
dropZone.addEventListener('dragenter', handleDragEnter);
dropZone.addEventListener('dragleave', handleDragLeave);
dropZone.addEventListener('drop', handleDrop);
function handleDragOver(e) {
e.preventDefault();
dropZone.classList.add('drag-over');
}
function handleDragEnter(e) {
e.preventDefault();
dropZone.classList.add('drag-over');
}
function handleDragLeave(e) {
e.preventDefault();
if (!dropZone.contains(e.relatedTarget)) {
dropZone.classList.remove('drag-over');
}
}
function handleDrop(e) {
e.preventDefault();
dropZone.classList.remove('drag-over');
const files = e.dataTransfer.files;
processFiles(files);
}
function handleFiles(e) {
const files = e.target.files;
processFiles(files);
}
function processFiles(files) {
Array.from(files).forEach(file => {
if (file.type === 'application/pdf') {
uploadFile(file);
} else {
showError(`"${file.name}" is not a PDF file. Only PDF files are allowed.`);
}
});
}
function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
// Show upload progress
showUploadProgress(`Uploading ${file.name}...`);
$.ajax({
url: '@Url.Action("UploadFile")',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(result) {
hideUploadProgress();
if (result.success) {
uploadedFiles.push(result);
addFileToList(result);
showSuccess(`"${result.fileName}" uploaded successfully`);
} else {
showError(`Upload failed: ${result.errorMessage}`);
}
},
error: function(xhr, status, error) {
hideUploadProgress();
showError(`Upload failed: ${error}`);
}
});
}
function addFileToList(file) {
const filesList = document.getElementById('filesList');
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-info">
<i class="fas fa-file-pdf"></i>
<span>${file.fileName}</span>
</div>
<div class="file-actions">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="viewFile('${file.filePath}')">
<i class="fas fa-eye"></i> View
</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteFile('${file.filePath}', this)">
<i class="fas fa-trash"></i> Delete
</button>
</div>
`;
filesList.appendChild(fileItem);
// Show the uploaded files section
document.getElementById('uploadedFiles').style.display = 'block';
}
function showUploadProgress(message) {
const progressDiv = document.getElementById('uploadProgress');
const statusText = document.getElementById('uploadStatus');
statusText.textContent = message;
progressDiv.style.display = 'block';
// Animate progress bar
const progressBar = progressDiv.querySelector('.progress-bar');
progressBar.style.width = '100%';
}
function hideUploadProgress() {
document.getElementById('uploadProgress').style.display = 'none';
const progressBar = document.querySelector('#uploadProgress .progress-bar');
progressBar.style.width = '0%';
}
function showError(message) {
// Use nopCommerce's notification system
displayBarNotification(message, 'error', 5000);
}
function showSuccess(message) {
// Use nopCommerce's notification system
displayBarNotification(message, 'success', 3000);
}
// Global functions for file actions
window.viewFile = function(filePath) {
window.open(filePath, '_blank');
};
window.deleteFile = function(filePath, button) {
if (confirm('Are you sure you want to delete this file?')) {
$.post('@Url.Action("DeleteUploadedFile")', { filePath: filePath })
.done(function(result) {
if (result.success) {
// Remove from uploaded files array
uploadedFiles = uploadedFiles.filter(f => f.filePath !== filePath);
// Remove from DOM
const fileItem = button.closest('.file-item');
fileItem.remove();
// Hide uploaded files section if no files remain
if (uploadedFiles.length === 0) {
document.getElementById('uploadedFiles').style.display = 'none';
}
showSuccess('File deleted successfully');
} else {
showError('Failed to delete file: ' + result.message);
}
})
.fail(function() {
showError('Failed to delete file');
});
}
};
});
</script>

View File

@ -0,0 +1,32 @@
@{
// Layout = "_AdminLayout";
ViewBag.PageTitle = "Shipments";
NopHtml.SetActiveMenuItemSystemName("Shipments");
}
<div class="content-header clearfix">
<h1 class="float-left">
<i class="fas fa-shipping-fast"></i>
Shipments
</h1>
</div>
<section class="content">
<div class="container-fluid">
<div class="form-horizontal">
<div class="cards-group">
<div class="card card-default">
<div class="card-header">
<h3 class="card-title">
Shipment Management
</h3>
</div>
<div class="card-body">
<p>This is our custom Shipments page.</p>
<p>Add shipment functionality here.</p>
</div>
</div>
</div>
</div>
</div>
</section>

View File

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

View File

@ -0,0 +1,3 @@
@{
Layout = "_AdminLayout";
}

View File

@ -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<OrderSearchModel> 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<OrderListModel> PrepareOrderListModelAsync(OrderSearchModel searchModel)
{
// get the default model first
var baseModel = await base.PrepareOrderListModelAsync(searchModel);
var extendedRows = new List<OrderModelExtended>();
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<OrderModel>().ToList(),
// RecordsTotal = baseModel.RecordsTotal
//};
var model = new OrderListModel();
CopyModelHelper.CopyPublicProperties(baseModel, model);
model.Data = extendedRows.ToList<OrderModel>(); // Different cast approach
return model;
}
// example async custom logic
private async Task<bool> 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);
}
}
}

View File

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

View File

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

View File

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

View File

@ -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<CustomModelFactory, ICustomerModelFactory>();
services.AddScoped<IPriceCalculationService, CustomPriceCalculationService>();
services.AddScoped<PriceCalculationService, CustomPriceCalculationService>();
services.AddScoped<IConsumer<OrderPlacedEvent>, EventConsumer>();
services.AddScoped<IOrderMeasurementService, OrderMeasurementService>();
services.AddScoped<PendingMeasurementCheckoutFilter>();
services.AddScoped<OrderListModel, OrderListModelExtended>();
services.AddScoped<OrderModel, OrderModelExtended>();
services.AddScoped<OrderSearchModel, OrderSearchModelExtended>();
services.AddScoped<IOrderModelFactory, CustomOrderModelFactory>();
services.AddScoped<IGenericAttributeService, GenericAttributeService>();
services.AddControllersWithViews(options =>
{
options.Filters.AddService<PendingMeasurementCheckoutFilter>();

View File

@ -22,10 +22,58 @@ public class RouteProvider : IRouteProvider
defaults: new { controller = "FruitBankPluginAdmin", action = "Configure", area = AreaNames.ADMIN });
//endpointRouteBuilder.MapHub<FruitBankHub>("/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 });
}
/// <summary>
/// Gets a priority of route provider
/// </summary>
public int Priority => 0;
public int Priority => 3000;
}

View File

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

View File

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

View File

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

View File

@ -20,6 +20,16 @@
</ItemGroup>
<ItemGroup>
<Content Include="Areas\Admin\Views\Index.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Areas\Admin\Views\_ViewStart.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="logo.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -33,7 +43,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Areas\Admin\Views\_ViewImports.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Views\_ViewImports.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@ -55,9 +65,8 @@
<Folder Include="Areas\Admin\Validators\" />
<Folder Include="Domains\Entities\" />
<Folder Include="Extensions\" />
<Folder Include="Factories\" />
<Folder Include="Models\" />
<Folder Include="Validators\" />
<Folder Include="Validators\" />
<Folder Include="Views\Admin\" />
</ItemGroup>
<ItemGroup>
@ -138,6 +147,21 @@
<None Update="Areas\Admin\Views\Configure\Configure.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Areas\Admin\Views\Invoice\List.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Areas\Admin\Views\Order\Test.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Areas\Admin\Views\Order\List.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Areas\Admin\Views\Shipment\Create.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Areas\Admin\Views\Shipment\List.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Views\Checkout\PendingMeasurementWarning.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>

View File

@ -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<OrderPlacedEvent>
public class EventConsumer : BaseAdminMenuCreatedEventConsumer, IConsumer<OrderPlacedEvent>, IConsumer<AdminMenuCreatedEvent>
{
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<IPlugin> 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<AdminMenuItem>
{
new()
{
Visible = true,
SystemName = "FruitBank",
Title = await _localizationService.GetResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.Configure"),
IconClass = "far fa-circle",
Url = _adminMenu.GetMenuItemUrl("FruitBankPlugin", "Configure"),
}
}
});
}
}
}