Keresés partnerre a rendelések között, Felhasználó létrehozásakor automatikus számlázási cím hozzáadás, adószám szinkronizálás, egyéb kisebb fixek

This commit is contained in:
Adam 2025-10-28 03:26:19 +01:00
parent 9a94bc6c06
commit 30c8cd3e00
10 changed files with 390 additions and 93 deletions

View File

@ -17,6 +17,7 @@ using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Tax;
using Nop.Core.Events;
using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Factories;
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
@ -157,7 +158,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
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
var model = await _orderModelFactory.PrepareOrderSearchModelAsync(new OrderSearchModelExtended
{
OrderStatusIds = orderStatuses,
PaymentStatusIds = paymentStatuses,
@ -169,7 +170,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
[HttpPost]
[CheckPermission(StandardPermission.Orders.ORDERS_VIEW)]
public async Task<IActionResult> OrderList(OrderSearchModel searchModel)
public async Task<IActionResult> OrderList(OrderSearchModelExtended searchModel)
{
//prepare model
var orderListModel = await GetOrderListModelByFilter(searchModel);
@ -213,7 +214,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/Edit.cshtml", model);
}
public async Task<OrderListModelExtended> GetOrderListModelByFilter(OrderSearchModel searchModel)
public async Task<OrderListModelExtended> GetOrderListModelByFilter(OrderSearchModelExtended searchModel)
{
//return _customOrderService.
var orderListModel = await _orderModelFactory.PrepareOrderListModelExtendedAsync(searchModel);
@ -736,7 +737,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
[HttpPost, ActionName("ExportXml")]
[FormValueRequired("exportxml-all")]
[CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)]
public virtual async Task<IActionResult> ExportXmlAll(OrderSearchModel model)
public virtual async Task<IActionResult> ExportXmlAll(OrderSearchModelExtended model)
{
var startDateValue = model.StartDate == null ? null
: (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync());
@ -832,7 +833,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
[HttpPost, ActionName("ExportExcel")]
[FormValueRequired("exportexcel-all")]
[CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)]
public virtual async Task<IActionResult> ExportExcelAll(OrderSearchModel model)
public virtual async Task<IActionResult> ExportExcelAll(OrderSearchModelExtended model)
{
var startDateValue = model.StartDate == null ? null
: (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync());

View File

@ -63,125 +63,137 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
{
try
{
// Validate and get order
var order = await _orderService.GetOrderByIdAsync(orderId);
if (order == null)
{
return Json(new { success = false, message = "Order not found" });
}
// Validate and get customer
var customer = await _customerService.GetCustomerByIdAsync(order.CustomerId);
if (customer == null)
{
return Json(new { success = false, message = "Customer not found" });
}
// Get billing address
// Get and validate billing address
var billingAddress = await _customerService.GetCustomerBillingAddressAsync(customer);
if (billingAddress == null)
{
return Json(new { success = false, message = "Billing address not found" });
}
var billingCountry = await _countryService.GetCountryByAddressAsync(billingAddress);
var billingCountryCode = billingCountry?.TwoLetterIsoCode ?? "HU";
// Get shipping address
var shippingAddress = await _customerService.GetCustomerShippingAddressAsync(customer);
if (shippingAddress == null)
{
shippingAddress = billingAddress;
}
var shippingCountry = shippingAddress != null
? await _countryService.GetCountryByAddressAsync(shippingAddress)
: null;
var shippingCountryCode = shippingCountry?.TwoLetterIsoCode ?? billingCountryCode;
// Get shipping address (fallback to billing if not found)
var shippingAddress = await _customerService.GetCustomerShippingAddressAsync(customer) ?? billingAddress;
var shippingCountry = await _countryService.GetCountryByAddressAsync(shippingAddress);
var shippingCountryCode = shippingCountry?.TwoLetterIsoCode ?? billingCountryCode;
// Create order request
var orderRequest = new OrderCreateRequest
{
VevoNev = $"{billingAddress.FirstName} {billingAddress.LastName}",
VevoIrsz = billingAddress.ZipPostalCode ?? "",
VevoTelep = billingAddress.City ?? "",
VevoNev = customer.Company ?? string.Empty,
VevoIrsz = billingAddress.ZipPostalCode ?? string.Empty,
VevoTelep = billingAddress.City ?? string.Empty,
VevoUtcaHsz = $"{billingAddress.Address1} {billingAddress.Address2}".Trim(),
VevoOrszag = billingCountryCode,
VevoAdoszam = billingAddress.Company, // Or a custom field for tax number
MegrendelestombID = 1, // Configure this based on your setup
VevoAdoszam = customer.VatNumber,
MegrendelestombID = 1,
MegrendelesKelte = order.CreatedOnUtc.ToLocalTime(),
Hatarido = DateTime.Now.AddDays(7), // 7 days delivery time
Devizanem = "Ft", //TODO get real deault - A.
Hatarido = DateTime.Now.AddDays(7),
Devizanem = "Ft", // TODO: get real default - A.
FizetesiMod = order.PaymentMethodSystemName ?? "átutalás",
Email = billingAddress.Email,
Telefon = billingAddress.PhoneNumber,
Email = customer.Email ?? string.Empty,
Telefon = billingAddress.PhoneNumber ?? string.Empty,
MegrendelesSzamStr = order.Id.ToString()
};
// Add shipping address if different
if (shippingAddress != null)
// Add shipping address details
orderRequest.SzallNev = $"{shippingAddress.FirstName} {shippingAddress.LastName}".Trim();
orderRequest.SzallIrsz = shippingAddress.ZipPostalCode ?? string.Empty;
orderRequest.SzallTelep = shippingAddress.City ?? string.Empty;
orderRequest.SzallUtcaHsz = $"{shippingAddress.Address1} {shippingAddress.Address2}".Trim();
orderRequest.SzallOrszag = shippingCountryCode;
// Get and validate order items
var orderItems = await _dbContext.OrderItemDtos.GetAllByOrderId(order.Id, true).ToListAsync();
if (orderItems == null || orderItems.Count == 0)
{
orderRequest.SzallNev = $"{shippingAddress.FirstName} {shippingAddress.LastName}";
orderRequest.SzallIrsz = shippingAddress.ZipPostalCode ?? "";
orderRequest.SzallTelep = shippingAddress.City ?? "";
orderRequest.SzallUtcaHsz = $"{shippingAddress.Address1} {shippingAddress.Address2}".Trim();
orderRequest.SzallOrszag = shippingCountryCode;
return Json(new { success = false, message = "Order has no items" });
}
// Add order items
//var orderItems = await _orderService.GetOrderItemsAsync(order.Id);
var orderItems = await _dbContext.OrderItemDtos.GetAllByOrderId(order.Id, true).ToListAsync();
Console.WriteLine($"Order Items Count: {orderItems.Count}");
foreach (var item in orderItems)
{
//var productDTO = await _productService.GetProductByIdAsync(item.ProductId);
var product = _dbContext.ProductDtos.GetById(item.ProductId);
//string unit = product != null && product.IsMeasurable ? "kg" : "kt";
if(item.IsMeasurable) { // in case of measurable products, quantity is in fact weight stored in item.EnteredQuantity
var productName = product?.Name ?? "Product";
if (item.IsMeasurable)
{
// For measurable products, quantity is weight stored in NetWeight
orderRequest.AddItem(new InnVoiceOrderItem
{
TetelNev = product?.Name ?? "Product",
AfaSzoveg = "27%", // Configure VAT rate as needed
TetelNev = productName,
AfaSzoveg = "27%", // TODO: Configure VAT rate dynamically
Brutto = true,
EgysegAr = item.UnitPriceInclTax,
Mennyiseg = Convert.ToDecimal(item.NetWeight),
Mennyiseg = item.NetWeight > 0 ? Convert.ToDecimal(item.NetWeight) : 0,
MennyisegEgyseg = "kg",
CikkSzam = ""
CikkSzam = string.Empty
});
}
else
{
orderRequest.AddItem(new InnVoiceOrderItem
{
TetelNev = product?.Name ?? "Product",
AfaSzoveg = "27%", // Configure VAT rate as needed
TetelNev = productName,
AfaSzoveg = "27%", // TODO: Configure VAT rate dynamically
Brutto = true,
EgysegAr = item.UnitPriceInclTax,
Mennyiseg = item.Quantity,
MennyisegEgyseg = "kt",
CikkSzam = ""
CikkSzam = string.Empty
});
}
}
// Create order via API
var response = await _innVoiceOrderService.CreateOrderAsync(orderRequest);
if (response == null)
{
return Json(new { success = false, message = "No response from InnVoice API" });
}
if (response.IsSuccess)
{
// Save the TechId, TableId, and PrintUrl to the order for future reference
var currentStore = await _storeContext.GetCurrentStoreAsync();
var storeId = currentStore?.Id ?? 0;
// Save InnVoice order details as attributes
await _genericAttributeService.SaveAttributeAsync(
order,
"InnVoiceOrderTechId",
response.TechId,
(await _storeContext.GetCurrentStoreAsync()).Id
storeId
);
await _genericAttributeService.SaveAttributeAsync(
order,
"InnVoiceOrderTableId",
response.TableId?.ToString(),
(await _storeContext.GetCurrentStoreAsync()).Id
response.TableId?.ToString() ?? string.Empty,
storeId
);
await _genericAttributeService.SaveAttributeAsync(
order,
"InnVoiceOrderPrintLink",
response.PrintUrl?.ToString(),
(await _storeContext.GetCurrentStoreAsync()).Id
response.PrintUrl?.ToString() ?? string.Empty,
storeId
);
return Json(new
@ -196,17 +208,18 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
}
});
}
else
return Json(new
{
return Json(new
{
success = false,
message = $"InnVoice API Error: {response.Message}"
});
}
success = false,
message = $"InnVoice API Error: {response.Message ?? "Unknown error"}"
});
}
catch (Exception ex)
{
// Log the exception here
// _logger.LogError(ex, "Error creating order {OrderId} in InnVoice", orderId);
return Json(new
{
success = false,

View File

@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Nop.Web.Areas.Admin.Models.Orders;
using Nop.Web.Framework.Mvc.ModelBinding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order
{
public partial record OrderSearchModelExtended : OrderSearchModel
{
[NopResourceDisplayName("Admin.Orders.List.BillingCompany")]
public string BillingCompany { get; set; }
//public IList<SelectListItem> AvailableCompanies { get; set; }
}
}

View File

@ -1,7 +1,9 @@
@model OrderSearchModel
@model OrderSearchModelExtended
@inject IStoreService storeService
@using FruitBank.Common.Interfaces
@using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order
@using Nop.Plugin.Misc.FruitBankPlugin.Models
@using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders
@using Nop.Services.Stores
@ -96,7 +98,7 @@
</li>
</ul>
</div>
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.OrderListButtons, additionalData = Model })
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.OrderListButtons, additionalData = (OrderSearchModel)Model })
</div>
</div>
@ -174,6 +176,45 @@
</script>
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="BillingCompany" />
</div>
<div class="col-md-8">
<input type="text" id="search-company-name" autocomplete="off" class="form-control" />
<span id="search-company-friendly-name"></span>
<button type="button" id="search-company-clear" class="btn bg-gray" style="display: none; margin-top: 5px;">@T("Admin.Common.Clear")</button>
<input asp-for="BillingCompany" autocomplete="off" style="display: none;" />
<script>
$(function() {
$('#search-company-name').autocomplete({
delay: 500,
minLength: 3,
source: '@Url.Action("CustomerSearchAutoComplete", "CustomOrder")',
select: function(event, ui) {
$('#@Html.IdFor(model => model.BillingCompany)').val(ui.item.value);
$('#search-company-friendly-name').text(ui.item.label);
$('#search-company-clear').show();
return false;
}
});
//remove button
$('#search-company-clear').click(function() {
$('#@Html.IdFor(model => model.BillingCompany)').val('');
$('#search-company-friendly-name').text('');
$('#search-company-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" />
@ -326,6 +367,7 @@
new FilterParameter(nameof(Model.BillingEmail)),
new FilterParameter(nameof(Model.BillingPhone)),
new FilterParameter(nameof(Model.BillingLastName)),
new FilterParameter(nameof(Model.BillingCompany)),
new FilterParameter(nameof(Model.BillingCountryId)),
new FilterParameter(nameof(Model.PaymentMethodSystemName)),
new FilterParameter(nameof(Model.ProductId)),

View File

@ -61,6 +61,7 @@ public class FruitBankDbContext : MgDbContextBase,
public IRepository<Customer> Customers { get; set; }
public IRepository<CustomerRole> CustomerRoles { get; set; }
public IRepository<CustomerCustomerRoleMapping> CustomerRoleMappings { get; set; }
public IRepository<CustomerAddressMapping> CustomerAddressMappings { get; set; }
public FruitBankDbContext(INopDataProvider dataProvider, ILockService lockService, FruitBankAttributeService fruitBankAttributeService, IStoreContext storeContext,
PartnerDbTable partnerDbTable, ShippingDbTable shippingDbTable, ShippingDocumentDbTable shippingDocumentDbTable, ShippingItemDbTable shippingItemDbTable,
@ -72,6 +73,7 @@ public class FruitBankDbContext : MgDbContextBase,
IRepository<Product> productRepository,
IRepository<Customer> customerRepository,
IRepository<CustomerCustomerRoleMapping> customerCustomerRoleMappingRepository,
IRepository<CustomerAddressMapping> customerAddressMappingRepository,
IRepository<CustomerRole> customerRoleRepository,IEventPublisher eventPublisher,
IEnumerable<IAcLogWriterBase> logWriters) : base(productRepository, orderRepository, orderItemRepository, dataProvider, lockService, new Logger<FruitBankDbContext>(logWriters.ToArray()))
{
@ -100,6 +102,7 @@ public class FruitBankDbContext : MgDbContextBase,
Customers = customerRepository;
CustomerRoles = customerRoleRepository;
CustomerRoleMappings = customerCustomerRoleMappingRepository;
CustomerAddressMappings = customerAddressMappingRepository;
}
public IQueryable<Customer> GetCustomersBySystemRoleName(string systemRoleName)
@ -528,4 +531,15 @@ public class FruitBankDbContext : MgDbContextBase,
var list = await ShippingDocuments.GetAll(true).Where(sd => sd.ShippingId == shippingId).ToListAsync();
return list;
}
public async Task<CustomerAddressMapping?> AddCustomerAddressMappingAsync(int customerId, int addressId)
{
var customerAddressMapping = new CustomerAddressMapping
{
CustomerId = customerId,
AddressId = addressId
};
await CustomerAddressMappings.InsertAsync(customerAddressMapping);
return customerAddressMapping;
}
}

View File

@ -39,6 +39,7 @@ using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Factories.MgBase;
using FruitBank.Common.Dtos;
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order;
namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
{
@ -149,7 +150,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
_orderMeasurementService = orderMeasurementService;
}
#endregion Ctor
public override async Task<OrderSearchModel> PrepareOrderSearchModelAsync(OrderSearchModel searchModel)
public override async Task<OrderSearchModelExtended> PrepareOrderSearchModelAsync(OrderSearchModelExtended searchModel)
{
var baseModel = await base.PrepareOrderSearchModelAsync(searchModel);
return baseModel;
@ -158,7 +159,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
public override async Task<OrderListModel> PrepareOrderListModelAsync(OrderSearchModel searchModel)
=> await base.PrepareOrderListModelAsync(searchModel);
public async Task<OrderListModelExtended> PrepareOrderListModelExtendedAsync(OrderSearchModel searchModel)
public async Task<OrderListModelExtended> PrepareOrderListModelExtendedAsync(OrderSearchModelExtended searchModel)
{
Dictionary<int, OrderDto> orderDtosById = null;

View File

@ -1,6 +1,8 @@
using AyCode.Core.Extensions;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Newtonsoft.Json;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
@ -8,6 +10,7 @@ using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Tax;
using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
@ -30,7 +33,9 @@ using Nop.Services.Stores;
using Nop.Services.Tax;
using Nop.Services.Vendors;
using Nop.Web.Areas.Admin.Factories;
using Nop.Web.Areas.Admin.Models.Common;
using Nop.Web.Areas.Admin.Models.Orders;
using Nop.Web.Framework.Extensions;
namespace Nop.Plugin.Misc.FruitBankPlugin.Factories.MgBase;
@ -138,18 +143,120 @@ public class MgOrderModelFactory<TOrderListModelExt, TOrderModelExt> : OrderMode
}
#endregion Cotr
public override Task<OrderSearchModel> PrepareOrderSearchModelAsync(OrderSearchModel searchModel)
=> base.PrepareOrderSearchModelAsync(searchModel);
public override Task<OrderListModel> PrepareOrderListModelAsync(OrderSearchModel searchModel)
=> base.PrepareOrderListModelAsync(searchModel);
public virtual async Task<TOrderListModelExt> PrepareOrderListModelExtendedAsync(OrderSearchModel searchModel, Func<OrderListModel, TOrderModelExt, Task> dataItemCopiedCallback)
public virtual async Task<OrderSearchModelExtended> PrepareOrderSearchModelAsync(OrderSearchModelExtended searchModel)
{
var orderListModel = await PrepareOrderListModelAsync(searchModel);
var extendedRows = new List<TOrderModelExt>(orderListModel.RecordsFiltered);
ArgumentNullException.ThrowIfNull(searchModel);
foreach (var orderModel in orderListModel.Data.ToList())
searchModel.IsLoggedInAsVendor = await _workContext.GetCurrentVendorAsync() != null;
searchModel.BillingPhoneEnabled = _addressSettings.PhoneEnabled;
var licenseCheckModel = new LicenseCheckModel();
try
{
var result = await _nopHttpClient.GetLicenseCheckDetailsAsync();
if (!string.IsNullOrEmpty(result))
{
licenseCheckModel = JsonConvert.DeserializeObject<LicenseCheckModel>(result);
if (licenseCheckModel.DisplayWarning == false && licenseCheckModel.BlockPages == false)
await _settingService.SetSettingAsync($"{nameof(AdminAreaSettings)}.{nameof(AdminAreaSettings.CheckLicense)}", false);
}
}
catch { }
searchModel.LicenseCheckModel = licenseCheckModel;
//prepare available order, payment and shipping statuses
await _baseAdminModelFactory.PrepareOrderStatusesAsync(searchModel.AvailableOrderStatuses);
if (searchModel.AvailableOrderStatuses.Any())
{
if (searchModel.OrderStatusIds?.Any() ?? false)
{
var ids = searchModel.OrderStatusIds.Select(id => id.ToString());
var statusItems = searchModel.AvailableOrderStatuses.Where(statusItem => ids.Contains(statusItem.Value)).ToList();
foreach (var statusItem in statusItems)
{
statusItem.Selected = true;
}
}
else
searchModel.AvailableOrderStatuses.FirstOrDefault().Selected = true;
}
await _baseAdminModelFactory.PreparePaymentStatusesAsync(searchModel.AvailablePaymentStatuses);
if (searchModel.AvailablePaymentStatuses.Any())
{
if (searchModel.PaymentStatusIds?.Any() ?? false)
{
var ids = searchModel.PaymentStatusIds.Select(id => id.ToString());
var statusItems = searchModel.AvailablePaymentStatuses.Where(statusItem => ids.Contains(statusItem.Value)).ToList();
foreach (var statusItem in statusItems)
{
statusItem.Selected = true;
}
}
else
searchModel.AvailablePaymentStatuses.FirstOrDefault().Selected = true;
}
await _baseAdminModelFactory.PrepareShippingStatusesAsync(searchModel.AvailableShippingStatuses);
if (searchModel.AvailableShippingStatuses.Any())
{
if (searchModel.ShippingStatusIds?.Any() ?? false)
{
var ids = searchModel.ShippingStatusIds.Select(id => id.ToString());
var statusItems = searchModel.AvailableShippingStatuses.Where(statusItem => ids.Contains(statusItem.Value)).ToList();
foreach (var statusItem in statusItems)
{
statusItem.Selected = true;
}
}
else
searchModel.AvailableShippingStatuses.FirstOrDefault().Selected = true;
}
//prepare available stores
await _baseAdminModelFactory.PrepareStoresAsync(searchModel.AvailableStores);
//prepare available vendors
await _baseAdminModelFactory.PrepareVendorsAsync(searchModel.AvailableVendors);
//prepare available warehouses
await _baseAdminModelFactory.PrepareWarehousesAsync(searchModel.AvailableWarehouses);
//prepare available payment methods
searchModel.AvailablePaymentMethods = (await _paymentPluginManager.LoadAllPluginsAsync()).Select(method =>
new SelectListItem { Text = method.PluginDescriptor.FriendlyName, Value = method.PluginDescriptor.SystemName }).ToList();
searchModel.AvailablePaymentMethods.Insert(0, new SelectListItem { Text = await _localizationService.GetResourceAsync("Admin.Common.All"), Value = string.Empty });
//prepare available billing countries
searchModel.AvailableCountries = (await _countryService.GetAllCountriesForBillingAsync(showHidden: true))
.Select(country => new SelectListItem { Text = country.Name, Value = country.Id.ToString() }).ToList();
searchModel.AvailableCountries.Insert(0, new SelectListItem { Text = await _localizationService.GetResourceAsync("Admin.Common.All"), Value = "0" });
//searchModel.AvailableCompanies = (await _customerService.GetAllCustomersAsync())
// .Select(customer => new SelectListItem { Text = customer.Company, Value = customer.Id.ToString() }).ToList();
//searchModel.AvailableCompanies.Insert(0, new SelectListItem { Text = await _localizationService.GetResourceAsync("Admin.Common.All"), Value = "0" });
//prepare grid
searchModel.SetGridPageSize();
searchModel.HideStoresList = _catalogSettings.IgnoreStoreLimitations || searchModel.AvailableStores.SelectionIsNotPossible();
return searchModel;
}
public override async Task<OrderListModel> PrepareOrderListModelAsync(OrderSearchModel searchModel)
{
var preFiltered = await base.PrepareOrderListModelAsync(searchModel);
return preFiltered;
}
public virtual async Task<TOrderListModelExt> PrepareOrderListModelExtendedAsync(OrderSearchModelExtended searchModel, Func<OrderListModel, TOrderModelExt, Task> dataItemCopiedCallback)
{
var prefiltered = await PrepareOrderListModelAsync(searchModel);
var extendedRows = new List<TOrderModelExt>(prefiltered.RecordsFiltered);
foreach (var orderModel in prefiltered.Data.ToList())
{
var orderModelExtended = Activator.CreateInstance<TOrderModelExt>();
@ -157,15 +264,22 @@ public class MgOrderModelFactory<TOrderListModelExt, TOrderModelExt> : OrderMode
extendedRows.Add(orderModelExtended);
if (dataItemCopiedCallback == null) continue;
await dataItemCopiedCallback.Invoke(orderListModel, orderModelExtended);
await dataItemCopiedCallback.Invoke(prefiltered, orderModelExtended);
}
orderListModel.Data = null;
prefiltered.Data = null;
//var orderListModelExtended = orderListModel.ToJson().JsonTo<TOrderListModelExt>();
var orderListModelExtended = orderListModel.CloneTo<TOrderListModelExt>();
orderListModelExtended.Data = extendedRows;
if(searchModel.BillingCompany != null)
{
//extendedRows = extendedRows.Where(x => x.CustomerCompany != null && x.CustomerCompany.IndexOf(searchModel.BillingCompany, StringComparison.InvariantCultureIgnoreCase) >= 0).ToList();
extendedRows = extendedRows.Where(x => x.CustomerId == Convert.ToInt32(searchModel.BillingCompany)).ToList();
}
var orderListModelExtended = prefiltered.CloneTo<TOrderListModelExt>();
orderListModelExtended.Data = extendedRows;
orderListModelExtended.RecordsFiltered = extendedRows.Count;
return orderListModelExtended;
}
}

View File

@ -1,9 +1,17 @@
using FruitBank.Common.Interfaces;
using FruitBank.Common.Server;
using Mango.Nop.Core.Dtos;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Tax;
using Nop.Core.Events;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Models;
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
using Nop.Services.Catalog;
@ -16,11 +24,10 @@ using Nop.Web.Framework.Events;
using Nop.Web.Framework.Menu;
using Nop.Web.Models.Sitemap;
using System.Linq;
using Microsoft.IdentityModel.Tokens;
namespace Nop.Plugin.Misc.FruitBankPlugin.Services
{
public class EventConsumer : BaseAdminMenuCreatedEventConsumer, IConsumer<OrderPlacedEvent>, IConsumer<EntityUpdatedEvent<Order>>, IConsumer<AdminMenuCreatedEvent>
public class EventConsumer : BaseAdminMenuCreatedEventConsumer, IConsumer<EntityUpdatedEvent<Customer>>, IConsumer<EntityInsertedEvent<Customer>>, IConsumer<OrderPlacedEvent>, IConsumer<EntityUpdatedEvent<Order>>, IConsumer<AdminMenuCreatedEvent>
{
private readonly IGenericAttributeService _genericAttributeService;
private readonly IProductService _productService;
@ -32,7 +39,9 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
private readonly IAdminMenu _adminMenu;
private readonly ILocalizationService _localizationService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAddressService _addressService;
private readonly FruitBankAttributeService _fruitBankAttributeService;
private readonly FruitBankDbContext _dbContext;
public EventConsumer(
IGenericAttributeService genericAttributeService,
@ -46,7 +55,9 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
IAdminMenu adminMenu,
ILocalizationService localizationService,
IHttpContextAccessor httpContextAccessor,
FruitBankAttributeService fruitBankAttributeService) : base(pluginManager)
IAddressService addressService,
FruitBankAttributeService fruitBankAttributeService,
FruitBankDbContext dbContext) : base(pluginManager)
{
_genericAttributeService = genericAttributeService;
_productService = productService;
@ -58,7 +69,9 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
_adminMenu = adminMenu;
_localizationService = localizationService;
_httpContextAccessor = httpContextAccessor;
_addressService = addressService;
_fruitBankAttributeService = fruitBankAttributeService;
_dbContext = dbContext;
}
protected override string PluginSystemName => "Misc.FruitBankPlugin";
@ -210,5 +223,82 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
});
}
public async Task HandleEventAsync(EntityInsertedEvent<Customer> eventMessage)
{
await ProcessCustomerEventAsync(eventMessage?.Entity);
}
public async Task HandleEventAsync(EntityUpdatedEvent<Customer> eventMessage)
{
await ProcessCustomerEventAsync(eventMessage?.Entity);
}
private async Task ProcessCustomerEventAsync(Customer customer)
{
if (customer == null)
{
return;
}
if (!IsRegisteredCustomer(customer))
{
return;
}
await EnsureBillingAddressAsync(customer);
await SynchronizeTaxInformationAsync(customer);
}
private bool IsRegisteredCustomer(Customer customer)
{
var customerRoles = _dbContext.GetCustomerRolesByCustomerId(customer.Id);
return customerRoles?.Any(role => role.SystemName == "Registered") ?? false;
}
private async Task EnsureBillingAddressAsync(Customer customer)
{
if (customer.BillingAddressId != null)
{
return;
}
var address = new Address
{
ZipPostalCode = customer.ZipPostalCode,
City = customer.City,
Address1 = customer.StreetAddress,
Address2 = customer.StreetAddress2,
CountryId = customer.CountryId > 0 ? customer.CountryId : null,
StateProvinceId = customer.StateProvinceId > 0 ? customer.StateProvinceId : null,
PhoneNumber = customer.Phone,
Company = customer.Company,
FirstName = customer.FirstName,
LastName = customer.LastName,
Email = customer.Email,
County = customer.County
};
await _addressService.InsertAddressAsync(address);
await _dbContext.AddCustomerAddressMappingAsync(customer.Id, address.Id);
customer.BillingAddressId = address.Id;
}
private async Task SynchronizeTaxInformationAsync(Customer customer)
{
var taxId = await _fruitBankAttributeService.GetGenericAttributeValueAsync<Customer, string>(customer.Id, "TaxId");
if (!string.IsNullOrWhiteSpace(taxId) && string.IsNullOrWhiteSpace(customer.VatNumber))
{
customer.VatNumber = taxId;
}
else if (string.IsNullOrWhiteSpace(taxId) && !string.IsNullOrWhiteSpace(customer.VatNumber))
{
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Customer, string>(
customer.Id,
"TaxId",
customer.VatNumber);
}
}
}
}

