feat: implement VAT management service and integrate VAT calculations across components
This commit is contained in:
@@ -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>);
|
||||||
|
|||||||
@@ -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) -->
|
||||||
|
|||||||
@@ -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">
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||||
|
}
|
||||||
|
else if (_features == null || !_features.Any())
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info">
|
||||||
|
هنوز ویژگیای برای شما فعال نشده است.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudPaper Elevation="2" Class="pa-2 rounded-lg">
|
||||||
|
<MudList T="ClubFeatureDto" Dense="true">
|
||||||
|
@foreach (var feature in _features)
|
||||||
|
{
|
||||||
|
<MudListItem>
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Style="width: 100%;">
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Discount" Color="Color.Primary" Size="Size.Large" />
|
<MudIcon Icon="@Icons.Material.Filled.CheckCircle"
|
||||||
<MudText Typo="Typo.h6" Color="Color.Primary">تخفیفهای ویژه</MudText>
|
Color="@(feature.IsEnabled ? Color.Success : Color.Default)" />
|
||||||
|
<MudText>@feature.Title</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudText Typo="Typo.body2">
|
<MudButton Variant="Variant.Text"
|
||||||
• تخفیف 10% تا 30% برای تمام محصولات<br />
|
Color="Color.Primary"
|
||||||
• تخفیفهای فصلی و مناسبتی اختصاصی<br />
|
Size="Size.Small"
|
||||||
• پیشنهادات ویژه برای اعضای باشگاه<br />
|
OnClick="@(() => ShowFeatureDetails(feature))">
|
||||||
• کد تخفیف اختصاصی ماهانه
|
جزئیات
|
||||||
</MudText>
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
</MudListItem>
|
||||||
|
@if (feature != _features.Last())
|
||||||
|
{
|
||||||
|
<MudDivider />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudList>
|
||||||
</MudPaper>
|
</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.Diamond" Color="Color.Secondary" Size="Size.Large" />
|
|
||||||
<MudText Typo="Typo.h6" Color="Color.Secondary">امتیاز خرید</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.LocalShipping" Color="Color.Success" Size="Size.Large" />
|
|
||||||
<MudText Typo="Typo.h6" Color="Color.Success">ارسال رایگان</MudText>
|
|
||||||
</MudStack>
|
|
||||||
<MudText Typo="Typo.body2">
|
|
||||||
• ارسال رایگان برای خریدهای بالای 500 هزار تومان<br />
|
|
||||||
• اولویت در ارسال سفارشات<br />
|
|
||||||
• ارسال اکسپرس با تخفیف 50%<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.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">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Discount" Color="Color.Primary" Size="Size.Large" />
|
|
||||||
<MudStack Spacing="0">
|
<MudStack Spacing="0">
|
||||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary">تخفیف ویژه</MudText>
|
<MudText Typo="Typo.subtitle1">شارژ کیف پول فروشگاه تخفیفی</MudText>
|
||||||
<MudText Typo="Typo.caption">تا 30% تخفیف</MudText>
|
<MudText Typo="Typo.caption" Color="Color.Default">شارژ ۵۶ میلیون تومان کیف پول فروشگاه تخفیفی</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
<MudDivider />
|
||||||
</MudItem>
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Primary" Size="Size.Large" />
|
||||||
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="background-color: var(--mud-palette-secondary-lighten);">
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Diamond" Color="Color.Secondary" Size="Size.Large" />
|
|
||||||
<MudStack Spacing="0">
|
<MudStack Spacing="0">
|
||||||
<MudText Typo="Typo.subtitle1" Color="Color.Secondary">امتیاز خرید</MudText>
|
<MudText Typo="Typo.subtitle1">عضویت در شبکه بازاریابی</MudText>
|
||||||
<MudText Typo="Typo.caption">برای هر خرید</MudText>
|
<MudText Typo="Typo.caption" Color="Color.Default">عضویت در شبکه بازاریابی و دریافت پورسانت</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
<MudDivider />
|
||||||
</MudItem>
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudIcon Icon="@Icons.Material.Filled.GroupAdd" Color="Color.Secondary" Size="Size.Large" />
|
||||||
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="background-color: var(--mud-palette-success-lighten);">
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.LocalShipping" Color="Color.Success" Size="Size.Large" />
|
|
||||||
<MudStack Spacing="0">
|
<MudStack Spacing="0">
|
||||||
<MudText Typo="Typo.subtitle1" Color="Color.Success">ارسال رایگان</MudText>
|
<MudText Typo="Typo.subtitle1">جذب زیرمجموعه</MudText>
|
||||||
<MudText Typo="Typo.caption">برای سفارشات بالای 500 هزار تومان</MudText>
|
<MudText Typo="Typo.caption" Color="Color.Default">امکان جذب زیرمجموعه و گسترش شبکه</MudText>
|
||||||
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</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!;
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
@if (VAT.IsEnabled)
|
||||||
|
{
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (۹%):</MudText>
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (@VAT.VatPercentage%):</MudText>
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(CalculateVAT())</MudText>
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(CalculateVAT())</MudText>
|
||||||
</MudStack>
|
</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>
|
||||||
|
|
||||||
|
@if (_order.VatInfo is not null)
|
||||||
|
{
|
||||||
|
var vatPercent = (int)(_order.VatInfo.VatRate * 100);
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (۹%):</MudText>
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (@vatPercent%):</MudText>
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(vatAmount)</MudText>
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(_order.VatInfo.VatAmount)</MudText>
|
||||||
</MudStack>
|
</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(totalWithVat)</MudText>
|
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(_order.VatInfo.TotalAmount)</MudText>
|
||||||
</MudStack>
|
</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,10 +77,36 @@ else
|
|||||||
@* </MudStack> *@
|
@* </MudStack> *@
|
||||||
@* } *@
|
@* } *@
|
||||||
<MudDivider Class="my-2"/>
|
<MudDivider Class="my-2"/>
|
||||||
|
<MudStack Spacing="1">
|
||||||
<MudText Typo="Typo.h5" Color="Color.Primary">@FormatPrice(_product.Price)</MudText>
|
<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">
|
||||||
|
@if (IsInStock)
|
||||||
|
{
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
<MudNumericField T="int" @bind-Value="_qty" Min="@MinQty" Max="@MaxQty" Immediate="true"
|
<MudNumericField T="int" @bind-Value="_qty" Min="@MinQty" Max="@MaxQty" Immediate="true"
|
||||||
HideSpinButtons="true" Style="max-width:120px"/>
|
HideSpinButtons="true" Style="max-width:120px"/>
|
||||||
@@ -89,6 +115,13 @@ else
|
|||||||
سبد
|
سبد
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudStack>
|
</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,6 +136,8 @@ 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;">
|
||||||
|
|
||||||
|
@if (IsInStock)
|
||||||
|
{
|
||||||
<MudGrid Class="align-center" Justify="Justify.SpaceBetween">
|
<MudGrid Class="align-center" Justify="Justify.SpaceBetween">
|
||||||
<MudItem xs="6">
|
<MudItem xs="6">
|
||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
@@ -138,7 +173,6 @@ else
|
|||||||
{
|
{
|
||||||
<MudItem xs="6" >
|
<MudItem xs="6" >
|
||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
|
|
||||||
<MudButton Variant="Variant.Filled" Size="Size.Large" Color="Color.Error" Class="mud-width-full"
|
<MudButton Variant="Variant.Filled" Size="Size.Large" Color="Color.Error" Class="mud-width-full"
|
||||||
StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">
|
StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">
|
||||||
افزودن به سبد
|
افزودن به سبد
|
||||||
@@ -147,7 +181,13 @@ else
|
|||||||
</MudItem>
|
</MudItem>
|
||||||
}
|
}
|
||||||
</MudGrid>
|
</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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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"
|
@* *@
|
||||||
|
@* </MudBadge> *@
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.ShoppingCart" Color="@(_cartCount > 0?Color.Success:Color.Inherit)"
|
||||||
Href="@(RouteConstants.Store.Cart)" />
|
Href="@(RouteConstants.Store.Cart)" />
|
||||||
</MudBadge>
|
|
||||||
|
|
||||||
<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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user