Try your search with a different keyword or use * as a wildcard.
using Microsoft.AspNetCore.Mvc;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Tax;
using Nop.Plugin.Misc.Zettle.Domain;
using Nop.Plugin.Misc.Zettle.Models;
using Nop.Plugin.Misc.Zettle.Services;
using Nop.Services.Catalog;
using Nop.Services.Configuration;
using Nop.Services.Directory;
using Nop.Services.Helpers;
using Nop.Services.Localization;
using Nop.Services.Messages;
using Nop.Services.ScheduleTasks;
using Nop.Services.Security;
using Nop.Web.Areas.Admin.Factories;
using Nop.Web.Areas.Admin.Models.Catalog;
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Models.Extensions;
using Nop.Web.Framework.Mvc;
using Nop.Web.Framework.Mvc.Filters;
namespace Nop.Plugin.Misc.Zettle.Controllers;
[Area(AreaNames.ADMIN)]
[AuthorizeAdmin]
[AutoValidateAntiforgeryToken]
public class ZettleAdminController : BasePluginController
{
#region Fields
protected readonly CurrencySettings _currencySettings;
protected readonly IBaseAdminModelFactory _baseAdminModelFactory;
protected readonly ICurrencyService _currencyService;
protected readonly IDateTimeHelper _dateTimeHelper;
protected readonly ILocalizationService _localizationService;
protected readonly INotificationService _notificationService;
protected readonly IPermissionService _permissionService;
protected readonly IProductService _productService;
protected readonly IScheduleTaskService _scheduleTaskService;
protected readonly ISettingService _settingService;
protected readonly IStoreContext _storeContext;
protected readonly TaxSettings _taxSettings;
protected readonly ZettleRecordService _zettleRecordService;
protected readonly ZettleService _zettleService;
protected readonly ZettleSettings _zettleSettings;
#endregion
#region Ctor
public ZettleAdminController(CurrencySettings currencySettings,
IBaseAdminModelFactory baseAdminModelFactory,
ICurrencyService currencyService,
IDateTimeHelper dateTimeHelper,
ILocalizationService localizationService,
INotificationService notificationService,
IPermissionService permissionService,
IProductService productService,
IScheduleTaskService scheduleTaskService,
ISettingService settingService,
IStoreContext storeContext,
TaxSettings taxSettings,
ZettleRecordService zettleRecordService,
ZettleService zettleService,
ZettleSettings zettleSettings)
{
_currencySettings = currencySettings;
_baseAdminModelFactory = baseAdminModelFactory;
_currencyService = currencyService;
_dateTimeHelper = dateTimeHelper;
_localizationService = localizationService;
_notificationService = notificationService;
_permissionService = permissionService;
_productService = productService;
_scheduleTaskService = scheduleTaskService;
_settingService = settingService;
_storeContext = storeContext;
_taxSettings = taxSettings;
_zettleRecordService = zettleRecordService;
_zettleService = zettleService;
_zettleSettings = zettleSettings;
}
#endregion
#region Methods
#region Configuration
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> Configure()
{
var model = new ConfigurationModel
{
ClientId = _zettleSettings.ClientId,
ApiKey = _zettleSettings.ApiKey,
DisconnectOnUninstall = _zettleSettings.DisconnectOnUninstall,
AutoSyncEnabled = _zettleSettings.AutoSyncEnabled,
AutoSyncPeriod = _zettleSettings.AutoSyncPeriod,
DeleteBeforeImport = _zettleSettings.DeleteBeforeImport,
SyncEnabled = _zettleSettings.SyncEnabled,
PriceSyncEnabled = _zettleSettings.PriceSyncEnabled,
ImageSyncEnabled = _zettleSettings.ImageSyncEnabled,
InventoryTrackingEnabled = _zettleSettings.InventoryTrackingEnabled,
DefaultTaxEnabled = _zettleSettings.DefaultTaxEnabled,
DiscountSyncEnabled = _zettleSettings.DiscountSyncEnabled,
};
if (ZettleService.IsConfigured(_zettleSettings))
{
//account info
var (accountInfo, error) = await _zettleService.GetAccountInfoAsync();
if (!string.IsNullOrEmpty(error) || accountInfo is null)
{
var locale = await _localizationService.GetResourceAsync("Plugins.Misc.Zettle.Configuration.Error");
var errorMessage = string.Format(locale, error, Url.Action("List", "Log"));
_notificationService.ErrorNotification(errorMessage, false);
return View("~/Plugins/Misc.Zettle/Views/Configure.cshtml", model);
}
model.Connected = true;
model.Account.Name = accountInfo.Name;
model.Account.CustomerStatus = accountInfo.CustomerStatus?.ToLower() ?? "undefined";
model.Account.Accepted = string.Equals(accountInfo.CustomerStatus, "ACCEPTED", StringComparison.InvariantCultureIgnoreCase);
//ensure the same currencies are used
var storeCurrency = await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId);
if (string.IsNullOrEmpty(accountInfo.Currency) ||
!accountInfo.Currency.Equals(storeCurrency.CurrencyCode, StringComparison.InvariantCultureIgnoreCase))
{
var locale = await _localizationService.GetResourceAsync("Plugins.Misc.Zettle.Account.Fields.Currency.Warning");
var warning = string.Format(locale, storeCurrency.CurrencyCode, accountInfo.Currency, Url.Action("List", "Currency"));
_notificationService.WarningNotification(warning, false);
}
model.Account.Currency = accountInfo.Currency;
//ensure the same tax types are used
var taxNone = string.Equals(accountInfo.TaxationType, "NONE", StringComparison.InvariantCultureIgnoreCase);
var taxVat = string.Equals(accountInfo.TaxationType, "VAT", StringComparison.InvariantCultureIgnoreCase);
var taxRates = string.Equals(accountInfo.TaxationType, "SALES_TAX", StringComparison.InvariantCultureIgnoreCase);
if (_taxSettings.EuVatEnabled != taxVat)
{
var locale = await _localizationService.GetResourceAsync("Plugins.Misc.Zettle.Account.Fields.TaxationType.Vat.Warning");
var warning = string.Format(locale, Url.Action("Tax", "Setting"));
_notificationService.WarningNotification(warning, false);
}
model.Account.TaxationType = accountInfo.TaxationType?.Replace('_', ' ');
if (taxVat)
{
_zettleSettings.DefaultTaxEnabled = true;
if (accountInfo.VatPercentage.HasValue)
model.Account.TaxationType = $"{model.Account.TaxationType} ({accountInfo.VatPercentage}%)";
}
if (taxRates)
{
var (percentage, _) = await _zettleService.GetDefaultTaxRateAsync();
if (percentage.HasValue)
model.Account.TaxationType = $"{model.Account.TaxationType} ({percentage}% by default)";
else
{
var warning = await _localizationService.GetResourceAsync("Plugins.Misc.Zettle.Account.Fields.TaxationType.SalesTax.Warning");
_notificationService.WarningNotification(warning);
}
}
//ensure the same price types are used
var pricesIncludeTax = string.Equals(accountInfo.TaxationMode, "INCLUSIVE", StringComparison.InvariantCultureIgnoreCase);
if (_taxSettings.PricesIncludeTax != pricesIncludeTax || (_taxSettings.PricesIncludeTax && taxNone))
{
var locale = await _localizationService.GetResourceAsync("Plugins.Misc.Zettle.Account.Fields.TaxationMode.Warning");
var warning = string.Format(locale, Url.Action("Tax", "Setting"));
_notificationService.WarningNotification(warning, false);
}
model.Account.TaxationMode = accountInfo.TaxationMode;
//ensure the webhook is created
var store = await _storeContext.GetCurrentStoreAsync();
var webhookUrl = $"{store.Url.TrimEnd('/')}{Url.RouteUrl(ZettleDefaults.WebhookRouteName)}".ToLowerInvariant();
var (webhook, _) = await _zettleService.CreateWebhookAsync(webhookUrl);
_zettleSettings.WebhookUrl = webhook?.Destination;
_zettleSettings.WebhookKey = webhook?.SigningKey;
await _settingService.SaveSettingAsync(_zettleSettings);
if (string.IsNullOrEmpty(_zettleSettings.WebhookUrl))
{
var locale = await _localizationService.GetResourceAsync("Plugins.Misc.Zettle.Configuration.Webhook.Warning");
var warning = string.Format(locale, Url.Action("List", "Log"));
_notificationService.WarningNotification(warning, false);
}
//last import details
if (!string.IsNullOrEmpty(_zettleSettings.ImportId))
{
var (import, _) = await _zettleService.GetImportAsync();
if (import is not null)
{
model.Import.StartDate = import.Created;
model.Import.EndDate = import.Finished;
model.Import.State = import.State?.Replace('_', ' ');
model.Import.Items = import.Items?.ToString();
model.Import.Active = string.Equals(import.State, "IMPORTING", StringComparison.InvariantCultureIgnoreCase);
}
}
}
var scheduleTask = await _scheduleTaskService.GetTaskByTypeAsync(ZettleDefaults.SynchronizationTask.Type);
if (scheduleTask is not null)
{
model.AutoSyncEnabled = scheduleTask.Enabled;
model.AutoSyncPeriod = scheduleTask.Seconds / 60;
}
return View("~/Plugins/Misc.Zettle/Views/Configure.cshtml", model);
}
[HttpPost, ActionName("Configure")]
[FormValueRequired("credentials")]
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> SaveCredentials(ConfigurationModel model)
{
if (!ModelState.IsValid)
return await Configure();
if (!model.ClientId?.Equals(_zettleSettings.ClientId) ?? true)
{
//credentials are changed
await _zettleRecordService.ClearRecordsAsync();
_zettleSettings.WebhookUrl = string.Empty;
_zettleSettings.WebhookKey = string.Empty;
_zettleSettings.ImportId = string.Empty;
if (ZettleService.IsConfigured(_zettleSettings))
{
if (_zettleSettings.DisconnectOnUninstall)
await _zettleService.DisconnectAsync();
else if (!string.IsNullOrEmpty(_zettleSettings.WebhookUrl))
await _zettleService.DeleteWebhookAsync();
}
}
_zettleSettings.ClientId = model.ClientId;
_zettleSettings.ApiKey = model.ApiKey;
_zettleSettings.DisconnectOnUninstall = model.DisconnectOnUninstall;
await _settingService.SaveSettingAsync(_zettleSettings);
_notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Plugins.Saved"));
return await Configure();
}
[HttpPost, ActionName("Configure")]
[FormValueRequired("sync")]
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> SaveSync(ConfigurationModel model)
{
if (!ModelState.IsValid)
return await Configure();
_zettleSettings.AutoSyncEnabled = model.AutoSyncEnabled;
_zettleSettings.AutoSyncPeriod = model.AutoSyncPeriod;
_zettleSettings.DeleteBeforeImport = model.DeleteBeforeImport;
_zettleSettings.SyncEnabled = model.SyncEnabled;
_zettleSettings.PriceSyncEnabled = model.PriceSyncEnabled;
_zettleSettings.ImageSyncEnabled = model.ImageSyncEnabled;
_zettleSettings.InventoryTrackingEnabled = model.InventoryTrackingEnabled;
_zettleSettings.DefaultTaxEnabled = model.DefaultTaxEnabled;
_zettleSettings.DiscountSyncEnabled = model.DiscountSyncEnabled;
await _settingService.SaveSettingAsync(_zettleSettings);
var scheduleTask = await _scheduleTaskService.GetTaskByTypeAsync(ZettleDefaults.SynchronizationTask.Type);
if (scheduleTask is not null)
{
if (!scheduleTask.Enabled && _zettleSettings.AutoSyncEnabled)
scheduleTask.LastEnabledUtc = DateTime.UtcNow;
scheduleTask.Enabled = _zettleSettings.AutoSyncEnabled;
scheduleTask.Seconds = _zettleSettings.AutoSyncPeriod * 60;
await _scheduleTaskService.UpdateTaskAsync(scheduleTask);
}
_notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Plugins.Saved"));
return await Configure();
}
[HttpPost, ActionName("Configure")]
[FormValueRequired("revoke")]
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> RevokeAccess()
{
if (!ZettleService.IsConfigured(_zettleSettings))
return await Configure();
if (!string.IsNullOrEmpty(_zettleSettings.WebhookUrl))
await _zettleService.DeleteWebhookAsync();
var (_, error) = await _zettleService.DisconnectAsync();
if (!string.IsNullOrEmpty(error))
{
var locale = await _localizationService.GetResourceAsync("Plugins.Misc.Zettle.Configuration.Error");
var errorMessage = string.Format(locale, error, Url.Action("List", "Log"));
_notificationService.ErrorNotification(errorMessage, false);
return await Configure();
}
await _zettleRecordService.ClearRecordsAsync();
_zettleSettings.ClientId = string.Empty;
_zettleSettings.ApiKey = string.Empty;
_zettleSettings.WebhookUrl = string.Empty;
_zettleSettings.WebhookKey = string.Empty;
_zettleSettings.ImportId = string.Empty;
await _settingService.SaveSettingAsync(_zettleSettings);
_notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Plugins.Misc.Zettle.Credentials.AccessRevoked"));
return await Configure();
}
#endregion
#region Synchronization
[HttpPost]
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> SyncRecordList(SyncRecordSearchModel searchModel)
{
var records = await _zettleRecordService.GetAllRecordsAsync(productOnly: true,
pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize);
var products = await _productService.GetProductsByIdsAsync(records.Select(record => record.ProductId).Distinct().ToArray());
var model = await new SyncRecordListModel().PrepareToGridAsync(searchModel, records, () =>
{
return records.SelectAwait(async record => new SyncRecordModel
{
Id = record.Id,
Active = record.Active,
ProductId = record.ProductId,
ProductName = products.FirstOrDefault(product => product.Id == record.ProductId)?.Name ?? "Not found",
PriceSyncEnabled = record.PriceSyncEnabled,
ImageSyncEnabled = record.ImageSyncEnabled,
InventoryTrackingEnabled = record.InventoryTrackingEnabled,
UpdatedDate = record.UpdatedOnUtc.HasValue
? await _dateTimeHelper.ConvertToUserTimeAsync(record.UpdatedOnUtc.Value, DateTimeKind.Utc)
: null
});
});
return Json(model);
}
[HttpPost]
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> SyncRecordUpdate(SyncRecordModel model)
{
var productRecord = await _zettleRecordService.GetRecordByIdAsync(model.Id)
?? throw new ArgumentException("No record found");
var records = (await _zettleRecordService.GetAllRecordsAsync(productUuid: productRecord.Uuid)).ToList();
foreach (var record in records)
{
record.Active = model.Active;
record.PriceSyncEnabled = model.PriceSyncEnabled;
record.ImageSyncEnabled = model.ImageSyncEnabled;
record.InventoryTrackingEnabled = model.InventoryTrackingEnabled;
record.OperationType = OperationType.Update;
}
await _zettleRecordService.UpdateRecordsAsync(records);
return new NullJsonResult();
}
[HttpPost]
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> SyncRecordDelete(ICollection<int> selectedIds)
{
if (!selectedIds?.Any() ?? true)
return NoContent();
foreach (var id in selectedIds)
{
var productRecord = await _zettleRecordService.GetRecordByIdAsync(id);
if (productRecord is null)
continue;
var records = (await _zettleRecordService.GetAllRecordsAsync(productUuid: productRecord.Uuid)).ToList();
await _zettleRecordService.DeleteRecordsAsync(records.Select(record => record.Id).ToList());
}
return new NullJsonResult();
}
[CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> ProductToSync()
{
var model = new AddProductToSyncSearchModel();
await _baseAdminModelFactory.PrepareProductTypesAsync(model.AvailableProductTypes);
await _baseAdminModelFactory.PrepareCategoriesAsync(model.AvailableCategories);
await _baseAdminModelFactory.PrepareManufacturersAsync(model.AvailableManufacturers);
await _baseAdminModelFactory.PrepareStoresAsync(model.AvailableStores);
await _baseAdminModelFactory.PrepareVendorsAsync(model.AvailableVendors);
model.SetPopupGridPageSize();
return View("~/Plugins/Misc.Zettle/Views/ProductToSync.cshtml", model);
}
[HttpPost]
[CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> ProductListToSync(AddProductToSyncSearchModel searchModel)
{
var products = await _productService.SearchProductsAsync(showHidden: true,
keywords: searchModel.SearchProductName,
productType: searchModel.SearchProductTypeId > 0 ? (ProductType?)searchModel.SearchProductTypeId : null,
categoryIds: new List<int> { searchModel.SearchCategoryId },
manufacturerIds: new List<int> { searchModel.SearchManufacturerId },
storeId: searchModel.SearchStoreId,
vendorId: searchModel.SearchVendorId,
pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize);
var model = new AddProductToSyncListModel().PrepareToGrid(searchModel, products, () =>
{
return products.Select(product => new ProductModel
{
Id = product.Id,
Name = product.Name,
Sku = product.Sku,
Price = product.Price,
Published = product.Published
}).ToList();
});
return Json(model);
}
[HttpPost]
[FormValueRequired("save")]
[CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> ProductToSync(AddProductToSyncModel model)
{
var invalidProducts = await _zettleRecordService.AddRecordsAsync(model.SelectedProductIds?.ToList());
if (invalidProducts > 0)
{
var warning = await _localizationService.GetResourceAsync("Plugins.Misc.Zettle.Sync.AddProduct.Warning");
_notificationService.WarningNotification(string.Format(warning, invalidProducts));
}
else
_notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Plugins.Misc.Zettle.Sync.AddProduct.Success"));
ViewBag.RefreshPage = true;
return View("~/Plugins/Misc.Zettle/Views/ProductToSync.cshtml", new AddProductToSyncSearchModel());
}
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> SyncStart()
{
await _zettleService.ImportAsync();
return new NullJsonResult();
}
[CheckPermission(StandardPermission.Configuration.MANAGE_PLUGINS)]
public async Task<IActionResult> SyncUpdate()
{
if (string.IsNullOrEmpty(_zettleSettings.ImportId))
return ErrorJson("Synchronization not found");
var (import, _) = await _zettleService.GetImportAsync();
if (string.IsNullOrEmpty(import?.State))
return ErrorJson("Synchronization error");
if (string.Equals(import.State, "IMPORTING", StringComparison.InvariantCultureIgnoreCase))
return Json(new { StartDate = import.Created, Items = import.Items?.ToString(), State = import.State });
return Json(new { Completed = true });
}
#endregion
#endregion
}