1086 lines
51 KiB
C#
1086 lines
51 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading.Tasks;
|
||
using Sozsoft.Platform.Entities;
|
||
using Volo.Abp.Domain.Repositories;
|
||
using System.Text;
|
||
using Sozsoft.Platform.Data.Seeds;
|
||
using Sozsoft.Sender.Mail;
|
||
using Volo.Abp.Settings;
|
||
using Sozsoft.Platform.Demos;
|
||
using Microsoft.Extensions.Logging;
|
||
using Volo.Abp.Domain.Entities;
|
||
using System.Linq;
|
||
using System.Net;
|
||
using Volo.Abp.Application.Dtos;
|
||
using System.Text.Json;
|
||
using Volo.Abp.Identity;
|
||
using Sozsoft.Languages;
|
||
using Sozsoft.Languages.Entities;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using static Sozsoft.Platform.Data.Seeds.SeedConsts;
|
||
|
||
namespace Sozsoft.Platform.Public;
|
||
|
||
/// <summary>
|
||
/// Public site uc noktalari. Okuma metotlari anonim erisime aciktir; sayfa tasarimini
|
||
/// degistiren <c>Save*PageAsync</c> metotlari <see cref="AppCodes.WebSiteDesign"/>
|
||
/// yetkileriyle korunur (yetkiler PermissionsData.json icinde tanimli, seeder ile yonetilir).
|
||
/// </summary>
|
||
public class PublicAppService : PlatformAppService
|
||
{
|
||
private readonly IRepository<Service, Guid> _serviceRepository;
|
||
private readonly ISettingProvider _settingProvider;
|
||
private readonly ISozsoftEmailSender _emailSender;
|
||
private readonly IRepository<Demo, Guid> _demoRepository;
|
||
private readonly IRepository<BlogPost, Guid> _postRepository;
|
||
private readonly IRepository<BlogCategory, Guid> _categoryRepository;
|
||
private readonly IRepository<Product, Guid> _productRepository;
|
||
private readonly IRepository<PaymentMethod, Guid> _paymentMethodRepository;
|
||
private readonly IRepository<InstallmentOption> _installmentOptionRepository;
|
||
private readonly IRepository<Order, Guid> _orderRepository;
|
||
private readonly IRepository<About, Guid> _aboutRepository;
|
||
private readonly IRepository<Home, Guid> _homeRepository;
|
||
private readonly IRepository<Contact, Guid> _contactRepository;
|
||
private readonly IRepository<Country, string> _countryRepository;
|
||
private readonly IRepository<City, Guid> _cityRepository;
|
||
private readonly IRepository<District, Guid> _districtRepository;
|
||
private readonly IIdentityUserRepository _identityUserRepository;
|
||
private readonly IRepository<LanguageKey, Guid> _languageKeyRepository;
|
||
private readonly IRepository<LanguageText, Guid> _languageTextRepository;
|
||
private readonly LanguageTextAppService _languageTextAppService;
|
||
|
||
public PublicAppService(
|
||
IRepository<Service, Guid> serviceRepository,
|
||
ISettingProvider settingProvider,
|
||
ISozsoftEmailSender emailSender,
|
||
IRepository<Demo, Guid> demoRepository,
|
||
IRepository<BlogPost, Guid> postRepository,
|
||
IRepository<BlogCategory, Guid> categoryRepository,
|
||
IRepository<Product, Guid> productRepository,
|
||
IRepository<PaymentMethod, Guid> paymentMethodRepository,
|
||
IRepository<InstallmentOption> installmentOptionRepository,
|
||
IRepository<Order, Guid> orderRepository,
|
||
IRepository<About, Guid> aboutRepository,
|
||
IRepository<Home, Guid> homeRepository,
|
||
IRepository<Contact, Guid> contactRepository,
|
||
IRepository<Country, string> countryRepository,
|
||
IRepository<City, Guid> cityRepository,
|
||
IRepository<District, Guid> districtRepository,
|
||
IIdentityUserRepository identityUserRepository,
|
||
IRepository<LanguageKey, Guid> languageKeyRepository,
|
||
IRepository<LanguageText, Guid> languageTextRepository,
|
||
LanguageTextAppService languageTextAppService
|
||
)
|
||
{
|
||
_serviceRepository = serviceRepository;
|
||
_settingProvider = settingProvider;
|
||
_emailSender = emailSender;
|
||
_demoRepository = demoRepository;
|
||
_postRepository = postRepository;
|
||
_categoryRepository = categoryRepository;
|
||
_productRepository = productRepository;
|
||
_paymentMethodRepository = paymentMethodRepository;
|
||
_installmentOptionRepository = installmentOptionRepository;
|
||
_orderRepository = orderRepository;
|
||
_aboutRepository = aboutRepository;
|
||
_homeRepository = homeRepository;
|
||
_contactRepository = contactRepository;
|
||
_identityUserRepository = identityUserRepository;
|
||
_languageKeyRepository = languageKeyRepository;
|
||
_languageTextRepository = languageTextRepository;
|
||
_languageTextAppService = languageTextAppService;
|
||
_countryRepository = countryRepository;
|
||
_cityRepository = cityRepository;
|
||
_districtRepository = districtRepository;
|
||
}
|
||
|
||
public async Task<List<ServiceDto>> GetServicesListAsync()
|
||
{
|
||
var queryable = await _serviceRepository.GetQueryableAsync();
|
||
var entity = await AsyncExecuter.ToListAsync(queryable.OrderBy(a => a.CreationTime));
|
||
|
||
return ObjectMapper.Map<List<Service>, List<ServiceDto>>(entity);
|
||
}
|
||
|
||
[Authorize(AppCodes.WebSiteDesign.About)]
|
||
public async Task SaveAboutPageAsync(SaveAboutPageInput input)
|
||
{
|
||
var entity = await _aboutRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(About));
|
||
|
||
entity.StatsJson = JsonSerializer.Serialize(input.Stats.Select(stat => new StatDto
|
||
{
|
||
Icon = stat.Icon,
|
||
Value = stat.Value,
|
||
LabelKey = stat.LabelKey,
|
||
UseCounter = stat.UseCounter,
|
||
CounterEnd = stat.CounterEnd,
|
||
CounterSuffix = stat.CounterSuffix,
|
||
CounterDuration = stat.CounterDuration,
|
||
}).ToList());
|
||
|
||
entity.DescriptionsJson = JsonSerializer.Serialize(input.Descriptions.Select(item => item.Key).ToList());
|
||
|
||
entity.SectionsJson = JsonSerializer.Serialize(input.Sections.Select(section => new SectionDto
|
||
{
|
||
Key = section.TitleKey,
|
||
DescKey = section.DescriptionKey,
|
||
}).ToList());
|
||
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroTitleKey, input.HeroTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroSubtitleKey, input.HeroSubtitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroImageKey, input.HeroImageValue);
|
||
|
||
foreach (var stat in input.Stats)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, stat.LabelKey, stat.LabelValue);
|
||
}
|
||
|
||
foreach (var description in input.Descriptions)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, description.Key, description.Value);
|
||
}
|
||
|
||
foreach (var section in input.Sections)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, section.TitleKey, section.TitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, section.DescriptionKey, section.DescriptionValue);
|
||
}
|
||
|
||
foreach (var styleText in input.StyleTexts)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, styleText.Key, styleText.Value);
|
||
}
|
||
|
||
await _aboutRepository.UpdateAsync(entity, autoSave: true);
|
||
await _languageTextAppService.ClearRedisCacheAsync();
|
||
}
|
||
|
||
[Authorize(AppCodes.WebSiteDesign.Services)]
|
||
public async Task SaveServicesPageAsync(SaveServicesPageInput input)
|
||
{
|
||
var existingEntities = await _serviceRepository.GetListAsync();
|
||
|
||
foreach (var entity in existingEntities)
|
||
{
|
||
await _serviceRepository.DeleteAsync(entity, autoSave: false);
|
||
}
|
||
|
||
foreach (var item in input.ServiceItems.Concat(input.SupportItems))
|
||
{
|
||
var entity = new Service
|
||
{
|
||
Icon = item.Icon,
|
||
Title = item.TitleKey,
|
||
Description = item.DescriptionKey,
|
||
Type = item.Type,
|
||
Features = item.Features.Select(feature => feature.Key).ToArray(),
|
||
};
|
||
|
||
await _serviceRepository.InsertAsync(entity, autoSave: false);
|
||
|
||
await UpsertLanguageTextAsync(input.CultureName, item.TitleKey, item.TitleValue);
|
||
|
||
if (!item.DescriptionKey.IsNullOrWhiteSpace())
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, item.DescriptionKey!, item.DescriptionValue ?? string.Empty);
|
||
}
|
||
|
||
foreach (var feature in item.Features)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, feature.Key, feature.Value);
|
||
}
|
||
}
|
||
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroTitleKey, input.HeroTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroSubtitleKey, input.HeroSubtitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroImageKey, input.HeroImageValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.SupportTitleKey, input.SupportTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.SupportButtonLabelKey, input.SupportButtonLabelValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleKey, input.CtaTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.CtaDescriptionKey, input.CtaDescriptionValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue);
|
||
|
||
foreach (var styleText in input.StyleTexts)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, styleText.Key, styleText.Value);
|
||
}
|
||
|
||
await CurrentUnitOfWork!.SaveChangesAsync();
|
||
await _languageTextAppService.ClearRedisCacheAsync();
|
||
}
|
||
|
||
public async Task<HomeDto> GetHomeAsync()
|
||
{
|
||
var entity = await _homeRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Home));
|
||
return ObjectMapper.Map<Home, HomeDto>(entity);
|
||
}
|
||
|
||
[Authorize(AppCodes.WebSiteDesign.Home)]
|
||
public async Task SaveHomePageAsync(SaveHomePageInput input)
|
||
{
|
||
var entity = await _homeRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Home));
|
||
|
||
entity.HeroBackgroundImageKey = input.HeroBackgroundImageKey;
|
||
entity.HeroPrimaryCtaKey = input.HeroPrimaryCtaKey;
|
||
entity.HeroSecondaryCtaKey = input.HeroSecondaryCtaKey;
|
||
entity.FeaturesTitleKey = input.FeaturesTitleKey;
|
||
entity.FeaturesSubtitleKey = input.FeaturesSubtitleKey;
|
||
entity.SolutionsTitleKey = input.SolutionsTitleKey;
|
||
entity.SolutionsSubtitleKey = input.SolutionsSubtitleKey;
|
||
entity.CtaTitleKey = input.CtaTitleKey;
|
||
entity.CtaSubtitleKey = input.CtaSubtitleKey;
|
||
entity.CtaButtonLabelKey = input.CtaButtonLabelKey;
|
||
|
||
entity.SlidesJson = JsonSerializer.Serialize(input.Slides.Select(slide => new HomeSlideDto
|
||
{
|
||
TitleKey = slide.TitleKey,
|
||
SubtitleKey = slide.SubtitleKey,
|
||
StyleClass = slide.StyleClass,
|
||
Services = slide.Services.Select(service => new HomeSlideServiceDto
|
||
{
|
||
Icon = service.Icon,
|
||
TitleKey = service.TitleKey,
|
||
DescriptionKey = service.DescriptionKey,
|
||
StyleClass = service.StyleClass,
|
||
}).ToList(),
|
||
}).ToList());
|
||
|
||
entity.FeaturesJson = JsonSerializer.Serialize(input.Features.Select(feature => new HomeFeatureDto
|
||
{
|
||
Icon = feature.Icon,
|
||
TitleKey = feature.TitleKey,
|
||
DescriptionKey = feature.DescriptionKey,
|
||
StyleClass = feature.StyleClass,
|
||
}).ToList());
|
||
|
||
entity.SolutionsJson = JsonSerializer.Serialize(input.Solutions.Select(solution => new HomeSolutionDto
|
||
{
|
||
Icon = solution.Icon,
|
||
ColorClass = solution.ColorClass,
|
||
TitleKey = solution.TitleKey,
|
||
DescriptionKey = solution.DescriptionKey,
|
||
StyleClass = solution.StyleClass,
|
||
}).ToList());
|
||
|
||
await _homeRepository.UpdateAsync(entity, autoSave: false);
|
||
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroBackgroundImageKey, input.HeroBackgroundImageValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaKey, input.HeroPrimaryCtaValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaStyleKey, input.HeroPrimaryCtaStyleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaKey, input.HeroSecondaryCtaValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaStyleKey, input.HeroSecondaryCtaStyleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleKey, input.FeaturesTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleStyleKey, input.FeaturesTitleStyleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleKey, input.FeaturesSubtitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleStyleKey, input.FeaturesSubtitleStyleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleKey, input.SolutionsTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleStyleKey, input.SolutionsTitleStyleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleKey, input.SolutionsSubtitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleStyleKey, input.SolutionsSubtitleStyleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleKey, input.CtaTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleStyleKey, input.CtaTitleStyleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleKey, input.CtaSubtitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleStyleKey, input.CtaSubtitleStyleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonStyleKey, input.CtaButtonStyleValue);
|
||
|
||
foreach (var slide in input.Slides)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, slide.TitleKey, slide.TitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, slide.SubtitleKey, slide.SubtitleValue);
|
||
|
||
foreach (var service in slide.Services)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, service.TitleKey, service.TitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, service.DescriptionKey, service.DescriptionValue);
|
||
}
|
||
}
|
||
|
||
foreach (var feature in input.Features)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, feature.TitleKey, feature.TitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, feature.DescriptionKey, feature.DescriptionValue);
|
||
}
|
||
|
||
foreach (var solution in input.Solutions)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, solution.TitleKey, solution.TitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, solution.DescriptionKey, solution.DescriptionValue);
|
||
}
|
||
|
||
await CurrentUnitOfWork!.SaveChangesAsync();
|
||
await _languageTextAppService.ClearRedisCacheAsync();
|
||
}
|
||
|
||
public async Task CreateDemoAsync(DemoDto input)
|
||
{
|
||
var demo = ObjectMapper.Map<DemoDto, Demo>(input);
|
||
await _demoRepository.InsertAsync(demo);
|
||
|
||
var bodyBuilder = new StringBuilder();
|
||
bodyBuilder.AppendLine($"Şirket: {input.OrganizationName}");
|
||
bodyBuilder.AppendLine($"Ad Soyad: {input.Name}");
|
||
bodyBuilder.AppendLine($"E-Posta: {input.Email}");
|
||
bodyBuilder.AppendLine($"Telefon: {input.PhoneNumber}");
|
||
bodyBuilder.AppendLine($"Adres: {input.Address}");
|
||
bodyBuilder.AppendLine($"Şube Sayısı: {input.NumberOfBranches}");
|
||
bodyBuilder.AppendLine($"Kullanıcı Sayısı: {input.NumberOfUsers}");
|
||
bodyBuilder.AppendLine($"Mesaj: {input.Message}");
|
||
|
||
var SenderName = await _settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromDisplayName);
|
||
var SenderEmailAddress = await _settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromAddress);
|
||
|
||
await _emailSender.QueueEmailAsync(
|
||
SenderEmailAddress ?? string.Empty,
|
||
new KeyValuePair<string, string>(SenderName ?? string.Empty, SenderEmailAddress ?? string.Empty),
|
||
null,
|
||
bodyBuilder.ToString(),
|
||
subject: PlatformConsts.AppName + " : Demo Talebi");
|
||
|
||
}
|
||
|
||
public async Task<BlogPostAndCategoriesDto> GetPostListAsync(GetBlogPostsInput input)
|
||
{
|
||
// IQueryable
|
||
var postQuery = await _postRepository.GetQueryableAsync();
|
||
|
||
// 🔠Arama
|
||
if (!input.Search.IsNullOrWhiteSpace())
|
||
{
|
||
postQuery = postQuery.Where(p =>
|
||
p.ContentTr.Contains(input.Search) ||
|
||
p.ContentEn.Contains(input.Search));
|
||
}
|
||
|
||
// 📠Kategori filtresi
|
||
if (input.CategoryId.HasValue)
|
||
{
|
||
postQuery = postQuery.Where(p => p.CategoryId == input.CategoryId.Value);
|
||
}
|
||
|
||
// Toplam adet (sayfalama öncesi)
|
||
var totalCount = await AsyncExecuter.CountAsync(postQuery);
|
||
|
||
// Sayfalama + sıralama
|
||
var pagedPosts = await AsyncExecuter.ToListAsync(
|
||
postQuery
|
||
.OrderByDescending(p => p.CreationTime)
|
||
.PageBy(input)
|
||
);
|
||
|
||
// Sayfadaki kategori kayıtları
|
||
var categoryIds = pagedPosts.Select(x => x.CategoryId).Distinct().ToList();
|
||
var pageCategories = await _categoryRepository.GetListAsync(x => categoryIds.Contains(x.Id));
|
||
var categoryDict = pageCategories.ToDictionary(x => x.Id, x => x);
|
||
|
||
// Post DTO mapping
|
||
var postDtos = pagedPosts.Select(post =>
|
||
{
|
||
var dto = ObjectMapper.Map<BlogPost, BlogPostListDto>(post);
|
||
if (categoryDict.TryGetValue(post.CategoryId, out var c))
|
||
{
|
||
dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(c);
|
||
}
|
||
|
||
return dto;
|
||
}).ToList();
|
||
|
||
// ----------- KATEGORİLER (PostCount ile) - Optimize edildi -----------
|
||
var categoryQueryable = await _categoryRepository.GetQueryableAsync();
|
||
var postQueryableForCount = await _postRepository.GetQueryableAsync();
|
||
|
||
// Kategori listesi ve post sayıları tek sorguda
|
||
var categoriesWithCounts = await AsyncExecuter.ToListAsync(
|
||
from category in categoryQueryable
|
||
join post in postQueryableForCount.Where(p => p.IsPublished)
|
||
on category.Id equals post.CategoryId into postGroup
|
||
select new
|
||
{
|
||
Category = category,
|
||
PostCount = postGroup.Count()
|
||
}
|
||
);
|
||
|
||
var categoryDtos = categoriesWithCounts.Select(x =>
|
||
{
|
||
var dto = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(x.Category);
|
||
dto.PostCount = x.PostCount;
|
||
return dto;
|
||
}).ToList();
|
||
|
||
return new BlogPostAndCategoriesDto
|
||
{
|
||
Posts = new PagedResultDto<BlogPostListDto>(totalCount, postDtos),
|
||
Categories = categoryDtos
|
||
};
|
||
}
|
||
|
||
private async Task<BlogPostDto> GetPostAsync(Guid id)
|
||
{
|
||
// Tek sorguda post ve category'yi çek (N+1 önleme)
|
||
var queryable = await _postRepository.GetQueryableAsync();
|
||
var categoryQueryable = await _categoryRepository.GetQueryableAsync();
|
||
|
||
var result = await AsyncExecuter.FirstOrDefaultAsync(
|
||
from post in queryable.Where(p => p.Id == id)
|
||
join category in categoryQueryable on post.CategoryId equals category.Id
|
||
select new { Post = post, Category = category }
|
||
);
|
||
|
||
if (result == null)
|
||
throw new EntityNotFoundException(typeof(BlogPost));
|
||
|
||
var dto = ObjectMapper.Map<BlogPost, BlogPostDto>(result.Post);
|
||
dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(result.Category);
|
||
|
||
return dto;
|
||
}
|
||
|
||
public async Task<BlogPostDto> GetPostBySlugAsync(string slug)
|
||
{
|
||
var post = await _postRepository.FirstOrDefaultAsync(x => x.Slug == slug);
|
||
if (post == null)
|
||
{
|
||
throw new EntityNotFoundException(typeof(BlogPost));
|
||
}
|
||
|
||
return await GetPostAsync(post.Id);
|
||
}
|
||
|
||
public async Task<List<ProductDto>> GetProductListAsync()
|
||
{
|
||
// Performans: Sıralamayı database'de yap
|
||
var queryable = await _productRepository.GetQueryableAsync();
|
||
var products = await AsyncExecuter.ToListAsync(
|
||
queryable.OrderBy(p => p.Order)
|
||
);
|
||
|
||
return ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
|
||
}
|
||
|
||
public async Task<List<PaymentMethodDto>> GetPaymentMethodListAsync()
|
||
{
|
||
var paymentMethods = await _paymentMethodRepository.GetListAsync();
|
||
|
||
return ObjectMapper.Map<List<PaymentMethod>, List<PaymentMethodDto>>(paymentMethods);
|
||
}
|
||
|
||
public async Task<List<InstallmentOptionDto>> GetInstallmentOptionListAsync()
|
||
{
|
||
var installmentOptions = await _installmentOptionRepository.GetListAsync();
|
||
|
||
return ObjectMapper.Map<List<InstallmentOption>, List<InstallmentOptionDto>>(installmentOptions);
|
||
}
|
||
|
||
public async Task<OrderDto> CreateOrderAsync(OrderDto input)
|
||
{
|
||
var entity = new Order(GuidGenerator.Create())
|
||
{
|
||
TenantId = input.Tenant.Id,
|
||
OrganizationName = input.Tenant.OrganizationName,
|
||
Founder = input.Tenant.Founder,
|
||
VknTckn = input.Tenant.VknTckn,
|
||
TaxOffice = input.Tenant.TaxOffice,
|
||
Address1 = input.Tenant.Address1,
|
||
Address2 = input.Tenant.Address2,
|
||
Country = input.Tenant.Country,
|
||
City = input.Tenant.City,
|
||
District = input.Tenant.District,
|
||
Township = input.Tenant.Township,
|
||
PostalCode = input.Tenant.PostalCode,
|
||
MobileNumber = NormalizePhoneNumber(input.Tenant.MobileNumber, input.Tenant.Country),
|
||
PhoneNumber = NormalizePhoneNumber(input.Tenant.PhoneNumber, input.Tenant.Country),
|
||
FaxNumber = NormalizePhoneNumber(input.Tenant.FaxNumber, input.Tenant.Country),
|
||
Email = input.Tenant.Email,
|
||
Website = input.Tenant.Website,
|
||
MenuGroup = input.Tenant.MenuGroup,
|
||
|
||
BillingCycle = input.BillingCycle,
|
||
Period = input.Period,
|
||
LicenseStartTime = input.LicenseStartTime,
|
||
LicenseEndTime = input.LicenseEndTime,
|
||
Subtotal = input.Subtotal,
|
||
VatTotal = input.VatTotal,
|
||
Commission = input.Commission,
|
||
Total = input.Total,
|
||
PaymentMethodId = input.PaymentMethodId,
|
||
Installment = input.Installment,
|
||
PaymentDataJson = JsonSerializer.Serialize(input.PaymentData),
|
||
|
||
IsExisting = input.IsExisting,
|
||
};
|
||
|
||
decimal subtotal = 0;
|
||
decimal vatTotal = 0;
|
||
foreach (var item in input.Items)
|
||
{
|
||
var productId = item.ProductId != Guid.Empty ? item.ProductId : item.Product.Id;
|
||
var product = await _productRepository.FindAsync(productId);
|
||
if (product == null && !item.Product.Name.IsNullOrWhiteSpace())
|
||
{
|
||
product = await _productRepository.FirstOrDefaultAsync(x => x.Name == item.Product.Name);
|
||
}
|
||
|
||
if (product == null)
|
||
{
|
||
throw new EntityNotFoundException(typeof(Product), productId);
|
||
}
|
||
|
||
var vatRate = product.VatRate;
|
||
var price = GetOrderItemPrice(product, item.BillingCycle);
|
||
var grossTotal = Math.Round(item.TotalPrice, 2, MidpointRounding.AwayFromZero);
|
||
var netTotal = vatRate > 0
|
||
? Math.Round(grossTotal / (1 + vatRate), 2, MidpointRounding.AwayFromZero)
|
||
: grossTotal;
|
||
var vatAmount = Math.Round(grossTotal - netTotal, 2, MidpointRounding.AwayFromZero);
|
||
|
||
subtotal += netTotal;
|
||
vatTotal += vatAmount;
|
||
|
||
entity.Items.Add(new OrderItem
|
||
{
|
||
OrderId = entity.Id,
|
||
Order = entity,
|
||
ProductId = product.Id,
|
||
ProductName = product.Name,
|
||
BillingCycle = item.BillingCycle,
|
||
Period = input.Period,
|
||
Quantity = item.Quantity,
|
||
Price = price,
|
||
VatRate = vatRate,
|
||
VatAmount = vatAmount,
|
||
TotalPrice = grossTotal
|
||
});
|
||
}
|
||
|
||
entity.Subtotal = Math.Round(subtotal, 2, MidpointRounding.AwayFromZero);
|
||
entity.VatTotal = Math.Round(vatTotal, 2, MidpointRounding.AwayFromZero);
|
||
entity.Total = Math.Round(entity.Subtotal + entity.VatTotal + entity.Commission, 2, MidpointRounding.AwayFromZero);
|
||
|
||
await _orderRepository.InsertAsync(entity, autoSave: true);
|
||
await SendOrderCreatedEmailsAsync(entity);
|
||
|
||
return new OrderDto
|
||
{
|
||
Id = entity.Id,
|
||
Total = entity.Total,
|
||
PaymentMethodId = entity.PaymentMethodId
|
||
};
|
||
}
|
||
|
||
private static decimal GetOrderItemPrice(Product product, string billingCycle)
|
||
{
|
||
return string.Equals(billingCycle, "monthly", StringComparison.OrdinalIgnoreCase)
|
||
? product.MonthlyPrice ?? 0
|
||
: product.YearlyPrice ?? 0;
|
||
}
|
||
|
||
private async Task SendOrderCreatedEmailsAsync(Order order)
|
||
{
|
||
var recipients = new List<string>();
|
||
if (!order.Email.IsNullOrWhiteSpace())
|
||
{
|
||
recipients.Add(order.Email.Trim());
|
||
}
|
||
|
||
var notificationEmails = await _settingProvider.GetOrNullAsync("App.SiteManagement.General.NewTenantNotificationEmails");
|
||
if (!notificationEmails.IsNullOrWhiteSpace())
|
||
{
|
||
recipients.AddRange(notificationEmails
|
||
.Split(new[] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)
|
||
.Select(email => email.Trim())
|
||
.Where(email => !email.IsNullOrWhiteSpace()));
|
||
}
|
||
|
||
recipients = recipients
|
||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||
.ToList();
|
||
|
||
if (recipients.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var senderName = await _settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromDisplayName);
|
||
var senderEmail = await _settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromAddress);
|
||
KeyValuePair<string, string>? sender = null;
|
||
if (!senderEmail.IsNullOrWhiteSpace())
|
||
{
|
||
sender = new KeyValuePair<string, string>(senderName ?? string.Empty, senderEmail);
|
||
}
|
||
|
||
var body = BuildOrderEmailBody(order);
|
||
var subject = $"{PlatformConsts.AppName} : Yeni Sipariş #{order.Id}";
|
||
|
||
foreach (var recipient in recipients)
|
||
{
|
||
try
|
||
{
|
||
await _emailSender.SendEmailAsync(
|
||
recipient,
|
||
sender,
|
||
new { },
|
||
body,
|
||
subject,
|
||
null,
|
||
true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.LogError(ex, "Order email could not be sent to {Recipient}", recipient);
|
||
}
|
||
}
|
||
}
|
||
|
||
private string BuildOrderEmailBody(Order order)
|
||
{
|
||
var itemRows = string.Join(string.Empty, order.Items.Select(item => $@"
|
||
<tr>
|
||
<td style=""padding:16px 14px;border-bottom:1px solid #e5e7eb;font-weight:700;color:#111827;line-height:20px;"">{Html(Translate(item.ProductName))}</td>
|
||
<td align=""right"" style=""padding:16px 14px;border-bottom:1px solid #e5e7eb;text-align:right;color:#374151;white-space:nowrap;line-height:20px;"">{FormatPrice(item.Price)}</td>
|
||
<td align=""right"" style=""padding:16px 14px;border-bottom:1px solid #e5e7eb;text-align:right;color:#374151;white-space:nowrap;line-height:20px;"">{item.Period}</td>
|
||
<td align=""right"" style=""padding:16px 14px;border-bottom:1px solid #e5e7eb;text-align:right;color:#374151;white-space:nowrap;line-height:20px;"">{item.Quantity}</td>
|
||
<td align=""right"" style=""padding:16px 14px;border-bottom:1px solid #e5e7eb;text-align:right;color:#374151;white-space:nowrap;line-height:20px;"">{BillingCycleLabel(item.BillingCycle)}</td>
|
||
<td align=""right"" style=""padding:16px 14px;border-bottom:1px solid #e5e7eb;text-align:right;color:#374151;white-space:nowrap;line-height:20px;"">%{item.VatRate * 100:0.##} / {FormatPrice(item.VatAmount)}</td>
|
||
<td align=""right"" style=""padding:16px 14px;border-bottom:1px solid #e5e7eb;text-align:right;font-weight:800;color:#111827;white-space:nowrap;line-height:20px;"">{FormatPrice(item.TotalPrice)}</td>
|
||
</tr>"));
|
||
|
||
return $@"
|
||
<!doctype html>
|
||
<html>
|
||
<head>
|
||
<meta http-equiv=""Content-Type"" content=""text/html; charset=utf-8"" />
|
||
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0"" />
|
||
<meta name=""x-apple-disable-message-reformatting"" />
|
||
</head>
|
||
<body bgcolor=""#f3f4f6"" style=""margin:0;padding:0;background-color:#f3f4f6;font-family:Arial,Helvetica,sans-serif;color:#111827;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;"">
|
||
<div style=""display:none;font-size:1px;color:#f3f4f6;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;"">
|
||
{Html(Translate("App.OrderSuccess.SuccessTitle"))} #{order.Id}
|
||
</div>
|
||
<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" bgcolor=""#f3f4f6"" style=""width:100%;background-color:#f3f4f6;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<tr>
|
||
<td align=""center"" style=""padding:24px 12px;"">
|
||
<table role=""presentation"" width=""980"" cellpadding=""0"" cellspacing=""0"" border=""0"" align=""center"" bgcolor=""#ffffff"" style=""width:100%;max-width:980px;background-color:#ffffff;border:1px solid #e5e7eb;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<tr>
|
||
<td bgcolor=""#f9fafb"" style=""padding:24px;background-color:#f9fafb;border-bottom:1px solid #e5e7eb;"">
|
||
<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" style=""width:100%;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<tr>
|
||
<td style=""vertical-align:middle;"">
|
||
<table role=""presentation"" cellpadding=""0"" cellspacing=""0"" border=""0"" style=""border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<tr>
|
||
<td style=""width:46px;vertical-align:middle;"">
|
||
<table role=""presentation"" width=""40"" height=""40"" cellpadding=""0"" cellspacing=""0"" border=""0"" bgcolor=""#22c55e"" style=""width:40px;height:40px;background-color:#22c55e;border-collapse:collapse;"">
|
||
<tr>
|
||
<td align=""center"" valign=""middle"" style=""font-size:22px;line-height:40px;font-weight:800;color:#ffffff;"">✓</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
<td style=""vertical-align:middle;"">
|
||
<table role=""presentation"" cellpadding=""0"" cellspacing=""0"" border=""0"" style=""border-collapse:collapse;"">
|
||
<tr>
|
||
<td style=""font-size:22px;line-height:28px;font-weight:800;color:#111827;"">{Html(Translate("App.OrderSuccess.SuccessTitle"))}</td>
|
||
</tr>
|
||
<tr>
|
||
<td style=""padding-top:6px;color:#4b5563;font-size:14px;line-height:20px;"">{Html(Translate("App.OrderSuccess.Number"))} <strong style=""color:#2563eb;"">#{order.Id}</strong></td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
<td align=""right"" style=""text-align:right;vertical-align:middle;color:#4b5563;"">
|
||
<table role=""presentation"" cellpadding=""0"" cellspacing=""0"" border=""0"" align=""right"" style=""border-collapse:collapse;"">
|
||
<tr>
|
||
<td align=""right"" style=""font-size:12px;line-height:16px;text-transform:uppercase;letter-spacing:.08em;color:#6b7280;"">Invoice</td>
|
||
</tr>
|
||
<tr>
|
||
<td align=""right"" style=""padding-top:4px;font-size:14px;line-height:20px;font-weight:700;color:#111827;"">{FormatDate(DateTime.Now)}</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td style=""padding:24px;"">
|
||
<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" style=""width:100%;border-collapse:separate;border-spacing:0;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<tr>
|
||
<td width=""50%"" style=""vertical-align:top;padding:0 10px 20px 0;"">
|
||
<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" bgcolor=""#ffffff"" style=""width:100%;background-color:#ffffff;border:1px solid #e5e7eb;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<tr>
|
||
<td style=""padding:18px;"">
|
||
<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" style=""width:100%;border-collapse:collapse;"">
|
||
<tr>
|
||
<td style=""font-size:18px;line-height:24px;font-weight:800;color:#111827;padding-bottom:14px;"">Customer Information</td>
|
||
</tr>
|
||
<tr>
|
||
<td style=""line-height:24px;color:#374151;font-size:14px;"">
|
||
<strong style=""color:#111827;"">{Html(order.OrganizationName)}</strong><br />
|
||
<a href=""mailto:{Html(order.Email)}"" style=""color:#2563eb;text-decoration:none;"">{Html(order.Email)}</a><br />
|
||
{Html(FormatPhoneNumber(order.PhoneNumber ?? order.MobileNumber))}<br />
|
||
{Html(order.Address1)}<br />
|
||
{Html(order.Township)} / {Html(order.District)} / {Html(order.City)} / {Html(order.Country)}
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
<td width=""50%"" style=""vertical-align:top;padding:0 0 20px 10px;"">
|
||
<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" bgcolor=""#ffffff"" style=""width:100%;background-color:#ffffff;border:1px solid #e5e7eb;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<tr>
|
||
<td style=""padding:18px;"">
|
||
<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" style=""width:100%;border-collapse:collapse;"">
|
||
<tr>
|
||
<td style=""font-size:18px;line-height:24px;font-weight:800;color:#111827;padding-bottom:14px;"">License Information</td>
|
||
</tr>
|
||
</table>
|
||
<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" style=""width:100%;border-collapse:collapse;font-size:14px;color:#374151;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<tr>
|
||
<td bgcolor=""#f9fafb"" style=""padding:8px;background-color:#f9fafb;color:#6b7280;line-height:20px;"">License Start Time</td>
|
||
<td bgcolor=""#f9fafb"" align=""right"" style=""padding:8px;background-color:#f9fafb;text-align:right;font-weight:700;color:#111827;line-height:20px;"">{FormatDate(order.LicenseStartTime)}</td>
|
||
</tr>
|
||
<tr>
|
||
<td style=""padding:8px;color:#6b7280;line-height:20px;"">License End Time</td>
|
||
<td align=""right"" style=""padding:8px;text-align:right;font-weight:700;color:#111827;line-height:20px;"">{FormatDate(order.LicenseEndTime)}</td>
|
||
</tr>
|
||
<tr>
|
||
<td bgcolor=""#f9fafb"" style=""padding:8px;background-color:#f9fafb;color:#6b7280;line-height:20px;"">Billing Cycle</td>
|
||
<td bgcolor=""#f9fafb"" align=""right"" style=""padding:8px;background-color:#f9fafb;text-align:right;font-weight:700;color:#111827;line-height:20px;"">{BillingCycleLabel(order.BillingCycle)}</td>
|
||
</tr>
|
||
<tr>
|
||
<td style=""padding:8px;color:#6b7280;line-height:20px;"">Period</td>
|
||
<td align=""right"" style=""padding:8px;text-align:right;font-weight:700;color:#111827;line-height:20px;"">{order.Period}</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<table width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" style=""width:100%;border-collapse:collapse;border:1px solid #e5e7eb;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<thead>
|
||
<tr style=""background:#f9fafb;color:#6b7280;font-size:12px;text-transform:uppercase;letter-spacing:.06em;"">
|
||
<th bgcolor=""#f9fafb"" style=""padding:12px 14px;text-align:left;background-color:#f9fafb;line-height:16px;"">Product</th>
|
||
<th bgcolor=""#f9fafb"" align=""right"" style=""padding:12px 14px;text-align:right;background-color:#f9fafb;line-height:16px;"">Price</th>
|
||
<th bgcolor=""#f9fafb"" align=""right"" style=""padding:12px 14px;text-align:right;background-color:#f9fafb;line-height:16px;"">Period</th>
|
||
<th bgcolor=""#f9fafb"" align=""right"" style=""padding:12px 14px;text-align:right;background-color:#f9fafb;line-height:16px;"">Qty</th>
|
||
<th bgcolor=""#f9fafb"" align=""right"" style=""padding:12px 14px;text-align:right;background-color:#f9fafb;line-height:16px;"">Cycle</th>
|
||
<th bgcolor=""#f9fafb"" align=""right"" style=""padding:12px 14px;text-align:right;background-color:#f9fafb;line-height:16px;"">VAT</th>
|
||
<th bgcolor=""#f9fafb"" align=""right"" style=""padding:12px 14px;text-align:right;background-color:#f9fafb;line-height:16px;"">Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>{itemRows}</tbody>
|
||
</table>
|
||
|
||
<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" style=""width:100%;border-collapse:collapse;margin-top:20px;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<tr>
|
||
<td style=""width:58%;""> </td>
|
||
<td style=""width:42%;vertical-align:top;"">
|
||
<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" border=""0"" style=""width:100%;border-collapse:collapse;color:#374151;font-size:14px;mso-table-lspace:0pt;mso-table-rspace:0pt;"">
|
||
<tr>
|
||
<td style=""padding:8px 0;text-align:left;line-height:20px;"">{Html(Translate("App.Listform.ListformField.Subtotal"))}</td>
|
||
<td align=""right"" style=""padding:8px 0;text-align:right;font-weight:700;color:#111827;white-space:nowrap;line-height:20px;"">{FormatPrice(order.Subtotal)}</td>
|
||
</tr>
|
||
<tr>
|
||
<td style=""padding:8px 0;text-align:left;line-height:20px;"">{Html(Translate("App.PublicProducts.Kdv"))}</td>
|
||
<td align=""right"" style=""padding:8px 0;text-align:right;font-weight:700;color:#111827;white-space:nowrap;line-height:20px;"">{FormatPrice(order.VatTotal)}</td>
|
||
</tr>
|
||
<tr>
|
||
<td style=""padding:8px 0;text-align:left;line-height:20px;"">{Html(Translate("App.Listform.ListformField.Commission"))}</td>
|
||
<td align=""right"" style=""padding:8px 0;text-align:right;font-weight:700;color:#111827;white-space:nowrap;line-height:20px;"">{FormatPrice(order.Commission)}</td>
|
||
</tr>
|
||
<tr>
|
||
<td style=""padding:14px 0 0;border-top:1px solid #e5e7eb;text-align:left;font-size:18px;line-height:24px;font-weight:800;color:#111827;"">{Html(Translate("App.Listform.ListformField.Total"))}</td>
|
||
<td align=""right"" style=""padding:14px 0 0;border-top:1px solid #e5e7eb;text-align:right;font-size:18px;line-height:24px;font-weight:800;color:#2563eb;white-space:nowrap;"">{FormatPrice(order.Total)}</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</body>
|
||
</html>";
|
||
}
|
||
|
||
private string Translate(string key)
|
||
{
|
||
if (key.IsNullOrWhiteSpace())
|
||
{
|
||
return string.Empty;
|
||
}
|
||
|
||
var normalizedKey = key.StartsWith("::", StringComparison.Ordinal) ? key[2..] : key;
|
||
var text = L[normalizedKey];
|
||
return text.ResourceNotFound ? key : text.Value;
|
||
}
|
||
|
||
private static string BillingCycleLabel(string billingCycle)
|
||
{
|
||
return billingCycle == "monthly" ? "Aylık" : "Yıllık";
|
||
}
|
||
|
||
private static string NormalizePhoneNumber(string value, string country)
|
||
{
|
||
if (value.IsNullOrWhiteSpace())
|
||
{
|
||
return value;
|
||
}
|
||
|
||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||
if (country == "Türkiye" || digits.StartsWith("90"))
|
||
{
|
||
if (digits.StartsWith("90") && digits.Length > 10)
|
||
{
|
||
digits = digits[^10..];
|
||
}
|
||
|
||
if (digits.StartsWith("0") && digits.Length > 10)
|
||
{
|
||
digits = digits[^10..];
|
||
}
|
||
}
|
||
|
||
return digits;
|
||
}
|
||
|
||
private static string FormatPhoneNumber(string value)
|
||
{
|
||
var digits = NormalizePhoneNumber(value, string.Empty);
|
||
if (digits?.Length != 10)
|
||
{
|
||
return value;
|
||
}
|
||
|
||
return $"({digits[..3]}) {digits[3..6]}-{digits[6..]}";
|
||
}
|
||
|
||
private static string FormatDate(DateTime? value)
|
||
{
|
||
return value.HasValue ? value.Value.ToString("dd.MM.yyyy") : "-";
|
||
}
|
||
|
||
private static string FormatPrice(decimal value)
|
||
{
|
||
return string.Format(new System.Globalization.CultureInfo("tr-TR"), "{0:C2}", value);
|
||
}
|
||
|
||
private static string Html(string value)
|
||
{
|
||
return WebUtility.HtmlEncode(value ?? string.Empty);
|
||
}
|
||
|
||
public async Task<AboutDto> GetAboutAsync()
|
||
{
|
||
var entity = await _aboutRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(About));
|
||
return ObjectMapper.Map<About, AboutDto>(entity);
|
||
}
|
||
|
||
public async Task<ContactDto> GetContactAsync()
|
||
{
|
||
var entity = await _contactRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Contact));
|
||
|
||
return ObjectMapper.Map<Contact, ContactDto>(entity);
|
||
}
|
||
|
||
public async Task<List<CountryDto>> GetCountryAsync()
|
||
{
|
||
var queryable = await _countryRepository.GetQueryableAsync();
|
||
|
||
return await AsyncExecuter.ToListAsync(
|
||
queryable
|
||
.OrderBy(country => country.Name)
|
||
.Select(country => new CountryDto
|
||
{
|
||
Id = country.Id,
|
||
Name = country.Name,
|
||
GroupName = country.GroupName,
|
||
Currency = country.Currency,
|
||
PhoneCode = country.PhoneCode,
|
||
PhoneNumberMinLength = country.PhoneNumberMinLength,
|
||
PhoneNumberMaxLength = country.PhoneNumberMaxLength,
|
||
PhoneNumberFormat = country.PhoneNumberFormat,
|
||
TaxLabel = country.TaxLabel,
|
||
ZipRequired = country.ZipRequired,
|
||
StateRequired = country.StateRequired,
|
||
Cities = new List<CityDto>()
|
||
}));
|
||
}
|
||
|
||
public async Task<List<CityDto>> GetCityAsync(string country)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(country))
|
||
{
|
||
return [];
|
||
}
|
||
|
||
var queryable = await _cityRepository.GetQueryableAsync();
|
||
|
||
return await AsyncExecuter.ToListAsync(
|
||
queryable
|
||
.Where(city => city.Country == country)
|
||
.OrderBy(city => city.Name)
|
||
.Select(city => new CityDto
|
||
{
|
||
Id = city.Id,
|
||
Country = city.Country,
|
||
Name = city.Name,
|
||
PlateCode = city.PlateCode
|
||
}));
|
||
}
|
||
|
||
public async Task<List<DistrictDto>> GetDistrictAsync(string country, string city)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(country) || string.IsNullOrWhiteSpace(city))
|
||
{
|
||
return [];
|
||
}
|
||
|
||
var queryable = await _districtRepository.GetQueryableAsync();
|
||
var entities = await AsyncExecuter.ToListAsync(
|
||
queryable
|
||
.Where(district => district.Country == country && district.City == city)
|
||
.OrderBy(district => district.Name));
|
||
|
||
return entities
|
||
.Select(district => new DistrictDto
|
||
{
|
||
Id = district.Id,
|
||
Country = district.Country,
|
||
City = district.City,
|
||
Name = district.Name,
|
||
Township = district.Township,
|
||
PostalCode = district.PostalCode
|
||
})
|
||
.ToList();
|
||
}
|
||
|
||
[Authorize(AppCodes.WebSiteDesign.Contact)]
|
||
public async Task SaveContactPageAsync(SaveContactPageInput input)
|
||
{
|
||
var entity = await _contactRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Contact));
|
||
|
||
entity.Address = input.AddressKey;
|
||
entity.PhoneNumber = input.PhoneNumber;
|
||
entity.Email = input.Email;
|
||
entity.Location = input.Location;
|
||
entity.TaxNumber = long.TryParse(input.TaxNumber, out var taxNumber) ? taxNumber : null;
|
||
|
||
entity.BankJson = JsonSerializer.Serialize(new BankDto
|
||
{
|
||
AccountHolder = input.BankAccountHolder,
|
||
Branch = input.BankBranch,
|
||
AccountNumber = input.BankAccountNumber,
|
||
Iban = input.BankIban,
|
||
StyleClass = input.BankStyleClass,
|
||
});
|
||
|
||
entity.WorkHoursJson = JsonSerializer.Serialize(new WorkHoursDto
|
||
{
|
||
Weekday = input.WorkWeekdayKey,
|
||
Weekend = input.WorkWeekendKey,
|
||
Whatsapp = input.WorkWhatsappKey,
|
||
StyleClass = input.WorkHoursStyleClass,
|
||
});
|
||
|
||
entity.MapJson = JsonSerializer.Serialize(new MapDto
|
||
{
|
||
Title = input.MapTitleKey,
|
||
Src = input.MapSrc,
|
||
Width = input.MapWidth,
|
||
Height = input.MapHeight,
|
||
AllowFullScreen = input.MapAllowFullScreen,
|
||
Loading = input.MapLoading,
|
||
ReferrerPolicy = input.MapReferrerPolicy,
|
||
ContainerStyleClass = input.MapContainerStyleClass,
|
||
FrameStyleClass = input.MapFrameStyleClass,
|
||
});
|
||
|
||
await _contactRepository.UpdateAsync(entity, autoSave: false);
|
||
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroTitleKey, input.HeroTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroSubtitleKey, input.HeroSubtitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.HeroImageKey, input.HeroImageValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.ContactInfoTitleKey, input.ContactInfoTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.AddressKey, input.AddressValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.BankTitleKey, input.BankTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.WorkHoursTitleKey, input.WorkHoursTitleValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.WorkWeekdayKey, input.WorkWeekdayValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.WorkWeekendKey, input.WorkWeekendValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.WorkWhatsappKey, input.WorkWhatsappValue);
|
||
await UpsertLanguageTextAsync(input.CultureName, input.MapTitleKey, input.MapTitleValue);
|
||
|
||
foreach (var styleText in input.StyleTexts)
|
||
{
|
||
await UpsertLanguageTextAsync(input.CultureName, styleText.Key, styleText.Value);
|
||
}
|
||
|
||
await CurrentUnitOfWork!.SaveChangesAsync();
|
||
await _languageTextAppService.ClearRedisCacheAsync();
|
||
}
|
||
|
||
private async Task UpsertLanguageTextAsync(string cultureName, string key, string value)
|
||
{
|
||
if (key.IsNullOrWhiteSpace())
|
||
{
|
||
return;
|
||
}
|
||
|
||
var normalizedCultureName = NormalizeCultureName(cultureName);
|
||
var resourceName = PlatformConsts.AppName;
|
||
|
||
var languageKey = await _languageKeyRepository.FirstOrDefaultAsync(a => a.ResourceName == resourceName && a.Key == key);
|
||
if (languageKey == null)
|
||
{
|
||
languageKey = await _languageKeyRepository.InsertAsync(new LanguageKey
|
||
{
|
||
ResourceName = resourceName,
|
||
Key = key,
|
||
}, autoSave: false);
|
||
}
|
||
|
||
var languageText = await _languageTextRepository.FirstOrDefaultAsync(a =>
|
||
a.ResourceName == resourceName &&
|
||
a.Key == languageKey.Key &&
|
||
a.CultureName == normalizedCultureName);
|
||
|
||
if (languageText == null)
|
||
{
|
||
await _languageTextRepository.InsertAsync(new LanguageText
|
||
{
|
||
ResourceName = resourceName,
|
||
Key = languageKey.Key,
|
||
CultureName = normalizedCultureName,
|
||
Value = value ?? string.Empty,
|
||
}, autoSave: false);
|
||
|
||
return;
|
||
}
|
||
|
||
languageText.Value = value ?? string.Empty;
|
||
await _languageTextRepository.UpdateAsync(languageText, autoSave: false);
|
||
}
|
||
|
||
private static string NormalizeCultureName(string cultureName)
|
||
{
|
||
if (cultureName.IsNullOrWhiteSpace())
|
||
{
|
||
return PlatformConsts.DefaultLanguage;
|
||
}
|
||
|
||
return cultureName.Split('-')[0].ToLowerInvariant();
|
||
}
|
||
}
|
||
|
||
|