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
+166 -125
View File
@@ -1,6 +1,8 @@
@attribute [Route(RouteConstants.Club.Features)]
@using FrontOffice.Main.Utilities
@using MudBlazor
@inject ClubConfigurationService ClubConfigService
@inject IDialogService DialogService
<PageTitle>ویژگی‌های باشگاه مشتریان</PageTitle>
@@ -11,142 +13,181 @@
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Club.Membership">بازگشت</MudButton>
</MudStack>
<!-- توضیحات کلی باشگاه -->
<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>
</MudPaper>
<MudGrid Spacing="3">
<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.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">
<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>
<!-- لیست فیچرها -->
<MudText Typo="Typo.h6" Class="mt-4">ویژگی‌های شما</MudText>
@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">
<MudIcon Icon="@Icons.Material.Filled.CheckCircle"
Color="@(feature.IsEnabled ? Color.Success : Color.Default)" />
<MudText>@feature.Title</MudText>
</MudStack>
<MudButton Variant="Variant.Text"
Color="Color.Primary"
Size="Size.Small"
OnClick="@(() => ShowFeatureDetails(feature))">
جزئیات
</MudButton>
</MudStack>
</MudListItem>
@if (feature != _features.Last())
{
<MudDivider />
}
}
</MudList>
</MudPaper>
}
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
FullWidth="true"
Size="Size.Large"
StartIcon="@Icons.Material.Filled.CheckCircle"
StartIcon="@Icons.Material.Filled.ArrowBack"
Href="@RouteConstants.Club.Membership">
همین حالا عضو شوید
بازگشت به صفحه باشگاه
</MudButton>
</MudStack>
</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">
<MudText>شما هنوز عضو باشگاه مشتریان نیستید. برای استفاده از مزایای ویژه، اکنون فعال کنید!</MudText>
</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>
</MudPaper>
@@ -63,41 +76,31 @@
<!-- مزایای باشگاه -->
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">مزایای عضویت باشگاه</MudText>
<MudGrid Spacing="2">
<MudItem xs="12" sm="6" md="4">
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="background-color: var(--mud-palette-primary-lighten);">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.Discount" Color="Color.Primary" Size="Size.Large" />
<MudStack Spacing="0">
<MudText Typo="Typo.subtitle1" Color="Color.Primary">تخفیف ویژه</MudText>
<MudText Typo="Typo.caption">تا 30% تخفیف</MudText>
</MudStack>
</MudStack>
</MudPaper>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<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">
<MudText Typo="Typo.subtitle1" Color="Color.Secondary">امتیاز خرید</MudText>
<MudText Typo="Typo.caption">برای هر خرید</MudText>
</MudStack>
</MudStack>
</MudPaper>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<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">
<MudText Typo="Typo.subtitle1" Color="Color.Success">ارسال رایگان</MudText>
<MudText Typo="Typo.caption">برای سفارشات بالای 500 هزار تومان</MudText>
</MudStack>
</MudStack>
</MudPaper>
</MudItem>
</MudGrid>
<MudStack Spacing="2">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
<MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Color="Color.Success" Size="Size.Large" />
<MudStack Spacing="0">
<MudText Typo="Typo.subtitle1">شارژ کیف پول فروشگاه تخفیفی</MudText>
<MudText Typo="Typo.caption" Color="Color.Default">شارژ ۵۶ میلیون تومان کیف پول فروشگاه تخفیفی</MudText>
</MudStack>
</MudStack>
<MudDivider />
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Primary" Size="Size.Large" />
<MudStack Spacing="0">
<MudText Typo="Typo.subtitle1">عضویت در شبکه بازاریابی</MudText>
<MudText Typo="Typo.caption" Color="Color.Default">عضویت در شبکه بازاریابی و دریافت پورسانت</MudText>
</MudStack>
</MudStack>
<MudDivider />
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
<MudIcon Icon="@Icons.Material.Filled.GroupAdd" Color="Color.Secondary" Size="Size.Large" />
<MudStack Spacing="0">
<MudText Typo="Typo.subtitle1">جذب زیرمجموعه</MudText>
<MudText Typo="Typo.caption" Color="Color.Default">امکان جذب زیرمجموعه و گسترش شبکه</MudText>
</MudStack>
</MudStack>
</MudStack>
</MudPaper>
<!-- فعال‌سازی یا تمدید عضویت -->
@@ -120,7 +123,7 @@
<MudAlert Severity="Severity.Error" Variant="Variant.Filled">
خطا در دریافت اطلاعات عضویت. لطفا دوباره تلاش کنید.
</MudAlert>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="LoadMembershipAsync">تلاش مجدد</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="LoadDataAsync">تلاش مجدد</MudButton>
}
</MudStack>
</MudContainer>
@@ -7,23 +7,33 @@ namespace FrontOffice.Main.Pages.Club;
public partial class MembershipPage : ComponentBase
{
[Inject] private ClubMembershipService ClubService { get; set; } = default!;
[Inject] private ClubConfigurationService ConfigService { get; set; } = default!;
private ClubMembershipDto? _membership;
private ClubConfigDto? _clubConfig;
private bool _isLoading = true;
private bool _hasError = false;
protected override async Task OnInitializedAsync()
{
await LoadMembershipAsync();
await LoadDataAsync();
}
private async Task LoadMembershipAsync()
private async Task LoadDataAsync()
{
try
{
_isLoading = true;
_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)
{
@@ -38,7 +48,7 @@ public partial class MembershipPage : ComponentBase
private async Task HandleActivationSuccess()
{
await LoadMembershipAsync();
await LoadDataAsync();
Snackbar.Add("عضویت باشگاه با موفقیت فعال شد!", Severity.Success);
}
@@ -40,7 +40,7 @@ public partial class Addresses : ComponentBase
{
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result;
if (!result.Canceled)
if (result is not null && !result.Canceled)
await LoadAddresses();
}
@@ -51,7 +51,7 @@ public partial class Addresses : ComponentBase
{ x => x.Model, address }
});
var result = await dialog.Result;
if (!result.Canceled)
if (result is not null && !result.Canceled)
await LoadAddresses();
}
@@ -1,5 +1,5 @@
<MudDialog>
<MudDialog >
<TitleContent>
<MudText Typo="Typo.h4" Align="Align.Center">افزودن آدرس جدید</MudText>
</TitleContent>
@@ -9,7 +9,7 @@ namespace FrontOffice.Main.Pages.Profile.Components;
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 CityProto.CityContract.CityContractClient CityContract { get; set; } = default!;
@@ -19,7 +19,8 @@ public partial class AddAddressDialog : ComponentBase
private CreateNewUserAddressRequest _request = new();
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)
return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>();
@@ -71,5 +72,6 @@ public partial class AddAddressDialog : ComponentBase
}
}
private void Cancel() => MudDialog.Close(DialogResult.Cancel());
}
@@ -10,7 +10,7 @@ namespace FrontOffice.Main.Pages.Profile.Components;
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 CityProto.CityContract.CityContractClient CityContract { get; set; } = default!;
+39 -11
View File
@@ -141,11 +141,22 @@
</MudHidden>
@if (DeviceDetector.IsMobile())
{
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center"
Class="mobile-actions-stack px-5">
<MudText Typo="Typo.subtitle2"> تخفیف کل: <b>@FormatPrice(CartData.TotalDiscount)</b></MudText>
<MudText Typo="Typo.h5"> مبلغ کل: <b>@FormatPrice(CartData.Total)</b></MudText>
<MudStack Spacing="1" Class="mobile-actions-stack px-5">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body2">جمع کالاها:</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 Row="true" Justify="Justify.SpaceBetween" Spacing="2" Class="mobile-actions-stack">
@@ -159,12 +170,29 @@
}
else
{
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center"
Class="mobile-actions-stack px-5">
<MudText Typo="Typo.subtitle2"> تخفیف کل: <b>@FormatPrice(CartData.TotalDiscount)</b></MudText>
<MudText Typo="Typo.h5"> مبلغ کل: <b>@FormatPrice(CartData.Total)</b></MudText>
<MudButton Variant="Variant.Filled" Color="Color.Primary"
<MudPaper Class="pa-4 rounded-lg" Style="background-color: var(--mud-palette-background-grey);">
<MudStack Spacing="1">
<MudStack Row="true" Justify="Justify.SpaceBetween">
<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>
<MudStack Row="true" Justify="Justify.FlexEnd" Spacing="2" Class="mt-3">
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">افزودن محصول
</MudButton>
<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
{
[Inject] private CartService CartService { get; set; } = default!;
[Inject] private VATService VAT { get; set; } = default!;
// Navigation and Snackbar are available via _Imports.razor
private CartService CartData => CartService;
protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
await CartService.EnsureInitializedAsync();
CartService.OnChange += StateHasChanged;
await VAT.LoadAsync();
}
private async Task IncrementQty(long productId)
@@ -44,7 +48,13 @@ public partial class Cart : ComponentBase, IDisposable
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)
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl;
@@ -108,14 +108,17 @@
<MudText Typo="Typo.body2">جمع کالاها:</MudText>
<MudText Typo="Typo.body2">@FormatPrice(Cart.Total)</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (۹%):</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(CalculateVAT())</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(CalculateVAT())</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(Cart.Total + CalculateVAT())</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(VAT.AddVAT(Cart.Total))</MudText>
</MudStack>
</MudStack>
@@ -10,6 +10,7 @@ public partial class CheckoutSummary : ComponentBase
{
[Inject] private CartService Cart { 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 UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
// Snackbar and Navigation are injected via _Imports.razor
@@ -25,8 +26,11 @@ public partial class CheckoutSummary : ComponentBase
protected override async Task OnInitializedAsync()
{
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
await Cart.EnsureInitializedAsync();
await LoadAddresses();
await LoadWalletBalance();
await VAT.LoadAsync();
}
private async Task LoadWalletBalance()
@@ -92,9 +96,9 @@ public partial class CheckoutSummary : ComponentBase
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
/// <summary>
/// محاسبه مالیات بر ارزش افزوده (۹%)
/// محاسبه مالیات بر ارزش افزوده
/// </summary>
private long CalculateVAT() => (long)(Cart.Total * 0.09);
private long CalculateVAT() => VAT.CalculateVAT(Cart.Total);
private static string GetProductImageUrl(string? 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 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 Row="true" Justify="Justify.SpaceBetween">
@@ -87,15 +85,26 @@ else
<MudText Typo="Typo.body2">@FormatPrice(subtotal)</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (۹%):</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>
@if (_order.VatInfo is not null)
{
var vatPercent = (int)(_order.VatInfo.VatRate * 100);
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (@vatPercent%):</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(_order.VatInfo.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(_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>
</MudPaper>
@@ -9,6 +9,7 @@ namespace FrontOffice.Main.Pages.Store;
public partial class Orders : ComponentBase
{
[Inject] private OrderService OrderService { get; set; } = default!;
[Inject] private VATService VAT { get; set; } = default!;
private List<GetUserOrderResponse> _orders = new();
private bool _loading;
@@ -18,6 +19,7 @@ public partial class Orders : ComponentBase
_loading = true;
try
{
await VAT.LoadAsync();
_orders = await OrderService.GetOrdersAsync();
}
catch (Exception ex)
@@ -77,18 +77,51 @@ else
@* </MudStack> *@
@* } *@
<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 -->
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudNumericField T="int" @bind-Value="_qty" Min="@MinQty" Max="@MaxQty" Immediate="true"
HideSpinButtons="true" Style="max-width:120px"/>
<MudButton Variant="Variant.Filled" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">افزودن به
سبد
</MudButton>
</MudStack>
@if (IsInStock)
{
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudNumericField T="int" @bind-Value="_qty" Min="@MinQty" Max="@MaxQty" Immediate="true"
HideSpinButtons="true" Style="max-width:120px"/>
<MudButton Variant="Variant.Filled" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">افزودن به
سبد
</MudButton>
</MudStack>
}
else
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="true">
این محصول در حال حاضر موجود نیست.
</MudAlert>
}
</MudHidden>
<!-- Mobile actions use sticky bar rendered below -->
@@ -103,51 +136,58 @@ else
<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;">
<MudGrid Class="align-center" Justify="Justify.SpaceBetween">
<MudItem xs="6">
<MudStack Spacing="1">
@if (HasDiscount && OriginalPrice is not null)
{
<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" >
@if (IsInStock)
{
<MudGrid Class="align-center" Justify="Justify.SpaceBetween">
<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>
@if (HasDiscount && OriginalPrice is not null)
{
<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>
}
</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>
</MudHidden>
@@ -11,6 +11,7 @@ public partial class ProductDetail : ComponentBase, IDisposable
{
[Inject] private ProductService ProductService { get; set; } = default!;
[Inject] private CartService Cart { get; set; } = default!;
[Inject] private VATService VAT { get; set; } = default!;
[Parameter] public long id { get; set; }
@@ -18,12 +19,18 @@ public partial class ProductDetail : ComponentBase, IDisposable
private bool _loading;
private int _qty = 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<ProductCategoryPathInfo> _categoryPaths = Array.Empty<ProductCategoryPathInfo>();
private ProductGalleryImage? _selectedGalleryImage;
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 long? OriginalPrice => HasDiscount && _product is not null
@@ -44,8 +51,13 @@ public partial class ProductDetail : ComponentBase, IDisposable
protected override async Task OnInitializedAsync()
{
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
await Cart.EnsureInitializedAsync();
Cart.OnChange += HandleCartChanged;
// بارگذاری نرخ VAT
await VAT.LoadAsync();
_loading = true;
_product = await ProductService.GetByIdAsync(id);
_loading = false;
@@ -127,7 +139,9 @@ public partial class ProductDetail : ComponentBase, IDisposable
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)
{
@@ -59,8 +59,20 @@
<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 class="pa-1 flex-grow-1 d-flex flex-column justify-space-between">
<MudText Typo="Typo.subtitle1">@p.Title</MudText>
@@ -73,7 +85,9 @@
@* Href="@($"{RouteConstants.Store.ProductDetail}{p.Id}")">جزئیات *@
@* </MudButton> *@
<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>
</MudCardActions>
</MudCard>
@@ -12,6 +12,7 @@ public partial class Products : ComponentBase, IDisposable
[Inject] private ProductService ProductService { get; set; } = default!;
[Inject] private CategoryService CategoryService { get; set; } = default!;
[Inject] private CartService Cart { get; set; } = default!;
[Inject] private VATService VAT { get; set; } = default!;
private string _query = string.Empty;
private bool _loading;
@@ -21,8 +22,11 @@ public partial class Products : ComponentBase, IDisposable
protected override async Task OnInitializedAsync()
{
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
await Cart.EnsureInitializedAsync();
Cart.OnChange += StateHasChanged;
Navigation.LocationChanged += HandleLocationChanged;
await VAT.LoadAsync();
await Load();
}
@@ -47,7 +51,7 @@ public partial class Products : ComponentBase, IDisposable
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()
{