feat: implement VAT management service and integrate VAT calculations across components
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using DateTimeConverterCL;
|
||||
using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
@@ -16,13 +17,18 @@ public record CartItem(long cartId,long ProductId, string Title, string ImageUrl
|
||||
public class CartService
|
||||
{
|
||||
private readonly ShopingCartContract.ShopingCartContractClient _client;
|
||||
private readonly ILocalStorageService _localStorage;
|
||||
private readonly List<CartItem> _items = new();
|
||||
private const string TokenStorageKey = "auth:token";
|
||||
private bool _isInitialized;
|
||||
|
||||
public event Action? OnChange;
|
||||
|
||||
public CartService(ShopingCartContract.ShopingCartContractClient client)
|
||||
public CartService(ShopingCartContract.ShopingCartContractClient client, ILocalStorageService localStorage)
|
||||
{
|
||||
_client = client;
|
||||
_ = LoadFromServerAsync();
|
||||
_localStorage = localStorage;
|
||||
// لود سبد خرید به صورت lazy انجام میشه - نه در constructor
|
||||
}
|
||||
|
||||
public IReadOnlyList<CartItem> Items => _items.AsReadOnly();
|
||||
@@ -33,6 +39,13 @@ public class CartService
|
||||
public async Task Add(Product product, int quantity = 1)
|
||||
{
|
||||
if (quantity <= 0) return;
|
||||
|
||||
// اطمینان از لود شدن سبد خرید
|
||||
await EnsureInitializedAsync();
|
||||
|
||||
// چک لاگین بودن کاربر
|
||||
if (!await IsAuthenticatedAsync()) return;
|
||||
|
||||
var existing = _items.FirstOrDefault(i => i.ProductId == product.Id);
|
||||
int newQuantity;
|
||||
|
||||
@@ -82,11 +95,14 @@ public class CartService
|
||||
|
||||
public async Task UpdateQuantity(long productId, int quantity)
|
||||
{
|
||||
// چک لاگین بودن کاربر
|
||||
if (!await IsAuthenticatedAsync()) return;
|
||||
|
||||
var existing = _items.FirstOrDefault(i => i.ProductId == productId);
|
||||
if (existing is null) return;
|
||||
if (quantity <= 0)
|
||||
{
|
||||
Remove(existing.cartId);
|
||||
await Remove(existing.cartId);
|
||||
return;
|
||||
}
|
||||
var idx = _items.IndexOf(existing);
|
||||
@@ -109,6 +125,9 @@ public class CartService
|
||||
|
||||
public async Task Remove(long cartId)
|
||||
{
|
||||
// چک لاگین بودن کاربر
|
||||
if (!await IsAuthenticatedAsync()) return;
|
||||
|
||||
_items.RemoveAll(i => i.cartId == cartId);
|
||||
Notify();
|
||||
|
||||
@@ -129,6 +148,9 @@ public class CartService
|
||||
|
||||
public async Task Clear()
|
||||
{
|
||||
// چک لاگین بودن کاربر
|
||||
if (!await IsAuthenticatedAsync()) return;
|
||||
|
||||
var productIds = _items.Select(i => i.ProductId).ToList();
|
||||
_items.Clear();
|
||||
Notify();
|
||||
@@ -152,8 +174,44 @@ public class CartService
|
||||
|
||||
private void Notify() => OnChange?.Invoke();
|
||||
|
||||
/// <summary>
|
||||
/// بررسی اینکه کاربر لاگین کرده یا نه
|
||||
/// </summary>
|
||||
private async Task<bool> IsAuthenticatedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = await _localStorage.GetItemAsync<string>(TokenStorageKey);
|
||||
return !string.IsNullOrWhiteSpace(token);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// اطمینان از لود شدن سبد خرید - فقط اگر کاربر لاگین کرده باشد
|
||||
/// </summary>
|
||||
public async Task EnsureInitializedAsync()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
if (await IsAuthenticatedAsync())
|
||||
{
|
||||
await LoadFromServerAsync();
|
||||
}
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
private async Task LoadFromServerAsync()
|
||||
{
|
||||
// ابتدا چک کن که کاربر لاگین کرده
|
||||
if (!await IsAuthenticatedAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _client.GetAllUserCartAsync(new Empty());
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using FrontOffice.BFF.Configuration.Protobuf.Protos.Configuration;
|
||||
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس تنظیمات باشگاه مشتریان
|
||||
/// </summary>
|
||||
public class ClubConfigurationService
|
||||
{
|
||||
private readonly ConfigurationContract.ConfigurationContractClient _client;
|
||||
|
||||
public ClubConfigurationService(ConfigurationContract.ConfigurationContractClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تنظیمات باشگاه مشتریان
|
||||
/// </summary>
|
||||
public async Task<ClubConfigDto> GetClubConfigurationAsync()
|
||||
{
|
||||
var response = await _client.GetClubConfigurationAsync(new Google.Protobuf.WellKnownTypes.Empty());
|
||||
|
||||
return new ClubConfigDto
|
||||
{
|
||||
ActivationFee = response.ActivationFee,
|
||||
MembershipGiftValue = response.MembershipGiftValue
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت لیست فیچرهای باشگاه برای کاربر جاری
|
||||
/// </summary>
|
||||
public async Task<List<ClubFeatureDto>> GetClubFeaturesAsync()
|
||||
{
|
||||
var response = await _client.GetClubFeaturesAsync(new Google.Protobuf.WellKnownTypes.Empty());
|
||||
|
||||
return response.Features.Select(f => new ClubFeatureDto
|
||||
{
|
||||
Id = f.Id,
|
||||
Title = f.Title,
|
||||
Description = f.Description,
|
||||
IsEnabled = f.IsEnabled,
|
||||
DisplayOrder = f.DisplayOrder,
|
||||
GrantedAt = f.GrantedAt?.ToDateTime(),
|
||||
CreatedAt = f.CreatedAt?.ToDateTime(),
|
||||
Notes = f.Notes
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public class ClubConfigDto
|
||||
{
|
||||
public long ActivationFee { get; set; }
|
||||
public long MembershipGiftValue { get; set; }
|
||||
}
|
||||
|
||||
public class ClubFeatureDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
public int DisplayOrder { get; set; }
|
||||
public DateTime? GrantedAt { get; set; }
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
@@ -79,4 +79,25 @@ public class OrderService
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت نرخ مالیات بر ارزش افزوده
|
||||
/// </summary>
|
||||
public async Task<GetVATRateResponse> GetVATRateAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _userOrderContractClient.GetVATRateAsync(new Google.Protobuf.WellKnownTypes.Empty());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// در صورت خطا، مقادیر پیشفرض
|
||||
return new GetVATRateResponse
|
||||
{
|
||||
VatRate = 0.10,
|
||||
VatPercentage = 10,
|
||||
IsEnabled = true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using Blazored.LocalStorage;
|
||||
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس مدیریت مالیات بر ارزش افزوده
|
||||
/// نرخ VAT یک بار در روز از سرور گرفته و در LocalStorage ذخیره میشود
|
||||
/// </summary>
|
||||
public class VATService
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
private const string VAT_RATE_KEY = "vat_rate";
|
||||
private const string VAT_PERCENTAGE_KEY = "vat_percentage";
|
||||
private const string VAT_DATE_KEY = "vat_date";
|
||||
|
||||
// مقادیر پیشفرض
|
||||
private double _vatRate = 0.0999;
|
||||
private int _vatPercentage = 99;
|
||||
private bool _isEnabled = true;
|
||||
private bool _isLoaded = false;
|
||||
|
||||
public VATService(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// نرخ مالیات (مثلاً 0.09)
|
||||
/// </summary>
|
||||
public double VatRate => _vatRate;
|
||||
|
||||
/// <summary>
|
||||
/// درصد مالیات (مثلاً 9)
|
||||
/// </summary>
|
||||
public int VatPercentage => _vatPercentage;
|
||||
|
||||
/// <summary>
|
||||
/// آیا مالیات فعال است
|
||||
/// </summary>
|
||||
public bool IsEnabled => _isEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// بارگذاری نرخ VAT - اگر امروز گرفته شده از cache، وگرنه از سرور
|
||||
/// </summary>
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
if (_isLoaded) return;
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var localStorage = scope.ServiceProvider.GetRequiredService<ILocalStorageService>();
|
||||
|
||||
// چک کن آیا امروز قبلاً گرفته شده
|
||||
var savedDate = await localStorage.GetItemAsStringAsync(VAT_DATE_KEY);
|
||||
var today = DateTime.Today.ToString("yyyy-MM-dd");
|
||||
|
||||
if (savedDate == today)
|
||||
{
|
||||
// از cache بخوان
|
||||
var savedRate = await localStorage.GetItemAsync<double>(VAT_RATE_KEY);
|
||||
var savedPercentage = await localStorage.GetItemAsync<int>(VAT_PERCENTAGE_KEY);
|
||||
if (savedRate > 0 && savedPercentage > 0)
|
||||
{
|
||||
_vatRate = savedRate;
|
||||
_vatPercentage = savedPercentage;
|
||||
_isLoaded = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// از سرور بگیر
|
||||
await RefreshFromServerAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// در صورت خطا از مقادیر پیشفرض استفاده شود
|
||||
_isLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// بروزرسانی از سرور و ذخیره در cache
|
||||
/// </summary>
|
||||
public async Task RefreshFromServerAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// ایجاد scope برای دریافت client و localStorage
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var client = scope.ServiceProvider.GetRequiredService<UserOrderContract.UserOrderContractClient>();
|
||||
var localStorage = scope.ServiceProvider.GetRequiredService<ILocalStorageService>();
|
||||
|
||||
var response = await client.GetVATRateAsync(new Google.Protobuf.WellKnownTypes.Empty());
|
||||
|
||||
_vatRate = response.VatRate;
|
||||
_vatPercentage = response.VatPercentage;
|
||||
_isEnabled = response.IsEnabled;
|
||||
|
||||
// ذخیره در LocalStorage
|
||||
await localStorage.SetItemAsync(VAT_RATE_KEY, _vatRate);
|
||||
await localStorage.SetItemAsync(VAT_PERCENTAGE_KEY, _vatPercentage);
|
||||
await localStorage.SetItemAsStringAsync(VAT_DATE_KEY, DateTime.Today.ToString("yyyy-MM-dd"));
|
||||
|
||||
_isLoaded = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// مقادیر پیشفرض
|
||||
_isLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// محاسبه مبلغ مالیات
|
||||
/// </summary>
|
||||
public long CalculateVAT(long amount) => _isEnabled ? (long)(amount * _vatRate) : 0;
|
||||
|
||||
/// <summary>
|
||||
/// محاسبه قیمت با احتساب مالیات
|
||||
/// </summary>
|
||||
public long AddVAT(long amount) => _isEnabled ? amount + CalculateVAT(amount) : amount;
|
||||
|
||||
/// <summary>
|
||||
/// فرمت قیمت با نمایش مالیات
|
||||
/// </summary>
|
||||
public string FormatPriceWithVAT(long price)
|
||||
{
|
||||
var priceWithVat = AddVAT(price);
|
||||
return $"{priceWithVat:N0} تومان";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// فرمت قیمت با جزئیات مالیات
|
||||
/// </summary>
|
||||
public (long BasePrice, long VatAmount, long TotalPrice) GetPriceBreakdown(long basePrice)
|
||||
{
|
||||
var vatAmount = CalculateVAT(basePrice);
|
||||
return (basePrice, vatAmount, basePrice + vatAmount);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user