diff --git a/src/BackOffice/Common/Configure/ConfigureService.cs b/src/BackOffice/Common/Configure/ConfigureService.cs index cdea920..f8a28af 100644 --- a/src/BackOffice/Common/Configure/ConfigureService.cs +++ b/src/BackOffice/Common/Configure/ConfigureService.cs @@ -59,6 +59,7 @@ public static class ConfigureServices services.AddGrpcServices(configuration); // Application Services + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/BackOffice/Pages/Club/ClubMembers.razor b/src/BackOffice/Pages/Club/ClubMembers.razor index da0af30..bbf5685 100644 --- a/src/BackOffice/Pages/Club/ClubMembers.razor +++ b/src/BackOffice/Pages/Club/ClubMembers.razor @@ -17,7 +17,7 @@ لیست اعضا - + + @if (Mode == ManualPaymentDialogMode.Create) + { + + ثبت پرداخت دستی جدید + + + + + + + + + + + + } + else if (Model is not null) + { + + جزئیات پرداخت دستی + شناسه: @Model.Id + کاربر: @Model.UserFullName (@Model.UserId) + مبلغ: @Model.Amount.ToString("N0") ریال + نوع: @Model.TypeDisplay + وضعیت: @Model.StatusDisplay + + @if (!string.IsNullOrWhiteSpace(Model.ReferenceNumber)) + { + شماره مرجع: @Model.ReferenceNumber + } + + توضیحات: @Model.Description + + @if (!string.IsNullOrWhiteSpace(Model.RejectionReason)) + { + + دلیل رد: @Model.RejectionReason + + } + + @if (Model.Status == (int)ManualPaymentDialogStatus.Pending) + { + + } + + } + + + + بستن + + + @if (Mode == ManualPaymentDialogMode.Create) + { + + ثبت + + } + else if (Model is not null && Model.Status == (int)ManualPaymentDialogStatus.Pending) + { + + تایید + + + رد + + } + + + diff --git a/src/BackOffice/Pages/Payment/Components/ManualPaymentDialog.razor.cs b/src/BackOffice/Pages/Payment/Components/ManualPaymentDialog.razor.cs new file mode 100644 index 0000000..4d827ba --- /dev/null +++ b/src/BackOffice/Pages/Payment/Components/ManualPaymentDialog.razor.cs @@ -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()); +} + diff --git a/src/BackOffice/Pages/Payment/Components/TransactionDetailsDialog.razor b/src/BackOffice/Pages/Payment/Components/TransactionDetailsDialog.razor new file mode 100644 index 0000000..56449bb --- /dev/null +++ b/src/BackOffice/Pages/Payment/Components/TransactionDetailsDialog.razor @@ -0,0 +1,42 @@ +@namespace BackOffice.Pages.Payment.Components + +@using MudBlazor + + + + @if (Model is not null) + { + + جزئیات تراکنش + شناسه: @Model.Id + RefId: @Model.RefId + مبلغ: @Model.Amount.ToString("N0") ریال + وضعیت: @StatusText + تاریخ پرداخت: @Model.PaymentDate.ToLocalTime().MiladiToJalaliWithTime() + + @if (!string.IsNullOrWhiteSpace(Model.BankReferenceId)) + { + BankReferenceId: @Model.BankReferenceId + } + + @if (!string.IsNullOrWhiteSpace(Model.TrackingCode)) + { + TrackingCode: @Model.TrackingCode + } + + @if (Model.PaymentFailed && !string.IsNullOrWhiteSpace(Model.PaymentFailedReason)) + { + + دلیل خطا: @Model.PaymentFailedReason + + } + + } + + + + بستن + + + + diff --git a/src/BackOffice/Pages/Payment/Components/TransactionDetailsDialog.razor.cs b/src/BackOffice/Pages/Payment/Components/TransactionDetailsDialog.razor.cs new file mode 100644 index 0000000..8d60e3c --- /dev/null +++ b/src/BackOffice/Pages/Payment/Components/TransactionDetailsDialog.razor.cs @@ -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; } + } +} + diff --git a/src/BackOffice/Pages/Payment/ManualPayments.razor b/src/BackOffice/Pages/Payment/ManualPayments.razor new file mode 100644 index 0000000..4e5610c --- /dev/null +++ b/src/BackOffice/Pages/Payment/ManualPayments.razor @@ -0,0 +1,246 @@ +@page "/payment/manual-payments" +@using BackOffice.BFF.ManualPayment.Protobuf +@using BackOffice.Pages.Payment.Components +@using BackOffice.Common.BaseComponents + + + + + + + + + در انتظار + تایید شده + رد شده + لغو شده + + + + واریز نقدی + شارژ کیف‌پول تخفیف + شارژ کیف‌پول شبکه + تسویه حساب + اصلاح خطا + بازگشت وجه + سایر + + + + + + پرداخت‌های دستی + + ثبت پرداخت دستی جدید + + + + + + + + + + + + @context.Item.Amount.ToString("N0") + + + + + @GetTypeText(context.Item.Type) + + + + + + @GetStatusText(context.Item.Status) + + + + + + @context.Item.Created.ToLocalTime().MiladiToJalaliWithTime() + + + + + + + + + + + + + + + + +@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> LoadData(GridState 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 + { + 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("ثبت پرداخت دستی", 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($"جزئیات پرداخت دستی #{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 + }; + } +} diff --git a/src/BackOffice/Pages/Payment/Transactions.razor b/src/BackOffice/Pages/Payment/Transactions.razor new file mode 100644 index 0000000..633104f --- /dev/null +++ b/src/BackOffice/Pages/Payment/Transactions.razor @@ -0,0 +1,82 @@ +@page "/payment/transactions" +@using BackOffice.Common.BaseComponents + + + + + + + در انتظار + موفق + ناموفق + بازگشت وجه + + + + نامشخص + خرید پکیج + پرداخت دستی + شارژ کیف‌پول + بازگشت وجه + + + + + مدیریت تراکنش‌ها + + + + + + + + + @context.Item.Amount.ToString("N0") + + + + + + @GetStatusText(context.Item.PaymentStatus) + + + + + + @context.Item.PaymentDate.ToLocalTime().MiladiToJalaliWithTime() + + + + + @GetTypeText(context.Item.Type) + + + + + + + + + + + + + + diff --git a/src/BackOffice/Pages/Payment/Transactions.razor.cs b/src/BackOffice/Pages/Payment/Transactions.razor.cs new file mode 100644 index 0000000..dc7b4e6 --- /dev/null +++ b/src/BackOffice/Pages/Payment/Transactions.razor.cs @@ -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> LoadData(GridState state) + { + // TODO: Connect to BackOffice.BFF Transactions when API is ready + await Task.CompletedTask; + + return new GridData + { + Items = Array.Empty(), + 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; } + } +} + diff --git a/src/BackOffice/Pages/Products/ProductsMainPage.razor.cs b/src/BackOffice/Pages/Products/ProductsMainPage.razor.cs index 4c8407e..3659432 100644 --- a/src/BackOffice/Pages/Products/ProductsMainPage.razor.cs +++ b/src/BackOffice/Pages/Products/ProductsMainPage.razor.cs @@ -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 - { - { x => x.ProductId, model.Id }, - { x => x.ProductTitle, model.Title } - }; + // var parameters = new DialogParameters + // { + // { x => x.ProductId, model.Id }, + // { x => x.ProductTitle, model.Title } + // }; - await DialogService.ShowAsync("مدیریت تگ‌های محصول", parameters, - new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true }); + // await DialogService.ShowAsync("مدیریت تگ‌های محصول", 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) diff --git a/src/BackOffice/Pages/Role/Components/RolePermissionsDialog.razor b/src/BackOffice/Pages/Role/Components/RolePermissionsDialog.razor new file mode 100644 index 0000000..18d5761 --- /dev/null +++ b/src/BackOffice/Pages/Role/Components/RolePermissionsDialog.razor @@ -0,0 +1,39 @@ +@namespace BackOffice.Pages.Role.Components + +@using MudBlazor + + + + + دسترسی‌های نقش @RoleName + + @foreach (var category in _categories) + { + + + + @foreach (var permission in category.Permissions) + { + + } + + + + } + + + + + انصراف + + + ذخیره + + + + diff --git a/src/BackOffice/Pages/Role/Components/RolePermissionsDialog.razor.cs b/src/BackOffice/Pages/Role/Components/RolePermissionsDialog.razor.cs new file mode 100644 index 0000000..0fdd416 --- /dev/null +++ b/src/BackOffice/Pages/Role/Components/RolePermissionsDialog.razor.cs @@ -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 _categories = new(); + private readonly HashSet _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 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 Permissions); + + private record PermissionItem(string Key, string DisplayName); +} + diff --git a/src/BackOffice/Pages/Role/RoleMainPage.razor b/src/BackOffice/Pages/Role/RoleMainPage.razor index a8b0f09..8e2a2d6 100644 --- a/src/BackOffice/Pages/Role/RoleMainPage.razor +++ b/src/BackOffice/Pages/Role/RoleMainPage.razor @@ -41,6 +41,15 @@ + + + + @@ -56,4 +65,3 @@ - diff --git a/src/BackOffice/Pages/Role/RoleMainPage.razor.cs b/src/BackOffice/Pages/Role/RoleMainPage.razor.cs index 506af20..efde8cb 100644 --- a/src/BackOffice/Pages/Role/RoleMainPage.razor.cs +++ b/src/BackOffice/Pages/Role/RoleMainPage.razor.cs @@ -93,4 +93,22 @@ public partial class RoleMainPage _request = new() { Filter = new() { } }; ReLoadData(); } + + private async Task EditPermissions(DataModel model) + { + var parameters = new DialogParameters + { + { x => x.RoleName, model.Name } + }; + + var options = new DialogOptions + { + CloseButton = true, + FullWidth = true, + MaxWidth = MaxWidth.Medium + }; + + var dialog = await DialogService.ShowAsync($"دسترسی‌های نقش {model.Title}", parameters, options); + await dialog.Result; + } } diff --git a/src/BackOffice/Pages/Settings/UserSettings.razor b/src/BackOffice/Pages/Settings/UserSettings.razor index 29f53ab..52a6a2c 100644 --- a/src/BackOffice/Pages/Settings/UserSettings.razor +++ b/src/BackOffice/Pages/Settings/UserSettings.razor @@ -30,11 +30,11 @@ English - - @@ -67,15 +67,15 @@ - - - @@ -163,7 +163,7 @@ - diff --git a/src/BackOffice/Pages/SystemManagement/Configuration.razor b/src/BackOffice/Pages/SystemManagement/Configuration.razor index 4cd67e0..8185927 100644 --- a/src/BackOffice/Pages/SystemManagement/Configuration.razor +++ b/src/BackOffice/Pages/SystemManagement/Configuration.razor @@ -81,13 +81,13 @@ - - @@ -158,19 +158,19 @@ - - - @@ -244,19 +244,19 @@ - - - @@ -372,6 +372,38 @@ + + + + تنظیمات مالیات بر ارزش افزوده (VAT) + + درصد پیش‌فرض VAT که روی سفارش‌ها اعمال می‌شود. + + + + + + + + + + + + + ذخیره تنظیمات VAT + + + + @@ -383,6 +415,7 @@ private NetworkConfig _networkConfig = new(); private ClubConfig _clubConfig = new(); private SystemConfig _systemConfig = new(); + private VatConfig _vatConfig = new(); private Dictionary _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; } + } } diff --git a/src/BackOffice/Pages/UserAddress/Components/CreateDialog.razor b/src/BackOffice/Pages/UserAddress/Components/CreateDialog.razor index 6dd1948..92b57d0 100644 --- a/src/BackOffice/Pages/UserAddress/Components/CreateDialog.razor +++ b/src/BackOffice/Pages/UserAddress/Components/CreateDialog.razor @@ -16,7 +16,7 @@ - + diff --git a/src/BackOffice/Pages/UserAddress/Components/UpdateDialog.razor b/src/BackOffice/Pages/UserAddress/Components/UpdateDialog.razor index 86c2d5a..23a5251 100644 --- a/src/BackOffice/Pages/UserAddress/Components/UpdateDialog.razor +++ b/src/BackOffice/Pages/UserAddress/Components/UpdateDialog.razor @@ -17,7 +17,7 @@ - + diff --git a/src/BackOffice/Pages/UserOrder/Components/CancelOrderDialog.razor b/src/BackOffice/Pages/UserOrder/Components/CancelOrderDialog.razor index f24489d..3307cfc 100644 --- a/src/BackOffice/Pages/UserOrder/Components/CancelOrderDialog.razor +++ b/src/BackOffice/Pages/UserOrder/Components/CancelOrderDialog.razor @@ -20,7 +20,7 @@ Required="true" Lines="3" /> - + بازگشت وجه به کاربر (در صورت پرداخت موفق) diff --git a/src/BackOffice/Pages/UserOrder/Components/UserOrderDetailsDialog.razor b/src/BackOffice/Pages/UserOrder/Components/UserOrderDetailsDialog.razor index 1135cf0..e9cdd8c 100644 --- a/src/BackOffice/Pages/UserOrder/Components/UserOrderDetailsDialog.razor +++ b/src/BackOffice/Pages/UserOrder/Components/UserOrderDetailsDialog.razor @@ -51,6 +51,21 @@ } + @if (_model.VatAmount > 0) + { + + جزئیات مالیات بر ارزش افزوده + + مبلغ کالا (قبل از مالیات): @_model.VatBaseAmount.ToString("N0") تومان + + + مالیات (@_model.VatPercentage.ToString("0")٪): @_model.VatAmount.ToString("N0") تومان + + + مبلغ نهایی (شامل مالیات): @_model.VatTotalAmount.ToString("N0") تومان + + } + Timeline وضعیت diff --git a/src/BackOffice/Pages/UserOrder/UserOrderMainPage.razor b/src/BackOffice/Pages/UserOrder/UserOrderMainPage.razor index f92ccb6..df64d65 100644 --- a/src/BackOffice/Pages/UserOrder/UserOrderMainPage.razor +++ b/src/BackOffice/Pages/UserOrder/UserOrderMainPage.razor @@ -131,6 +131,16 @@ + + + @(context.Item.VatAmount > 0 ? context.Item.VatAmount.ToString("N0") : "-") + + + + + @(context.Item.VatPercentage > 0 ? $"{context.Item.VatPercentage:0}٪" : "-") + + diff --git a/src/BackOffice/Services/Authorization/AuthorizationService.cs b/src/BackOffice/Services/Authorization/AuthorizationService.cs new file mode 100644 index 0000000..c6274f3 --- /dev/null +++ b/src/BackOffice/Services/Authorization/AuthorizationService.cs @@ -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 HasPermissionAsync(string permission) + { + if (string.IsNullOrWhiteSpace(permission)) + { + return true; + } + + var cachedPermissions = await _localStorage.GetItemAsync>(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 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; + } +} + diff --git a/src/BackOffice/Services/Authorization/IAuthorizationService.cs b/src/BackOffice/Services/Authorization/IAuthorizationService.cs new file mode 100644 index 0000000..57f20a6 --- /dev/null +++ b/src/BackOffice/Services/Authorization/IAuthorizationService.cs @@ -0,0 +1,9 @@ +namespace BackOffice.Services.Authorization; + +public interface IAuthorizationService +{ + Task HasPermissionAsync(string permission); + + Task GetUserRoleAsync(); +} + diff --git a/src/BackOffice/Shared/NavMenu.razor b/src/BackOffice/Shared/NavMenu.razor index aa9c5f8..f66a0ff 100644 --- a/src/BackOffice/Shared/NavMenu.razor +++ b/src/BackOffice/Shared/NavMenu.razor @@ -1,20 +1,24 @@ @using Microsoft.AspNetCore.Components.Authorization +@inject BackOffice.Services.Authorization.IAuthorizationService AuthorizationService منوی اصلی - - داشبورد - + @if (CanViewDashboard) + { + + داشبورد + - - نمای کلی سیستم - + + نمای کلی سیستم + + } کمیسیون و شبکه @@ -83,47 +87,68 @@ - - مدیریت پکیج - + @if (CanManagePackages) + { + + مدیریت پکیج + + } - - مدیریت محصول - + @if (CanManageProducts) + { + + مدیریت محصول + + } - - سفارش‌ها - + @if (CanViewOrders) + { + + سفارش‌ها + + } - - مدیریت دسته‌بندی - + @if (CanManageCategories) + { + + مدیریت دسته‌بندی + + } - - مدیریت تگ‌ها - + @if (CanManageTags) + { + + مدیریت تگ‌ها + + } - - مدیریت کاربر - + @if (CanManageUsers) + { + + مدیریت کاربر + + } - - مدیریت نقش - + @if (CanManageRoles) + { + + مدیریت نقش + + } @@ -132,37 +157,43 @@ - - - محصولات تخفیفی - + @if (CanManageDiscountShop) + { + + + محصولات تخفیفی + - - دسته‌بندی‌های فروشگاه - + + دسته‌بندی‌های فروشگاه + - - سفارشات فروشگاه - + + سفارشات فروشگاه + - - گزارش فروش - - + + گزارش فروش + + + } - - پیام‌های عمومی - + @if (CanManagePublicMessages) + { + + پیام‌های عمومی + + } @@ -171,23 +202,32 @@ - - مدیریت هشدارها - + @if (CanViewSystemAlerts) + { + + مدیریت هشدارها + + } - - سلامت سیستم - + @if (CanViewSystemHealth) + { + + سلامت سیستم + + } - - تنظیمات سیستم - + @if (CanManageSystemConfiguration) + { + + تنظیمات سیستم + + } @@ -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(); + } }