feat: implement VAT management service and integrate VAT calculations across components

This commit is contained in:
masoodafar-web
2025-12-19 00:30:33 +03:30
parent 6b457d0ce6
commit 9ee464b8b4
25 changed files with 751 additions and 268 deletions
+61 -3
View File
@@ -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);
}
}