- Added InventoryMainPage for managing inventory items with filtering options. - Created LowStockPage to display low stock items with filtering capabilities. - Developed MovementsPage to track inventory movements with detailed filtering. - Introduced WarehousesPage for managing warehouses, including creation and viewing inventory. - Enhanced backend integration with InventoryBFFContract for data retrieval. - Implemented user-friendly UI components using MudBlazor for better user experience. - Added necessary dialog components for adding stock, adjusting inventory, and viewing movements.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
# فیچر فعالسازی (پرداخت) دستی باشگاه مشتریان
|
||||
|
||||
## خلاصه
|
||||
این فیچر امکان فعالسازی دستی عضویت باشگاه مشتریان را برای ادمین فراهم میکند. ادمین میتواند کاربر را انتخاب کرده، تصویر فیش پرداخت را آپلود کند و عضویت را فعال کند.
|
||||
|
||||
## تاریخ: 2 ژانویه 2026
|
||||
|
||||
---
|
||||
|
||||
## تغییرات انجام شده
|
||||
|
||||
### 1. CMS (Backend)
|
||||
|
||||
#### Entity - `ManualPayment.cs`
|
||||
- اضافه شدن فیلد `ImageDocumentId` برای ذخیره شناسه سند در FMS
|
||||
|
||||
```csharp
|
||||
public long? ImageDocumentId { get; set; }
|
||||
```
|
||||
|
||||
#### Proto - `manualpayment.proto`
|
||||
- اضافه شدن `image_document_id` به `CreateManualPaymentRequest` (فیلد 7)
|
||||
- اضافه شدن `image_document_id` به `ManualPaymentModel` (فیلد 21)
|
||||
|
||||
#### Command - `CreateManualPaymentCommand.cs`
|
||||
- اضافه شدن پراپرتی `ImageDocumentId`
|
||||
|
||||
#### Handler - `CreateManualPaymentCommandHandler.cs`
|
||||
- ذخیره `ImageDocumentId` از request در entity
|
||||
|
||||
---
|
||||
|
||||
### 2. BFF (Backend For Frontend)
|
||||
|
||||
#### Proto - `manualpayment.proto`
|
||||
- اضافه شدن `image_document_id` به `ManualPaymentModel` (فیلد 21)
|
||||
- اضافه شدن `FileUploadModel` برای آپلود فایل
|
||||
- اضافه شدن `image_file` به `CreateManualPaymentRequest`
|
||||
|
||||
#### Command - `CreateManualPaymentCommand.cs`
|
||||
- اضافه شدن `FileUploadDto` برای دریافت فایل از فرانت
|
||||
|
||||
#### Handler - `CreateManualPaymentCommandHandler.cs`
|
||||
- اتصال به FMS برای آپلود فایل
|
||||
- استخراج `ImagePath` و `ImageDocumentId` از پاسخ FMS
|
||||
- ارسال هر دو به CMS
|
||||
|
||||
```csharp
|
||||
var fileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new()
|
||||
{
|
||||
Directory = "Images/ManualPayments",
|
||||
IsBase64 = false,
|
||||
MIME = request.ImageFile.Mime,
|
||||
FileName = request.ImageFile.FileName,
|
||||
File = ByteString.CopyFrom(request.ImageFile.File)
|
||||
}, cancellationToken: cancellationToken);
|
||||
|
||||
if (fileInfo != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(fileInfo.File))
|
||||
grpcRequest.ImagePath = fileInfo.File;
|
||||
if (fileInfo.Id > 0)
|
||||
grpcRequest.ImageDocumentId = fileInfo.Id;
|
||||
}
|
||||
```
|
||||
|
||||
#### Query Handler - `GetManualPaymentsQueryHandler.cs`
|
||||
- اضافه شدن mapping برای `ImagePath` و `ImageDocumentId`
|
||||
|
||||
#### DTO - `GetManualPaymentsResponseDto.cs`
|
||||
- اضافه شدن فیلدهای:
|
||||
```csharp
|
||||
public string? ImagePath { get; set; }
|
||||
public long? ImageDocumentId { get; set; }
|
||||
```
|
||||
|
||||
#### Mapping - `ManualPaymentProfile.cs`
|
||||
- اضافه شدن mapping برای `FileUploadModel -> FileUploadDto`
|
||||
|
||||
---
|
||||
|
||||
### 3. Frontend (Blazor)
|
||||
|
||||
#### `ManualPaymentDialog.razor`
|
||||
- استفاده از `UserAutoComplete` برای انتخاب کاربر
|
||||
- استفاده از `MudFileUpload` برای آپلود تصویر فیش
|
||||
- استفاده از `MudSelect` برای انتخاب نوع پرداخت
|
||||
- پیشنمایش تصویر قبل از ارسال
|
||||
- تغییر عنوانها از "پرداخت دستی" به "فعالسازی دستی"
|
||||
|
||||
#### `ManualPaymentDialog.razor.cs`
|
||||
- هندل کردن انتخاب فایل با `IBrowserFile`
|
||||
- تبدیل فایل به Base64 برای پیشنمایش
|
||||
- ایجاد `FileUploadModel` برای ارسال به BFF
|
||||
- مقادیر پیشفرض:
|
||||
- `Type = 1` (واریز نقدی)
|
||||
- `Description = "عضویت دستی باشگاه مشتریان"`
|
||||
|
||||
#### `ManualPayments.razor`
|
||||
- تغییر عنوان صفحه به "فعالسازی (پرداخت) دستی"
|
||||
- تغییر دکمه به "ثبت فعالسازی دستی جدید"
|
||||
|
||||
#### `NavMenu.razor`
|
||||
- حذف آیتم منوی "پرداخت دستی عضویت"
|
||||
- تغییر نام "پرداختهای دستی" به "فعالسازی (پرداخت) دستی"
|
||||
|
||||
#### حذف شده
|
||||
- `ManualMembershipPayment.razor` و `ManualMembershipPayment.razor.cs`
|
||||
|
||||
---
|
||||
|
||||
## مقادیر ثابت
|
||||
|
||||
```csharp
|
||||
// مبلغ پایه پکیج: 56 میلیون ریال
|
||||
SystemConstants.BasePackageAmount = 56_000_000;
|
||||
|
||||
// شارژ کیف پول:
|
||||
// - مجموع شارژ: 56M ریال
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## فلو کامل
|
||||
|
||||
1. ادمین کاربر را با `UserAutoComplete` انتخاب میکند
|
||||
2. نوع پرداخت را انتخاب میکند (پیشفرض: واریز نقدی)
|
||||
3. توضیحات را وارد میکند (پیشفرض: عضویت دستی باشگاه مشتریان)
|
||||
4. تصویر فیش را آپلود میکند (اختیاری)
|
||||
5. دکمه ثبت را میزند
|
||||
6. Frontend فایل را به BFF ارسال میکند
|
||||
7. BFF فایل را به FMS آپلود میکند
|
||||
8. FMS مسیر فایل (`File`) و شناسه سند (`Id`) را برمیگرداند
|
||||
9. BFF هر دو را به CMS ارسال میکند
|
||||
10. CMS تراکنش، پرداخت دستی و لاگ کیف پول را ثبت میکند
|
||||
11. کیف پول کاربر شارژ میشود
|
||||
|
||||
---
|
||||
|
||||
## فایلهای تغییر یافته
|
||||
|
||||
### CMS
|
||||
- `CMSMicroservice.Domain/Entities/Payment/ManualPayment.cs`
|
||||
- `CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs`
|
||||
- `CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs`
|
||||
- `Protos/manualpayment.proto`
|
||||
|
||||
### BFF
|
||||
- `BackOffice.BFF.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs`
|
||||
- `BackOffice.BFF.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs`
|
||||
- `BackOffice.BFF.Application/ManualPaymentCQ/Queries/GetManualPayments/GetManualPaymentsQueryHandler.cs`
|
||||
- `BackOffice.BFF.Application/ManualPaymentCQ/Queries/GetManualPayments/GetManualPaymentsResponseDto.cs`
|
||||
- `BackOffice.BFF.Application/ManualPaymentCQ/ManualPaymentProfile.cs`
|
||||
- `Protobufs/BackOffice.BFF.ManualPayment.Protobuf/Protos/manualpayment.proto`
|
||||
|
||||
### Frontend
|
||||
- `BackOffice/Pages/Payment/Components/ManualPaymentDialog.razor`
|
||||
- `BackOffice/Pages/Payment/Components/ManualPaymentDialog.razor.cs`
|
||||
- `BackOffice/Pages/Payment/ManualPayments.razor`
|
||||
- `BackOffice/Shared/NavMenu.razor`
|
||||
|
||||
### حذف شده
|
||||
- `BackOffice.Main/Pages/Payment/ManualMembershipPayment.razor`
|
||||
- `BackOffice.Main/Pages/Payment/ManualMembershipPayment.razor.cs`
|
||||
+69
-3
@@ -1,12 +1,78 @@
|
||||
# کارهای باقیمانده - BackOffice
|
||||
|
||||
> آخرین بروزرسانی: December 20, 2025
|
||||
> آخرین بروزرسانی: January 1, 2026
|
||||
|
||||
## وضعیت کلی
|
||||
|
||||
**Build Status**: ✅ SUCCESS (0 Errors)
|
||||
**Enabled Modules**: 10+ ماژول کامل
|
||||
**Remaining Tasks**: فقط Backend Implementation
|
||||
**Enabled Modules**: 12+ ماژول کامل
|
||||
**System Status**: **PRODUCTION READY** 🚀
|
||||
|
||||
---
|
||||
|
||||
## ✅ کارهای انجام شده - Session January 1, 2026
|
||||
|
||||
### فعالسازی ماژولهای فروشگاه تخفیفی (DiscountShop Frontend)
|
||||
|
||||
**وضعیت**: ✅ COMPLETED - همه چیز فعال و build موفق
|
||||
|
||||
**فایل اصلی تغییر یافته**:
|
||||
- `BackOffice/Common/Configure/ConfigureService.cs`
|
||||
|
||||
**تغییرات**:
|
||||
|
||||
#### 1. Using Statements فعال شدند:
|
||||
```csharp
|
||||
// Discount Shop Proto Clients
|
||||
using BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct;
|
||||
using BackOffice.BFF.DiscountCategory.Protobuf.Protos.DiscountCategory;
|
||||
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
|
||||
using BackOffice.BFF.Tag.Protobuf.Protos.Tag;
|
||||
using BackOffice.BFF.ProductTag.Protobuf.Protos.ProductTag;
|
||||
using Foursat.BackOffice.BFF.PublicMessage.Protobuf;
|
||||
|
||||
// Application Services
|
||||
using BackOffice.Services.DiscountProduct;
|
||||
using BackOffice.Services.DiscountCategory;
|
||||
using BackOffice.Services.DiscountOrder;
|
||||
using BackOffice.Services.PublicMessage;
|
||||
using BackOffice.Services.Tag;
|
||||
```
|
||||
|
||||
#### 2. gRPC Clients فعال شدند:
|
||||
```csharp
|
||||
// Discount Shop Services
|
||||
services.AddTransient(sp => new DiscountProductContract.DiscountProductContractClient(...));
|
||||
services.AddTransient(sp => new DiscountCategoryContract.DiscountCategoryContractClient(...));
|
||||
services.AddTransient(sp => new DiscountOrderContract.DiscountOrderContractClient(...));
|
||||
|
||||
// Public Message Service
|
||||
services.AddTransient(sp => new PublicMessageContract.PublicMessageContractClient(...));
|
||||
|
||||
// Tag Management Services
|
||||
services.AddTransient(sp => new TagContract.TagContractClient(...));
|
||||
services.AddTransient(sp => new ProductTagContract.ProductTagContractClient(...));
|
||||
```
|
||||
|
||||
#### 3. Application Services فعال شدند:
|
||||
```csharp
|
||||
services.AddScoped<IDiscountProductService, DiscountProductService>();
|
||||
services.AddScoped<IDiscountCategoryService, DiscountCategoryService>();
|
||||
services.AddScoped<IDiscountOrderService, DiscountOrderService>();
|
||||
services.AddScoped<IPublicMessageService, PublicMessageService>();
|
||||
services.AddScoped<ITagService, TagService>();
|
||||
```
|
||||
|
||||
### صفحات فعال شده:
|
||||
|
||||
| صفحه | Route | توضیحات |
|
||||
|------|-------|---------|
|
||||
| مدیریت محصولات تخفیفی | `/discount-products` | CRUD + گالری تصاویر |
|
||||
| مدیریت دستهبندیها | `/discount-categories` | CRUD + سلسلهمراتب |
|
||||
| مدیریت سفارشات | `/discount-orders` | مشاهده + تغییر وضعیت |
|
||||
| گزارش فروش | `/sales-reports` | آمار و نمودار |
|
||||
| مدیریت تگها | `/tags` | CRUD تگها |
|
||||
| پیامهای عمومی | `/public-messages` | CRUD + انتشار |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
@page "/payment/membership"
|
||||
@using BackOffice.BFF.ManualPayment.Protobuf
|
||||
@using BackOffice.Main.Pages.AutoComplete
|
||||
@attribute [Authorize(Roles = "Admin,SuperAdmin")]
|
||||
|
||||
<PageTitle>پرداخت دستی عضویت</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h5">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Payment" Class="ml-2" />
|
||||
پرداخت دستی عضویت
|
||||
</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<!-- انتخاب کاربر -->
|
||||
<MudItem xs="12" md="6">
|
||||
<UserAutoComplete
|
||||
Label="انتخاب کاربر *"
|
||||
@bind-SelectedUserId="_userId"
|
||||
HelperText="جستجو با نام، موبایل یا شناسه" />
|
||||
</MudItem>
|
||||
|
||||
<!-- مبلغ -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField
|
||||
@bind-Value="_amount"
|
||||
Label="مبلغ (ریال) *"
|
||||
Min="0"
|
||||
Step="10000"
|
||||
Format="N0"
|
||||
Variant="Variant.Outlined"
|
||||
HelperText="مبلغ پرداختی به ریال" />
|
||||
</MudItem>
|
||||
|
||||
<!-- شماره مرجع -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField
|
||||
@bind-Value="_referenceNumber"
|
||||
Label="شماره مرجع *"
|
||||
Variant="Variant.Outlined"
|
||||
MaxLength="50"
|
||||
HelperText="شماره مرجع تراکنش یا فیش" />
|
||||
</MudItem>
|
||||
|
||||
<!-- توضیحات -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField
|
||||
@bind-Value="_description"
|
||||
Label="توضیحات"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="3"
|
||||
MaxLength="500"
|
||||
HelperText="توضیحات اختیاری" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@if (_showResult)
|
||||
{
|
||||
<MudAlert Severity="Severity.Success" Class="mt-4" ShowCloseIcon CloseIconClicked="@(() => _showResult = false)">
|
||||
<MudText>@_resultMessage</MudText>
|
||||
<MudText Typo="Typo.caption">شماره تراکنش: @_transactionId</MudText>
|
||||
<MudText Typo="Typo.caption">شماره سفارش: @_orderId</MudText>
|
||||
<MudText Typo="Typo.caption">موجودی جدید کیف پول: @_newWalletBalance.ToString("N0") ریال</MudText>
|
||||
</MudAlert>
|
||||
}
|
||||
</MudCardContent>
|
||||
|
||||
<MudCardActions>
|
||||
<MudButton
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Check"
|
||||
OnClick="ProcessPayment"
|
||||
Disabled="@_isProcessing">
|
||||
@if (_isProcessing)
|
||||
{
|
||||
<MudProgressCircular Class="ml-2" Size="Size.Small" Indeterminate="true" />
|
||||
<MudText>در حال ثبت...</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText>ثبت پرداخت</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
|
||||
<MudButton
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Default"
|
||||
StartIcon="@Icons.Material.Filled.Clear"
|
||||
OnClick="ResetForm"
|
||||
Disabled="@_isProcessing">
|
||||
پاک کردن فرم
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
|
||||
<!-- راهنما -->
|
||||
<MudCard Class="mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Info" Class="ml-2" />
|
||||
راهنما
|
||||
</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.body2" Class="mb-2">این صفحه برای ثبت پرداختهای دستی عضویت استفاده میشود. پس از ثبت:</MudText>
|
||||
<MudList Dense="true">
|
||||
<MudListItem Icon="@Icons.Material.Filled.AccountBalance">
|
||||
<MudText Typo="Typo.body2">مبلغ به کیف پول کاربر (Balance و DiscountBalance) اضافه میشود</MudText>
|
||||
</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.History">
|
||||
<MudText Typo="Typo.body2">لاگ تغییرات کیف پول ثبت میشود</MudText>
|
||||
</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.Receipt">
|
||||
<MudText Typo="Typo.body2">تراکنش با وضعیت موفق ثبت میشود</MudText>
|
||||
</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.ShoppingCart">
|
||||
<MudText Typo="Typo.body2">سفارش خرید پکیج عضویت ثبت میشود</MudText>
|
||||
</MudListItem>
|
||||
</MudList>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
@@ -1,103 +0,0 @@
|
||||
using BackOffice.BFF.ManualPayment.Protobuf;
|
||||
using BackOffice.Main.Components;
|
||||
using Grpc.Core;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace BackOffice.Main.Pages.Payment;
|
||||
|
||||
public partial class ManualMembershipPayment
|
||||
{
|
||||
[Inject] private ManualPaymentContract.ManualPaymentContractClient ManualPaymentClient { get; set; } = default!;
|
||||
[Inject] private ISnackbar Snackbar { get; set; } = default!;
|
||||
|
||||
private long? _userId;
|
||||
private long _amount = 0;
|
||||
private string _referenceNumber = string.Empty;
|
||||
private string? _description;
|
||||
|
||||
private bool _isProcessing = false;
|
||||
private bool _showResult = false;
|
||||
private string _resultMessage = string.Empty;
|
||||
private long _transactionId = 0;
|
||||
private long _orderId = 0;
|
||||
private long _newWalletBalance = 0;
|
||||
|
||||
private async Task ProcessPayment()
|
||||
{
|
||||
// Validation
|
||||
if (!_userId.HasValue || _userId.Value <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفا کاربر را انتخاب کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_amount <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفا مبلغ معتبری وارد کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_referenceNumber))
|
||||
{
|
||||
Snackbar.Add("لطفا شماره مرجع را وارد کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_isProcessing = true;
|
||||
_showResult = false;
|
||||
|
||||
var request = new ProcessManualMembershipPaymentRequest
|
||||
{
|
||||
UserId = _userId.Value,
|
||||
Amount = _amount,
|
||||
ReferenceNumber = _referenceNumber
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_description))
|
||||
{
|
||||
request.Description = _description;
|
||||
}
|
||||
|
||||
var response = await ManualPaymentClient.ProcessManualMembershipPaymentAsync(request);
|
||||
|
||||
_resultMessage = response.Message;
|
||||
_transactionId = response.TransactionId;
|
||||
_orderId = response.OrderId;
|
||||
_newWalletBalance = response.NewWalletBalance;
|
||||
_showResult = true;
|
||||
|
||||
Snackbar.Add("پرداخت دستی با موفقیت ثبت شد", Severity.Success);
|
||||
|
||||
// Reset form
|
||||
await Task.Delay(2000);
|
||||
ResetForm();
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در ثبت پرداخت: {ex.Status.Detail}", Severity.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطای غیرمنتظره: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetForm()
|
||||
{
|
||||
_userId = null;
|
||||
_amount = 0;
|
||||
_referenceNumber = string.Empty;
|
||||
_description = null;
|
||||
_showResult = false;
|
||||
_resultMessage = string.Empty;
|
||||
_transactionId = 0;
|
||||
_orderId = 0;
|
||||
_newWalletBalance = 0;
|
||||
}
|
||||
}
|
||||
@@ -117,25 +117,27 @@
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Commission.Protobuf" Version="0.0.15"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Common.Protobuf" Version="0.0.3"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Configuration.Protobuf" Version="1.0.20"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.DiscountCategory.Protobuf" Version="0.0.3"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.DiscountOrder.Protobuf" Version="0.0.3"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.DiscountProduct.Protobuf" Version="0.0.3"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.DiscountShoppingCart.Protobuf" Version="0.0.3"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.DiscountCategory.Protobuf" Version="0.0.4"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.DiscountOrder.Protobuf" Version="0.0.4"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.DiscountProduct.Protobuf" Version="0.0.4"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.DiscountShoppingCart.Protobuf" Version="0.0.4"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Health.Protobuf" Version="1.0.5"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.ManualPayment.Protobuf" Version="0.0.5"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.ManualPayment.Protobuf" Version="0.0.14"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.NetworkMembership.Protobuf" Version="0.0.11"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Otp.Protobuf" Version="0.0.113"/>
|
||||
<PackageReference Include="FourSat.BackOffice.BFF.Package.Protobuf" Version="0.0.113"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Products.Protobuf" Version="0.0.11"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.ProductTag.Protobuf" Version="0.0.3"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.ProductTag.Protobuf" Version="0.0.4"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.PublicMessage.Protobuf" Version="0.0.5"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Role.Protobuf" Version="0.0.113"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Tag.Protobuf" Version="0.0.4"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Tag.Protobuf" Version="0.0.5"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.User.Protobuf" Version="0.0.114"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.UserAddress.Protobuf" Version="0.0.113"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.UserOrder.Protobuf" Version="0.0.116"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.UserRole.Protobuf" Version="0.0.113"/>
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Inventory.Protobuf" Version="0.0.1"/>
|
||||
|
||||
|
||||
<!-- Proto Projects - Using ProjectReference for latest changes -->
|
||||
<!-- <ProjectReference Include="../../../BackOffice.BFF/src/Protobufs/BackOffice.BFF.Category.Protobuf/BackOffice.BFF.Category.Protobuf.csproj" />-->
|
||||
<!-- <ProjectReference Include="../../../BackOffice.BFF/src/Protobufs/BackOffice.BFF.ClubMembership.Protobuf/BackOffice.BFF.ClubMembership.Protobuf.csproj" />-->
|
||||
|
||||
@@ -14,19 +14,21 @@ using Foursat.BackOffice.BFF.NetworkMembership.Protos;
|
||||
using Foursat.BackOffice.BFF.Health.Protobuf;
|
||||
using BackOffice.BFF.Configuration.Protobuf.Protos.AppVersion;
|
||||
|
||||
// TODO: Create these proto projects - temporarily disabled
|
||||
// using BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct;
|
||||
// using BackOffice.BFF.DiscountCategory.Protobuf.Protos.DiscountCategory;
|
||||
// using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
|
||||
// using BackOffice.BFF.Tag.Protobuf.Protos.Tag;
|
||||
// using BackOffice.BFF.ProductTag.Protobuf.Protos.ProductTag;
|
||||
// using BackOffice.BFF.PublicMessage.Protobuf.Protos.PublicMessage;
|
||||
// Discount Shop Proto Clients
|
||||
using BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct;
|
||||
using BackOffice.BFF.DiscountCategory.Protobuf.Protos.DiscountCategory;
|
||||
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
|
||||
using BackOffice.BFF.Tag.Protobuf.Protos.Tag;
|
||||
using BackOffice.BFF.ProductTag.Protobuf.Protos.ProductTag;
|
||||
using BackOffice.BFF.ManualPayment.Protobuf;
|
||||
using Foursat.BackOffice.BFF.PublicMessage.Protobuf;
|
||||
using Foursat.BackOffice.BFF.Inventory.Protos;
|
||||
using BackOffice.Common.Utilities;
|
||||
// using BackOffice.Services.DiscountProduct;
|
||||
// using BackOffice.Services.DiscountCategory;
|
||||
// using BackOffice.Services.DiscountOrder;
|
||||
// using BackOffice.Services.PublicMessage;
|
||||
// using BackOffice.Services.Tag;
|
||||
using BackOffice.Services.DiscountProduct;
|
||||
using BackOffice.Services.DiscountCategory;
|
||||
using BackOffice.Services.DiscountOrder;
|
||||
using BackOffice.Services.PublicMessage;
|
||||
using BackOffice.Services.Tag;
|
||||
using Blazored.LocalStorage;
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
@@ -37,9 +39,6 @@ using MudBlazor.Services;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Foursat.BackOffice.BFF.Health.Protobuf;
|
||||
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
|
||||
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -70,12 +69,13 @@ public static class ConfigureServices
|
||||
// Application Services
|
||||
services.AddScoped<BackOffice.Services.Authorization.IAuthorizationService, BackOffice.Services.Authorization.AuthorizationService>();
|
||||
services.AddScoped<BackOffice.Services.AppVersion.IAppVersionService, BackOffice.Services.AppVersion.AppVersionService>();
|
||||
// TODO: Re-enable when proto projects are created
|
||||
// services.AddScoped<IDiscountProductService, DiscountProductService>();
|
||||
// services.AddScoped<IDiscountCategoryService, DiscountCategoryService>();
|
||||
// services.AddScoped<IDiscountOrderService, DiscountOrderService>();
|
||||
// services.AddScoped<IPublicMessageService, PublicMessageService>();
|
||||
// services.AddScoped<ITagService, TagService>();
|
||||
|
||||
// Discount Shop Services
|
||||
services.AddScoped<IDiscountProductService, DiscountProductService>();
|
||||
services.AddScoped<IDiscountCategoryService, DiscountCategoryService>();
|
||||
services.AddScoped<IDiscountOrderService, DiscountOrderService>();
|
||||
services.AddScoped<IPublicMessageService, PublicMessageService>();
|
||||
services.AddScoped<ITagService, TagService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -120,18 +120,23 @@ public static class ConfigureServices
|
||||
services.AddTransient(sp => new HealthContract.HealthContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new AppVersionContract.AppVersionContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
|
||||
// TODO: Re-enable when proto projects are created
|
||||
// Discount Shop Services
|
||||
// services.AddTransient(sp => new DiscountProductsContract.DiscountProductsContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
// services.AddTransient(sp => new DiscountCategoriesContract.DiscountCategoriesContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
// services.AddTransient(sp => new DiscountOrdersContract.DiscountOrdersContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new DiscountProductContract.DiscountProductContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new DiscountCategoryContract.DiscountCategoryContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new DiscountOrderContract.DiscountOrderContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
|
||||
// Public Message Service
|
||||
// services.AddTransient(sp => new PublicMessagesContract.PublicMessagesContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new PublicMessageContract.PublicMessageContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
|
||||
// Tag Management Services
|
||||
// services.AddTransient(sp => new TagContract.TagContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
// services.AddTransient(sp => new ProductTagContract.ProductTagContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new TagContract.TagContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new ProductTagContract.ProductTagContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
|
||||
// Manual Payment Service
|
||||
services.AddTransient(sp => new ManualPaymentContract.ManualPaymentContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
|
||||
// Inventory Management Service
|
||||
services.AddTransient(sp => new InventoryBFFContract.InventoryBFFContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -17,4 +17,11 @@ public static class RouteConstance
|
||||
public const string ProductCategories = "/ProductCategoriesPage/";
|
||||
public const string ProductsBulkEdit = "/ProductsBulkEditPage/";
|
||||
public const string CategoryProducts = "/CategoryProductsPage/";
|
||||
|
||||
// Inventory Management
|
||||
public const string Inventory = "/InventoryPage/";
|
||||
public const string InventoryLowStock = "/InventoryLowStockPage/";
|
||||
public const string InventoryWarehouses = "/InventoryWarehousesPage/";
|
||||
public const string InventoryAddStock = "/InventoryAddStockPage/";
|
||||
public const string InventoryMovements = "/InventoryMovementsPage/";
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="pa-3" Elevation="0">
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">جزئیات Payout</MudText>
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">جزئیات پرداخت</MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>شناسه Payout:</strong></td>
|
||||
<td><strong>شناسه پرداخت:</strong></td>
|
||||
<td>@Payout.Id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -229,7 +229,7 @@
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Payments"
|
||||
Href="/commission/payouts">
|
||||
مشاهده Payout ها
|
||||
مشاهده پرداختها
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Secondary"
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<MudTd>@context.Id</MudTd>
|
||||
<MudTd>@context.UserFullName (@context.UserNationalCode)</MudTd>
|
||||
<MudTd>@context.Amount.ToString("N0")</MudTd>
|
||||
<MudTd>@context.PaymentDate.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")</MudTd>
|
||||
<MudTd>@context.PaymentDate.ToDateTime().MiladiToJalali()</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
@using Foursat.BackOffice.BFF.Inventory.Protos
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
@using InventoryProductType = Foursat.BackOffice.BFF.Inventory.Protos.ProductType
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form" @bind-IsValid="_formValid">
|
||||
<MudGrid>
|
||||
@if (ProductIdParam == null)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="InventoryProductType" @bind-Value="_productType"
|
||||
Label="نوع محصول"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="نوع محصول الزامی است">
|
||||
<MudSelectItem Value="InventoryProductType.Regular">محصول عادی</MudSelectItem>
|
||||
<MudSelectItem Value="InventoryProductType.Discount">محصول تخفیفی</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="long" @bind-Value="_productId"
|
||||
Label="شناسه محصول"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="شناسه محصول الزامی است"
|
||||
Min="1" />
|
||||
</MudItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudAlert Severity="Severity.Info">
|
||||
محصول: <strong>@ProductName</strong>
|
||||
</MudAlert>
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="long?" @bind-Value="_warehouseId"
|
||||
Label="انبار"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="انبار الزامی است">
|
||||
@foreach (var wh in Warehouses)
|
||||
{
|
||||
<MudSelectItem Value="@((long?)wh.Id)">@wh.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" @bind-Value="_quantity"
|
||||
Label="تعداد"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="تعداد الزامی است"
|
||||
Min="1" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" @bind-Value="_referenceNumber"
|
||||
Label="شماره مرجع (فاکتور/رسید)"
|
||||
Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" @bind-Value="_note"
|
||||
Label="توضیحات"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="3" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">انصراف</MudButton>
|
||||
<MudButton Color="Color.Primary"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="@(!_formValid || _isSubmitting)">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||||
}
|
||||
ثبت موجودی
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Inject] public InventoryBFFContract.InventoryBFFContractClient InventoryContract { get; set; } = default!;
|
||||
|
||||
[Parameter] public List<WarehouseDto> Warehouses { get; set; } = new();
|
||||
[Parameter] public long? ProductIdParam { get; set; }
|
||||
[Parameter] public InventoryProductType? ProductTypeParam { get; set; }
|
||||
[Parameter] public string? ProductName { get; set; }
|
||||
[Parameter] public long? WarehouseIdParam { get; set; }
|
||||
|
||||
private MudForm? _form;
|
||||
private bool _formValid;
|
||||
private bool _isSubmitting;
|
||||
|
||||
private InventoryProductType _productType = InventoryProductType.Regular;
|
||||
private long _productId;
|
||||
private long? _warehouseId;
|
||||
private int _quantity = 1;
|
||||
private string _referenceNumber = string.Empty;
|
||||
private string _note = string.Empty;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (ProductIdParam.HasValue)
|
||||
{
|
||||
_productId = ProductIdParam.Value;
|
||||
}
|
||||
if (ProductTypeParam.HasValue)
|
||||
{
|
||||
_productType = ProductTypeParam.Value;
|
||||
}
|
||||
if (WarehouseIdParam.HasValue)
|
||||
{
|
||||
_warehouseId = WarehouseIdParam.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
if (_form != null)
|
||||
{
|
||||
await _form.Validate();
|
||||
if (!_formValid) return;
|
||||
}
|
||||
|
||||
if (!_warehouseId.HasValue)
|
||||
{
|
||||
Snackbar.Add("لطفاً انبار را انتخاب کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_isSubmitting = true;
|
||||
try
|
||||
{
|
||||
var request = new AddStockRequest
|
||||
{
|
||||
ProductId = ProductIdParam ?? _productId,
|
||||
ProductType = ProductTypeParam ?? _productType,
|
||||
Quantity = _quantity,
|
||||
ReferenceNumber = _referenceNumber ?? string.Empty,
|
||||
Note = _note ?? string.Empty,
|
||||
WarehouseId = _warehouseId.Value
|
||||
};
|
||||
|
||||
var response = await InventoryContract.AddStockAsync(request);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
Snackbar.Add($"موجودی جدید: {response.NewQuantity} عدد", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add(response.Message ?? "خطا در ثبت موجودی", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
@using Foursat.BackOffice.BFF.Inventory.Protos
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form" @bind-IsValid="_formValid">
|
||||
<MudStack Spacing="4">
|
||||
<MudAlert Severity="Severity.Info">
|
||||
<MudStack Spacing="1">
|
||||
<MudText>محصول: <strong>@Item.ProductName</strong></MudText>
|
||||
<MudText>موجودی فعلی: <strong>@Item.Quantity</strong> عدد</MudText>
|
||||
<MudText>رزرو شده: <strong>@Item.ReservedQuantity</strong> عدد</MudText>
|
||||
<MudText>قابل فروش: <strong>@Item.AvailableQuantity</strong> عدد</MudText>
|
||||
</MudStack>
|
||||
</MudAlert>
|
||||
|
||||
<MudNumericField T="int" @bind-Value="_newQuantity"
|
||||
Label="موجودی جدید"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="موجودی الزامی است"
|
||||
Min="0"
|
||||
HelperText="@GetDifferenceText()" />
|
||||
|
||||
<MudTextField T="string" @bind-Value="_note"
|
||||
Label="توضیحات (دلیل تغییر)"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="3"
|
||||
Required="true"
|
||||
RequiredError="توضیحات الزامی است" />
|
||||
|
||||
@if (_newQuantity != Item.Quantity)
|
||||
{
|
||||
<MudAlert Severity="@(GetDifference() > 0 ? Severity.Success : Severity.Warning)">
|
||||
@GetDifferenceAlertText()
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">انصراف</MudButton>
|
||||
<MudButton Color="Color.Primary"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="@(!_formValid || _isSubmitting || _newQuantity == Item.Quantity)">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||||
}
|
||||
ذخیره تغییرات
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Inject] public InventoryBFFContract.InventoryBFFContractClient InventoryContract { get; set; } = default!;
|
||||
|
||||
[Parameter] public InventoryItemDto Item { get; set; } = default!;
|
||||
|
||||
private MudForm? _form;
|
||||
private bool _formValid;
|
||||
private bool _isSubmitting;
|
||||
|
||||
private int _newQuantity;
|
||||
private string _note = string.Empty;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_newQuantity = Item.Quantity;
|
||||
}
|
||||
|
||||
private int GetDifference() => _newQuantity - Item.Quantity;
|
||||
|
||||
private string GetDifferenceText()
|
||||
{
|
||||
var diff = GetDifference();
|
||||
if (diff == 0) return "بدون تغییر";
|
||||
return diff > 0 ? $"افزایش {diff} عدد" : $"کاهش {Math.Abs(diff)} عدد";
|
||||
}
|
||||
|
||||
private string GetDifferenceAlertText()
|
||||
{
|
||||
var diff = GetDifference();
|
||||
if (diff > 0)
|
||||
return $"موجودی {diff} عدد افزایش مییابد (از {Item.Quantity} به {_newQuantity})";
|
||||
return $"موجودی {Math.Abs(diff)} عدد کاهش مییابد (از {Item.Quantity} به {_newQuantity})";
|
||||
}
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
if (_form != null)
|
||||
{
|
||||
await _form.Validate();
|
||||
if (!_formValid) return;
|
||||
}
|
||||
|
||||
if (_newQuantity == Item.Quantity)
|
||||
{
|
||||
Snackbar.Add("هیچ تغییری در موجودی انجام نشده است", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_isSubmitting = true;
|
||||
try
|
||||
{
|
||||
var productId = Item.ProductType == ProductType.Regular
|
||||
? (Item.ProductId ?? 0)
|
||||
: (Item.DiscountProductId ?? 0);
|
||||
|
||||
var request = new AdjustStockRequest
|
||||
{
|
||||
ProductId = productId,
|
||||
ProductType = Item.ProductType,
|
||||
NewQuantity = _newQuantity,
|
||||
Note = _note ?? string.Empty
|
||||
};
|
||||
|
||||
var response = await InventoryContract.AdjustStockAsync(request);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
Snackbar.Add(response.Message ?? "موجودی با موفقیت تنظیم شد", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add(response.Message ?? "خطا در تنظیم موجودی", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
@using Foursat.BackOffice.BFF.Inventory.Protos
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form" @bind-IsValid="_formValid">
|
||||
<MudStack Spacing="4">
|
||||
<MudTextField T="string" @bind-Value="_name"
|
||||
Label="نام انبار"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="نام انبار الزامی است"
|
||||
MaxLength="100" />
|
||||
|
||||
<MudTextField T="string" @bind-Value="_code"
|
||||
Label="کد انبار"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="کد انبار الزامی است"
|
||||
MaxLength="20"
|
||||
HelperText="کد منحصر به فرد برای شناسایی انبار (مثال: WH-001)" />
|
||||
|
||||
<MudTextField T="string" @bind-Value="_address"
|
||||
Label="آدرس"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="2"
|
||||
MaxLength="500" />
|
||||
|
||||
<MudSwitch T="bool" @bind-Value="_isDefault"
|
||||
Color="Color.Primary"
|
||||
Label="انبار پیشفرض"
|
||||
LabelPosition="LabelPosition.Start" />
|
||||
|
||||
@if (_isDefault)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">
|
||||
این انبار به عنوان انبار پیشفرض برای ورود کالا تنظیم میشود.
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">انصراف</MudButton>
|
||||
<MudButton Color="Color.Primary"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="@(!_formValid || _isSubmitting)">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||||
}
|
||||
ایجاد انبار
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Inject] public InventoryBFFContract.InventoryBFFContractClient InventoryContract { get; set; } = default!;
|
||||
|
||||
private MudForm? _form;
|
||||
private bool _formValid;
|
||||
private bool _isSubmitting;
|
||||
|
||||
private string _name = string.Empty;
|
||||
private string _code = string.Empty;
|
||||
private string _address = string.Empty;
|
||||
private bool _isDefault = false;
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
if (_form != null)
|
||||
{
|
||||
await _form.Validate();
|
||||
if (!_formValid) return;
|
||||
}
|
||||
|
||||
_isSubmitting = true;
|
||||
try
|
||||
{
|
||||
var request = new CreateWarehouseRequest
|
||||
{
|
||||
Name = _name,
|
||||
Code = _code,
|
||||
Address = _address ?? string.Empty,
|
||||
IsDefault = _isDefault
|
||||
};
|
||||
|
||||
var response = await InventoryContract.CreateWarehouseAsync(request);
|
||||
|
||||
if (response.Id > 0)
|
||||
{
|
||||
Snackbar.Add($"انبار '{_name}' با شناسه {response.Id} ایجاد شد", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(response.Id));
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("خطا در ایجاد انبار", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
@using Foursat.BackOffice.BFF.Inventory.Protos
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form" @bind-IsValid="_formValid">
|
||||
<MudStack Spacing="4">
|
||||
<MudAlert Severity="Severity.Info">
|
||||
<MudStack Spacing="1">
|
||||
<MudText>محصول: <strong>@Item.ProductName</strong></MudText>
|
||||
<MudText>انبار: <strong>@Item.WarehouseName</strong></MudText>
|
||||
</MudStack>
|
||||
</MudAlert>
|
||||
|
||||
<MudNumericField T="int" @bind-Value="_lowStockThreshold"
|
||||
Label="حد هشدار کمموجودی"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="این فیلد الزامی است"
|
||||
Min="0"
|
||||
HelperText="وقتی موجودی کمتر از این عدد شود، هشدار داده میشود" />
|
||||
|
||||
<MudNumericField T="int" @bind-Value="_reorderPoint"
|
||||
Label="نقطه سفارش مجدد"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="این فیلد الزامی است"
|
||||
Min="0"
|
||||
HelperText="وقتی موجودی به این عدد برسد، باید سفارش داده شود" />
|
||||
|
||||
<MudNumericField T="int" @bind-Value="_maxStockLevel"
|
||||
Label="حداکثر موجودی مجاز"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="این فیلد الزامی است"
|
||||
Min="1"
|
||||
HelperText="حداکثر تعدادی که در انبار نگهداری میشود" />
|
||||
|
||||
<MudDivider />
|
||||
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
مقادیر فعلی:
|
||||
حد هشدار: @Item.LowStockThreshold |
|
||||
نقطه سفارش: @Item.ReorderPoint |
|
||||
حداکثر: @Item.MaxStockLevel
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">انصراف</MudButton>
|
||||
<MudButton Color="Color.Primary"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="@(!_formValid || _isSubmitting)">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||||
}
|
||||
ذخیره تنظیمات
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Inject] public InventoryBFFContract.InventoryBFFContractClient InventoryContract { get; set; } = default!;
|
||||
|
||||
[Parameter] public InventoryItemDto Item { get; set; } = default!;
|
||||
|
||||
private MudForm? _form;
|
||||
private bool _formValid;
|
||||
private bool _isSubmitting;
|
||||
|
||||
private int _lowStockThreshold;
|
||||
private int _reorderPoint;
|
||||
private int _maxStockLevel;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_lowStockThreshold = Item.LowStockThreshold;
|
||||
_reorderPoint = Item.ReorderPoint;
|
||||
_maxStockLevel = Item.MaxStockLevel;
|
||||
}
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
if (_form != null)
|
||||
{
|
||||
await _form.Validate();
|
||||
if (!_formValid) return;
|
||||
}
|
||||
|
||||
// Validation
|
||||
if (_lowStockThreshold > _reorderPoint)
|
||||
{
|
||||
Snackbar.Add("حد هشدار نمیتواند از نقطه سفارش بیشتر باشد", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_reorderPoint > _maxStockLevel)
|
||||
{
|
||||
Snackbar.Add("نقطه سفارش نمیتواند از حداکثر موجودی بیشتر باشد", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_isSubmitting = true;
|
||||
try
|
||||
{
|
||||
var request = new UpdateInventorySettingsRequest
|
||||
{
|
||||
InventoryItemId = Item.Id,
|
||||
LowStockThreshold = _lowStockThreshold,
|
||||
ReorderPoint = _reorderPoint,
|
||||
MaxStockLevel = _maxStockLevel
|
||||
};
|
||||
|
||||
await InventoryContract.UpdateInventorySettingsAsync(request);
|
||||
|
||||
Snackbar.Add("تنظیمات با موفقیت ذخیره شد", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
@using Foursat.BackOffice.BFF.Inventory.Protos
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form" @bind-IsValid="_formValid">
|
||||
<MudStack Spacing="4">
|
||||
<MudAlert Severity="Severity.Warning">
|
||||
<MudStack Spacing="1">
|
||||
<MudText>محصول: <strong>@Item.ProductName</strong></MudText>
|
||||
<MudText>موجودی فعلی: <strong>@Item.Quantity</strong> عدد</MudText>
|
||||
</MudStack>
|
||||
</MudAlert>
|
||||
|
||||
<MudNumericField T="int" @bind-Value="_quantity"
|
||||
Label="تعداد خسارت دیده"
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="تعداد الزامی است"
|
||||
Min="1"
|
||||
Max="@Item.Quantity"
|
||||
HelperText="@($"حداکثر: {Item.Quantity} عدد")" />
|
||||
|
||||
<MudTextField T="string" @bind-Value="_reason"
|
||||
Label="دلیل خسارت"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="2"
|
||||
Required="true"
|
||||
RequiredError="دلیل خسارت الزامی است"
|
||||
Placeholder="مثال: آسیب در حمل و نقل، منقضی شده، ..." />
|
||||
|
||||
<MudTextField T="string" @bind-Value="_referenceNumber"
|
||||
Label="شماره مرجع"
|
||||
Variant="Variant.Outlined"
|
||||
HelperText="شماره گزارش یا سند داخلی" />
|
||||
|
||||
@if (_quantity > 0 && _quantity <= Item.Quantity)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error">
|
||||
<MudText>
|
||||
<strong>@_quantity</strong> عدد از موجودی کسر خواهد شد.
|
||||
موجودی جدید: <strong>@(Item.Quantity - _quantity)</strong> عدد
|
||||
</MudText>
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">انصراف</MudButton>
|
||||
<MudButton Color="Color.Error"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="@(!_formValid || _isSubmitting)">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||||
}
|
||||
ثبت خسارت
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Inject] public InventoryBFFContract.InventoryBFFContractClient InventoryContract { get; set; } = default!;
|
||||
|
||||
[Parameter] public InventoryItemDto Item { get; set; } = default!;
|
||||
|
||||
private MudForm? _form;
|
||||
private bool _formValid;
|
||||
private bool _isSubmitting;
|
||||
|
||||
private int _quantity = 1;
|
||||
private string _reason = string.Empty;
|
||||
private string _referenceNumber = string.Empty;
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
if (_form != null)
|
||||
{
|
||||
await _form.Validate();
|
||||
if (!_formValid) return;
|
||||
}
|
||||
|
||||
if (_quantity > Item.Quantity)
|
||||
{
|
||||
Snackbar.Add("تعداد خسارت نمیتواند از موجودی فعلی بیشتر باشد", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_isSubmitting = true;
|
||||
try
|
||||
{
|
||||
var productId = Item.ProductType == ProductType.Regular
|
||||
? (Item.ProductId ?? 0)
|
||||
: (Item.DiscountProductId ?? 0);
|
||||
|
||||
var request = new RecordLossRequest
|
||||
{
|
||||
ProductId = productId,
|
||||
ProductType = Item.ProductType,
|
||||
Quantity = _quantity,
|
||||
Reason = _reason ?? string.Empty,
|
||||
ReferenceNumber = _referenceNumber ?? string.Empty
|
||||
};
|
||||
|
||||
await InventoryContract.RecordLossAsync(request);
|
||||
|
||||
Snackbar.Add("خسارت با موفقیت ثبت شد", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
@attribute [Route(RouteConstance.Inventory)]
|
||||
@attribute [Authorize]
|
||||
|
||||
@using Foursat.BackOffice.BFF.Inventory.Protos
|
||||
@using BackOffice.Pages.Inventory.Components
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">مدیریت انبار</MudText>
|
||||
|
||||
@* Filters Section *@
|
||||
<MudPaper Class="pa-4 mb-4" Elevation="2">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudSelect T="long?" @bind-Value="_filterWarehouseId"
|
||||
Label="انبار"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Clearable="true">
|
||||
@foreach (var wh in _warehouses)
|
||||
{
|
||||
<MudSelectItem Value="@((long?)wh.Id)">@wh.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudSelect T="ProductType" @bind-Value="_filterProductType"
|
||||
Label="نوع محصول"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense">
|
||||
<MudSelectItem Value="ProductType.Unspecified">همه</MudSelectItem>
|
||||
<MudSelectItem Value="ProductType.Regular">محصول عادی</MudSelectItem>
|
||||
<MudSelectItem Value="ProductType.Discount">محصول تخفیفی</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudTextField T="string" @bind-Value="_searchTerm"
|
||||
Label="جستجو"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
Clearable="true" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3" Class="d-flex align-end">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="ApplyFilter"
|
||||
StartIcon="@Icons.Material.Filled.FilterAlt"
|
||||
Class="mr-2">
|
||||
اعمال فیلتر
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
OnClick="ClearFilter"
|
||||
StartIcon="@Icons.Material.Filled.Clear">
|
||||
پاک کردن
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
@* Stats Cards *@
|
||||
<MudGrid Class="mb-4">
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.caption">کل اقلام انبار</MudText>
|
||||
<MudText Typo="Typo.h5">@_totalCount</MudText>
|
||||
</MudStack>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Inventory" Color="Color.Primary" Size="Size.Large" />
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.caption">محصولات کمموجود</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Warning">@_lowStockCount</MudText>
|
||||
</MudStack>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Warning" Size="Size.Large" />
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@* Data Grid *@
|
||||
<MudDataGrid T="InventoryItemDto"
|
||||
ServerData="@(new Func<GridState<InventoryItemDto>, Task<GridData<InventoryItemDto>>>(ServerReload))"
|
||||
Hover="true"
|
||||
@ref="_gridData"
|
||||
Height="60vh">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">لیست موجودی انبار</MudText>
|
||||
<MudSpacer />
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="OpenAddStockDialog"
|
||||
StartIcon="@Icons.Material.Filled.Add">
|
||||
ورود کالا
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
|
||||
<PropertyColumn Property="x => x.ProductName" Title="نام محصول">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.body2"><strong>@context.Item.ProductName</strong></MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
@GetProductTypeLabel(context.Item.ProductType)
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.WarehouseName" Title="انبار">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
||||
@context.Item.WarehouseName
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.Quantity" Title="موجودی">
|
||||
<CellTemplate>
|
||||
<MudText Color="@(context.Item.IsLowStock ? Color.Error : Color.Default)">
|
||||
<strong>@context.Item.Quantity</strong>
|
||||
</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.ReservedQuantity" Title="رزرو شده">
|
||||
<CellTemplate>
|
||||
<MudText Color="Color.Warning">@context.Item.ReservedQuantity</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.AvailableQuantity" Title="قابل فروش">
|
||||
<CellTemplate>
|
||||
<MudText Color="Color.Success"><strong>@context.Item.AvailableQuantity</strong></MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.LowStockThreshold" Title="حد هشدار" />
|
||||
|
||||
<PropertyColumn Property="x => x.IsLowStock" Title="وضعیت">
|
||||
<CellTemplate>
|
||||
@if (context.Item.IsLowStock)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Error" Size="Size.Small">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Warning" Size="Size.Small" Class="mr-1" />
|
||||
کمموجود
|
||||
</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudChip T="string" Color="Color.Success" Size="Size.Small">
|
||||
موجود
|
||||
</MudChip>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<TemplateColumn Title="عملیات" Sortable="false">
|
||||
<CellTemplate>
|
||||
<MudStack Row="true" Spacing="1">
|
||||
<MudTooltip Text="ورود کالا">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.AddCircle"
|
||||
Size="Size.Small"
|
||||
Color="Color.Success"
|
||||
OnClick="@(() => OpenAddStockDialogForItem(context.Item))" />
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="تنظیم موجودی">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Size="Size.Small"
|
||||
Color="Color.Primary"
|
||||
OnClick="@(() => OpenAdjustStockDialog(context.Item))" />
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="ثبت خسارت">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle"
|
||||
Size="Size.Small"
|
||||
Color="Color.Error"
|
||||
OnClick="@(() => OpenRecordLossDialog(context.Item))" />
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="تنظیمات">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Settings"
|
||||
Size="Size.Small"
|
||||
Color="Color.Secondary"
|
||||
OnClick="@(() => OpenSettingsDialog(context.Item))" />
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="تاریخچه تغییرات">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.History"
|
||||
Size="Size.Small"
|
||||
Color="Color.Info"
|
||||
OnClick="@(() => ViewMovements(context.Item))" />
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="InventoryItemDto"
|
||||
PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد سطرهای صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,250 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Foursat.BackOffice.BFF.Inventory.Protos;
|
||||
using BackOffice.Pages.Inventory.Components;
|
||||
using BackOffice.Common.Utilities;
|
||||
|
||||
namespace BackOffice.Pages.Inventory;
|
||||
|
||||
public partial class InventoryMainPage
|
||||
{
|
||||
[Inject] public InventoryBFFContract.InventoryBFFContractClient InventoryContract { get; set; } = default!;
|
||||
|
||||
private MudDataGrid<InventoryItemDto>? _gridData;
|
||||
private List<WarehouseDto> _warehouses = new();
|
||||
|
||||
// Filter fields
|
||||
private long? _filterWarehouseId;
|
||||
private ProductType _filterProductType = ProductType.Unspecified;
|
||||
private string _searchTerm = string.Empty;
|
||||
|
||||
// Stats
|
||||
private int _totalCount;
|
||||
private int _lowStockCount;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadWarehouses();
|
||||
await LoadLowStockCount();
|
||||
}
|
||||
|
||||
private async Task LoadWarehouses()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await InventoryContract.GetAllWarehousesAsync(new GetAllWarehousesRequest
|
||||
{
|
||||
ActiveOnly = true
|
||||
});
|
||||
_warehouses = response.Warehouses.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری انبارها: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadLowStockCount()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await InventoryContract.GetLowStockItemsAsync(new GetLowStockItemsRequest
|
||||
{
|
||||
Count = 100 // Get up to 100 low stock items to count
|
||||
});
|
||||
_lowStockCount = response.TotalCount;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_lowStockCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<GridData<InventoryItemDto>> ServerReload(GridState<InventoryItemDto> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetAllInventoryItemsRequest
|
||||
{
|
||||
PageIndex = state.Page + 1,
|
||||
PageSize = state.PageSize,
|
||||
ProductType = _filterProductType,
|
||||
SearchTerm = _searchTerm ?? string.Empty
|
||||
};
|
||||
|
||||
if (_filterWarehouseId.HasValue)
|
||||
{
|
||||
request.WarehouseId = _filterWarehouseId.Value;
|
||||
}
|
||||
|
||||
var result = await InventoryContract.GetAllInventoryItemsAsync(request);
|
||||
|
||||
if (result?.Items != null)
|
||||
{
|
||||
_totalCount = result.TotalCount;
|
||||
return new GridData<InventoryItemDto>
|
||||
{
|
||||
Items = result.Items.ToList(),
|
||||
TotalItems = result.TotalCount
|
||||
};
|
||||
}
|
||||
|
||||
return new GridData<InventoryItemDto>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری دادهها: {ex.Message}", Severity.Error);
|
||||
return new GridData<InventoryItemDto>();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetProductTypeLabel(ProductType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ProductType.Regular => "محصول عادی",
|
||||
ProductType.Discount => "محصول تخفیفی",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ApplyFilter()
|
||||
{
|
||||
if (_gridData != null)
|
||||
{
|
||||
await _gridData.ReloadServerData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearFilter()
|
||||
{
|
||||
_filterWarehouseId = null;
|
||||
_filterProductType = ProductType.Unspecified;
|
||||
_searchTerm = string.Empty;
|
||||
await ApplyFilter();
|
||||
}
|
||||
|
||||
private async Task OpenAddStockDialog()
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["Warehouses"] = _warehouses
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<AddStockDialog>("ورود کالا به انبار", parameters, new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Medium,
|
||||
FullWidth = true
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result!.Canceled)
|
||||
{
|
||||
Snackbar.Add("موجودی با موفقیت اضافه شد", Severity.Success);
|
||||
await ApplyFilter();
|
||||
await LoadLowStockCount();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenAddStockDialogForItem(InventoryItemDto item)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["Warehouses"] = _warehouses,
|
||||
["ProductIdParam"] = item.ProductType == ProductType.Regular ? item.ProductId : item.DiscountProductId,
|
||||
["ProductTypeParam"] = item.ProductType,
|
||||
["ProductName"] = item.ProductName,
|
||||
["WarehouseIdParam"] = item.WarehouseId
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<AddStockDialog>($"ورود کالا - {item.ProductName}", parameters, new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Medium,
|
||||
FullWidth = true
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result!.Canceled)
|
||||
{
|
||||
Snackbar.Add("موجودی با موفقیت اضافه شد", Severity.Success);
|
||||
await ApplyFilter();
|
||||
await LoadLowStockCount();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenAdjustStockDialog(InventoryItemDto item)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["Item"] = item
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<AdjustStockDialog>($"تنظیم موجودی - {item.ProductName}", parameters, new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Small,
|
||||
FullWidth = true
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result!.Canceled)
|
||||
{
|
||||
Snackbar.Add("موجودی با موفقیت تنظیم شد", Severity.Success);
|
||||
await ApplyFilter();
|
||||
await LoadLowStockCount();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenRecordLossDialog(InventoryItemDto item)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["Item"] = item
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<RecordLossDialog>($"ثبت خسارت - {item.ProductName}", parameters, new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Small,
|
||||
FullWidth = true
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result!.Canceled)
|
||||
{
|
||||
Snackbar.Add("خسارت با موفقیت ثبت شد", Severity.Warning);
|
||||
await ApplyFilter();
|
||||
await LoadLowStockCount();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenSettingsDialog(InventoryItemDto item)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["Item"] = item
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<InventorySettingsDialog>($"تنظیمات - {item.ProductName}", parameters, new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Small,
|
||||
FullWidth = true
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result!.Canceled)
|
||||
{
|
||||
Snackbar.Add("تنظیمات با موفقیت ذخیره شد", Severity.Success);
|
||||
await ApplyFilter();
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewMovements(InventoryItemDto item)
|
||||
{
|
||||
Navigation.NavigateTo($"{RouteConstance.InventoryMovements}?itemId={item.Id}&productName={item.ProductName}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
@attribute [Route(RouteConstance.InventoryLowStock)]
|
||||
@attribute [Authorize]
|
||||
|
||||
@using Foursat.BackOffice.BFF.Inventory.Protos
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mb-4">
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.h4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Warning" Class="mr-2" />
|
||||
محصولات کمموجود
|
||||
</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Secondary">
|
||||
لیست محصولاتی که موجودی آنها کمتر از حد هشدار است
|
||||
</MudText>
|
||||
</MudStack>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
OnClick="GoBack">
|
||||
بازگشت به انبار
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@* Filters Section *@
|
||||
<MudPaper Class="pa-4 mb-4" Elevation="2">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudSelect T="long?" @bind-Value="_filterWarehouseId"
|
||||
Label="انبار"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Clearable="true">
|
||||
@foreach (var wh in _warehouses)
|
||||
{
|
||||
<MudSelectItem Value="@((long?)wh.Id)">@wh.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudSelect T="ProductType" @bind-Value="_filterProductType"
|
||||
Label="نوع محصول"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense">
|
||||
<MudSelectItem Value="ProductType.Unspecified">همه</MudSelectItem>
|
||||
<MudSelectItem Value="ProductType.Regular">محصول عادی</MudSelectItem>
|
||||
<MudSelectItem Value="ProductType.Discount">محصول تخفیفی</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4" Class="d-flex align-end">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="LoadData"
|
||||
StartIcon="@Icons.Material.Filled.Refresh"
|
||||
Class="mr-2">
|
||||
بارگذاری
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
@* Stats *@
|
||||
<MudAlert Severity="Severity.Warning" Class="mb-4">
|
||||
<MudText>
|
||||
<strong>@_items.Count</strong> محصول کمموجود شناسایی شده است.
|
||||
لطفاً برای جلوگیری از کمبود موجودی، اقدام به سفارش مجدد کنید.
|
||||
</MudText>
|
||||
</MudAlert>
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudDataGrid T="LowStockItemDto" Items="_items" Hover="true" Height="60vh">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
|
||||
<PropertyColumn Property="x => x.ProductName" Title="نام محصول">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.body2"><strong>@context.Item.ProductName</strong></MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
@GetProductTypeLabel(context.Item.ProductType)
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.WarehouseName" Title="انبار">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
||||
@context.Item.WarehouseName
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.Quantity" Title="موجودی فعلی">
|
||||
<CellTemplate>
|
||||
<MudText Color="Color.Error"><strong>@context.Item.Quantity</strong></MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.AvailableQuantity" Title="قابل فروش">
|
||||
<CellTemplate>
|
||||
<MudText Color="Color.Warning">@context.Item.AvailableQuantity</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.LowStockThreshold" Title="حد هشدار" />
|
||||
|
||||
<PropertyColumn Property="x => x.ReorderPoint" Title="نقطه سفارش" />
|
||||
|
||||
<TemplateColumn Title="کمبود">
|
||||
<CellTemplate>
|
||||
@{
|
||||
var shortage = context.Item.LowStockThreshold - context.Item.Quantity;
|
||||
}
|
||||
<MudChip T="string" Color="Color.Error" Size="Size.Small">
|
||||
@shortage عدد
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
|
||||
<TemplateColumn Title="عملیات" Sortable="false">
|
||||
<CellTemplate>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Success"
|
||||
Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="@(() => OpenAddStockDialog(context.Item))">
|
||||
ورود کالا
|
||||
</MudButton>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="LowStockItemDto"
|
||||
PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد سطرهای صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
}
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,120 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Foursat.BackOffice.BFF.Inventory.Protos;
|
||||
using BackOffice.Pages.Inventory.Components;
|
||||
using BackOffice.Common.Utilities;
|
||||
|
||||
namespace BackOffice.Pages.Inventory;
|
||||
|
||||
public partial class LowStockPage
|
||||
{
|
||||
[Inject] public InventoryBFFContract.InventoryBFFContractClient InventoryContract { get; set; } = default!;
|
||||
|
||||
private List<LowStockItemDto> _items = new();
|
||||
private List<WarehouseDto> _warehouses = new();
|
||||
private bool _isLoading = true;
|
||||
|
||||
// Filter fields
|
||||
private long? _filterWarehouseId;
|
||||
private ProductType _filterProductType = ProductType.Unspecified;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadWarehouses();
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private async Task LoadWarehouses()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await InventoryContract.GetAllWarehousesAsync(new GetAllWarehousesRequest
|
||||
{
|
||||
ActiveOnly = true
|
||||
});
|
||||
_warehouses = response.Warehouses.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری انبارها: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
_isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var request = new GetLowStockItemsRequest
|
||||
{
|
||||
Count = 200, // Get up to 200 low stock items
|
||||
ProductType = _filterProductType
|
||||
};
|
||||
|
||||
if (_filterWarehouseId.HasValue)
|
||||
{
|
||||
request.WarehouseId = _filterWarehouseId.Value;
|
||||
}
|
||||
|
||||
var response = await InventoryContract.GetLowStockItemsAsync(request);
|
||||
_items = response.Items.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری دادهها: {ex.Message}", Severity.Error);
|
||||
_items = new();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetProductTypeLabel(ProductType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ProductType.Regular => "محصول عادی",
|
||||
ProductType.Discount => "محصول تخفیفی",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private async Task OpenAddStockDialog(LowStockItemDto item)
|
||||
{
|
||||
var productId = item.ProductType == ProductType.Regular
|
||||
? (item.ProductId ?? 0)
|
||||
: (item.DiscountProductId ?? 0);
|
||||
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["Warehouses"] = _warehouses,
|
||||
["ProductIdParam"] = productId,
|
||||
["ProductTypeParam"] = item.ProductType,
|
||||
["ProductName"] = item.ProductName,
|
||||
["WarehouseIdParam"] = item.WarehouseId
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<AddStockDialog>($"ورود کالا - {item.ProductName}", parameters, new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Medium,
|
||||
FullWidth = true
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result!.Canceled)
|
||||
{
|
||||
Snackbar.Add("موجودی با موفقیت اضافه شد", Severity.Success);
|
||||
await LoadData();
|
||||
}
|
||||
}
|
||||
|
||||
private void GoBack()
|
||||
{
|
||||
Navigation.NavigateTo(RouteConstance.Inventory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
@attribute [Route(RouteConstance.InventoryMovements)]
|
||||
@attribute [Authorize]
|
||||
|
||||
@using Foursat.BackOffice.BFF.Inventory.Protos
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mb-4">
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.h4">تاریخچه تغییرات موجودی</MudText>
|
||||
@if (!string.IsNullOrEmpty(_productName))
|
||||
{
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Secondary">محصول: @_productName</MudText>
|
||||
}
|
||||
</MudStack>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
OnClick="GoBack">
|
||||
بازگشت به انبار
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@* Filters Section *@
|
||||
<MudPaper Class="pa-4 mb-4" Elevation="2">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudSelect T="StockMovementType" @bind-Value="_filterMovementType"
|
||||
Label="نوع تغییر"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense">
|
||||
<MudSelectItem Value="StockMovementType.Unspecified">همه</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.InitialStock">موجودی اولیه</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.Restock">ورود کالا</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.Return">برگشت از مشتری</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.Sale">فروش</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.AdjustmentIncrease">افزایش موجودی</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.AdjustmentDecrease">کاهش موجودی</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.Reserved">رزرو</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.Released">آزادسازی رزرو</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.Loss">خسارت</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.Damaged">آسیبدیده</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.Expired">منقضی</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.TransferOut">انتقال به بیرون</MudSelectItem>
|
||||
<MudSelectItem Value="StockMovementType.TransferIn">انتقال به داخل</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudSelect T="ProductType" @bind-Value="_filterProductType"
|
||||
Label="نوع محصول"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense">
|
||||
<MudSelectItem Value="ProductType.Unspecified">همه</MudSelectItem>
|
||||
<MudSelectItem Value="ProductType.Regular">محصول عادی</MudSelectItem>
|
||||
<MudSelectItem Value="ProductType.Discount">محصول تخفیفی</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudDatePicker @bind-Date="_filterFromDate"
|
||||
Label="از تاریخ"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Clearable="true" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudDatePicker @bind-Date="_filterToDate"
|
||||
Label="تا تاریخ"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Clearable="true" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" Class="d-flex justify-end">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="ApplyFilter"
|
||||
StartIcon="@Icons.Material.Filled.FilterAlt"
|
||||
Class="mr-2">
|
||||
اعمال فیلتر
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
OnClick="ClearFilter"
|
||||
StartIcon="@Icons.Material.Filled.Clear">
|
||||
پاک کردن
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
@* Data Grid *@
|
||||
<MudDataGrid T="StockMovementDto"
|
||||
ServerData="@(new Func<GridState<StockMovementDto>, Task<GridData<StockMovementDto>>>(ServerReload))"
|
||||
Hover="true"
|
||||
@ref="_gridData"
|
||||
Height="65vh">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">تغییرات موجودی</MudText>
|
||||
<MudSpacer />
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||
کل: @_totalCount ردیف
|
||||
</MudText>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
|
||||
<PropertyColumn Property="x => x.ProductName" Title="محصول" />
|
||||
|
||||
<PropertyColumn Property="x => x.MovementTypeName" Title="نوع تغییر">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Color="@GetMovementColor(context.Item.MovementType)"
|
||||
Size="Size.Small">
|
||||
@context.Item.MovementTypeName
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.Quantity" Title="تعداد">
|
||||
<CellTemplate>
|
||||
<MudText Color="@(IsIncreaseMovement(context.Item.MovementType) ? Color.Success : Color.Error)">
|
||||
@(IsIncreaseMovement(context.Item.MovementType) ? "+" : "-")@Math.Abs(context.Item.Quantity)
|
||||
</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.QuantityBefore" Title="قبل" />
|
||||
<PropertyColumn Property="x => x.QuantityAfter" Title="بعد" />
|
||||
|
||||
<PropertyColumn Property="x => x.ReferenceNumber" Title="شماره مرجع">
|
||||
<CellTemplate>
|
||||
@if (!string.IsNullOrEmpty(context.Item.ReferenceNumber))
|
||||
{
|
||||
<MudText Typo="Typo.caption">@context.Item.ReferenceNumber</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">-</MudText>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.PerformedByUserName" Title="کاربر">
|
||||
<CellTemplate>
|
||||
@if (!string.IsNullOrEmpty(context.Item.PerformedByUserName))
|
||||
{
|
||||
<MudText Typo="Typo.caption">@context.Item.PerformedByUserName</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">سیستم</MudText>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.CreatedAt" Title="تاریخ">
|
||||
<CellTemplate>
|
||||
@if (context.Item.CreatedAt != null)
|
||||
{
|
||||
<MudText Typo="Typo.caption">
|
||||
@context.Item.CreatedAt.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")
|
||||
</MudText>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.Note" Title="توضیحات">
|
||||
<CellTemplate>
|
||||
@if (!string.IsNullOrEmpty(context.Item.Note))
|
||||
{
|
||||
<MudTooltip Text="@context.Item.Note">
|
||||
<MudText Typo="Typo.caption" Style="max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
@context.Item.Note
|
||||
</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="StockMovementDto"
|
||||
PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد سطرهای صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,132 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Foursat.BackOffice.BFF.Inventory.Protos;
|
||||
using BackOffice.Common.Utilities;
|
||||
|
||||
namespace BackOffice.Pages.Inventory;
|
||||
|
||||
public partial class MovementsPage
|
||||
{
|
||||
[Inject] public InventoryBFFContract.InventoryBFFContractClient InventoryContract { get; set; } = default!;
|
||||
|
||||
[SupplyParameterFromQuery(Name = "itemId")]
|
||||
public long? ItemId { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery(Name = "productName")]
|
||||
public string? _productName { get; set; }
|
||||
|
||||
private MudDataGrid<StockMovementDto>? _gridData;
|
||||
private int _totalCount;
|
||||
|
||||
// Filter fields
|
||||
private StockMovementType _filterMovementType = StockMovementType.Unspecified;
|
||||
private ProductType _filterProductType = ProductType.Unspecified;
|
||||
private DateTime? _filterFromDate;
|
||||
private DateTime? _filterToDate;
|
||||
|
||||
private async Task<GridData<StockMovementDto>> ServerReload(GridState<StockMovementDto> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetStockMovementsRequest
|
||||
{
|
||||
PageIndex = state.Page + 1,
|
||||
PageSize = state.PageSize,
|
||||
MovementType = _filterMovementType,
|
||||
ProductType = _filterProductType
|
||||
};
|
||||
|
||||
if (ItemId.HasValue)
|
||||
{
|
||||
request.InventoryItemId = ItemId.Value;
|
||||
}
|
||||
|
||||
if (_filterFromDate.HasValue)
|
||||
{
|
||||
request.FromDate = Timestamp.FromDateTime(_filterFromDate.Value.ToUniversalTime());
|
||||
}
|
||||
|
||||
if (_filterToDate.HasValue)
|
||||
{
|
||||
request.ToDate = Timestamp.FromDateTime(_filterToDate.Value.ToUniversalTime());
|
||||
}
|
||||
|
||||
var result = await InventoryContract.GetStockMovementsAsync(request);
|
||||
|
||||
if (result?.Movements != null)
|
||||
{
|
||||
_totalCount = result.TotalCount;
|
||||
return new GridData<StockMovementDto>
|
||||
{
|
||||
Items = result.Movements.ToList(),
|
||||
TotalItems = result.TotalCount
|
||||
};
|
||||
}
|
||||
|
||||
return new GridData<StockMovementDto>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری دادهها: {ex.Message}", Severity.Error);
|
||||
return new GridData<StockMovementDto>();
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetMovementColor(StockMovementType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
StockMovementType.InitialStock => Color.Info,
|
||||
StockMovementType.Restock => Color.Success,
|
||||
StockMovementType.Return => Color.Success,
|
||||
StockMovementType.Sale => Color.Primary,
|
||||
StockMovementType.AdjustmentIncrease => Color.Success,
|
||||
StockMovementType.AdjustmentDecrease => Color.Warning,
|
||||
StockMovementType.Reserved => Color.Secondary,
|
||||
StockMovementType.Released => Color.Tertiary,
|
||||
StockMovementType.Loss => Color.Error,
|
||||
StockMovementType.Damaged => Color.Error,
|
||||
StockMovementType.Expired => Color.Error,
|
||||
StockMovementType.TransferOut => Color.Warning,
|
||||
StockMovementType.TransferIn => Color.Info,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
|
||||
private bool IsIncreaseMovement(StockMovementType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
StockMovementType.InitialStock => true,
|
||||
StockMovementType.Restock => true,
|
||||
StockMovementType.Return => true,
|
||||
StockMovementType.AdjustmentIncrease => true,
|
||||
StockMovementType.Released => true,
|
||||
StockMovementType.TransferIn => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ApplyFilter()
|
||||
{
|
||||
if (_gridData != null)
|
||||
{
|
||||
await _gridData.ReloadServerData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearFilter()
|
||||
{
|
||||
_filterMovementType = StockMovementType.Unspecified;
|
||||
_filterProductType = ProductType.Unspecified;
|
||||
_filterFromDate = null;
|
||||
_filterToDate = null;
|
||||
await ApplyFilter();
|
||||
}
|
||||
|
||||
private void GoBack()
|
||||
{
|
||||
Navigation.NavigateTo(RouteConstance.Inventory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
@attribute [Route(RouteConstance.InventoryWarehouses)]
|
||||
@attribute [Authorize]
|
||||
|
||||
@using Foursat.BackOffice.BFF.Inventory.Protos
|
||||
@using BackOffice.Pages.Inventory.Components
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mb-4">
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.h4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Warehouse" Color="Color.Primary" Class="mr-2" />
|
||||
مدیریت انبارها
|
||||
</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Secondary">
|
||||
تعریف و مدیریت انبارهای سیستم
|
||||
</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="OpenCreateWarehouseDialog">
|
||||
انبار جدید
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
OnClick="GoBack">
|
||||
بازگشت به انبار
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
|
||||
@* Filter Toggle *@
|
||||
<MudPaper Class="pa-4 mb-4" Elevation="2">
|
||||
<MudSwitch T="bool" @bind-Value="_showActiveOnly"
|
||||
Color="Color.Primary"
|
||||
Label="فقط انبارهای فعال"
|
||||
LabelPosition="LabelPosition.Start" />
|
||||
<MudButton Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
OnClick="LoadWarehouses"
|
||||
StartIcon="@Icons.Material.Filled.Refresh"
|
||||
Class="ml-4">
|
||||
بارگذاری مجدد
|
||||
</MudButton>
|
||||
</MudPaper>
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudGrid>
|
||||
@foreach (var warehouse in _warehouses)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudCard Elevation="3" Class="@(warehouse.IsDefault ? "border-2 border-solid mud-border-primary" : "")">
|
||||
<MudCardHeader>
|
||||
<CardHeaderAvatar>
|
||||
<MudAvatar Color="@(warehouse.IsActive ? Color.Success : Color.Default)" Size="Size.Large">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Warehouse" />
|
||||
</MudAvatar>
|
||||
</CardHeaderAvatar>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
@warehouse.Name
|
||||
@if (warehouse.IsDefault)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Primary" Size="Size.Small" Class="ml-2">پیشفرض</MudChip>
|
||||
}
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
کد: @warehouse.Code
|
||||
</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
<MudChip T="string" Color="@(warehouse.IsActive ? Color.Success : Color.Error)" Size="Size.Small">
|
||||
@(warehouse.IsActive ? "فعال" : "غیرفعال")
|
||||
</MudChip>
|
||||
</CardHeaderActions>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.LocationOn" Size="Size.Small" Color="Color.Secondary" />
|
||||
<MudText Typo="Typo.body2">
|
||||
@(string.IsNullOrEmpty(warehouse.Address) ? "آدرس ثبت نشده" : warehouse.Address)
|
||||
</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.CalendarToday" Size="Size.Small" Color="Color.Secondary" />
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
ایجاد: @(warehouse.Created?.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd") ?? "-")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.Visibility"
|
||||
OnClick="@(() => ViewWarehouseInventory(warehouse))">
|
||||
مشاهده موجودی
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
@if (_warehouses.Count == 0)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudAlert Severity="Severity.Info">
|
||||
هیچ انباری یافت نشد. برای شروع، یک انبار جدید ایجاد کنید.
|
||||
</MudAlert>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
}
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,73 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Foursat.BackOffice.BFF.Inventory.Protos;
|
||||
using BackOffice.Pages.Inventory.Components;
|
||||
using BackOffice.Common.Utilities;
|
||||
|
||||
namespace BackOffice.Pages.Inventory;
|
||||
|
||||
public partial class WarehousesPage
|
||||
{
|
||||
[Inject] public InventoryBFFContract.InventoryBFFContractClient InventoryContract { get; set; } = default!;
|
||||
|
||||
private List<WarehouseDto> _warehouses = new();
|
||||
private bool _isLoading = true;
|
||||
private bool _showActiveOnly = true;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadWarehouses();
|
||||
}
|
||||
|
||||
private async Task LoadWarehouses()
|
||||
{
|
||||
_isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var response = await InventoryContract.GetAllWarehousesAsync(new GetAllWarehousesRequest
|
||||
{
|
||||
ActiveOnly = _showActiveOnly
|
||||
});
|
||||
_warehouses = response.Warehouses.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری انبارها: {ex.Message}", Severity.Error);
|
||||
_warehouses = new();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenCreateWarehouseDialog()
|
||||
{
|
||||
var dialog = await DialogService.ShowAsync<CreateWarehouseDialog>("ایجاد انبار جدید", new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Small,
|
||||
FullWidth = true
|
||||
});
|
||||
|
||||
var result = await dialog.Result;
|
||||
if (!result!.Canceled)
|
||||
{
|
||||
Snackbar.Add("انبار با موفقیت ایجاد شد", Severity.Success);
|
||||
await LoadWarehouses();
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewWarehouseInventory(WarehouseDto warehouse)
|
||||
{
|
||||
Navigation.NavigateTo($"{RouteConstance.Inventory}?warehouseId={warehouse.Id}");
|
||||
}
|
||||
|
||||
private void GoBack()
|
||||
{
|
||||
Navigation.NavigateTo(RouteConstance.Inventory);
|
||||
}
|
||||
}
|
||||
@@ -366,7 +366,7 @@
|
||||
Color="Color.Info"
|
||||
StartIcon="@Icons.Material.Filled.Money"
|
||||
OnClick="@(() => NavigationManager.NavigateTo($"/commission/payouts?userId={UserId}"))">
|
||||
Payout های کاربر
|
||||
پرداختهای کاربر
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
|
||||
@@ -1,46 +1,86 @@
|
||||
@using BackOffice.BFF.ManualPayment.Protobuf
|
||||
@using BackOffice.Pages.Payment.Components
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@if (Mode == ManualPaymentDialogMode.Create)
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h6">ثبت پرداخت دستی جدید</MudText>
|
||||
<MudText Typo="Typo.h6">ثبت فعالسازی دستی جدید</MudText>
|
||||
|
||||
<MudNumericField T="long"
|
||||
Label="شناسه کاربر"
|
||||
Variant="Variant.Outlined"
|
||||
@bind-Value="_createModel.UserId" />
|
||||
<UserAutoComplete Label="انتخاب کاربر"
|
||||
@bind-SelectedUserId="_selectedUserId" />
|
||||
|
||||
<MudNumericField T="long"
|
||||
Label="مبلغ"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentText="ریال"
|
||||
AdornmentText="تومان"
|
||||
Disabled="true"
|
||||
@bind-Value="_createModel.Amount" />
|
||||
|
||||
<MudTextField T="string"
|
||||
Label="نوع / توضیح نوع"
|
||||
Variant="Variant.Outlined"
|
||||
@bind-Value="_createModel.TypeDisplay" />
|
||||
<MudSelect T="int"
|
||||
Label="نوع پرداخت"
|
||||
Variant="Variant.Outlined"
|
||||
@bind-Value="_selectedType">
|
||||
<MudSelectItem Value="1">واریز نقدی</MudSelectItem>
|
||||
<MudSelectItem Value="2">شارژ کیف پول تخفیف</MudSelectItem>
|
||||
<MudSelectItem Value="3">شارژ کیف پول شبکه</MudSelectItem>
|
||||
<MudSelectItem Value="4">تسویه حساب</MudSelectItem>
|
||||
<MudSelectItem Value="5">اصلاح خطا</MudSelectItem>
|
||||
<MudSelectItem Value="6">بازپرداخت</MudSelectItem>
|
||||
<MudSelectItem Value="99">سایر</MudSelectItem>
|
||||
</MudSelect>
|
||||
|
||||
<MudTextField T="string"
|
||||
Label="توضیحات"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="3"
|
||||
@bind-Value="_createModel.Description" />
|
||||
Required="true"
|
||||
RequiredError="توضیحات الزامی است"
|
||||
@bind-Value="_description" />
|
||||
|
||||
<MudTextField T="string"
|
||||
Label="شماره مرجع (اختیاری)"
|
||||
Variant="Variant.Outlined"
|
||||
@bind-Value="_createModel.ReferenceNumber" />
|
||||
|
||||
<MudText Typo="Typo.subtitle2">تصویر فیش پرداخت (اختیاری)</MudText>
|
||||
<MudFileUpload T="IBrowserFile"
|
||||
FilesChanged="OnImageFileSelected"
|
||||
Accept=".jpg,.jpeg,.png,.gif,.webp"
|
||||
MaximumFileCount="1">
|
||||
<ActivatorContent>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.CloudUpload">
|
||||
انتخاب تصویر
|
||||
</MudButton>
|
||||
</ActivatorContent>
|
||||
</MudFileUpload>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_imagePreview))
|
||||
{
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudImage Src="@_imagePreview"
|
||||
Alt="پیشنمایش فیش"
|
||||
Width="150"
|
||||
Height="150"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Class="rounded" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
OnClick="RemoveImage" />
|
||||
</MudStack>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
else if (Model is not null)
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h6">جزئیات پرداخت دستی</MudText>
|
||||
<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>
|
||||
@@ -52,6 +92,16 @@
|
||||
<MudText Typo="Typo.body2">شماره مرجع: @Model.ReferenceNumber</MudText>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.ImagePath))
|
||||
{
|
||||
<MudText Typo="Typo.body2">تصویر فیش:</MudText>
|
||||
<MudImage Src="@Model.ImagePath"
|
||||
Alt="تصویر فیش"
|
||||
Width="200"
|
||||
ObjectFit="ObjectFit.Contain"
|
||||
Class="rounded" />
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.body2">توضیحات: @Model.Description</MudText>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.RejectionReason))
|
||||
@@ -79,7 +129,11 @@
|
||||
|
||||
@if (Mode == ManualPaymentDialogMode.Create)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="CreateAsync">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="CreateAsync" Disabled="@_isSubmitting">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="me-2" />
|
||||
}
|
||||
ثبت
|
||||
</MudButton>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using BackOffice.BFF.ManualPayment.Protobuf;
|
||||
using Google.Protobuf;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.Payment.Components;
|
||||
@@ -27,19 +29,81 @@ public partial class ManualPaymentDialog
|
||||
[Parameter] public ManualPaymentDialogMode Mode { get; set; }
|
||||
[Parameter] public ManualPaymentModel? Model { get; set; }
|
||||
|
||||
private ManualPaymentModel _createModel = new();
|
||||
private ManualPaymentModel _createModel = new(){
|
||||
Amount = 56000000,
|
||||
};
|
||||
private string? _adminNote;
|
||||
|
||||
// User selection
|
||||
private long? _selectedUserId;
|
||||
|
||||
// Type and Description
|
||||
private int _selectedType = 1; // Default: CashDeposit
|
||||
private string _description = "عضویت دستی باشگاه مشتریان";
|
||||
|
||||
// Image upload
|
||||
private IBrowserFile? _imageFile;
|
||||
private string? _imagePreview;
|
||||
private const long MaxFileSize = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
private bool _isSubmitting;
|
||||
|
||||
private async Task OnImageFileSelected(IBrowserFile? file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
RemoveImage();
|
||||
return;
|
||||
}
|
||||
|
||||
_imageFile = file;
|
||||
|
||||
// Create preview
|
||||
var buffer = new byte[file.Size];
|
||||
await file.OpenReadStream(MaxFileSize).ReadAsync(buffer);
|
||||
_imagePreview = $"data:{file.ContentType};base64,{Convert.ToBase64String(buffer)}";
|
||||
}
|
||||
|
||||
private void RemoveImage()
|
||||
{
|
||||
_imageFile = null;
|
||||
_imagePreview = null;
|
||||
}
|
||||
|
||||
private async Task CreateAsync()
|
||||
{
|
||||
if (_isSubmitting) return;
|
||||
|
||||
// Validation
|
||||
if (!_selectedUserId.HasValue || _selectedUserId.Value <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفاً کاربر را انتخاب کنید.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_selectedType <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفاً نوع پرداخت را انتخاب کنید.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_description))
|
||||
{
|
||||
Snackbar.Add("لطفاً توضیحات را وارد کنید.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_isSubmitting = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var request = new CreateManualPaymentRequest
|
||||
{
|
||||
UserId = _createModel.UserId,
|
||||
UserId = _selectedUserId.Value,
|
||||
Amount = _createModel.Amount,
|
||||
Type = _createModel.Type,
|
||||
Description = _createModel.Description
|
||||
Type = _selectedType,
|
||||
Description = _description
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_createModel.ReferenceNumber))
|
||||
@@ -47,6 +111,20 @@ public partial class ManualPaymentDialog
|
||||
request.ReferenceNumber = _createModel.ReferenceNumber;
|
||||
}
|
||||
|
||||
// Upload image file
|
||||
if (_imageFile != null)
|
||||
{
|
||||
var buffer = new byte[_imageFile.Size];
|
||||
await _imageFile.OpenReadStream(MaxFileSize).ReadAsync(buffer);
|
||||
|
||||
request.ImageFile = new FileUploadModel
|
||||
{
|
||||
File = ByteString.CopyFrom(buffer),
|
||||
FileName = _imageFile.Name,
|
||||
Mime = _imageFile.ContentType
|
||||
};
|
||||
}
|
||||
|
||||
await ManualPaymentClient.CreateManualPaymentAsync(request);
|
||||
Snackbar.Add("پرداخت دستی با موفقیت ثبت شد.", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
@@ -55,6 +133,11 @@ public partial class ManualPaymentDialog
|
||||
{
|
||||
Snackbar.Add($"خطا در ثبت پرداخت دستی: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSubmitting = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApproveAsync()
|
||||
|
||||
@@ -45,11 +45,11 @@
|
||||
<Content>
|
||||
<MudPaper Class="pa-2 mb-2">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.subtitle1">پرداختهای دستی</MudText>
|
||||
<MudText Typo="Typo.subtitle1">فعالسازی (پرداخت) دستی</MudText>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
OnClick="OpenCreateDialog">
|
||||
ثبت پرداخت دستی جدید
|
||||
ثبت فعالسازی دستی جدید
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
</Filters>
|
||||
<Content>
|
||||
<MudGrid Class="mb-2">
|
||||
<MudItem xs="12" md="4">
|
||||
@* <MudItem xs="12" md="4">
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.subtitle2">جمع سفارشها در بازه فعلی</MudText>
|
||||
@@ -107,7 +107,7 @@
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudItem> *@
|
||||
</MudGrid>
|
||||
|
||||
<MudDataGrid T="DataModel"
|
||||
@@ -127,7 +127,7 @@
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه"/>
|
||||
<PropertyColumn Property="x => x.Amount" Title="مبلغ"/>
|
||||
<PropertyColumn Property="x => x.VatAmount" Title="مبلغ VAT">
|
||||
@* <PropertyColumn Property="x => x.VatAmount" Title="مبلغ VAT">
|
||||
<CellTemplate>
|
||||
@(context.Item.VatAmount > 0 ? context.Item.VatAmount.ToString("N0") : "-")
|
||||
</CellTemplate>
|
||||
@@ -136,7 +136,7 @@
|
||||
<CellTemplate>
|
||||
@(context.Item.VatPercentage > 0 ? $"{context.Item.VatPercentage:0}٪" : "-")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
</PropertyColumn> *@
|
||||
<PropertyColumn Property="x => x.UserFullName" Title="نام کاربر"/>
|
||||
<PropertyColumn Property="x => x.UserNationalCode" Title="کدملی"/>
|
||||
<TemplateColumn Title="روش پرداخت">
|
||||
|
||||
@@ -81,7 +81,16 @@
|
||||
آمار باشگاه
|
||||
</MudNavLink>
|
||||
</MudNavGroup>
|
||||
|
||||
@if (CanManagePayments)
|
||||
{
|
||||
<MudNavGroup Title="پرداختها و فعالسازیها" Icon="@Icons.Material.Filled.Payment" Expanded="false">
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/payment/manual-payments"
|
||||
Icon="@Icons.Material.Filled.Payments">
|
||||
فعالسازی (پرداخت) دستی
|
||||
</MudNavLink>
|
||||
</MudNavGroup>
|
||||
}
|
||||
<MudDivider Class="my-2" />
|
||||
<MudText Class="nav-menu__title" Typo="Typo.subtitle2">مدیریت</MudText>
|
||||
|
||||
@@ -106,7 +115,7 @@
|
||||
</MudNavLink>
|
||||
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/products/bulk-edit"
|
||||
Href="@(RouteConstance.ProductsBulkEdit)"
|
||||
Icon="@Icons.Material.Filled.EditNote">
|
||||
ویرایش دستهجمعی
|
||||
</MudNavLink>
|
||||
@@ -129,6 +138,29 @@
|
||||
مدیریت تگها
|
||||
</MudNavLink>
|
||||
</MudNavGroup>
|
||||
|
||||
<MudNavGroup Title="انبارداری" Icon="@Icons.Material.Filled.Warehouse" Expanded="false">
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.Inventory)"
|
||||
Icon="@Icons.Material.Filled.Inventory">
|
||||
موجودی انبار
|
||||
</MudNavLink>
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.InventoryLowStock)"
|
||||
Icon="@Icons.Material.Filled.Warning">
|
||||
محصولات کمموجود
|
||||
</MudNavLink>
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.InventoryWarehouses)"
|
||||
Icon="@Icons.Material.Filled.Store">
|
||||
مدیریت انبارها
|
||||
</MudNavLink>
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="@(RouteConstance.InventoryMovements)"
|
||||
Icon="@Icons.Material.Filled.History">
|
||||
تاریخچه تغییرات
|
||||
</MudNavLink>
|
||||
</MudNavGroup>
|
||||
}
|
||||
|
||||
@if (CanViewOrders)
|
||||
@@ -158,21 +190,7 @@
|
||||
</MudNavLink>
|
||||
}
|
||||
|
||||
@if (CanManagePayments)
|
||||
{
|
||||
<MudNavGroup Title="پرداختها" Icon="@Icons.Material.Filled.Payment" Expanded="false">
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/payment/manual-payments"
|
||||
Icon="@Icons.Material.Filled.Payments">
|
||||
پرداختهای دستی
|
||||
</MudNavLink>
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/payment/membership"
|
||||
Icon="@Icons.Material.Filled.CardMembership">
|
||||
پرداخت دستی عضویت
|
||||
</MudNavLink>
|
||||
</MudNavGroup>
|
||||
}
|
||||
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user