feat: Implement role-based access control and manual payment management
- Added role-based permission checks in NavMenu for dashboard and management links. - Created ManualPaymentDialog for creating and managing manual payments. - Implemented TransactionDetailsDialog for displaying transaction details. - Developed Transactions page for viewing and filtering transactions. - Introduced RolePermissionsDialog for managing role permissions. - Established AuthorizationService to handle permission checks and user roles. - Enhanced UI components with MudBlazor for better user experience.
This commit is contained in:
@@ -59,6 +59,7 @@ public static class ConfigureServices
|
||||
services.AddGrpcServices(configuration);
|
||||
|
||||
// Application Services
|
||||
services.AddScoped<BackOffice.Services.Authorization.IAuthorizationService, BackOffice.Services.Authorization.AuthorizationService>();
|
||||
services.AddScoped<IDiscountProductService, DiscountProductService>();
|
||||
services.AddScoped<IDiscountCategoryService, DiscountCategoryService>();
|
||||
services.AddScoped<IDiscountOrderService, DiscountOrderService>();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<MudText Typo="Typo.h6">لیست اعضا</MudText>
|
||||
<MudSpacer />
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudSwitch @bind-Value="_filterIsActive"
|
||||
<MudSwitch T="bool" @bind-Value="_filterIsActive"
|
||||
Color="Color.Success"
|
||||
Label="فقط فعالها" />
|
||||
<MudButton Variant="Variant.Filled"
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
@using BackOffice.BFF.ManualPayment.Protobuf
|
||||
@using BackOffice.Pages.Payment.Components
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@if (Mode == ManualPaymentDialogMode.Create)
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h6">ثبت پرداخت دستی جدید</MudText>
|
||||
|
||||
<MudNumericField T="long"
|
||||
Label="شناسه کاربر"
|
||||
Variant="Variant.Outlined"
|
||||
@bind-Value="_createModel.UserId" />
|
||||
|
||||
<MudNumericField T="long"
|
||||
Label="مبلغ"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentText="ریال"
|
||||
@bind-Value="_createModel.Amount" />
|
||||
|
||||
<MudTextField T="string"
|
||||
Label="نوع / توضیح نوع"
|
||||
Variant="Variant.Outlined"
|
||||
@bind-Value="_createModel.TypeDisplay" />
|
||||
|
||||
<MudTextField T="string"
|
||||
Label="توضیحات"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="3"
|
||||
@bind-Value="_createModel.Description" />
|
||||
|
||||
<MudTextField T="string"
|
||||
Label="شماره مرجع (اختیاری)"
|
||||
Variant="Variant.Outlined"
|
||||
@bind-Value="_createModel.ReferenceNumber" />
|
||||
</MudStack>
|
||||
}
|
||||
else if (Model is not null)
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h6">جزئیات پرداخت دستی</MudText>
|
||||
<MudText Typo="Typo.body2">شناسه: @Model.Id</MudText>
|
||||
<MudText Typo="Typo.body2">کاربر: @Model.UserFullName (@Model.UserId)</MudText>
|
||||
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.body2">نوع: @Model.TypeDisplay</MudText>
|
||||
<MudText Typo="Typo.body2">وضعیت: @Model.StatusDisplay</MudText>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.ReferenceNumber))
|
||||
{
|
||||
<MudText Typo="Typo.body2">شماره مرجع: @Model.ReferenceNumber</MudText>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.body2">توضیحات: @Model.Description</MudText>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.RejectionReason))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error">
|
||||
دلیل رد: @Model.RejectionReason
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
@if (Model.Status == (int)ManualPaymentDialogStatus.Pending)
|
||||
{
|
||||
<MudTextField T="string"
|
||||
Label="یادداشت تایید / دلیل رد"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="3"
|
||||
@bind-Value="_adminNote" />
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Default" OnClick="Close">
|
||||
بستن
|
||||
</MudButton>
|
||||
|
||||
@if (Mode == ManualPaymentDialogMode.Create)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="CreateAsync">
|
||||
ثبت
|
||||
</MudButton>
|
||||
}
|
||||
else if (Model is not null && Model.Status == (int)ManualPaymentDialogStatus.Pending)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Success" OnClick="ApproveAsync">
|
||||
تایید
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="RejectAsync">
|
||||
رد
|
||||
</MudButton>
|
||||
}
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
using BackOffice.BFF.ManualPayment.Protobuf;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.Payment.Components;
|
||||
|
||||
public enum ManualPaymentDialogMode
|
||||
{
|
||||
Create = 0,
|
||||
Details = 1
|
||||
}
|
||||
|
||||
public enum ManualPaymentDialogStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Approved = 1,
|
||||
Rejected = 2,
|
||||
Cancelled = 3
|
||||
}
|
||||
|
||||
public partial class ManualPaymentDialog
|
||||
{
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Inject] public ManualPaymentContract.ManualPaymentContractClient ManualPaymentClient { get; set; } = default!;
|
||||
[Inject] public ISnackbar Snackbar { get; set; } = default!;
|
||||
|
||||
[Parameter] public ManualPaymentDialogMode Mode { get; set; }
|
||||
[Parameter] public ManualPaymentModel? Model { get; set; }
|
||||
|
||||
private ManualPaymentModel _createModel = new();
|
||||
private string? _adminNote;
|
||||
|
||||
private async Task CreateAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new CreateManualPaymentRequest
|
||||
{
|
||||
UserId = _createModel.UserId,
|
||||
Amount = _createModel.Amount,
|
||||
Type = _createModel.Type,
|
||||
Description = _createModel.Description
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_createModel.ReferenceNumber))
|
||||
{
|
||||
request.ReferenceNumber = new Google.Protobuf.WellKnownTypes.StringValue
|
||||
{
|
||||
Value = _createModel.ReferenceNumber
|
||||
};
|
||||
}
|
||||
|
||||
await ManualPaymentClient.CreateManualPaymentAsync(request);
|
||||
Snackbar.Add("پرداخت دستی با موفقیت ثبت شد.", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در ثبت پرداخت دستی: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApproveAsync()
|
||||
{
|
||||
if (Model is null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var request = new ApproveManualPaymentRequest
|
||||
{
|
||||
ManualPaymentId = Model.Id
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_adminNote))
|
||||
{
|
||||
request.ApprovalNote = new Google.Protobuf.WellKnownTypes.StringValue
|
||||
{
|
||||
Value = _adminNote
|
||||
};
|
||||
}
|
||||
|
||||
await ManualPaymentClient.ApproveManualPaymentAsync(request);
|
||||
Snackbar.Add("پرداخت دستی تایید شد.", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در تایید پرداخت دستی: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RejectAsync()
|
||||
{
|
||||
if (Model is null) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_adminNote))
|
||||
{
|
||||
Snackbar.Add("لطفاً دلیل رد را وارد کنید.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new RejectManualPaymentRequest
|
||||
{
|
||||
ManualPaymentId = Model.Id,
|
||||
RejectionReason = _adminNote
|
||||
};
|
||||
|
||||
await ManualPaymentClient.RejectManualPaymentAsync(request);
|
||||
Snackbar.Add("پرداخت دستی رد شد.", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در رد پرداخت دستی: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void Close() => MudDialog.Close(DialogResult.Cancel());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
@namespace BackOffice.Pages.Payment.Components
|
||||
|
||||
@using MudBlazor
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@if (Model is not null)
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h6">جزئیات تراکنش</MudText>
|
||||
<MudText Typo="Typo.body2">شناسه: @Model.Id</MudText>
|
||||
<MudText Typo="Typo.body2">RefId: @Model.RefId</MudText>
|
||||
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.body2">وضعیت: @StatusText</MudText>
|
||||
<MudText Typo="Typo.body2">تاریخ پرداخت: @Model.PaymentDate.ToLocalTime().MiladiToJalaliWithTime()</MudText>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.BankReferenceId))
|
||||
{
|
||||
<MudText Typo="Typo.body2">BankReferenceId: @Model.BankReferenceId</MudText>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.TrackingCode))
|
||||
{
|
||||
<MudText Typo="Typo.body2">TrackingCode: @Model.TrackingCode</MudText>
|
||||
}
|
||||
|
||||
@if (Model.PaymentFailed && !string.IsNullOrWhiteSpace(Model.PaymentFailedReason))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error">
|
||||
دلیل خطا: @Model.PaymentFailedReason
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Default" OnClick="Close">
|
||||
بستن
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.Payment.Components;
|
||||
|
||||
public partial class TransactionDetailsDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public TransactionDetailsModel? Model { get; set; }
|
||||
|
||||
private string StatusText => Model is null ? string.Empty : GetStatusText(Model.PaymentStatus);
|
||||
|
||||
private void Close()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
private static string GetStatusText(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => "در انتظار",
|
||||
1 => "موفق",
|
||||
2 => "ناموفق",
|
||||
3 => "بازگشت وجه",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
public class TransactionDetailsModel
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string? RefId { get; set; }
|
||||
public long Amount { get; set; }
|
||||
public int PaymentStatus { get; set; }
|
||||
public DateTime PaymentDate { get; set; }
|
||||
public string? BankReferenceId { get; set; }
|
||||
public string? TrackingCode { get; set; }
|
||||
public bool PaymentFailed { get; set; }
|
||||
public string? PaymentFailedReason { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
246
src/BackOffice/Pages/Payment/ManualPayments.razor
Normal file
246
src/BackOffice/Pages/Payment/ManualPayments.razor
Normal file
@@ -0,0 +1,246 @@
|
||||
@page "/payment/manual-payments"
|
||||
@using BackOffice.BFF.ManualPayment.Protobuf
|
||||
@using BackOffice.Pages.Payment.Components
|
||||
@using BackOffice.Common.BaseComponents
|
||||
|
||||
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||
<Filters>
|
||||
<MudNumericField T="long?"
|
||||
HideSpinButtons="true"
|
||||
Clearable="true"
|
||||
Label="شناسه کاربر"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
@bind-Value="_userIdFilter" />
|
||||
|
||||
<MudTextField T="string"
|
||||
Label="شماره مرجع"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
@bind-Value="_referenceFilter" />
|
||||
|
||||
<MudSelect T="int?"
|
||||
Clearable="true"
|
||||
Label="وضعیت"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
@bind-Value="_statusFilter">
|
||||
<MudSelectItem T="int?" Value="@(0)">در انتظار</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(1)">تایید شده</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(2)">رد شده</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(3)">لغو شده</MudSelectItem>
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect T="int?"
|
||||
Clearable="true"
|
||||
Label="نوع"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
@bind-Value="_typeFilter">
|
||||
<MudSelectItem T="int?" Value="@(1)">واریز نقدی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(2)">شارژ کیفپول تخفیف</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(3)">شارژ کیفپول شبکه</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(4)">تسویه حساب</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(5)">اصلاح خطا</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(6)">بازگشت وجه</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(99)">سایر</MudSelectItem>
|
||||
</MudSelect>
|
||||
</Filters>
|
||||
<Content>
|
||||
<MudPaper Class="pa-2 mb-2">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.subtitle1">پرداختهای دستی</MudText>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
OnClick="OpenCreateDialog">
|
||||
ثبت پرداخت دستی جدید
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<MudDataGrid T="ManualPaymentModel"
|
||||
ServerData="LoadData"
|
||||
Hover="true"
|
||||
Dense="true">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
<PropertyColumn Property="x => x.UserId" Title="شناسه کاربر" />
|
||||
<PropertyColumn Property="x => x.UserFullName" Title="نام کاربر" />
|
||||
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
|
||||
<CellTemplate>
|
||||
@context.Item.Amount.ToString("N0")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<TemplateColumn Title="نوع">
|
||||
<CellTemplate>
|
||||
@GetTypeText(context.Item.Type)
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Size="Size.Small"
|
||||
Color="@GetStatusColor(context.Item.Status)">
|
||||
@GetStatusText(context.Item.Status)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="تاریخ ایجاد">
|
||||
<CellTemplate>
|
||||
@context.Item.Created.ToLocalTime().MiladiToJalaliWithTime()
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="عملیات">
|
||||
<CellTemplate>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudTooltip Text="جزئیات">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenDetails(context.Item))" />
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
</MudDataGrid>
|
||||
</Content>
|
||||
</BasePageComponent>
|
||||
|
||||
@code {
|
||||
[Inject] public BackOffice.BFF.ManualPayment.Protobuf.ManualPaymentContract.ManualPaymentContractClient ManualPaymentClient { get; set; } = default!;
|
||||
[Inject] public IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
private BasePageComponent? _basePage;
|
||||
private long? _userIdFilter;
|
||||
private string? _referenceFilter;
|
||||
private int? _statusFilter;
|
||||
private int? _typeFilter;
|
||||
|
||||
private async Task<GridData<ManualPaymentModel>> LoadData(GridState<ManualPaymentModel> state)
|
||||
{
|
||||
var pageNumber = state.Page + 1;
|
||||
var pageSize = state.PageSize;
|
||||
|
||||
var request = new GetManualPaymentsRequest
|
||||
{
|
||||
PageNumber = pageNumber,
|
||||
PageSize = pageSize
|
||||
};
|
||||
|
||||
if (_userIdFilter.HasValue)
|
||||
{
|
||||
request.UserId = new Google.Protobuf.WellKnownTypes.Int64Value { Value = _userIdFilter.Value };
|
||||
}
|
||||
|
||||
if (_statusFilter.HasValue)
|
||||
{
|
||||
request.Status = new Google.Protobuf.WellKnownTypes.Int32Value { Value = _statusFilter.Value };
|
||||
}
|
||||
|
||||
if (_typeFilter.HasValue)
|
||||
{
|
||||
request.Type = new Google.Protobuf.WellKnownTypes.Int32Value { Value = _typeFilter.Value };
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_referenceFilter))
|
||||
{
|
||||
request.ReferenceNumber = new Google.Protobuf.WellKnownTypes.StringValue { Value = _referenceFilter };
|
||||
}
|
||||
|
||||
var response = await ManualPaymentClient.GetManualPaymentsAsync(request);
|
||||
|
||||
return new GridData<ManualPaymentModel>
|
||||
{
|
||||
Items = response.Models.ToList(),
|
||||
TotalItems = (int)response.MetaData.TotalCount
|
||||
};
|
||||
}
|
||||
|
||||
private async Task OnFilterSubmit()
|
||||
{
|
||||
await _basePage!.ReloadAsync();
|
||||
}
|
||||
|
||||
private async Task OnFilterCleared()
|
||||
{
|
||||
_userIdFilter = null;
|
||||
_referenceFilter = null;
|
||||
_statusFilter = null;
|
||||
_typeFilter = null;
|
||||
await _basePage!.ReloadAsync();
|
||||
}
|
||||
|
||||
private async Task OpenCreateDialog()
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(ManualPaymentDialog.Mode), ManualPaymentDialogMode.Create }
|
||||
};
|
||||
|
||||
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
|
||||
|
||||
var dialog = DialogService.Show<ManualPaymentDialog>("ثبت پرداخت دستی", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled)
|
||||
{
|
||||
await _basePage!.ReloadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenDetails(ManualPaymentModel model)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(ManualPaymentDialog.Mode), ManualPaymentDialogMode.Details },
|
||||
{ nameof(ManualPaymentDialog.Model), model }
|
||||
};
|
||||
|
||||
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true };
|
||||
|
||||
var dialog = DialogService.Show<ManualPaymentDialog>($"جزئیات پرداخت دستی #{model.Id}", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
if (!result.Cancelled)
|
||||
{
|
||||
await _basePage!.ReloadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetTypeText(int type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
1 => "واریز نقدی",
|
||||
2 => "شارژ کیفپول تخفیف",
|
||||
3 => "شارژ کیفپول شبکه",
|
||||
4 => "تسویه حساب",
|
||||
5 => "اصلاح خطا",
|
||||
6 => "بازگشت وجه",
|
||||
99 => "سایر",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetStatusText(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => "در انتظار",
|
||||
1 => "تایید شده",
|
||||
2 => "رد شده",
|
||||
3 => "لغو شده",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private static Color GetStatusColor(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => Color.Warning,
|
||||
1 => Color.Success,
|
||||
2 => Color.Error,
|
||||
3 => Color.Default,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
}
|
||||
82
src/BackOffice/Pages/Payment/Transactions.razor
Normal file
82
src/BackOffice/Pages/Payment/Transactions.razor
Normal file
@@ -0,0 +1,82 @@
|
||||
@page "/payment/transactions"
|
||||
@using BackOffice.Common.BaseComponents
|
||||
|
||||
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||
<Filters>
|
||||
<BackOffice.Common.BaseComponents.DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه تاریخی" />
|
||||
|
||||
<MudSelect T="int?"
|
||||
Clearable="true"
|
||||
Label="وضعیت پرداخت"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
@bind-Value="_statusFilter">
|
||||
<MudSelectItem T="int?" Value="@(0)">در انتظار</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(1)">موفق</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(2)">ناموفق</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(3)">بازگشت وجه</MudSelectItem>
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect T="int?"
|
||||
Clearable="true"
|
||||
Label="نوع تراکنش"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
@bind-Value="_typeFilter">
|
||||
<MudSelectItem T="int?" Value="@(0)">نامشخص</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(1)">خرید پکیج</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(2)">پرداخت دستی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(3)">شارژ کیفپول</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(4)">بازگشت وجه</MudSelectItem>
|
||||
</MudSelect>
|
||||
</Filters>
|
||||
<Content>
|
||||
<MudPaper Class="pa-2 mb-2">
|
||||
<MudText Typo="Typo.subtitle1">مدیریت تراکنشها</MudText>
|
||||
</MudPaper>
|
||||
|
||||
<MudDataGrid T="TransactionModel"
|
||||
ServerData="LoadData"
|
||||
Hover="true"
|
||||
Dense="true">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
<PropertyColumn Property="x => x.RefId" Title="شماره مرجع" />
|
||||
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
|
||||
<CellTemplate>
|
||||
@context.Item.Amount.ToString("N0")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<TemplateColumn Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Size="Size.Small"
|
||||
Color="@GetStatusColor(context.Item.PaymentStatus)">
|
||||
@GetStatusText(context.Item.PaymentStatus)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="تاریخ پرداخت">
|
||||
<CellTemplate>
|
||||
@context.Item.PaymentDate.ToLocalTime().MiladiToJalaliWithTime()
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="نوع">
|
||||
<CellTemplate>
|
||||
@GetTypeText(context.Item.Type)
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="عملیات">
|
||||
<CellTemplate>
|
||||
<MudTooltip Text="جزئیات">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenDetails(context.Item))" />
|
||||
</MudTooltip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
</MudDataGrid>
|
||||
</Content>
|
||||
</BasePageComponent>
|
||||
|
||||
94
src/BackOffice/Pages/Payment/Transactions.razor.cs
Normal file
94
src/BackOffice/Pages/Payment/Transactions.razor.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using BackOffice.Common.BaseComponents;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.Payment;
|
||||
|
||||
public partial class Transactions
|
||||
{
|
||||
private BasePageComponent? _basePage;
|
||||
|
||||
private DateTime? _fromDate;
|
||||
private DateTime? _toDate;
|
||||
private int? _statusFilter;
|
||||
private int? _typeFilter;
|
||||
|
||||
private async Task<GridData<TransactionModel>> LoadData(GridState<TransactionModel> state)
|
||||
{
|
||||
// TODO: Connect to BackOffice.BFF Transactions when API is ready
|
||||
await Task.CompletedTask;
|
||||
|
||||
return new GridData<TransactionModel>
|
||||
{
|
||||
Items = Array.Empty<TransactionModel>(),
|
||||
TotalItems = 0
|
||||
};
|
||||
}
|
||||
|
||||
private Task OnFilterSubmit()
|
||||
{
|
||||
_basePage?.ReloadGrid();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnFilterCleared()
|
||||
{
|
||||
_fromDate = null;
|
||||
_toDate = null;
|
||||
_statusFilter = null;
|
||||
_typeFilter = null;
|
||||
_basePage?.ReloadGrid();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OpenDetails(TransactionModel model)
|
||||
{
|
||||
// TODO: Open TransactionDetailsDialog with model
|
||||
}
|
||||
|
||||
private static string GetStatusText(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => "در انتظار",
|
||||
1 => "موفق",
|
||||
2 => "ناموفق",
|
||||
3 => "بازگشت وجه",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private static Color GetStatusColor(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => Color.Warning,
|
||||
1 => Color.Success,
|
||||
2 => Color.Error,
|
||||
3 => Color.Info,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetTypeText(int type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
1 => "خرید پکیج",
|
||||
2 => "پرداخت دستی",
|
||||
3 => "شارژ کیفپول",
|
||||
4 => "بازگشت وجه",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private class TransactionModel
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string? RefId { get; set; }
|
||||
public long Amount { get; set; }
|
||||
public int PaymentStatus { get; set; }
|
||||
public DateTime PaymentDate { get; set; }
|
||||
public int Type { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using BackOffice.BFF.Products.Protobuf.Protos.Products;
|
||||
using BackOffice.Common.BaseComponents;
|
||||
using BackOffice.Common.Utilities;
|
||||
using BackOffice.Pages.Tag.Components;
|
||||
// TODO: Uncomment when Tag proto project is created
|
||||
// using BackOffice.Pages.Tag.Components;
|
||||
using BackOffice.Pages.Products.Components;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -239,16 +240,20 @@ public partial class ProductsMainPage
|
||||
Navigation.NavigateTo($"{RouteConstance.ProductCategories}{model.Id}");
|
||||
}
|
||||
|
||||
// TODO: Enable when Tag proto project is created
|
||||
public async Task OpenTagAssignment(DataModel model)
|
||||
{
|
||||
var parameters = new DialogParameters<AssignTagsDialog>
|
||||
{
|
||||
{ x => x.ProductId, model.Id },
|
||||
{ x => x.ProductTitle, model.Title }
|
||||
};
|
||||
// var parameters = new DialogParameters<AssignTagsDialog>
|
||||
// {
|
||||
// { x => x.ProductId, model.Id },
|
||||
// { x => x.ProductTitle, model.Title }
|
||||
// };
|
||||
|
||||
await DialogService.ShowAsync<AssignTagsDialog>("مدیریت تگهای محصول", parameters,
|
||||
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true });
|
||||
// await DialogService.ShowAsync<AssignTagsDialog>("مدیریت تگهای محصول", parameters,
|
||||
// new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true });
|
||||
|
||||
await Task.CompletedTask;
|
||||
Snackbar.Add("قابلیت تگگذاری در حال توسعه است", Severity.Info);
|
||||
}
|
||||
|
||||
public async Task OpenImagePreview(string imagePath, string title)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
@namespace BackOffice.Pages.Role.Components
|
||||
|
||||
@using MudBlazor
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h6">دسترسیهای نقش @RoleName</MudText>
|
||||
|
||||
@foreach (var category in _categories)
|
||||
{
|
||||
<MudExpansionPanels Elevation="0">
|
||||
<MudExpansionPanel Text="@category.DisplayName" Expanded="false">
|
||||
<MudStack Spacing="1">
|
||||
@foreach (var permission in category.Permissions)
|
||||
{
|
||||
<MudCheckBox T="bool"
|
||||
Label="@permission.DisplayName"
|
||||
LabelPosition="LabelPosition.End"
|
||||
Color="Color.Primary"
|
||||
Value="@_selectedPermissions.Contains(permission.Key)"
|
||||
ValueChanged="(checkedValue => OnPermissionChanged(checkedValue, permission.Key))" />
|
||||
}
|
||||
</MudStack>
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
}
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Color="Color.Default" Variant="Variant.Text" OnClick="Cancel">
|
||||
انصراف
|
||||
</MudButton>
|
||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="SaveAsync">
|
||||
ذخیره
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.Role.Components;
|
||||
|
||||
public partial class RolePermissionsDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string RoleName { get; set; } = string.Empty;
|
||||
|
||||
private readonly List<PermissionCategory> _categories = new();
|
||||
private readonly HashSet<string> _selectedPermissions = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
_categories.AddRange(GetPermissionCategories());
|
||||
|
||||
// پیشانتخاب براساس RoleName (همراستا با BFF RolePermissionConfig)
|
||||
if (string.Equals(RoleName, "SuperAdmin", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (var permission in _categories.SelectMany(c => c.Permissions))
|
||||
{
|
||||
_selectedPermissions.Add(permission.Key);
|
||||
}
|
||||
}
|
||||
else if (string.Equals(RoleName, "Admin", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (var permission in _categories.SelectMany(c => c.Permissions))
|
||||
{
|
||||
if (!permission.Key.StartsWith("settings.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_selectedPermissions.Add(permission.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (string.Equals(RoleName, "Inspector", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (var permission in _categories.SelectMany(c => c.Permissions))
|
||||
{
|
||||
if (permission.Key.EndsWith(".view", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_selectedPermissions.Add(permission.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPermissionChanged(bool isChecked, string permissionKey)
|
||||
{
|
||||
if (isChecked)
|
||||
{
|
||||
_selectedPermissions.Add(permissionKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedPermissions.Remove(permissionKey);
|
||||
}
|
||||
}
|
||||
|
||||
private Task SaveAsync()
|
||||
{
|
||||
// TODO: اتصال به API واقعی RBAC در BFF/CMS برای ذخیره RolePermissions
|
||||
MudDialog.Close(DialogResult.Ok(_selectedPermissions.ToList()));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
private static IEnumerable<PermissionCategory> GetPermissionCategories()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new PermissionCategory(
|
||||
"orders",
|
||||
"سفارشها",
|
||||
new[]
|
||||
{
|
||||
new PermissionItem("orders.view", "مشاهده سفارشها"),
|
||||
new PermissionItem("orders.create", "ایجاد سفارش"),
|
||||
new PermissionItem("orders.update", "ویرایش سفارش"),
|
||||
new PermissionItem("orders.delete", "حذف سفارش"),
|
||||
new PermissionItem("orders.cancel", "لغو سفارش"),
|
||||
new PermissionItem("orders.approve", "تایید سفارش / عملیات حساس")
|
||||
}),
|
||||
new PermissionCategory(
|
||||
"products",
|
||||
"محصولات",
|
||||
new[]
|
||||
{
|
||||
new PermissionItem("products.view", "مشاهده محصولات"),
|
||||
new PermissionItem("products.create", "ایجاد محصول"),
|
||||
new PermissionItem("products.update", "ویرایش محصول"),
|
||||
new PermissionItem("products.delete", "حذف محصول")
|
||||
}),
|
||||
new PermissionCategory(
|
||||
"users",
|
||||
"کاربران",
|
||||
new[]
|
||||
{
|
||||
new PermissionItem("users.view", "مشاهده کاربران"),
|
||||
new PermissionItem("users.update", "ویرایش کاربران"),
|
||||
new PermissionItem("users.delete", "حذف کاربران")
|
||||
}),
|
||||
new PermissionCategory(
|
||||
"commission",
|
||||
"کمیسیون و برداشتها",
|
||||
new[]
|
||||
{
|
||||
new PermissionItem("commission.view", "مشاهده گزارشهای کمیسیون"),
|
||||
new PermissionItem("commission.approve_withdrawal", "تایید درخواست برداشت")
|
||||
}),
|
||||
new PermissionCategory(
|
||||
"discountshop",
|
||||
"فروشگاه تخفیفی",
|
||||
new[]
|
||||
{
|
||||
new PermissionItem("discountshop.manage", "مدیریت فروشگاه تخفیفی (محصولات/سفارشها/گزارش فروش)")
|
||||
}),
|
||||
new PermissionCategory(
|
||||
"publicmessages",
|
||||
"پیامهای عمومی",
|
||||
new[]
|
||||
{
|
||||
new PermissionItem("publicmessages.view", "مشاهده پیامهای عمومی"),
|
||||
new PermissionItem("publicmessages.create", "ایجاد پیام عمومی"),
|
||||
new PermissionItem("publicmessages.update", "ویرایش پیام عمومی"),
|
||||
new PermissionItem("publicmessages.publish", "انتشار/بایگانی پیامها")
|
||||
}),
|
||||
new PermissionCategory(
|
||||
"settings",
|
||||
"تنظیمات و سیستم",
|
||||
new[]
|
||||
{
|
||||
new PermissionItem("settings.view", "مشاهده تنظیمات"),
|
||||
new PermissionItem("settings.manage_configuration", "مدیریت تنظیمات سیستم"),
|
||||
new PermissionItem("settings.manage_vat", "مدیریت تنظیمات VAT"),
|
||||
new PermissionItem("system.alerts.view", "مشاهده هشدارهای سیستم"),
|
||||
new PermissionItem("system.health.view", "مشاهده وضعیت سلامت سیستم")
|
||||
}),
|
||||
new PermissionCategory(
|
||||
"reports",
|
||||
"گزارشها",
|
||||
new[]
|
||||
{
|
||||
new PermissionItem("reports.view", "مشاهده گزارشها")
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
private record PermissionCategory(string Key, string DisplayName, IReadOnlyList<PermissionItem> Permissions);
|
||||
|
||||
private record PermissionItem(string Key, string DisplayName);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,15 @@
|
||||
<MudTooltip Text="آرشیو">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OnDelete(context.Item))" Style="cursor:pointer;" />
|
||||
</MudTooltip>
|
||||
|
||||
<MudTooltip Text="ویرایش دسترسیها">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Security"
|
||||
Size="Size.Small"
|
||||
Color="Color.Primary"
|
||||
ButtonType="ButtonType.Button"
|
||||
OnClick="@(() => EditPermissions(context.Item))"
|
||||
Style="cursor:pointer;" />
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
@@ -56,4 +65,3 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -93,4 +93,22 @@ public partial class RoleMainPage
|
||||
_request = new() { Filter = new() { } };
|
||||
ReLoadData();
|
||||
}
|
||||
|
||||
private async Task EditPermissions(DataModel model)
|
||||
{
|
||||
var parameters = new DialogParameters<RolePermissionsDialog>
|
||||
{
|
||||
{ x => x.RoleName, model.Name }
|
||||
};
|
||||
|
||||
var options = new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
FullWidth = true,
|
||||
MaxWidth = MaxWidth.Medium
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<RolePermissionsDialog>($"دسترسیهای نقش {model.Title}", parameters, options);
|
||||
await dialog.Result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
<MudSelectItem Value="@("en")">English</MudSelectItem>
|
||||
</MudSelect>
|
||||
|
||||
<MudSwitch @bind-Value="_darkMode"
|
||||
<MudSwitch T="bool" @bind-Value="_darkMode"
|
||||
Color="Color.Primary"
|
||||
Label="حالت تاریک" />
|
||||
|
||||
<MudSwitch @bind-Value="_compactMode"
|
||||
<MudSwitch T="bool" @bind-Value="_compactMode"
|
||||
Color="Color.Secondary"
|
||||
Label="حالت فشرده" />
|
||||
|
||||
@@ -67,15 +67,15 @@
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudSwitch @bind-Value="_emailNotifications"
|
||||
<MudSwitch T="bool" @bind-Value="_emailNotifications"
|
||||
Color="Color.Primary"
|
||||
Label="دریافت اعلانهای ایمیل" />
|
||||
|
||||
<MudSwitch @bind-Value="_smsNotifications"
|
||||
<MudSwitch T="bool" @bind-Value="_smsNotifications"
|
||||
Color="Color.Secondary"
|
||||
Label="دریافت اعلانهای پیامک" />
|
||||
|
||||
<MudSwitch @bind-Value="_systemNotifications"
|
||||
<MudSwitch T="bool" @bind-Value="_systemNotifications"
|
||||
Color="Color.Info"
|
||||
Label="اعلانهای سیستمی" />
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudSwitch @bind-Value="_twoFactorEnabled"
|
||||
<MudSwitch T="bool" @bind-Value="_twoFactorEnabled"
|
||||
Color="Color.Success"
|
||||
Label="فعالسازی احراز هویت دو مرحلهای" />
|
||||
|
||||
|
||||
@@ -81,13 +81,13 @@
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="_commissionConfig.AutoCalculationEnabled"
|
||||
<MudSwitch T="bool" @bind-Value="_commissionConfig.AutoCalculationEnabled"
|
||||
Color="Color.Primary"
|
||||
Label="محاسبه خودکار هفتگی فعال باشد" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="_commissionConfig.AutoPayoutEnabled"
|
||||
<MudSwitch T="bool" @bind-Value="_commissionConfig.AutoPayoutEnabled"
|
||||
Color="Color.Primary"
|
||||
Label="پرداخت خودکار فعال باشد" />
|
||||
</MudItem>
|
||||
@@ -158,19 +158,19 @@
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="_networkConfig.BinaryTreeEnabled"
|
||||
<MudSwitch T="bool" @bind-Value="_networkConfig.BinaryTreeEnabled"
|
||||
Color="Color.Primary"
|
||||
Label="سیستم باینری فعال باشد" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="_networkConfig.AutoPlacementEnabled"
|
||||
<MudSwitch T="bool" @bind-Value="_networkConfig.AutoPlacementEnabled"
|
||||
Color="Color.Primary"
|
||||
Label="جایگذاری خودکار در درخت" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="_networkConfig.SpilloverEnabled"
|
||||
<MudSwitch T="bool" @bind-Value="_networkConfig.SpilloverEnabled"
|
||||
Color="Color.Primary"
|
||||
Label="Spillover فعال باشد"
|
||||
HelperText="اعضای اضافی به پایین درخت منتقل شوند" />
|
||||
@@ -244,19 +244,19 @@
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="_clubConfig.AutoRenewalEnabled"
|
||||
<MudSwitch T="bool" @bind-Value="_clubConfig.AutoRenewalEnabled"
|
||||
Color="Color.Primary"
|
||||
Label="تمدید خودکار عضویت" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="_clubConfig.EmailNotificationsEnabled"
|
||||
<MudSwitch T="bool" @bind-Value="_clubConfig.EmailNotificationsEnabled"
|
||||
Color="Color.Primary"
|
||||
Label="ارسال ایمیل یادآوری انقضا" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="_clubConfig.SmsNotificationsEnabled"
|
||||
<MudSwitch T="bool" @bind-Value="_clubConfig.SmsNotificationsEnabled"
|
||||
Color="Color.Primary"
|
||||
Label="ارسال پیامک یادآوری انقضا" />
|
||||
</MudItem>
|
||||
@@ -372,6 +372,38 @@
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Class="mt-4" Elevation="0">
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h6" Class="mb-2">تنظیمات مالیات بر ارزش افزوده (VAT)</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary" Class="mb-2">
|
||||
درصد پیشفرض VAT که روی سفارشها اعمال میشود.
|
||||
</MudText>
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudNumericField @bind-Value="_vatConfig.DefaultVatPercentage"
|
||||
Label="درصد VAT پیشفرض"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentText="%"
|
||||
Min="0"
|
||||
Max="100" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudDivider Class="my-4" />
|
||||
|
||||
<MudStack Row="true" Justify="Justify.FlexEnd" Spacing="2">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="SaveVatConfig"
|
||||
StartIcon="@Icons.Material.Filled.Save">
|
||||
ذخیره تنظیمات VAT
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
</MudContainer>
|
||||
@@ -383,6 +415,7 @@
|
||||
private NetworkConfig _networkConfig = new();
|
||||
private ClubConfig _clubConfig = new();
|
||||
private SystemConfig _systemConfig = new();
|
||||
private VatConfig _vatConfig = new();
|
||||
|
||||
private Dictionary<string, string> _configurations = new();
|
||||
|
||||
@@ -453,6 +486,11 @@
|
||||
TwoFactorAuthEnabled = GetBoolConfig("System.TwoFactorAuth", true),
|
||||
EmailVerificationRequired = GetBoolConfig("System.EmailVerification", true)
|
||||
};
|
||||
|
||||
_vatConfig = new VatConfig
|
||||
{
|
||||
DefaultVatPercentage = GetIntConfig("DefaultVatPercentage", 10)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -543,6 +581,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveVatConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
await SaveConfig("DefaultVatPercentage", _vatConfig.DefaultVatPercentage.ToString(), 0);
|
||||
Snackbar.Add("تنظیمات VAT با موفقیت ذخیره شد", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در ذخیره تنظیمات VAT: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveConfig(string key, string value, int scope)
|
||||
{
|
||||
var request = new BackOffice.BFF.Configuration.Protobuf.CreateOrUpdateConfigurationRequest
|
||||
@@ -631,4 +682,9 @@
|
||||
public bool TwoFactorAuthEnabled { get; set; }
|
||||
public bool EmailVerificationRequired { get; set; }
|
||||
}
|
||||
|
||||
private class VatConfig
|
||||
{
|
||||
public int DefaultVatPercentage { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<MudTextField T="string" @bind-Value="Model.PostalCode" Disabled="_isLoading" Label="کد پستی" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="Model.IsDefault" Color="Color.Primary" Label="ایا پیش فرض است" LabelPosition="LabelPosition.End" />
|
||||
<MudSwitch T="bool" @bind-Value="Model.IsDefault" Color="Color.Primary" Label="ایا پیش فرض است" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
|
||||
</MudStack>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<MudTextField T="string" @bind-Value="Model.PostalCode" Disabled="_isLoading" Label="کد پستی" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="Model.IsDefault" Color="Color.Primary" Label="ایا پیش فرض است" LabelPosition="LabelPosition.End" />
|
||||
<MudSwitch T="bool" @bind-Value="Model.IsDefault" Color="Color.Primary" Label="ایا پیش فرض است" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
Required="true"
|
||||
Lines="3" />
|
||||
|
||||
<MudSwitch @bind-Checked="_refundPayment" Color="Color.Primary" />
|
||||
<MudSwitch T="bool" @bind-Value="_refundPayment" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body2">
|
||||
بازگشت وجه به کاربر (در صورت پرداخت موفق)
|
||||
</MudText>
|
||||
|
||||
@@ -51,6 +51,21 @@
|
||||
}
|
||||
</MudText>
|
||||
|
||||
@if (_model.VatAmount > 0)
|
||||
{
|
||||
<MudDivider Class="my-2" />
|
||||
<MudText Typo="Typo.subtitle2">جزئیات مالیات بر ارزش افزوده</MudText>
|
||||
<MudText Typo="Typo.body2">
|
||||
مبلغ کالا (قبل از مالیات): @_model.VatBaseAmount.ToString("N0") تومان
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2">
|
||||
مالیات (@_model.VatPercentage.ToString("0")٪): @_model.VatAmount.ToString("N0") تومان
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2">
|
||||
مبلغ نهایی (شامل مالیات): @_model.VatTotalAmount.ToString("N0") تومان
|
||||
</MudText>
|
||||
}
|
||||
|
||||
<MudDivider Class="my-2" />
|
||||
<MudText Typo="Typo.subtitle2">Timeline وضعیت</MudText>
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
||||
|
||||
@@ -131,6 +131,16 @@
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه"/>
|
||||
<PropertyColumn Property="x => x.Amount" Title="مبلغ"/>
|
||||
<PropertyColumn Property="x => x.VatAmount" Title="مبلغ VAT">
|
||||
<CellTemplate>
|
||||
@(context.Item.VatAmount > 0 ? context.Item.VatAmount.ToString("N0") : "-")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.VatPercentage" Title="درصد VAT">
|
||||
<CellTemplate>
|
||||
@(context.Item.VatPercentage > 0 ? $"{context.Item.VatPercentage:0}٪" : "-")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.UserFullName" Title="نام کاربر"/>
|
||||
<PropertyColumn Property="x => x.UserNationalCode" Title="کدملی"/>
|
||||
<TemplateColumn Title="روش پرداخت">
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Security.Claims;
|
||||
using Blazored.LocalStorage;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
namespace BackOffice.Services.Authorization;
|
||||
|
||||
public class AuthorizationService : IAuthorizationService
|
||||
{
|
||||
private const string PermissionsCacheKey = "BackOffice.Permissions";
|
||||
private readonly ILocalStorageService _localStorage;
|
||||
private readonly AuthenticationStateProvider _authenticationStateProvider;
|
||||
|
||||
public AuthorizationService(
|
||||
ILocalStorageService localStorage,
|
||||
AuthenticationStateProvider authenticationStateProvider)
|
||||
{
|
||||
_localStorage = localStorage;
|
||||
_authenticationStateProvider = authenticationStateProvider;
|
||||
}
|
||||
|
||||
public async Task<bool> HasPermissionAsync(string permission)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(permission))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var cachedPermissions = await _localStorage.GetItemAsync<HashSet<string>>(PermissionsCacheKey);
|
||||
if (cachedPermissions == null || cachedPermissions.Count == 0)
|
||||
{
|
||||
// فعلاً بر اساس Role ساده تصمیم میگیریم تا زمانی که BFF Permission API آماده شود
|
||||
var role = await GetUserRoleAsync();
|
||||
if (string.IsNullOrWhiteSpace(role))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// SuperAdmin: همه دسترسیها
|
||||
if (string.Equals(role, "SuperAdmin", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Admin: اجازه دسترسی به بیشتر صفحات مدیریتی
|
||||
if (string.Equals(role, "Admin", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// فعلاً همه permissionهای UI را برای Admin آزاد میکنیم
|
||||
return true;
|
||||
}
|
||||
|
||||
// Inspector: فقط view
|
||||
if (string.Equals(role, "Inspector", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return permission.EndsWith(".view", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return cachedPermissions.Contains(permission, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public async Task<string?> GetUserRoleAsync()
|
||||
{
|
||||
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||
var user = authState.User;
|
||||
|
||||
if (user.Identity is not { IsAuthenticated: true })
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var roleClaim = user.FindFirst(ClaimTypes.Role) ?? user.FindFirst("role");
|
||||
return roleClaim?.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BackOffice.Services.Authorization;
|
||||
|
||||
public interface IAuthorizationService
|
||||
{
|
||||
Task<bool> HasPermissionAsync(string permission);
|
||||
|
||||
Task<string?> GetUserRoleAsync();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@inject BackOffice.Services.Authorization.IAuthorizationService AuthorizationService
|
||||
|
||||
<MudNavMenu Bordered="false" Class="nav-menu">
|
||||
<MudText Class="nav-menu__title" Typo="Typo.subtitle2">منوی اصلی</MudText>
|
||||
<MudDivider Class="mb-2" />
|
||||
|
||||
@if (CanViewDashboard)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/"
|
||||
Icon="@Icons.Material.Filled.Dashboard">
|
||||
@@ -15,6 +18,7 @@
|
||||
Icon="@Icons.Material.Filled.ViewQuilt">
|
||||
نمای کلی سیستم
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
<MudDivider Class="my-2" />
|
||||
<MudText Class="nav-menu__title" Typo="Typo.subtitle2">کمیسیون و شبکه</MudText>
|
||||
@@ -83,47 +87,68 @@
|
||||
|
||||
<AuthorizeView Roles="Administrator">
|
||||
<Authorized>
|
||||
@if (CanManagePackages)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.Package)"
|
||||
Icon="@Icons.Material.Filled.Inventory2">
|
||||
مدیریت پکیج
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
@if (CanManageProducts)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.Products)"
|
||||
Icon="@Icons.Material.Filled.ShoppingBag">
|
||||
مدیریت محصول
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
@if (CanViewOrders)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.Orders)"
|
||||
Icon="@Icons.Material.Filled.ReceiptLong">
|
||||
سفارشها
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
@if (CanManageCategories)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.Category)"
|
||||
Icon="@Icons.Material.Filled.Category">
|
||||
مدیریت دستهبندی
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
@if (CanManageTags)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/tags"
|
||||
Icon="@Icons.Material.Filled.Label">
|
||||
مدیریت تگها
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
@if (CanManageUsers)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.UserPage)"
|
||||
Icon="@Icons.Material.Filled.People">
|
||||
مدیریت کاربر
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
@if (CanManageRoles)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.Role)"
|
||||
Icon="@Icons.Material.Filled.AdminPanelSettings">
|
||||
مدیریت نقش
|
||||
</MudNavLink>
|
||||
}
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@@ -132,6 +157,8 @@
|
||||
|
||||
<AuthorizeView Roles="Administrator">
|
||||
<Authorized>
|
||||
@if (CanManageDiscountShop)
|
||||
{
|
||||
<MudNavGroup Title="فروشگاه تخفیفی" Icon="@Icons.Material.Filled.Discount" Expanded="false">
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/discount-products"
|
||||
@@ -157,12 +184,16 @@
|
||||
گزارش فروش
|
||||
</MudNavLink>
|
||||
</MudNavGroup>
|
||||
}
|
||||
|
||||
@if (CanManagePublicMessages)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/public-messages"
|
||||
Icon="@Icons.Material.Filled.Campaign">
|
||||
پیامهای عمومی
|
||||
</MudNavLink>
|
||||
}
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@@ -171,23 +202,32 @@
|
||||
|
||||
<AuthorizeView Roles="Administrator">
|
||||
<Authorized>
|
||||
@if (CanViewSystemAlerts)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/system/alerts"
|
||||
Icon="@Icons.Material.Filled.NotificationsActive">
|
||||
مدیریت هشدارها
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
@if (CanViewSystemHealth)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/system/health"
|
||||
Icon="@Icons.Material.Filled.MonitorHeart">
|
||||
سلامت سیستم
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
@if (CanManageSystemConfiguration)
|
||||
{
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/system/configuration"
|
||||
Icon="@Icons.Material.Filled.Tune">
|
||||
تنظیمات سیستم
|
||||
</MudNavLink>
|
||||
}
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@@ -215,4 +255,50 @@
|
||||
await LocalStorageService.RemoveItemAsync("AuthToken");
|
||||
Navigation.NavigateTo("/Login");
|
||||
}
|
||||
|
||||
private bool _initialized;
|
||||
private bool CanViewDashboard;
|
||||
private bool CanManagePackages;
|
||||
private bool CanManageProducts;
|
||||
private bool CanViewOrders;
|
||||
private bool CanManageCategories;
|
||||
private bool CanManageTags;
|
||||
private bool CanManageUsers;
|
||||
private bool CanManageRoles;
|
||||
private bool CanManageDiscountShop;
|
||||
private bool CanManagePublicMessages;
|
||||
private bool CanViewSystemAlerts;
|
||||
private bool CanViewSystemHealth;
|
||||
private bool CanManageSystemConfiguration;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await InitializePermissionsAsync();
|
||||
}
|
||||
|
||||
private async Task InitializePermissionsAsync()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CanViewDashboard = await AuthorizationService.HasPermissionAsync("dashboard.view");
|
||||
|
||||
CanManagePackages = await AuthorizationService.HasPermissionAsync("packages.manage");
|
||||
CanManageProducts = await AuthorizationService.HasPermissionAsync("products.view");
|
||||
CanViewOrders = await AuthorizationService.HasPermissionAsync("orders.view");
|
||||
CanManageCategories = await AuthorizationService.HasPermissionAsync("categories.manage");
|
||||
CanManageTags = await AuthorizationService.HasPermissionAsync("tags.manage");
|
||||
CanManageUsers = await AuthorizationService.HasPermissionAsync("users.view");
|
||||
CanManageRoles = await AuthorizationService.HasPermissionAsync("roles.manage");
|
||||
CanManageDiscountShop = await AuthorizationService.HasPermissionAsync("discountshop.manage");
|
||||
CanManagePublicMessages = await AuthorizationService.HasPermissionAsync("publicmessages.view");
|
||||
CanViewSystemAlerts = await AuthorizationService.HasPermissionAsync("system.alerts.view");
|
||||
CanViewSystemHealth = await AuthorizationService.HasPermissionAsync("system.health.view");
|
||||
CanManageSystemConfiguration = await AuthorizationService.HasPermissionAsync("settings.manage_configuration");
|
||||
|
||||
_initialized = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user