View File

@ -18,7 +18,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
private readonly string _password;
private readonly string _baseUrl;
public InnVoiceOrderService(string companyName = "fruitbank", string username = "fruitbank", string password = "YkKzM1dwJax0HTIPWIMABSqdSA", string baseUrl = "https://api.innvoice.hu")
public InnVoiceOrderService(string companyName = "fruitbank", string username = "fruitbank", string password = "YkKzM1dwJax0HTlPWlMABSqdSA", string baseUrl = "https://api.innvoice.hu")
{
_companyName = companyName ?? throw new ArgumentNullException(nameof(companyName));
_username = username ?? throw new ArgumentNullException(nameof(username));

View File

@ -10,8 +10,9 @@
<div class="card-body">
<!-- Order Subsection -->
<div class="form-group row">
<h3>Innvoice</h3>
<div class="col-12 col-md-3">
<h5><i class="fas fa-shopping-cart"></i> Order</h5>
<h5><i class="fas fa-shopping-cart"></i> Megrendelés beküldése Innvoice-ba</h5>
<div id="orderStatus" class="alert alert-info" style="display: none;">
<i class="fas fa-info-circle"></i> <span id="orderStatusMessage"></span>
</div>
@ -20,22 +21,22 @@
<p><strong>Order Tech ID:</strong> <span id="orderTechId"></span></p>
<p>
<a id="orderPdfLink" href="#" target="_blank" class="btn btn-sm btn-info">
<i class="fas fa-file-pdf"></i> View Order PDF
<i class="fas fa-file-pdf"></i> PDF megtekintése
</a>
</p>
</div>
</div>
<div class="col-12 col-md-3 text-right">
<div class="col-12 col-md-3 text-right float-end">
<button type="button" id="createOrderBtn" class="btn btn-success">
<i class="fas fa-shopping-cart"></i> Create Order in InnVoice
<i class="fas fa-shopping-cart"></i> Létrehozás
</button>
<button type="button" id="checkOrderBtn" class="btn btn-secondary" style="display: none;">
<i class="fas fa-sync"></i> Refresh Order
<i class="fas fa-sync"></i> Invoice adat ellenőrzése
</button>
</div>
<div class="col-12 col-md-3">
@* <div class="col-12 col-md-3">
<h5><i class="fas fa-file-invoice-dollar"></i> Invoice</h5>
<div id="invoiceStatus" class="alert alert-info" style="display: none;">
<i class="fas fa-info-circle"></i> <span id="invoiceStatusMessage"></span>
@ -49,16 +50,16 @@
</a>
</p>
</div>
</div>
</div> *@
<div class="col-12 col-md-3 text-right">
@* <div class="col-12 col-md-3 text-right">
<button type="button" id="createInvoiceBtn" class="btn btn-success">
<i class="fas fa-file-invoice-dollar"></i> Create Invoice
</button>
<button type="button" id="checkInvoiceBtn" class="btn btn-secondary" style="display: none;">
<i class="fas fa-sync"></i> Refresh Invoice
</button>
</div>
</div> *@
</div>
<!-- Attributes Section (NO FORM TAG - just a div) -->