diff --git a/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs b/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs index df0e509..7ced6f9 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs @@ -57,8 +57,8 @@ public static class LookupQueryValues else { return $"SELECT * FROM (" + - $"SELECT NULL AS \"Key\", 'Host' AS \"Name\" " + - $"UNION ALL " + + // $"SELECT NULL AS \"Key\", 'Host' AS \"Name\" " + + // $"UNION ALL " + $"SELECT " + $"\"AbpTenants\".\"Id\" AS \"Key\", " + $"\"AbpTenants\".\"Name\" AS \"Name\" " + @@ -178,11 +178,13 @@ public static class LookupQueryValues public static string ProductValues = $"SELECT " + - $"\"Id\" AS \"Key\", " + - $"\"Name\" AS \"Name\" " + - $"FROM \"{TableNameResolver.GetFullTableName(nameof(TableNameEnum.Product))}\" " + - $"WHERE \"IsDeleted\" = 'false' " + - $"ORDER BY \"Name\";"; + $"\"{FullNameTable(TableNameEnum.Product)}\".\"Id\" AS \"Key\", " + + $"\"{FullNameTable(TableNameEnum.LanguageText)}\".\"Value\" AS \"Name\" " + + $"FROM \"{FullNameTable(TableNameEnum.Product)}\" " + + $"LEFT OUTER JOIN \"{FullNameTable(TableNameEnum.LanguageText)}\" " + + $"ON \"{FullNameTable(TableNameEnum.Product)}\".\"Name\" = \"{FullNameTable(TableNameEnum.LanguageText)}\".\"Key\" " + + $"WHERE \"{FullNameTable(TableNameEnum.Product)}\".\"IsDeleted\" = 'false' " + + $"ORDER BY \"{FullNameTable(TableNameEnum.LanguageText)}\".\"Value\";"; public static string MenuCodeValues = $"SELECT " + diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Public/OrderDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Public/OrderDto.cs index 6c7b3ce..fdc619e 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Public/OrderDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Public/OrderDto.cs @@ -8,15 +8,17 @@ namespace Sozsoft.Platform.Public; public class OrderDto : EntityDto { public CustomTenantDto Tenant { get; set; } - public List Items { get; set; } + public bool IsExisting { get; set; } + public string BillingCycle { get; set; } public int Period { get; set; } public DateTime? LicenseStartTime { get; set; } - public DateTime? LicenseStopTime { get; set; } + public DateTime? LicenseEndTime { get; set; } public decimal Subtotal { get; set; } + public decimal VatTotal { get; set; } public decimal Commission { get; set; } public decimal Total { get; set; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Public/OrderItemDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Public/OrderItemDto.cs index 55686b9..9636745 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Public/OrderItemDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Public/OrderItemDto.cs @@ -8,6 +8,8 @@ public class OrderItemDto public ProductDto Product { get; set; } public string BillingCycle { get; set; } // monthly | yearly public int Quantity { get; set; } + public decimal VatRate { get; set; } + public decimal VatAmount { get; set; } public decimal TotalPrice { get; set; } } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Public/ProductDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Public/ProductDto.cs index 061e957..facf2a9 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Public/ProductDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Public/ProductDto.cs @@ -13,6 +13,7 @@ public class ProductDto : EntityDto public int Order { get; set; } public decimal? MonthlyPrice { get; set; } public decimal? YearlyPrice { get; set; } + public decimal VatRate { get; set; } public bool IsQuantityBased { get; set; } public string ImageUrl { get; set; } public string Unit => diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Tenants/CustomTenantDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Tenants/CustomTenantDto.cs index 3b0d68c..587ca66 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Tenants/CustomTenantDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Tenants/CustomTenantDto.cs @@ -4,11 +4,8 @@ namespace Sozsoft.Platform.Tenants; public class CustomTenantDto { - public string Name { get; set; } - public bool IsActive { get; set; } - public bool IsExisting { get; set; } - public Guid? Id { get; set; } + public string Name { get; set; } public string OrganizationName { get; set; } public string Founder { get; set; } public long? VknTckn { get; set; } @@ -26,5 +23,6 @@ public class CustomTenantDto public string Email { get; set; } public string Website { get; set; } public string MenuGroup { get; set; } + public string Reference { get; set; } } diff --git a/api/src/Sozsoft.Platform.Application/Identity/IdentityMappers.cs b/api/src/Sozsoft.Platform.Application/Identity/IdentityMappers.cs index ebd417e..ba89a3e 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/IdentityMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/IdentityMappers.cs @@ -1,4 +1,5 @@ using Sozsoft.Platform.Identity.Dto; +using Sozsoft.Platform.Extensions; using Sozsoft.Platform.OrganizationUnits; using Sozsoft.Platform.Tenants; using Volo.Abp.Identity; @@ -76,10 +77,44 @@ public partial class CreateUpdateOrganizationUnitDtoToOrganizationUnitMapper : M [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] public partial class TenantToCustomTenantDtoMapper : MapperBase { - [MapperIgnoreTarget(nameof(CustomTenantDto.IsExisting))] + [MapperIgnoreTarget(nameof(CustomTenantDto.OrganizationName))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Founder))] + [MapperIgnoreTarget(nameof(CustomTenantDto.VknTckn))] + [MapperIgnoreTarget(nameof(CustomTenantDto.TaxOffice))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Country))] + [MapperIgnoreTarget(nameof(CustomTenantDto.City))] + [MapperIgnoreTarget(nameof(CustomTenantDto.District))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Township))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Address1))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Address2))] + [MapperIgnoreTarget(nameof(CustomTenantDto.PostalCode))] + [MapperIgnoreTarget(nameof(CustomTenantDto.MobileNumber))] + [MapperIgnoreTarget(nameof(CustomTenantDto.PhoneNumber))] + [MapperIgnoreTarget(nameof(CustomTenantDto.FaxNumber))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Email))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Website))] + [MapperIgnoreTarget(nameof(CustomTenantDto.MenuGroup))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Reference))] public override partial CustomTenantDto Map(Tenant source); - [MapperIgnoreTarget(nameof(CustomTenantDto.IsExisting))] + [MapperIgnoreTarget(nameof(CustomTenantDto.OrganizationName))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Founder))] + [MapperIgnoreTarget(nameof(CustomTenantDto.VknTckn))] + [MapperIgnoreTarget(nameof(CustomTenantDto.TaxOffice))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Country))] + [MapperIgnoreTarget(nameof(CustomTenantDto.City))] + [MapperIgnoreTarget(nameof(CustomTenantDto.District))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Township))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Address1))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Address2))] + [MapperIgnoreTarget(nameof(CustomTenantDto.PostalCode))] + [MapperIgnoreTarget(nameof(CustomTenantDto.MobileNumber))] + [MapperIgnoreTarget(nameof(CustomTenantDto.PhoneNumber))] + [MapperIgnoreTarget(nameof(CustomTenantDto.FaxNumber))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Email))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Website))] + [MapperIgnoreTarget(nameof(CustomTenantDto.MenuGroup))] + [MapperIgnoreTarget(nameof(CustomTenantDto.Reference))] public override partial void Map(Tenant source, CustomTenantDto destination); public override void BeforeMap(Tenant source) @@ -88,6 +123,23 @@ public partial class TenantToCustomTenantDtoMapper : MapperBase CreateOrderAsync(OrderDto input) { - var billingCycle = input.BillingCycle.IsNullOrWhiteSpace() ? "yearly" : input.BillingCycle; - var period = input.Period > 0 ? input.Period : 1; - - var entity = new Order() + var entity = new Order(GuidGenerator.Create()) { - Name = input.Tenant.Name, - IsActive = input.Tenant.IsActive, - IsExisting = input.Tenant.IsExisting, - + TenantId = input.Tenant.Id, OrganizationName = input.Tenant.OrganizationName, Founder = input.Tenant.Founder, VknTckn = input.Tenant.VknTckn, @@ -482,40 +478,77 @@ public class PublicAppService : PlatformAppService Country = input.Tenant.Country, City = input.Tenant.City, PostalCode = input.Tenant.PostalCode, - MobileNumber = input.Tenant.MobileNumber, - PhoneNumber = input.Tenant.PhoneNumber, - FaxNumber = input.Tenant.FaxNumber, + MobileNumber = NormalizePhoneNumber(input.Tenant.MobileNumber, input.Tenant.Country), + PhoneNumber = NormalizePhoneNumber(input.Tenant.PhoneNumber, input.Tenant.Country), + FaxNumber = NormalizePhoneNumber(input.Tenant.FaxNumber, input.Tenant.Country), Email = input.Tenant.Email, Website = input.Tenant.Website, MenuGroup = input.Tenant.MenuGroup, - BillingCycle = billingCycle, - Period = period, + BillingCycle = input.BillingCycle, + Period = input.Period, LicenseStartTime = input.LicenseStartTime, - LicenseStopTime = input.LicenseStopTime, + LicenseEndTime = input.LicenseEndTime, Subtotal = input.Subtotal, + VatTotal = input.VatTotal, Commission = input.Commission, Total = input.Total, PaymentMethodId = input.PaymentMethodId, Installment = input.Installment, PaymentDataJson = JsonSerializer.Serialize(input.PaymentData), + + IsExisting = input.IsExisting, }; + decimal subtotal = 0; + decimal vatTotal = 0; foreach (var item in input.Items) { + var productId = item.ProductId != Guid.Empty ? item.ProductId : item.Product.Id; + var product = await _productRepository.FindAsync(productId); + if (product == null && !item.Product.Name.IsNullOrWhiteSpace()) + { + product = await _productRepository.FirstOrDefaultAsync(x => x.Name == item.Product.Name); + } + + if (product == null) + { + throw new EntityNotFoundException(typeof(Product), productId); + } + + var vatRate = product.VatRate; + var price = GetOrderItemPrice(product, item.BillingCycle); + var grossTotal = Math.Round(item.TotalPrice, 2, MidpointRounding.AwayFromZero); + var netTotal = vatRate > 0 + ? Math.Round(grossTotal / (1 + vatRate), 2, MidpointRounding.AwayFromZero) + : grossTotal; + var vatAmount = Math.Round(grossTotal - netTotal, 2, MidpointRounding.AwayFromZero); + + subtotal += netTotal; + vatTotal += vatAmount; + entity.Items.Add(new OrderItem { OrderId = entity.Id, Order = entity, - ProductId = item.Product.Id, - ProductName = item.Product.Name, + ProductId = product.Id, + ProductName = product.Name, BillingCycle = item.BillingCycle, + Period = input.Period, Quantity = item.Quantity, - TotalPrice = item.TotalPrice + Price = price, + VatRate = vatRate, + VatAmount = vatAmount, + TotalPrice = grossTotal }); } + entity.Subtotal = Math.Round(subtotal, 2, MidpointRounding.AwayFromZero); + entity.VatTotal = Math.Round(vatTotal, 2, MidpointRounding.AwayFromZero); + entity.Total = Math.Round(entity.Subtotal + entity.VatTotal + entity.Commission, 2, MidpointRounding.AwayFromZero); + await _orderRepository.InsertAsync(entity, autoSave: true); + await SendOrderCreatedEmailsAsync(entity); return new OrderDto { @@ -525,6 +558,202 @@ public class PublicAppService : PlatformAppService }; } + 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(); + 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? sender = null; + if (!senderEmail.IsNullOrWhiteSpace()) + { + sender = new KeyValuePair(senderName ?? string.Empty, senderEmail); + } + + var body = BuildOrderEmailBody(order); + var subject = $"{PlatformConsts.AppName} : Yeni Sipariş #{order.Id}"; + + foreach (var recipient in recipients) + { + try + { + await _emailSender.SendEmailAsync( + recipient, + sender, + new { }, + body, + subject, + null, + true); + } + catch (Exception ex) + { + Logger.LogError(ex, "Order email could not be sent to {Recipient}", recipient); + } + } + } + + private static string BuildOrderEmailBody(Order order) + { + var itemRows = string.Join(string.Empty, order.Items.Select(item => $@" + + {Html(item.ProductName)} + {item.Quantity} + {BillingCycleLabel(item.BillingCycle)} + %{item.VatRate * 100:0.##} / {FormatPrice(item.VatAmount)} + {FormatPrice(item.TotalPrice)} + ")); + + return $@" + + + +
+
+
+
+
Sipariş Oluşturuldu
+
Sipariş No: #{order.Id}
+
+
+
Invoice
+
{FormatDate(DateTime.Now)}
+
+
+ +
+
+
+
Customer Information
+
+ {Html(order.OrganizationName)}
+ {Html(order.Email)}
+ {Html(FormatPhoneNumber(order.PhoneNumber ?? order.MobileNumber))}
+ {Html(order.Address1)}
+ {Html(order.District)} / {Html(order.City)} / {Html(order.Country)} +
+
+
+
License Information
+
+
License Start Time: {FormatDate(order.LicenseStartTime)}
+
License End Time: {FormatDate(order.LicenseEndTime)}
+
Billing Cycle: {BillingCycleLabel(order.BillingCycle)}
+
Period: {order.Period}
+
+
+
+ + + + + + + + + + + + {itemRows} +
ProductQtyCycleVATAmount
+ +
+
Subtotal{FormatPrice(order.Subtotal)}
+
VAT{FormatPrice(order.VatTotal)}
+
Commission{FormatPrice(order.Commission)}
+
Total{FormatPrice(order.Total)}
+
+
+
+
+ +"; + } + + private static string BillingCycleLabel(string billingCycle) + { + return billingCycle == "monthly" ? "Aylık" : "Yıllık"; + } + + private static string NormalizePhoneNumber(string value, string country) + { + if (value.IsNullOrWhiteSpace()) + { + return value; + } + + var digits = new string(value.Where(char.IsDigit).ToArray()); + if (country == "Türkiye" || digits.StartsWith("90")) + { + if (digits.StartsWith("90") && digits.Length > 10) + { + digits = digits[^10..]; + } + + if (digits.StartsWith("0") && digits.Length > 10) + { + digits = digits[^10..]; + } + } + + return digits; + } + + private static string FormatPhoneNumber(string value) + { + var digits = NormalizePhoneNumber(value, string.Empty); + if (digits?.Length != 10) + { + return value; + } + + return $"({digits[..3]}) {digits[3..6]}-{digits[6..]}"; + } + + private static string FormatDate(DateTime? value) + { + return value.HasValue ? value.Value.ToString("dd.MM.yyyy") : "-"; + } + + private static string FormatPrice(decimal value) + { + return string.Format(new System.Globalization.CultureInfo("tr-TR"), "{0:C2}", value); + } + + private static string Html(string value) + { + return WebUtility.HtmlEncode(value ?? string.Empty); + } + public async Task GetAboutAsync() { var entity = await _aboutRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(About)); diff --git a/api/src/Sozsoft.Platform.Application/Tenants/PlatformTenantAppService.cs b/api/src/Sozsoft.Platform.Application/Tenants/PlatformTenantAppService.cs index 4456c08..52853c4 100644 --- a/api/src/Sozsoft.Platform.Application/Tenants/PlatformTenantAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Tenants/PlatformTenantAppService.cs @@ -74,7 +74,7 @@ public class PlatformTenantAppService : TenantAppService, IPlatformTenantAppServ } var dto = ObjectMapper.Map(tenant); - dto.IsActive = tenant.GetIsActive(); + dto.Name = tenant.Name; dto.OrganizationName = tenant.GetOrganizationName(); dto.Founder = tenant.GetFounder(); dto.VknTckn = tenant.GetVknTckn(); diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json index a1feb0d..69f8c45 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json @@ -40,6 +40,22 @@ "selectOptions": {}, "order": 2 }, + { + "code": "App.SiteManagement.General.NewTenantNotificationEmails", + "nameKey": "App.SiteManagement.General.NewTenantNotificationEmails", + "descriptionKey": "App.SiteManagement.General.NewTenantNotificationEmails.Description", + "defaultValue": "destek@sozsoft.com", + "isVisibleToClients": false, + "providers": "T|G|D", + "isInherited": false, + "isEncrypted": false, + "mainGroupKey": "App.SiteManagement", + "subGroupKey": "App.SiteManagement.General", + "requiredPermissionName": "App.SiteManagement.General", + "dataType": "Text", + "selectOptions": {}, + "order": 3 + }, { "code": "App.SiteManagement.Theme.Style", "nameKey": "App.SiteManagement.Theme.Style", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 60c8c9f..a085055 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -2334,6 +2334,18 @@ "en": "The emails sent for timed login notifications.", "tr": "Süreli giriş bildirimleri için gönderilen mailler." }, + { + "resourceName": "Platform", + "key": "App.SiteManagement.General.NewTenantNotificationEmails", + "en": "New Tenant Notification Emails", + "tr": "Yeni Kiracı Bildirim Mailleri" + }, + { + "resourceName": "Platform", + "key": "App.SiteManagement.General.NewTenantNotificationEmails.Description", + "en": "The emails sent to new tenants.", + "tr": "Yeni kiracılar için gönderilen mailler." + }, { "resourceName": "Platform", "key": "Abp.Localization", @@ -8061,14 +8073,14 @@ { "resourceName": "Platform", "key": "Public.payment.customer.code", - "tr": "Kurum Kodu", - "en": "Organization Code" + "tr": "Kurum", + "en": "Organization" }, { "resourceName": "Platform", "key": "Public.payment.customer.company", - "tr": "Şirket", - "en": "Company" + "tr": "Kurum Adı", + "en": "Organization Name" }, { "resourceName": "Platform", @@ -8268,6 +8280,18 @@ "tr": "Adet", "en": "Quantity" }, + { + "resourceName": "Platform", + "key": "Public.products.kdv", + "tr": "KDV", + "en": "VAT" + }, + { + "resourceName": "Platform", + "key": "Public.products.kdvdahil", + "tr": "KDV Dahil", + "en": "VAT Included" + }, { "resourceName": "Platform", "key": "Public.products.savings.yearly", @@ -13758,6 +13782,12 @@ "en": "Billing Cycle", "tr": "Fatura Döngüsü" }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.Price", + "en": "Price", + "tr": "Fiyat" + }, { "resourceName": "Platform", "key": "App.Listform.ListformField.Period", @@ -13772,8 +13802,8 @@ }, { "resourceName": "Platform", - "key": "App.Listform.ListformField.LicenseStopTime", - "en": "License Stop Time", + "key": "App.Listform.ListformField.LicenseEndTime", + "en": "License End Time", "tr": "Lisans Bitiş Tarihi" }, { @@ -16180,7 +16210,7 @@ "resourceName": "Platform", "key": "App.Listform.ListformField.IsExisting", "en": "Existing Customer", - "tr": "Mevcut Musteri" + "tr": "Mevcut Müşteri" }, { "resourceName": "Platform", @@ -17178,6 +17208,24 @@ "en": "Total Price", "tr": "Toplam Fiyat" }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.VatAmount", + "en": "VAT Amount", + "tr": "KDV Tutarı" + }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.VatRate", + "en": "VAT Rate", + "tr": "KDV Oranı" + }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.VatTotal", + "en": "VAT Total", + "tr": "KDV Toplamı" + }, { "resourceName": "Platform", "key": "App.Listform.ListformField.TotalRevenue", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs index 74176e0..148cf8e 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs @@ -122,6 +122,14 @@ public static class ListFormSeeder_DefaultJsons { IsPivot = true }); + public static string DefaultColumnTotalSummaryJson(string showInColumn, string summaryType = "sum") => JsonSerializer.Serialize(new ColumnTotalSummaryDto + { + SummaryType = summaryType, + DisplayFormat = "{0}", + ValueFormat = "#,##0.00", + ShowInColumn = showInColumn + }); + public static string DefaultGroupingJson(int groupIndex) => JsonSerializer.Serialize(new ColumnGroupingDto { AllowGrouping = true, diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs index de12a7d..1f90e10 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs @@ -95,8 +95,8 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency { new() { Order=1, ColCount=3, ColSpan=1, ItemType="group", Items = [ - new EditingFormItemDto { Order=1, DataField = "OrganizationName", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order=2, DataField = "Name", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, + new EditingFormItemDto { Order=1, DataField = "Name", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, + new EditingFormItemDto { Order=2, DataField = "OrganizationName", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order=3, DataField = "Founder", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order=4, DataField = "VknTckn", ColSpan=1, EditorType2=EditorTypes.dxNumberBox }, new EditingFormItemDto { Order=5, DataField = "TaxOffice", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, @@ -382,6 +382,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency Visible = true, IsActive = true, AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, ColumnCustomizationJson = DefaultColumnCustomizationJson, PermissionJson = DefaultFieldPermissionJson(TenantManagementPermissions.Tenants.Create, TenantManagementPermissions.Tenants.Default, TenantManagementPermissions.Tenants.Update, true, true, false), PivotSettingsJson = DefaultPivotSettingsJson @@ -6442,13 +6443,15 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency new EditingFormItemDto { Order = 4, DataField = "Order", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox }, new EditingFormItemDto { Order = 5, DataField = "MonthlyPrice", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() }, new EditingFormItemDto { Order = 6, DataField = "YearlyPrice", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() }, - new EditingFormItemDto { Order = 7, DataField = "IsQuantityBased", ColSpan = 1, EditorType2 = EditorTypes.dxCheckBox }, - new EditingFormItemDto { Order = 8, DataField = "ImageUrl", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 7, DataField = "VatRate", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() }, + new EditingFormItemDto { Order = 8, DataField = "IsQuantityBased", ColSpan = 1, EditorType2 = EditorTypes.dxCheckBox }, + new EditingFormItemDto { Order = 9, DataField = "ImageUrl", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, ] } }), InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(), FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] { + new() { FieldName = "VatRate", FieldDbType = DbType.Decimal, Value = "0.20", CustomValueType = FieldCustomValueTypeEnum.Value }, new() { FieldName = "IsQuantityBased", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value } }) }); @@ -6610,6 +6613,26 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency EditorOptions=EditorOptionValues.NumberStandartFormat() }, new() + { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Decimal, + FieldName = "VatRate", + CaptionName = "App.Listform.ListformField.VatRate", + Alignment = "right", + Width = 0, + ListOrderNo = 8, + Visible = true, + IsActive = true, + + AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson, + EditorOptions=EditorOptionValues.NumberStandartFormat() + }, + new() { ListFormCode = listForm.ListFormCode, CultureName = LanguageCodes.En, @@ -6617,7 +6640,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "IsQuantityBased", CaptionName = "App.Listform.ListformField.IsQuantityBased", Width = 0, - ListOrderNo = 8, + ListOrderNo = 9, Visible = true, IsActive = true, @@ -6634,7 +6657,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "ImageUrl", CaptionName = "App.Listform.ListformField.ImageUrl", Width = 0, - ListOrderNo = 9, + ListOrderNo = 10, Visible = true, IsActive = true, @@ -6956,41 +6979,42 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Order)), DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(), PagerOptionJson = DefaultPagerOptionJson, - EditingOptionJson = DefaultEditingOptionJson(listFormName, 1000, 700, true, true, true, true, false, true), + EditingOptionJson = DefaultEditingOptionJson(listFormName, 1200, 700, true, true, true, true, false, true), EditingFormJson = JsonSerializer.Serialize(new List { - new() { Order=1, ColCount=3, ColSpan=1, ItemType="group", Items = + new() { Order=1, ColCount=4, ColSpan=1, ItemType="group", Items = [ - new EditingFormItemDto { Order = 1, DataField = "OrganizationName", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 2, DataField = "Founder", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 3, DataField = "VknTckn", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox }, - new EditingFormItemDto { Order = 4, DataField = "TaxOffice", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 5, DataField = "Address1", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 6, DataField = "Address2", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 7, DataField = "Country", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, - new EditingFormItemDto { Order = 8, DataField = "City", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, - new EditingFormItemDto { Order = 9, DataField = "District", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, - new EditingFormItemDto { Order = 10, DataField = "PostalCode", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 1, DataField = "TenantId", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, + new EditingFormItemDto { Order = 2, DataField = "IsExisting", ColSpan = 1, EditorType2 = EditorTypes.dxCheckBox }, + new EditingFormItemDto { Order = 3, DataField = "OrganizationName", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 4, DataField = "Founder", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 5, DataField = "VknTckn", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox }, + new EditingFormItemDto { Order = 6, DataField = "TaxOffice", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 7, DataField = "Address1", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 8, DataField = "Address2", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 9, DataField = "Country", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, + new EditingFormItemDto { Order = 10, DataField = "City", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, + new EditingFormItemDto { Order = 11, DataField = "District", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, + new EditingFormItemDto { Order = 12, DataField = "PostalCode", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 13, DataField = "PhoneNumber", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox, EditorOptions=EditorOptionValues.PhoneEditorOptions }, + new EditingFormItemDto { Order = 14, DataField = "MobileNumber", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox, EditorOptions=EditorOptionValues.PhoneEditorOptions }, + new EditingFormItemDto { Order = 15, DataField = "FaxNumber", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox, EditorOptions=EditorOptionValues.PhoneEditorOptions }, ] }, - new() { Order=2, ColCount=3, ColSpan=1, ItemType="group", Items = + new() { Order=2, ColCount=4, ColSpan=1, ItemType="group", Items = [ - new EditingFormItemDto { Order = 11, DataField = "PhoneNumber", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox, EditorOptions=EditorOptionValues.PhoneEditorOptions }, - new EditingFormItemDto { Order = 12, DataField = "MobileNumber", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox, EditorOptions=EditorOptionValues.PhoneEditorOptions }, - new EditingFormItemDto { Order = 13, DataField = "FaxNumber", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox, EditorOptions=EditorOptionValues.PhoneEditorOptions }, - new EditingFormItemDto { Order = 14, DataField = "Email", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 15, DataField = "Website", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 16, DataField = "Subtotal", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() }, - new EditingFormItemDto { Order = 17, DataField = "Commission", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() }, - new EditingFormItemDto { Order = 18, DataField = "Total", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() }, - new EditingFormItemDto { Order = 19, DataField = "PaymentMethodId", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, - new EditingFormItemDto { Order = 20, DataField = "Installment", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, - new EditingFormItemDto { Order = 21, DataField = "BillingCycle", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 22, DataField = "Period", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox }, - new EditingFormItemDto { Order = 23, DataField = "LicenseStartTime", ColSpan = 1, EditorType2 = EditorTypes.dxDateBox }, - new EditingFormItemDto { Order = 24, DataField = "LicenseStopTime", ColSpan = 1, EditorType2 = EditorTypes.dxDateBox }, - new EditingFormItemDto { Order = 25, DataField = "IsExisting", ColSpan = 1, EditorType2 = EditorTypes.dxCheckBox }, - new EditingFormItemDto { Order = 26, DataField = "IsActive", ColSpan = 1, EditorType2 = EditorTypes.dxCheckBox }, + new EditingFormItemDto { Order = 16, DataField = "PaymentMethodId", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, + new EditingFormItemDto { Order = 17, DataField = "Installment", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, + new EditingFormItemDto { Order = 18, DataField = "LicenseStartTime", ColSpan = 1, EditorType2 = EditorTypes.dxDateBox }, + new EditingFormItemDto { Order = 19, DataField = "BillingCycle", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, + new EditingFormItemDto { Order = 20, DataField = "Period", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox }, + new EditingFormItemDto { Order = 21, DataField = "LicenseEndTime", ColSpan = 1, EditorType2 = EditorTypes.dxDateBox }, + new EditingFormItemDto { Order = 22, DataField = "Email", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 23, DataField = "Website", ColSpan = 1, EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 24, DataField = "Subtotal", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() }, + new EditingFormItemDto { Order = 25, DataField = "VatTotal", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() }, + new EditingFormItemDto { Order = 26, DataField = "Commission", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() }, + new EditingFormItemDto { Order = 27, DataField = "Total", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() }, ] } }), @@ -7000,7 +7024,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency new() { FieldName = "BillingCycle", FieldDbType = DbType.String, Value = "yearly", CustomValueType = FieldCustomValueTypeEnum.Value }, new() { FieldName = "Period", FieldDbType = DbType.Int32, Value = "1", CustomValueType = FieldCustomValueTypeEnum.Value }, new() { FieldName = "IsExisting", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value }, - new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value }, + new() { FieldName = "VatTotal", FieldDbType = DbType.Decimal, Value = "0", CustomValueType = FieldCustomValueTypeEnum.Value }, new() { FieldName = "Commission", FieldDbType = DbType.Decimal, Value = "0", CustomValueType = FieldCustomValueTypeEnum.Value } }), SubFormsJson = JsonSerializer.Serialize(new List() { @@ -7019,7 +7043,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency }) }); - #region Purchase Order Fields + #region Sales Order Fields await _listFormFieldRepository.InsertManyAsync([ new() { @@ -7039,6 +7063,29 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency PivotSettingsJson = DefaultPivotSettingsJson, }, new() + { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Guid, + FieldName = "TenantId", + CaptionName = "App.Listform.ListformField.TenantId", + Width = 0, + ListOrderNo = 2, + Visible = true, + IsActive = true, + LookupJson = JsonSerializer.Serialize(new LookupDto { + DataSourceType = UiLookupDataSourceTypeEnum.Query, + DisplayExpr = "Name", + ValueExpr = "Key", + LookupQuery = LookupQueryValues.TenantValues(), + }), + AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson, + }, + new() { ListFormCode = listForm.ListFormCode, CultureName = LanguageCodes.En, @@ -7046,7 +7093,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "OrganizationName", CaptionName = "App.Listform.ListformField.OrganizationName", Width = 0, - ListOrderNo = 2, + ListOrderNo = 3, Visible = true, IsActive = true, @@ -7064,7 +7111,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "Founder", CaptionName = "App.Listform.ListformField.Founder", Width = 0, - ListOrderNo = 3, + ListOrderNo = 4, Visible = true, IsActive = true, @@ -7082,7 +7129,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "IsExisting", CaptionName = "App.Listform.ListformField.IsExisting", Width = 0, - ListOrderNo = 4, + ListOrderNo = 5, Visible = true, IsActive = true, @@ -7099,11 +7146,20 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "BillingCycle", CaptionName = "App.Listform.ListformField.BillingCycle", Width = 0, - ListOrderNo = 5, + ListOrderNo = 6, Visible = true, IsActive = true, AllowSearch = true, + LookupJson = JsonSerializer.Serialize(new LookupDto { + DataSourceType = UiLookupDataSourceTypeEnum.StaticData, + DisplayExpr = "name", + ValueExpr = "key", + LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] { + new () { Key="yearly", Name="Yearly" }, + new () { Key="monthly", Name="Monthly" }, + }), + }), ColumnCustomizationJson = DefaultColumnCustomizationJson, PermissionJson = DefaultFieldPermissionJson(listForm.Name), PivotSettingsJson = DefaultPivotSettingsJson, @@ -7116,7 +7172,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "Period", CaptionName = "App.Listform.ListformField.Period", Width = 0, - ListOrderNo = 6, + ListOrderNo = 7, Visible = true, IsActive = true, @@ -7133,7 +7189,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "LicenseStartTime", CaptionName = "App.Listform.ListformField.LicenseStartTime", Width = 0, - ListOrderNo = 7, + ListOrderNo = 8, Visible = true, IsActive = true, @@ -7147,10 +7203,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency ListFormCode = listForm.ListFormCode, CultureName = LanguageCodes.En, SourceDbType = DbType.DateTime, - FieldName = "LicenseStopTime", - CaptionName = "App.Listform.ListformField.LicenseStopTime", + FieldName = "LicenseEndTime", + CaptionName = "App.Listform.ListformField.LicenseEndTime", Width = 0, - ListOrderNo = 8, + ListOrderNo = 9, Visible = true, IsActive = true, @@ -7167,7 +7223,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "VknTckn", CaptionName = "App.Listform.ListformField.VknTckn", Width = 0, - ListOrderNo = 9, + ListOrderNo = 10, Visible = true, IsActive = true, @@ -7185,7 +7241,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "TaxOffice", CaptionName = "App.Listform.ListformField.TaxOffice", Width = 0, - ListOrderNo = 10, + ListOrderNo = 11, Visible = true, IsActive = true, @@ -7203,7 +7259,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "Address1", CaptionName = "App.Listform.ListformField.Address1", Width = 0, - ListOrderNo = 11, + ListOrderNo = 12, Visible = true, IsActive = true, @@ -7221,7 +7277,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "Address2", CaptionName = "App.Listform.ListformField.Address2", Width = 0, - ListOrderNo = 12, + ListOrderNo = 13, Visible = true, IsActive = true, @@ -7238,7 +7294,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "Country", CaptionName = "App.Listform.ListformField.Country", Width = 0, - ListOrderNo = 13, + ListOrderNo = 14, Visible = true, IsActive = true, @@ -7264,7 +7320,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "City", CaptionName = "App.Listform.ListformField.City", Width = 0, - ListOrderNo = 14, + ListOrderNo = 15, Visible = true, IsActive = true, @@ -7293,7 +7349,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "District", CaptionName = "App.Listform.ListformField.District", Width = 0, - ListOrderNo = 15, + ListOrderNo = 16, Visible = true, IsActive = true, @@ -7321,7 +7377,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "PhoneNumber", CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber", Width = 0, - ListOrderNo = 16, + ListOrderNo = 17, Visible = true, IsActive = true, @@ -7340,7 +7396,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "MobileNumber", CaptionName = "App.Listform.ListformField.MobileNumber", Width = 0, - ListOrderNo = 17, + ListOrderNo = 18, Visible = true, IsActive = true, @@ -7359,7 +7415,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "FaxNumber", CaptionName = "App.Listform.ListformField.FaxNumber", Width = 0, - ListOrderNo = 18, + ListOrderNo = 19, Visible = true, IsActive = true, @@ -7377,7 +7433,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "Website", CaptionName = "App.Listform.ListformField.Website", Width = 0, - ListOrderNo = 19, + ListOrderNo = 20, Visible = true, IsActive = true, @@ -7396,7 +7452,27 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency CaptionName = "App.Listform.ListformField.Subtotal", Alignment = "right", Width = 0, - ListOrderNo = 20, + ListOrderNo = 21, + Visible = true, + IsActive = true, + + AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson, + EditorOptions=EditorOptionValues.NumberStandartFormat() + }, + new() + { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Decimal, + FieldName = "VatTotal", + CaptionName = "App.Listform.ListformField.VatTotal", + Alignment = "right", + Width = 0, + ListOrderNo = 22, Visible = true, IsActive = true, @@ -7416,7 +7492,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency CaptionName = "App.Listform.ListformField.Commission", Alignment = "right", Width = 0, - ListOrderNo = 21, + ListOrderNo = 23, Visible = true, IsActive = true, @@ -7436,7 +7512,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency CaptionName = "App.Listform.ListformField.Total", Alignment = "right", Width = 0, - ListOrderNo = 22, + ListOrderNo = 25, Visible = true, IsActive = true, @@ -7455,7 +7531,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "PaymentMethodId", CaptionName = "App.Listform.ListformField.PaymentMethodId", Width = 0, - ListOrderNo = 23, + ListOrderNo = 24, Visible = true, IsActive = true, @@ -7474,7 +7550,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "Installment", CaptionName = "App.Listform.ListformField.Installment", Width = 0, - ListOrderNo = 24, + ListOrderNo = 26, Visible = true, IsActive = true, @@ -7493,7 +7569,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "IsActive", CaptionName = "App.Listform.ListformField.IsActive", Width = 0, - ListOrderNo = 25, + ListOrderNo = 27, Visible = true, IsActive = true, @@ -7510,7 +7586,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "PostalCode", CaptionName = "App.Listform.ListformField.PostalCode", Width = 0, - ListOrderNo = 26, + ListOrderNo = 27, Visible = true, IsActive = true, @@ -7528,7 +7604,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency FieldName = "Email", CaptionName = "App.Listform.ListformField.Email", Width = 0, - ListOrderNo = 27, + ListOrderNo = 28, Visible = true, IsActive = true, @@ -7541,133 +7617,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency ]); #endregion } - else - { - var existingListForm = await _listFormRepository.FirstOrDefaultAsync(a => a.ListFormCode == listFormName); - - if (existingListForm != null) - { - if (!await _listFormFieldRepository.AnyAsync(a => - a.ListFormCode == listFormName && - a.FieldName == "BillingCycle" && - a.CultureName == LanguageCodes.En)) - { - await _listFormFieldRepository.InsertAsync(new() - { - ListFormCode = existingListForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.String, - FieldName = "BillingCycle", - CaptionName = "App.Listform.ListformField.BillingCycle", - Width = 0, - ListOrderNo = 20, - Visible = true, - IsActive = true, - - AllowSearch = true, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(existingListForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }); - } - - if (!await _listFormFieldRepository.AnyAsync(a => - a.ListFormCode == listFormName && - a.FieldName == "Period" && - a.CultureName == LanguageCodes.En)) - { - await _listFormFieldRepository.InsertAsync(new() - { - ListFormCode = existingListForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.Int32, - FieldName = "Period", - CaptionName = "App.Listform.ListformField.Period", - Width = 0, - ListOrderNo = 21, - Visible = true, - IsActive = true, - - AllowSearch = true, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(existingListForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }); - } - - if (!await _listFormFieldRepository.AnyAsync(a => - a.ListFormCode == listFormName && - a.FieldName == "LicenseStartTime" && - a.CultureName == LanguageCodes.En)) - { - await _listFormFieldRepository.InsertAsync(new() - { - ListFormCode = existingListForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.DateTime, - FieldName = "LicenseStartTime", - CaptionName = "App.Listform.ListformField.LicenseStartTime", - Width = 0, - ListOrderNo = 22, - Visible = true, - IsActive = true, - - AllowSearch = true, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(existingListForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }); - } - - if (!await _listFormFieldRepository.AnyAsync(a => - a.ListFormCode == listFormName && - a.FieldName == "LicenseStopTime" && - a.CultureName == LanguageCodes.En)) - { - await _listFormFieldRepository.InsertAsync(new() - { - ListFormCode = existingListForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.DateTime, - FieldName = "LicenseStopTime", - CaptionName = "App.Listform.ListformField.LicenseStopTime", - Width = 0, - ListOrderNo = 23, - Visible = true, - IsActive = true, - - AllowSearch = true, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(existingListForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }); - } - - if (!await _listFormFieldRepository.AnyAsync(a => - a.ListFormCode == listFormName && - a.FieldName == "IsExisting" && - a.CultureName == LanguageCodes.En)) - { - await _listFormFieldRepository.InsertAsync(new() - { - ListFormCode = existingListForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.Boolean, - FieldName = "IsExisting", - CaptionName = "App.Listform.ListformField.IsExisting", - Width = 0, - ListOrderNo = 24, - Visible = true, - IsActive = true, - - AllowSearch = true, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(existingListForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }); - } - } - } #endregion #region Sales Order Item @@ -7705,7 +7654,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency SelectionJson = DefaultSelectionSingleJson, ColumnOptionJson = DefaultColumnOptionJson(), PermissionJson = DefaultPermissionJson(listFormName), - DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Uom)), + DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.OrderItem)), DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(), InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(), PagerOptionJson = DefaultPagerOptionJson, @@ -7716,7 +7665,11 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency new EditingFormItemDto { Order = 1, DataField = "ProductId", ColSpan = 1, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, new EditingFormItemDto { Order = 2, DataField = "BillingCycle", ColSpan = 1, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, new EditingFormItemDto { Order = 3, DataField = "Quantity", ColSpan = 1, EditorType2=EditorTypes.dxNumberBox }, - new EditingFormItemDto { Order = 4, DataField = "TotalPrice", ColSpan = 1, EditorType2=EditorTypes.dxNumberBox }, + new EditingFormItemDto { Order = 4, DataField = "Price", ColSpan = 1, EditorType2=EditorTypes.dxNumberBox }, + new EditingFormItemDto { Order = 5, DataField = "Period", ColSpan = 1, EditorType2=EditorTypes.dxNumberBox }, + new EditingFormItemDto { Order = 6, DataField = "VatRate", ColSpan = 1, EditorType2=EditorTypes.dxNumberBox }, + new EditingFormItemDto { Order = 7, DataField = "VatAmount", ColSpan = 1, EditorType2=EditorTypes.dxNumberBox }, + new EditingFormItemDto { Order = 8, DataField = "TotalPrice", ColSpan = 1, EditorType2=EditorTypes.dxNumberBox }, ]} }), }, autoSave: true @@ -7818,8 +7771,8 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency ListFormCode = listForm.ListFormCode, CultureName = LanguageCodes.En, SourceDbType = DbType.Decimal, - FieldName = "TotalPrice", - CaptionName = "App.Listform.ListformField.TotalPrice", + FieldName = "Price", + CaptionName = "App.Listform.ListformField.Price", Alignment = "right", Width = 0, ListOrderNo = 5, @@ -7833,6 +7786,88 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency PivotSettingsJson = DefaultPivotSettingsJson, EditorOptions=EditorOptionValues.NumberStandartFormat() }, + new ListFormField + { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Int32, + FieldName = "Period", + CaptionName = "App.Listform.ListformField.Period", + Alignment = "right", + Width = 0, + ListOrderNo = 6, + Visible = true, + IsActive = true, + + AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson, + EditorOptions=EditorOptionValues.NumberStandartFormat() + }, + new ListFormField + { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Decimal, + FieldName = "VatRate", + CaptionName = "App.Listform.ListformField.VatRate", + Alignment = "right", + Width = 0, + ListOrderNo = 7, + Visible = true, + IsActive = true, + + AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson, + EditorOptions=EditorOptionValues.NumberStandartFormat() + }, + new ListFormField + { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Decimal, + FieldName = "VatAmount", + CaptionName = "App.Listform.ListformField.VatAmount", + Alignment = "right", + Width = 0, + ListOrderNo = 8, + Visible = true, + IsActive = true, + + AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson, + EditorOptions = EditorOptionValues.NumberStandartFormat(), + TotalSummaryJson = DefaultColumnTotalSummaryJson("VatAmount") + }, + new ListFormField + { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Decimal, + FieldName = "TotalPrice", + CaptionName = "App.Listform.ListformField.TotalPrice", + Alignment = "right", + Width = 0, + ListOrderNo = 9, + Visible = true, + IsActive = true, + + AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson, + EditorOptions=EditorOptionValues.NumberStandartFormat(), + TotalSummaryJson = DefaultColumnTotalSummaryJson("TotalPrice") + }, ], autoSave: true); #endregion } diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Order.cs b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Order.cs index fa29b10..fef562b 100644 --- a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Order.cs +++ b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Order.cs @@ -6,10 +6,7 @@ namespace Sozsoft.Platform.Entities; public class Order : FullAuditedEntity { - public string Name { get; set; } - public bool IsActive { get; set; } - public bool IsExisting { get; set; } - + public Guid? TenantId { get; set; } public string OrganizationName { get; set; } public string Founder { get; set; } public long? VknTckn { get; set; } @@ -27,24 +24,33 @@ public class Order : FullAuditedEntity public string Website { get; set; } public string MenuGroup { get; set; } + public bool IsExisting { get; set; } + public bool IsActive { get; set; } + public string BillingCycle { get; set; } public int Period { get; set; } public DateTime? LicenseStartTime { get; set; } - public DateTime? LicenseStopTime { get; set; } + public DateTime? LicenseEndTime { get; set; } - public List Items { get; set; } = new(); public decimal Subtotal { get; set; } + public decimal VatTotal { get; set; } public decimal Commission { get; set; } public decimal Total { get; set; } public Guid PaymentMethodId { get; set; } public int? Installment { get; set; } - public string PaymentDataJson { get; set; } // JSON olarak saklanacak + public List Items { get; set; } = new(); + public Order() { } + + public Order(Guid id) + : base(id) + { + } } public class OrderItem : FullAuditedEntity @@ -54,7 +60,11 @@ public class OrderItem : FullAuditedEntity public Guid ProductId { get; set; } public string ProductName { get; set; } public string BillingCycle { get; set; } + public int Period { get; set; } public int Quantity { get; set; } + public decimal Price { get; set; } + public decimal VatRate { get; set; } + public decimal VatAmount { get; set; } public decimal TotalPrice { get; set; } public OrderItem() diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Product.cs b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Product.cs index 68255a3..f3b3db2 100644 --- a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Product.cs +++ b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Product.cs @@ -11,6 +11,7 @@ public class Product : FullAuditedEntity public int Order { get; set; } public decimal? MonthlyPrice { get; set; } public decimal? YearlyPrice { get; set; } + public decimal VatRate { get; set; } = 0.20m; public bool IsQuantityBased { get; set; } = false; public string ImageUrl { get; set; } } diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index 46c9882..2daf729 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -980,6 +980,7 @@ public class PlatformDbContext : b.Property(x => x.Category).IsRequired().HasMaxLength(64); b.Property(x => x.MonthlyPrice).HasPrecision(18, 2); b.Property(x => x.YearlyPrice).HasPrecision(18, 2); + b.Property(x => x.VatRate).IsRequired().HasPrecision(5, 3).HasDefaultValue(0.20m); b.Property(x => x.ImageUrl).HasMaxLength(512); b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0"); @@ -1013,7 +1014,12 @@ public class PlatformDbContext : b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Order)), Prefix.DbSchema); b.ConfigureByConvention(); - b.Property(o => o.Name).HasMaxLength(64); + var isActiveComputedColumnSql = _isPostgreSql + ? "CASE WHEN \"LicenseEndTime\" IS NOT NULL AND CURRENT_DATE > \"LicenseEndTime\"::date THEN false ELSE true END" + : "CONVERT(bit, CASE WHEN [LicenseEndTime] IS NOT NULL AND CONVERT(date, GETDATE()) > CONVERT(date, [LicenseEndTime]) THEN 0 ELSE 1 END)"; + + b.Property(o => o.IsActive) + .HasComputedColumnSql(isActiveComputedColumnSql, stored: false); b.Property(o => o.IsExisting).IsRequired().HasDefaultValue(false); b.Property(o => o.OrganizationName).HasMaxLength(128); b.Property(o => o.Founder).HasMaxLength(128); @@ -1034,6 +1040,7 @@ public class PlatformDbContext : b.Property(o => o.Period).IsRequired().HasDefaultValue(1); b.Property(o => o.Subtotal).IsRequired().HasPrecision(18, 2); + b.Property(o => o.VatTotal).IsRequired().HasPrecision(18, 2); b.Property(o => o.Commission).IsRequired().HasPrecision(18, 2); b.Property(o => o.Total).IsRequired().HasPrecision(18, 2); @@ -1052,6 +1059,10 @@ public class PlatformDbContext : b.Property(i => i.ProductName).IsRequired().HasMaxLength(128); b.Property(i => i.BillingCycle).IsRequired().HasMaxLength(32); + b.Property(i => i.Period).IsRequired().HasDefaultValue(1); + b.Property(i => i.Price).IsRequired().HasPrecision(18, 2); + b.Property(i => i.VatRate).IsRequired().HasPrecision(5, 3); + b.Property(i => i.VatAmount).IsRequired().HasPrecision(18, 2); b.Property(i => i.TotalPrice).IsRequired().HasPrecision(18, 2); }); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260706213526_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260707105700_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260706213526_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260707105700_Initial.Designer.cs index efdb97b..21e3acf 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260706213526_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260707105700_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260706213526_Initial")] + [Migration("20260707105700_Initial")] partial class Initial { /// @@ -4137,7 +4137,9 @@ namespace Sozsoft.Platform.Migrations .HasColumnType("int"); b.Property("IsActive") - .HasColumnType("bit"); + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bit") + .HasComputedColumnSql("CONVERT(bit, CASE WHEN [LicenseEndTime] IS NOT NULL AND CONVERT(date, GETDATE()) > CONVERT(date, [LicenseEndTime]) THEN 0 ELSE 1 END)", false); b.Property("IsDeleted") .ValueGeneratedOnAdd() @@ -4158,10 +4160,10 @@ namespace Sozsoft.Platform.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); - b.Property("LicenseStartTime") + b.Property("LicenseEndTime") .HasColumnType("datetime2"); - b.Property("LicenseStopTime") + b.Property("LicenseStartTime") .HasColumnType("datetime2"); b.Property("MenuGroup") @@ -4172,10 +4174,6 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(20) .HasColumnType("nvarchar(20)"); - b.Property("Name") - .HasMaxLength(64) - .HasColumnType("nvarchar(64)"); - b.Property("OrganizationName") .HasMaxLength(128) .HasColumnType("nvarchar(128)"); @@ -4208,10 +4206,17 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(128) .HasColumnType("nvarchar(128)"); + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + b.Property("Total") .HasPrecision(18, 2) .HasColumnType("decimal(18,2)"); + b.Property("VatTotal") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + b.Property("VknTckn") .HasColumnType("bigint"); @@ -4267,6 +4272,15 @@ namespace Sozsoft.Platform.Migrations b.Property("OrderId") .HasColumnType("uniqueidentifier"); + b.Property("Period") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + b.Property("ProductId") .HasColumnType("uniqueidentifier"); @@ -4282,6 +4296,14 @@ namespace Sozsoft.Platform.Migrations .HasPrecision(18, 2) .HasColumnType("decimal(18,2)"); + b.Property("VatAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("VatRate") + .HasPrecision(5, 3) + .HasColumnType("decimal(5,3)"); + b.HasKey("Id"); b.HasIndex("OrderId"); @@ -4409,6 +4431,12 @@ namespace Sozsoft.Platform.Migrations b.Property("Order") .HasColumnType("int"); + b.Property("VatRate") + .ValueGeneratedOnAdd() + .HasPrecision(5, 3) + .HasColumnType("decimal(5,3)") + .HasDefaultValue(0.20m); + b.Property("YearlyPrice") .HasPrecision(18, 2) .HasColumnType("decimal(18,2)"); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260706213526_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260707105700_Initial.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260706213526_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260707105700_Initial.cs index d222171..2ef9b86 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260706213526_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260707105700_Initial.cs @@ -1571,9 +1571,7 @@ namespace Sozsoft.Platform.Migrations columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - IsActive = table.Column(type: "bit", nullable: false), - IsExisting = table.Column(type: "bit", nullable: false, defaultValue: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), OrganizationName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), Founder = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), VknTckn = table.Column(type: "bigint", nullable: true), @@ -1590,11 +1588,14 @@ namespace Sozsoft.Platform.Migrations Email = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), Website = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), MenuGroup = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + IsExisting = table.Column(type: "bit", nullable: false, defaultValue: false), + IsActive = table.Column(type: "bit", nullable: false, computedColumnSql: "CONVERT(bit, CASE WHEN [LicenseEndTime] IS NOT NULL AND CONVERT(date, GETDATE()) > CONVERT(date, [LicenseEndTime]) THEN 0 ELSE 1 END)", stored: false), BillingCycle = table.Column(type: "nvarchar(32)", maxLength: 32, nullable: true), Period = table.Column(type: "int", nullable: false, defaultValue: 1), LicenseStartTime = table.Column(type: "datetime2", nullable: true), - LicenseStopTime = table.Column(type: "datetime2", nullable: true), + LicenseEndTime = table.Column(type: "datetime2", nullable: true), Subtotal = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + VatTotal = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), Commission = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), Total = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), PaymentMethodId = table.Column(type: "uniqueidentifier", nullable: false), @@ -1645,6 +1646,7 @@ namespace Sozsoft.Platform.Migrations Order = table.Column(type: "int", nullable: false), MonthlyPrice = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true), YearlyPrice = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true), + VatRate = table.Column(type: "decimal(5,3)", precision: 5, scale: 3, nullable: false, defaultValue: 0.20m), IsQuantityBased = table.Column(type: "bit", nullable: false), ImageUrl = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), @@ -2890,7 +2892,11 @@ namespace Sozsoft.Platform.Migrations ProductId = table.Column(type: "uniqueidentifier", nullable: false), ProductName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), BillingCycle = table.Column(type: "nvarchar(32)", maxLength: 32, nullable: false), + Period = table.Column(type: "int", nullable: false, defaultValue: 1), Quantity = table.Column(type: "int", nullable: false), + Price = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + VatRate = table.Column(type: "decimal(5,3)", precision: 5, scale: 3, nullable: false), + VatAmount = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), TotalPrice = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), CreationTime = table.Column(type: "datetime2", nullable: false), CreatorId = table.Column(type: "uniqueidentifier", nullable: true), diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 4346b54..15b63e6 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -4134,7 +4134,9 @@ namespace Sozsoft.Platform.Migrations .HasColumnType("int"); b.Property("IsActive") - .HasColumnType("bit"); + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bit") + .HasComputedColumnSql("CONVERT(bit, CASE WHEN [LicenseEndTime] IS NOT NULL AND CONVERT(date, GETDATE()) > CONVERT(date, [LicenseEndTime]) THEN 0 ELSE 1 END)", false); b.Property("IsDeleted") .ValueGeneratedOnAdd() @@ -4155,10 +4157,10 @@ namespace Sozsoft.Platform.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); - b.Property("LicenseStartTime") + b.Property("LicenseEndTime") .HasColumnType("datetime2"); - b.Property("LicenseStopTime") + b.Property("LicenseStartTime") .HasColumnType("datetime2"); b.Property("MenuGroup") @@ -4169,10 +4171,6 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(20) .HasColumnType("nvarchar(20)"); - b.Property("Name") - .HasMaxLength(64) - .HasColumnType("nvarchar(64)"); - b.Property("OrganizationName") .HasMaxLength(128) .HasColumnType("nvarchar(128)"); @@ -4205,10 +4203,17 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(128) .HasColumnType("nvarchar(128)"); + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + b.Property("Total") .HasPrecision(18, 2) .HasColumnType("decimal(18,2)"); + b.Property("VatTotal") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + b.Property("VknTckn") .HasColumnType("bigint"); @@ -4264,6 +4269,15 @@ namespace Sozsoft.Platform.Migrations b.Property("OrderId") .HasColumnType("uniqueidentifier"); + b.Property("Period") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + b.Property("ProductId") .HasColumnType("uniqueidentifier"); @@ -4279,6 +4293,14 @@ namespace Sozsoft.Platform.Migrations .HasPrecision(18, 2) .HasColumnType("decimal(18,2)"); + b.Property("VatAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("VatRate") + .HasPrecision(5, 3) + .HasColumnType("decimal(5,3)"); + b.HasKey("Id"); b.HasIndex("OrderId"); @@ -4406,6 +4428,12 @@ namespace Sozsoft.Platform.Migrations b.Property("Order") .HasColumnType("int"); + b.Property("VatRate") + .ValueGeneratedOnAdd() + .HasPrecision(5, 3) + .HasColumnType("decimal(5,3)") + .HasDefaultValue(0.20m); + b.Property("YearlyPrice") .HasPrecision(18, 2) .HasColumnType("decimal(18,2)"); diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs index cda0ce4..d9f814a 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs @@ -29,6 +29,7 @@ public class DynamicFormReport : XtraReport public const string ReportName = nameof(DynamicFormReport); private const string QueryName = "DynamicForm"; private const int HorizontalMargin = 50; + private const int LandscapeColumnThreshold = 4; private const float RowHeight = 24; private const float ColumnGap = 10; private static readonly System.Drawing.Color FieldBorderColor = System.Drawing.Color.FromArgb(226, 232, 240); @@ -118,17 +119,23 @@ public class DynamicFormReport : XtraReport var row = GetFirstRow(await dynamicDataRepository.QueryAsync(selectQuery, connectionString)); - ConfigurePage(); + var formGroups = GetFormGroups(listForm, reportFields).ToList(); + var listSubForms = GetListSubForms(listForm, row).ToList(); + var subReports = await CreateSubReportsAsync(listSubForms); + var requiresLandscape = HasWideFormLayout(formGroups) || + subReports.Any(x => x.Report.RequiresLandscapeLayout); + + ConfigurePage(requiresLandscape); ConfigureDataSource(selectQuery, connectionString, dataSourceType); - await BuildLayoutAsync(listForm, reportFields, row, localizer, DynamicReportImageHelper.GetImageBaseUrl(configuration), currentUser.UserName, isSubReport); + BuildLayout(listForm, formGroups, row, subReports, localizer, DynamicReportImageHelper.GetImageBaseUrl(configuration), currentUser.UserName, isSubReport); RequestParameters = false; } - private void ConfigurePage() + private void ConfigurePage(bool landscape) { Margins = CreateReportMargins(HorizontalMargin, HorizontalMargin, 35, 35); PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4; - Landscape = false; + Landscape = landscape; Font = DynamicReportFontHelper.CreateDataFont(); } @@ -153,23 +160,22 @@ public class DynamicFormReport : XtraReport DataMember = QueryName; } - private async Task BuildLayoutAsync( + private void BuildLayout( ListForm listForm, - IReadOnlyList fields, + IReadOnlyList formGroups, IDictionary row, + IReadOnlyList subReports, IStringLocalizer localizer, string imageBaseUrl, string currentUserName, bool isSubReport) { Bands.Clear(); - var formGroups = GetFormGroups(listForm, fields).ToList(); - var listSubForms = GetListSubForms(listForm, row).ToList(); var topMargin = new TopMarginBand { HeightF = Margins.Top }; var bottomMargin = new BottomMarginBand { HeightF = 32 }; var reportHeader = new ReportHeaderBand { HeightF = 38 }; - var detail = new DetailBand { HeightF = CalculateDetailHeight(formGroups, listSubForms.Count) }; + var detail = new DetailBand { HeightF = CalculateDetailHeight(formGroups, subReports.Count) }; Bands.AddRange([topMargin, reportHeader, detail, bottomMargin]); @@ -242,12 +248,12 @@ public class DynamicFormReport : XtraReport y += 8; } - foreach (var subForm in listSubForms) + foreach (var subReport in subReports) { detail.Controls.Add(new XRSubreport { BoundsF = new System.Drawing.RectangleF(0, y, pageWidth, 24), - ReportSource = await DynamicGridReport.CreateAsync(CreateSubReportParameters(subForm.Form, subForm.Filter)) + ReportSource = subReport.Report }); y += 28; } @@ -255,6 +261,24 @@ public class DynamicFormReport : XtraReport detail.HeightF = Math.Max(y, RowHeight); } + private static bool HasWideFormLayout(IEnumerable formGroups) + { + return formGroups.Any(x => GetSafeColumnCount(x.ColumnCount) >= LandscapeColumnThreshold); + } + + private static async Task> CreateSubReportsAsync( + IReadOnlyList<(SubFormDto Form, string Filter)> subForms) + { + var reports = new List(); + foreach (var subForm in subForms) + { + reports.Add(new SubReportSource( + await DynamicGridReport.CreateAsync(CreateSubReportParameters(subForm.Form, subForm.Filter)))); + } + + return reports; + } + private static XRLabel CreateCaptionLabel( FormReportField field, IStringLocalizer localizer, @@ -728,6 +752,7 @@ public class DynamicFormReport : XtraReport private sealed record FieldWithEditor(ListFormField Field, string EditorType, int ColumnSpan); private sealed record FormGroup(string Caption, int ColumnCount, IReadOnlyList Fields); private sealed record FormLayoutItem(FormReportField Field, int ColumnIndex, int ColumnSpan); + private sealed record SubReportSource(DynamicGridReport Report); private sealed record FormReportField( ListFormField Field, string BindingName, diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs index 96e6bfc..753627e 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs @@ -33,6 +33,8 @@ public class DynamicGridReport : XtraReport private static readonly System.Drawing.Color TableBorderColor = System.Drawing.Color.FromArgb(226, 232, 240); private static IServiceScopeFactory _scopeFactory; + public bool RequiresLandscapeLayout { get; private set; } + public static void SetScopeFactory(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; @@ -177,7 +179,7 @@ public class DynamicGridReport : XtraReport localizer); reportColumns = await ApplyImageCountsAsync(reportColumns, dynamicDataRepository, selectQuery, connectionString, imageBaseUrl); - ConfigurePage(reportColumns, localizer, stateWidths, isSubReport); + ConfigurePage(reportColumns, localizer, stateWidths); ConfigureDataSource( selectQuery, connectionString, @@ -213,8 +215,7 @@ public class DynamicGridReport : XtraReport private void ConfigurePage( IReadOnlyCollection visibleFields, IStringLocalizer localizer, - IReadOnlyDictionary stateWidths, - bool isSubReport) + IReadOnlyDictionary stateWidths) { Margins = CreateReportMargins(HorizontalMargin, HorizontalMargin, 35, 35); PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4; @@ -222,8 +223,9 @@ public class DynamicGridReport : XtraReport var requestedContentWidth = visibleFields.Sum(x => x.IsImage ? GetImageColumnWidth(x) : DynamicReportGridStateHelper.GetPreferredColumnWidth(x.Field, localizer, stateWidths)); - Landscape = !isSubReport && - (visibleFields.Count >= LandscapeColumnThreshold || requestedContentWidth > portraitContentWidth); + RequiresLandscapeLayout = visibleFields.Count >= LandscapeColumnThreshold || + requestedContentWidth > portraitContentWidth; + Landscape = RequiresLandscapeLayout; Font = DynamicReportFontHelper.CreateDataFont(); } diff --git a/ui/public/version.json b/ui/public/version.json index b7d3314..ddf7013 100644 --- a/ui/public/version.json +++ b/ui/public/version.json @@ -1,6 +1,16 @@ { - "commit": "e7670c6", + "commit": "708bfd0", "releases": [ + { + "version": "1.1.06", + "buildDate": "2026-07-06", + "commit": "bab639ce6343b451b81d6688e4f7af666f0233da", + "changeLog": [ + "- Çekirdek kod için optimizasyon", + "- List Reportlara Gruplu ve Alttoplam eklendi.", + "- Paketlerdeki deprecated uyarları kaldırıldı" + ] + }, { "version": "1.1.05", "buildDate": "2026-06-27", diff --git a/ui/src/components/orders/Cart.tsx b/ui/src/components/orders/Cart.tsx index 1985b03..a713ce8 100644 --- a/ui/src/components/orders/Cart.tsx +++ b/ui/src/components/orders/Cart.tsx @@ -6,6 +6,8 @@ import { Button } from '@/components/ui' interface CartState { items: BasketItem[] + subtotal: number + vatTotal: number total: number globalBillingCycle: BillingCycle globalPeriod: number @@ -109,7 +111,9 @@ export const Cart: React.FC = ({
{item.product.isQuantityBased && item.quantity > 1 && ( - {item.quantity}x - + + {item.quantity}x -{' '} + )} {item.billingCycle === 'monthly' ? translate('::Public.products.billingcycle.monthly') @@ -135,7 +139,9 @@ export const Cart: React.FC = ({ variant="default" size="xs" /> - {item.quantity} + + {item.quantity} +
)} -
-
- {formatPrice(item.totalPrice)} +
+ {formatPrice(item.totalPrice)} +
))} @@ -157,21 +163,26 @@ export const Cart: React.FC = ({ {cartState.items.length > 0 && (
-
- - {translate('::App.Listform.ListformField.Total')} - - - {formatPrice(cartState.total)} - +
+
+ {translate('::App.Listform.ListformField.Subtotal')} + {formatPrice(cartState.subtotal)} +
+
+ {translate('::Public.products.kdv')} + {formatPrice(cartState.vatTotal)} +
+
+ + {translate('::App.Listform.ListformField.Total')} + + + {formatPrice(cartState.total)} + +
-
diff --git a/ui/src/components/orders/OrderSuccess.tsx b/ui/src/components/orders/OrderSuccess.tsx index 902c155..3a50fdf 100644 --- a/ui/src/components/orders/OrderSuccess.tsx +++ b/ui/src/components/orders/OrderSuccess.tsx @@ -1,46 +1,214 @@ import React from 'react' -import { FaCheckCircle, FaArrowLeft } from 'react-icons/fa' +import { FaArrowLeft, FaBuilding, FaCalendarAlt, FaCheckCircle, FaEnvelope, FaPhone } from 'react-icons/fa' import { useNavigate } from 'react-router-dom' import { ROUTES_ENUM } from '@/routes/route.constant' import { useLocalization } from '@/utils/hooks/useLocalization' import { Button } from '@/components/ui' +import { BasketItem, BillingCycle } from '@/proxy/order/models' +import { CustomTenantDto } from '@/proxy/config/models' + +export interface OrderSuccessData { + orderId: string + tenant: CustomTenantDto + items: BasketItem[] + billingCycle: BillingCycle + period: number + licenseStartTime?: string + licenseEndTime?: string + subtotal: number + vatTotal: number + commission: number + total: number + installment?: number +} interface OrderSuccessProps { orderId: string + order?: OrderSuccessData | null onBackToShop: () => void } -export const OrderSuccess: React.FC = ({ orderId }) => { +export const OrderSuccess: React.FC = ({ orderId, order }) => { const navigate = useNavigate() const { translate } = useLocalization() + const formatPrice = (price: number) => + new Intl.NumberFormat('tr-TR', { + style: 'currency', + currency: 'TRY', + minimumFractionDigits: 2, + }).format(price) + + const formatDate = (value?: string) => { + if (!value) return '-' + + return new Intl.DateTimeFormat('tr-TR', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }).format(new Date(value)) + } + + const billingCycleLabel = + order?.billingCycle === 'monthly' + ? 'Aylık' + : order?.billingCycle === 'yearly' + ? 'Yıllık' + : '-' + + const getItemPrice = (item: BasketItem) => + item.billingCycle === 'monthly' + ? item.product.monthlyPrice ?? 0 + : item.product.yearlyPrice ?? 0 + + const vatRateLabel = (() => { + if (!order) return '' + + const rates = Array.from( + new Set(order.items.map((item) => Math.round((item.vatRate ?? 0) * 100))), + ).filter((rate) => rate > 0) + + return rates.length > 0 ? ` (${rates.map((rate) => `%${rate}`).join(', ')})` : '' + })() return ( -
-
-
- -

- {translate('::Public.order.success.title')} -

- -

- {translate('::Public.order.success.number')}{' '} - #{orderId} -

+
+
+
+
+ +
+

+ {translate('::Public.order.success.title')} +

+

+ {translate('::Public.order.success.number')}{' '} + #{orderId} +

+
+
+
+
+ Invoice +
+
+ {formatDate(new Date().toISOString())} +
+
-
-

- {translate('::Public.order.success.nextSteps')} -

-
    -
  • • {translate('::Public.order.success.step1')}
  • -
  • • {translate('::Public.order.success.step2')}
  • -
  • • {translate('::Public.order.success.step3')}
  • -
-
+ {order && ( +
+
+
+

+ Customer Information +

+
+
+ + {order.tenant.organizationName} +
+
+ + {order.tenant.email} +
+
+ + {order.tenant.phoneNumber || order.tenant.mobileNumber} +
+
+
{order.tenant.address1}
+
+ {order.tenant.district} / {order.tenant.city} / {order.tenant.country} +
+
+
+
-
+
+

+ + License Information +

+
+
+
License Start Time
+
{formatDate(order.licenseStartTime)}
+
+
+
License End Time
+
{formatDate(order.licenseEndTime)}
+
+
+
Billing Cycle
+
{billingCycleLabel}
+
+
+
Period
+
{order.period}
+
+
+
+
+ +
+
+
Product
+
Price
+
Period
+
Qty
+
Cycle
+
{translate('::Public.products.kdv')}
+
Amount
+
+ {order.items.map((item, index) => ( +
+
+ {translate('::' + item.product.name)} +
+
{formatPrice(getItemPrice(item))}
+
{order.period}
+
{item.quantity}
+
+ {item.billingCycle === 'monthly' ? 'Aylık' : 'Yıllık'} +
+
+ %{((item.vatRate ?? 0) * 100).toFixed(0)} / {formatPrice(item.vatAmount ?? 0)} +
+
+ {formatPrice(item.totalPrice)} +
+
+ ))} +
+ +
+
+
+ {translate('::Public.payment.summary.subtotal')} + {formatPrice(order.subtotal)} +
+
+ {translate('::Public.products.kdv')}{vatRateLabel} + {formatPrice(order.vatTotal)} +
+
+ {translate('::Public.payment.installments.commission')} + {formatPrice(order.commission)} +
+
+ {translate('::App.Listform.ListformField.Total')} + {formatPrice(order.total)} +
+
+
+
+ )} + +
@@ -476,12 +590,6 @@ export const TenantForm: React.FC = ({ onSubmit }) => { {formData.organizationName && (
-
- - {translate('::LoginPanel.Profil')} - {formData.founder} -
-
@@ -490,6 +598,12 @@ export const TenantForm: React.FC = ({ onSubmit }) => { {formData.organizationName}
+
+ + {translate('::LoginPanel.Profil')} + {formData.founder} +
+
diff --git a/ui/src/proxy/config/models.ts b/ui/src/proxy/config/models.ts index 1f9bbba..2d16128 100644 --- a/ui/src/proxy/config/models.ts +++ b/ui/src/proxy/config/models.ts @@ -21,28 +21,24 @@ export interface TenantDto extends ExtensibleEntityDto { } export interface CustomTenantDto extends TenantDto { - isExisting: boolean - reference?: string - id?: string - name: string - isActive: boolean organizationName: string founder?: string vknTckn: number taxOffice: string - address1: string - address2: string - district: string country: string city: string + district: string + address1: string + address2: string postalCode: string - phoneNumber: string mobileNumber: string + phoneNumber: string faxNumber: string email: string website: string menuGroup?: string + reference?: string } export interface MultiTenancyInfoDto { diff --git a/ui/src/proxy/order/models.ts b/ui/src/proxy/order/models.ts index 7dbe5d3..5b3f1c8 100644 --- a/ui/src/proxy/order/models.ts +++ b/ui/src/proxy/order/models.ts @@ -9,6 +9,7 @@ export interface Product { category: string monthlyPrice?: number yearlyPrice?: number + vatRate: number unit: BillingCycle isQuantityBased: boolean imageUrl?: string @@ -22,6 +23,7 @@ export interface ProductDto { category: string monthlyPrice?: number yearlyPrice?: number + vatRate: number unit: string isQuantityBased: boolean imageUrl: string @@ -31,6 +33,8 @@ export interface BasketItem { product: ProductDto quantity: number billingCycle: BillingCycle + vatRate: number + vatAmount: number totalPrice: number } @@ -60,14 +64,21 @@ export interface InstallmentOptionDto { export interface OrderDto { tenant: CustomTenantDto items: BasketItem[] + + isExisting: boolean + billingCycle: BillingCycle period: number licenseStartTime?: string - licenseStopTime?: string + licenseEndTime?: string + subtotal: number + vatTotal: number commission: number total: number + paymentMethodId: string installment: number + paymentData: Record } diff --git a/ui/src/services/order.service.ts b/ui/src/services/order.service.ts index 7c78ef3..8a8d37a 100644 --- a/ui/src/services/order.service.ts +++ b/ui/src/services/order.service.ts @@ -44,10 +44,11 @@ export class OrderService { data: { tenant: orderData.tenant, items: orderData.items, + isExisting: orderData.isExisting, billingCycle: orderData.billingCycle, period: orderData.period, licenseStartTime: orderData.licenseStartTime, - licenseStopTime: orderData.licenseStopTime, + licenseEndTime: orderData.licenseEndTime, subtotal: orderData.subtotal, commission: orderData.commission, total: orderData.total, diff --git a/ui/src/utils/cartUtils.ts b/ui/src/utils/cartUtils.ts index 538dadf..0a56142 100644 --- a/ui/src/utils/cartUtils.ts +++ b/ui/src/utils/cartUtils.ts @@ -2,6 +2,8 @@ import { BillingCycle, BasketItem, ProductDto } from "@/proxy/order/models"; export interface CartState { items: BasketItem[]; + subtotal: number; + vatTotal: number; total: number; globalBillingCycle: BillingCycle; globalPeriod: number; @@ -9,6 +11,8 @@ export interface CartState { export const initialCartState: CartState = { items: [], + subtotal: 0, + vatTotal: 0, total: 0, globalBillingCycle: 'yearly', globalPeriod: 1 @@ -24,7 +28,43 @@ export const calculateItemPrice = ( period: number = 1 ): number => { const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!; - return price * quantity * period; + const vatRate = product.vatRate ?? 0.2; + return roundMoney(price * quantity * period * (1 + vatRate)); +}; + +const roundMoney = (value: number): number => Math.round(value * 100) / 100; + +const calculateItemVatAmount = ( + totalPrice: number, + vatRate: number = 0.2, +): number => (vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0); + +const withItemVat = ( + product: ProductDto, + quantity: number, + billingCycle: BillingCycle, + period: number, +): BasketItem => { + const vatRate = product.vatRate ?? 0.2; + const totalPrice = calculateItemPrice(product, quantity, billingCycle, period); + return { + product, + quantity, + billingCycle, + vatRate, + vatAmount: calculateItemVatAmount(totalPrice, vatRate), + totalPrice, + }; +}; + +const calculateCartTotals = (items: BasketItem[]) => { + const total = roundMoney(items.reduce((sum, item) => sum + item.totalPrice, 0)); + const vatTotal = roundMoney(items.reduce((sum, item) => sum + (item.vatAmount ?? 0), 0)); + return { + subtotal: roundMoney(total - vatTotal), + vatTotal, + total, + }; }; export const addItemToCart = ( @@ -47,28 +87,28 @@ export const addItemToCart = ( if (existingItemIndex > -1) { newItems = [...cartState.items]; newItems[existingItemIndex].quantity += quantity; - newItems[existingItemIndex].totalPrice = calculateItemPrice( + newItems[existingItemIndex] = withItemVat( product, newItems[existingItemIndex].quantity, billingCycle, - cartState.globalPeriod + cartState.globalPeriod, ); } else { const actualQuantity = !product.isQuantityBased ? 1 : quantity; - const totalPrice = calculateItemPrice(product, actualQuantity, billingCycle, cartState.globalPeriod); - newItems = [...cartState.items, { product, quantity: actualQuantity, billingCycle, totalPrice }]; + newItems = [ + ...cartState.items, + withItemVat(product, actualQuantity, billingCycle, cartState.globalPeriod), + ]; } - const total = newItems.reduce((sum, item) => sum + item.totalPrice, 0); - const updatedState = { ...cartState, items: newItems, total }; + const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }; saveCartToLocalStorage(updatedState); return updatedState; }; export const removeItemFromCart = (cartState: CartState, id: string): CartState => { const newItems = cartState.items.filter(item => item.product.id !== id); - const total = newItems.reduce((sum, item) => sum + item.totalPrice, 0); - const updatedState = { ...cartState, items: newItems, total }; + const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }; saveCartToLocalStorage(updatedState); return updatedState; }; @@ -79,17 +119,12 @@ export const updateCartItemQuantity = (cartState: CartState, id: string, quantit if (quantity <= 0) return null; if (!item.product.isQuantityBased) return item; - return { - ...item, - quantity, - totalPrice: calculateItemPrice(item.product, quantity, item.billingCycle, cartState.globalPeriod) - }; + return withItemVat(item.product, quantity, item.billingCycle, cartState.globalPeriod); } return item; }).filter(Boolean) as BasketItem[]; - const total = newItems.reduce((sum, item) => sum + item.totalPrice, 0); - const updatedState = { ...cartState, items: newItems, total }; + const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }; saveCartToLocalStorage(updatedState); return updatedState; }; @@ -100,24 +135,19 @@ export const clearCart = (): CartState => { }; export const setCartGlobalBillingCycle = (cartState: CartState, cycle: BillingCycle): CartState => { - const newItems = cartState.items.map(item => ({ - ...item, - billingCycle: cycle, - totalPrice: calculateItemPrice(item.product, item.quantity, cycle, cartState.globalPeriod) - })); - const total = newItems.reduce((sum, item) => sum + item.totalPrice, 0); - const updatedState = { ...cartState, globalBillingCycle: cycle, items: newItems, total }; + const newItems = cartState.items.map(item => + withItemVat(item.product, item.quantity, cycle, cartState.globalPeriod) + ); + const updatedState = { ...cartState, globalBillingCycle: cycle, items: newItems, ...calculateCartTotals(newItems) }; saveCartToLocalStorage(updatedState); return updatedState; }; export const setCartGlobalPeriod = (cartState: CartState, period: number): CartState => { - const newItems = cartState.items.map(item => ({ - ...item, - totalPrice: calculateItemPrice(item.product, item.quantity, item.billingCycle, period) - })); - const total = newItems.reduce((sum, item) => sum + item.totalPrice, 0); - const updatedState = { ...cartState, globalPeriod: period, items: newItems, total }; + const newItems = cartState.items.map(item => + withItemVat(item.product, item.quantity, item.billingCycle, period) + ); + const updatedState = { ...cartState, globalPeriod: period, items: newItems, ...calculateCartTotals(newItems) }; saveCartToLocalStorage(updatedState); return updatedState; }; @@ -142,5 +172,17 @@ const saveCartToLocalStorage = (cartState: CartState) => { // Utility: Load from localStorage export const loadCartFromLocalStorage = (): CartState => { const saved = localStorage.getItem(CART_STORAGE_KEY); - return saved ? JSON.parse(saved) : initialCartState; + if (!saved) return initialCartState; + + const parsed = JSON.parse(saved) as CartState; + const period = parsed.globalPeriod || 1; + const items = (parsed.items || []).map(item => + withItemVat(item.product, item.quantity, item.billingCycle, period) + ); + return { + ...initialCartState, + ...parsed, + items, + ...calculateCartTotals(items), + }; }; diff --git a/ui/src/views/public/Checkout.tsx b/ui/src/views/public/Checkout.tsx index 2cf64c5..38513e4 100644 --- a/ui/src/views/public/Checkout.tsx +++ b/ui/src/views/public/Checkout.tsx @@ -6,7 +6,7 @@ import { ROUTES_ENUM } from '@/routes/route.constant' import { CartState, clearCart, - initialCartState, + loadCartFromLocalStorage, removeItemFromCart, updateCartItemQuantity, } from '@/utils/cartUtils' @@ -15,14 +15,15 @@ import React, { useState, useEffect } from 'react' import { Helmet } from 'react-helmet' import { useNavigate } from 'react-router-dom' +const TENANT_DATA_STORAGE_KEY = 'tenantData' + const Checkout: React.FC = () => { const navigate = useNavigate() const [isCartOpen, setIsCartOpen] = useState(false) const { translate } = useLocalization() const [cartState, setCartState] = useState(() => { - const savedCart = localStorage.getItem('cartState') - return savedCart ? JSON.parse(savedCart) : initialCartState + return loadCartFromLocalStorage() }) useEffect(() => { @@ -30,7 +31,7 @@ const Checkout: React.FC = () => { }, [cartState]) const handleTenantSubmit = (tenantData: CustomTenantDto) => { - localStorage.setItem('tenantData', JSON.stringify(tenantData)) + localStorage.setItem(TENANT_DATA_STORAGE_KEY, JSON.stringify(tenantData)) navigate(ROUTES_ENUM.public.payment) } diff --git a/ui/src/views/public/Payment.tsx b/ui/src/views/public/Payment.tsx index 106e012..bd1fffb 100644 --- a/ui/src/views/public/Payment.tsx +++ b/ui/src/views/public/Payment.tsx @@ -10,6 +10,7 @@ import { CartState, clearCart, initialCartState, + loadCartFromLocalStorage, removeItemFromCart, updateCartItemQuantity, } from '@/utils/cartUtils' @@ -18,6 +19,9 @@ import React, { useState, useEffect } from 'react' import { Helmet } from 'react-helmet' import { useNavigate } from 'react-router-dom' +const TENANT_DATA_STORAGE_KEY = 'tenantData' +const TENANT_FORM_DRAFT_STORAGE_KEY = 'tenantFormDraft' + const Payment: React.FC = () => { const navigate = useNavigate() const [tenant, setTenant] = useState(null) @@ -26,8 +30,7 @@ const Payment: React.FC = () => { const { translate } = useLocalization() useEffect(() => { - const tenantData = localStorage.getItem('tenantData') // ✅ güncellendi - const savedCartState = localStorage.getItem('cartState') + const tenantData = localStorage.getItem(TENANT_DATA_STORAGE_KEY) // ✅ güncellendi if (tenantData) { setTenant(JSON.parse(tenantData)) @@ -35,9 +38,7 @@ const Payment: React.FC = () => { navigate(ROUTES_ENUM.public.checkout) } - if (savedCartState) { - setCartState(JSON.parse(savedCartState)) - } + setCartState(loadCartFromLocalStorage()) }, [navigate]) // Save cart state to localStorage whenever it changes @@ -62,35 +63,25 @@ const Payment: React.FC = () => { setCartState(clearCart()) } - const calculateLicenseStopTime = (startTime: Date, billingCycle: string, period: number) => { - const stopTime = new Date(startTime) - - if (billingCycle === 'monthly') { - stopTime.setMonth(stopTime.getMonth() + period) - } else { - stopTime.setFullYear(stopTime.getFullYear() + period) - } - - return stopTime - } - const handlePaymentComplete = async (paymentData: Record) => { if (!tenant) return try { const billingCycle = cartState.globalBillingCycle || 'yearly' const period = cartState.globalPeriod > 0 ? cartState.globalPeriod : 1 - const licenseStartTime = new Date() - const licenseStopTime = calculateLicenseStopTime(licenseStartTime, billingCycle, period) + const licenseStartTime = new Date(paymentData.licenseStartTime as string) + const licenseEndTime = new Date(paymentData.licenseEndTime as string) const orderData = { tenant, items: cartState.items, + isExisting: paymentData.isExisting, billingCycle, period, licenseStartTime: licenseStartTime.toISOString(), - licenseStopTime: licenseStopTime.toISOString(), - subtotal: cartState.total, + licenseEndTime: licenseEndTime.toISOString(), + subtotal: cartState.subtotal, + vatTotal: cartState.vatTotal, commission: paymentData.commission, total: paymentData.total, paymentMethodId: paymentData.paymentMethodId, @@ -109,8 +100,16 @@ const Payment: React.FC = () => { if (result.success && result.orderId) { localStorage.setItem('orderId', result.orderId) // ✅ güncellendi + localStorage.setItem( + 'orderSuccessData', + JSON.stringify({ + orderId: result.orderId, + ...orderData, + }), + ) setCartState(clearCart()) - localStorage.removeItem('tenantData') // ✅ temizlik + localStorage.removeItem(TENANT_DATA_STORAGE_KEY) // ✅ temizlik + localStorage.removeItem(TENANT_FORM_DRAFT_STORAGE_KEY) localStorage.removeItem('cartState') // ✅ temizlik navigate(ROUTES_ENUM.public.success) } else { diff --git a/ui/src/views/public/Products.tsx b/ui/src/views/public/Products.tsx index 566a45d..0bfb154 100644 --- a/ui/src/views/public/Products.tsx +++ b/ui/src/views/public/Products.tsx @@ -7,7 +7,7 @@ import { ROUTES_ENUM } from '@/routes/route.constant' import { CartState, clearCart, - initialCartState, + loadCartFromLocalStorage, removeItemFromCart, setCartGlobalBillingCycle, setCartGlobalPeriod, @@ -24,9 +24,7 @@ const Products: React.FC = () => { const { translate } = useLocalization() const [cartState, setCartState] = useState(() => { - // Load cart state from localStorage if available - const savedCart = localStorage.getItem('cartState') - return savedCart ? JSON.parse(savedCart) : initialCartState + return loadCartFromLocalStorage() }) const navigate = useNavigate() diff --git a/ui/src/views/public/Success.tsx b/ui/src/views/public/Success.tsx index 58a1ce2..248620a 100644 --- a/ui/src/views/public/Success.tsx +++ b/ui/src/views/public/Success.tsx @@ -1,4 +1,4 @@ -import { OrderSuccess } from '@/components/orders/OrderSuccess' +import { OrderSuccess, OrderSuccessData } from '@/components/orders/OrderSuccess' import { Loading } from '@/components/shared' import { APP_NAME } from '@/constants/app.constant' import { ROUTES_ENUM } from '@/routes/route.constant' @@ -10,15 +10,21 @@ import { useNavigate } from 'react-router-dom' const Success: React.FC = () => { const navigate = useNavigate() const [orderId, setOrderId] = useState('') + const [order, setOrder] = useState(null) const { translate } = useLocalization() useEffect(() => { // Get order ID from local storage const orderIdData = localStorage.getItem('orderId') + const orderSuccessData = localStorage.getItem('orderSuccessData') if (orderIdData) { setOrderId(orderIdData) + if (orderSuccessData) { + setOrder(JSON.parse(orderSuccessData)) + } // Clear order ID from local storage localStorage.removeItem('orderId') + localStorage.removeItem('orderSuccessData') } else { navigate(ROUTES_ENUM.public.home) } @@ -56,7 +62,7 @@ const Success: React.FC = () => {
- +
)