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;
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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;
|
2026-03-10 21:18:35 +00:00
|
|
|
|
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;
|
2026-03-17 11:27:30 +00:00
|
|
|
|
private readonly IRepository<Home, Guid> _homeRepository;
|
2026-02-24 20:44:16 +00:00
|
|
|
|
private readonly IRepository<Contact, Guid> _contactRepository;
|
2026-05-05 07:23:02 +00:00
|
|
|
|
private readonly IRepository<Country, string> _countryRepository;
|
2026-07-06 21:53:45 +00:00
|
|
|
|
private readonly IRepository<City, Guid> _cityRepository;
|
|
|
|
|
|
private readonly IRepository<District, Guid> _districtRepository;
|
2026-02-24 20:44:16 +00:00
|
|
|
|
private readonly IIdentityUserRepository _identityUserRepository;
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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,
|
2026-03-10 21:18:35 +00:00
|
|
|
|
IRepository<PaymentMethod, Guid> paymentMethodRepository,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
IRepository<InstallmentOption> installmentOptionRepository,
|
|
|
|
|
|
IRepository<Order, Guid> orderRepository,
|
|
|
|
|
|
IRepository<About, Guid> aboutRepository,
|
2026-03-17 11:27:30 +00:00
|
|
|
|
IRepository<Home, Guid> homeRepository,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
IRepository<Contact, Guid> contactRepository,
|
2026-05-05 07:23:02 +00:00
|
|
|
|
IRepository<Country, string> countryRepository,
|
2026-07-06 21:53:45 +00:00
|
|
|
|
IRepository<City, Guid> cityRepository,
|
|
|
|
|
|
IRepository<District, Guid> districtRepository,
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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;
|
2026-03-17 11:27:30 +00:00
|
|
|
|
_homeRepository = homeRepository;
|
2026-02-24 20:44:16 +00:00
|
|
|
|
_contactRepository = contactRepository;
|
|
|
|
|
|
_identityUserRepository = identityUserRepository;
|
2026-03-17 09:54:25 +00:00
|
|
|
|
_languageKeyRepository = languageKeyRepository;
|
|
|
|
|
|
_languageTextRepository = languageTextRepository;
|
|
|
|
|
|
_languageTextAppService = languageTextAppService;
|
2026-05-05 07:23:02 +00:00
|
|
|
|
_countryRepository = countryRepository;
|
2026-07-06 21:53:45 +00:00
|
|
|
|
_cityRepository = cityRepository;
|
|
|
|
|
|
_districtRepository = districtRepository;
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<ServiceDto>> GetServicesListAsync()
|
|
|
|
|
|
{
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 09:54:25 +00:00
|
|
|
|
await CurrentUnitOfWork!.SaveChangesAsync();
|
|
|
|
|
|
await _languageTextAppService.ClearRedisCacheAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 11:27:30 +00:00
|
|
|
|
public async Task<HomeDto> GetHomeAsync()
|
|
|
|
|
|
{
|
2026-06-01 10:26:04 +00:00
|
|
|
|
var entity = await _homeRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Home));
|
2026-03-17 11:27:30 +00:00
|
|
|
|
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));
|
2026-03-17 11:27:30 +00:00
|
|
|
|
|
|
|
|
|
|
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,
|
2026-03-17 11:27:30 +00:00
|
|
|
|
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,
|
2026-03-17 11:27:30 +00:00
|
|
|
|
}).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,
|
2026-03-17 11:27:30 +00:00
|
|
|
|
}).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,
|
2026-03-17 11:27:30 +00:00
|
|
|
|
}).ToList());
|
|
|
|
|
|
|
2026-06-01 10:26:04 +00:00
|
|
|
|
await _homeRepository.UpdateAsync(entity, autoSave: false);
|
2026-03-17 11:27:30 +00:00
|
|
|
|
|
|
|
|
|
|
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);
|
2026-03-17 11:27:30 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaKey, input.HeroSecondaryCtaValue);
|
2026-03-17 13:35:58 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaStyleKey, input.HeroSecondaryCtaStyleValue);
|
2026-03-17 11:27:30 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleKey, input.FeaturesTitleValue);
|
2026-03-17 13:35:58 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleStyleKey, input.FeaturesTitleStyleValue);
|
2026-03-17 11:27:30 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleKey, input.FeaturesSubtitleValue);
|
2026-03-17 13:35:58 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleStyleKey, input.FeaturesSubtitleStyleValue);
|
2026-03-17 11:27:30 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleKey, input.SolutionsTitleValue);
|
2026-03-17 13:35:58 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleStyleKey, input.SolutionsTitleStyleValue);
|
2026-03-17 11:27:30 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleKey, input.SolutionsSubtitleValue);
|
2026-03-17 13:35:58 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleStyleKey, input.SolutionsSubtitleStyleValue);
|
2026-03-17 11:27:30 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleKey, input.CtaTitleValue);
|
2026-03-17 13:35:58 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleStyleKey, input.CtaTitleStyleValue);
|
2026-03-17 11:27:30 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleKey, input.CtaSubtitleValue);
|
2026-03-17 13:35:58 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleStyleKey, input.CtaSubtitleStyleValue);
|
2026-03-17 11:27:30 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue);
|
2026-03-17 13:35:58 +00:00
|
|
|
|
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonStyleKey, input.CtaButtonStyleValue);
|
2026-03-17 11:27:30 +00:00
|
|
|
|
|
|
|
|
|
|
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,
|
2026-07-07 21:00:35 +00:00
|
|
|
|
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,
|
2026-07-06 21:53:45 +00:00
|
|
|
|
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;"">✓</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%;""> </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);
|
|
|
|
|
|
}
|
2026-03-17 09:54:25 +00:00
|
|
|
|
|
2026-05-05 07:23:02 +00:00
|
|
|
|
public async Task<List<CountryDto>> GetCountryAsync()
|
|
|
|
|
|
{
|
2026-07-06 21:53:45 +00:00
|
|
|
|
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));
|
2026-05-05 07:23:02 +00:00
|
|
|
|
|
2026-07-06 21:53:45 +00:00
|
|
|
|
return entities
|
|
|
|
|
|
.Select(district => new DistrictDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = district.Id,
|
|
|
|
|
|
Country = district.Country,
|
|
|
|
|
|
City = district.City,
|
|
|
|
|
|
Name = district.Name,
|
2026-07-07 21:00:35 +00:00
|
|
|
|
Township = district.Township,
|
|
|
|
|
|
PostalCode = district.PostalCode
|
2026-07-06 21:53:45 +00:00
|
|
|
|
})
|
|
|
|
|
|
.ToList();
|
2026-05-05 07:23:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 11:27:30 +00:00
|
|
|
|
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,
|
2026-03-17 11:27:30 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
2026-03-17 11:27:30 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
2026-03-17 11:27:30 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 11:27:30 +00:00
|
|
|
|
await CurrentUnitOfWork!.SaveChangesAsync();
|
|
|
|
|
|
await _languageTextAppService.ClearRedisCacheAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|