sozsoft-platform/api/src/Sozsoft.Platform.Application/Public/PublicAppService.cs

1076 lines
50 KiB
C#
Raw Normal View History

2026-02-24 20:44:16 +00:00
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;
2026-07-07 13:41:07 +00:00
using Microsoft.Extensions.Logging;
2026-02-24 20:44:16 +00:00
using Volo.Abp.Domain.Entities;
using System.Linq;
2026-07-07 13:41:07 +00:00
using System.Net;
2026-02-24 20:44:16 +00:00
using Volo.Abp.Application.Dtos;
using System.Text.Json;
using Volo.Abp.Identity;
using Sozsoft.Languages;
using Sozsoft.Languages.Entities;
2026-02-24 20:44:16 +00:00
namespace Sozsoft.Platform.Public;
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;
2026-02-24 20:44:16 +00:00
private readonly IRepository<InstallmentOption> _installmentOptionRepository;
private readonly IRepository<Order, Guid> _orderRepository;
private readonly IRepository<About, Guid> _aboutRepository;
private readonly IRepository<Home, Guid> _homeRepository;
2026-02-24 20:44:16 +00:00
private readonly IRepository<Contact, Guid> _contactRepository;
private readonly IRepository<Country, string> _countryRepository;
private readonly IRepository<City, Guid> _cityRepository;
private readonly IRepository<District, Guid> _districtRepository;
2026-02-24 20:44:16 +00:00
private readonly IIdentityUserRepository _identityUserRepository;
private readonly IRepository<LanguageKey, Guid> _languageKeyRepository;
private readonly IRepository<LanguageText, Guid> _languageTextRepository;
private readonly LanguageTextAppService _languageTextAppService;
2026-02-24 20:44:16 +00:00
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,
2026-02-24 20:44:16 +00:00
IRepository<InstallmentOption> installmentOptionRepository,
IRepository<Order, Guid> orderRepository,
IRepository<About, Guid> aboutRepository,
IRepository<Home, Guid> homeRepository,
2026-02-24 20:44:16 +00:00
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
2026-02-24 20:44:16 +00:00
)
{
_serviceRepository = serviceRepository;
_settingProvider = settingProvider;
_emailSender = emailSender;
_demoRepository = demoRepository;
_postRepository = postRepository;
_categoryRepository = categoryRepository;
_productRepository = productRepository;
_paymentMethodRepository = paymentMethodRepository;
_installmentOptionRepository = installmentOptionRepository;
_orderRepository = orderRepository;
_aboutRepository = aboutRepository;
_homeRepository = homeRepository;
2026-02-24 20:44:16 +00:00
_contactRepository = contactRepository;
_identityUserRepository = identityUserRepository;
_languageKeyRepository = languageKeyRepository;
_languageTextRepository = languageTextRepository;
_languageTextAppService = languageTextAppService;
_countryRepository = countryRepository;
_cityRepository = cityRepository;
_districtRepository = districtRepository;
2026-02-24 20:44:16 +00:00
}
public async Task<List<ServiceDto>> GetServicesListAsync()
{
var queryable = await _serviceRepository.GetQueryableAsync();
var entity = await AsyncExecuter.ToListAsync(queryable.OrderBy(a => a.CreationTime));
2026-02-24 20:44:16 +00:00
return ObjectMapper.Map<List<Service>, List<ServiceDto>>(entity);
}
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);
}
2026-03-17 13:35:58 +00:00
foreach (var styleText in input.StyleTexts)
{
await UpsertLanguageTextAsync(input.CultureName, styleText.Key, styleText.Value);
}
await _aboutRepository.UpdateAsync(entity, autoSave: true);
await _languageTextAppService.ClearRedisCacheAsync();
}
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);
2026-03-17 13:35:58 +00:00
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()
{
2026-06-01 10:26:04 +00:00
var entity = await _homeRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Home));
return ObjectMapper.Map<Home, HomeDto>(entity);
}
public async Task SaveHomePageAsync(SaveHomePageInput input)
{
2026-06-01 10:26:04 +00:00
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,
2026-03-17 13:35:58 +00:00
StyleClass = slide.StyleClass,
Services = slide.Services.Select(service => new HomeSlideServiceDto
{
Icon = service.Icon,
TitleKey = service.TitleKey,
DescriptionKey = service.DescriptionKey,
2026-03-17 13:35:58 +00:00
StyleClass = service.StyleClass,
}).ToList(),
}).ToList());
entity.FeaturesJson = JsonSerializer.Serialize(input.Features.Select(feature => new HomeFeatureDto
{
Icon = feature.Icon,
TitleKey = feature.TitleKey,
DescriptionKey = feature.DescriptionKey,
2026-03-17 13:35:58 +00:00
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,
2026-03-17 13:35:58 +00:00
StyleClass = solution.StyleClass,
}).ToList());
2026-06-01 10:26:04 +00:00
await _homeRepository.UpdateAsync(entity, autoSave: false);
await UpsertLanguageTextAsync(input.CultureName, input.HeroBackgroundImageKey, input.HeroBackgroundImageValue);
await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaKey, input.HeroPrimaryCtaValue);
2026-03-17 13:35:58 +00:00
await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaStyleKey, input.HeroPrimaryCtaStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaKey, input.HeroSecondaryCtaValue);
2026-03-17 13:35:58 +00:00
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaStyleKey, input.HeroSecondaryCtaStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleKey, input.FeaturesTitleValue);
2026-03-17 13:35:58 +00:00
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleStyleKey, input.FeaturesTitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleKey, input.FeaturesSubtitleValue);
2026-03-17 13:35:58 +00:00
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleStyleKey, input.FeaturesSubtitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleKey, input.SolutionsTitleValue);
2026-03-17 13:35:58 +00:00
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleStyleKey, input.SolutionsTitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleKey, input.SolutionsSubtitleValue);
2026-03-17 13:35:58 +00:00
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleStyleKey, input.SolutionsSubtitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleKey, input.CtaTitleValue);
2026-03-17 13:35:58 +00:00
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleStyleKey, input.CtaTitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleKey, input.CtaSubtitleValue);
2026-03-17 13:35:58 +00:00
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleStyleKey, input.CtaSubtitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue);
2026-03-17 13:35:58 +00:00
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();
}
2026-02-24 20:44:16 +00:00
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)
{
2026-07-07 13:41:07 +00:00
var entity = new Order(GuidGenerator.Create())
2026-02-24 20:44:16 +00:00
{
2026-07-07 13:41:07 +00:00
TenantId = input.Tenant.Id,
2026-02-24 20:44:16 +00:00
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,
2026-02-24 20:44:16 +00:00
PostalCode = input.Tenant.PostalCode,
2026-07-07 13:41:07 +00:00
MobileNumber = NormalizePhoneNumber(input.Tenant.MobileNumber, input.Tenant.Country),
PhoneNumber = NormalizePhoneNumber(input.Tenant.PhoneNumber, input.Tenant.Country),
FaxNumber = NormalizePhoneNumber(input.Tenant.FaxNumber, input.Tenant.Country),
2026-02-24 20:44:16 +00:00
Email = input.Tenant.Email,
Website = input.Tenant.Website,
MenuGroup = input.Tenant.MenuGroup,
2026-07-07 13:41:07 +00:00
BillingCycle = input.BillingCycle,
Period = input.Period,
LicenseStartTime = input.LicenseStartTime,
2026-07-07 13:41:07 +00:00
LicenseEndTime = input.LicenseEndTime,
2026-02-24 20:44:16 +00:00
Subtotal = input.Subtotal,
2026-07-07 13:41:07 +00:00
VatTotal = input.VatTotal,
2026-02-24 20:44:16 +00:00
Commission = input.Commission,
Total = input.Total,
PaymentMethodId = input.PaymentMethodId,
Installment = input.Installment,
PaymentDataJson = JsonSerializer.Serialize(input.PaymentData),
2026-07-07 13:41:07 +00:00
IsExisting = input.IsExisting,
2026-02-24 20:44:16 +00:00
};
2026-07-07 13:41:07 +00:00
decimal subtotal = 0;
decimal vatTotal = 0;
2026-02-24 20:44:16 +00:00
foreach (var item in input.Items)
{
2026-07-07 13:41:07 +00:00
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;
2026-02-24 20:44:16 +00:00
entity.Items.Add(new OrderItem
{
OrderId = entity.Id,
Order = entity,
2026-07-07 13:41:07 +00:00
ProductId = product.Id,
ProductName = product.Name,
2026-02-24 20:44:16 +00:00
BillingCycle = item.BillingCycle,
2026-07-07 13:41:07 +00:00
Period = input.Period,
2026-02-24 20:44:16 +00:00
Quantity = item.Quantity,
2026-07-07 13:41:07 +00:00
Price = price,
VatRate = vatRate,
VatAmount = vatAmount,
TotalPrice = grossTotal
2026-02-24 20:44:16 +00:00
});
}
2026-07-07 13:41:07 +00:00
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);
2026-02-24 20:44:16 +00:00
await _orderRepository.InsertAsync(entity, autoSave: true);
2026-07-07 13:41:07 +00:00
await SendOrderCreatedEmailsAsync(entity);
2026-02-24 20:44:16 +00:00
return new OrderDto
{
Id = entity.Id,
Total = entity.Total,
PaymentMethodId = entity.PaymentMethodId
};
}
2026-07-07 13:41:07 +00:00
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);
}
}
}
2026-07-07 21:13:31 +00:00
private string BuildOrderEmailBody(Order order)
2026-07-07 13:41:07 +00:00
{
var itemRows = string.Join(string.Empty, order.Items.Select(item => $@"
<tr>
2026-07-07 21:13:31 +00:00
<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>
2026-07-07 13:41:07 +00:00
</tr>"));
return $@"
<!doctype html>
<html>
2026-07-07 21:13:31 +00:00
<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("Public.order.success.title"))} #{order.Id}
2026-07-07 13:41:07 +00:00
</div>
2026-07-07 21:13:31 +00:00
<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;"">&#10003;</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("Public.order.success.title"))}</td>
</tr>
<tr>
<td style=""padding-top:6px;color:#4b5563;font-size:14px;line-height:20px;"">{Html(Translate("Public.order.success.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%;"">&nbsp;</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("Public.payment.summary.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("Public.products.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("Public.payment.installments.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>
2026-07-07 13:41:07 +00:00
</body>
</html>";
}
2026-07-07 21:13:31 +00:00
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;
}
2026-07-07 13:41:07 +00:00
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);
}
2026-02-24 20:44:16 +00:00
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();
}
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,
2026-03-17 13:35:58 +00:00
StyleClass = input.BankStyleClass,
});
entity.WorkHoursJson = JsonSerializer.Serialize(new WorkHoursDto
{
Weekday = input.WorkWeekdayKey,
Weekend = input.WorkWeekendKey,
Whatsapp = input.WorkWhatsappKey,
2026-03-17 13:35:58 +00:00
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,
2026-03-17 13:35:58 +00:00
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);
2026-03-17 13:35:58 +00:00
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();
}
2026-02-24 20:44:16 +00:00
}