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
@@ -16,6 +16,7 @@ using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart;
// New Proto imports // New Proto imports
using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership; using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership;
using FrontOffice.BFF.Commission.Protobuf.Protos.Commission; using FrontOffice.BFF.Commission.Protobuf.Protos.Commission;
using FrontOffice.BFF.Configuration.Protobuf.Protos.Configuration;
using FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership; using FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership;
using FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop; using FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop;
using FrontOffice.BFF.City.Protobuf; using FrontOffice.BFF.City.Protobuf;
@@ -52,10 +53,12 @@ public static class ConfigureServices
services.AddScoped<CategoryService>(); services.AddScoped<CategoryService>();
services.AddScoped<OrderService>(); services.AddScoped<OrderService>();
services.AddScoped<WalletService>(); services.AddScoped<WalletService>();
services.AddSingleton<VATService>(); // Singleton برای cache روزانه
// Package service // Package service
services.AddScoped<PackageService>(); services.AddScoped<PackageService>();
// New services for Club, Network, Commission // New services for Club, Network, Commission
services.AddScoped<ClubMembershipService>(); services.AddScoped<ClubMembershipService>();
services.AddScoped<ClubConfigurationService>();
services.AddScoped<NetworkMembershipService>(); services.AddScoped<NetworkMembershipService>();
services.AddScoped<CommissionService>(); services.AddScoped<CommissionService>();
// Device detection: very light, dependency-free // Device detection: very light, dependency-free
@@ -103,6 +106,7 @@ public static class ConfigureServices
// New gRPC clients for Club, Network, Commission, DiscountShop // New gRPC clients for Club, Network, Commission, DiscountShop
services.AddScoped(CreateAuthenticatedClient<ClubMembershipContract.ClubMembershipContractClient>); services.AddScoped(CreateAuthenticatedClient<ClubMembershipContract.ClubMembershipContractClient>);
services.AddScoped(CreateAuthenticatedClient<CommissionContract.CommissionContractClient>); services.AddScoped(CreateAuthenticatedClient<CommissionContract.CommissionContractClient>);
services.AddScoped(CreateAuthenticatedClient<ConfigurationContract.ConfigurationContractClient>);
services.AddScoped(CreateAuthenticatedClient<NetworkMembershipContract.NetworkMembershipContractClient>); services.AddScoped(CreateAuthenticatedClient<NetworkMembershipContract.NetworkMembershipContractClient>);
services.AddScoped(CreateAuthenticatedClient<DiscountShopContract.DiscountShopContractClient>); services.AddScoped(CreateAuthenticatedClient<DiscountShopContract.DiscountShopContractClient>);
services.AddScoped(CreateAuthenticatedClient<CityContract.CityContractClient>); services.AddScoped(CreateAuthenticatedClient<CityContract.CityContractClient>);
+3 -1
View File
@@ -20,7 +20,7 @@
<PackageReference Include="Foursat.FrontOffice.BFF.Category.Protobuf" Version="0.0.13" /> <PackageReference Include="Foursat.FrontOffice.BFF.Category.Protobuf" Version="0.0.13" />
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.User.Protobuf" Version="0.0.117" /> --> <!-- <PackageReference Include="Foursat.FrontOffice.BFF.User.Protobuf" Version="0.0.117" /> -->
<PackageReference Include="Foursat.FrontOffice.BFF.UserAddress.Protobuf" Version="0.0.115" /> <PackageReference Include="Foursat.FrontOffice.BFF.UserAddress.Protobuf" Version="0.0.115" />
<PackageReference Include="Foursat.FrontOffice.BFF.UserOrder.Protobuf" Version="0.0.115" /> <!-- <PackageReference Include="Foursat.FrontOffice.BFF.UserOrder.Protobuf" Version="0.0.115" />-->
<PackageReference Include="Foursat.FrontOffice.BFF.ShopingCart.Protobuf" Version="0.0.16" /> <PackageReference Include="Foursat.FrontOffice.BFF.ShopingCart.Protobuf" Version="0.0.16" />
<PackageReference Include="Foursat.FrontOffice.BFF.UserWallet.Protobuf" Version="0.0.15" /> <PackageReference Include="Foursat.FrontOffice.BFF.UserWallet.Protobuf" Version="0.0.15" />
<!-- UserWallet moved to ProjectReference for latest proto --> <!-- UserWallet moved to ProjectReference for latest proto -->
@@ -40,8 +40,10 @@
<ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.Package.Protobuf\FrontOffice.BFF.Package.Protobuf.csproj" /> <ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.Package.Protobuf\FrontOffice.BFF.Package.Protobuf.csproj" />
<ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.ClubMembership.Protobuf\FrontOffice.BFF.ClubMembership.Protobuf.csproj" /> <ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.ClubMembership.Protobuf\FrontOffice.BFF.ClubMembership.Protobuf.csproj" />
<ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.City.Protobuf\FrontOffice.BFF.City.Protobuf.csproj" /> <ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.City.Protobuf\FrontOffice.BFF.City.Protobuf.csproj" />
<ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.Configuration.Protobuf\FrontOffice.BFF.Configuration.Protobuf.csproj" />
<ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.User.Protobuf\FrontOffice.BFF.User.Protobuf.csproj" /> <ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.User.Protobuf\FrontOffice.BFF.User.Protobuf.csproj" />
<ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.NetworkMembership.Protobuf\FrontOffice.BFF.NetworkMembership.Protobuf.csproj" /> <ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.NetworkMembership.Protobuf\FrontOffice.BFF.NetworkMembership.Protobuf.csproj" />
<ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.UserOrder.Protobuf\FrontOffice.BFF.UserOrder.Protobuf.csproj" />
</ItemGroup> </ItemGroup>
<!-- New Proto Projects (local references until NuGet publish) --> <!-- New Proto Projects (local references until NuGet publish) -->
+165 -124
View File
@@ -1,6 +1,8 @@
@attribute [Route(RouteConstants.Club.Features)] @attribute [Route(RouteConstants.Club.Features)]
@using FrontOffice.Main.Utilities @using FrontOffice.Main.Utilities
@using MudBlazor @using MudBlazor
@inject ClubConfigurationService ClubConfigService
@inject IDialogService DialogService
<PageTitle>ویژگی‌های باشگاه مشتریان</PageTitle> <PageTitle>ویژگی‌های باشگاه مشتریان</PageTitle>
@@ -11,142 +13,181 @@
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Club.Membership">بازگشت</MudButton> <MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Club.Membership">بازگشت</MudButton>
</MudStack> </MudStack>
<!-- توضیحات کلی باشگاه -->
<MudPaper Elevation="2" Class="pa-4 rounded-lg"> <MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">چرا باشگاه مشتریان؟</MudText> <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-3">
<MudIcon Icon="@Icons.Material.Filled.Stars" Color="Color.Primary" Size="Size.Large" />
<MudText Typo="Typo.h6" Color="Color.Primary">ویژگی‌های اختصاصی شما</MudText>
</MudStack>
<MudText Typo="Typo.body1" Class="mb-2"> <MudText Typo="Typo.body1" Class="mb-2">
با عضویت در باشگاه مشتریان فورست، از تخفیف‌های ویژه، امتیازات خرید و خدمات اختصاصی بهره‌مند شوید. علاوه بر مزایای پایه عضویت در باشگاه مشتریان، شما به مجموعه‌ای از ویژگی‌های اختصاصی دسترسی دارید. برخی از این ویژگی‌ها ممکن است نیاز به زمان برای فعال‌سازی داشته باشند.
با کلیک روی دکمه «جزئیات» هر ویژگی، می‌توانید اطلاعات بیشتری درباره نحوه استفاده از آن امکان را مشاهده کنید.
</MudText> </MudText>
</MudPaper> </MudPaper>
<MudGrid Spacing="3"> <!-- لیست فیچرها -->
<MudItem xs="12" md="6"> <MudText Typo="Typo.h6" Class="mt-4">ویژگی‌های شما</MudText>
<MudPaper Elevation="2" Class="pa-4 rounded-lg" Style="height: 100%;">
<MudStack Spacing="2">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.Discount" Color="Color.Primary" Size="Size.Large" />
<MudText Typo="Typo.h6" Color="Color.Primary">تخفیف‌های ویژه</MudText>
</MudStack>
<MudText Typo="Typo.body2">
• تخفیف 10% تا 30% برای تمام محصولات<br />
• تخفیف‌های فصلی و مناسبتی اختصاصی<br />
• پیشنهادات ویژه برای اعضای باشگاه<br />
• کد تخفیف اختصاصی ماهانه
</MudText>
</MudStack>
</MudPaper>
</MudItem>
<MudItem xs="12" md="6"> @if (_isLoading)
<MudPaper Elevation="2" Class="pa-4 rounded-lg" Style="height: 100%;"> {
<MudStack Spacing="2"> <MudProgressLinear Color="Color.Primary" Indeterminate="true" />
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2"> }
<MudIcon Icon="@Icons.Material.Filled.Diamond" Color="Color.Secondary" Size="Size.Large" /> else if (_features == null || !_features.Any())
<MudText Typo="Typo.h6" Color="Color.Secondary">امتیاز خرید</MudText> {
</MudStack> <MudAlert Severity="Severity.Info">
<MudText Typo="Typo.body2"> هنوز ویژگی‌ای برای شما فعال نشده است.
• دریافت امتیاز برای هر خرید<br /> </MudAlert>
• تبدیل امتیاز به تخفیف باشگاه<br /> }
• امتیاز ویژه در روزهای خاص<br /> else
• قابلیت انتقال امتیاز به دوستان {
</MudText> <MudPaper Elevation="2" Class="pa-2 rounded-lg">
</MudStack> <MudList T="ClubFeatureDto" Dense="true">
</MudPaper> @foreach (var feature in _features)
</MudItem> {
<MudListItem>
<MudItem xs="12" md="6"> <MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Style="width: 100%;">
<MudPaper Elevation="2" Class="pa-4 rounded-lg" Style="height: 100%;"> <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudStack Spacing="2"> <MudIcon Icon="@Icons.Material.Filled.CheckCircle"
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2"> Color="@(feature.IsEnabled ? Color.Success : Color.Default)" />
<MudIcon Icon="@Icons.Material.Filled.LocalShipping" Color="Color.Success" Size="Size.Large" /> <MudText>@feature.Title</MudText>
<MudText Typo="Typo.h6" Color="Color.Success">ارسال رایگان</MudText> </MudStack>
</MudStack> <MudButton Variant="Variant.Text"
<MudText Typo="Typo.body2"> Color="Color.Primary"
• ارسال رایگان برای خریدهای بالای 500 هزار تومان<br /> Size="Size.Small"
• اولویت در ارسال سفارشات<br /> OnClick="@(() => ShowFeatureDetails(feature))">
• ارسال اکسپرس با تخفیف 50%<br /> جزئیات
• امکان ارسال به چند آدرس </MudButton>
</MudText> </MudStack>
</MudStack> </MudListItem>
</MudPaper> @if (feature != _features.Last())
</MudItem> {
<MudDivider />
<MudItem xs="12" md="6"> }
<MudPaper Elevation="2" Class="pa-4 rounded-lg" Style="height: 100%;"> }
<MudStack Spacing="2"> </MudList>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2"> </MudPaper>
<MudIcon Icon="@Icons.Material.Filled.Support" Color="Color.Info" Size="Size.Large" /> }
<MudText Typo="Typo.h6" Color="Color.Info">پشتیبانی اختصاصی</MudText>
</MudStack>
<MudText Typo="Typo.body2">
• پشتیبانی 24/7 برای اعضای باشگاه<br />
• مشاوره خرید تخصصی<br />
• خط ویژه پاسخگویی<br />
• پیگیری سریع‌تر سفارشات
</MudText>
</MudStack>
</MudPaper>
</MudItem>
<MudItem xs="12" md="6">
<MudPaper Elevation="2" Class="pa-4 rounded-lg" Style="height: 100%;">
<MudStack Spacing="2">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.Groups" Color="Color.Warning" Size="Size.Large" />
<MudText Typo="Typo.h6" Color="Color.Warning">درآمد شبکه</MudText>
</MudStack>
<MudText Typo="Typo.body2">
• دریافت کمیسیون از خریدهای زیرمجموعه<br />
• سیستم باینری درختی<br />
• محاسبه خودکار درآمد هفتگی<br />
• امکان برداشت درآمد شبکه
</MudText>
</MudStack>
</MudPaper>
</MudItem>
<MudItem xs="12" md="6">
<MudPaper Elevation="2" Class="pa-4 rounded-lg" Style="height: 100%;">
<MudStack Spacing="2">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.Event" Color="Color.Tertiary" Size="Size.Large" />
<MudText Typo="Typo.h6" Color="Color.Tertiary">رویدادهای ویژه</MudText>
</MudStack>
<MudText Typo="Typo.body2">
• دعوت به رویدادهای اختصاصی<br />
• پیش‌فروش محصولات جدید<br />
• وبینارهای آموزشی رایگان<br />
• جوایز و مسابقات ماهانه
</MudText>
</MudStack>
</MudPaper>
</MudItem>
</MudGrid>
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">چگونه عضو شویم؟</MudText>
<MudStepper Orientation="Orientation.Horizontal" Color="Color.Primary">
<MudStep Title="انتخاب بسته">
<MudText>بسته عضویت مناسب خود را انتخاب کنید</MudText>
</MudStep>
<MudStep Title="پرداخت">
<MudText>هزینه عضویت را پرداخت کنید</MudText>
</MudStep>
<MudStep Title="فعال‌سازی">
<MudText>عضویت شما فورا فعال می‌شود</MudText>
</MudStep>
<MudStep Title="استفاده">
<MudText>از مزایای باشگاه لذت ببرید</MudText>
</MudStep>
</MudStepper>
</MudPaper>
<MudButton Variant="Variant.Filled" <MudButton Variant="Variant.Filled"
Color="Color.Primary" Color="Color.Primary"
FullWidth="true" FullWidth="true"
Size="Size.Large" Size="Size.Large"
StartIcon="@Icons.Material.Filled.CheckCircle" StartIcon="@Icons.Material.Filled.ArrowBack"
Href="@RouteConstants.Club.Membership"> Href="@RouteConstants.Club.Membership">
همین حالا عضو شوید بازگشت به صفحه باشگاه
</MudButton> </MudButton>
</MudStack> </MudStack>
</MudContainer> </MudContainer>
<!-- مدال جزئیات فیچر -->
<MudDialog @bind-Visible="_showDetailDialog" Options="@_dialogOptions">
<TitleContent>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.Star" Color="Color.Primary" />
<MudText Typo="Typo.h6">@_selectedFeature?.Title</MudText>
</MudStack>
</TitleContent>
<DialogContent>
@if (_selectedFeature != null)
{
<MudStack Spacing="3">
<!-- وضعیت -->
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudText Typo="Typo.subtitle2">وضعیت:</MudText>
@if (_selectedFeature.IsEnabled)
{
<MudChip T="string" Color="Color.Success" Size="Size.Small">فعال</MudChip>
}
else
{
<MudChip T="string" Color="Color.Default" Size="Size.Small">غیرفعال</MudChip>
}
</MudStack>
<!-- توضیحات کوتاه -->
@if (!string.IsNullOrEmpty(_selectedFeature.Description))
{
<MudText Typo="Typo.body1">@_selectedFeature.Description</MudText>
}
<!-- تاریخ‌ها -->
<MudDivider />
<MudGrid>
<MudItem xs="6">
<MudText Typo="Typo.caption" Color="Color.Default">تاریخ ایجاد:</MudText>
<MudText Typo="Typo.body2">
@(_selectedFeature.CreatedAt?.ToLocalTime().ToString("yyyy/MM/dd HH:mm") ?? "-")
</MudText>
</MudItem>
<MudItem xs="6">
<MudText Typo="Typo.caption" Color="Color.Default">تاریخ فعال‌سازی:</MudText>
<MudText Typo="Typo.body2">
@(_selectedFeature.GrantedAt.HasValue && _selectedFeature.GrantedAt.Value > DateTime.MinValue
? _selectedFeature.GrantedAt.Value.ToLocalTime().ToString("yyyy/MM/dd HH:mm")
: "-")
</MudText>
</MudItem>
</MudGrid>
<!-- یادداشت (Notes) - محتوای اصلی مدال -->
@if (!string.IsNullOrEmpty(_selectedFeature.Notes))
{
<MudDivider />
<MudText Typo="Typo.subtitle2" Class="mb-2">توضیحات تکمیلی:</MudText>
<MudPaper Elevation="0" Class="pa-3 rounded" Style="background-color: var(--mud-palette-background-grey);">
@((MarkupString)_selectedFeature.Notes)
</MudPaper>
}
</MudStack>
}
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(() => _showDetailDialog = false)">
بستن
</MudButton>
</DialogActions>
</MudDialog>
@code {
private List<ClubFeatureDto>? _features;
private bool _isLoading = true;
private bool _showDetailDialog;
private ClubFeatureDto? _selectedFeature;
private DialogOptions _dialogOptions = new()
{
MaxWidth = MaxWidth.Small,
FullWidth = true,
CloseButton = true,
CloseOnEscapeKey = true
};
protected override async Task OnInitializedAsync()
{
await LoadFeatures();
}
private async Task LoadFeatures()
{
_isLoading = true;
try
{
_features = await ClubConfigService.GetClubFeaturesAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Error loading features: {ex.Message}");
_features = new List<ClubFeatureDto>();
}
finally
{
_isLoading = false;
}
}
private void ShowFeatureDetails(ClubFeatureDto feature)
{
_selectedFeature = feature;
_showDetailDialog = true;
}
}
@@ -56,6 +56,19 @@
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined"> <MudAlert Severity="Severity.Warning" Variant="Variant.Outlined">
<MudText>شما هنوز عضو باشگاه مشتریان نیستید. برای استفاده از مزایای ویژه، اکنون فعال کنید!</MudText> <MudText>شما هنوز عضو باشگاه مشتریان نیستید. برای استفاده از مزایای ویژه، اکنون فعال کنید!</MudText>
</MudAlert> </MudAlert>
@if (_clubConfig != null)
{
<MudPaper Elevation="0" Class="pa-3 mt-3 rounded-lg" Style="background-color: var(--mud-palette-info-lighten);">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Color="Color.Info">هزینه عضویت در باشگاه مشتریان</MudText>
<MudText Typo="Typo.caption">شامل @((_clubConfig.MembershipGiftValue / 10000).ToString("N0")) تومان هدیه</MudText>
</MudStack>
<MudText Typo="Typo.h6" Color="Color.Info">@((_clubConfig.ActivationFee / 10000).ToString("N0")) تومان</MudText>
</MudStack>
</MudPaper>
}
} }
</MudStack> </MudStack>
</MudPaper> </MudPaper>
@@ -63,41 +76,31 @@
<!-- مزایای باشگاه --> <!-- مزایای باشگاه -->
<MudPaper Elevation="2" Class="pa-4 rounded-lg"> <MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">مزایای عضویت باشگاه</MudText> <MudText Typo="Typo.h6" Class="mb-3">مزایای عضویت باشگاه</MudText>
<MudGrid Spacing="2"> <MudStack Spacing="2">
<MudItem xs="12" sm="6" md="4"> <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="background-color: var(--mud-palette-primary-lighten);"> <MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Color="Color.Success" Size="Size.Large" />
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2"> <MudStack Spacing="0">
<MudIcon Icon="@Icons.Material.Filled.Discount" Color="Color.Primary" Size="Size.Large" /> <MudText Typo="Typo.subtitle1">شارژ کیف پول فروشگاه تخفیفی</MudText>
<MudStack Spacing="0"> <MudText Typo="Typo.caption" Color="Color.Default">شارژ ۵۶ میلیون تومان کیف پول فروشگاه تخفیفی</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary">تخفیف ویژه</MudText> </MudStack>
<MudText Typo="Typo.caption">تا 30% تخفیف</MudText> </MudStack>
</MudStack> <MudDivider />
</MudStack> <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
</MudPaper> <MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Primary" Size="Size.Large" />
</MudItem> <MudStack Spacing="0">
<MudItem xs="12" sm="6" md="4"> <MudText Typo="Typo.subtitle1">عضویت در شبکه بازاریابی</MudText>
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="background-color: var(--mud-palette-secondary-lighten);"> <MudText Typo="Typo.caption" Color="Color.Default">عضویت در شبکه بازاریابی و دریافت پورسانت</MudText>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2"> </MudStack>
<MudIcon Icon="@Icons.Material.Filled.Diamond" Color="Color.Secondary" Size="Size.Large" /> </MudStack>
<MudStack Spacing="0"> <MudDivider />
<MudText Typo="Typo.subtitle1" Color="Color.Secondary">امتیاز خرید</MudText> <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
<MudText Typo="Typo.caption">برای هر خرید</MudText> <MudIcon Icon="@Icons.Material.Filled.GroupAdd" Color="Color.Secondary" Size="Size.Large" />
</MudStack> <MudStack Spacing="0">
</MudStack> <MudText Typo="Typo.subtitle1">جذب زیرمجموعه</MudText>
</MudPaper> <MudText Typo="Typo.caption" Color="Color.Default">امکان جذب زیرمجموعه و گسترش شبکه</MudText>
</MudItem> </MudStack>
<MudItem xs="12" sm="6" md="4"> </MudStack>
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="background-color: var(--mud-palette-success-lighten);"> </MudStack>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.LocalShipping" Color="Color.Success" Size="Size.Large" />
<MudStack Spacing="0">
<MudText Typo="Typo.subtitle1" Color="Color.Success">ارسال رایگان</MudText>
<MudText Typo="Typo.caption">برای سفارشات بالای 500 هزار تومان</MudText>
</MudStack>
</MudStack>
</MudPaper>
</MudItem>
</MudGrid>
</MudPaper> </MudPaper>
<!-- فعال‌سازی یا تمدید عضویت --> <!-- فعال‌سازی یا تمدید عضویت -->
@@ -120,7 +123,7 @@
<MudAlert Severity="Severity.Error" Variant="Variant.Filled"> <MudAlert Severity="Severity.Error" Variant="Variant.Filled">
خطا در دریافت اطلاعات عضویت. لطفا دوباره تلاش کنید. خطا در دریافت اطلاعات عضویت. لطفا دوباره تلاش کنید.
</MudAlert> </MudAlert>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="LoadMembershipAsync">تلاش مجدد</MudButton> <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="LoadDataAsync">تلاش مجدد</MudButton>
} }
</MudStack> </MudStack>
</MudContainer> </MudContainer>
@@ -7,23 +7,33 @@ namespace FrontOffice.Main.Pages.Club;
public partial class MembershipPage : ComponentBase public partial class MembershipPage : ComponentBase
{ {
[Inject] private ClubMembershipService ClubService { get; set; } = default!; [Inject] private ClubMembershipService ClubService { get; set; } = default!;
[Inject] private ClubConfigurationService ConfigService { get; set; } = default!;
private ClubMembershipDto? _membership; private ClubMembershipDto? _membership;
private ClubConfigDto? _clubConfig;
private bool _isLoading = true; private bool _isLoading = true;
private bool _hasError = false; private bool _hasError = false;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await LoadMembershipAsync(); await LoadDataAsync();
} }
private async Task LoadMembershipAsync() private async Task LoadDataAsync()
{ {
try try
{ {
_isLoading = true; _isLoading = true;
_hasError = false; _hasError = false;
_membership = await ClubService.GetMyMembershipAsync();
// بارگذاری همزمان عضویت و تنظیمات
var membershipTask = ClubService.GetMyMembershipAsync();
var configTask = ConfigService.GetClubConfigurationAsync();
await Task.WhenAll(membershipTask, configTask);
_membership = await membershipTask;
_clubConfig = await configTask;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -38,7 +48,7 @@ public partial class MembershipPage : ComponentBase
private async Task HandleActivationSuccess() private async Task HandleActivationSuccess()
{ {
await LoadMembershipAsync(); await LoadDataAsync();
Snackbar.Add("عضویت باشگاه با موفقیت فعال شد!", Severity.Success); Snackbar.Add("عضویت باشگاه با موفقیت فعال شد!", Severity.Success);
} }
@@ -40,7 +40,7 @@ public partial class Addresses : ComponentBase
{ {
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید"); var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result; var result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
await LoadAddresses(); await LoadAddresses();
} }
@@ -51,7 +51,7 @@ public partial class Addresses : ComponentBase
{ x => x.Model, address } { x => x.Model, address }
}); });
var result = await dialog.Result; var result = await dialog.Result;
if (!result.Canceled) if (result is not null && !result.Canceled)
await LoadAddresses(); await LoadAddresses();
} }
@@ -1,5 +1,5 @@
<MudDialog> <MudDialog >
<TitleContent> <TitleContent>
<MudText Typo="Typo.h4" Align="Align.Center">افزودن آدرس جدید</MudText> <MudText Typo="Typo.h4" Align="Align.Center">افزودن آدرس جدید</MudText>
</TitleContent> </TitleContent>
@@ -9,7 +9,7 @@ namespace FrontOffice.Main.Pages.Profile.Components;
public partial class AddAddressDialog : ComponentBase public partial class AddAddressDialog : ComponentBase
{ {
[CascadingParameter] private IDialogReference MudDialog { get; set; } = default!; [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
[Inject] private CityProto.CityContract.CityContractClient CityContract { get; set; } = default!; [Inject] private CityProto.CityContract.CityContractClient CityContract { get; set; } = default!;
@@ -19,7 +19,8 @@ public partial class AddAddressDialog : ComponentBase
private CreateNewUserAddressRequest _request = new(); private CreateNewUserAddressRequest _request = new();
private CityProto.GetAllCitiesByFilterResponseModel? _selectedCity; private CityProto.GetAllCitiesByFilterResponseModel? _selectedCity;
private async Task<IEnumerable<CityProto.GetAllCitiesByFilterResponseModel>> SearchCities(string value, CancellationToken ct) private async Task<IEnumerable<CityProto.GetAllCitiesByFilterResponseModel>> SearchCities(string value,
CancellationToken ct)
{ {
if (string.IsNullOrWhiteSpace(value) || value.Length < 2) if (string.IsNullOrWhiteSpace(value) || value.Length < 2)
return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>(); return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>();
@@ -71,5 +72,6 @@ public partial class AddAddressDialog : ComponentBase
} }
} }
private void Cancel() => MudDialog.Close(DialogResult.Cancel()); private void Cancel() => MudDialog.Close(DialogResult.Cancel());
} }
@@ -10,7 +10,7 @@ namespace FrontOffice.Main.Pages.Profile.Components;
public partial class EditAddressDialog : ComponentBase public partial class EditAddressDialog : ComponentBase
{ {
[CascadingParameter] private IDialogReference MudDialog { get; set; } = default!; [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
[Inject] private CityProto.CityContract.CityContractClient CityContract { get; set; } = default!; [Inject] private CityProto.CityContract.CityContractClient CityContract { get; set; } = default!;
+38 -10
View File
@@ -141,11 +141,22 @@
</MudHidden> </MudHidden>
@if (DeviceDetector.IsMobile()) @if (DeviceDetector.IsMobile())
{ {
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" <MudStack Spacing="1" Class="mobile-actions-stack px-5">
Class="mobile-actions-stack px-5"> <MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.subtitle2"> تخفیف کل: <b>@FormatPrice(CartData.TotalDiscount)</b></MudText> <MudText Typo="Typo.body2">جمع کالاها:</MudText>
<MudText Typo="Typo.h5"> مبلغ کل: <b>@FormatPrice(CartData.Total)</b></MudText> <MudText Typo="Typo.body2">@FormatPrice(CartData.Total) تومان</MudText>
</MudStack>
@if (VAT.IsEnabled)
{
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.caption" Class="mud-text-secondary">مالیات (@VAT.VatPercentage%):</MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">@FormatPrice(VATAmount) تومان</MudText>
</MudStack>
}
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h6" Style="font-weight:bold;">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.h6" Color="Color.Primary" Style="font-weight:bold;">@FormatPrice(TotalWithVAT) تومان</MudText>
</MudStack>
</MudStack> </MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween" Spacing="2" Class="mobile-actions-stack"> <MudStack Row="true" Justify="Justify.SpaceBetween" Spacing="2" Class="mobile-actions-stack">
@@ -159,12 +170,29 @@
} }
else else
{ {
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" <MudPaper Class="pa-4 rounded-lg" Style="background-color: var(--mud-palette-background-grey);">
Class="mobile-actions-stack px-5"> <MudStack Spacing="1">
<MudText Typo="Typo.subtitle2"> تخفیف کل: <b>@FormatPrice(CartData.TotalDiscount)</b></MudText> <MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.h5"> مبلغ کل: <b>@FormatPrice(CartData.Total)</b></MudText> <MudText Typo="Typo.body2">جمع کالاها:</MudText>
<MudText Typo="Typo.body2">@FormatPrice(CartData.Total) تومان</MudText>
</MudStack>
@if (VAT.IsEnabled)
{
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (@VAT.VatPercentage%):</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(VATAmount) تومان</MudText>
</MudStack>
}
<MudDivider Class="my-1" />
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.subtitle1" Style="font-weight:bold;">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight:bold;">@FormatPrice(TotalWithVAT) تومان</MudText>
</MudStack>
</MudStack>
</MudPaper>
<MudButton Variant="Variant.Filled" Color="Color.Primary" <MudStack Row="true" Justify="Justify.FlexEnd" Spacing="2" Class="mt-3">
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">افزودن محصول OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">افزودن محصول
</MudButton> </MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Success" OnClick="ProceedCheckout" <MudButton Variant="Variant.Filled" Color="Color.Success" OnClick="ProceedCheckout"
+12 -2
View File
@@ -7,12 +7,16 @@ namespace FrontOffice.Main.Pages.Store;
public partial class Cart : ComponentBase, IDisposable public partial class Cart : ComponentBase, IDisposable
{ {
[Inject] private CartService CartService { get; set; } = default!; [Inject] private CartService CartService { get; set; } = default!;
[Inject] private VATService VAT { get; set; } = default!;
// Navigation and Snackbar are available via _Imports.razor // Navigation and Snackbar are available via _Imports.razor
private CartService CartData => CartService; private CartService CartData => CartService;
protected override void OnInitialized() protected override async Task OnInitializedAsync()
{ {
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
await CartService.EnsureInitializedAsync();
CartService.OnChange += StateHasChanged; CartService.OnChange += StateHasChanged;
await VAT.LoadAsync();
} }
private async Task IncrementQty(long productId) private async Task IncrementQty(long productId)
@@ -44,7 +48,13 @@ public partial class Cart : ComponentBase, IDisposable
Navigation.NavigateTo(RouteConstants.Store.CheckoutSummary); Navigation.NavigateTo(RouteConstants.Store.CheckoutSummary);
} }
private static string FormatPrice(long price) => string.Format("{0:N0} ", price); private string FormatPrice(long price) => $"{price:N0} ";
private string FormatPriceWithVAT(long price) => $"{VAT.AddVAT(price):N0} ";
private long TotalWithVAT => VAT.AddVAT(CartData.Total);
private long VATAmount => VAT.CalculateVAT(CartData.Total);
private static string GetProductImageUrl(string? imageUrl) private static string GetProductImageUrl(string? imageUrl)
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl; => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl;
@@ -108,14 +108,17 @@
<MudText Typo="Typo.body2">جمع کالاها:</MudText> <MudText Typo="Typo.body2">جمع کالاها:</MudText>
<MudText Typo="Typo.body2">@FormatPrice(Cart.Total)</MudText> <MudText Typo="Typo.body2">@FormatPrice(Cart.Total)</MudText>
</MudStack> </MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween"> @if (VAT.IsEnabled)
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (۹%):</MudText> {
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(CalculateVAT())</MudText> <MudStack Row="true" Justify="Justify.SpaceBetween">
</MudStack> <MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (@VAT.VatPercentage%):</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(CalculateVAT())</MudText>
</MudStack>
}
<MudDivider Class="my-1" /> <MudDivider Class="my-1" />
<MudStack Row="true" Justify="Justify.SpaceBetween"> <MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText> <MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(Cart.Total + CalculateVAT())</MudText> <MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(VAT.AddVAT(Cart.Total))</MudText>
</MudStack> </MudStack>
</MudStack> </MudStack>
@@ -10,6 +10,7 @@ public partial class CheckoutSummary : ComponentBase
{ {
[Inject] private CartService Cart { get; set; } = default!; [Inject] private CartService Cart { get; set; } = default!;
[Inject] private OrderService OrderService { get; set; } = default!; [Inject] private OrderService OrderService { get; set; } = default!;
[Inject] private VATService VAT { get; set; } = default!;
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
[Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!; [Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
// Snackbar and Navigation are injected via _Imports.razor // Snackbar and Navigation are injected via _Imports.razor
@@ -25,8 +26,11 @@ public partial class CheckoutSummary : ComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
await Cart.EnsureInitializedAsync();
await LoadAddresses(); await LoadAddresses();
await LoadWalletBalance(); await LoadWalletBalance();
await VAT.LoadAsync();
} }
private async Task LoadWalletBalance() private async Task LoadWalletBalance()
@@ -92,9 +96,9 @@ public partial class CheckoutSummary : ComponentBase
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
/// <summary> /// <summary>
/// محاسبه مالیات بر ارزش افزوده (۹%) /// محاسبه مالیات بر ارزش افزوده
/// </summary> /// </summary>
private long CalculateVAT() => (long)(Cart.Total * 0.09); private long CalculateVAT() => VAT.CalculateVAT(Cart.Total);
private static string GetProductImageUrl(string? imageUrl) private static string GetProductImageUrl(string? imageUrl)
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl; => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl;
@@ -78,8 +78,6 @@ else
@* نمایش جزئیات مالی *@ @* نمایش جزئیات مالی *@
@{ @{
var subtotal = _order.FactorDetails.Sum(s => s.UnitPrice.Value * s.Count.Value); var subtotal = _order.FactorDetails.Sum(s => s.UnitPrice.Value * s.Count.Value);
var vatAmount = (long)(subtotal * 0.09);
var totalWithVat = subtotal + vatAmount;
} }
<MudStack Spacing="1" Class="pa-2" Style="background-color: var(--mud-palette-background-grey); border-radius: 8px;"> <MudStack Spacing="1" Class="pa-2" Style="background-color: var(--mud-palette-background-grey); border-radius: 8px;">
<MudStack Row="true" Justify="Justify.SpaceBetween"> <MudStack Row="true" Justify="Justify.SpaceBetween">
@@ -87,15 +85,26 @@ else
<MudText Typo="Typo.body2">@FormatPrice(subtotal)</MudText> <MudText Typo="Typo.body2">@FormatPrice(subtotal)</MudText>
</MudStack> </MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween"> @if (_order.VatInfo is not null)
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (۹%):</MudText> {
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(vatAmount)</MudText> var vatPercent = (int)(_order.VatInfo.VatRate * 100);
</MudStack> <MudStack Row="true" Justify="Justify.SpaceBetween">
<MudDivider Class="my-1" /> <MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (@vatPercent%):</MudText>
<MudStack Row="true" Justify="Justify.SpaceBetween"> <MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(_order.VatInfo.VatAmount)</MudText>
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText> </MudStack>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(totalWithVat)</MudText> <MudDivider Class="my-1" />
</MudStack> <MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(_order.VatInfo.TotalAmount)</MudText>
</MudStack>
}
else
{
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(subtotal)</MudText>
</MudStack>
}
</MudStack> </MudStack>
</MudStack> </MudStack>
</MudPaper> </MudPaper>
@@ -9,6 +9,7 @@ namespace FrontOffice.Main.Pages.Store;
public partial class Orders : ComponentBase public partial class Orders : ComponentBase
{ {
[Inject] private OrderService OrderService { get; set; } = default!; [Inject] private OrderService OrderService { get; set; } = default!;
[Inject] private VATService VAT { get; set; } = default!;
private List<GetUserOrderResponse> _orders = new(); private List<GetUserOrderResponse> _orders = new();
private bool _loading; private bool _loading;
@@ -18,6 +19,7 @@ public partial class Orders : ComponentBase
_loading = true; _loading = true;
try try
{ {
await VAT.LoadAsync();
_orders = await OrderService.GetOrdersAsync(); _orders = await OrderService.GetOrdersAsync();
} }
catch (Exception ex) catch (Exception ex)
@@ -77,18 +77,51 @@ else
@* </MudStack> *@ @* </MudStack> *@
@* } *@ @* } *@
<MudDivider Class="my-2"/> <MudDivider Class="my-2"/>
<MudText Typo="Typo.h5" Color="Color.Primary">@FormatPrice(_product.Price)</MudText> <MudStack Spacing="1">
<MudText Typo="Typo.h5" Color="Color.Primary">@FormatPrice(_product.Price)</MudText>
@if (VAT.IsEnabled)
{
<MudText Typo="Typo.caption" Class="mud-text-secondary">
(شامل @VAT.VatPercentage% مالیات بر ارزش افزوده)
</MudText>
}
</MudStack>
<!-- نمایش وضعیت موجودی -->
@if (IsInStock)
{
<MudChip T="string" Color="Color.Success" Variant="Variant.Filled" Size="Size.Small">
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Size="Size.Small" Class="me-1" />
موجود در انبار (@_product.RemainingCount عدد)
</MudChip>
}
else
{
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small">
<MudIcon Icon="@Icons.Material.Filled.Cancel" Size="Size.Small" Class="me-1" />
ناموجود
</MudChip>
}
<!-- Desktop/tablet actions --> <!-- Desktop/tablet actions -->
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true"> <MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2"> @if (IsInStock)
<MudNumericField T="int" @bind-Value="_qty" Min="@MinQty" Max="@MaxQty" Immediate="true" {
HideSpinButtons="true" Style="max-width:120px"/> <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudButton Variant="Variant.Filled" Color="Color.Primary" <MudNumericField T="int" @bind-Value="_qty" Min="@MinQty" Max="@MaxQty" Immediate="true"
StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">افزودن به HideSpinButtons="true" Style="max-width:120px"/>
سبد <MudButton Variant="Variant.Filled" Color="Color.Primary"
</MudButton> StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">افزودن به
</MudStack> سبد
</MudButton>
</MudStack>
}
else
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="true">
این محصول در حال حاضر موجود نیست.
</MudAlert>
}
</MudHidden> </MudHidden>
<!-- Mobile actions use sticky bar rendered below --> <!-- Mobile actions use sticky bar rendered below -->
@@ -103,51 +136,58 @@ else
<MudPaper Class="mud-width-full mud-elevation-3 pa-3" <MudPaper Class="mud-width-full mud-elevation-3 pa-3"
Style="position:sticky;bottom:0;left:0;z-index:9;border-top-left-radius:1rem;border-top-right-radius:1rem;"> Style="position:sticky;bottom:0;left:0;z-index:9;border-top-left-radius:1rem;border-top-right-radius:1rem;">
<MudGrid Class="align-center" Justify="Justify.SpaceBetween"> @if (IsInStock)
<MudItem xs="6"> {
<MudStack Spacing="1"> <MudGrid Class="align-center" Justify="Justify.SpaceBetween">
@if (HasDiscount && OriginalPrice is not null) <MudItem xs="6">
{
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudText Typo="Typo.caption"
Class="mud-text-secondary mud-line-through">@FormatPrice(OriginalPrice.Value)</MudText>
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small"
Label="true">@($"٪{_product!.Discount}")</MudChip>
</MudStack>
}
<MudText Typo="Typo.h6" Color="Color.Primary">@FormatPrice(TotalPrice)</MudText>
</MudStack>
</MudItem>
@if (IsInCart)
{
<MudItem xs="5" Class="">
<MudPaper Class="mud-width-full d-flex align-center justify-space-between px-2"
Elevation="1" Style="border-radius:1rem;">
<MudIconButton
Icon="@(CurrentCartQuantity == 1 ? Icons.Material.Filled.Delete : Icons.Material.Filled.Remove)"
Color="Color.Error" Variant="Variant.Text" OnClick="RemoveFromCart"/>
<MudText Typo="Typo.subtitle2" Class="mud-font-weight-bold">@CurrentCartQuantity</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Add" Color="Color.Primary"
Variant="Variant.Text" Disabled="@(CurrentCartQuantity >= MaxQty)"
OnClick="AddToCart"/>
</MudPaper>
</MudItem>
}
else
{
<MudItem xs="6" >
<MudStack Spacing="1"> <MudStack Spacing="1">
@if (HasDiscount && OriginalPrice is not null)
<MudButton Variant="Variant.Filled" Size="Size.Large" Color="Color.Error" Class="mud-width-full" {
StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart"> <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
افزودن به سبد <MudText Typo="Typo.caption"
</MudButton> Class="mud-text-secondary mud-line-through">@FormatPrice(OriginalPrice.Value)</MudText>
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small"
Label="true">@($"٪{_product!.Discount}")</MudChip>
</MudStack>
}
<MudText Typo="Typo.h6" Color="Color.Primary">@FormatPrice(TotalPrice)</MudText>
</MudStack> </MudStack>
</MudItem> </MudItem>
}
</MudGrid>
@if (IsInCart)
{
<MudItem xs="5" Class="">
<MudPaper Class="mud-width-full d-flex align-center justify-space-between px-2"
Elevation="1" Style="border-radius:1rem;">
<MudIconButton
Icon="@(CurrentCartQuantity == 1 ? Icons.Material.Filled.Delete : Icons.Material.Filled.Remove)"
Color="Color.Error" Variant="Variant.Text" OnClick="RemoveFromCart"/>
<MudText Typo="Typo.subtitle2" Class="mud-font-weight-bold">@CurrentCartQuantity</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Add" Color="Color.Primary"
Variant="Variant.Text" Disabled="@(CurrentCartQuantity >= MaxQty)"
OnClick="AddToCart"/>
</MudPaper>
</MudItem>
}
else
{
<MudItem xs="6" >
<MudStack Spacing="1">
<MudButton Variant="Variant.Filled" Size="Size.Large" Color="Color.Error" Class="mud-width-full"
StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">
افزودن به سبد
</MudButton>
</MudStack>
</MudItem>
}
</MudGrid>
}
else
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Filled" Dense="true" Class="mb-0">
این محصول ناموجود است
</MudAlert>
}
</MudPaper> </MudPaper>
</MudHidden> </MudHidden>
@@ -11,6 +11,7 @@ public partial class ProductDetail : ComponentBase, IDisposable
{ {
[Inject] private ProductService ProductService { get; set; } = default!; [Inject] private ProductService ProductService { get; set; } = default!;
[Inject] private CartService Cart { get; set; } = default!; [Inject] private CartService Cart { get; set; } = default!;
[Inject] private VATService VAT { get; set; } = default!;
[Parameter] public long id { get; set; } [Parameter] public long id { get; set; }
@@ -18,12 +19,18 @@ public partial class ProductDetail : ComponentBase, IDisposable
private bool _loading; private bool _loading;
private int _qty = 1; private int _qty = 1;
private const int MinQty = 1; private const int MinQty = 1;
private const int MaxQty = 20;
// حداکثر تعداد قابل خرید بر اساس موجودی انبار
private int MaxQty => _product?.RemainingCount > 0 ? _product.RemainingCount : 0;
// آیا محصول موجود است
private bool IsInStock => _product is not null && _product.RemainingCount > 0;
private IReadOnlyList<ProductGalleryImage> _galleryItems = Array.Empty<ProductGalleryImage>(); private IReadOnlyList<ProductGalleryImage> _galleryItems = Array.Empty<ProductGalleryImage>();
private IReadOnlyList<ProductCategoryPathInfo> _categoryPaths = Array.Empty<ProductCategoryPathInfo>(); private IReadOnlyList<ProductCategoryPathInfo> _categoryPaths = Array.Empty<ProductCategoryPathInfo>();
private ProductGalleryImage? _selectedGalleryImage; private ProductGalleryImage? _selectedGalleryImage;
private readonly List<BreadcrumbItem> _breadcrumbItems = new(); private readonly List<BreadcrumbItem> _breadcrumbItems = new();
private long TotalPrice => (_product?.Price ?? 0) * _qty; private long TotalPrice => VAT.AddVAT((_product?.Price ?? 0) * _qty);
private bool HasDiscount => _product is { Discount: > 0 and < 100 }; private bool HasDiscount => _product is { Discount: > 0 and < 100 };
private long? OriginalPrice => HasDiscount && _product is not null private long? OriginalPrice => HasDiscount && _product is not null
@@ -44,8 +51,13 @@ public partial class ProductDetail : ComponentBase, IDisposable
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
await Cart.EnsureInitializedAsync();
Cart.OnChange += HandleCartChanged; Cart.OnChange += HandleCartChanged;
// بارگذاری نرخ VAT
await VAT.LoadAsync();
_loading = true; _loading = true;
_product = await ProductService.GetByIdAsync(id); _product = await ProductService.GetByIdAsync(id);
_loading = false; _loading = false;
@@ -127,7 +139,9 @@ public partial class ProductDetail : ComponentBase, IDisposable
Cart.OnChange -= HandleCartChanged; Cart.OnChange -= HandleCartChanged;
} }
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); private string FormatPrice(long price) => $"{VAT.AddVAT(price):N0} تومان";
private string FormatPriceWithoutVAT(long price) => $"{price:N0} تومان";
private static IReadOnlyList<ProductGalleryImage> BuildGalleryItems(Product product) private static IReadOnlyList<ProductGalleryImage> BuildGalleryItems(Product product)
{ {
@@ -59,8 +59,20 @@
<MudCardContent Class="d-flex flex-column pa-1 h-100"> <MudCardContent Class="d-flex flex-column pa-1 h-100">
<div style="height: 60%;background-image: url('@p.ImageUrl');background-size: cover; background-position: center;border-radius: 0.5rem"> <div style="height: 60%;background-image: url('@p.ImageUrl');background-size: cover; background-position: center;border-radius: 0.5rem; position: relative;">
@if (p.RemainingCount <= 0)
{
<div style="position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;border-radius:0.5rem;">
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small">ناموجود</MudChip>
</div>
}
else if (p.RemainingCount <= 5)
{
<MudChip T="string" Color="Color.Warning" Variant="Variant.Filled" Size="Size.Small"
Style="position:absolute;top:8px;right:8px;">
فقط @p.RemainingCount عدد
</MudChip>
}
</div> </div>
<div class="pa-1 flex-grow-1 d-flex flex-column justify-space-between"> <div class="pa-1 flex-grow-1 d-flex flex-column justify-space-between">
<MudText Typo="Typo.subtitle1">@p.Title</MudText> <MudText Typo="Typo.subtitle1">@p.Title</MudText>
@@ -73,7 +85,9 @@
@* Href="@($"{RouteConstants.Store.ProductDetail}{p.Id}")">جزئیات *@ @* Href="@($"{RouteConstants.Store.ProductDetail}{p.Id}")">جزئیات *@
@* </MudButton> *@ @* </MudButton> *@
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="() => AddToCart(p)" <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="() => AddToCart(p)"
StartIcon="@Icons.Material.Filled.AddShoppingCart">افزودن StartIcon="@Icons.Material.Filled.AddShoppingCart"
Disabled="@(p.RemainingCount <= 0)">
@(p.RemainingCount <= 0 ? "ناموجود" : "افزودن")
</MudButton> </MudButton>
</MudCardActions> </MudCardActions>
</MudCard> </MudCard>
@@ -12,6 +12,7 @@ public partial class Products : ComponentBase, IDisposable
[Inject] private ProductService ProductService { get; set; } = default!; [Inject] private ProductService ProductService { get; set; } = default!;
[Inject] private CategoryService CategoryService { get; set; } = default!; [Inject] private CategoryService CategoryService { get; set; } = default!;
[Inject] private CartService Cart { get; set; } = default!; [Inject] private CartService Cart { get; set; } = default!;
[Inject] private VATService VAT { get; set; } = default!;
private string _query = string.Empty; private string _query = string.Empty;
private bool _loading; private bool _loading;
@@ -21,8 +22,11 @@ public partial class Products : ComponentBase, IDisposable
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
await Cart.EnsureInitializedAsync();
Cart.OnChange += StateHasChanged; Cart.OnChange += StateHasChanged;
Navigation.LocationChanged += HandleLocationChanged; Navigation.LocationChanged += HandleLocationChanged;
await VAT.LoadAsync();
await Load(); await Load();
} }
@@ -47,7 +51,7 @@ public partial class Products : ComponentBase, IDisposable
await Cart.Add(p, 1); await Cart.Add(p, 1);
} }
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); private string FormatPrice(long price) => $"{VAT.AddVAT(price):N0} تومان";
public void Dispose() public void Dispose()
{ {
+5 -5
View File
@@ -58,11 +58,11 @@
<div class="d-flex align-center gap-2"> <div class="d-flex align-center gap-2">
@if (_isAuthenticated) @if (_isAuthenticated)
{ {
<MudBadge Content="@_cartCount" Color="Color.Error" Overlap="true" Visible="@(_cartCount > 0)"> @* <MudBadge Content="@_cartCount" Color="Color.Error" Overlap="true" Visible="@(_cartCount > 0)"> *@
<MudIconButton Icon="@Icons.Material.Filled.ShoppingCart" Color="Color.Inherit" @* *@
Href="@(RouteConstants.Store.Cart)" /> @* </MudBadge> *@
</MudBadge> <MudIconButton Icon="@Icons.Material.Filled.ShoppingCart" Color="@(_cartCount > 0?Color.Success:Color.Inherit)"
Href="@(RouteConstants.Store.Cart)" />
<MudMenu Icon="@Icons.Material.Filled.Person" Color="Color.Inherit" Size="Size.Medium"> <MudMenu Icon="@Icons.Material.Filled.Person" Color="Color.Inherit" Size="Size.Medium">
<MudMenuItem OnClick="NavigateToProfile" Disabled="@(!AuthService.IsCompleteRegister())"> <MudMenuItem OnClick="NavigateToProfile" Disabled="@(!AuthService.IsCompleteRegister())">
پروفایل پروفایل
@@ -38,6 +38,8 @@ public partial class MainLayout : IDisposable
if (_isAuthenticated) if (_isAuthenticated)
{ {
// لود سبد خرید فقط برای کاربر لاگین شده
await CartService.EnsureInitializedAsync();
CartService.OnChange += OnCartChanged; CartService.OnChange += OnCartChanged;
_cartCount = CartService.Count; _cartCount = CartService.Count;
} }
+61 -3
View File
@@ -1,6 +1,7 @@
using DateTimeConverterCL; using DateTimeConverterCL;
using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart; using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart;
using Google.Protobuf.WellKnownTypes; using Google.Protobuf.WellKnownTypes;
using Blazored.LocalStorage;
namespace FrontOffice.Main.Utilities; namespace FrontOffice.Main.Utilities;
@@ -16,13 +17,18 @@ public record CartItem(long cartId,long ProductId, string Title, string ImageUrl
public class CartService public class CartService
{ {
private readonly ShopingCartContract.ShopingCartContractClient _client; private readonly ShopingCartContract.ShopingCartContractClient _client;
private readonly ILocalStorageService _localStorage;
private readonly List<CartItem> _items = new(); private readonly List<CartItem> _items = new();
private const string TokenStorageKey = "auth:token";
private bool _isInitialized;
public event Action? OnChange; public event Action? OnChange;
public CartService(ShopingCartContract.ShopingCartContractClient client) public CartService(ShopingCartContract.ShopingCartContractClient client, ILocalStorageService localStorage)
{ {
_client = client; _client = client;
_ = LoadFromServerAsync(); _localStorage = localStorage;
// لود سبد خرید به صورت lazy انجام میشه - نه در constructor
} }
public IReadOnlyList<CartItem> Items => _items.AsReadOnly(); public IReadOnlyList<CartItem> Items => _items.AsReadOnly();
@@ -33,6 +39,13 @@ public class CartService
public async Task Add(Product product, int quantity = 1) public async Task Add(Product product, int quantity = 1)
{ {
if (quantity <= 0) return; if (quantity <= 0) return;
// اطمینان از لود شدن سبد خرید
await EnsureInitializedAsync();
// چک لاگین بودن کاربر
if (!await IsAuthenticatedAsync()) return;
var existing = _items.FirstOrDefault(i => i.ProductId == product.Id); var existing = _items.FirstOrDefault(i => i.ProductId == product.Id);
int newQuantity; int newQuantity;
@@ -82,11 +95,14 @@ public class CartService
public async Task UpdateQuantity(long productId, int quantity) public async Task UpdateQuantity(long productId, int quantity)
{ {
// چک لاگین بودن کاربر
if (!await IsAuthenticatedAsync()) return;
var existing = _items.FirstOrDefault(i => i.ProductId == productId); var existing = _items.FirstOrDefault(i => i.ProductId == productId);
if (existing is null) return; if (existing is null) return;
if (quantity <= 0) if (quantity <= 0)
{ {
Remove(existing.cartId); await Remove(existing.cartId);
return; return;
} }
var idx = _items.IndexOf(existing); var idx = _items.IndexOf(existing);
@@ -109,6 +125,9 @@ public class CartService
public async Task Remove(long cartId) public async Task Remove(long cartId)
{ {
// چک لاگین بودن کاربر
if (!await IsAuthenticatedAsync()) return;
_items.RemoveAll(i => i.cartId == cartId); _items.RemoveAll(i => i.cartId == cartId);
Notify(); Notify();
@@ -129,6 +148,9 @@ public class CartService
public async Task Clear() public async Task Clear()
{ {
// چک لاگین بودن کاربر
if (!await IsAuthenticatedAsync()) return;
var productIds = _items.Select(i => i.ProductId).ToList(); var productIds = _items.Select(i => i.ProductId).ToList();
_items.Clear(); _items.Clear();
Notify(); Notify();
@@ -152,8 +174,44 @@ public class CartService
private void Notify() => OnChange?.Invoke(); 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() private async Task LoadFromServerAsync()
{ {
// ابتدا چک کن که کاربر لاگین کرده
if (!await IsAuthenticatedAsync())
{
return;
}
try try
{ {
var response = await _client.GetAllUserCartAsync(new Empty()); 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; 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);
}
}