Sales Order güncellemeleri
This commit is contained in:
parent
5c9c99206c
commit
4fd22fe682
34 changed files with 1399 additions and 578 deletions
|
|
@ -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 " +
|
||||
|
|
|
|||
|
|
@ -8,15 +8,17 @@ namespace Sozsoft.Platform.Public;
|
|||
public class OrderDto : EntityDto<Guid>
|
||||
{
|
||||
public CustomTenantDto Tenant { get; set; }
|
||||
|
||||
public List<OrderItemDto> 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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public class ProductDto : EntityDto<Guid>
|
|||
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 =>
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Tenant, CustomTenantDto>
|
||||
{
|
||||
[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<Tenant, CustomTe
|
|||
|
||||
public override void AfterMap(Tenant source, CustomTenantDto destination)
|
||||
{
|
||||
destination.OrganizationName = source.GetOrganizationName();
|
||||
destination.Founder = source.GetFounder();
|
||||
destination.VknTckn = source.GetVknTckn();
|
||||
destination.TaxOffice = source.GetTaxOffice();
|
||||
destination.Country = source.GetCountry();
|
||||
destination.City = source.GetCity();
|
||||
destination.District = source.GetDistrict();
|
||||
destination.Township = source.GetTownship();
|
||||
destination.Address1 = source.GetAddress1();
|
||||
destination.Address2 = source.GetAddress2();
|
||||
destination.PostalCode = source.GetPostalCode();
|
||||
destination.MobileNumber = source.GetMobileNumber();
|
||||
destination.PhoneNumber = source.GetPhoneNumber();
|
||||
destination.FaxNumber = source.GetFaxNumber();
|
||||
destination.Email = source.GetEmail();
|
||||
destination.Website = source.GetWebsite();
|
||||
destination.MenuGroup = source.GetMenuGroup();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ using Sozsoft.Platform.Data.Seeds;
|
|||
using Sozsoft.Sender.Mail;
|
||||
using Volo.Abp.Settings;
|
||||
using Sozsoft.Platform.Demos;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using System.Text.Json;
|
||||
using Volo.Abp.Identity;
|
||||
|
|
@ -463,15 +465,9 @@ public class PublicAppService : PlatformAppService
|
|||
|
||||
public async Task<OrderDto> 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<string>();
|
||||
if (!order.Email.IsNullOrWhiteSpace())
|
||||
{
|
||||
recipients.Add(order.Email.Trim());
|
||||
}
|
||||
|
||||
var notificationEmails = await _settingProvider.GetOrNullAsync("App.SiteManagement.General.NewTenantNotificationEmails");
|
||||
if (!notificationEmails.IsNullOrWhiteSpace())
|
||||
{
|
||||
recipients.AddRange(notificationEmails
|
||||
.Split(new[] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(email => email.Trim())
|
||||
.Where(email => !email.IsNullOrWhiteSpace()));
|
||||
}
|
||||
|
||||
recipients = recipients
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
if (recipients.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var senderName = await _settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromDisplayName);
|
||||
var senderEmail = await _settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromAddress);
|
||||
KeyValuePair<string, string>? sender = null;
|
||||
if (!senderEmail.IsNullOrWhiteSpace())
|
||||
{
|
||||
sender = new KeyValuePair<string, string>(senderName ?? string.Empty, senderEmail);
|
||||
}
|
||||
|
||||
var body = BuildOrderEmailBody(order);
|
||||
var subject = $"{PlatformConsts.AppName} : Yeni Sipariş #{order.Id}";
|
||||
|
||||
foreach (var recipient in recipients)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _emailSender.SendEmailAsync(
|
||||
recipient,
|
||||
sender,
|
||||
new { },
|
||||
body,
|
||||
subject,
|
||||
null,
|
||||
true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Order email could not be sent to {Recipient}", recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildOrderEmailBody(Order order)
|
||||
{
|
||||
var itemRows = string.Join(string.Empty, order.Items.Select(item => $@"
|
||||
<tr>
|
||||
<td style=""padding:12px;border-bottom:1px solid #e5e7eb;font-weight:600;color:#111827;"">{Html(item.ProductName)}</td>
|
||||
<td style=""padding:12px;border-bottom:1px solid #e5e7eb;text-align:right;color:#374151;"">{item.Quantity}</td>
|
||||
<td style=""padding:12px;border-bottom:1px solid #e5e7eb;text-align:right;color:#374151;"">{BillingCycleLabel(item.BillingCycle)}</td>
|
||||
<td style=""padding:12px;border-bottom:1px solid #e5e7eb;text-align:right;color:#374151;"">%{item.VatRate * 100:0.##} / {FormatPrice(item.VatAmount)}</td>
|
||||
<td style=""padding:12px;border-bottom:1px solid #e5e7eb;text-align:right;font-weight:700;color:#111827;"">{FormatPrice(item.TotalPrice)}</td>
|
||||
</tr>"));
|
||||
|
||||
return $@"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body style=""margin:0;background:#f3f4f6;font-family:Arial,Helvetica,sans-serif;color:#111827;"">
|
||||
<div style=""max-width:920px;margin:0 auto;padding:24px;"">
|
||||
<div style=""background:#ffffff;border:1px solid #e5e7eb;border-radius:14px;overflow:hidden;"">
|
||||
<div style=""display:flex;justify-content:space-between;gap:16px;padding:24px;background:#f9fafb;border-bottom:1px solid #e5e7eb;"">
|
||||
<div>
|
||||
<div style=""font-size:22px;font-weight:800;"">Sipariş Oluşturuldu</div>
|
||||
<div style=""margin-top:6px;color:#4b5563;"">Sipariş No: <strong>#{order.Id}</strong></div>
|
||||
</div>
|
||||
<div style=""text-align:right;color:#4b5563;"">
|
||||
<div style=""font-size:12px;text-transform:uppercase;letter-spacing:.08em;"">Invoice</div>
|
||||
<div style=""font-weight:700;color:#111827;"">{FormatDate(DateTime.Now)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style=""padding:24px;"">
|
||||
<div style=""display:grid;grid-template-columns:1fr 1fr;gap:20px;"">
|
||||
<div style=""border:1px solid #e5e7eb;border-radius:12px;padding:18px;"">
|
||||
<div style=""font-size:18px;font-weight:800;margin-bottom:14px;"">Customer Information</div>
|
||||
<div style=""line-height:1.7;color:#374151;"">
|
||||
<strong>{Html(order.OrganizationName)}</strong><br />
|
||||
{Html(order.Email)}<br />
|
||||
{Html(FormatPhoneNumber(order.PhoneNumber ?? order.MobileNumber))}<br />
|
||||
{Html(order.Address1)}<br />
|
||||
{Html(order.District)} / {Html(order.City)} / {Html(order.Country)}
|
||||
</div>
|
||||
</div>
|
||||
<div style=""border:1px solid #e5e7eb;border-radius:12px;padding:18px;"">
|
||||
<div style=""font-size:18px;font-weight:800;margin-bottom:14px;"">License Information</div>
|
||||
<div style=""line-height:1.8;color:#374151;"">
|
||||
<div><strong>License Start Time:</strong> {FormatDate(order.LicenseStartTime)}</div>
|
||||
<div><strong>License End Time:</strong> {FormatDate(order.LicenseEndTime)}</div>
|
||||
<div><strong>Billing Cycle:</strong> {BillingCycleLabel(order.BillingCycle)}</div>
|
||||
<div><strong>Period:</strong> {order.Period}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table style=""width:100%;border-collapse:collapse;margin-top:24px;border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;"">
|
||||
<thead>
|
||||
<tr style=""background:#f9fafb;color:#6b7280;font-size:12px;text-transform:uppercase;letter-spacing:.06em;"">
|
||||
<th style=""padding:12px;text-align:left;"">Product</th>
|
||||
<th style=""padding:12px;text-align:right;"">Qty</th>
|
||||
<th style=""padding:12px;text-align:right;"">Cycle</th>
|
||||
<th style=""padding:12px;text-align:right;"">VAT</th>
|
||||
<th style=""padding:12px;text-align:right;"">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{itemRows}</tbody>
|
||||
</table>
|
||||
|
||||
<div style=""max-width:360px;margin:24px 0 0 auto;line-height:1.9;color:#374151;"">
|
||||
<div style=""display:flex;justify-content:space-between;""><span>Subtotal</span><strong>{FormatPrice(order.Subtotal)}</strong></div>
|
||||
<div style=""display:flex;justify-content:space-between;""><span>VAT</span><strong>{FormatPrice(order.VatTotal)}</strong></div>
|
||||
<div style=""display:flex;justify-content:space-between;""><span>Commission</span><strong>{FormatPrice(order.Commission)}</strong></div>
|
||||
<div style=""display:flex;justify-content:space-between;border-top:1px solid #e5e7eb;margin-top:8px;padding-top:10px;font-size:18px;color:#111827;""><span>Total</span><strong>{FormatPrice(order.Total)}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
}
|
||||
|
||||
private static string BillingCycleLabel(string billingCycle)
|
||||
{
|
||||
return billingCycle == "monthly" ? "Aylık" : "Yıllık";
|
||||
}
|
||||
|
||||
private static string NormalizePhoneNumber(string value, string country)
|
||||
{
|
||||
if (value.IsNullOrWhiteSpace())
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (country == "Türkiye" || digits.StartsWith("90"))
|
||||
{
|
||||
if (digits.StartsWith("90") && digits.Length > 10)
|
||||
{
|
||||
digits = digits[^10..];
|
||||
}
|
||||
|
||||
if (digits.StartsWith("0") && digits.Length > 10)
|
||||
{
|
||||
digits = digits[^10..];
|
||||
}
|
||||
}
|
||||
|
||||
return digits;
|
||||
}
|
||||
|
||||
private static string FormatPhoneNumber(string value)
|
||||
{
|
||||
var digits = NormalizePhoneNumber(value, string.Empty);
|
||||
if (digits?.Length != 10)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return $"({digits[..3]}) {digits[3..6]}-{digits[6..]}";
|
||||
}
|
||||
|
||||
private static string FormatDate(DateTime? value)
|
||||
{
|
||||
return value.HasValue ? value.Value.ToString("dd.MM.yyyy") : "-";
|
||||
}
|
||||
|
||||
private static string FormatPrice(decimal value)
|
||||
{
|
||||
return string.Format(new System.Globalization.CultureInfo("tr-TR"), "{0:C2}", value);
|
||||
}
|
||||
|
||||
private static string Html(string value)
|
||||
{
|
||||
return WebUtility.HtmlEncode(value ?? string.Empty);
|
||||
}
|
||||
|
||||
public async Task<AboutDto> GetAboutAsync()
|
||||
{
|
||||
var entity = await _aboutRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(About));
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public class PlatformTenantAppService : TenantAppService, IPlatformTenantAppServ
|
|||
}
|
||||
|
||||
var dto = ObjectMapper.Map<Tenant, CustomTenantDto>(tenant);
|
||||
dto.IsActive = tenant.GetIsActive();
|
||||
dto.Name = tenant.Name;
|
||||
dto.OrganizationName = tenant.GetOrganizationName();
|
||||
dto.Founder = tenant.GetFounder();
|
||||
dto.VknTckn = tenant.GetVknTckn();
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<EditingFormDto>
|
||||
{
|
||||
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<dynamic>() {
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ namespace Sozsoft.Platform.Entities;
|
|||
|
||||
public class Order : FullAuditedEntity<Guid>
|
||||
{
|
||||
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<Guid>
|
|||
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<OrderItem> 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<OrderItem> Items { get; set; } = new();
|
||||
|
||||
public Order()
|
||||
{
|
||||
}
|
||||
|
||||
public Order(Guid id)
|
||||
: base(id)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class OrderItem : FullAuditedEntity<Guid>
|
||||
|
|
@ -54,7 +60,11 @@ public class OrderItem : FullAuditedEntity<Guid>
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ public class Product : FullAuditedEntity<Guid>
|
|||
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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Sozsoft.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20260706213526_Initial")]
|
||||
[Migration("20260707105700_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -4137,7 +4137,9 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("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<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
|
|
@ -4158,10 +4160,10 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<DateTime?>("LicenseStartTime")
|
||||
b.Property<DateTime?>("LicenseEndTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("LicenseStopTime")
|
||||
b.Property<DateTime?>("LicenseStartTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("MenuGroup")
|
||||
|
|
@ -4172,10 +4174,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasMaxLength(20)
|
||||
.HasColumnType("nvarchar(20)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("OrganizationName")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
|
@ -4208,10 +4206,17 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<decimal>("Total")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("VatTotal")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<long?>("VknTckn")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
|
|
@ -4267,6 +4272,15 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Property<Guid>("OrderId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Period")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<Guid>("ProductId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
|
|
@ -4282,6 +4296,14 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("VatAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("VatRate")
|
||||
.HasPrecision(5, 3)
|
||||
.HasColumnType("decimal(5,3)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OrderId");
|
||||
|
|
@ -4409,6 +4431,12 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("VatRate")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasPrecision(5, 3)
|
||||
.HasColumnType("decimal(5,3)")
|
||||
.HasDefaultValue(0.20m);
|
||||
|
||||
b.Property<decimal?>("YearlyPrice")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
|
@ -1571,9 +1571,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||
IsExisting = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
OrganizationName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
||||
Founder = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
||||
VknTckn = table.Column<long>(type: "bigint", nullable: true),
|
||||
|
|
@ -1590,11 +1588,14 @@ namespace Sozsoft.Platform.Migrations
|
|||
Email = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
||||
Website = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
||||
MenuGroup = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
IsExisting = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
IsActive = table.Column<bool>(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<string>(type: "nvarchar(32)", maxLength: 32, nullable: true),
|
||||
Period = table.Column<int>(type: "int", nullable: false, defaultValue: 1),
|
||||
LicenseStartTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LicenseStopTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LicenseEndTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
Subtotal = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
VatTotal = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Commission = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Total = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
PaymentMethodId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
|
|
@ -1645,6 +1646,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
Order = table.Column<int>(type: "int", nullable: false),
|
||||
MonthlyPrice = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
|
||||
YearlyPrice = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
|
||||
VatRate = table.Column<decimal>(type: "decimal(5,3)", precision: 5, scale: 3, nullable: false, defaultValue: 0.20m),
|
||||
IsQuantityBased = table.Column<bool>(type: "bit", nullable: false),
|
||||
ImageUrl = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
|
|
@ -2890,7 +2892,11 @@ namespace Sozsoft.Platform.Migrations
|
|||
ProductId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ProductName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
BillingCycle = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
Period = table.Column<int>(type: "int", nullable: false, defaultValue: 1),
|
||||
Quantity = table.Column<int>(type: "int", nullable: false),
|
||||
Price = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
VatRate = table.Column<decimal>(type: "decimal(5,3)", precision: 5, scale: 3, nullable: false),
|
||||
VatAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
TotalPrice = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
|
|
@ -4134,7 +4134,9 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("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<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
|
|
@ -4155,10 +4157,10 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<DateTime?>("LicenseStartTime")
|
||||
b.Property<DateTime?>("LicenseEndTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("LicenseStopTime")
|
||||
b.Property<DateTime?>("LicenseStartTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("MenuGroup")
|
||||
|
|
@ -4169,10 +4171,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasMaxLength(20)
|
||||
.HasColumnType("nvarchar(20)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("OrganizationName")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
|
@ -4205,10 +4203,17 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<decimal>("Total")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("VatTotal")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<long?>("VknTckn")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
|
|
@ -4264,6 +4269,15 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Property<Guid>("OrderId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Period")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<Guid>("ProductId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
|
|
@ -4279,6 +4293,14 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("VatAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("VatRate")
|
||||
.HasPrecision(5, 3)
|
||||
.HasColumnType("decimal(5,3)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OrderId");
|
||||
|
|
@ -4406,6 +4428,12 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("VatRate")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasPrecision(5, 3)
|
||||
.HasColumnType("decimal(5,3)")
|
||||
.HasDefaultValue(0.20m);
|
||||
|
||||
b.Property<decimal?>("YearlyPrice")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
|
|
|||
|
|
@ -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<FormReportField> fields,
|
||||
IReadOnlyList<FormGroup> formGroups,
|
||||
IDictionary<string, object> row,
|
||||
IReadOnlyList<SubReportSource> subReports,
|
||||
IStringLocalizer<PlatformResource> 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<FormGroup> formGroups)
|
||||
{
|
||||
return formGroups.Any(x => GetSafeColumnCount(x.ColumnCount) >= LandscapeColumnThreshold);
|
||||
}
|
||||
|
||||
private static async Task<List<SubReportSource>> CreateSubReportsAsync(
|
||||
IReadOnlyList<(SubFormDto Form, string Filter)> subForms)
|
||||
{
|
||||
var reports = new List<SubReportSource>();
|
||||
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<PlatformResource> 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<FormReportField> 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,
|
||||
|
|
|
|||
|
|
@ -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<ReportColumn> visibleFields,
|
||||
IStringLocalizer<PlatformResource> localizer,
|
||||
IReadOnlyDictionary<string, float> stateWidths,
|
||||
bool isSubReport)
|
||||
IReadOnlyDictionary<string, float> 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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<CartProps> = ({
|
|||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm text-gray-600 dark:text-gray-300">
|
||||
{item.product.isQuantityBased && item.quantity > 1 && (
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100">{item.quantity}x - </span>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{item.quantity}x -{' '}
|
||||
</span>
|
||||
)}
|
||||
{item.billingCycle === 'monthly'
|
||||
? translate('::Public.products.billingcycle.monthly')
|
||||
|
|
@ -135,7 +139,9 @@ export const Cart: React.FC<CartProps> = ({
|
|||
variant="default"
|
||||
size="xs"
|
||||
/>
|
||||
<span className="w-8 text-center text-sm dark:text-gray-100">{item.quantity}</span>
|
||||
<span className="w-2 text-center text-sm dark:text-gray-100">
|
||||
{item.quantity}
|
||||
</span>
|
||||
<Button
|
||||
onClick={() => updateQuantity(item.product.id, item.quantity + 1)}
|
||||
icon={<FaPlus className="w-3 h-3" />}
|
||||
|
|
@ -144,10 +150,10 @@ export const Cart: React.FC<CartProps> = ({
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-2 font-semibold text-blue-600">
|
||||
{formatPrice(item.totalPrice)}
|
||||
<div className="font-semibold text-blue-600">
|
||||
{formatPrice(item.totalPrice)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -157,21 +163,26 @@ export const Cart: React.FC<CartProps> = ({
|
|||
|
||||
{cartState.items.length > 0 && (
|
||||
<div className="border-t border-gray-200 p-6 dark:border-gray-700">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<span className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
{translate('::App.Listform.ListformField.Total')}
|
||||
</span>
|
||||
<span className="text-xl font-bold text-blue-600">
|
||||
{formatPrice(cartState.total)}
|
||||
</span>
|
||||
<div className="space-y-2 mb-4">
|
||||
<div className="flex justify-between text-sm text-gray-600 dark:text-gray-300">
|
||||
<span>{translate('::App.Listform.ListformField.Subtotal')}</span>
|
||||
<span>{formatPrice(cartState.subtotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm text-gray-600 dark:text-gray-300">
|
||||
<span>{translate('::Public.products.kdv')}</span>
|
||||
<span>{formatPrice(cartState.vatTotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pt-2 border-t border-gray-200 dark:border-gray-700">
|
||||
<span className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
{translate('::App.Listform.ListformField.Total')}
|
||||
</span>
|
||||
<span className="text-xl font-bold text-blue-600">
|
||||
{formatPrice(cartState.total)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={onCheckout}
|
||||
block
|
||||
variant="solid"
|
||||
size="md"
|
||||
>
|
||||
<Button onClick={onCheckout} block variant="solid" size="md">
|
||||
{translate('::Public.cart.checkout')}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<OrderSuccessProps> = ({ orderId }) => {
|
||||
export const OrderSuccess: React.FC<OrderSuccessProps> = ({ 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 (
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<div className="bg-white rounded-xl shadow-lg border border-gray-200 p-8 dark:border-gray-700 dark:bg-gray-900 dark:shadow-gray-950/40">
|
||||
<div className="mb-6">
|
||||
<FaCheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{translate('::Public.order.success.title')}
|
||||
</h2>
|
||||
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
{translate('::Public.order.success.number')}{' '}
|
||||
<span className="font-semibold text-blue-600">#{orderId}</span>
|
||||
</p>
|
||||
<div className="max-w-6xl mx-auto pb-7">
|
||||
<div className="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden dark:border-gray-700 dark:bg-gray-900 dark:shadow-gray-950/40">
|
||||
<div className="flex flex-col gap-4 border-b border-gray-200 bg-gray-50 px-6 py-5 dark:border-gray-700 dark:bg-gray-800/70 md:flex-row md:items-center md:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<FaCheckCircle className="h-10 w-10 text-green-500" />
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{translate('::Public.order.success.title')}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
{translate('::Public.order.success.number')}{' '}
|
||||
<span className="font-semibold text-blue-600">#{orderId}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-left md:text-right">
|
||||
<div className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
Invoice
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{formatDate(new Date().toISOString())}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-6 dark:border-green-900/60 dark:bg-green-950/30">
|
||||
<h3 className="font-semibold text-green-800 mb-2 dark:text-green-300">
|
||||
{translate('::Public.order.success.nextSteps')}
|
||||
</h3>
|
||||
<ul className="text-sm text-green-700 space-y-1 text-left dark:text-green-200">
|
||||
<li>• {translate('::Public.order.success.step1')}</li>
|
||||
<li>• {translate('::Public.order.success.step2')}</li>
|
||||
<li>• {translate('::Public.order.success.step3')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
{order && (
|
||||
<div className="p-6">
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<section className="rounded-lg border border-gray-200 p-5 dark:border-gray-700">
|
||||
<h3 className="mb-4 text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
Customer Information
|
||||
</h3>
|
||||
<div className="space-y-3 text-sm text-gray-700 dark:text-gray-300">
|
||||
<div className="flex items-center gap-3">
|
||||
<FaBuilding className="h-4 w-4 text-gray-500" />
|
||||
<span className="font-medium">{order.tenant.organizationName}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<FaEnvelope className="h-4 w-4 text-gray-500" />
|
||||
<span>{order.tenant.email}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<FaPhone className="h-4 w-4 text-gray-500" />
|
||||
<span>{order.tenant.phoneNumber || order.tenant.mobileNumber}</span>
|
||||
</div>
|
||||
<div className="pt-2 text-gray-600 dark:text-gray-400">
|
||||
<div>{order.tenant.address1}</div>
|
||||
<div>
|
||||
{order.tenant.district} / {order.tenant.city} / {order.tenant.country}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 items-center justify-center">
|
||||
<section className="rounded-lg border border-gray-200 p-5 dark:border-gray-700">
|
||||
<h3 className="mb-4 flex items-center gap-2 text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
<FaCalendarAlt className="h-5 w-5 text-green-600" />
|
||||
License Information
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 gap-3 text-sm text-gray-700 dark:text-gray-300 sm:grid-cols-2">
|
||||
<div className="rounded-lg bg-gray-50 p-3 dark:bg-gray-800/70">
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">License Start Time</div>
|
||||
<div className="font-semibold">{formatDate(order.licenseStartTime)}</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-gray-50 p-3 dark:bg-gray-800/70">
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">License End Time</div>
|
||||
<div className="font-semibold">{formatDate(order.licenseEndTime)}</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-gray-50 p-3 dark:bg-gray-800/70">
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">Billing Cycle</div>
|
||||
<div className="font-semibold">{billingCycleLabel}</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-gray-50 p-3 dark:bg-gray-800/70">
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">Period</div>
|
||||
<div className="font-semibold">{order.period}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section className="mt-6 overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<div className="grid grid-cols-12 bg-gray-50 px-4 py-3 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:bg-gray-800/70 dark:text-gray-400">
|
||||
<div className="col-span-3">Product</div>
|
||||
<div className="col-span-2 text-right">Price</div>
|
||||
<div className="col-span-1 text-right">Period</div>
|
||||
<div className="col-span-1 text-right">Qty</div>
|
||||
<div className="col-span-1 text-right">Cycle</div>
|
||||
<div className="col-span-2 text-right">{translate('::Public.products.kdv')}</div>
|
||||
<div className="col-span-2 text-right">Amount</div>
|
||||
</div>
|
||||
{order.items.map((item, index) => (
|
||||
<div
|
||||
key={`${item.product.id}-${index}`}
|
||||
className="grid grid-cols-12 border-t border-gray-200 px-4 py-4 text-sm text-gray-700 dark:border-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<div className="col-span-3 font-medium text-gray-900 dark:text-gray-100">
|
||||
{translate('::' + item.product.name)}
|
||||
</div>
|
||||
<div className="col-span-2 text-right">{formatPrice(getItemPrice(item))}</div>
|
||||
<div className="col-span-1 text-right">{order.period}</div>
|
||||
<div className="col-span-1 text-right">{item.quantity}</div>
|
||||
<div className="col-span-1 text-right">
|
||||
{item.billingCycle === 'monthly' ? 'Aylık' : 'Yıllık'}
|
||||
</div>
|
||||
<div className="col-span-2 text-right">
|
||||
%{((item.vatRate ?? 0) * 100).toFixed(0)} / {formatPrice(item.vatAmount ?? 0)}
|
||||
</div>
|
||||
<div className="col-span-2 text-right font-semibold">
|
||||
{formatPrice(item.totalPrice)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<div className="mt-6 flex justify-end">
|
||||
<div className="w-full max-w-sm space-y-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<div className="flex justify-between">
|
||||
<span>{translate('::Public.payment.summary.subtotal')}</span>
|
||||
<span>{formatPrice(order.subtotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{translate('::Public.products.kdv')}{vatRateLabel}</span>
|
||||
<span>{formatPrice(order.vatTotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{translate('::Public.payment.installments.commission')}</span>
|
||||
<span>{formatPrice(order.commission)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-t border-gray-200 pt-3 text-lg font-bold text-gray-900 dark:border-gray-700 dark:text-gray-100">
|
||||
<span>{translate('::App.Listform.ListformField.Total')}</span>
|
||||
<span className="text-blue-600">{formatPrice(order.total)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t border-gray-200 px-6 py-5 dark:border-gray-700">
|
||||
<Button
|
||||
onClick={() => navigate(ROUTES_ENUM.public.home)}
|
||||
icon={<FaArrowLeft className="w-4 h-4" />}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import {
|
||||
FaCreditCard,
|
||||
FaLock,
|
||||
FaArrowLeft,
|
||||
FaUser,
|
||||
FaBuilding,
|
||||
FaMailBulk,
|
||||
FaPhone,
|
||||
FaMapMarkerAlt,
|
||||
FaMap,
|
||||
FaGlobe,
|
||||
FaMoneyBillWave,
|
||||
FaDollarSign,
|
||||
FaUserPlus,
|
||||
} from 'react-icons/fa'
|
||||
import { FaCreditCard, FaLock, FaArrowLeft, FaCalendarAlt, FaClock } from 'react-icons/fa'
|
||||
import {
|
||||
BillingCycle,
|
||||
BasketItem,
|
||||
|
|
@ -27,6 +13,8 @@ import { Button } from '@/components/ui'
|
|||
|
||||
interface CartState {
|
||||
items: BasketItem[]
|
||||
subtotal: number
|
||||
vatTotal: number
|
||||
total: number
|
||||
globalBillingCycle: BillingCycle
|
||||
globalPeriod: number
|
||||
|
|
@ -39,12 +27,7 @@ interface PaymentFormProps {
|
|||
cartState: CartState
|
||||
}
|
||||
|
||||
export const PaymentForm: React.FC<PaymentFormProps> = ({
|
||||
tenant,
|
||||
onBack,
|
||||
onComplete,
|
||||
cartState,
|
||||
}) => {
|
||||
export const PaymentForm: React.FC<PaymentFormProps> = ({ onBack, onComplete, cartState }) => {
|
||||
const defaultPaymentMethod = 'Kredi Kartı'
|
||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string>(defaultPaymentMethod)
|
||||
const [selectedInstallment, setSelectedInstallment] = useState<InstallmentOptionDto>()
|
||||
|
|
@ -54,6 +37,9 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
|
|||
cvv: '',
|
||||
cardName: '',
|
||||
})
|
||||
const [licenseStartTime, setLicenseStartTime] = useState(() =>
|
||||
new Date().toISOString().slice(0, 10),
|
||||
)
|
||||
|
||||
const { translate } = useLocalization()
|
||||
const [paymentMethods, setPaymentMethods] = useState<PaymentMethodDto[]>([])
|
||||
|
|
@ -92,6 +78,9 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
|
|||
}
|
||||
|
||||
const finalTotal = cartState.total + commission
|
||||
const period = cartState.globalPeriod > 0 ? cartState.globalPeriod : 1
|
||||
const billingCycle = cartState.globalBillingCycle || 'yearly'
|
||||
const licenseEndTime = calculateLicenseEndTime(new Date(licenseStartTime), billingCycle, period)
|
||||
|
||||
const formatPrice = (price: number) =>
|
||||
new Intl.NumberFormat('tr-TR', {
|
||||
|
|
@ -100,10 +89,20 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
|
|||
minimumFractionDigits: 2,
|
||||
}).format(price)
|
||||
|
||||
const vatRateLabel = (() => {
|
||||
const rates = Array.from(
|
||||
new Set(cartState.items.map((item) => Math.round((item.vatRate ?? 0) * 100))),
|
||||
).filter((rate) => rate > 0)
|
||||
|
||||
return rates.length > 0 ? ` (${rates.map((rate) => `%${rate}`).join(', ')})` : ''
|
||||
})()
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
onComplete({
|
||||
...paymentData,
|
||||
licenseStartTime: new Date(licenseStartTime).toISOString(),
|
||||
licenseEndTime: licenseEndTime.toISOString(),
|
||||
paymentMethodId: selectedMethod?.id,
|
||||
installment:
|
||||
selectedPaymentMethod === defaultPaymentMethod
|
||||
|
|
@ -118,136 +117,75 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
|
|||
setPaymentData((prev) => ({ ...prev, [field]: value }))
|
||||
}
|
||||
|
||||
function calculateLicenseEndTime(
|
||||
startTime: Date,
|
||||
selectedBillingCycle: string,
|
||||
selectedPeriod: number,
|
||||
) {
|
||||
const stopTime = new Date(startTime)
|
||||
|
||||
if (selectedBillingCycle === 'monthly') {
|
||||
stopTime.setMonth(stopTime.getMonth() + selectedPeriod)
|
||||
} else {
|
||||
stopTime.setFullYear(stopTime.getFullYear() + selectedPeriod)
|
||||
}
|
||||
|
||||
return stopTime
|
||||
}
|
||||
|
||||
const formatDate = (date: Date) =>
|
||||
new Intl.DateTimeFormat('tr-TR', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
}).format(date)
|
||||
|
||||
const billingCycleLabel = billingCycle === 'monthly' ? 'Aylık' : 'Yıllık'
|
||||
|
||||
return (
|
||||
<div className="mx-auto px-4 lg:px-8 mt-5">
|
||||
{loading ? (
|
||||
<div className="text-center py-12 text-gray-500">
|
||||
{translate('::App.Loading')}
|
||||
</div>
|
||||
<div className="text-center py-12 text-gray-500">{translate('::App.Loading')}</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="flex flex-col lg:flex-row gap-6">
|
||||
{/* 3 Sütun: Ödeme Yöntemi | Taksit Seçenekleri | Sipariş Özeti */}
|
||||
<div className="w-full lg:w-1/3 flex flex-col gap-6">
|
||||
<div className="bg-white rounded-xl shadow border p-6 dark:border-gray-700 dark:bg-gray-900 dark:shadow-gray-950/40">
|
||||
{tenant && (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||
<FaUser className="w-5 h-5 text-green-600 mr-2" />{' '}
|
||||
{translate('::Public.payment.customerInfo')}
|
||||
</h2>
|
||||
<div className="pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<div className="grid grid-cols-1 gap-y-3 text-sm text-gray-700 dark:text-gray-300">
|
||||
<div className="flex items-center gap-2">
|
||||
<FaUser className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Public.payment.customer.code')}
|
||||
</span>
|
||||
<span>{tenant.name}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaUser className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::LoginPanel.Profil')}
|
||||
</span>
|
||||
<span>{tenant.founder}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaBuilding className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Public.payment.customer.company')}
|
||||
</span>
|
||||
<span>{tenant.organizationName}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaMailBulk className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Abp.Account.EmailAddress')}
|
||||
</span>
|
||||
<span>{tenant.email}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaPhone className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Abp.Identity.User.UserInformation.PhoneNumber')}
|
||||
</span>
|
||||
<span>{tenant.phoneNumber}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-2">
|
||||
<FaMapMarkerAlt className="w-4 h-4 text-gray-500 mt-0.5" />
|
||||
<div>
|
||||
<span className="font-medium">
|
||||
{translate('::App.Address')}:
|
||||
</span>
|
||||
<div>{tenant.address1}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaGlobe className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Public.payment.customer.country')}
|
||||
</span>
|
||||
<span>{tenant.country}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaGlobe className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Public.common.city')}
|
||||
</span>
|
||||
<span>{tenant.city}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaMap className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Public.payment.customer.district')}
|
||||
</span>
|
||||
<span>{tenant.district}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaMapMarkerAlt className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Public.payment.customer.postalCode')}:
|
||||
</span>
|
||||
<span>{tenant.postalCode}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaMoneyBillWave className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Public.contact.taxOffice')}:
|
||||
</span>
|
||||
<span>{tenant.taxOffice}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaDollarSign className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Public.contact.taxNumber')}:
|
||||
</span>
|
||||
<span>{tenant.vknTckn}</span>
|
||||
</div>
|
||||
|
||||
{tenant.reference && (
|
||||
<div className="flex items-center gap-2">
|
||||
<FaUserPlus className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
{translate('::Public.payment.customer.reference')}:
|
||||
</span>
|
||||
<span>{tenant.reference}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center text-gray-900 dark:text-gray-100">
|
||||
<FaCalendarAlt className="w-5 h-5 text-green-600 mr-2" />
|
||||
Lisans Bilgileri
|
||||
</h2>
|
||||
<div className="space-y-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
License Start Time
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
required
|
||||
value={licenseStartTime}
|
||||
onChange={(e) => setLicenseStartTime(e.target.value)}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 text-sm text-gray-700 dark:text-gray-300">
|
||||
<div className="flex items-center justify-between rounded-lg bg-gray-50 px-4 py-3 dark:bg-gray-800/70">
|
||||
<span className="font-medium">Period</span>
|
||||
<span>{period}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex items-center justify-between rounded-lg bg-gray-50 px-4 py-3 dark:bg-gray-800/70">
|
||||
<span className="font-medium">Billing Cycle</span>
|
||||
<span>{billingCycleLabel}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-lg bg-blue-50 px-4 py-3 text-blue-800 dark:bg-blue-950/40 dark:text-blue-200">
|
||||
<span className="font-medium flex items-center gap-2">
|
||||
<FaClock className="h-4 w-4" />
|
||||
License End Time
|
||||
</span>
|
||||
<span>{formatDate(licenseEndTime)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -288,7 +226,9 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
|
|||
{method.logo}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="font-semibold text-gray-900 dark:text-gray-100">{method.name}</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{method.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{method.name === defaultPaymentMethod
|
||||
? translate('::Public.payment.method.installmentsAvailable')
|
||||
|
|
@ -412,21 +352,20 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
|
|||
|
||||
<div className="space-y-4 mb-4">
|
||||
{cartState.items.map((item, index) => (
|
||||
<div key={`${item.product.id}-${index}`} className="flex justify-between text-sm">
|
||||
<div>
|
||||
<div className="font-medium">{translate('::' + item.product.name)}</div>
|
||||
<div className="text-gray-500">
|
||||
{item.quantity}x -{' '}
|
||||
{item.billingCycle === 'monthly'
|
||||
? 'Aylık'
|
||||
: item.billingCycle === 'yearly'
|
||||
? 'Yıllık'
|
||||
: 'Aylık'}{' '}
|
||||
{cartState.globalPeriod > 1 &&
|
||||
`(${cartState.globalPeriod} ${item.billingCycle === 'monthly' ? 'Ay' : 'Yıl'})`}
|
||||
<div key={`${item.product.id}-${index}`} className="text-sm">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
{item.product.imageUrl && (
|
||||
<img
|
||||
src={item.product.imageUrl}
|
||||
alt={translate('::' + item.product.name)}
|
||||
className="h-10 w-10 shrink-0 rounded-md object-cover"
|
||||
/>
|
||||
)}
|
||||
<div className="min-w-0 font-medium">{translate('::' + item.product.name)}</div>
|
||||
</div>
|
||||
<div className="font-medium text-right">{formatPrice(item.totalPrice)}</div>
|
||||
</div>
|
||||
<div className="font-medium">{formatPrice(item.totalPrice)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -434,7 +373,11 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
|
|||
<div className="space-y-1 text-sm border-t border-gray-200 dark:border-gray-700 pt-4 text-gray-600 dark:text-gray-300">
|
||||
<div className="flex justify-between">
|
||||
<span>{translate('::Public.payment.summary.subtotal')}</span>
|
||||
<span>{formatPrice(cartState.total)}</span>
|
||||
<span>{formatPrice(cartState.subtotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{translate('::Public.products.kdv')}{vatRateLabel}</span>
|
||||
<span>{formatPrice(cartState.vatTotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{translate('::Public.payment.installments.commission')}</span>
|
||||
|
|
@ -486,4 +429,3 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,10 +40,20 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|||
return globalBillingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!
|
||||
}
|
||||
|
||||
const getVatRate = () => product.vatRate ?? 0.2
|
||||
|
||||
const getTotalPrice = () => {
|
||||
return getCurrentPrice() * quantity * globalPeriod
|
||||
}
|
||||
|
||||
const getVatAmount = () => {
|
||||
return getTotalPrice() * getVatRate()
|
||||
}
|
||||
|
||||
const getTotalPriceWithVat = () => {
|
||||
return getTotalPrice() + getVatAmount()
|
||||
}
|
||||
|
||||
const handleAddToCart = () => {
|
||||
// Non-quantity products should not be added if already in cart
|
||||
if (!product.isQuantityBased && isInCart) {
|
||||
|
|
@ -153,12 +163,20 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|||
{formatPrice(getCurrentPrice())}
|
||||
<span className="text-sm font-normal text-gray-500 ml-1 dark:text-gray-400">{getUnitText()}</span>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{translate('::Public.products.kdv')} (%{(getVatRate() * 100).toFixed(0)}): {formatPrice(getVatAmount())}
|
||||
</div>
|
||||
{globalPeriod > 1 && (
|
||||
<div className="text-lg font-semibold text-blue-600 mt-1">
|
||||
{translate('::App.Listform.ListformField.Total')} {formatPrice(getTotalPrice())}
|
||||
{translate('::App.Listform.ListformField.Total')} {formatPrice(getTotalPriceWithVat())}
|
||||
<span className="text-sm font-normal text-gray-500 ml-1 dark:text-gray-400">{getPeriodText()}</span>
|
||||
</div>
|
||||
)}
|
||||
{globalPeriod <= 1 && (
|
||||
<div className="text-lg font-semibold text-blue-600 mt-1">
|
||||
{translate('::Public.products.kdvdahil')} {formatPrice(getTotalPriceWithVat())}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(() => {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
FaSearch,
|
||||
FaUser,
|
||||
FaUserPlus,
|
||||
FaCheckCircle,
|
||||
} from 'react-icons/fa'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
|
@ -36,6 +37,8 @@ interface LocationOption<T> {
|
|||
}
|
||||
|
||||
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
const TENANT_DATA_STORAGE_KEY = 'tenantData'
|
||||
const TENANT_FORM_DRAFT_STORAGE_KEY = 'tenantFormDraft'
|
||||
const DEFAULT_PHONE_FORMAT = '000 000 000 000 000'
|
||||
const TURKEY_PHONE_FORMAT = '(000) 000-0000'
|
||||
const REQUIRED_NEW_CUSTOMER_FIELDS: Array<keyof CustomTenantDto> = [
|
||||
|
|
@ -123,6 +126,9 @@ const formatPhoneNumber = (value: string, country?: CountryDto) => {
|
|||
return `+${country.phoneCode}${nationalNumber ? ` ${nationalNumber}` : ''}`
|
||||
}
|
||||
|
||||
const normalizePhoneForStorage = (value?: string, country?: CountryDto) =>
|
||||
value ? getNationalPhoneDigits(value, country) : value
|
||||
|
||||
const isBlankValue = (value: unknown) =>
|
||||
value === undefined || value === null || String(value).trim() === ''
|
||||
|
||||
|
|
@ -133,6 +139,11 @@ const hasRequiredFields = (
|
|||
|
||||
const normalizeCode = (value?: string) => value?.trim().toLowerCase() || ''
|
||||
|
||||
type TenantFormDraft = Partial<CustomTenantDto> & {
|
||||
isExisting?: boolean
|
||||
foundTenantName?: string
|
||||
}
|
||||
|
||||
interface TenantFormProps {
|
||||
onSubmit: (tenant: CustomTenantDto) => void
|
||||
onBack: () => void
|
||||
|
|
@ -156,6 +167,22 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
const navigate = useNavigate()
|
||||
const { translate } = useLocalization()
|
||||
|
||||
useEffect(() => {
|
||||
const savedDraft = localStorage.getItem(TENANT_FORM_DRAFT_STORAGE_KEY)
|
||||
const savedTenant = localStorage.getItem(TENANT_DATA_STORAGE_KEY)
|
||||
const savedData = savedDraft || savedTenant
|
||||
if (!savedData) return
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(savedData) as TenantFormDraft
|
||||
setIsExisting(parsed.isExisting ?? true)
|
||||
setFoundTenantName(parsed.foundTenantName || parsed.name || '')
|
||||
setFormData(parsed)
|
||||
} catch (error) {
|
||||
console.error('Müşteri form taslağı okunamadı:', error)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const loadCountries = async () => {
|
||||
setIsLoadingCountries(true)
|
||||
|
|
@ -197,6 +224,48 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
void loadCountries()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(formData).length === 0) return
|
||||
|
||||
localStorage.setItem(
|
||||
TENANT_FORM_DRAFT_STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
...formData,
|
||||
isExisting,
|
||||
foundTenantName,
|
||||
}),
|
||||
)
|
||||
}, [formData, foundTenantName, isExisting])
|
||||
|
||||
useEffect(() => {
|
||||
const loadLocationOptions = async () => {
|
||||
if (!formData.country) return
|
||||
|
||||
setIsLoadingCities(true)
|
||||
try {
|
||||
const cityResponse = await getCities(formData.country)
|
||||
setCities(cityResponse.data)
|
||||
} catch (error) {
|
||||
console.error('Şehirler alınırken hata:', error)
|
||||
} finally {
|
||||
setIsLoadingCities(false)
|
||||
}
|
||||
|
||||
if (!formData.city) return
|
||||
setIsLoadingDistricts(true)
|
||||
try {
|
||||
const districtResponse = await getDistricts(formData.country, formData.city)
|
||||
setDistricts(districtResponse.data)
|
||||
} catch (error) {
|
||||
console.error('İlçeler alınırken hata:', error)
|
||||
} finally {
|
||||
setIsLoadingDistricts(false)
|
||||
}
|
||||
}
|
||||
|
||||
void loadLocationOptions()
|
||||
}, [formData.country, formData.city])
|
||||
|
||||
const selectedCountry = countries.find((country) => country.name === formData.country)
|
||||
const phoneMask = selectedCountry
|
||||
? `+${selectedCountry.phoneCode} ${getPhoneFormat(selectedCountry)}`
|
||||
|
|
@ -233,7 +302,13 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
return
|
||||
}
|
||||
|
||||
onSubmit({ ...formData, isExisting: true } as CustomTenantDto)
|
||||
onSubmit({
|
||||
...formData,
|
||||
phoneNumber: normalizePhoneForStorage(formData.phoneNumber, selectedCountry),
|
||||
mobileNumber: normalizePhoneForStorage(formData.mobileNumber, selectedCountry),
|
||||
faxNumber: normalizePhoneForStorage(formData.faxNumber, selectedCountry),
|
||||
isExisting: true,
|
||||
} as CustomTenantDto)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -254,13 +329,46 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
}
|
||||
|
||||
if (hasValidationError) return
|
||||
onSubmit({ ...formData, isExisting: false } as CustomTenantDto)
|
||||
onSubmit({
|
||||
...formData,
|
||||
phoneNumber: normalizePhoneForStorage(formData.phoneNumber, selectedCountry),
|
||||
mobileNumber: normalizePhoneForStorage(formData.mobileNumber, selectedCountry),
|
||||
faxNumber: normalizePhoneForStorage(formData.faxNumber, selectedCountry),
|
||||
isExisting: false,
|
||||
} as CustomTenantDto)
|
||||
}
|
||||
|
||||
const handleInputChange = (field: keyof CustomTenantDto, value: string) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }))
|
||||
}
|
||||
|
||||
const getDefaultNewCustomerData = (): Partial<CustomTenantDto> => {
|
||||
const defaultCountry = countries.find((country) => country.name === 'Türkiye')
|
||||
return defaultCountry
|
||||
? {
|
||||
country: defaultCountry.name,
|
||||
city: '',
|
||||
district: '',
|
||||
phoneNumber: `+${defaultCountry.phoneCode}`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
|
||||
const handleCustomerTypeChange = (nextIsExisting: boolean) => {
|
||||
if (nextIsExisting === isExisting) return
|
||||
|
||||
setIsExisting(nextIsExisting)
|
||||
setFoundTenantName('')
|
||||
setExistingTenantError('')
|
||||
setFormError('')
|
||||
setEmailError('')
|
||||
setPhoneError('')
|
||||
setDistricts([])
|
||||
setFormData(nextIsExisting ? {} : getDefaultNewCustomerData())
|
||||
localStorage.removeItem(TENANT_DATA_STORAGE_KEY)
|
||||
localStorage.removeItem(TENANT_FORM_DRAFT_STORAGE_KEY)
|
||||
}
|
||||
|
||||
const resetExistingTenantDetails = (name: string) => {
|
||||
setFoundTenantName('')
|
||||
setExistingTenantError('')
|
||||
|
|
@ -343,25 +451,25 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
...prev,
|
||||
id: tenant.data.id,
|
||||
name: tenant.data.name,
|
||||
isActive: tenant.data.isActive,
|
||||
organizationName: tenant.data.organizationName,
|
||||
founder: tenant.data.founder,
|
||||
vknTckn: tenant.data.vknTckn,
|
||||
taxOffice: tenant.data.taxOffice,
|
||||
address1: tenant.data.address1,
|
||||
address2: tenant.data.address2,
|
||||
district: tenant.data.district,
|
||||
country: tenant.data.country,
|
||||
city: tenant.data.city,
|
||||
district: tenant.data.district,
|
||||
postalCode: tenant.data.postalCode,
|
||||
phoneNumber: tenant.data.phoneNumber,
|
||||
mobileNumber: tenant.data.mobileNumber,
|
||||
faxNumber: tenant.data.faxNumber,
|
||||
address1: tenant.data.address1,
|
||||
address2: tenant.data.address2,
|
||||
email: tenant.data.email,
|
||||
website: tenant.data.website,
|
||||
menuGroup: tenant.data.menuGroup,
|
||||
}))
|
||||
setFoundTenantName(tenant.data.name)
|
||||
|
||||
if (tenant.data.name) setFoundTenantName(tenant.data.name)
|
||||
} catch (error) {
|
||||
setFoundTenantName('')
|
||||
setExistingTenantError('Kurum bulunamadı. Lütfen organization code bilgisini kontrol edin.')
|
||||
|
|
@ -384,35 +492,41 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<Button
|
||||
onClick={() => setIsExisting(true)}
|
||||
onClick={() => handleCustomerTypeChange(true)}
|
||||
variant="plain"
|
||||
className={`block h-auto w-full p-4 border-2 rounded-xl transition-all text-left select-none ${
|
||||
className={`relative block h-auto w-full p-4 border-2 rounded-xl transition-all text-left select-none shadow-sm ${
|
||||
isExisting === true
|
||||
? 'border-blue-500 bg-blue-50 shadow-md scale-[1.02] dark:bg-blue-950/30'
|
||||
: 'border-gray-200 hover:border-blue-300 dark:border-gray-700 dark:hover:border-blue-500'
|
||||
? 'border-blue-600 bg-blue-100 shadow-lg ring-4 ring-blue-200 scale-[1.02] dark:border-sky-400 dark:bg-sky-950/60 dark:ring-sky-500/30'
|
||||
: 'border-gray-200 bg-white hover:border-blue-400 hover:bg-blue-50/60 dark:border-gray-700 dark:bg-gray-800/70 dark:hover:border-sky-500 dark:hover:bg-sky-950/30'
|
||||
}`}
|
||||
>
|
||||
{isExisting === true && (
|
||||
<FaCheckCircle className="absolute right-4 top-4 h-5 w-5 text-blue-600 dark:text-sky-300" />
|
||||
)}
|
||||
<div className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||
{translate('::Public.products.tenantForm.existing.title')}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
{translate('::Public.products.tenantForm.existing.desc')}
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => setIsExisting(false)}
|
||||
onClick={() => handleCustomerTypeChange(false)}
|
||||
variant="plain"
|
||||
className={`block h-auto w-full p-4 border-2 rounded-xl transition-all text-left select-none ${
|
||||
className={`relative block h-auto w-full p-4 border-2 rounded-xl transition-all text-left select-none shadow-sm ${
|
||||
isExisting === false
|
||||
? 'border-blue-500 bg-blue-50 shadow-md scale-[1.02] dark:bg-blue-950/30'
|
||||
: 'border-gray-200 hover:border-blue-300 dark:border-gray-700 dark:hover:border-blue-500'
|
||||
? 'border-blue-600 bg-blue-100 shadow-lg ring-4 ring-blue-200 scale-[1.02] dark:border-sky-400 dark:bg-sky-950/60 dark:ring-sky-500/30'
|
||||
: 'border-gray-200 bg-white hover:border-blue-400 hover:bg-blue-50/60 dark:border-gray-700 dark:bg-gray-800/70 dark:hover:border-sky-500 dark:hover:bg-sky-950/30'
|
||||
}`}
|
||||
>
|
||||
{isExisting === false && (
|
||||
<FaCheckCircle className="absolute right-4 top-4 h-5 w-5 text-blue-600 dark:text-sky-300" />
|
||||
)}
|
||||
<div className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||
{translate('::Public.products.tenantForm.new.title')}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
{translate('::Public.products.tenantForm.new.desc')}
|
||||
</div>
|
||||
</Button>
|
||||
|
|
@ -476,12 +590,6 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
|
||||
{formData.organizationName && (
|
||||
<div className="grid grid-cols-1 gap-y-3 text-sm text-gray-700 dark:text-gray-300 p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<FaUser className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">{translate('::LoginPanel.Profil')}</span>
|
||||
<span>{formData.founder}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaBuilding className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
|
|
@ -490,6 +598,12 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
<span>{formData.organizationName}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaUser className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">{translate('::LoginPanel.Profil')}</span>
|
||||
<span>{formData.founder}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<FaEnvelope className="w-4 h-4 text-gray-500" />
|
||||
<span className="font-medium">
|
||||
|
|
|
|||
|
|
@ -21,28 +21,24 @@ export interface TenantDto extends ExtensibleEntityDto<string> {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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<string, unknown>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<CartState>(() => {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<CustomTenantDto | null>(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<string, unknown>) => {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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<CartState>(() => {
|
||||
// Load cart state from localStorage if available
|
||||
const savedCart = localStorage.getItem('cartState')
|
||||
return savedCart ? JSON.parse(savedCart) : initialCartState
|
||||
return loadCartFromLocalStorage()
|
||||
})
|
||||
const navigate = useNavigate()
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string>('')
|
||||
const [order, setOrder] = useState<OrderSuccessData | null>(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 = () => {
|
|||
</div>
|
||||
|
||||
<div className="container mx-auto px-4 pt-12">
|
||||
<OrderSuccess orderId={orderId} onBackToShop={handleBackToShop} />
|
||||
<OrderSuccess orderId={orderId} order={order} onBackToShop={handleBackToShop} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue