diff --git a/Directory.Packages.props b/Directory.Packages.props index 448f8b957d..bb1c0d7b65 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,75 +5,80 @@ - + + - - - - - - - + + + + + + + - - - - - + + + + + - - + + - - - - - - - + + + + + + + - + - - - - - - - + + + + + + + - + - - + + - + - - + + - - + + - - - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Business/Grand.Business.Common/Grand.Business.Common.csproj b/src/Business/Grand.Business.Common/Grand.Business.Common.csproj index 39e4981080..e9684ff464 100644 --- a/src/Business/Grand.Business.Common/Grand.Business.Common.csproj +++ b/src/Business/Grand.Business.Common/Grand.Business.Common.csproj @@ -1,7 +1,8 @@  - + + diff --git a/src/Business/Grand.Business.Common/Utilities/PowerExcelExtensions.cs b/src/Business/Grand.Business.Common/Utilities/PowerExcelExtensions.cs index efd1757ba8..33ace35212 100644 --- a/src/Business/Grand.Business.Common/Utilities/PowerExcelExtensions.cs +++ b/src/Business/Grand.Business.Common/Utilities/PowerExcelExtensions.cs @@ -7,14 +7,6 @@ namespace Grand.Business.Common.Utilities; internal static class PowerExcelExtensions { - [Obsolete("This method is obsolete. Use the new method instead.")] - internal static IEnumerable Rows(this ISheet sheet) - { - var e = sheet.GetRowEnumerator(); - while (e.MoveNext()) - yield return e.Current as IRow; - } - internal static bool IsIdenticalTo(this MemberInfo memberInfo, MemberInfo other) { if (memberInfo == null || other == null) return false; diff --git a/src/Business/Grand.Business.Common/Utilities/PowerExcelMapper.cs b/src/Business/Grand.Business.Common/Utilities/PowerExcelMapper.cs index 33406fd154..9f0cfe8fcf 100644 --- a/src/Business/Grand.Business.Common/Utilities/PowerExcelMapper.cs +++ b/src/Business/Grand.Business.Common/Utilities/PowerExcelMapper.cs @@ -47,7 +47,7 @@ private IEnumerable Fetch(ISheet sheet, Type type, Func var firstRowNumber = HeaderRowNumber; if (!HeaderRow) - firstRowNumber = sheet.Rows().Where(r => r.RowNum >= MinRowNumber && r.RowNum <= MaxRowNumber) + firstRowNumber = sheet.Where(r => r.RowNum >= MinRowNumber && r.RowNum <= MaxRowNumber) .OrderByDescending(r => r.LastCellNum).FirstOrDefault()?.RowNum ?? 0; var firstRow = sheet.GetRow(firstRowNumber); diff --git a/src/Tests/Grand.Infrastructure.Tests/Caching/Redis/RedisMessageBusTests.cs b/src/Tests/Grand.Infrastructure.Tests/Caching/Redis/RedisMessageBusTests.cs index 656c4fcbbd..22187b0890 100644 --- a/src/Tests/Grand.Infrastructure.Tests/Caching/Redis/RedisMessageBusTests.cs +++ b/src/Tests/Grand.Infrastructure.Tests/Caching/Redis/RedisMessageBusTests.cs @@ -110,7 +110,8 @@ public async Task PublishAsync_SubscriberThrows_DoesNotPropagateException() { _subscriberMock.Setup(s => s.PublishAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .ThrowsAsync(new RedisConnectionException(ConnectionFailureType.SocketFailure, "connection lost")); + .ThrowsAsync(new RedisConnectionException(ConnectionFailureType.SocketFailure, CommandFlags.None, + "connection lost", null, CommandStatus.Unknown)); await _bus.PublishAsync(new MessageEvent { Key = "key", MessageType = (int)MessageEventType.RemoveKey }); } diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseAddressAttributeControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseAddressAttributeControllerTests.cs index ea684ba0aa..be386517da 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseAddressAttributeControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseAddressAttributeControllerTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using Grand.Business.Core.Interfaces.Common.Addresses; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Common; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCheckoutAttributeControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCheckoutAttributeControllerTests.cs index 2cbcb740e2..7fa92346fa 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCheckoutAttributeControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCheckoutAttributeControllerTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using Grand.Business.Core.Interfaces.Catalog.Directory; using Grand.Business.Core.Interfaces.Checkout.CheckoutAttributes; using Grand.Business.Core.Interfaces.Common.Directory; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseDiscountControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseDiscountControllerTests.cs index a8709fa610..44f39f0ea7 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseDiscountControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseDiscountControllerTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using Grand.Business.Core.Interfaces.Catalog.Brands; using Grand.Business.Core.Interfaces.Catalog.Categories; using Grand.Business.Core.Interfaces.Catalog.Collections; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseEmailAccountControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseEmailAccountControllerTests.cs index 32c4edcfd7..b007b83a04 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseEmailAccountControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseEmailAccountControllerTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Messages; using Grand.Domain.Messages; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductAttributeControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductAttributeControllerTests.cs index 6779f9df21..7333738f85 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductAttributeControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductAttributeControllerTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseTaxCategoryControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseTaxCategoryControllerTests.cs index f52e80a3d3..28c1fc6483 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseTaxCategoryControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseTaxCategoryControllerTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using Grand.Business.Core.Interfaces.Catalog.Tax; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Common.Stores; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedBlogCategoryDataScopeTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedBlogCategoryDataScopeTests.cs index ef73e7cbb2..52c8dabcc4 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedBlogCategoryDataScopeTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedBlogCategoryDataScopeTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using Grand.Domain.Blogs; using Grand.Web.AdminShared.Interfaces; using Grand.Web.AdminShared.Services; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedBlogPostDataScopeTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedBlogPostDataScopeTests.cs index 066b512ad3..e87b48444b 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedBlogPostDataScopeTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedBlogPostDataScopeTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using Grand.Domain.Blogs; using Grand.Web.AdminShared.Interfaces; using Grand.Web.AdminShared.Services; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedEmailAccountDataScopeTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedEmailAccountDataScopeTests.cs index 678892bf77..369ae92695 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedEmailAccountDataScopeTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedEmailAccountDataScopeTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using Grand.Domain.Customers; using Grand.Domain.Messages; using Grand.Infrastructure; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedTaxCategoryDataScopeTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedTaxCategoryDataScopeTests.cs index 3046425383..0626705d68 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedTaxCategoryDataScopeTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/RoutedTaxCategoryDataScopeTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using Grand.Domain.Customers; using Grand.Domain.Tax; using Grand.Infrastructure; diff --git a/src/Web/Grand.Web.Admin/Controllers/DiscountController.cs b/src/Web/Grand.Web.Admin/Controllers/DiscountController.cs index 67163fb9ab..569ef6eda0 100644 --- a/src/Web/Grand.Web.Admin/Controllers/DiscountController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/DiscountController.cs @@ -56,10 +56,10 @@ public class DiscountController( public async Task VendorList(DataSourceRequest command, string discountId, [FromServices] IVendorService vendorService) { - var discount = await discountService.GetDiscountById(discountId); + var discount = await DiscountService.GetDiscountById(discountId); if (discount == null) throw new Exception("No discount found with the specified id"); - if (!await scope.CanView(discount)) + if (!await Scope.CanView(discount)) return new JsonResult(new DataSourceResult { Errors = "Access denied" }); var vendors = await vendorService.GetAllVendorsByDiscount(discount.Id); @@ -73,10 +73,10 @@ public async Task VendorList(DataSourceRequest command, string di public async Task VendorDelete(string discountId, string vendorId, [FromServices] IVendorService vendorService) { - var discount = await discountService.GetDiscountById(discountId); + var discount = await DiscountService.GetDiscountById(discountId); if (discount == null) throw new Exception("No discount found with the specified id"); - if (!await scope.HasAccess(discount)) + if (!await Scope.HasAccess(discount)) return new JsonResult(new DataSourceResult { Errors = "Access denied" }); var vendor = await vendorService.GetVendorById(vendorId); @@ -84,7 +84,7 @@ public async Task VendorDelete(string discountId, string vendorId throw new Exception("No vendor found with the specified id"); if (ModelState.IsValid) { - await discountViewModelService.DeleteVendor(discount, vendor); + await DiscountViewModelService.DeleteVendor(discount, vendor); return new JsonResult(""); } return ErrorForKendoGridJson(ModelState); @@ -93,10 +93,10 @@ public async Task VendorDelete(string discountId, string vendorId [PermissionAuthorizeAction(PermissionActionName.Edit)] public async Task VendorAddPopup(string discountId) { - var discount = await discountService.GetDiscountById(discountId); + var discount = await DiscountService.GetDiscountById(discountId); if (discount == null) throw new Exception("No discount found with the specified id"); - if (!await scope.HasAccess(discount)) + if (!await Scope.HasAccess(discount)) return new JsonResult(new DataSourceResult { Errors = "Access denied" }); return View(new DiscountModel.AddVendorToDiscountModel()); @@ -122,13 +122,13 @@ public async Task VendorAddPopupList(DataSourceRequest command, [HttpPost] public async Task VendorAddPopup(DiscountModel.AddVendorToDiscountModel model) { - var discount = await discountService.GetDiscountById(model.DiscountId); + var discount = await DiscountService.GetDiscountById(model.DiscountId); if (discount == null) throw new Exception("No discount found with the specified id"); - if (!await scope.HasAccess(discount)) + if (!await Scope.HasAccess(discount)) return Content("Access denied"); - if (model.SelectedVendorIds != null) await discountViewModelService.InsertVendorToDiscountModel(model); + if (model.SelectedVendorIds != null) await DiscountViewModelService.InsertVendorToDiscountModel(model); return Content(""); } diff --git a/src/Web/Grand.Web.Admin/Controllers/EmailAccountController.cs b/src/Web/Grand.Web.Admin/Controllers/EmailAccountController.cs index 2906ac8687..0f137ff6af 100644 --- a/src/Web/Grand.Web.Admin/Controllers/EmailAccountController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/EmailAccountController.cs @@ -42,7 +42,7 @@ public IActionResult List() [PermissionAuthorizeAction(PermissionActionName.List)] public async Task List(DataSourceRequest command) { - var emailAccounts = await emailAccountService.GetAllEmailAccounts(scope.DefaultStoreId ?? "", + var emailAccounts = await EmailAccountService.GetAllEmailAccounts(Scope.DefaultStoreId ?? "", pageIndex: command.Page - 1, pageSize: command.PageSize); var emailAccountModels = emailAccounts.Select(x => x.ToModel()).ToList(); foreach (var eam in emailAccountModels) @@ -59,7 +59,7 @@ public async Task List(DataSourceRequest command) [PermissionAuthorizeAction(PermissionActionName.Edit)] public async Task MarkAsDefaultEmail(string id) { - var defaultEmailAccount = await emailAccountService.GetEmailAccountById(id); + var defaultEmailAccount = await EmailAccountService.GetEmailAccountById(id); if (defaultEmailAccount != null) { emailAccountSettings.DefaultEmailAccountId = defaultEmailAccount.Id; diff --git a/src/Web/Grand.Web.Admin/Controllers/MessageTemplateController.cs b/src/Web/Grand.Web.Admin/Controllers/MessageTemplateController.cs index a1ebd1c8eb..0ec8513955 100644 --- a/src/Web/Grand.Web.Admin/Controllers/MessageTemplateController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/MessageTemplateController.cs @@ -43,7 +43,7 @@ public async Task List() { var model = new MessageTemplateListModel(); model.AvailableStores.Add(new SelectListItem - { Text = translationService.GetResource("Admin.Common.All"), Value = "" }); + { Text = TranslationService.GetResource("Admin.Common.All"), Value = "" }); foreach (var s in await storeService.GetAllStores()) model.AvailableStores.Add(new SelectListItem { Text = s.Shortcut, Value = s.Id }); @@ -54,7 +54,7 @@ public async Task List() [HttpPost] public async Task List(DataSourceRequest command, MessageTemplateListModel model) { - var messageTemplates = await messageTemplateService.GetAllMessageTemplates( + var messageTemplates = await MessageTemplateService.GetAllMessageTemplates( model.SearchStoreId, keywords: model.Name, pageIndex: command.Page - 1, diff --git a/src/Web/Grand.Web.Admin/Controllers/OnlineCustomerController.cs b/src/Web/Grand.Web.Admin/Controllers/OnlineCustomerController.cs index eee3393560..8ece19efe5 100644 --- a/src/Web/Grand.Web.Admin/Controllers/OnlineCustomerController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/OnlineCustomerController.cs @@ -28,5 +28,5 @@ public class OnlineCustomerController( IContextAccessor contextAccessor) : BaseOnlineCustomerController(customerService, dateTimeService, customerSettings, translationService, contextAccessor) { - protected override string SalesEmployeeIdFilter => contextAccessor.WorkContext.CurrentCustomer.SeId; + protected override string SalesEmployeeIdFilter => ContextAccessor.WorkContext.CurrentCustomer.SeId; } diff --git a/src/Web/Grand.Web.Admin/Controllers/ReportsController.cs b/src/Web/Grand.Web.Admin/Controllers/ReportsController.cs index 79ff93d9b3..c7a058b45b 100644 --- a/src/Web/Grand.Web.Admin/Controllers/ReportsController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/ReportsController.cs @@ -64,7 +64,7 @@ public class ReportsController( /// this check explicitly. public override async Task CountryReport() { - if (!await permissionService.Authorize(StandardPermission.ManageCustomers)) + if (!await PermissionService.Authorize(StandardPermission.ManageCustomers)) return AccessDeniedView(); return await base.CountryReport(); } @@ -74,7 +74,7 @@ public override async Task CountryReport() /// this one) but not on Store's or Vendor's. public override async Task Customer() { - if (!await permissionService.Authorize(StandardPermission.ManageCustomers)) + if (!await PermissionService.Authorize(StandardPermission.ManageCustomers)) return AccessDeniedView(); return await base.Customer(); } @@ -84,7 +84,7 @@ public override async Task Customer() [HttpPost] public async Task PopularSearchTermsReport(DataSourceRequest command) { - if (!await permissionService.Authorize(StandardPermission.ManageProducts)) + if (!await PermissionService.Authorize(StandardPermission.ManageProducts)) return AccessDeniedView(); var searchTermRecordLines = await searchTermService.GetStats(command.Page - 1, command.PageSize); diff --git a/src/Web/Grand.Web.Admin/Controllers/TaxController.cs b/src/Web/Grand.Web.Admin/Controllers/TaxController.cs index dae826550d..d3cb6cb4e5 100644 --- a/src/Web/Grand.Web.Admin/Controllers/TaxController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/TaxController.cs @@ -129,16 +129,16 @@ public async Task Settings() model.TaxDisplayTypeValues = enumTranslationService.ToSelectList(taxSettings.TaxDisplayType); //tax categories - var taxCategories = await taxCategoryService.GetAllTaxCategories(); + var taxCategories = await TaxCategoryService.GetAllTaxCategories(); model.TaxCategories.Add(new SelectListItem { - Text = translationService.GetResource("Admin.Configuration.Tax.Settings.TaxCategories.None"), Value = "" + Text = TranslationService.GetResource("Admin.Configuration.Tax.Settings.TaxCategories.None"), Value = "" }); foreach (var tc in taxCategories) model.TaxCategories.Add(new SelectListItem { Text = tc.Name, Value = tc.Id }); //EU VAT countries model.EuVatShopCountries.Add(new SelectListItem - { Text = translationService.GetResource("Admin.Address.SelectCountry"), Value = "" }); + { Text = TranslationService.GetResource("Admin.Address.SelectCountry"), Value = "" }); foreach (var c in await countryService.GetAllCountries(showHidden: true)) model.EuVatShopCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id, Selected = c.Id == taxSettings.EuVatShopCountryId }); @@ -151,7 +151,7 @@ public async Task Settings() model.DefaultTaxAddress = new AddressModel(); model.DefaultTaxAddress.AvailableCountries.Add(new SelectListItem - { Text = translationService.GetResource("Admin.Address.SelectCountry"), Value = "" }); + { Text = TranslationService.GetResource("Admin.Address.SelectCountry"), Value = "" }); foreach (var c in await countryService.GetAllCountries(showHidden: true)) model.DefaultTaxAddress.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id, Selected = defaultAddress != null && c.Id == defaultAddress.CountryId }); @@ -185,7 +185,7 @@ public async Task Settings(TaxSettingsModel model) //now clear cache await ClearCache(); - Success(translationService.GetResource("Admin.Configuration.Updated")); + Success(TranslationService.GetResource("Admin.Configuration.Updated")); return RedirectToAction("Settings"); } @@ -197,8 +197,8 @@ public async Task Categories() { var model = new TaxCategoryListModel(); model.AvailableStores.Add(new SelectListItem - { Text = translationService.GetResource("Admin.Common.All"), Value = "" }); - foreach (var s in await storeService.GetAllStores()) + { Text = TranslationService.GetResource("Admin.Common.All"), Value = "" }); + foreach (var s in await StoreService.GetAllStores()) model.AvailableStores.Add(new SelectListItem { Text = s.Shortcut, Value = s.Id }); return View(model); } diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseBlogController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseBlogController.cs index 0737726e96..d705531288 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseBlogController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseBlogController.cs @@ -50,6 +50,8 @@ protected virtual void EditWarningCheck(BlogPost blogPost) { } // Exposed for host subclasses: primary-constructor parameters are not visible to derived classes // by name in C#. + protected IBlogService BlogService => blogService; + protected IDateTimeService DateTimeService => dateTimeService; protected ITranslationService TranslationService => translationService; protected IAdminDataScope PostScope => postScope; protected IAdminDataScope CategoryScope => categoryScope; diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseDiscountController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseDiscountController.cs index f0c7ce20f0..4b7a3663ca 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseDiscountController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseDiscountController.cs @@ -33,6 +33,18 @@ public abstract class BaseDiscountController( IAdminDataScope scope) : BaseController { + /// Exposed so host controllers that extend this base (Admin's DiscountController) can + /// reuse the same instance for their own vendor-region actions instead of re-capturing their + /// own copy of the primary-constructor parameter (which would trigger CS9107 - the parameter + /// stored twice, once here and once in the derived class). + protected IDiscountViewModelService DiscountViewModelService => discountViewModelService; + + /// See . + protected IDiscountService DiscountService => discountService; + + /// See . + protected IAdminDataScope Scope => scope; + #region Discounts public IActionResult Index() => RedirectToAction("List"); diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseEmailAccountController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseEmailAccountController.cs index c3ffec27cd..e04e73d5fd 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseEmailAccountController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseEmailAccountController.cs @@ -22,6 +22,15 @@ public abstract class BaseEmailAccountController( IAdminDataScope scope) : BaseController { + /// Exposed so host controllers that extend this base (Admin/Store's + /// EmailAccountController) can reuse the same instance for their own List action instead of + /// re-capturing their own copy of the primary-constructor parameter (which would trigger + /// CS9107 - the parameter stored twice, once here and once in the derived class). + protected IEmailAccountService EmailAccountService => emailAccountService; + + /// See . + protected IAdminDataScope Scope => scope; + #region Create [PermissionAuthorizeAction(PermissionActionName.Create)] diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseFullReportsController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseFullReportsController.cs index 975fff3102..1b10620a38 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseFullReportsController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseFullReportsController.cs @@ -101,9 +101,9 @@ protected virtual async Task> GetReportOrderPe (30, "Admin.Reports.Period.month"), (365, "Admin.Reports.Period.year") }) { - var reportPeriod = await orderReportService.GetOrderPeriodReport(days, scope.StoreId); + var reportPeriod = await OrderReportService.GetOrderPeriodReport(days, Scope.StoreId); report.Add(new OrderPeriodReportLineModel { - Period = translationService.GetResource(resourceKey), + Period = TranslationService.GetResource(resourceKey), Count = reportPeriod.Count, Amount = reportPeriod.Amount }); @@ -130,7 +130,7 @@ public virtual async Task ReportOrderTimeChart(DataSourceRequest if (!await permissionService.Authorize(StandardPermission.ManageOrders)) return Content(""); - var model = await orderReportService.GetOrderByTimeReport(scope.StoreId, startDate, endDate); + var model = await OrderReportService.GetOrderByTimeReport(Scope.StoreId, startDate, endDate); return Json(new DataSourceResult { Data = model }); } @@ -141,22 +141,22 @@ public virtual async Task OrderAverageReportList(DataSourceReques return Content(""); var report = new List { - await orderReportService.OrderAverageReport(scope.StoreId, (int)OrderStatusSystem.Pending), - await orderReportService.OrderAverageReport(scope.StoreId, (int)OrderStatusSystem.Processing), - await orderReportService.OrderAverageReport(scope.StoreId, (int)OrderStatusSystem.Complete), - await orderReportService.OrderAverageReport(scope.StoreId, (int)OrderStatusSystem.Cancelled) + await OrderReportService.OrderAverageReport(Scope.StoreId, (int)OrderStatusSystem.Pending), + await OrderReportService.OrderAverageReport(Scope.StoreId, (int)OrderStatusSystem.Processing), + await OrderReportService.OrderAverageReport(Scope.StoreId, (int)OrderStatusSystem.Complete), + await OrderReportService.OrderAverageReport(Scope.StoreId, (int)OrderStatusSystem.Cancelled) }; - var statuses = await orderStatusService.GetAll(); + var statuses = await OrderStatusService.GetAll(); var model = new List(); foreach (var x in report) model.Add(new OrderAverageReportLineSummaryModel { OrderStatus = statuses.FirstOrDefault(y => y.StatusId == x.OrderStatus)?.Name, - SumTodayOrders = priceFormatter.FormatPrice(x.SumTodayOrders, await currencyService.GetPrimaryStoreCurrency()), - SumThisWeekOrders = priceFormatter.FormatPrice(x.SumThisWeekOrders, await currencyService.GetPrimaryStoreCurrency()), - SumThisMonthOrders = priceFormatter.FormatPrice(x.SumThisMonthOrders, await currencyService.GetPrimaryStoreCurrency()), - SumThisYearOrders = priceFormatter.FormatPrice(x.SumThisYearOrders, await currencyService.GetPrimaryStoreCurrency()), - SumAllTimeOrders = priceFormatter.FormatPrice(x.SumAllTimeOrders, await currencyService.GetPrimaryStoreCurrency()) + SumTodayOrders = PriceFormatter.FormatPrice(x.SumTodayOrders, await CurrencyService.GetPrimaryStoreCurrency()), + SumThisWeekOrders = PriceFormatter.FormatPrice(x.SumThisWeekOrders, await CurrencyService.GetPrimaryStoreCurrency()), + SumThisMonthOrders = PriceFormatter.FormatPrice(x.SumThisMonthOrders, await CurrencyService.GetPrimaryStoreCurrency()), + SumThisYearOrders = PriceFormatter.FormatPrice(x.SumThisYearOrders, await CurrencyService.GetPrimaryStoreCurrency()), + SumAllTimeOrders = PriceFormatter.FormatPrice(x.SumAllTimeOrders, await CurrencyService.GetPrimaryStoreCurrency()) }); var gridModel = new DataSourceResult { Data = model, Total = model.Count }; @@ -171,28 +171,28 @@ public virtual async Task ReportLatestOrder(DataSourceRequest com return Content(""); var orders = await orderService.SearchOrders( - storeId: scope.StoreId, + storeId: Scope.StoreId, createdFromUtc: startDate, createdToUtc: endDate, pageIndex: command.Page - 1, pageSize: command.PageSize); - var statuses = await orderStatusService.GetAll(); + var statuses = await OrderStatusService.GetAll(); var items = new List(); foreach (var x in orders) { - var store = await storeService.GetStoreById(x.StoreId); + var store = await StoreService.GetStoreById(x.StoreId); items.Add(new OrderModel { Id = x.Id, OrderNumber = x.OrderNumber, StoreName = store != null ? store.Shortcut : "Unknown", - OrderTotal = priceFormatter.FormatPrice(x.OrderTotal, await currencyService.GetPrimaryStoreCurrency()), + OrderTotal = PriceFormatter.FormatPrice(x.OrderTotal, await CurrencyService.GetPrimaryStoreCurrency()), OrderStatus = statuses.FirstOrDefault(y => y.StatusId == x.OrderStatusId)?.Name, - PaymentStatus = enumTranslationService.GetTranslationEnum(x.PaymentStatusId), - ShippingStatus = enumTranslationService.GetTranslationEnum(x.ShippingStatusId), + PaymentStatus = EnumTranslationService.GetTranslationEnum(x.PaymentStatusId), + ShippingStatus = EnumTranslationService.GetTranslationEnum(x.ShippingStatusId), CustomerEmail = x.BillingAddress.Email, CustomerFullName = $"{x.BillingAddress.FirstName} {x.BillingAddress.LastName}", - CreatedOn = dateTimeService.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) + CreatedOn = DateTimeService.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) }); } @@ -202,7 +202,7 @@ public virtual async Task ReportLatestOrder(DataSourceRequest com /// Area constant for the "View" link on each row: reproduces each host's original /// literal (Admin used Constants.AreaAdmin, Store used Constants.AreaStore) via - /// scope.ResourceKeyPrefix, which happens to already equal "Admin" for both Admin and Store (Store + /// Scope.ResourceKeyPrefix, which happens to already equal "Admin" for both Admin and Store (Store /// reuses Admin's resource keys — Task 2's StoreReportDataScope) — but the *area* value must be /// the actual routing area, not the resource-key prefix, so this uses /// ViewContext.RouteData.Values["area"] directly instead, matching the same @@ -218,30 +218,30 @@ public virtual async Task OrderIncompleteReportList(DataSourceReq var area = ControllerContext.RouteData.Values["area"]?.ToString(); var model = new List(); - var psPending = await orderReportService.GetOrderAverageReportLine(scope.StoreId, ps: PaymentStatus.Pending, + var psPending = await OrderReportService.GetOrderAverageReportLine(Scope.StoreId, ps: PaymentStatus.Pending, ignoreCancelledOrders: true); model.Add(new OrderIncompleteReportLineModel { - Item = translationService.GetResource("Admin.Reports.Incomplete.TotalUnpaidOrders"), + Item = TranslationService.GetResource("Admin.Reports.Incomplete.TotalUnpaidOrders"), Count = psPending.CountOrders, - Total = priceFormatter.FormatPrice(psPending.SumOrders, await currencyService.GetPrimaryStoreCurrency()), + Total = PriceFormatter.FormatPrice(psPending.SumOrders, await CurrencyService.GetPrimaryStoreCurrency()), ViewLink = Url.Action("List", "Order", new { paymentStatusId = ((int)PaymentStatus.Pending).ToString(), area }) }); - var ssPending = await orderReportService.GetOrderAverageReportLine(scope.StoreId, ss: ShippingStatus.Pending, + var ssPending = await OrderReportService.GetOrderAverageReportLine(Scope.StoreId, ss: ShippingStatus.Pending, ignoreCancelledOrders: true); model.Add(new OrderIncompleteReportLineModel { - Item = translationService.GetResource("Admin.Reports.Incomplete.TotalNotShippedOrders"), + Item = TranslationService.GetResource("Admin.Reports.Incomplete.TotalNotShippedOrders"), Count = ssPending.CountOrders, - Total = priceFormatter.FormatPrice(ssPending.SumOrders, await currencyService.GetPrimaryStoreCurrency()), + Total = PriceFormatter.FormatPrice(ssPending.SumOrders, await CurrencyService.GetPrimaryStoreCurrency()), ViewLink = Url.Action("List", "Order", new { shippingStatusId = ((int)ShippingStatus.Pending).ToString(), area }) }); - var osPending = await orderReportService.GetOrderAverageReportLine(scope.StoreId, os: (int)OrderStatusSystem.Pending, + var osPending = await OrderReportService.GetOrderAverageReportLine(Scope.StoreId, os: (int)OrderStatusSystem.Pending, ignoreCancelledOrders: true); model.Add(new OrderIncompleteReportLineModel { - Item = translationService.GetResource("Admin.Reports.Incomplete.TotalIncompleteOrders"), + Item = TranslationService.GetResource("Admin.Reports.Incomplete.TotalIncompleteOrders"), Count = osPending.CountOrders, - Total = priceFormatter.FormatPrice(osPending.SumOrders, await currencyService.GetPrimaryStoreCurrency()), + Total = PriceFormatter.FormatPrice(osPending.SumOrders, await CurrencyService.GetPrimaryStoreCurrency()), ViewLink = Url.Action("List", "Order", new { orderStatusId = ((int)OrderStatusSystem.Pending).ToString(), area }) }); @@ -257,10 +257,10 @@ public virtual async Task OrderIncompleteReportList(DataSourceReq public virtual async Task ReportBestCustomersByNumberOfOrdersList(DataSourceRequest command, BestCustomersReportModel model) { - if (!string.IsNullOrEmpty(scope.StoreId)) model.StoreId = scope.StoreId; + if (!string.IsNullOrEmpty(Scope.StoreId)) model.StoreId = Scope.StoreId; - var (bestCustomerReportLineModels, totalCount) = await customerReportViewModelService - .PrepareBestCustomerReportLineModel(model, 2, command.Page, command.PageSize, scope.VendorId); + var (bestCustomerReportLineModels, totalCount) = await CustomerReportViewModelService + .PrepareBestCustomerReportLineModel(model, 2, command.Page, command.PageSize, Scope.VendorId); var gridModel = new DataSourceResult { Data = bestCustomerReportLineModels.ToList(), Total = totalCount }; return Json(gridModel); @@ -269,7 +269,7 @@ public virtual async Task ReportBestCustomersByNumberOfOrdersList [HttpPost] public virtual async Task ReportRegisteredCustomersList(DataSourceRequest command) { - var model = await customerReportViewModelService.GetReportRegisteredCustomersModel(scope.StoreId, scope.VendorId); + var model = await CustomerReportViewModelService.GetReportRegisteredCustomersModel(Scope.StoreId, Scope.VendorId); var gridModel = new DataSourceResult { Data = model, Total = model.Count }; return Json(gridModel); } @@ -278,7 +278,7 @@ public virtual async Task ReportRegisteredCustomersList(DataSourc public virtual async Task ReportCustomerTimeChart(DataSourceRequest command, DateTime? startDate, DateTime? endDate) { - var model = await customerReportService.GetCustomerByTimeReport(scope.StoreId, startDate, endDate); + var model = await customerReportService.GetCustomerByTimeReport(Scope.StoreId, startDate, endDate); return Json(new DataSourceResult { Data = model }); } diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseMessageTemplateController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseMessageTemplateController.cs index 08d81071ad..7029562e48 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseMessageTemplateController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseMessageTemplateController.cs @@ -27,6 +27,15 @@ public abstract class BaseMessageTemplateController( EmailAccountSettings emailAccountSettings) : BaseController { + /// Exposed so host controllers that extend this base (Admin/Store's + /// MessageTemplateController) can reuse the same instance for their own List actions instead + /// of re-capturing their own copy of the primary-constructor parameter (which would trigger + /// CS9107 - the parameter stored twice, once here and once in the derived class). + protected IMessageTemplateService MessageTemplateService => messageTemplateService; + + /// See . + protected ITranslationService TranslationService => translationService; + public IActionResult Index() => RedirectToAction("List"); #region Create diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseNewsController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseNewsController.cs index edf63ebff9..7857d4a6d7 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseNewsController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseNewsController.cs @@ -36,6 +36,8 @@ protected virtual void EditWarningCheck(NewsItem newsItem) { } // Exposed for host subclasses: primary-constructor parameters are not visible to derived classes // by name in C#. + protected INewsService NewsService => newsService; + protected IDateTimeService DateTimeService => dateTimeService; protected ITranslationService TranslationService => translationService; protected IAdminDataScope Scope => scope; diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseOnlineCustomerController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseOnlineCustomerController.cs index 0ac9b553c8..0057b7d1f5 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseOnlineCustomerController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseOnlineCustomerController.cs @@ -29,6 +29,13 @@ public abstract class BaseOnlineCustomerController( IContextAccessor contextAccessor) : BaseController { + /// Exposed so host controllers that extend this base (Admin's + /// OnlineCustomerController) can reuse the same instance for their own SalesEmployeeIdFilter + /// override instead of re-capturing their own copy of the primary-constructor parameter (which + /// would trigger CS9107 - the parameter stored twice, once here and once in the derived + /// class). + protected IContextAccessor ContextAccessor => contextAccessor; + /// /// Sales-Manager restriction on the online-customers list. Admin restricts to the current /// Sales-Manager's own customers; Store has no such concept and never restricts by it. diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderManagementController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderManagementController.cs index 82315436c1..cd6fc4dc89 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderManagementController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderManagementController.cs @@ -68,9 +68,9 @@ public async Task SaveOrderTags(OrderModel orderModel) try { - await orderViewModelService.SaveOrderTags(order, orderModel.OrderTags); + await OrderViewModelService.SaveOrderTags(order, orderModel.OrderTags); var model = new OrderModel(); - await orderViewModelService.PrepareOrderDetailsModel(model, order); + await OrderViewModelService.PrepareOrderDetailsModel(model, order); return RedirectToAction("Edit", "Order", new { id = order.Id }); } catch (Exception exception) @@ -93,15 +93,15 @@ public async Task ChangeOrderStatus(string id, OrderModel model) ArgumentNullException.ThrowIfNull(status); order.OrderStatusId = model.OrderStatusId; - await orderService.UpdateOrder(order); + await OrderService.UpdateOrder(order); - await orderService.InsertOrderNote(new OrderNote { + await OrderService.InsertOrderNote(new OrderNote { Note = $"Order status has been edited. New status: {status.Name}", DisplayToCustomer = false, OrderId = order.Id }); model = new OrderModel(); - await orderViewModelService.PrepareOrderDetailsModel(model, order); + await OrderViewModelService.PrepareOrderDetailsModel(model, order); return RedirectToAction("Edit", "Order", new { id }); } catch (Exception exc) @@ -134,15 +134,15 @@ public async Task EditOrderTotals(string id, OrderModel model) order.OrderDiscount = model.OrderTotalDiscountValue; order.OrderTotal = model.OrderTotalValue; order.CurrencyRate = model.CurrencyRate; - await orderService.UpdateOrder(order); + await OrderService.UpdateOrder(order); - await orderService.InsertOrderNote(new OrderNote { + await OrderService.InsertOrderNote(new OrderNote { Note = "Order totals have been edited", DisplayToCustomer = false, OrderId = order.Id }); - await orderViewModelService.PrepareOrderDetailsModel(model, order); + await OrderViewModelService.PrepareOrderDetailsModel(model, order); return RedirectToAction("Edit", "Order", new { id }); } @@ -154,14 +154,14 @@ public async Task EditShippingMethod(string id, OrderModel model) if (denied != null) return denied; order.ShippingMethod = model.ShippingMethod; - await orderService.UpdateOrder(order); + await OrderService.UpdateOrder(order); - await orderService.InsertOrderNote(new OrderNote { + await OrderService.InsertOrderNote(new OrderNote { Note = "Shipping method has been edited", DisplayToCustomer = false, OrderId = order.Id }); - await orderViewModelService.PrepareOrderDetailsModel(model, order); + await OrderViewModelService.PrepareOrderDetailsModel(model, order); await SaveSelectedTabIndex(persistForTheNextRequest: true); return RedirectToAction("Edit", "Order", new { id }); @@ -174,8 +174,8 @@ public async Task EditUserFields(string id, OrderModel model) if (denied != null) return denied; order.UserFields = model.UserFields; - await orderService.UpdateOrder(order); - await orderViewModelService.PrepareOrderDetailsModel(model, order); + await OrderService.UpdateOrder(order); + await OrderViewModelService.PrepareOrderDetailsModel(model, order); await SaveSelectedTabIndex(persistForTheNextRequest: true); return RedirectToAction("Edit", "Order", new { id }); @@ -305,9 +305,9 @@ public async Task ResetDownloadCount(string id, string orderItemI var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId) ?? throw new ArgumentException("No order item found with the specified id"); orderItem.DownloadCount = 0; - await orderService.UpdateOrder(order); + await OrderService.UpdateOrder(order); var model = new OrderModel(); - await orderViewModelService.PrepareOrderDetailsModel(model, order); + await OrderViewModelService.PrepareOrderDetailsModel(model, order); await SaveSelectedTabIndex(persistForTheNextRequest: true); return RedirectToAction("Edit", "Order", new { id }); @@ -323,9 +323,9 @@ public async Task ActivateDownloadItem(string id, string orderIte var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId) ?? throw new ArgumentException("No order item found with the specified id"); orderItem.IsDownloadActivated = !orderItem.IsDownloadActivated; - await orderService.UpdateOrder(order); + await OrderService.UpdateOrder(order); var model = new OrderModel(); - await orderViewModelService.PrepareOrderDetailsModel(model, order); + await OrderViewModelService.PrepareOrderDetailsModel(model, order); await SaveSelectedTabIndex(persistForTheNextRequest: true); return RedirectToAction("Edit", "Order", new { id }); @@ -365,7 +365,7 @@ public async Task UploadLicenseFilePopup(OrderModel.UploadLicense var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == model.OrderItemId) ?? throw new ArgumentException("No order item found with the specified id"); orderItem.LicenseDownloadId = !string.IsNullOrEmpty(model.LicenseDownloadId) ? model.LicenseDownloadId : null; - await orderService.UpdateOrder(order); + await OrderService.UpdateOrder(order); model.RefreshPage = true; return View(model); @@ -381,7 +381,7 @@ public async Task DeleteLicenseFilePopup(OrderModel.UploadLicense var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == model.OrderItemId) ?? throw new ArgumentException("No order item found with the specified id"); orderItem.LicenseDownloadId = null; - await orderService.UpdateOrder(order); + await OrderService.UpdateOrder(order); return RedirectToAction("Edit", "Order", new { id = model.OrderId }); } @@ -396,7 +396,7 @@ public async Task AddProductToOrder(string orderId) var (order, denied) = await LoadAuthorizedOrder(orderId); if (denied != null) return denied; - var model = await orderViewModelService.PrepareAddOrderProductModel(order); + var model = await OrderViewModelService.PrepareAddOrderProductModel(order); return View(model); } @@ -411,7 +411,7 @@ public async Task AddProductToOrder( var gridModel = new Grand.Web.Common.DataSource.DataSourceResult(); var products = (await productService.SearchProducts(categoryIds: categoryIds, - storeId: scope.DefaultStoreId, + storeId: Scope.DefaultStoreId, brandId: model.SearchBrandId, collectionId: model.SearchCollectionId, productType: model.SearchProductTypeId > 0 ? (Grand.Domain.Catalog.ProductType?)model.SearchProductTypeId : null, @@ -434,7 +434,7 @@ public async Task AddProductToOrderDetails(string orderId, string var (order, denied) = await LoadAuthorizedOrder(orderId); if (denied != null) return denied; - var model = await orderViewModelService.PrepareAddProductToOrderModel(order, productId); + var model = await OrderViewModelService.PrepareAddProductToOrderModel(order, productId); return View(model); } @@ -445,10 +445,10 @@ public async Task AddProductToOrderDetails(AddProductToOrderModel var (order, denied) = await LoadAuthorizedOrder(model.OrderId); if (denied != null) return denied; - var warnings = await orderViewModelService.AddProductToOrderDetails(model); + var warnings = await OrderViewModelService.AddProductToOrderDetails(model); if (!warnings.Any()) return RedirectToAction("Edit", "Order", new { id = model.OrderId }); - var result = await orderViewModelService.PrepareAddProductToOrderModel(order, model.ProductId); + var result = await OrderViewModelService.PrepareAddProductToOrderModel(order, model.ProductId); result.Warnings.AddRange(warnings); return View(result); } @@ -477,7 +477,7 @@ public async Task AddressEdit(string addressId, string orderId, b if (address == null) throw new ArgumentException("No address found with the specified id", nameof(addressId)); - var model = await orderViewModelService.PrepareOrderAddressModel(order, address); + var model = await OrderViewModelService.PrepareOrderAddressModel(order, address); model.BillingAddress = billingAddress; return View(model); } @@ -505,12 +505,12 @@ public async Task AddressEdit(OrderAddressModel model, if (ModelState.IsValid) { var customAttributes = await model.Address.ParseCustomAddressAttributes(addressAttributeParser, addressAttributeService); - await orderViewModelService.UpdateOrderAddress(order, address, model, customAttributes); + await OrderViewModelService.UpdateOrderAddress(order, address, model, customAttributes); return RedirectToAction("AddressEdit", new { addressId = model.Address.Id, orderId = model.OrderId, model.BillingAddress }); } - model = await orderViewModelService.PrepareOrderAddressModel(order, address); + model = await OrderViewModelService.PrepareOrderAddressModel(order, address); return View(model); } @@ -522,15 +522,15 @@ public async Task AddressEdit(OrderAddressModel model, [HttpPost] public async Task OrderNotesSelect(string orderId, Grand.Web.Common.DataSource.DataSourceRequest command) { - var order = await orderService.GetOrderById(orderId) + var order = await OrderService.GetOrderById(orderId) ?? throw new ArgumentException("No order found with the specified id"); // Preserved host divergence: Admin's original throws for both not-found and Sales-Manager // denial; Store's original throws only for not-found and soft-denies (empty content) for // store-mismatch. Unifying these into one behavior would be a real change for one host - // deliberately not done here. See plan's Global Constraints. - if (!await scope.HasAccess(order)) return Content(""); + if (!await Scope.HasAccess(order)) return Content(""); - var orderNoteModels = await orderViewModelService.PrepareOrderNotes(order); + var orderNoteModels = await OrderViewModelService.PrepareOrderNotes(order); var gridModel = new Grand.Web.Common.DataSource.DataSourceResult { Data = orderNoteModels, Total = orderNoteModels.Count @@ -541,10 +541,10 @@ public async Task OrderNotesSelect(string orderId, Grand.Web.Comm [PermissionAuthorizeAction(PermissionActionName.Edit)] public async Task OrderNoteAdd(string orderId, string downloadId, bool displayToCustomer, string message) { - var order = await orderService.GetOrderById(orderId); - if (order == null || !await scope.HasAccess(order)) return Json(new { Result = false }); + var order = await OrderService.GetOrderById(orderId); + if (order == null || !await Scope.HasAccess(order)) return Json(new { Result = false }); - await orderViewModelService.InsertOrderNote(order, downloadId, displayToCustomer, message); + await OrderViewModelService.InsertOrderNote(order, downloadId, displayToCustomer, message); return Json(new { Result = true }); } @@ -552,11 +552,11 @@ public async Task OrderNoteAdd(string orderId, string downloadId, [HttpPost] public async Task OrderNoteDelete(string id, string orderId) { - var order = await orderService.GetOrderById(orderId) + var order = await OrderService.GetOrderById(orderId) ?? throw new ArgumentException("No order found with the specified id"); - if (!await scope.HasAccess(order)) return Json(new { Result = false }); + if (!await Scope.HasAccess(order)) return Json(new { Result = false }); - await orderViewModelService.DeleteOrderNote(order, id); + await OrderViewModelService.DeleteOrderNote(order, id); return new JsonResult(""); } diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BasePageController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BasePageController.cs index 77477cda70..4addf52f32 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BasePageController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BasePageController.cs @@ -33,6 +33,10 @@ protected virtual void EditWarningCheck(Page page) { } // Exposed for host subclasses: primary-constructor parameters are not visible to derived classes // by name in C#. + protected IPageViewModelService PageViewModelService => pageViewModelService; + protected IPageService PageService => pageService; + protected ILanguageService LanguageService => languageService; + protected IDateTimeService DateTimeService => dateTimeService; protected ITranslationService TranslationService => translationService; protected IAdminDataScope Scope => scope; diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseTaxCategoryController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseTaxCategoryController.cs index b58740ffcd..85f3735988 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseTaxCategoryController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseTaxCategoryController.cs @@ -30,6 +30,21 @@ public abstract class BaseTaxCategoryController( IAdminDataScope scope) : BaseController { + /// Exposed so host controllers that extend this base (Admin/Store's TaxController) + /// can reuse the same instance for their own Providers/Settings actions instead of re-capturing + /// their own copy of the primary-constructor parameter (which would trigger CS9107 - the + /// parameter stored twice, once here and once in the derived class). + protected ITaxCategoryService TaxCategoryService => taxCategoryService; + + /// See . + protected IStoreService StoreService => storeService; + + /// See . + protected ITranslationService TranslationService => translationService; + + /// See . + protected IAdminDataScope Scope => scope; + [HttpPost] [PermissionAuthorizeAction(PermissionActionName.List)] public virtual async Task Categories(DataSourceRequest command) diff --git a/src/Web/Grand.Web.AdminShared/Interfaces/IReportDataScope.cs b/src/Web/Grand.Web.AdminShared/Interfaces/IReportDataScope.cs index 9ac616e8f4..1c4fb13ed7 100644 --- a/src/Web/Grand.Web.AdminShared/Interfaces/IReportDataScope.cs +++ b/src/Web/Grand.Web.AdminShared/Interfaces/IReportDataScope.cs @@ -40,5 +40,5 @@ public interface IReportDataScope /// filtering) for Admin and Store. Vendor overrides this to additionally drop rows whose product /// the current vendor sub-account has no access to (WorkContext.HasAccessToProduct) — preserves /// Vendor's existing BestsellersReportList behavior exactly (see spec §2.3). - bool CanIncludeProduct(Grand.Domain.Catalog.Product product) => true; + bool CanIncludeProduct(Grand.Domain.Catalog.Product? product) => true; } diff --git a/src/Web/Grand.Web.AdminShared/Services/AdminReportDataScope.cs b/src/Web/Grand.Web.AdminShared/Services/AdminReportDataScope.cs index f3a9540a67..8af5d35fe9 100644 --- a/src/Web/Grand.Web.AdminShared/Services/AdminReportDataScope.cs +++ b/src/Web/Grand.Web.AdminShared/Services/AdminReportDataScope.cs @@ -19,5 +19,5 @@ public class AdminReportDataScope : IReportDataScope public bool ShowStoreSelector => true; public bool ShowVendorSelector => true; public string ResourceKeyPrefix => "Admin"; - public bool CanIncludeProduct(Product product) => true; + public bool CanIncludeProduct(Product? product) => true; } diff --git a/src/Web/Grand.Web.AdminShared/Services/RoutedReportDataScope.cs b/src/Web/Grand.Web.AdminShared/Services/RoutedReportDataScope.cs index 4051f92db3..4ea54da815 100644 --- a/src/Web/Grand.Web.AdminShared/Services/RoutedReportDataScope.cs +++ b/src/Web/Grand.Web.AdminShared/Services/RoutedReportDataScope.cs @@ -43,5 +43,5 @@ private IReportDataScope Resolved public bool ShowStoreSelector => Resolved.ShowStoreSelector; public bool ShowVendorSelector => Resolved.ShowVendorSelector; public string ResourceKeyPrefix => Resolved.ResourceKeyPrefix; - public bool CanIncludeProduct(Product product) => Resolved.CanIncludeProduct(product); + public bool CanIncludeProduct(Product? product) => Resolved.CanIncludeProduct(product); } diff --git a/src/Web/Grand.Web.AdminShared/Services/StoreReportDataScope.cs b/src/Web/Grand.Web.AdminShared/Services/StoreReportDataScope.cs index 5c933dbce9..d9a4fc15c2 100644 --- a/src/Web/Grand.Web.AdminShared/Services/StoreReportDataScope.cs +++ b/src/Web/Grand.Web.AdminShared/Services/StoreReportDataScope.cs @@ -19,5 +19,5 @@ public class StoreReportDataScope(IContextAccessor contextAccessor) : IReportDat public bool ShowStoreSelector => false; public bool ShowVendorSelector => false; public string ResourceKeyPrefix => "Admin"; - public bool CanIncludeProduct(Product product) => true; + public bool CanIncludeProduct(Product? product) => true; } diff --git a/src/Web/Grand.Web.AdminShared/Services/VendorReportDataScope.cs b/src/Web/Grand.Web.AdminShared/Services/VendorReportDataScope.cs index 115ed84f4e..2329274a36 100644 --- a/src/Web/Grand.Web.AdminShared/Services/VendorReportDataScope.cs +++ b/src/Web/Grand.Web.AdminShared/Services/VendorReportDataScope.cs @@ -24,6 +24,6 @@ public class VendorReportDataScope(IContextAccessor contextAccessor) : IReportDa public bool ShowVendorSelector => false; public string ResourceKeyPrefix => "Vendor"; - public bool CanIncludeProduct(Product product) => + public bool CanIncludeProduct(Product? product) => product is not null && product.VendorId == contextAccessor.WorkContext.CurrentVendor.Id; } diff --git a/src/Web/Grand.Web.Store/Controllers/BlogController.cs b/src/Web/Grand.Web.Store/Controllers/BlogController.cs index 8b2284667e..a80d15b0d3 100644 --- a/src/Web/Grand.Web.Store/Controllers/BlogController.cs +++ b/src/Web/Grand.Web.Store/Controllers/BlogController.cs @@ -56,11 +56,11 @@ protected override void EditWarningCheck(BlogPost blogPost) [PermissionAuthorizeAction(PermissionActionName.Preview)] public async Task Preview(string id) { - var blogPost = await blogService.GetBlogPostById(id); + var blogPost = await BlogService.GetBlogPostById(id); if (blogPost == null) return RedirectToAction("List"); if (!await PostScope.HasAccess(blogPost)) return RedirectToAction("List"); - var model = blogPost.ToModel(dateTimeService); + var model = blogPost.ToModel(DateTimeService); return View(model); } } diff --git a/src/Web/Grand.Web.Store/Controllers/CustomerController.cs b/src/Web/Grand.Web.Store/Controllers/CustomerController.cs index b6d8cf5636..3436368ceb 100644 --- a/src/Web/Grand.Web.Store/Controllers/CustomerController.cs +++ b/src/Web/Grand.Web.Store/Controllers/CustomerController.cs @@ -73,12 +73,12 @@ public override async Task OnActionExecutionAsync(ActionExecutingContext context /// ApplyPostConstraints_CraftedPost_CannotSmuggleOwnershipFields for the exact contract. protected override async Task ApplyPostConstraints(CustomerModel model) { - model.StoreId = scope.DefaultStoreId; + model.StoreId = Scope.DefaultStoreId; model.Owner = ""; model.VendorId = ""; model.StaffStoreId = ""; model.SeId = ""; - var registered = await groupService.GetCustomerGroupBySystemName(SystemCustomerGroupNames.Registered); + var registered = await GroupService.GetCustomerGroupBySystemName(SystemCustomerGroupNames.Registered); model.CustomerGroups = registered != null ? new[] { registered.Id } : Array.Empty(); } } diff --git a/src/Web/Grand.Web.Store/Controllers/EmailAccountController.cs b/src/Web/Grand.Web.Store/Controllers/EmailAccountController.cs index e25ba71b9e..8b19554a65 100644 --- a/src/Web/Grand.Web.Store/Controllers/EmailAccountController.cs +++ b/src/Web/Grand.Web.Store/Controllers/EmailAccountController.cs @@ -36,7 +36,7 @@ public IActionResult List() [PermissionAuthorizeAction(PermissionActionName.List)] public async Task List(DataSourceRequest command) { - var emailAccounts = await emailAccountService.GetAllEmailAccounts(scope.DefaultStoreId ?? "", + var emailAccounts = await EmailAccountService.GetAllEmailAccounts(Scope.DefaultStoreId ?? "", pageIndex: command.Page - 1, pageSize: command.PageSize); var emailAccountModels = emailAccounts.Select(x => x.ToModel()).ToList(); diff --git a/src/Web/Grand.Web.Store/Controllers/MessageTemplateController.cs b/src/Web/Grand.Web.Store/Controllers/MessageTemplateController.cs index 6b7d9e44af..8a66f0268d 100644 --- a/src/Web/Grand.Web.Store/Controllers/MessageTemplateController.cs +++ b/src/Web/Grand.Web.Store/Controllers/MessageTemplateController.cs @@ -43,7 +43,7 @@ public class MessageTemplateController( [HttpPost] public async Task ListGlobal(DataSourceRequest command) { - var allTemplates = await messageTemplateService.GetAllMessageTemplates(""); + var allTemplates = await MessageTemplateService.GetAllMessageTemplates(""); var globalTemplates = allTemplates .Where(t => !t.LimitedToStores) .ToList(); @@ -62,7 +62,7 @@ public async Task ListGlobal(DataSourceRequest command) [HttpPost] public async Task ListStore(DataSourceRequest command) { - var allTemplates = await messageTemplateService.GetAllMessageTemplates(""); + var allTemplates = await MessageTemplateService.GetAllMessageTemplates(""); var storeTemplates = allTemplates .Where(t => t.LimitedToStores && t.Stores.Contains(CurrentStoreId)) .ToList(); diff --git a/src/Web/Grand.Web.Store/Controllers/NewsController.cs b/src/Web/Grand.Web.Store/Controllers/NewsController.cs index a5012ac112..82e3cfedba 100644 --- a/src/Web/Grand.Web.Store/Controllers/NewsController.cs +++ b/src/Web/Grand.Web.Store/Controllers/NewsController.cs @@ -51,11 +51,11 @@ protected override void EditWarningCheck(NewsItem newsItem) [PermissionAuthorizeAction(PermissionActionName.Preview)] public async Task Preview(string id) { - var newsItem = await newsService.GetNewsById(id); + var newsItem = await NewsService.GetNewsById(id); if (newsItem == null) return RedirectToAction("List"); if (!await Scope.HasAccess(newsItem)) return RedirectToAction("List"); - var model = newsItem.ToModel(dateTimeService); + var model = newsItem.ToModel(DateTimeService); return View(model); } } diff --git a/src/Web/Grand.Web.Store/Controllers/PageController.cs b/src/Web/Grand.Web.Store/Controllers/PageController.cs index e34390cb8b..ec0be4b668 100644 --- a/src/Web/Grand.Web.Store/Controllers/PageController.cs +++ b/src/Web/Grand.Web.Store/Controllers/PageController.cs @@ -52,11 +52,11 @@ protected override void EditWarningCheck(Page page) [HttpPost] public async Task StorePagesList(DataSourceRequest command, PageListModel model) { - var pages = await pageService.GetAllPages(Scope.DefaultStoreId, true); + var pages = await PageService.GetAllPages(Scope.DefaultStoreId, true); var pageModels = pages .Where(x => x.LimitedToStores && x.Stores.Count == 1) - .Select(x => x.ToModel(dateTimeService)) + .Select(x => x.ToModel(DateTimeService)) .ToList(); if (!string.IsNullOrEmpty(model.Name)) @@ -75,11 +75,11 @@ public async Task StorePagesList(DataSourceRequest command, PageL [HttpPost] public async Task GlobalPagesList(DataSourceRequest command, PageListModel model) { - var pages = await pageService.GetAllPages(Scope.DefaultStoreId, true); + var pages = await PageService.GetAllPages(Scope.DefaultStoreId, true); var pageModels = pages .Where(x => !x.LimitedToStores || x.Stores.Count > 1) - .Select(x => x.ToModel(dateTimeService)) + .Select(x => x.ToModel(DateTimeService)) .ToList(); if (!string.IsNullOrEmpty(model.Name)) @@ -99,7 +99,7 @@ public async Task GlobalPagesList(DataSourceRequest command, Page public async Task Copy(string id) { var storeId = Scope.DefaultStoreId; - var page = await pageService.GetPageById(id); + var page = await PageService.GetPageById(id); if (page == null) return RedirectToAction("List"); // A page is copyable only while it is still readable here and not yet owned by this store, so @@ -111,19 +111,19 @@ public async Task Copy(string id) if (page.LimitedToStores && page.Stores.Count <= 1) return RedirectToAction("Edit", new { id }); - var storePages = await pageService.GetAllPages(storeId, true); + var storePages = await PageService.GetAllPages(storeId, true); if (storePages.Any(p => p.Id != page.Id && p.SystemName.Equals(page.SystemName, StringComparison.OrdinalIgnoreCase))) { - Error(translationService.GetResource("Admin.Content.Pages.Copy.DuplicateSystemName")); + Error(TranslationService.GetResource("Admin.Content.Pages.Copy.DuplicateSystemName")); return RedirectToAction("Edit", new { id }); } - var model = page.ToModel(dateTimeService); + var model = page.ToModel(DateTimeService); model.Id = ""; model.Stores = [storeId]; - await AddLocales(languageService, model.Locales, (locale, languageId) => + await AddLocales(LanguageService, model.Locales, (locale, languageId) => { locale.Title = page.GetTranslation(x => x.Title, languageId, false); locale.Body = page.GetTranslation(x => x.Body, languageId, false); @@ -133,8 +133,8 @@ await AddLocales(languageService, model.Locales, (locale, languageId) => locale.SeName = page.GetSeName(languageId, false); }); - var newPage = await pageViewModelService.InsertPageModel(model); - Success(translationService.GetResource("Admin.Content.Pages.Added")); + var newPage = await PageViewModelService.InsertPageModel(model); + Success(TranslationService.GetResource("Admin.Content.Pages.Added")); return RedirectToAction("Edit", new { id = newPage.Id }); } } diff --git a/src/Web/Grand.Web.Store/Controllers/TaxController.cs b/src/Web/Grand.Web.Store/Controllers/TaxController.cs index b3d2141302..bb45034873 100644 --- a/src/Web/Grand.Web.Store/Controllers/TaxController.cs +++ b/src/Web/Grand.Web.Store/Controllers/TaxController.cs @@ -111,15 +111,15 @@ public async Task Settings() model.TaxBasedOnValues = enumTranslationService.ToSelectList(taxSettings.TaxBasedOn); model.TaxDisplayTypeValues = enumTranslationService.ToSelectList(taxSettings.TaxDisplayType); - var taxCategories = await taxCategoryService.GetAllTaxCategories(CurrentStoreId); + var taxCategories = await TaxCategoryService.GetAllTaxCategories(CurrentStoreId); model.TaxCategories.Add(new SelectListItem { - Text = translationService.GetResource("Admin.Configuration.Tax.Settings.TaxCategories.None"), Value = "" + Text = TranslationService.GetResource("Admin.Configuration.Tax.Settings.TaxCategories.None"), Value = "" }); foreach (var tc in taxCategories) model.TaxCategories.Add(new SelectListItem { Text = tc.Name, Value = tc.Id }); model.EuVatShopCountries.Add(new SelectListItem - { Text = translationService.GetResource("Admin.Address.SelectCountry"), Value = "" }); + { Text = TranslationService.GetResource("Admin.Address.SelectCountry"), Value = "" }); foreach (var c in await countryService.GetAllCountries(showHidden: true)) model.EuVatShopCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id, Selected = c.Id == taxSettings.EuVatShopCountryId }); @@ -130,7 +130,7 @@ public async Task Settings() : new AddressModel(); model.DefaultTaxAddress.AvailableCountries.Add(new SelectListItem - { Text = translationService.GetResource("Admin.Address.SelectCountry"), Value = "" }); + { Text = TranslationService.GetResource("Admin.Address.SelectCountry"), Value = "" }); foreach (var c in await countryService.GetAllCountries(showHidden: true)) model.DefaultTaxAddress.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id, Selected = defaultAddress != null && c.Id == defaultAddress.CountryId }); @@ -159,7 +159,7 @@ public async Task Settings(TaxSettingsModel model) taxSettings = model.ToEntity(taxSettings); await settingService.SaveSetting(taxSettings, CurrentStoreId); await cacheBase.Clear(); - Success(translationService.GetResource("Admin.Configuration.Updated")); + Success(TranslationService.GetResource("Admin.Configuration.Updated")); return RedirectToAction("Settings"); }