Compare commits
88 Commits
fac3d2c85e
...
production
| Author | SHA1 | Date | |
|---|---|---|---|
| 3eff5b6dfa | |||
| aa97d25302 | |||
| 24ae4ec15c | |||
| c3ee46ff62 | |||
| e9f8328404 | |||
| 6a9d142dc6 | |||
| 1d78633e17 | |||
| cbc61ad0f5 | |||
| 68d0aed468 | |||
| a9aa03d6a8 | |||
| e63b80fe47 | |||
| 4115f64fff | |||
| 7d892c2f6a | |||
| d757c8220c | |||
| b53d9facc9 | |||
| 5a004e5c88 | |||
| 5d83eb4e05 | |||
| 6126ecf023 | |||
| 31f0fc4860 | |||
| 0a2c5f913b | |||
| 2f040cab02 | |||
| 933cdb1094 | |||
| 4596296d97 | |||
| f2316eafe4 | |||
| 9c6149bbb9 | |||
| 8afc684098 | |||
| c17ad68c30 | |||
| 9378b7474c | |||
| 68d7647cb1 | |||
| c4712a00ac | |||
| 3b4562665d | |||
| 52c8965704 | |||
| 7fb2062721 | |||
| 797bfef964 | |||
| 6d8d681046 | |||
| b7d1fe13c7 | |||
| 67f57ce99f | |||
| c4cbe94ed7 | |||
| ed24f505d3 | |||
| 3c2a0edca8 | |||
| aed6258fb3 | |||
| e6d1d833ec | |||
| a8c52db544 | |||
| c0c7bb3ac5 | |||
| d0fe99492d | |||
| d38f50584c | |||
| 1c67d7798a | |||
| 2a7bb0592d | |||
| 9699833ac9 | |||
| b3e6066c90 | |||
| 148b8e4011 | |||
| 1ef5edf939 | |||
| 76dbee7d41 | |||
| 148a9e0493 | |||
| 851a1bbc4f | |||
| aa1d21a616 | |||
| 7147e9930f | |||
| 85208da1b5 | |||
| ea7414dc7f | |||
| a450d9a001 | |||
| 92c992249e | |||
| 6939780cd1 | |||
| e6cf90e3fa | |||
| e0203541bf | |||
| 8be98ae33e | |||
| c96377a674 | |||
| 89f5241680 | |||
| f1b0085181 | |||
| 60c91438ec | |||
| 172101d0ab | |||
| 1d8f34ae6c | |||
| 76b7211a5c | |||
| fe72774d08 | |||
| bdea2e8aef | |||
| 0386a2b1aa | |||
| 8f29fbaf71 | |||
| 7a62e3caac | |||
| 4eaca9bc4c | |||
| e541aec115 | |||
| c9affe62bd | |||
| 6147499f9c | |||
| 3409133757 | |||
| af51c02d3d | |||
| b9171d548b | |||
| 743403e4d4 | |||
| 44fd5f29d9 | |||
| e7bed65149 | |||
| 6fc00e134f |
@@ -0,0 +1,2 @@
|
|||||||
|
# Keep production config from current branch during merges — never overwrite
|
||||||
|
src/BackOffice/wwwroot/appsettings.Production.json merge=ours
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
name: Build and Deploy to Kubernetes
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- kub-stage
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: 194.5.195.53:30080
|
||||||
|
IMAGE_NAME: admin/backoffice
|
||||||
|
K8S_SERVER: 194.5.195.53
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: 194.5.195.53:32082/docker-sshpass:latest
|
||||||
|
options: --privileged
|
||||||
|
steps:
|
||||||
|
- name: Start Docker daemon
|
||||||
|
run: |
|
||||||
|
mkdir -p /etc/docker
|
||||||
|
cat > /etc/docker/daemon.json << 'DAEMON'
|
||||||
|
{
|
||||||
|
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"]
|
||||||
|
}
|
||||||
|
DAEMON
|
||||||
|
echo "🚀 Starting Docker daemon..."
|
||||||
|
dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs &
|
||||||
|
|
||||||
|
# Wait up to 3 minutes for Docker to be ready
|
||||||
|
for i in $(seq 1 90); do
|
||||||
|
if docker info >/dev/null 2>&1; then
|
||||||
|
echo "✅ Docker daemon is ready (attempt $i)"
|
||||||
|
docker version
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "⏳ Waiting for Docker daemon... (attempt $i/90)"
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Final check
|
||||||
|
if ! docker info >/dev/null 2>&1; then
|
||||||
|
echo "❌ Docker daemon failed to start after 3 minutes"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
run: |
|
||||||
|
git clone --depth 1 --branch kub-stage http://gitea-svc:3000/admin/BackOffice.git .
|
||||||
|
|
||||||
|
- name: Login to Docker registries
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin
|
||||||
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin
|
||||||
|
|
||||||
|
- name: Build Docker Image
|
||||||
|
run: |
|
||||||
|
cd src
|
||||||
|
DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -f BackOffice/Dockerfile .
|
||||||
|
|
||||||
|
- name: Push to Registry
|
||||||
|
run: |
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
|
- name: Deploy to Kubernetes
|
||||||
|
run: |
|
||||||
|
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
|
||||||
|
|
||||||
|
# Copy K8s manifests to server
|
||||||
|
sshpass -e scp -o StrictHostKeyChecking=no k8s/staging/backoffice-deployment.yaml root@${{ env.K8S_SERVER }}:/tmp/backoffice-deployment.yaml
|
||||||
|
|
||||||
|
# Apply manifests and restart
|
||||||
|
sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} "
|
||||||
|
kubectl apply -f /tmp/backoffice-deployment.yaml &&
|
||||||
|
kubectl rollout restart deployment/backoffice &&
|
||||||
|
kubectl rollout status deployment/backoffice --timeout=180s &&
|
||||||
|
rm -f /tmp/backoffice-deployment.yaml
|
||||||
|
"
|
||||||
|
echo "✅ Deployed!"
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
# سرویسهای BackOffice (پنل ادمین)
|
||||||
|
|
||||||
|
این سند تمام سرویسهایی را که **BackOffice** (فرانت ادمین) به **CMS** از طریق gRPC Gateway (`GwUrl`) فراخوانی میکند، به ترتیب **Use Case** فهرست میکند.
|
||||||
|
|
||||||
|
- **مسیر wrapper سرویسها:** `BackOffice/src/BackOffice/Services/`
|
||||||
|
- **ثبت gRPC:** `BackOffice/src/BackOffice/Common/Configure/ConfigureService.cs`
|
||||||
|
- **تاریخ:** ۱۴۰۵/۰۳/۱۷ (بروز: خرداد ۱۴۰۵ — گزارشهای read-only + proto 0.0.201؛ **بروز ۱۴۰۵/۰۳/۱۸ — دفتر تراکنشهای مالی proto 0.0.203**)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱. ورود و احراز هویت ادمین
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 1.1 | `OtpTokenContract.CreateNewOtpToken` | `OtpTokenContract` | `/Login/` (`LoginPage`) | ارسال OTP ورود ادمین |
|
||||||
|
| 1.2 | `UserContract.VerifyOtpToken` | `UserContract` | `/VerifyCodePage/{Mobile}` | تأیید OTP و دریافت JWT |
|
||||||
|
| 1.3 | `OtpTokenContract.CreateNewOtpToken` (resend) | `OtpTokenContract` | `/VerifyCodePage/{Mobile}` | ارسال مجدد OTP |
|
||||||
|
| 1.4 | `AuthorizationService.HasPermissionAsync` | — (JWT Role/Claim) | `NavMenu`, صفحات محافظتشده | کنترل نمایش منو بر اساس نقش (فعلاً role-based، نه API) |
|
||||||
|
| 1.5 | `AppTokenProvider` / `TokenHandler` | — (localStorage) | سراسری | attach کردن Bearer token به همه gRPC callها |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲. داشبورد
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 2.1 | `UserContract.GetAllUserByFilter` | `UserContract` | `/` (Index) | شمارش کاربران |
|
||||||
|
| 2.2 | `ProductsContract.GetAllProductsByFilter` | `ProductsContract` | `/` | شمارش محصولات |
|
||||||
|
| 2.3 | `PackageContract.GetAllPackageByFilter` | `PackageContract` | `/` | شمارش پکیجها |
|
||||||
|
| 2.4 | `CategoryContract.GetAllCategoryByFilter` | `CategoryContract` | `/` | شمارش دستهبندیها |
|
||||||
|
| 2.5 | `CommissionContract.GetAvailableWeeks` | `CommissionContract` | `/` | هفته جاری کمیسیون |
|
||||||
|
| 2.6 | `CommissionContract.GetWeeklyCommissionPool` | `CommissionContract` | `/` | وضعیت pool هفته جاری |
|
||||||
|
| 2.7 | `ClubMembershipContract.GetAllClubMemberships` | `ClubMembershipContract` | `/` | آمار اعضای باشگاه |
|
||||||
|
| 2.8 | `UserOrderContract.GetAllUserOrderByFilter` | `UserOrderContract` | `/` | سفارشهای اخیر |
|
||||||
|
| 2.9 | `IDiscountOrderService.GetOrdersAsync` | `DiscountOrderContract` | `Dashboard/DiscountShopWidget` | ویجت سفارشهای فروشگاه تخفیفی |
|
||||||
|
| 2.10 | `CommissionContract.GetWithdrawalRequests` | `CommissionContract` | `NavMenu` | badge درخواستهای برداشت معلق |
|
||||||
|
| 2.11 | `GlobalSearch` — `UserContract.GetAllUserByFilter` | `UserContract` | `MainLayout/GlobalSearch` | جستجوی سریع کاربر + ناوبری صفحات |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۳. مدیریت کاربران
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 3.1 | `UserContract.GetAllUserByFilter` | `UserContract` | `/UserPage/`, `UserAutoComplete`, GlobalSearch | لیست/جستجوی کاربران |
|
||||||
|
| 3.2 | `UserContract.DeleteUser` | `UserContract` | `/UserPage/` | حذف کاربر |
|
||||||
|
| 3.3 | Export users | `UserContract.GetAllUserByFilter` | `/UserPage/` | خروجی CSV کاربران |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۴. نقشها و دسترسیها
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 4.1 | `RoleContract.GetAllRoleByFilter` | `RoleContract` | `/RolePage/`, `RoleAutoComplete` | لیست نقشها |
|
||||||
|
| 4.2 | `RoleContract.CreateNewRole` | `RoleContract` | `Role/Components/CreateDialog` | ایجاد نقش |
|
||||||
|
| 4.3 | `RoleContract.UpdateRole` | `RoleContract` | `Role/Components/UpdateDialog` | ویرایش نقش |
|
||||||
|
| 4.4 | `RoleContract.DeleteRole` | `RoleContract` | `/RolePage/` | حذف نقش |
|
||||||
|
| 4.5 | `RolePermissionsDialog` | — (UI فقط) | `/RolePage/` | ویرایش permissionها — **هنوز به API وصل نشده** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵. نقش کاربر و آدرس
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 5.1 | `UserRoleContract.GetAllUserRoleByFilter` | `UserRoleContract` | `/UserRolePage/{UserId}` | نقشهای یک کاربر |
|
||||||
|
| 5.2 | `UserRoleContract.CreateNewUserRole` | `UserRoleContract` | CreateDialog, `UserRoleDialog` | تخصیص نقش |
|
||||||
|
| 5.3 | `UserRoleContract.UpdateUserRole` | `UserRoleContract` | UpdateDialog | ویرایش نقش کاربر |
|
||||||
|
| 5.4 | `UserRoleContract.DeleteUserRole` | `UserRoleContract` | `/UserRolePage/{UserId}` | حذف نقش کاربر |
|
||||||
|
| 5.5 | `RoleContract.GetAllRoleByFilter` | `RoleContract` | `UserRoleDialog`, `RoleTitleColumn` | نمایش عنوان نقش |
|
||||||
|
| 5.6 | `UserAddressContract.GetAllUserAddressByFilter` | `UserAddressContract` | `/UserAddressPage/{UserId}` | آدرسهای کاربر |
|
||||||
|
| 5.7 | `UserAddressContract.CreateNewUserAddress` | `UserAddressContract` | CreateDialog | ثبت آدرس |
|
||||||
|
| 5.8 | `UserAddressContract.UpdateUserAddress` | `UserAddressContract` | UpdateDialog | ویرایش آدرس |
|
||||||
|
| 5.9 | `UserAddressContract.DeleteUserAddress` | `UserAddressContract` | `/UserAddressPage/{UserId}` | حذف آدرس |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۶. پکیجها
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 6.1 | `PackageContract.GetAllPackageByFilter` | `PackageContract` | `/PackagePage/`, `PackageSelect`, ManualPaymentDialog | لیست پکیجها |
|
||||||
|
| 6.2 | `PackageContract.CreateNewPackage` | `PackageContract` | `Package/Components/CreateDialog` | ایجاد پکیج |
|
||||||
|
| 6.3 | `PackageContract.UpdatePackage` | `PackageContract` | `Package/Components/UpdateDialog` | ویرایش پکیج |
|
||||||
|
| 6.4 | `PackageContract.DeletePackage` | `PackageContract` | `/PackagePage/` | حذف پکیج |
|
||||||
|
| 6.5 | `ConfigurationContract.GetClubFeatures` | `ConfigurationContract` | Create/Update Dialog | انتخاب featureهای باشگاه مرتبط با پکیج |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۷. فروشگاه اصلی — محصولات و دستهبندی
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 7.1 | `ProductsContract.GetAllProductsByFilter` | `ProductsContract` | `/ProductsPage/`, BulkEdit, AutoComplete | لیست محصولات |
|
||||||
|
| 7.2 | `ProductsContract.CreateNewProducts` | `ProductsContract` | `Products/Components/CreateDialog` | ایجاد محصول |
|
||||||
|
| 7.3 | `ProductsContract.UpdateProducts` | `ProductsContract` | UpdateDialog | ویرایش محصول |
|
||||||
|
| 7.4 | `ProductsContract.DeleteProducts` | `ProductsContract` | `/ProductsPage/` | حذف محصول |
|
||||||
|
| 7.5 | `ProductsContract.ToggleProductStatus` | `ProductsContract` | `/ProductsPage/`, BulkEdit | فعال/غیرفعال |
|
||||||
|
| 7.6 | `ProductsContract.BulkUpdateProductPrices` | `ProductsContract` | `/ProductsBulkEditPage/` | ویرایش گروهی قیمت |
|
||||||
|
| 7.7 | `ProductsContract.GetProductGallery` / Add/Remove Image | `ProductsContract` | `GalleryDialog` | گالری تصاویر محصول |
|
||||||
|
| 7.8 | `ProductsContract.GetProducts` / `UpdateProductCategories` | `ProductsContract` | `/ProductCategoriesPage/{ProductId}` | drag-drop دستهبندی محصول |
|
||||||
|
| 7.9 | `CategoryContract.GetAllCategoryByFilter` | `CategoryContract` | `/CategoryPage/`, AutoComplete | لیست دستهها |
|
||||||
|
| 7.10 | `CategoryContract.GetCategory` / Create / Update / Delete | `CategoryContract` | CategoryMainPage, Dialogs | CRUD دستهبندی |
|
||||||
|
| 7.11 | `ProductsContract.GetProductsForCategory` / `UpdateCategoryProducts` | `ProductsContract` | `/CategoryProductsPage/{CategoryId}` | drag-drop محصولات یک دسته |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۸. انبارداری (Inventory)
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 8.1 | `InventoryContract.GetAllWarehouses` | `InventoryContract` | `/InventoryPage/`, Warehouses, LowStock | لیست انبارها |
|
||||||
|
| 8.2 | `InventoryContract.GetAllInventoryItems` | `InventoryContract` | `/InventoryPage/` | موجودی کالا |
|
||||||
|
| 8.3 | `InventoryContract.GetLowStockItems` | `InventoryContract` | `/InventoryPage/`, `/InventoryLowStockPage/` | اقلام کمموجود |
|
||||||
|
| 8.4 | `InventoryContract.AddStock` | `InventoryContract` | `AddStockDialog` | ورود کالا به انبار |
|
||||||
|
| 8.5 | `InventoryContract.AdjustStock` | `InventoryContract` | `AdjustStockDialog` | تعدیل موجودی |
|
||||||
|
| 8.6 | `InventoryContract.RecordLoss` | `InventoryContract` | `RecordLossDialog` | ثبت ضایعات |
|
||||||
|
| 8.7 | `InventoryContract.CreateWarehouse` | `InventoryContract` | `CreateWarehouseDialog` | ایجاد انبار |
|
||||||
|
| 8.8 | `InventoryContract.UpdateInventorySettings` | `InventoryContract` | `InventorySettingsDialog` | تنظیمات انبار |
|
||||||
|
| 8.9 | `InventoryContract.GetStockMovements` | `InventoryContract` | `/InventoryMovementsPage/` | تاریخچه جابجایی موجودی |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۹. سفارشهای فروشگاه اصلی
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 9.1 | `UserOrderContract.GetAllUserOrderByFilter` | `UserOrderContract` | `/OrdersPage/`, `/UserOrderPage/{UserId}`, SalesReports | لیست سفارشها |
|
||||||
|
| 9.2 | `UserOrderContract.GetUserOrder` | `UserOrderContract` | OrderDetailsDialog, Index | جزئیات سفارش |
|
||||||
|
| 9.3 | `UserOrderContract.UpdateUserOrder` | `UserOrderContract` | UserOrderDetailsDialog, ChangeOrderStatus | ویرایش سفارش / tracking |
|
||||||
|
| 9.4 | `UserOrderContract.UpdateOrderStatus` | `UserOrderContract` | ChangeOrderStatusDialog | تغییر وضعیت تحویل |
|
||||||
|
| 9.5 | `UserOrderContract.CancelOrder` | `UserOrderContract` | CancelOrderDialog | لغو سفارش |
|
||||||
|
| 9.6 | `UserOrderContract.DeleteUserOrder` | `UserOrderContract` | `/UserOrderPage/{UserId}` | حذف سفارش |
|
||||||
|
| 9.7 | `UserOrderContract.ApplyDiscountToOrder` | `UserOrderContract` | ApplyDiscountDialog | اعمال تخفیف روی سفارش |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۰. پرداخت دستی و تراکنشها
|
||||||
|
|
||||||
|
> **دو گزارش تراکنش — اشتباه نگیرید:**
|
||||||
|
>
|
||||||
|
> | مسیر | جدول DB | کاربرد |
|
||||||
|
> |------|---------|--------|
|
||||||
|
> | `/payment/transactions` | `PaymentTransactions` | ممیزی درگاه (Authority، ZarinPal، کارت) |
|
||||||
|
> | `/payment/ledger-transactions` | `Transactions` | دفتر مالی داخلی (نوع تراکنش، وضعیت، RefId) |
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 10.1 | `ManualPaymentContract.GetAllManualPayments` | `ManualPaymentContract` | `/payment/manual-payments` | لیست پرداختهای دستی |
|
||||||
|
| 10.2 | `ManualPaymentContract.CreateManualPayment` | `ManualPaymentContract` | `ManualPaymentDialog` | ثبت پرداخت دستی (مثلاً Daya) |
|
||||||
|
| 10.3 | `ManualPaymentContract.ApproveManualPayment` | `ManualPaymentContract` | ManualPayments, Dialog | تأیید پرداخت |
|
||||||
|
| 10.4 | `ManualPaymentContract.RejectManualPayment` | `ManualPaymentContract` | ManualPayments, Dialog | رد پرداخت |
|
||||||
|
| 10.5 | `PaymentTransactionContract.GetAllPaymentTransactionByFilter` | `PaymentTransactionContract` | `/payment/transactions` | گزارش read-only تراکنش **درگاه** (`PaymentTransactions`) |
|
||||||
|
| 10.5b | `PaymentTransactionContract.GetPaymentTransactionSummary` | `PaymentTransactionContract` | `/payment/transactions` | کارت خلاصه گزارش درگاه |
|
||||||
|
| 10.6 | `TransactionsContract.GetAllTransactionsByFilter` | `TransactionsContract` | `/payment/ledger-transactions` | گزارش read-only **دفتر مالی** (`Transactions`) |
|
||||||
|
| 10.7 | `TransactionsContract.GetTransactionSummary` | `TransactionsContract` | `/payment/ledger-transactions` | کارت خلاصه دفتر تراکنش |
|
||||||
|
| 10.8 | `UserPackagePurchaseContract.GetAllUserPackagePurchaseByFilter` | `UserPackagePurchaseContract` | `/club/purchases` | گزارش خرید پکیج |
|
||||||
|
| 10.9 | `ClubMembershipCycleContract.GetAllClubMembershipCycleByFilter` | `ClubMembershipCycleContract` | `/club/cycles` | گزارش دوره عضویت |
|
||||||
|
| 10.10 | `ClubMembershipCycleHistoryContract.GetAllClubMembershipCycleHistoryByFilter` | `ClubMembershipCycleHistoryContract` | `/club/history` (زیرتب دوره) | تاریخچه تغییرات دوره |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۱. کیف پول و تاریخچه
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 11.1 | `UserWalletContract.GetAllUserWalletByFilter` | `UserWalletContract` | `/wallets` | لیست کیف پول کاربران |
|
||||||
|
| 11.2 | `UserWalletHistoryContract.GetAllUserWalletHistoryByFilter` | `UserWalletHistoryContract` | `/wallets` (تب تاریخچه) | لاگ تغییرات کیف پول |
|
||||||
|
| 11.3 | `UserWalletContract.AdminManualCreditCharge` | `UserWalletContract` | `/payment/manual-credit-charges` | شارژ دستی کیف پول اصلی (Balance) |
|
||||||
|
| 11.4 | `UserWalletContract.GetManualCreditCharges` | `UserWalletContract` | `/payment/manual-credit-charges` | لیست شارژهای دستی (Type=17) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۲. قراردادهای کاربران
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 12.1 | `UserContractContract.GetAllUserContractByFilter` | `UserContractContract` | `/contracts` | لیست قراردادهای امضاشده (اصلی / باشگاه) |
|
||||||
|
| 12.2 | Download PDF | — (local JS) | `/contracts` | دانلود PDF قرارداد ذخیرهشده |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۳. کمیسیون و پاداش
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 13.1 | `CommissionContract.GetAvailableWeeks` | `CommissionContract` | `/commission/dashboard`, WeekNumberPicker | انتخاب هفته |
|
||||||
|
| 13.2 | `CommissionContract.GetWeeklyCommissionPool` | `CommissionContract` | `/commission/dashboard` | جزئیات pool هفتگی |
|
||||||
|
| 13.3 | `CommissionContract.TriggerWeeklyCalculation` | `CommissionContract` | Dashboard, WorkerControl | اجرای دستی محاسبه هفتگی |
|
||||||
|
| 13.4 | `CommissionContract.GetAllWeeklyPools` | `CommissionContract` | `/commission/reports` | گزارش poolهای هفتگی |
|
||||||
|
| 13.5 | `CommissionContract.GetUserCommissionPayouts` | `CommissionContract` | `/commission/payouts` | payoutهای کاربران |
|
||||||
|
| 13.6 | `CommissionContract.ProcessWithdrawal` | `CommissionContract` | UserPayouts, WithdrawalRequests | پردازش/واریز برداشت |
|
||||||
|
| 13.7 | `CommissionContract.BulkCreditPayouts` | `CommissionContract` | `/commission/payouts` | واریز گروهی payoutها |
|
||||||
|
| 13.8 | `CommissionContract.GetWithdrawalRequests` | `CommissionContract` | `/commission/withdrawals`, NavMenu | درخواستهای برداشت کاربران |
|
||||||
|
| 13.9 | `CommissionContract.ApproveWithdrawal` | `CommissionContract` | WithdrawalRequests | تأیید درخواست برداشت |
|
||||||
|
| 13.10 | `CommissionContract.RejectWithdrawal` | `CommissionContract` | WithdrawalRequests | رد درخواست برداشت |
|
||||||
|
| 13.11 | `CommissionContract.GetWithdrawalReports` | `CommissionContract` | `/commission/withdrawal-reports` | گزارش برداشتها |
|
||||||
|
| 13.12 | `CommissionContract.GetUserWeeklyBalances` | `CommissionContract` | `/network/balances` | گزارش بالانس هفتگی کاربران |
|
||||||
|
| 13.13 | `CommissionContract.GetWorkerStatus` | `CommissionContract` | `/system/worker-control` | وضعیت worker محاسبه |
|
||||||
|
| 13.14 | `CommissionContract.GetWorkerExecutionLogs` | `CommissionContract` | `/system/worker-control` | لاگ اجرای worker |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۴. شبکه فروش (MLM)
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 14.1 | `NetworkMembershipContract.GetNetworkTree` | `NetworkMembershipContract` | `/network/tree` | درخت شبکه (admin view) |
|
||||||
|
| 14.2 | `NetworkMembershipContract.GetUserNetwork` | `NetworkMembershipContract` | `/network/user-info/{UserId}` | اطلاعات شبکه یک کاربر |
|
||||||
|
| 14.3 | `NetworkMembershipContract.GetNetworkMembershipHistory` | `NetworkMembershipContract` | UserNetworkInfo | تاریخچه عضویت شبکه |
|
||||||
|
| 14.4 | `NetworkMembershipContract.ChangeNetworkParent` | `NetworkMembershipContract` | `ChangeParentDialog` | تغییر والد در درخت |
|
||||||
|
| 14.5 | `NetworkMembershipContract.GetNetworkStatistics` | `NetworkMembershipContract` | `/network/statistics` | آمار کلی شبکه (admin) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۵. باشگاه مشتریان
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 15.1 | `ClubMembershipContract.GetAllClubMemberships` | `ClubMembershipContract` | `/club/members`, Index | لیست اعضا |
|
||||||
|
| 15.2 | `ClubMembershipContract.ActivateClubMembership` | `ClubMembershipContract` | `ActivateClubDialog` | فعالسازی دستی عضویت |
|
||||||
|
| 15.3 | `ClubMembershipContract.DeactivateClubMembership` | `ClubMembershipContract` | `DeactivateClubDialog` | غیرفعالسازی |
|
||||||
|
| 15.4 | `ClubMembershipContract.GetClubMembershipHistory` | `ClubMembershipContract` | `/club/history` (زیرتب عضویت) | گزارش تاریخچه عضویت |
|
||||||
|
| 15.5 | `ClubMembershipContract.GetClubStatistics` | `ClubMembershipContract` | `/club/statistics` | آمار باشگاه |
|
||||||
|
| 15.6 | `ConfigurationContract.GetClubFeatures` | `ConfigurationContract` | `/club/features` | لیست featureهای باشگاه |
|
||||||
|
| 15.7 | `ClubMembershipContract.GetUserClubFeatures` | `ClubMembershipContract` | `/club/features` | featureهای یک کاربر |
|
||||||
|
| 15.8 | `ClubMembershipContract.ToggleUserClubFeature` | `ClubMembershipContract` | `/club/features` | فعال/غیرفعال feature برای کاربر |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۶. فروشگاه تخفیفی (Discount Shop)
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 16.1 | `IDiscountProductService` — CRUD | `DiscountProductContract` | `/discount-products` | مدیریت محصولات تخفیفی |
|
||||||
|
| 16.2 | `IDiscountCategoryService` — CRUD | `DiscountCategoryContract` | `/discount-categories` | مدیریت دستهبندی تخفیفی |
|
||||||
|
| 16.3 | `IDiscountOrderService.GetOrdersAsync` | `DiscountOrderContract.GetAllDiscountOrders` | `/discount-orders`, Dashboard widget | لیست سفارشها (admin) |
|
||||||
|
| 16.4 | `IDiscountOrderService.GetByIdAsync` | `DiscountOrderContract` | OrderDetailsDialog | جزئیات سفارش |
|
||||||
|
| 16.5 | `IDiscountOrderService.UpdateStatusAsync` | `DiscountOrderContract` | Orders page, OrderDetailsDialog | تغییر وضعیت پرداخت/ارسال |
|
||||||
|
| 16.6 | `IDiscountOrderService.GetSalesReportAsync` | `DiscountOrderContract` | `/discount-sales-reports` | گزارش فروش |
|
||||||
|
| 16.7 | `DiscountProductContract.GetDiscountProducts` | `DiscountProductContract` | `DiscountProductsAutoComplete` | autocomplete محصول |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۷. تگ محصولات
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 17.1 | `ITagService.GetTagsAsync` | `TagContract` | `/tags` | لیست تگها |
|
||||||
|
| 17.2 | `ITagService.CreateAsync` / `UpdateAsync` / `DeleteAsync` | `TagContract` | TagEditDialog, TagManagement | CRUD تگ |
|
||||||
|
| 17.3 | `ITagService.GetProductTagsAsync` | `ProductTagContract` | AssignTagsDialog | تگهای یک محصول |
|
||||||
|
| 17.4 | `ITagService.AssignToProductAsync` / `RemoveProductTagAsync` | `ProductTagContract` | AssignTagsDialog, Discount products | تخصیص/حذف تگ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۸. پیامهای عمومی
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 18.1 | `IPublicMessageService.GetMessagesAsync` | `PublicMessageContract` | `/public-messages` | لیست پیامها |
|
||||||
|
| 18.2 | `IPublicMessageService.GetByIdAsync` | `PublicMessageContract` | PublicMessages | جزئیات |
|
||||||
|
| 18.3 | `IPublicMessageService.CreateAsync` / `UpdateAsync` | `PublicMessageContract` | MessageFormDialog | ایجاد/ویرایش |
|
||||||
|
| 18.4 | `IPublicMessageService.PublishAsync` / `ArchiveAsync` | `PublicMessageContract` | PublicMessages | انتشار/بایگانی |
|
||||||
|
| 18.5 | `IPublicMessageService.DeleteAsync` | `PublicMessageContract` | PublicMessages | حذف |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۹. بلاگ
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 19.1 | `IBlogPostService.GetAllAsync` | `BlogPostContract` | `/blog/posts` | لیست پستها |
|
||||||
|
| 19.2 | `IBlogPostService.GetByIdAsync` | `BlogPostContract` | BlogPostEditDialog | جزئیات پست |
|
||||||
|
| 19.3 | `IBlogPostService.CreateAsync` / `UpdateAsync` | `BlogPostContract` | BlogPostEditDialog | ایجاد/ویرایش |
|
||||||
|
| 19.4 | `IBlogPostService.DeleteAsync` | `BlogPostContract` | BlogPostManagement | حذف |
|
||||||
|
| 19.5 | `IBlogPostService.PublishAsync` / `ArchiveAsync` | `BlogPostContract` | BlogPostManagement | انتشار/بایگانی |
|
||||||
|
| 19.6 | `IBlogCategoryService` — CRUD | `BlogCategoryContract` | `/blog/categories` | مدیریت دستهبندی بلاگ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲۰. مدیریت محتوای سایت
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 20.1 | `ISitePageSettingsService.GetAllAsync` | `SitePageSettingsContract` | `/content/page-settings` | لیست صفحات (landing, about, contact, licenses) |
|
||||||
|
| 20.2 | `ISitePageSettingsService.GetByKeyAsync` | `SitePageSettingsContract` | PageSettings dialogs | جزئیات یک صفحه |
|
||||||
|
| 20.3 | `ISitePageSettingsService.SaveSettingsAsync` | `SitePageSettingsContract` | PageSettingsEditDialog | ذخیره hero + JSON تنظیمات |
|
||||||
|
| 20.4 | `ISitePageSettingsService.SaveImageAsync` / `DeleteImageAsync` | `SitePageSettingsContract` | PageImagesDialog | مدیریت تصاویر گروهبندیشده |
|
||||||
|
| 20.5 | `ISitePageService` — CRUD sections | `SitePageContract` | `/content/pages` | صفحات داینامیک قدیمی (section-based) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲۱. تنظیمات سیستم و سلامت
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 21.1 | `ConfigurationContract.GetAllConfigurations` | `ConfigurationContract` | `/system/configuration` | خواندن key-value تنظیمات (کمیسیون، شبکه، باشگاه، VAT…) |
|
||||||
|
| 21.2 | `ConfigurationContract.CreateOrUpdateConfiguration` | `ConfigurationContract` | `/system/configuration` | ذخیره تنظیمات |
|
||||||
|
| 21.3 | `HealthContract.GetSystemHealth` | `HealthContract` | `/system/health` | وضعیت سلامت سرویسها |
|
||||||
|
| 21.4 | `AlertsMonitoring` | — (mock) | `/system/alerts` | **TODO — API هشدار هنوز وصل نشده** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲۲. نسخه اپلیکیشن
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 22.1 | `IAppVersionService.GetAllAsync` | `AppVersionContract` | `/settings/app-versions` | لیست نسخه FrontOffice/App |
|
||||||
|
| 22.2 | `IAppVersionService.GetByNameAsync` | `AppVersionContract` | AppVersions | جزئیات یک app |
|
||||||
|
| 22.3 | `IAppVersionService.UpdateAsync` | `AppVersionContract.UpdateAppVersion` | AppVersions dialog | بهروزرسانی نسخه + force update |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲۳. کامپوننتهای مشترک (AutoComplete / Hub)
|
||||||
|
|
||||||
|
| کامپوننت | Contract | کاربرد |
|
||||||
|
|----------|----------|--------|
|
||||||
|
| `UserAutoComplete` | `UserContract` | جستجوی کاربر در فرمها |
|
||||||
|
| `ProductsAutoComplete` | `ProductsContract` | جستجوی محصول |
|
||||||
|
| `CategoryAutoComplete` / MultiSelect | `CategoryContract` | انتخاب دسته |
|
||||||
|
| `PackageSelect` | `PackageContract` | انتخاب پکیج |
|
||||||
|
| `WeekNumberPicker` | `CommissionContract` | انتخاب هفته |
|
||||||
|
| `DiscountProductsAutoComplete` | `DiscountProductContract` | جستجوی محصول تخفیفی |
|
||||||
|
| Hub pages (`ClubHub`, `DiscountShopHub`, `SystemHub`, `OrdersHub`, `BlogHub`) | — | مسیریابی tab-based؛ RPC در child component |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲۴. سرویسهای ثبتشده ولی بدون استفاده / ناقص
|
||||||
|
|
||||||
|
| سرویس / صفحه | وضعیت |
|
||||||
|
|--------------|--------|
|
||||||
|
| `IBlogPostImageService` (`BlogPostImageContract`) | ثبت در DI؛ **هیچ صفحهای inject نمیکند** |
|
||||||
|
| `RolePermissionsDialog` | UI permission؛ **ذخیره در backend TODO** |
|
||||||
|
| `AuthorizationService` | فقط JWT role؛ **Permission API از CMS/BFF TODO** |
|
||||||
|
| `/system/alerts` | داده mock؛ **AlertContract وجود ندارد** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## نمودار جریان ورود ادمین
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[LoginPage] -->|CreateNewOtpToken| B[SMS OTP]
|
||||||
|
B --> C[VerifyCodePage]
|
||||||
|
C -->|VerifyOtpToken| D[JWT in localStorage]
|
||||||
|
D --> E[All gRPC via TokenHandler]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست gRPC Contractهای فعال BackOffice
|
||||||
|
|
||||||
|
| Contract | Wrapper / استفاده مستقیم |
|
||||||
|
|----------|---------------------------|
|
||||||
|
| `OtpTokenContract` | Login |
|
||||||
|
| `UserContract` | Login, Users, Dashboard, Search |
|
||||||
|
| `RoleContract` | Roles, UserRoles |
|
||||||
|
| `UserRoleContract` | UserRole pages |
|
||||||
|
| `UserAddressContract` | UserAddress pages |
|
||||||
|
| `PackageContract` | Packages, ManualPayment |
|
||||||
|
| `ProductsContract` | Products, Categories drag-drop |
|
||||||
|
| `CategoryContract` | Categories |
|
||||||
|
| `UserOrderContract` | Orders |
|
||||||
|
| `ManualPaymentContract` | Manual payments |
|
||||||
|
| `PaymentTransactionContract` | Gateway transaction report (`/payment/transactions`) |
|
||||||
|
| `TransactionsContract` | Ledger transaction report (`/payment/ledger-transactions`) |
|
||||||
|
| `UserPackagePurchaseContract` | Club package purchase report |
|
||||||
|
| `ClubMembershipCycleContract` | Club cycle report |
|
||||||
|
| `ClubMembershipCycleHistoryContract` | Club cycle history report |
|
||||||
|
| `UserWalletContract` | Wallet management |
|
||||||
|
| `UserWalletHistoryContract` | Wallet change log |
|
||||||
|
| `UserContractContract` | Signed contracts |
|
||||||
|
| `CommissionContract` | Commission, Network balances, Worker |
|
||||||
|
| `NetworkMembershipContract` | Network tree/info |
|
||||||
|
| `ClubMembershipContract` | Club members/features |
|
||||||
|
| `ConfigurationContract` | System config, Club features, Package |
|
||||||
|
| `DiscountProductContract` | DiscountProductService |
|
||||||
|
| `DiscountCategoryContract` | DiscountCategoryService |
|
||||||
|
| `DiscountOrderContract` | DiscountOrderService |
|
||||||
|
| `TagContract` / `ProductTagContract` | TagService |
|
||||||
|
| `PublicMessageContract` | PublicMessageService |
|
||||||
|
| `InventoryContract` | Inventory pages |
|
||||||
|
| `BlogPostContract` | BlogPostService |
|
||||||
|
| `BlogCategoryContract` | BlogCategoryService |
|
||||||
|
| `BlogPostImageContract` | BlogPostImageService *(unused)* |
|
||||||
|
| `SitePageSettingsContract` | SitePageSettingsService |
|
||||||
|
| `SitePageContract` | SitePageService |
|
||||||
|
| `AppVersionContract` | AppVersionService |
|
||||||
|
| `HealthContract` | Health dashboard |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مقایسه با FrontOffice
|
||||||
|
|
||||||
|
| موضوع | FrontOffice (مشتری) | BackOffice (ادمین) |
|
||||||
|
|-------|----------------------|-------------------|
|
||||||
|
| Auth | OTP + JWT (UserContract) | OTP + JWT (همان flow) |
|
||||||
|
| Orders | Customer RPC | Admin filter + status management |
|
||||||
|
| Discount Shop | User orders + checkout | Full CRUD + admin order management |
|
||||||
|
| Commission | Read-only (my payouts) | Full admin (calculation, approval, reports) |
|
||||||
|
| Network | My tree only | Full tree + change parent |
|
||||||
|
| Content | Read page settings | Edit page settings + blog + public messages |
|
||||||
|
| Wallet | My balances | All users wallet + history log |
|
||||||
|
|
||||||
|
مرجع مشابه برای فرانت مشتری: [`FrontOffice/docs/CUSTOMER-SERVICES.md`](../FrontOffice/docs/CUSTOMER-SERVICES.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## یادداشت deploy (خرداد ۱۴۰۵)
|
||||||
|
|
||||||
|
| گام | جزئیات |
|
||||||
|
|-----|--------|
|
||||||
|
| Proto | `Foursat.CMSMicroservice.Protobuf` **0.0.203** (گزارش دفتر تراکنش) |
|
||||||
|
| CMS | deploy WebApi (شامل `GetTransactionSummary` + فیلتر تاریخ) |
|
||||||
|
| BackOffice | NuGet 0.0.203 یا ProjectReference در monorepo |
|
||||||
|
| FrontOffice | **redeploy لازم نیست** برای گزارش ledger؛ برای شارژ کیف اصلی proto **0.0.202** لازم است |
|
||||||
|
|
||||||
|
گزارشهای read-only (بخش ۱۰ و ۱۵): فقط فیلتر + grid + summary + CSV — بدون CRUD یا detail dialog.
|
||||||
|
|
||||||
|
مستندات فنی کامل: [`totalDoc/technical/TECH-08-SESSION-LOG-202606.md`](../../totalDoc/technical/TECH-08-SESSION-LOG-202606.md) — §10 Ledger transactions
|
||||||
+2
-1008
File diff suppressed because it is too large
Load Diff
@@ -1,751 +1,3 @@
|
|||||||
# 📝 BackOffice — Changelog
|
# Docs moved to totalDoc
|
||||||
|
|
||||||
**آخرین بروزرسانی:** بهمن ۱۴۰۴ (February 2026)
|
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏷 فاز ۱۴ — فیکس Docker Build + Deployment (بهمن ۱۴۰۴)
|
|
||||||
|
|
||||||
> **هدف:** رفع مشکلات Docker build و CI/CD pipeline
|
|
||||||
|
|
||||||
### ۱۴.۱ — تغییر به NuGet Package ✅
|
|
||||||
|
|
||||||
- **مشکل:** `ProjectReference` به `../../../CMS/src/CMSMicroservice.Protobuf/` خارج از Docker build context بود
|
|
||||||
- **رفع:** تغییر به `PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.178"`
|
|
||||||
- **فایل:** `BackOffice.csproj`
|
|
||||||
|
|
||||||
### ۱۴.۲ — حذف COPY libs/ از Dockerfile ✅
|
|
||||||
|
|
||||||
- **مشکل:** `COPY ["libs/", "libs/"]` فیل میشد — `libs/` خارج از build context
|
|
||||||
- **رفع:** حذف خط — دیگه نیازی به DLLهای BFF نیست
|
|
||||||
- **فایل:** `Dockerfile`
|
|
||||||
|
|
||||||
### ۱۴.۳ — تغییر nginx:alpine به رجیستری لوکال ✅
|
|
||||||
|
|
||||||
- **مشکل:** `FROM nginx:alpine` از Docker Hub → TLS handshake timeout
|
|
||||||
- **رفع:** `FROM 194.5.195.53:32082/nginx:alpine`
|
|
||||||
- **فایل:** `Dockerfile`
|
|
||||||
|
|
||||||
### ۱۴.۴ — اضافه شدن SERVER_PASSWORD secret ✅
|
|
||||||
|
|
||||||
- **مشکل:** مرحله Deploy to Kubernetes با `Permission denied` فیل میشد
|
|
||||||
- **رفع:** سکرت `SERVER_PASSWORD` به ریپو Gitea اضافه شد (BackOffice + FrontOffice)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏷 فاز ۱۳ — بهبودهای UI بکآفیس (بهمن ۱۴۰۴)
|
|
||||||
|
|
||||||
> **هدف:** رفع مشکلات بصری، بهبود UX فرمها و ناوبری
|
|
||||||
|
|
||||||
### ۱۳.۱ — فیکس کامپوننت Image (پیشوند `/uploads/`) ✅
|
|
||||||
|
|
||||||
- **مشکل:** تصاویر آپلودشده (بلاگ، صفحات سایت، سکشنها) در بکآفیس نمایش داده نمیشدند — مسیر `GwUrl + Src` با مسیر `UploadsController` هماهنگ نبود
|
|
||||||
- **رفع:** اضافه شدن `/uploads/` بین `FileBaseUrl` و مسیر نسبی تصویر
|
|
||||||
- **فایل:** `BackOffice/Common/BaseComponents/Image.razor.cs`
|
|
||||||
|
|
||||||
### ۱۳.۲ — فیکس فیلتر هفته در گزارشهای هفتگی ✅
|
|
||||||
|
|
||||||
- **مشکل:** فیلتر هفته از `MudTextField` (ورودی متنی) استفاده میکرد — کاربر باید عدد حدس میزد
|
|
||||||
- **رفع:** جایگزینی با `WeekNumberPicker` — انتخاب هفته از لیست با بایندینگ `WeekDefinitionId`
|
|
||||||
- **فایلها:**
|
|
||||||
- `CMS/src/CMSMicroservice.Protobuf/Protos/commission.proto` — تغییر فیلدها از `StringValue` به `Int64Value`
|
|
||||||
- `CMS/.../CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQuery.cs` + `Handler.cs`
|
|
||||||
- `BackOffice/Pages/Commission/WeeklyReports.razor`
|
|
||||||
|
|
||||||
### ۱۳.۳ — فیکس دانلود PDF قراردادها ✅
|
|
||||||
|
|
||||||
- **مشکل:** فیلد `SignedPdfFile` محتوای HTML کامل دارد (نه URL فایل PDF) — دکمه دانلود کار نمیکرد
|
|
||||||
- **رفع:** تابع JS جدید `jsHtmlToPdf()` برای باز کردن HTML در پنجره جدید و چاپ/ذخیره بهعنوان PDF
|
|
||||||
- **فایلها:**
|
|
||||||
- `BackOffice/wwwroot/js/main.js` — تابع `jsHtmlToPdf`
|
|
||||||
- `BackOffice/Pages/Contract/UserContractPage.razor`
|
|
||||||
- `BackOffice/Pages/Contract/ContractDetailsDialog.razor`
|
|
||||||
|
|
||||||
### ۱۳.۴ — یکسانسازی ارتفاع ویرایشگر HTML ✅
|
|
||||||
|
|
||||||
- **مشکل:** ارتفاع `MudHtmlEditor` در صفحات مختلف متفاوت بود — برخی خیلی کوتاه
|
|
||||||
- **رفع:** اضافه شدن `MinHeight="300px"` به تمام ۷ ویرایشگر HTML
|
|
||||||
- **فایلها:** `BlogPostEditDialog`, `SitePageSectionEditDialog`, `DiscountShop/ProductFormDialog`, `Package/CreateDialog`, `Package/UpdateDialog`, `Products/CreateDialog`, `Products/UpdateDialog`
|
|
||||||
|
|
||||||
### ۱۳.۵ — تولید خودکار اسلاگ بلاگ ✅
|
|
||||||
|
|
||||||
- **مشکل:** کاربر باید اسلاگ URL را دستی وارد میکرد — غیرضروری و مستعد خطا
|
|
||||||
- **رفع:** حذف فیلد اسلاگ از فرم، تولید خودکار `post-{GUID}` در بکاند (حداکثر ۲۰ کاراکتر)
|
|
||||||
- **فایلها:**
|
|
||||||
- `CMS/.../BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandHandler.cs`
|
|
||||||
- `CMS/.../BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandHandler.cs`
|
|
||||||
- `BackOffice/Pages/Blog/Components/BlogPostEditDialog.razor`
|
|
||||||
|
|
||||||
### ۱۳.۶ — انتقال تگها به منوی جداگانه ✅
|
|
||||||
|
|
||||||
- **مشکل:** تگها فقط از تب سوم بلاگ قابل دسترسی بودند — ولی تگها بین محصولات و بلاگ مشترکند
|
|
||||||
- **رفع:** اضافه شدن مسیر `/tags` به `TagManagementPage`، لینک جدید «مدیریت تگها» در منوی ناوبری، حذف تب تگها از `BlogHub`
|
|
||||||
- **فایلها:**
|
|
||||||
- `BackOffice/Pages/Tag/TagManagementPage.razor`
|
|
||||||
- `BackOffice/Shared/NavMenu.razor`
|
|
||||||
- `BackOffice/Pages/Blog/BlogHub.razor`
|
|
||||||
|
|
||||||
### ۱۳.۷ — تولید خودکار کلید سکشن ✅
|
|
||||||
|
|
||||||
- **مشکل:** کاربر باید کلید سکشن صفحه سایت را دستی وارد میکرد (مثلاً `about-us-section`)
|
|
||||||
- **رفع:** فیلد اختیاری شد، تولید خودکار `section-{GUID}` (حداکثر ۲۰ کاراکتر) در هندلر CMS
|
|
||||||
- **فایلها:**
|
|
||||||
- `CMS/.../SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandHandler.cs`
|
|
||||||
- `CMS/.../SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandValidator.cs`
|
|
||||||
- `BackOffice/Pages/Content/Components/SitePageSectionEditDialog.razor`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏷 فاز ۱۲ — فیکس فلوی ثبتنام / ورود FrontOffice (بهمن ۱۴۰۴)
|
|
||||||
|
|
||||||
> **هدف:** رفع ۳ باگ بحرانی در فلوی ثبتنام و ورود FrontOffice
|
|
||||||
> **مستند کامل:** [`totalDoc/cms/REGISTRATION-FLOW-FIXES.md`](../../totalDoc/cms/REGISTRATION-FLOW-FIXES.md)
|
|
||||||
|
|
||||||
### ۱۲.۱ — رفع ایجاد کاربر جدید در VerifyOtpToken 🔴 ✅
|
|
||||||
|
|
||||||
- **مشکل:** `UserCQ/VerifyOtpTokenCommandHandler` کاربران جدید ایجاد نمیکرد — فقط خطای «کاربر یافت نشد»
|
|
||||||
- **رفع:** اضافه شدن لاجیک کامل: اعتبارسنجی کد معرف، بررسی ظرفیت درخت باینری (حداکثر ۲ فرزند)، تعیین شاخه (چپ/راست)، ایجاد User + UserRole + UserWallet، رویدادهای دامنه
|
|
||||||
- **فایل:** `CMS/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs`
|
|
||||||
|
|
||||||
### ۱۲.۲ — رفع جستجوی موبایل در AcceptContract 🔴 ✅
|
|
||||||
|
|
||||||
- **مشکل:** `_currentUserService.Username` = `"{FirstName} {LastName}"` — نه شماره موبایل! در نتیجه OTP و کاربر هیچوقت پیدا نمیشد
|
|
||||||
- **رفع:** ابتدا کاربر از `UserId` پیدا شود، سپس `user.Mobile` برای جستجوی OTP استفاده شود
|
|
||||||
- **فایل:** `CMS/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs`
|
|
||||||
|
|
||||||
### ۱۲.۳ — رفع `IsCompleteRegister()` در FrontOffice 🟡 ✅
|
|
||||||
|
|
||||||
- **مشکل:** `InitUserAuthInfo().GetAwaiter()` بدون `.GetResult()` — عملیات async اجرا نمیشود، منوی کناری وضعیت نادرست
|
|
||||||
- **رفع:** تغییر به `InitUserAuthInfo().GetAwaiter().GetResult()`
|
|
||||||
- **فایل:** `FrontOffice/src/FrontOffice.Main/Utilities/AuthService.cs`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏷 فاز ۱۱ — رفع خطای صفحات + سرویس عمومی تصاویر (بهمن ۱۴۰۴)
|
|
||||||
|
|
||||||
> **هدف:** رفع خطای ۳ صفحه BackOffice + ایجاد اندپوینت HTTP عمومی برای سرو تصاویر
|
|
||||||
|
|
||||||
### ۱۱.۱ — سرویس عمومی سرو تصاویر (CMS) ✅
|
|
||||||
|
|
||||||
- **`UploadsController`** — اندپوینت `GET /uploads/{path}` برای سرو مستقیم فایلها
|
|
||||||
- `[AllowAnonymous]` — بدون نیاز به احراز هویت
|
|
||||||
- کش مرورگر ۲۴ ساعته (`ResponseCache`)
|
|
||||||
- پشتیبانی از Range Requests
|
|
||||||
- **FMS Fallback:** اگر فایل محلی نباشد، از `dl.afrino.co` دانلود و کش میشود
|
|
||||||
- `IHttpClientFactory` با named client `"FMS"` (timeout: 30s)
|
|
||||||
- **فایلها:** `CMS/WebApi/Controllers/UploadsController.cs`, `CMS/WebApi/Program.cs`
|
|
||||||
|
|
||||||
### ۱۱.۲ — رفع صفحه «صفحات سایت» ✅
|
|
||||||
|
|
||||||
- اضافه شدن **دکمه «افزودن صفحه جدید»** به بالای صفحه
|
|
||||||
- اضافه شدن **دکمه «حذف»** (آیکون قرمز) برای هر ردیف
|
|
||||||
- ایجاد **`SitePageCreateDialog`** — دیالوگ ایجاد صفحه جدید با فیلدها:
|
|
||||||
- کلید صفحه (PageKey)، عنوان، توضیحات متا، عنوان هیرو، زیرعنوان هیرو، تصویر هیرو، وضعیت
|
|
||||||
- Proto: اضافه شدن `CreateSitePage` و `DeleteSitePage` RPC
|
|
||||||
- CQRS: `CreateSitePageCommand/Handler` و `DeleteSitePageCommand/Handler`
|
|
||||||
- **فایلها:**
|
|
||||||
- `sitepage.proto` — 2 RPC جدید + پیامهای Request/Response
|
|
||||||
- `SitePageCQ/Commands/CreateSitePage/` — Command + Handler
|
|
||||||
- `SitePageCQ/Commands/DeleteSitePage/` — Command + Handler
|
|
||||||
- `CMS/WebApi/Services/SitePageService.cs` — 2 override جدید
|
|
||||||
- `BackOffice/Services/Content/ISitePageService.cs` — `CreateAsync` + `DeleteAsync` + `SitePageCreateDto`
|
|
||||||
- `BackOffice/Services/Content/SitePageService.cs` — پیادهسازی Create/Delete
|
|
||||||
- `BackOffice/Pages/Content/SitePageManagementPage.razor` — دکمههای Add/Delete
|
|
||||||
- `BackOffice/Pages/Content/SitePageManagementPage.razor.cs` — متدهای CreatePage/DeletePage
|
|
||||||
- `BackOffice/Pages/Content/Components/SitePageCreateDialog.razor` + `.razor.cs`
|
|
||||||
|
|
||||||
### ۱۱.۳ — رفع صفحه «قراردادها» (Contracts) ✅
|
|
||||||
|
|
||||||
- **مشکل:** صفحه `/contracts` اصلاً بارگذاری نمیشد — crash در DI
|
|
||||||
- **علت:** `UserContractContract.UserContractContractClient` در DI ثبت نشده بود
|
|
||||||
- **رفع:** اضافه شدن `using CMSMicroservice.Protobuf.Protos.UserContract` و ثبت gRPC client
|
|
||||||
- **فایل:** `BackOffice/Common/Configure/ConfigureService.cs`
|
|
||||||
|
|
||||||
### ۱۱.۴ — رفع صفحه «کیف پول کاربران» (Wallets) ✅
|
|
||||||
|
|
||||||
- **مشکل:** صفحه `/wallets` اصلاً بارگذاری نمیشد — crash در DI
|
|
||||||
- **علت:** دو سرویس ثبت نشده بود:
|
|
||||||
- `UserWalletContract.UserWalletContractClient`
|
|
||||||
- `UserWalletChangeLogContract.UserWalletChangeLogContractClient`
|
|
||||||
- **رفع:** اضافه شدن usingها و ثبت هر دو gRPC client
|
|
||||||
- **فایل:** `BackOffice/Common/Configure/ConfigureService.cs`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏷 فاز ۱۰ — بهبود مدیریت محتوا: بلاگ + صفحات سایت (بهمن ۱۴۰۴)
|
|
||||||
|
|
||||||
> **هدف:** فعالسازی آپلود تصویر باینری و ادیتور HTML در صفحات بلاگ و صفحات سایت
|
|
||||||
|
|
||||||
### ۱۰.۱ — بهبود ادیتور پست بلاگ ✅
|
|
||||||
|
|
||||||
- **`MudTextField Lines="10"`** → **`MudHtmlEditor`** برای ویرایش محتوای HTML
|
|
||||||
- **فیلد متنی مسیر تصویر** → **`MudFileUpload`** با پیشنمایش تصویر (`Image` component)
|
|
||||||
- حذف فیلد `FeaturedImageThumbnailPath` (تولید خودکار توسط بکاند)
|
|
||||||
- اضافه شدن `MudForm` با اعتبارسنجی + حالت بارگذاری (`_loading`)
|
|
||||||
- مدیریت خطا: try-catch + Snackbar
|
|
||||||
- **فایلها:** `Pages/Blog/Components/BlogPostEditDialog.razor` + `.razor.cs`
|
|
||||||
|
|
||||||
### ۱۰.۲ — بهبود ادیتور صفحه سایت ✅
|
|
||||||
|
|
||||||
- **فیلد متنی Hero Image** → **`MudFileUpload`** با پیشنمایش تصویر
|
|
||||||
- اضافه شدن `MudForm` + حالت آپلود + مدیریت خطا
|
|
||||||
- **فایلها:** `Pages/Content/Components/SitePageEditDialog.razor` + `.razor.cs`
|
|
||||||
|
|
||||||
### ۱۰.۳ — بهبود ادیتور بخش صفحه سایت (Section) ✅
|
|
||||||
|
|
||||||
- **`MudTextField Lines="8"`** → **`MudHtmlEditor`** برای ویرایش محتوای HTML بخشها
|
|
||||||
- **فیلدهای متنی تصویر/thumbnail** → **`MudFileUpload`** با پیشنمایش
|
|
||||||
- اضافه شدن `MudForm` + حالت آپلود + مدیریت خطا
|
|
||||||
- **فایلها:** `Pages/Content/Components/SitePageSectionEditDialog.razor` + `.razor.cs`
|
|
||||||
|
|
||||||
### ۱۰.۴ — تغییرات Proto ✅
|
|
||||||
|
|
||||||
| Proto | تغییر |
|
|
||||||
|-------|-------|
|
|
||||||
| `blogpost.proto` | اضافه شدن `BlogImageFileModel` + فیلد `image_file` در Create/Update |
|
|
||||||
| `sitepage.proto` | اضافه شدن `SitePageImageFileModel` + فیلد `image_file` در Update/CreateSection/UpdateSection |
|
|
||||||
|
|
||||||
### ۱۰.۵ — تغییرات CQRS (بکاند CMS) ✅
|
|
||||||
|
|
||||||
- **Commands:** اضافه شدن `ImageFileBytes`, `ImageMime`, `ImageFileName` به:
|
|
||||||
- `CreateBlogPostCommand`, `UpdateBlogPostCommand`
|
|
||||||
- `UpdateSitePageCommand`, `CreateSitePageSectionCommand`, `UpdateSitePageSectionCommand`
|
|
||||||
- **Handlers:** تزریق `IFileManager` + فراخوانی `UploadImageAsync`:
|
|
||||||
- BlogPost → `Images/BlogPosts/`
|
|
||||||
- SitePage → `Images/SitePages/`
|
|
||||||
- SitePageSection → `Images/SitePageSections/`
|
|
||||||
|
|
||||||
### ۱۰.۶ — تغییرات سرویسهای BackOffice ✅
|
|
||||||
|
|
||||||
- `BlogPostService.cs` — ارسال `BlogImageFileModel` با `ByteString.CopyFrom(dto.ImageFile)`
|
|
||||||
- `SitePageService.cs` — ارسال `SitePageImageFileModel` در Update/CreateSection/UpdateSection
|
|
||||||
- **DTOs:** اضافه شدن `ImageFile`, `ImageMime`, `ImageFileName` به `BlogPostEditDto`, `SitePageEditDto`, `SitePageSectionEditDto`
|
|
||||||
|
|
||||||
### خلاصه آمار فاز ۱۰
|
|
||||||
| معیار | تعداد |
|
|
||||||
|---|---|
|
|
||||||
| فایلهای proto تغییریافته | 2 |
|
|
||||||
| Commandهای بروزشده | 5 |
|
|
||||||
| Handlerهای بروزشده | 5 |
|
|
||||||
| سرویسهای gRPC تغییریافته | 2 (BlogPostService + SitePageService) |
|
|
||||||
| دیالوگهای UI بازنویسیشده | 3 (BlogPost, SitePage, SitePageSection) |
|
|
||||||
| سرویسهای BackOffice بروزشده | 2 |
|
|
||||||
| DTOهای بروزشده | 3 |
|
|
||||||
| Build | 0 Error ✅ (هر ۳ پروژه) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏷 فاز ۹ — معماری مدیریت فایل + رفع سفارشات + مهاجرت DB (بهمن ۱۴۰۴)
|
|
||||||
|
|
||||||
> **مستند کامل معماری فایل:** [`totalDoc/cms/FILE-MANAGEMENT-ARCHITECTURE.md`](../../totalDoc/cms/FILE-MANAGEMENT-ARCHITECTURE.md)
|
|
||||||
|
|
||||||
### ۹.۱ — حذف کد مرده FMS ✅
|
|
||||||
- حذف ۳ فایل غیرقابل استفاده مرتبط با FMS خارجی (`https://dl.afrino.co`):
|
|
||||||
|
|
||||||
| فایل حذفشده | شرح |
|
|
||||||
|-------------|------|
|
|
||||||
| `Infrastructure/Services/FmsFileManager.cs` | پیادهسازی HTTP upload به FMS |
|
|
||||||
| `Application/Common/FileManager/FileManagementService.cs` | سرویس قدیمی مدیریت فایل |
|
|
||||||
| `Application/Common/FileManager/IFileManagementService.cs` | اینترفیس قدیمی |
|
|
||||||
|
|
||||||
### ۹.۲ — رفع صفحه سفارشات (Orders) ✅
|
|
||||||
|
|
||||||
**مشکل ۱ — صفحه خالی (بدون پیغام خطا)**
|
|
||||||
- **علت:** `ServerReload` بدون try-catch — exceptionهای gRPC ساکت میشدند
|
|
||||||
- **رفع:** اضافه شدن try-catch + نمایش خطا با Snackbar
|
|
||||||
- **فایل:** `Pages/UserOrder/UserOrderMainPage.razor.cs`
|
|
||||||
|
|
||||||
**مشکل ۲ — Shadow FK تکراری در OrderVAT**
|
|
||||||
- **علت:** `.WithOne()` بدون inverse navigation → EF Core یک shadow FK اضافی میساخت
|
|
||||||
- **رفع:** `.WithOne()` → `.WithOne(x => x.OrderVAT)`
|
|
||||||
- **فایل:** `Persistence/Configurations/OrderVATConfiguration.cs`
|
|
||||||
|
|
||||||
**مشکل ۳ — فیلترهای oneof همیشه فعال**
|
|
||||||
- **علت:** فیلدهای proto `oneof` مقدار پیشفرض enum دارند (هیچوقت null نیستند)، پس `PaymentStatus != null` همیشه true بود
|
|
||||||
- **رفع:** `PaymentStatus != null` → `HasPaymentStatus == true` (و مشابه برای DeliveryStatus و PaymentMethod)
|
|
||||||
- **فایلها:** `UserOrderService.cs` — هر دو متد `GetAllUserOrderByFilter` و `GetCustomerOrders`
|
|
||||||
|
|
||||||
### ۹.۳ — مهاجرت DB: حذف محدودیت طول ستونهای تصویر ✅
|
|
||||||
|
|
||||||
- **علت:** data URIهای base64 بزرگتر از `nvarchar(500)` بودند → truncation خاموش
|
|
||||||
- **رفع:** حذف `HasMaxLength(500)` از تمام ستونهای image path
|
|
||||||
- **Migration:** `RemoveImagePathMaxLength`
|
|
||||||
|
|
||||||
| Entity | ستونها |
|
|
||||||
|--------|---------|
|
|
||||||
| Product | `ImagePath`, `ImageThumbnailPath` |
|
|
||||||
| DiscountProduct | `ImagePath`, `ImageThumbnailPath` |
|
|
||||||
| Category | `ImagePath` |
|
|
||||||
| DiscountCategory | `ImagePath` |
|
|
||||||
| BlogPost | `FeaturedImagePath`, `FeaturedImageThumbnailPath` |
|
|
||||||
| SitePage | `HeroImagePath` |
|
|
||||||
| SitePageSection | `ImagePath`, `ImageThumbnailPath` |
|
|
||||||
|
|
||||||
### ۹.۴ — بازنویسی کامل مدیریت فایل: ذخیره دیسکی ✅
|
|
||||||
|
|
||||||
**معماری قبلی:** ذخیره data URI مستقیم در DB (ناکارآمد + حجم بالا)
|
|
||||||
|
|
||||||
**معماری جدید:**
|
|
||||||
1. فایلها در دیسک ذخیره میشوند: `Uploads/Images/{folder}/{guid}.jpg`
|
|
||||||
2. مسیر نسبی در DB ذخیره میشود
|
|
||||||
3. `ImagePathResolverInterceptor` هنگام serve تبدیل به base64 میکند
|
|
||||||
|
|
||||||
**فایلهای اصلی:**
|
|
||||||
|
|
||||||
| فایل | شرح |
|
|
||||||
|------|------|
|
|
||||||
| `Application/Common/FileManager/IFileManager.cs` | اینترفیس: Upload, UploadImage, Delete, ResolveImageUrl |
|
|
||||||
| `Infrastructure/Services/LocalFileManager.cs` | پیادهسازی: ImageSharp, main 1200×1200 + thumb 300×300, JPEG Q75 |
|
|
||||||
| `WebApi/Interceptors/ImagePathResolverInterceptor.cs` | gRPC Interceptor: walk بازگشتی response → تبدیل path به data URI |
|
|
||||||
|
|
||||||
**تنظیمات:**
|
|
||||||
- `FileStorage:UploadPath` → پوشه ریشه آپلود (پیشفرض: `AppContext.BaseDirectory/Uploads`)
|
|
||||||
- gRPC MaxReceiveMessageSize: 50MB
|
|
||||||
|
|
||||||
### ۹.۵ — پاکسازی LoggingBehaviour ✅
|
|
||||||
|
|
||||||
- **قبل:** فیلدهای باینری (byte[] تصاویر) کامل در لاگ چاپ میشدند → لاگهای چند مگابایتی
|
|
||||||
- **بعد:**
|
|
||||||
- فرمت: `JsonFormatter.Default.Format()` به جای `{@Request}`
|
|
||||||
- Regex: حذف فیلدهای باینری (`File`, `ImageFile`, `image_file`, `file`)
|
|
||||||
- محدودیت: حداکثر 2000 کاراکتر
|
|
||||||
- **فایل:** `WebApi/Common/Behaviours/LoggingBehaviour.cs`
|
|
||||||
|
|
||||||
### خلاصه آمار فاز ۹
|
|
||||||
| معیار | تعداد |
|
|
||||||
|---|---|
|
|
||||||
| فایلهای حذفشده (کد مرده) | 3 |
|
|
||||||
| فایلهای جدید | 2 (LocalFileManager بازنویسی + ImagePathResolverInterceptor) |
|
|
||||||
| فایلهای ویرایششده | ~12 |
|
|
||||||
| باگهای رفعشده | 4 (Orders page ×3 + DB truncation) |
|
|
||||||
| Migrationهای جدید | 1 (RemoveImagePathMaxLength) |
|
|
||||||
| ستونهای DB تغییریافته | 9 (nvarchar(500) → nvarchar(max)) |
|
|
||||||
| Build | 0 Error ✅ (هر ۳ پروژه) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏷 فاز ۸ — یکسانسازی فروشگاه عادی و تخفیفی (بهمن ۱۴۰۴)
|
|
||||||
|
|
||||||
> **مستند کامل:** [`totalDoc/ui-modernization/BACKOFFICE-STORE-UNIFICATION.md`](../../totalDoc/ui-modernization/BACKOFFICE-STORE-UNIFICATION.md)
|
|
||||||
|
|
||||||
### ۸.۱ — بازسازی NavMenu ✅
|
|
||||||
- ایجاد دو NavGroup مجزا: «فروشگاه عادی» و «فروشگاه تخفیفی»
|
|
||||||
- حذف «ویرایش دستهجمعی» از منو
|
|
||||||
- انبارداری → گروه مستقل | پکیجها → آیتم مستقل
|
|
||||||
- **فایل:** `Shared/NavMenu.razor`
|
|
||||||
|
|
||||||
### ۸.۲ — حذف گزارشهای کوچک از سفارشات ✅
|
|
||||||
- حذف کارتهای آماری (TotalOrders + TotalAmount) و نمودار Bar ارسال
|
|
||||||
- حذف فیلدها: `_stats`, `_statusChartLabels`, `_statusChartSeries`
|
|
||||||
- حذف متد `UpdateStats()` و کلاس `OrderStatsViewModel`
|
|
||||||
- عنوان: «سفارشهای کاربر» → «لیست سفارشات»
|
|
||||||
- **فایلها:** `Pages/UserOrder/UserOrderMainPage.razor` + `.razor.cs`
|
|
||||||
|
|
||||||
### ۸.۳ — رفع باگ لیست خالی سفارشات ✅
|
|
||||||
- **علت:** `PaymentDate.ToDateTime()` بدون null check → exception ساکت در WASM
|
|
||||||
- **رفع:** `@if (context.Item.PaymentDate != null)` + fallback "-"
|
|
||||||
- **فایل:** `Pages/UserOrder/UserOrderMainPage.razor`
|
|
||||||
|
|
||||||
### ۸.۴ — بازنویسی صفحه محصولات تخفیفی ✅
|
|
||||||
- تبدیل به `BasePageComponent` (مطابق فروشگاه عادی)
|
|
||||||
- فیلترهای جدید: وضعیت + موجودی
|
|
||||||
- ستونهای جدید: تصویر inline + عنوان truncate + چیپ رنگی موجودی + چیپ وضعیت
|
|
||||||
- **فایلها:** `Pages/DiscountShop/DiscountProductsMainPage.razor` + `.razor.cs` (بازنویسی کامل)
|
|
||||||
|
|
||||||
### ۸.۵ — بازنویسی صفحه دستهبندیهای تخفیفی ✅
|
|
||||||
- تبدیل به `BasePageComponent` (مطابق فروشگاه عادی)
|
|
||||||
- ستونهای جدید: نام لاتین، دستهبندی والد (resolve شده)، تعداد محصولات، ترتیب
|
|
||||||
- code-behind جدید (`@code` درونخطی → `.razor.cs`)
|
|
||||||
- **فایلها:** `Pages/DiscountShop/DiscountCategoriesMainPage.razor` + `.razor.cs` (بازنویسی کامل)
|
|
||||||
|
|
||||||
### خلاصه آمار فاز ۸
|
|
||||||
| معیار | تعداد |
|
|
||||||
|---|---|
|
|
||||||
| فایلهای بازنویسیشده | 4 |
|
|
||||||
| فایلهای ویرایششده | 3 |
|
|
||||||
| باگهای رفعشده | 2 (PaymentDate null + لیست خالی) |
|
|
||||||
| Build | 0 Error ✅ |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏷 فاز ۷ — پس از اجرای Audit v7.0 (تیر ۱۴۰۴)
|
|
||||||
|
|
||||||
### خلاصه تغییرات
|
|
||||||
- **۵۷ مورد** از ۵۸ یافته audit اجرا شد (≈98%)
|
|
||||||
- **تمام ۱۶ مورد بحرانی 🔴** حل شد
|
|
||||||
- **تمام ۱۸ مورد جزئی 🟢** حل شد
|
|
||||||
- **فازهای ۱ تا ۶ تکمیل شدند** (بهجز DayaLoan و Audit Trail که نیاز به بکاند دارند)
|
|
||||||
- **فاز ۷** — Global Search, Notification Bell, NavMenu Badge, Export Excel 4 صفحه جدید
|
|
||||||
- **۱۳+ فایل جدید** ساخته شد
|
|
||||||
- **۹ صفحه** ادغام شدند (۱۴ صفحه → ۵ صفحه)
|
|
||||||
- **Build**: 0 error ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## فاز ۷ — Global Search + Notifications + Export All + NavMenu Badge (v7.0)
|
|
||||||
|
|
||||||
### 6.5 — Badge Pending در NavMenu — ✅
|
|
||||||
- **`NavMenu.razor`**: اضافه شدن `MudBadge` روی لینک «درخواستهای برداشت»
|
|
||||||
- نمایش تعداد درخواستهای Pending بصورت real-time از gRPC (`CommissionContract.GetWithdrawalRequestsAsync` با `Status=0`)
|
|
||||||
- fire-and-forget در `OnInitializedAsync` با try/catch silent
|
|
||||||
|
|
||||||
### 6.2 — Global Search در AppBar — ✅
|
|
||||||
- **فایل جدید**: `Shared/GlobalSearch.razor`
|
|
||||||
- `MudAutocomplete<SearchResultItem>` با Debounce 400ms
|
|
||||||
- دو نوع جستجو: ۱) نام صفحات (۱۵ صفحه اصلی) ۲) کاربران از gRPC (`UserContract.GetAllUserByFilterAsync`)
|
|
||||||
- Template سفارشی با آیکون + عنوان + زیرنویس
|
|
||||||
- ناوبری خودکار با `Navigation.NavigateTo`
|
|
||||||
- اضافه شده در `MainLayout.razor` داخل `<AuthorizeView>`
|
|
||||||
- CSS در `app.css` — rounded border-radius 24px, compact padding
|
|
||||||
|
|
||||||
### 6.1 — Notification Bell (placeholder) — ✅
|
|
||||||
- **`MainLayout.razor`**: اضافه شدن `MudMenu` + `MudBadge` (Dot) + آیکون Notifications
|
|
||||||
- Dropdown با هدر «اعلانها» + placeholder «اعلان جدیدی وجود ندارد»
|
|
||||||
- آماده اتصال به بکاند — فقط `Visible=true` و populate items
|
|
||||||
|
|
||||||
### 6.3 — Export Excel صفحات جدید — ✅ (۱۰ صفحه کامل)
|
|
||||||
- **۴ صفحه جدید** اضافه شدند به ۶ صفحه قبلی:
|
|
||||||
1. **UserMainPage** — خروجی CSV: شناسه, موبایل, نام, نام خانوادگی, کدملی
|
|
||||||
2. **DiscountOrdersMainPage** — خروجی CSV: شماره سفارش, تاریخ, مبلغ کل, تخفیف, قابل پرداخت, تعداد, وضعیت, پرداخت
|
|
||||||
3. **ManualPayments** — خروجی CSV: شناسه, شناسه کاربر, نام کاربر, مبلغ, نوع, وضعیت, تاریخ
|
|
||||||
4. **InventoryMainPage** — خروجی CSV: شناسه, محصول, انبار, موجودی, رزرو, قابل فروش, حد هشدار, وضعیت
|
|
||||||
- هر ۴ صفحه: دکمه «خروجی Excel» + `ExportToExcel()` + `EscapeCsv()` + UTF-8 BOM + `jsSaveAsFile`
|
|
||||||
- فایلهای code-behind جدید: `ManualPayments.razor.cs`, `DiscountOrdersMainPage.razor.cs`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## فاز ۶ — تاریخ شمسی + تاریخچهها + Export Excel (v6.0)
|
|
||||||
|
|
||||||
### تاریخ شمسی یکپارچه (3.6) — ✅ کامل
|
|
||||||
- **۱۹+ فایل** آپدیت شدند: تمام تاریخها از فرمت میلادی (`yyyy/MM/dd`) به شمسی تبدیل شدند
|
|
||||||
- از extension methodهای `DateTimeConverterCL` استفاده شد:
|
|
||||||
- `.MiladiToJalali()` — فرمت تاریخ فقط (مثال: ۱۴۰۴/۰۴/۱۲)
|
|
||||||
- `.MiladiToJalaliWithTime()` — فرمت تاریخ و ساعت
|
|
||||||
- **صفحات اصلاحشده شامل**: Club (Members, Statistics, Features, MemberDetailsDialog)، Commission (Dashboard, WeeklyReports, WithdrawalRequests, Payouts, WithdrawalReports)، Network (UserNetworkInfo, BalancesReport, Statistics, NetworkTreeViewer)، UserOrder, Products, Package, Inventory, DiscountShop, Blog, Content, و غیره
|
|
||||||
- `PropertyColumn Format="yyyy/MM/dd"` → `TemplateColumn` با `.MiladiToJalali()`
|
|
||||||
- `MudDatePicker DateFormat` تغییر نکرد (قابلیت تقویم شمسی نیاز به کتابخانه جداگانه دارد)
|
|
||||||
|
|
||||||
### تاریخچه عضویت باشگاه (5.7) — ✅
|
|
||||||
- **`MemberDetailsDialog.razor`**: تب تاریخچه با فیلدهای صحیح proto:
|
|
||||||
- ستونها: `Action`, `Created`, `PackageName`, `Reason`, `PerformedBy`
|
|
||||||
- Request: `PageIndex = 0` (نه PageNumber)
|
|
||||||
|
|
||||||
### تاریخچه تغییرات شبکه (5.8) — ✅
|
|
||||||
- **`UserNetworkInfo.razor`**: تب تاریخچه با فیلدهای تخصصی:
|
|
||||||
- ستونها: `Action`, `Created`, `OldParentId → NewParentId`, `OldNetworkLeg → NewNetworkLeg` (چپ/راست)
|
|
||||||
- ستون `Reason` و `PerformedBy` اضافه شد
|
|
||||||
- Request: `PageIndex = 0`
|
|
||||||
|
|
||||||
### لاگ اجرای Workerها (5.6) — ✅
|
|
||||||
- در تب WorkerControl از SystemHub قبلاً پیادهسازی شده بود
|
|
||||||
|
|
||||||
### Export Excel / CSV (6.3) — ✅ ۶ صفحه
|
|
||||||
قابلیت خروجی CSV (سازگار با Excel) به ۶ صفحه اصلی اضافه شد:
|
|
||||||
|
|
||||||
| صفحه | فایل(ها) | ستونهای خروجی |
|
|
||||||
|---|---|---|
|
|
||||||
| **Products** (قبلاً موجود) | ProductsMainPage.razor.cs | — |
|
|
||||||
| **Club Members** | ClubMembers.razor + .razor.cs | شناسه,کاربر,نام,پکیج,کد فعالسازی,تاریخ فعالسازی,تاریخ انقضا,فعال,منقضی |
|
|
||||||
| **Withdrawal Requests** | WithdrawalRequests.razor + .razor.cs | شناسه,کاربر,نام,مبلغ,وضعیت,روش,شبا,تاریخ درخواست,تاریخ پردازش,مرجع بانکی |
|
|
||||||
| **Weekly Reports** | WeeklyReports.razor + .razor.cs | هفته,مبلغ استخر,موجودیها,ارزش,وضعیت,تاریخ محاسبه |
|
|
||||||
| **User Orders** | UserOrderMainPage.razor + .razor.cs | شناسه,کاربر,نام,مبلغ,وضعیت پرداخت,وضعیت ارسال,روش پرداخت,تاریخ |
|
|
||||||
| **Stock Movements** | MovementsPage.razor + .razor.cs | شناسه,محصول,نوع,تعداد,قبل,بعد,مرجع,یادداشت,تاریخ |
|
|
||||||
|
|
||||||
**الگوی پیادهسازی:**
|
|
||||||
- دکمه `<MudButton>` با آیکون `FileDownload` در ToolBarContent
|
|
||||||
- `StringBuilder` → CSV با هدرهای فارسی
|
|
||||||
- UTF-8 BOM (`Encoding.UTF8.GetPreamble()`) برای نمایش صحیح فارسی در Excel
|
|
||||||
- `jsSaveAsFile(filename, base64)` از `wwwroot/js/main.js`
|
|
||||||
- متد `EscapeCsv()` برای مقادیر حاوی کاما/کوتیشن
|
|
||||||
- تاریخها در خروجی نیز شمسی هستند
|
|
||||||
|
|
||||||
### فایلهای جدید (این نسخه)
|
|
||||||
|
|
||||||
| فایل | عملکرد |
|
|
||||||
|---|---|
|
|
||||||
| `Pages/Commission/WeeklyReports.razor.cs` | Code-behind برای export (بهدلیل محدودیت Razor parser با escaped quotes) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## نسخه قبلی — Audit v5.0 (خرداد ۱۴۰۴)
|
|
||||||
|
|
||||||
### تم رنگی یکپارچه
|
|
||||||
- **`CustomMudTheme.cs`**: تم Primary از `#0380C0` به `#6366f1` (Indigo) تغییر کرد — هماهنگ با FrontOffice
|
|
||||||
- **`PaletteDark`** اضافه شد: Surface `#1a1a2e`, Background `#16213e`, AppbarBackground `#0f3460`
|
|
||||||
- **`app.css`**: رنگ loading از `#1b6ec2` به `var(--mud-palette-primary)` تغییر کرد
|
|
||||||
- **`NavMenu.razor.css`**: رنگ active از `#0380C0` هاردکد به CSS variable تغییر کرد
|
|
||||||
- **`MainLayout.razor`**: حذف inline style `border: 1px solid #5e4df9`
|
|
||||||
|
|
||||||
### Dark Mode Toggle
|
|
||||||
- آیکون ماه/آفتاب در AppBar اضافه شد
|
|
||||||
- `_isDarkMode` state با `MudThemeProvider` اتصال دارد
|
|
||||||
- تعویض بین `PaletteLight` و `PaletteDark` در CustomMudTheme
|
|
||||||
|
|
||||||
### عنوان اپلیکیشن
|
|
||||||
- "پنل مدیریت **فرصت**" → "پنل مدیریت **کارا بازار سلامت**"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## فاز ۲ — بازطراحی لایوت صفحات
|
|
||||||
|
|
||||||
### BasePageComponent بازنویسی کامل
|
|
||||||
- **قبل**: فیلتر ثابت سمت راست (20%) + جدول (80%)، ارتفاع 85vh هاردکد
|
|
||||||
- **بعد**: فیلتر collapsible بالای جدول، فیلدها در `MudGrid` با `MudItem`، responsive
|
|
||||||
- **۱۳ صفحه** آپدیت شدند: User, Products, Package, Category, UserOrder, UserAddress, UserRole, Role, BlogPost, BlogCategory, Tag, Transactions, Content
|
|
||||||
|
|
||||||
### DataGrid Height یکسانسازی
|
|
||||||
- همه DataGridها: `Height="calc(100vh - 240px)"`
|
|
||||||
- **صفحات اصلاحشده**: تمام ~33 صفحه دارای DataGrid
|
|
||||||
|
|
||||||
### Pager یکسانسازی
|
|
||||||
- **PageSize**: همه `20, 50, 100`
|
|
||||||
- **متن فارسی**: `RowsPerPageString="تعداد در صفحه"` و `InfoFormat="سطر {first_item} تا {last_item} از {all_items}"`
|
|
||||||
|
|
||||||
### MaxWidth یکسانسازی
|
|
||||||
- `MainLayout.razor` MudContainer wrapper اضافه شد → MaxWidth یکسان برای همه صفحات
|
|
||||||
- **۳۱ صفحه**: حذف `MudContainer` تکراری (double nesting)
|
|
||||||
|
|
||||||
### Breadcrumb
|
|
||||||
- **`Shared/AppBreadcrumb.razor`** ساخته شد
|
|
||||||
- جداکننده چپبهراست (`ChevronLeft`)
|
|
||||||
- نقشه فارسی URL segments (60+ مسیر)
|
|
||||||
- آخرین آیتم disabled (صفحه فعلی)
|
|
||||||
|
|
||||||
### Login + VerifyCode بازطراحی
|
|
||||||
- عرض ثابت 35% → responsive `MaxWidth.Small`
|
|
||||||
- لوگو + برندینگ "کارا بازار سلامت" اضافه شد
|
|
||||||
- طراحی gradient background
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## فاز ۲.۵ — پاکسازی ساختاری
|
|
||||||
|
|
||||||
### NavMenu بازسازی کامل
|
|
||||||
- **لینک مرده `categories-dragdrop`**: حذف شد
|
|
||||||
- **Transactions**: حذف از NavMenu (صفحه stub خالی)
|
|
||||||
- **AlertsMonitoring**: حذف از NavMenu (100% mock)
|
|
||||||
- **WorkerControl**: به SystemHub منتقل شد (قبلاً لینک نداشت)
|
|
||||||
- **«پیامهای عمومی»**: از بخش فروشگاه تخفیفی به «مدیریت محتوا» منتقل شد
|
|
||||||
- **ساختار گروهبندی** اصلاح شد:
|
|
||||||
- کمیسیون و شبکه: NavGroup واضح
|
|
||||||
- باشگاه مشتریان: اعضا و آمار + فیچرها
|
|
||||||
- فروشگاه: پکیجها، محصولات (group)، سفارشها، فروشگاه تخفیفی (group)
|
|
||||||
- مدیریت: کاربران، نقشها، پرداخت دستی، **کیفپول (جدید)**، **قراردادها (جدید)**
|
|
||||||
- محتوا: بلاگ، صفحات سایت، پیامهای عمومی
|
|
||||||
- سیستم: مدیریت سیستم، نسخه اپها
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## فاز ۳ — UX بهبودها
|
|
||||||
|
|
||||||
### NoRecordsContent (Empty State)
|
|
||||||
- **۳۳ DataGrid** آپدیت شدند
|
|
||||||
- پیام: `<MudAlert Severity="Info">موردی یافت نشد.</MudAlert>`
|
|
||||||
|
|
||||||
### Delete Confirmation با نام آیتم
|
|
||||||
- **۱۱ صفحه** اصلاح شدند:
|
|
||||||
- Role → `«{model.Title}»`
|
|
||||||
- User → `«{model.FirstName} {model.LastName}»`
|
|
||||||
- Package → `«{model.Title}»`
|
|
||||||
- UserAddress → `«{model.Title}»`
|
|
||||||
- UserRole → `شناسه «{model.Id}»`
|
|
||||||
- UserOrder → `شماره «{model.Id}»`
|
|
||||||
- Category → `«{model.Title}»`
|
|
||||||
- Products → `«{model.Title}»`
|
|
||||||
- DiscountCategories → `«{category?.Title}»` (lookup اضافه شد)
|
|
||||||
- DiscountProducts → `«{product?.Title}»` (lookup اضافه شد)
|
|
||||||
- PublicMessages → `«{message?.Title}»` (lookup اضافه شد)
|
|
||||||
|
|
||||||
### User Menu در AppBar
|
|
||||||
- `MudMenu` جایگزین متن ساده شماره موبایل شد
|
|
||||||
- آیتمها: نام کاربر (غیرفعال) + خروج
|
|
||||||
- آیکون `AccountCircle`
|
|
||||||
- خروج (`signout`) از NavMenu به AppBar منتقل شد
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## فاز ۳.۵ — ادغام صفحات
|
|
||||||
|
|
||||||
### Dashboard Merge ← `Pages/Index.razor`
|
|
||||||
- **`Index.razor` + `SystemOverview.razor`** ادغام شدند
|
|
||||||
- Routes: `/` + `/dashboard/overview`
|
|
||||||
- بخشها:
|
|
||||||
- 6 کارت آمار CMS (کاربران، محصولات، پکیجها، دستهبندیها، سفارشها، مبلغ پرداختشده)
|
|
||||||
- آمار کمیسیون (هفته جاری، استخر، موجودیها، وضعیت محاسبه)
|
|
||||||
- آمار باشگاه (کل/فعال/غیرفعال)
|
|
||||||
- جدول آخرین ۵ سفارش پرداختشده
|
|
||||||
- ۶ دکمه Quick Action
|
|
||||||
- بارگذاری موازی با `Task.WhenAll` + `.ResponseAsync`
|
|
||||||
- `SystemOverview.razor` **حذف شد**
|
|
||||||
|
|
||||||
### DiscountShopHub ← `Pages/DiscountShop/DiscountShopHub.razor`
|
|
||||||
- Routes: `/discount-shop`, `/discount-orders`, `/discount-sales-reports`
|
|
||||||
- ۲ تب: DiscountOrdersMainPage + SalesReports
|
|
||||||
- انتخاب تب از URL
|
|
||||||
|
|
||||||
### ClubHub ← `Pages/Club/ClubHub.razor`
|
|
||||||
- Routes: `/club`, `/club/members`, `/club/statistics`
|
|
||||||
- ۲ تب: ClubMembers + Statistics
|
|
||||||
|
|
||||||
### BlogHub ← `Pages/Blog/BlogHub.razor`
|
|
||||||
- Routes: `/blog`, `/blog/posts`, `/blog/categories`, `/tags`
|
|
||||||
- ۳ تب: BlogPostManagementPage + BlogCategoryManagementPage + TagManagementPage
|
|
||||||
|
|
||||||
### SystemHub ← `Pages/SystemManagement/SystemHub.razor`
|
|
||||||
- Routes: `/system`, `/system/configuration`, `/system/worker-control`, `/system/health`
|
|
||||||
- ۳ تب: Configuration + WorkerControl + HealthDashboard
|
|
||||||
|
|
||||||
### ۱۰ صفحه — `@page` directive حذف شد
|
|
||||||
صفحاتی که به hub منتقل شدند دیگر route مستقل ندارند:
|
|
||||||
DiscountOrdersMainPage, SalesReports, ClubMembers, Statistics, BlogPostManagementPage, BlogCategoryManagementPage, TagManagementPage, Configuration, WorkerControl, HealthDashboard
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## فاز ۴ — فیکسهای بیزینسی
|
|
||||||
|
|
||||||
### Dashboard آمار اصلاح شد
|
|
||||||
- **قبل**: آمار از ۱۰ رکورد اول محاسبه میشد (نادرست)
|
|
||||||
- **بعد**: از `metadata.TotalCount` و APIهای جداگانه استفاده میشود
|
|
||||||
|
|
||||||
### ManualPayments — تأیید/رد
|
|
||||||
- دکمههای Approve/Reject وقتی `Status==0` (در انتظار) نمایش داده میشوند
|
|
||||||
- Confirmation dialog قبل از اجرا
|
|
||||||
- از `ManualPaymentId` (نه `Id`) استفاده میشود
|
|
||||||
|
|
||||||
### UserOrder — فیلتر Cancelled
|
|
||||||
- `<MudSelectItem Value="@(5)">لغو شده</MudSelectItem>` اضافه شد
|
|
||||||
|
|
||||||
### WithdrawalRequests — بهبود کامل
|
|
||||||
- **Pager**: 10/25/50/100 → 20/50/100
|
|
||||||
- **`WithdrawalDetailsDialog.razor`** (جدید): نمایش کامل اطلاعات کاربر، درخواست، و بانکی
|
|
||||||
- **`RejectReasonDialog.razor`** (جدید): ورود دلیل رد با اعتبارسنجی
|
|
||||||
- ViewDetails: از Snackbar TODO به دیالوگ واقعی تغییر کرد
|
|
||||||
- RejectRequest: از دلیل هاردکد به دیالوگ custom تغییر کرد
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## فاز ۵ — صفحات مفقود (CMS-Derived)
|
|
||||||
|
|
||||||
### WalletManagementPage ← `Pages/Wallet/WalletManagementPage.razor`
|
|
||||||
- Route: `/wallets`
|
|
||||||
- **تب ۱ — کیفپولها**: DataGrid با ServerData، فیلتر UserAutoComplete
|
|
||||||
- ستونها: شناسه، کاربر، موجودی اصلی، موجودی شبکه، مشاهده تغییرات
|
|
||||||
- نوع: `GetAllUserWalletByFilterResponseModel`
|
|
||||||
- **تب ۲ — تاریخچه تغییرات**: DataGrid با ServerData، فیلتر کاربر + نوع (افزایش/کاهش)
|
|
||||||
- ستونها: شناسه، کیفپول، نوع (چیپ رنگی)، تغییر موجودی، موجودی فعلی، تغییر شبکه، موجودی شبکه فعلی، شناسه مرجع، تاریخ
|
|
||||||
- نوع: `GetAllUserWalletChangeLogByFilterResponseModel`
|
|
||||||
- فیلدهای proto با typo: `ChangeNerworkValue`, `RefrenceId`
|
|
||||||
|
|
||||||
### UserContractPage ← `Pages/Contract/UserContractPage.razor`
|
|
||||||
- Route: `/contracts`
|
|
||||||
- DataGrid با ServerData، فیلتر UserAutoComplete
|
|
||||||
- ستونها: شناسه، کاربر، قرارداد، کد امضا، لینک PDF، جزئیات
|
|
||||||
- نوع: `GetAllUserContractByFilterResponseModel`
|
|
||||||
|
|
||||||
### ContractDetailsDialog ← `Pages/Contract/ContractDetailsDialog.razor`
|
|
||||||
- جدول ساده: شناسه، کاربر، قالب قرارداد، کد امضا، لینک دانلود PDF
|
|
||||||
|
|
||||||
### ClubFeaturesPage ← `Pages/Club/ClubFeaturesPage.razor`
|
|
||||||
- Route: `/club/features`
|
|
||||||
- **تب ۱ — فیچرهای تعریفشده**: کارتهای MudPaper با عنوان، توضیحات، وضعیت (فعال/غیرفعال)، ترتیب
|
|
||||||
- از `ConfigClient.GetClubFeaturesAsync`
|
|
||||||
- **تب ۲ — فیچرهای کاربران**: DataGrid با فیلتر کاربر، سوئیچ فعال/غیرفعال
|
|
||||||
- از `ClubClient.GetUserClubFeaturesAsync` + `ToggleUserClubFeatureAsync`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠️ نکات فنی مهم
|
|
||||||
|
|
||||||
### الگوهای Proto
|
|
||||||
- **فیلتر**: `new GetAllXxxByFilterFilter()` (سطح بالا، بدون `.Types.`)
|
|
||||||
- **پاسخ لیست**: `GetAllXxxByFilterResponseModel` (نه `GetXxxResponse`)
|
|
||||||
- **Typoها در proto**: `ChangeNerworkValue` و `RefrenceId` — باید دقیقاً همینها استفاده شوند
|
|
||||||
|
|
||||||
### _Imports.razor — سرویسهای Global
|
|
||||||
سرویسهای زیر از `_Imports.razor` تزریق میشوند و نباید در code-behind با `[Inject]` دوباره تعریف شوند:
|
|
||||||
- `NavigationManager Navigation`
|
|
||||||
- `ILocalStorageService LocalStorageService`
|
|
||||||
- `ISnackbar Snackbar`
|
|
||||||
- `IJSRuntime jsRuntime`
|
|
||||||
- `IDialogService DialogService`
|
|
||||||
- `AuthenticationStateProvider`
|
|
||||||
|
|
||||||
### MudBlazor محدودیتها
|
|
||||||
- `Variant.Flat` وجود ندارد → از `Variant.Text` استفاده شود
|
|
||||||
- `GutterSize` روی `MudGrid` deprecated است
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## فاز ۴ (ادامه) — تکمیل فیکسهای بیزینسی
|
|
||||||
|
|
||||||
### UserSettings — حذف تبهای جعلی (2.5.5)
|
|
||||||
- **`UserSettings.razor`**: تب Notifications (۷ تاگل جعلی بدون بکاند) → placeholder «بهزودی فعال میشود» با آیکون NotificationsActive
|
|
||||||
- **`UserSettings.razor`**: تب Security (فرم تغییر رمز + 2FA بدون بکاند) → placeholder «بهزودی فعال میشود» با آیکون Lock
|
|
||||||
- **حذف کد مرده**: ~۱۵ متغیر notification/security + متدهای `SaveNotificationSettings()`, `SaveSecuritySettings()`, `ChangePassword()`
|
|
||||||
|
|
||||||
### HealthDashboard — حذف متریکهای Mock (2.5.6)
|
|
||||||
- **`HealthDashboard.razor`**: بخش System Resources (۴ کارت CPU=45.2%, Memory=62.8%, Disk=73.5%, Network) → یک کارت با پیام «مانیتورینگ منابع سیستم در نسخههای آینده با اتصال به Prometheus/Grafana فعال خواهد شد»
|
|
||||||
- **حذف کد مرده**: ۹ متغیر mock (`_cpuUsage`, `_memoryUsage`, etc.) + متد `GetResourceColor()`
|
|
||||||
|
|
||||||
### حذف cursor:pointer زائد (4.10)
|
|
||||||
- `Style="cursor:pointer;"` از **۱۷ فایل** حذف شد (MudButton/MudIconButton خودشان cursor:pointer دارند)
|
|
||||||
- شامل: ProductsMainPage, GalleryDialog, Image.razor (base component), RoleMainPage, UserOrderMainPage, PackageMainPage, etc.
|
|
||||||
|
|
||||||
### حذف Console.WriteLine (4.11)
|
|
||||||
- از **۱۱ فایل** حذف شد: DiscountProductService, DiscountCategoryService, NetworkTreeViewer, UserSettings, CategoryMainPage, UserOrderMainPage, Commission/Dashboard, DiscountProductsMainPage, DiscountCategoryMultiSelectCombo, UserRole/CreateDialog
|
|
||||||
- **`Commission/Dashboard.razor.cs`**: `using System.Text.Json` هم حذف شد (دیگر استفاده نمیشد)
|
|
||||||
|
|
||||||
### DiscountProducts — فیلتر دستهبندی (4.7)
|
|
||||||
- **`DiscountProductsMainPage.razor`**: `@inject IDiscountCategoryService` اضافه شد
|
|
||||||
- دستهبندیهای فعال از سرویس بارگذاری و در MudSelect نمایش داده میشوند
|
|
||||||
- جایگزینی `@* TODO: Load categories *@` با `@foreach` واقعی
|
|
||||||
|
|
||||||
### DiscountProducts — Items → ServerData (4.8)
|
|
||||||
- **`DiscountProductsMainPage.razor`**: تبدیل از `Items="@_products"` به `ServerData="LoadServerData"`
|
|
||||||
- متد `LoadServerData(GridState<T>)` اضافه شد با `PageNumber` و `PageSize` از state
|
|
||||||
- `Height="calc(100vh - 300px)"` + `FixedHeader="true"` اضافه شد
|
|
||||||
- Snackbar اطلاعرسانی اضافی در هر بارگذاری حذف شد (redundant)
|
|
||||||
|
|
||||||
### UserOrder — فعالسازی آمار (4.6)
|
|
||||||
- **`UserOrderMainPage.razor`**: بخش آمار (MudCard + MudChart) از حالت comment خارج شد
|
|
||||||
- نمایش تعداد سفارشها + مجموع مبلغ + نمودار Bar وضعیت ارسال
|
|
||||||
|
|
||||||
### UserSettings — persist با localStorage (4.9)
|
|
||||||
- تب General از قبل با localStorage کار میکرد ✅ (فقط تبهای جعلی حذف شدند)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## فاز ۴.۵ — Cross-Navigation
|
|
||||||
|
|
||||||
### لینک نام کاربر → پروفایل (4.5.1, 4.5.2)
|
|
||||||
- **`UserOrderMainPage.razor`**: ستون `UserFullName` از PropertyColumn به TemplateColumn تبدیل شد — MudLink به `/network/user-info/{UserId}`
|
|
||||||
- **`UserPayouts.razor`**: نام/شناسه کاربر به MudLink تبدیل شد → `/network/user-info/{UserId}`
|
|
||||||
- **`WithdrawalRequests.razor`**: نام کاربر به MudLink تبدیل شد → `/network/user-info/{UserId}`
|
|
||||||
|
|
||||||
### لینک محصول ↔ انبار (4.5.3, 4.5.4) — Skip
|
|
||||||
- صفحه جزئیات محصول وجود ندارد (فقط لیست + دیالوگ ویرایش)
|
|
||||||
- لینک cross-navigation بدون صفحه مقصد معنادار نیست
|
|
||||||
|
|
||||||
### Extract shared status helpers (3.5.6) — Skip
|
|
||||||
- بررسی نشان داد هر context مقادیر متفاوتی دارد:
|
|
||||||
- UserPayouts: status 1 = Success/پرداخت شده
|
|
||||||
- WithdrawalRequests: status 1 = Info/واریز شده
|
|
||||||
- PayoutDetailsDialog: فقط ۳ وضعیت (نه ۶)
|
|
||||||
- استخراج به helper مشترک باعث از دست رفتن معنای domain-specific میشود
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📈 آمار تغییرات
|
|
||||||
|
|
||||||
| معیار | تعداد |
|
|
||||||
|---|---|
|
|
||||||
| فایلهای جدید | 11 |
|
|
||||||
| فایلهای حذفشده | 1 (SystemOverview.razor) |
|
|
||||||
| فایلهای ویرایششده | ~65+ |
|
|
||||||
| DataGridهای آپدیتشده | 33 |
|
|
||||||
| Delete confirmation اصلاحشده | 11 |
|
|
||||||
| صفحات ادغامشده | 14 → 5 |
|
|
||||||
| خطاهای build حلشده | 9+ |
|
|
||||||
| Console.WriteLine حذفشده | 22+ (از ۱۱ فایل) |
|
|
||||||
| cursor:pointer حذفشده | 17 فایل |
|
|
||||||
| Cross-link اضافهشده | 3 صفحه (UserOrder, UserPayouts, WithdrawalRequests) |
|
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
# BackOffice UI Deployment (Blazor WASM + nginx)
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: backoffice
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
app: backoffice
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: backoffice
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: backoffice
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: backoffice
|
||||||
|
image: 194.5.195.53:30080/admin/backoffice:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
env:
|
||||||
|
- name: BLAZOR_ENVIRONMENT
|
||||||
|
value: "Staging"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: gitea-registry-secret
|
||||||
|
|
||||||
|
---
|
||||||
|
# BackOffice UI Service
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: backoffice-svc
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: backoffice
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
type: ClusterIP
|
||||||
|
|
||||||
|
---
|
||||||
|
# BackOffice UI Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: backoffice-ingress
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||||
|
nginx.ingress.kubernetes.io/service-upstream: "true"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- backoffice.se.kbs1.ir
|
||||||
|
secretName: backoffice-tls
|
||||||
|
rules:
|
||||||
|
- host: backoffice.se.kbs1.ir
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: backoffice-svc
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
|
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<!-- Suppress pre-existing warnings that do not affect runtime correctness -->
|
||||||
|
<NoWarn>MUD0002;CA2022;BL0005</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -136,9 +138,12 @@
|
|||||||
<!-- This enables direct communication with CMS (no BFF needed) -->
|
<!-- This enables direct communication with CMS (no BFF needed) -->
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
|
|
||||||
<!-- CMS Protobuf NuGet package (used for both local dev and Docker builds) -->
|
<!-- CMS Protobuf: NuGet in CI/Docker; ProjectReference for local monorepo dev -->
|
||||||
<ItemGroup>
|
<ItemGroup Condition="Exists('../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj')">
|
||||||
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.178" />
|
<ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="!Exists('../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj')">
|
||||||
|
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.208" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- ============================================================ -->
|
<!-- ============================================================ -->
|
||||||
|
|||||||
@@ -8,21 +8,46 @@ namespace BackOffice.Common.BaseComponents;
|
|||||||
public partial class DateRangePicker
|
public partial class DateRangePicker
|
||||||
{
|
{
|
||||||
private DateRange _dateRange = new();
|
private DateRange _dateRange = new();
|
||||||
|
private MudDateRangePicker _picker = default!;
|
||||||
|
private DateTime? _lastSyncedFrom;
|
||||||
|
private DateTime? _lastSyncedTo;
|
||||||
|
|
||||||
private MudDateRangePicker _picker;
|
|
||||||
[Parameter] public string Label { get; set; } = "انتخاب بازه زمانی";
|
[Parameter] public string Label { get; set; } = "انتخاب بازه زمانی";
|
||||||
[Parameter] public DateTime? DefaultStart { get; set; }
|
[Parameter] public DateTime? DefaultStart { get; set; }
|
||||||
[Parameter] public DateTime? DefaultEnd { get; set; }
|
[Parameter] public DateTime? DefaultEnd { get; set; }
|
||||||
[Parameter] public EventCallback<DateRange> OnChanged { get; set; }
|
[Parameter] public EventCallback<DateRange> OnChanged { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public DateTime? From { get; set; }
|
||||||
|
[Parameter] public EventCallback<DateTime?> FromChanged { get; set; }
|
||||||
|
[Parameter] public DateTime? To { get; set; }
|
||||||
|
[Parameter] public EventCallback<DateTime?> ToChanged { get; set; }
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
base.OnInitialized();
|
base.OnInitialized();
|
||||||
if (DefaultStart.HasValue)
|
var start = From ?? DefaultStart;
|
||||||
_dateRange.Start = DefaultStart.Value.Date;
|
var end = To ?? DefaultEnd;
|
||||||
|
ApplyExternalRange(start, end);
|
||||||
if (DefaultEnd.HasValue)
|
_lastSyncedFrom = From;
|
||||||
_dateRange.End = DefaultEnd.Value.Date;
|
_lastSyncedTo = To;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
// فقط وقتی والد From/To را عوض کرده (مثلاً پاک کردن فیلتر)، نه هنگام انتخاب داخل پیکر
|
||||||
|
if (From == _lastSyncedFrom && To == _lastSyncedTo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastSyncedFrom = From;
|
||||||
|
_lastSyncedTo = To;
|
||||||
|
ApplyExternalRange(From, To);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyExternalRange(DateTime? start, DateTime? end)
|
||||||
|
{
|
||||||
|
_dateRange = new DateRange(start?.Date, end?.Date);
|
||||||
|
}
|
||||||
|
|
||||||
public CultureInfo GetPersianCulture()
|
public CultureInfo GetPersianCulture()
|
||||||
{
|
{
|
||||||
var culture = new CultureInfo("fa-IR");
|
var culture = new CultureInfo("fa-IR");
|
||||||
@@ -53,19 +78,35 @@ public partial class DateRangePicker
|
|||||||
culture.NumberFormat.NumberNegativePattern = 0;
|
culture.NumberFormat.NumberNegativePattern = 0;
|
||||||
return culture;
|
return culture;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnClickOK()
|
private async Task OnClickOK()
|
||||||
{
|
{
|
||||||
if (_picker.DateRange is not null)
|
var range = _picker.DateRange ?? _dateRange;
|
||||||
{
|
_dateRange = range ?? new DateRange();
|
||||||
await OnChanged.InvokeAsync(_picker.DateRange);
|
await NotifyRangeChangedAsync(_dateRange);
|
||||||
}
|
|
||||||
|
|
||||||
await _picker.CloseAsync();
|
await _picker.CloseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnClickClear()
|
private async Task OnClickClear()
|
||||||
{
|
{
|
||||||
_dateRange = new DateRange();
|
_dateRange = new DateRange();
|
||||||
await OnChanged.InvokeAsync(_dateRange);
|
await NotifyRangeChangedAsync(_dateRange);
|
||||||
await _picker.CloseAsync();
|
await _picker.CloseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task NotifyRangeChangedAsync(DateRange range)
|
||||||
|
{
|
||||||
|
var start = range.Start?.Date;
|
||||||
|
var end = range.End?.Date;
|
||||||
|
|
||||||
|
_lastSyncedFrom = start;
|
||||||
|
_lastSyncedTo = end;
|
||||||
|
|
||||||
|
if (From != start)
|
||||||
|
await FromChanged.InvokeAsync(start);
|
||||||
|
if (To != end)
|
||||||
|
await ToChanged.InvokeAsync(end);
|
||||||
|
|
||||||
|
await OnChanged.InvokeAsync(range);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,15 @@ using CMSMicroservice.Protobuf.Protos.BlogPost;
|
|||||||
using CMSMicroservice.Protobuf.Protos.BlogCategory;
|
using CMSMicroservice.Protobuf.Protos.BlogCategory;
|
||||||
using CMSMicroservice.Protobuf.Protos.BlogPostImage;
|
using CMSMicroservice.Protobuf.Protos.BlogPostImage;
|
||||||
using CMSMicroservice.Protobuf.Protos.SitePage;
|
using CMSMicroservice.Protobuf.Protos.SitePage;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.SitePageSettings;
|
||||||
using CMSMicroservice.Protobuf.Protos.UserContract;
|
using CMSMicroservice.Protobuf.Protos.UserContract;
|
||||||
using CMSMicroservice.Protobuf.Protos.UserWallet;
|
using CMSMicroservice.Protobuf.Protos.UserWallet;
|
||||||
using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog;
|
using CMSMicroservice.Protobuf.Protos.UserWalletHistory;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.PaymentTransaction;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.Transactions;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.UserPackagePurchase;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.ClubMembershipCycle;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.ClubMembershipCycleHistory;
|
||||||
using BackOffice.Common.Utilities;
|
using BackOffice.Common.Utilities;
|
||||||
using BackOffice.Services.DiscountProduct;
|
using BackOffice.Services.DiscountProduct;
|
||||||
using BackOffice.Services.DiscountCategory;
|
using BackOffice.Services.DiscountCategory;
|
||||||
@@ -91,6 +97,7 @@ public static class ConfigureServices
|
|||||||
services.AddScoped<IBlogCategoryService, BlogCategoryService>();
|
services.AddScoped<IBlogCategoryService, BlogCategoryService>();
|
||||||
services.AddScoped<IBlogPostImageService, BlogPostImageService>();
|
services.AddScoped<IBlogPostImageService, BlogPostImageService>();
|
||||||
services.AddScoped<ISitePageService, SitePageService>();
|
services.AddScoped<ISitePageService, SitePageService>();
|
||||||
|
services.AddScoped<ISitePageSettingsService, SitePageSettingsService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
@@ -158,13 +165,21 @@ public static class ConfigureServices
|
|||||||
services.AddTransient(sp => new BlogCategoryContract.BlogCategoryContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new BlogCategoryContract.BlogCategoryContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
services.AddTransient(sp => new BlogPostImageContract.BlogPostImageContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new BlogPostImageContract.BlogPostImageContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
services.AddTransient(sp => new SitePageContract.SitePageContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new SitePageContract.SitePageContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
services.AddTransient(sp => new SitePageSettingsContract.SitePageSettingsContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
|
||||||
// User Contracts Service
|
// User Contracts Service
|
||||||
services.AddTransient(sp => new UserContractContract.UserContractContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new UserContractContract.UserContractContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
|
||||||
// User Wallet Services
|
// User Wallet Services
|
||||||
services.AddTransient(sp => new UserWalletContract.UserWalletContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new UserWalletContract.UserWalletContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
services.AddTransient(sp => new UserWalletChangeLogContract.UserWalletChangeLogContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new UserWalletHistoryContract.UserWalletHistoryContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
|
||||||
|
// Admin Report Services
|
||||||
|
services.AddTransient(sp => new PaymentTransactionContract.PaymentTransactionContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
services.AddTransient(sp => new TransactionsContract.TransactionsContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
services.AddTransient(sp => new UserPackagePurchaseContract.UserPackagePurchaseContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
services.AddTransient(sp => new ClubMembershipCycleContract.ClubMembershipCycleContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
services.AddTransient(sp => new ClubMembershipCycleHistoryContract.ClubMembershipCycleHistoryContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using BackOffice.Common.Utilities;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using BackOffice.Common.Utilities;
|
||||||
using Blazored.LocalStorage;
|
using Blazored.LocalStorage;
|
||||||
|
|
||||||
namespace BackOffice.Common.Utilities;
|
namespace BackOffice.Common.Utilities;
|
||||||
@@ -19,9 +20,41 @@ public class AppTokenProvider : ITokenProvider
|
|||||||
{
|
{
|
||||||
var authorizationToken = await _localStorage.GetItemAsync<string>(GlobalConstants.JwtTokenKey);
|
var authorizationToken = await _localStorage.GetItemAsync<string>(GlobalConstants.JwtTokenKey);
|
||||||
if (!string.IsNullOrEmpty(authorizationToken))
|
if (!string.IsNullOrEmpty(authorizationToken))
|
||||||
_token = authorizationToken.ToString().Replace("Bearer ", "");
|
{
|
||||||
|
var raw = authorizationToken.ToString().Replace("Bearer ", "");
|
||||||
|
// Don't send expired tokens — they cause 401 on anonymous endpoints like login
|
||||||
|
if (IsTokenExpired(raw))
|
||||||
|
{
|
||||||
|
await _localStorage.RemoveItemAsync(GlobalConstants.JwtTokenKey);
|
||||||
|
_token = null;
|
||||||
|
return _token;
|
||||||
|
}
|
||||||
|
_token = raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IsTokenExpired(_token))
|
||||||
|
{
|
||||||
|
// Cached token has expired since last check
|
||||||
|
await _localStorage.RemoveItemAsync(GlobalConstants.JwtTokenKey);
|
||||||
|
_token = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _token;
|
return _token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsTokenExpired(string token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var handler = new JwtSecurityTokenHandler();
|
||||||
|
var jwt = handler.ReadJwtToken(token);
|
||||||
|
// Add 30-second buffer to avoid edge-case race
|
||||||
|
return jwt.ValidTo < DateTime.UtcNow.AddSeconds(-30);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Malformed token — treat as expired
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BackOffice.Common.Utilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// خروجی سازگار با Excel فارسی:
|
||||||
|
/// - XML Spreadsheet (.xls) برای ستونبندی مطمئن + متن فارسی
|
||||||
|
/// - CSV با جداکننده ; بهعنوان جایگزین سبک
|
||||||
|
/// </summary>
|
||||||
|
public static class CsvExportHelper
|
||||||
|
{
|
||||||
|
/// <summary>جداکننده پیشفرض ویندوز فارسی برای CSV.</summary>
|
||||||
|
public const char Separator = ';';
|
||||||
|
|
||||||
|
public static string Escape(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// همیشه quote تا Excel فارسی ستون را نشکند
|
||||||
|
return $"\"{value.Replace("\"", "\"\"")}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Cell(object? value) => value switch
|
||||||
|
{
|
||||||
|
null => Escape(string.Empty),
|
||||||
|
string s => Escape(s),
|
||||||
|
bool b => b ? "1" : "0",
|
||||||
|
byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal
|
||||||
|
=> Convert.ToString(value, CultureInfo.InvariantCulture) ?? "0",
|
||||||
|
IFormattable f => Escape(f.ToString(null, CultureInfo.InvariantCulture) ?? string.Empty),
|
||||||
|
_ => Escape(Convert.ToString(value, CultureInfo.InvariantCulture))
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string Row(params object?[] cells) =>
|
||||||
|
string.Join(Separator, cells.Select(Cell));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نام نمایشی مشتری؛ اگر خالی باشد موبایل یا شناسه کاربر.
|
||||||
|
/// </summary>
|
||||||
|
public static string CustomerName(string? fullName, string? mobile = null, long? userId = null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(fullName))
|
||||||
|
return fullName.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(mobile))
|
||||||
|
return mobile.Trim();
|
||||||
|
if (userId is > 0)
|
||||||
|
return $"کاربر {userId}";
|
||||||
|
return "—";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToBase64(StringBuilder body)
|
||||||
|
{
|
||||||
|
// CRLF صریح — Excel روی ویندوز با \n لینوکس گاهی بههم میریزد
|
||||||
|
var text = body.ToString().Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", "\r\n");
|
||||||
|
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(text)).ToArray();
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// خروجی SpreadsheetML که Excel بهصورت ستونستون و با UTF-8 درست باز میکند.
|
||||||
|
/// فایل را با پسوند .xls ذخیره کنید.
|
||||||
|
/// </summary>
|
||||||
|
public static string ToExcelXmlBase64(string sheetName, IReadOnlyList<string> headers, IEnumerable<object?[]> rows)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine(@"<?xml version=""1.0"" encoding=""UTF-8""?>");
|
||||||
|
sb.AppendLine(@"<?mso-application progid=""Excel.Sheet""?>");
|
||||||
|
sb.AppendLine(@"<Workbook xmlns=""urn:schemas-microsoft-com:office:spreadsheet""");
|
||||||
|
sb.AppendLine(@" xmlns:o=""urn:schemas-microsoft-com:office:office""");
|
||||||
|
sb.AppendLine(@" xmlns:x=""urn:schemas-microsoft-com:office:excel""");
|
||||||
|
sb.AppendLine(@" xmlns:ss=""urn:schemas-microsoft-com:office:spreadsheet""");
|
||||||
|
sb.AppendLine(@" xmlns:html=""http://www.w3.org/TR/REC-html40"">");
|
||||||
|
sb.AppendLine(@"<Styles>");
|
||||||
|
sb.AppendLine(@"<Style ss:ID=""Header""><Font ss:Bold=""1""/></Style>");
|
||||||
|
sb.AppendLine(@"<Style ss:ID=""Text""><NumberFormat ss:Format=""@""/></Style>");
|
||||||
|
sb.AppendLine(@"</Styles>");
|
||||||
|
sb.Append("<Worksheet ss:Name=\"").Append(XmlEscape(sheetName)).AppendLine("\">");
|
||||||
|
sb.AppendLine("<Table>");
|
||||||
|
|
||||||
|
sb.AppendLine("<Row>");
|
||||||
|
foreach (var header in headers)
|
||||||
|
{
|
||||||
|
sb.Append("<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">")
|
||||||
|
.Append(XmlEscape(header))
|
||||||
|
.AppendLine("</Data></Cell>");
|
||||||
|
}
|
||||||
|
sb.AppendLine("</Row>");
|
||||||
|
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
sb.AppendLine("<Row>");
|
||||||
|
foreach (var cell in row)
|
||||||
|
AppendExcelCell(sb, cell);
|
||||||
|
sb.AppendLine("</Row>");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("</Table>");
|
||||||
|
sb.AppendLine("</Worksheet>");
|
||||||
|
sb.AppendLine("</Workbook>");
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendExcelCell(StringBuilder sb, object? value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case null:
|
||||||
|
sb.AppendLine(@"<Cell ss:StyleID=""Text""><Data ss:Type=""String""></Data></Cell>");
|
||||||
|
break;
|
||||||
|
case string s:
|
||||||
|
sb.Append(@"<Cell ss:StyleID=""Text""><Data ss:Type=""String"">")
|
||||||
|
.Append(XmlEscape(s))
|
||||||
|
.AppendLine("</Data></Cell>");
|
||||||
|
break;
|
||||||
|
case bool b:
|
||||||
|
sb.Append(@"<Cell><Data ss:Type=""Boolean"">")
|
||||||
|
.Append(b ? "1" : "0")
|
||||||
|
.AppendLine("</Data></Cell>");
|
||||||
|
break;
|
||||||
|
case byte or sbyte or short or ushort or int or uint or long or ulong:
|
||||||
|
sb.Append(@"<Cell><Data ss:Type=""Number"">")
|
||||||
|
.Append(Convert.ToString(value, CultureInfo.InvariantCulture))
|
||||||
|
.AppendLine("</Data></Cell>");
|
||||||
|
break;
|
||||||
|
case float or double or decimal:
|
||||||
|
sb.Append(@"<Cell><Data ss:Type=""Number"">")
|
||||||
|
.Append(Convert.ToString(value, CultureInfo.InvariantCulture))
|
||||||
|
.AppendLine("</Data></Cell>");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.Append(@"<Cell ss:StyleID=""Text""><Data ss:Type=""String"">")
|
||||||
|
.Append(XmlEscape(Convert.ToString(value, CultureInfo.InvariantCulture)))
|
||||||
|
.AppendLine("</Data></Cell>");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string XmlEscape(string? value) =>
|
||||||
|
WebUtility.HtmlEncode(value ?? string.Empty);
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using DateTimeConverterCL;
|
||||||
|
|
||||||
|
namespace BackOffice.Common.Utilities;
|
||||||
|
|
||||||
|
/// <summary>ساخت HTML فاکتور برای چاپ مرورگر (jsHtmlToPdf).</summary>
|
||||||
|
public static class OrderInvoicePrint
|
||||||
|
{
|
||||||
|
public sealed class InvoiceModel
|
||||||
|
{
|
||||||
|
public string ShopTitle { get; set; } = "فروشگاه";
|
||||||
|
public string OrderId { get; set; } = "";
|
||||||
|
public string? CustomerName { get; set; }
|
||||||
|
public string? CustomerMobile { get; set; }
|
||||||
|
public string? CustomerNationalCode { get; set; }
|
||||||
|
public string? Address { get; set; }
|
||||||
|
public string? PostalCode { get; set; }
|
||||||
|
public string? PaymentStatus { get; set; }
|
||||||
|
public string? PaymentMethod { get; set; }
|
||||||
|
public string? TransactionId { get; set; }
|
||||||
|
public string? TrackingCode { get; set; }
|
||||||
|
public DateTime? Created { get; set; }
|
||||||
|
public DateTime? PaymentDate { get; set; }
|
||||||
|
public long Subtotal { get; set; }
|
||||||
|
public long DiscountTotal { get; set; }
|
||||||
|
public long CreditUsed { get; set; }
|
||||||
|
public long GatewayAmount { get; set; }
|
||||||
|
public long VatAmount { get; set; }
|
||||||
|
public long TotalAmount { get; set; }
|
||||||
|
public List<InvoiceLine> Lines { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class InvoiceLine
|
||||||
|
{
|
||||||
|
public string Title { get; set; } = "";
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
public long UnitPrice { get; set; }
|
||||||
|
public long Discount { get; set; }
|
||||||
|
public long LineTotal { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildHtml(InvoiceModel model)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append("<!DOCTYPE html><html lang=\"fa\" dir=\"rtl\"><head><meta charset=\"utf-8\"/>");
|
||||||
|
sb.Append("<title>").Append(Esc($"فاکتور {model.OrderId}")).Append("</title>");
|
||||||
|
sb.Append("""
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body { font-family: Tahoma, "Segoe UI", sans-serif; color: #111; margin: 24px; font-size: 13px; }
|
||||||
|
h1 { font-size: 20px; margin: 0 0 4px; }
|
||||||
|
.muted { color: #555; }
|
||||||
|
.header { display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 2px solid #222; padding-bottom: 12px; margin-bottom: 16px; }
|
||||||
|
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; margin-bottom: 16px; }
|
||||||
|
.label { color: #666; font-size: 11px; }
|
||||||
|
.value { font-weight: 600; }
|
||||||
|
table { width: 100%; border-collapse: collapse; margin: 12px 0 16px; }
|
||||||
|
th, td { border: 1px solid #ccc; padding: 8px; text-align: right; }
|
||||||
|
th { background: #f3f3f3; }
|
||||||
|
.num { text-align: left; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
||||||
|
.totals { width: 320px; margin-right: auto; }
|
||||||
|
.totals td { border: none; padding: 4px 0; }
|
||||||
|
.totals .grand { font-size: 15px; font-weight: 700; border-top: 2px solid #222; padding-top: 8px; }
|
||||||
|
.footer { margin-top: 28px; font-size: 11px; color: #666; border-top: 1px dashed #bbb; padding-top: 8px; }
|
||||||
|
@media print {
|
||||||
|
body { margin: 0; }
|
||||||
|
.no-print { display: none !important; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
""");
|
||||||
|
sb.Append("</head><body>");
|
||||||
|
sb.Append("<div class=\"header\"><div>");
|
||||||
|
sb.Append("<h1>").Append(Esc(model.ShopTitle)).Append("</h1>");
|
||||||
|
sb.Append("<div class=\"muted\">فاکتور فروش</div></div><div>");
|
||||||
|
sb.Append("<div><span class=\"label\">شماره فاکتور: </span><span class=\"value\">").Append(Esc(model.OrderId)).Append("</span></div>");
|
||||||
|
if (model.Created.HasValue)
|
||||||
|
sb.Append("<div><span class=\"label\">تاریخ ثبت: </span>").Append(Esc(FormatDate(model.Created.Value))).Append("</div>");
|
||||||
|
if (model.PaymentDate.HasValue)
|
||||||
|
sb.Append("<div><span class=\"label\">تاریخ پرداخت: </span>").Append(Esc(FormatDate(model.PaymentDate.Value))).Append("</div>");
|
||||||
|
sb.Append("</div></div>");
|
||||||
|
|
||||||
|
sb.Append("<div class=\"grid\">");
|
||||||
|
AppendField(sb, "نام مشتری", model.CustomerName);
|
||||||
|
AppendField(sb, "موبایل", model.CustomerMobile);
|
||||||
|
AppendField(sb, "کد ملی", model.CustomerNationalCode);
|
||||||
|
AppendField(sb, "کد پستی", model.PostalCode);
|
||||||
|
AppendField(sb, "وضعیت پرداخت", model.PaymentStatus);
|
||||||
|
AppendField(sb, "روش پرداخت", model.PaymentMethod);
|
||||||
|
AppendField(sb, "کد تراکنش", model.TransactionId);
|
||||||
|
AppendField(sb, "کد رهگیری", model.TrackingCode);
|
||||||
|
if (!string.IsNullOrWhiteSpace(model.Address))
|
||||||
|
{
|
||||||
|
sb.Append("<div style=\"grid-column:1/-1\"><div class=\"label\">آدرس</div><div class=\"value\">")
|
||||||
|
.Append(Esc(model.Address)).Append("</div></div>");
|
||||||
|
}
|
||||||
|
sb.Append("</div>");
|
||||||
|
|
||||||
|
sb.Append("<table><thead><tr>");
|
||||||
|
sb.Append("<th>#</th><th>کالا</th><th>تعداد</th><th>قیمت واحد</th><th>تخفیف</th><th>مبلغ</th>");
|
||||||
|
sb.Append("</tr></thead><tbody>");
|
||||||
|
var i = 1;
|
||||||
|
foreach (var line in model.Lines)
|
||||||
|
{
|
||||||
|
sb.Append("<tr>");
|
||||||
|
sb.Append("<td class=\"num\">").Append(i++).Append("</td>");
|
||||||
|
sb.Append("<td>").Append(Esc(line.Title)).Append("</td>");
|
||||||
|
sb.Append("<td class=\"num\">").Append(line.Quantity).Append("</td>");
|
||||||
|
sb.Append("<td class=\"num\">").Append(Money(line.UnitPrice)).Append("</td>");
|
||||||
|
sb.Append("<td class=\"num\">").Append(Money(line.Discount)).Append("</td>");
|
||||||
|
sb.Append("<td class=\"num\">").Append(Money(line.LineTotal)).Append("</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
|
}
|
||||||
|
if (model.Lines.Count == 0)
|
||||||
|
sb.Append("<tr><td colspan=\"6\" class=\"muted\">آیتمی ثبت نشده است</td></tr>");
|
||||||
|
sb.Append("</tbody></table>");
|
||||||
|
|
||||||
|
sb.Append("<table class=\"totals\"><tbody>");
|
||||||
|
if (model.Subtotal > 0)
|
||||||
|
AppendTotalRow(sb, "جمع اقلام", model.Subtotal);
|
||||||
|
if (model.DiscountTotal > 0)
|
||||||
|
AppendTotalRow(sb, "تخفیف", model.DiscountTotal);
|
||||||
|
if (model.VatAmount > 0)
|
||||||
|
AppendTotalRow(sb, "مالیات بر ارزش افزوده", model.VatAmount);
|
||||||
|
if (model.CreditUsed > 0)
|
||||||
|
AppendTotalRow(sb, "پرداخت از کیف اعتباری", model.CreditUsed);
|
||||||
|
if (model.GatewayAmount > 0)
|
||||||
|
AppendTotalRow(sb, "پرداخت درگاه / نقدی", model.GatewayAmount);
|
||||||
|
sb.Append("<tr class=\"grand\"><td>مبلغ قابل پرداخت</td><td class=\"num\">")
|
||||||
|
.Append(Money(model.TotalAmount)).Append(" تومان</td></tr>");
|
||||||
|
sb.Append("</tbody></table>");
|
||||||
|
|
||||||
|
sb.Append("<div class=\"footer\">چاپشده از پنل ادمین — ").Append(Esc(FormatDate(DateTime.Now))).Append("</div>");
|
||||||
|
sb.Append("</body></html>");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendField(StringBuilder sb, string label, string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value)) return;
|
||||||
|
sb.Append("<div><div class=\"label\">").Append(Esc(label)).Append("</div><div class=\"value\">")
|
||||||
|
.Append(Esc(value)).Append("</div></div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendTotalRow(StringBuilder sb, string label, long amount)
|
||||||
|
{
|
||||||
|
sb.Append("<tr><td>").Append(Esc(label)).Append("</td><td class=\"num\">")
|
||||||
|
.Append(Money(amount)).Append(" تومان</td></tr>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Money(long amount) => amount.ToString("N0", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
private static string FormatDate(DateTime dt)
|
||||||
|
{
|
||||||
|
try { return dt.ToLocalTime().MiladiToJalaliWithTime(); }
|
||||||
|
catch { return dt.ToLocalTime().ToString("yyyy/MM/dd HH:mm"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Esc(string? s) => WebUtility.HtmlEncode(s ?? "");
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
namespace BackOffice.Common.Utilities;
|
||||||
|
|
||||||
|
using CMSMicroservice.Protobuf.Protos;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
public static class ReportLabels
|
||||||
|
{
|
||||||
|
public static string GetPaymentStatusLabel(bool success) => success ? "موفق" : "در انتظار / ناموفق";
|
||||||
|
|
||||||
|
public static string GetTransactionPaymentStatusLabel(PaymentStatus? status) => status switch
|
||||||
|
{
|
||||||
|
PaymentStatus.Success => "موفق",
|
||||||
|
PaymentStatus.Pending => "در انتظار",
|
||||||
|
PaymentStatus.Reject => "رد شده",
|
||||||
|
_ => "—"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Color GetTransactionPaymentStatusColor(PaymentStatus? status) => status switch
|
||||||
|
{
|
||||||
|
PaymentStatus.Success => Color.Success,
|
||||||
|
PaymentStatus.Pending => Color.Warning,
|
||||||
|
PaymentStatus.Reject => Color.Error,
|
||||||
|
_ => Color.Default
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string GetTransactionTypeLabel(TransactionType? type) => type switch
|
||||||
|
{
|
||||||
|
TransactionType.Buy => "خرید",
|
||||||
|
TransactionType.DepositIpg => "واریز درگاه",
|
||||||
|
TransactionType.DepositExternal1 => "واریز خارجی",
|
||||||
|
TransactionType.Withdraw => "برداشت",
|
||||||
|
TransactionType.NetworkCommission => "کمیسیون شبکه",
|
||||||
|
TransactionType.ClubActivation => "فعالسازی باشگاه",
|
||||||
|
TransactionType.DiscountWalletCharge => "شارژ کیف تخفیف",
|
||||||
|
TransactionType.DiscountShopPurchase => "خرید فروشگاه تخفیف",
|
||||||
|
TransactionType.MagicWalletDeposit => "واریز کیف جادویی",
|
||||||
|
TransactionType.MagicWalletBonus => "بونوس کیف جادویی",
|
||||||
|
TransactionType.CreditWalletCharge => "شارژ کیف اصلی",
|
||||||
|
TransactionType.ManualCreditWalletCharge => "شارژ دستی کیف اصلی",
|
||||||
|
_ => type?.ToString() ?? "—"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string GetPurchaseMethodLabel(int method) => method switch
|
||||||
|
{
|
||||||
|
1 => "دایا",
|
||||||
|
2 => "درگاه",
|
||||||
|
3 => "دستی",
|
||||||
|
_ => "نامشخص"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string GetClubMembershipActionLabel(int action) => action switch
|
||||||
|
{
|
||||||
|
0 => "فعالسازی",
|
||||||
|
1 => "غیرفعالسازی",
|
||||||
|
2 => "ویرایش",
|
||||||
|
3 => "اصلاح دستی",
|
||||||
|
_ => action.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string GetClubCycleActionLabel(int action) => action switch
|
||||||
|
{
|
||||||
|
0 => "شروع دوره",
|
||||||
|
1 => "ورود به حالت جادویی",
|
||||||
|
2 => "پایان حالت جادویی",
|
||||||
|
3 => "تکمیل دوره",
|
||||||
|
4 => "لغو",
|
||||||
|
5 => "اصلاح دستی",
|
||||||
|
_ => action.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string GetMagicStateLabel(int? magicStarted, DateTime? started, DateTime? completed)
|
||||||
|
{
|
||||||
|
if (completed.HasValue) return "جادویی تکمیلشده";
|
||||||
|
if (started.HasValue) return "در حالت جادویی";
|
||||||
|
return "عادی";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FormatActiveChange(bool oldActive, bool newActive) =>
|
||||||
|
$"{(oldActive ? "فعال" : "غیرفعال")} → {(newActive ? "فعال" : "غیرفعال")}";
|
||||||
|
|
||||||
|
public static string FormatBoolChange(bool oldValue, bool newValue) =>
|
||||||
|
$"{(oldValue ? "بله" : "خیر")} → {(newValue ? "بله" : "خیر")}";
|
||||||
|
}
|
||||||
+10
-10
@@ -16,7 +16,7 @@ WORKDIR "/src/BackOffice"
|
|||||||
RUN dotnet publish "BackOffice.csproj" -c Release -o /app/publish
|
RUN dotnet publish "BackOffice.csproj" -c Release -o /app/publish
|
||||||
|
|
||||||
# Runtime stage - nginx for Blazor WebAssembly
|
# Runtime stage - nginx for Blazor WebAssembly
|
||||||
FROM 194.5.195.53:32082/library/nginx:alpine AS final
|
FROM 194.5.195.53:32082/nginx:alpine AS final
|
||||||
WORKDIR /usr/share/nginx/html
|
WORKDIR /usr/share/nginx/html
|
||||||
|
|
||||||
# Remove default nginx files
|
# Remove default nginx files
|
||||||
@@ -25,14 +25,14 @@ RUN rm -rf /usr/share/nginx/html/*
|
|||||||
# Copy published wwwroot (Blazor WASM output)
|
# Copy published wwwroot (Blazor WASM output)
|
||||||
COPY --from=build /app/publish/wwwroot .
|
COPY --from=build /app/publish/wwwroot .
|
||||||
|
|
||||||
# Configure nginx for SPA routing
|
# Copy entrypoint script that sets blazor-environment header
|
||||||
RUN echo 'server { \
|
COPY ["BackOffice/docker-entrypoint.sh", "/docker-entrypoint.sh"]
|
||||||
listen 80; \
|
RUN chmod +x /docker-entrypoint.sh
|
||||||
server_name _; \
|
|
||||||
location / { \
|
# Default Blazor environment (override via K8s env var BLAZOR_ENVIRONMENT)
|
||||||
root /usr/share/nginx/html; \
|
ENV BLAZOR_ENVIRONMENT=Staging
|
||||||
try_files $uri $uri/ /index.html; \
|
# Default gateway URL for gRPC calls (injected into appsettings at startup)
|
||||||
} \
|
ENV GW_URL=https://cms.kbs2.ir
|
||||||
}' > /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
CMD ["/docker-entrypoint.sh"]
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<clear />
|
||||||
|
<!-- Local packages (dev builds) -->
|
||||||
|
<!-- <add key="Local" value="/home/masoud/Apps/project/FourSat/nupkg" /> -->
|
||||||
<!-- Nexus (hosts FourSat packages + proxies nuget.org) -->
|
<!-- Nexus (hosts FourSat packages + proxies nuget.org) -->
|
||||||
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json" allowInsecureConnections="true" />
|
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json" allowInsecureConnections="true" />
|
||||||
</packageSources>
|
</packageSources>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
@using CMSMicroservice.Protobuf.Protos.DiscountProduct
|
||||||
|
|
||||||
|
<MudAutocomplete T="DiscountProductDto"
|
||||||
|
Label="@Label"
|
||||||
|
Value="@_item"
|
||||||
|
DebounceInterval="700"
|
||||||
|
ValueChanged="@((e) => OnSelected(e))"
|
||||||
|
ToStringFunc="@(e => e == null ? null : e.Title)"
|
||||||
|
SearchFunc="@Search"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Clearable="true"
|
||||||
|
ShowProgressIndicator="true"
|
||||||
|
CoerceText="false"
|
||||||
|
OnClearButtonClick="()=> OnSelected(null)" />
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.AutoComplete;
|
||||||
|
|
||||||
|
public partial class DiscountProductsAutoComplete
|
||||||
|
{
|
||||||
|
[Inject] public DiscountProductContract.DiscountProductContractClient DiscountProductService { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public long? Value { get; set; }
|
||||||
|
[Parameter] public string? Label { get; set; } = "محصول تخفیفی";
|
||||||
|
[Parameter] public EventCallback<long?> ValueChanged { get; set; }
|
||||||
|
|
||||||
|
private List<DiscountProductDto> _items = new();
|
||||||
|
private DiscountProductDto? _item;
|
||||||
|
|
||||||
|
protected override async void OnParametersSet()
|
||||||
|
{
|
||||||
|
await base.OnParametersSetAsync();
|
||||||
|
|
||||||
|
if (Value.HasValue && Value.Value > 0 && (_item == null || _item.Id != Value.Value))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var getAll = await DiscountProductService.GetDiscountProductsAsync(new GetDiscountProductsRequest
|
||||||
|
{
|
||||||
|
PageNumber = 1,
|
||||||
|
PageSize = 50
|
||||||
|
});
|
||||||
|
|
||||||
|
if (getAll?.Models != null)
|
||||||
|
{
|
||||||
|
_item = getAll.Models.FirstOrDefault(x => x.Id == Value.Value);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Silently handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSelected(DiscountProductDto? model)
|
||||||
|
{
|
||||||
|
if (model == null)
|
||||||
|
{
|
||||||
|
_item = null;
|
||||||
|
await ValueChanged.InvokeAsync(null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_item = model;
|
||||||
|
await ValueChanged.InvokeAsync(model.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<DiscountProductDto>> Search(string value, CancellationToken token)
|
||||||
|
{
|
||||||
|
var request = new GetDiscountProductsRequest
|
||||||
|
{
|
||||||
|
PageNumber = 1,
|
||||||
|
PageSize = 9
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
request.SearchQuery = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var getAll = await DiscountProductService.GetDiscountProductsAsync(request);
|
||||||
|
_items = getAll?.Models?.ToList() ?? new List<DiscountProductDto>();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_items = new List<DiscountProductDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
@using CMSMicroservice.Protobuf.Protos.Package
|
||||||
|
|
||||||
|
<MudSelect T="long?"
|
||||||
|
Label="@Label"
|
||||||
|
Value="@SelectedPackageId"
|
||||||
|
ValueChanged="OnValueChanged"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Clearable="true"
|
||||||
|
Style="min-width: 160px;">
|
||||||
|
<MudSelectItem T="long?" Value="@((long?)null)">همه پکیجها</MudSelectItem>
|
||||||
|
@foreach (var pkg in _packages)
|
||||||
|
{
|
||||||
|
<MudSelectItem T="long?" Value="@((long?)pkg.Id)">@pkg.Title</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using CMSMicroservice.Protobuf.Protos.Package;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.AutoComplete;
|
||||||
|
|
||||||
|
public partial class PackageSelect
|
||||||
|
{
|
||||||
|
[Inject] public PackageContract.PackageContractClient PackageClient { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public string Label { get; set; } = "پکیج";
|
||||||
|
[Parameter] public long? SelectedPackageId { get; set; }
|
||||||
|
[Parameter] public EventCallback<long?> SelectedPackageIdChanged { get; set; }
|
||||||
|
|
||||||
|
private List<GetAllPackageByFilterResponseModel> _packages = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadPackages()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new GetAllPackageByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new() { PageNumber = 1, PageSize = 100 },
|
||||||
|
Filter = new()
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await PackageClient.GetAllPackageByFilterAsync(request);
|
||||||
|
_packages = result.Models?.OrderBy(p => p.SortOrder).ToList()
|
||||||
|
?? new List<GetAllPackageByFilterResponseModel>();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_packages = new List<GetAllPackageByFilterResponseModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnValueChanged(long? value)
|
||||||
|
{
|
||||||
|
SelectedPackageId = value;
|
||||||
|
await SelectedPackageIdChanged.InvokeAsync(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,11 @@
|
|||||||
Required="true"
|
Required="true"
|
||||||
Variant="Variant.Outlined" />
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.Slug"
|
||||||
|
Label="نشانی URL (slug)"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
HelperText="اختیاری — خالی بگذارید تا خودکار از عنوان ساخته شود. فقط حروف کوچک انگلیسی، عدد و خط تیره." />
|
||||||
|
|
||||||
<MudTextField @bind-Value="Model.Summary"
|
<MudTextField @bind-Value="Model.Summary"
|
||||||
Label="خلاصه"
|
Label="خلاصه"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.h5" Class="mb-3">فیچرهای باشگاه</MudText>
|
<MudText Typo="Typo.h5" Class="mb-3">فیچرهای باشگاه</MudText>
|
||||||
|
|
||||||
|
@* ── G9: راهنمای ماتریس فیچر (Q28) ── *@
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||||
|
فیچرهای تیکخورده برای خریداران این پکیج فعال میشود. تغییر فیچرها روی کاربران فعلی بلافاصله اعمال میشود.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
<MudTabs Elevation="0" Rounded="true" Border="true" ApplyEffectsToContainer="true"
|
<MudTabs Elevation="0" Rounded="true" Border="true" ApplyEffectsToContainer="true"
|
||||||
PanelClass="pt-4" @bind-ActivePanelIndex="_activeTab">
|
PanelClass="pt-4" @bind-ActivePanelIndex="_activeTab">
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,34 @@
|
|||||||
@page "/club"
|
@page "/club"
|
||||||
@page "/club/members"
|
@page "/club/members"
|
||||||
|
@page "/club/purchases"
|
||||||
|
@page "/club/customer-packages"
|
||||||
|
@page "/club/cycles"
|
||||||
|
@page "/club/history"
|
||||||
@page "/club/statistics"
|
@page "/club/statistics"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
@using BackOffice.Pages.Club.Reports
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.h5" Class="mb-3">باشگاه مشتریان</MudText>
|
<MudText Typo="Typo.h5" Class="mb-3">باشگاه مشتریان</MudText>
|
||||||
|
|
||||||
<MudTabs Elevation="0" Rounded="true" Border="true" ApplyEffectsToContainer="true"
|
<MudTabs Elevation="0" Rounded="true" Border="true" ApplyEffectsToContainer="true"
|
||||||
PanelClass="pt-4" @bind-ActivePanelIndex="_activeTab">
|
PanelClass="pt-4" @bind-ActivePanelIndex="_activeTab">
|
||||||
<MudTabPanel Text="اعضای باشگاه" Icon="@Icons.Material.Filled.Groups">
|
<MudTabPanel Text="اعضا" Icon="@Icons.Material.Filled.Groups">
|
||||||
<ClubMembers />
|
<ClubMembers />
|
||||||
</MudTabPanel>
|
</MudTabPanel>
|
||||||
<MudTabPanel Text="آمار باشگاه" Icon="@Icons.Material.Filled.PieChart">
|
<MudTabPanel Text="گزارش خرید پکیج" Icon="@Icons.Material.Filled.ShoppingBag">
|
||||||
|
<ClubPackagePurchasesReport />
|
||||||
|
</MudTabPanel>
|
||||||
|
<MudTabPanel Text="گزارش مشتریان پکیج" Icon="@Icons.Material.Filled.PeopleAlt">
|
||||||
|
<CustomerPackagePurchasesReport />
|
||||||
|
</MudTabPanel>
|
||||||
|
<MudTabPanel Text="گزارش دورهها" Icon="@Icons.Material.Filled.Loop">
|
||||||
|
<ClubCyclesReport />
|
||||||
|
</MudTabPanel>
|
||||||
|
<MudTabPanel Text="گزارش تاریخچه" Icon="@Icons.Material.Filled.History">
|
||||||
|
<ClubHistoriesReport />
|
||||||
|
</MudTabPanel>
|
||||||
|
<MudTabPanel Text="آمار" Icon="@Icons.Material.Filled.PieChart">
|
||||||
<Statistics />
|
<Statistics />
|
||||||
</MudTabPanel>
|
</MudTabPanel>
|
||||||
</MudTabs>
|
</MudTabs>
|
||||||
@@ -25,7 +42,11 @@
|
|||||||
var uri = Navigation.ToBaseRelativePath(Navigation.Uri).ToLowerInvariant().TrimEnd('/');
|
var uri = Navigation.ToBaseRelativePath(Navigation.Uri).ToLowerInvariant().TrimEnd('/');
|
||||||
_activeTab = uri switch
|
_activeTab = uri switch
|
||||||
{
|
{
|
||||||
"club/statistics" => 1,
|
"club/purchases" => 1,
|
||||||
|
"club/customer-packages" => 2,
|
||||||
|
"club/cycles" => 3,
|
||||||
|
"club/history" => 4,
|
||||||
|
"club/statistics" => 5,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,17 @@
|
|||||||
|
|
||||||
@using Google.Protobuf.WellKnownTypes
|
@using Google.Protobuf.WellKnownTypes
|
||||||
@using BackOffice.Pages.Club.Components
|
@using BackOffice.Pages.Club.Components
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
@using CMSMicroservice.Protobuf.Protos.ClubMembership
|
@using CMSMicroservice.Protobuf.Protos.ClubMembership
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.h4" Class="mb-4">مدیریت اعضای باشگاه</MudText>
|
<MudText Typo="Typo.h4" Class="mb-4">مدیریت اعضای باشگاه</MudText>
|
||||||
|
|
||||||
|
@* ── G13: راهنمای اعضای باشگاه (Q28) ── *@
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||||
|
فیلتر بر اساس آخرین پکیج خریداریشده اعمال میشود. هر کاربر میتواند چندین دوره پکیج داشته باشد.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
<MudDataGrid T="ClubMembershipModel"
|
<MudDataGrid T="ClubMembershipModel"
|
||||||
ServerData="@(new Func<GridState<ClubMembershipModel>, Task<GridData<ClubMembershipModel>>>(ServerReload))"
|
ServerData="@(new Func<GridState<ClubMembershipModel>, Task<GridData<ClubMembershipModel>>>(ServerReload))"
|
||||||
Hover="true"
|
Hover="true"
|
||||||
@@ -16,7 +22,8 @@
|
|||||||
<ToolBarContent>
|
<ToolBarContent>
|
||||||
<MudText Typo="Typo.h6">لیست اعضا</MudText>
|
<MudText Typo="Typo.h6">لیست اعضا</MudText>
|
||||||
<MudSpacer />
|
<MudSpacer />
|
||||||
<MudStack Row="true" Spacing="2">
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
|
<UserAutoComplete Label="جستجوی کاربر" @bind-SelectedUserId="_filterUserId" />
|
||||||
<MudSwitch T="bool" @bind-Value="_filterIsActive"
|
<MudSwitch T="bool" @bind-Value="_filterIsActive"
|
||||||
Color="Color.Success"
|
Color="Color.Success"
|
||||||
Label="فقط فعالها" />
|
Label="فقط فعالها" />
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public partial class ClubMembers
|
|||||||
|
|
||||||
private MudDataGrid<ClubMembershipModel> _gridData;
|
private MudDataGrid<ClubMembershipModel> _gridData;
|
||||||
private bool _filterIsActive = true;
|
private bool _filterIsActive = true;
|
||||||
|
private long? _filterUserId;
|
||||||
|
|
||||||
private async Task<GridData<ClubMembershipModel>> ServerReload(GridState<ClubMembershipModel> state)
|
private async Task<GridData<ClubMembershipModel>> ServerReload(GridState<ClubMembershipModel> state)
|
||||||
{
|
{
|
||||||
@@ -30,6 +31,9 @@ public partial class ClubMembers
|
|||||||
// Always use the filter value since it's now a bool (not nullable)
|
// Always use the filter value since it's now a bool (not nullable)
|
||||||
request.IsActive = _filterIsActive;
|
request.IsActive = _filterIsActive;
|
||||||
|
|
||||||
|
if (_filterUserId.HasValue && _filterUserId.Value > 0)
|
||||||
|
request.UserId = _filterUserId.Value;
|
||||||
|
|
||||||
var result = await ClubContract.GetAllClubMembershipsAsync(request);
|
var result = await ClubContract.GetAllClubMembershipsAsync(request);
|
||||||
|
|
||||||
if (result?.Models != null && result.Models.Any())
|
if (result?.Models != null && result.Models.Any())
|
||||||
|
|||||||
@@ -1,32 +1,39 @@
|
|||||||
@using CMSMicroservice.Protobuf.Protos.ClubMembership
|
@using CMSMicroservice.Protobuf.Protos.ClubMembership
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.Package
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<MudForm @ref="_form" @bind-IsValid="_formValid">
|
<MudForm @ref="_form" @bind-IsValid="_formValid">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudNumericField @bind-Value="_model.UserId"
|
<UserAutoComplete Label="انتخاب کاربر" @bind-SelectedUserId="_model.UserId" />
|
||||||
Label="شناسه کاربر"
|
|
||||||
Required="true"
|
|
||||||
RequiredError="شناسه کاربر الزامی است"
|
|
||||||
Variant="Variant.Outlined" />
|
|
||||||
|
|
||||||
<MudNumericField @bind-Value="_model.PackageId"
|
@if (_isLoadingPackages)
|
||||||
Label="شناسه پکیج"
|
{
|
||||||
Required="true"
|
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="56px" />
|
||||||
RequiredError="شناسه پکیج الزامی است"
|
}
|
||||||
Variant="Variant.Outlined" />
|
else
|
||||||
|
{
|
||||||
<MudNumericField @bind-Value="_model.DurationMonths"
|
<MudSelect T="long" @bind-Value="_model.PackageId"
|
||||||
Label="مدت زمان (ماه)"
|
Label="انتخاب پکیج"
|
||||||
Required="true"
|
Required="true"
|
||||||
RequiredError="مدت زمان الزامی است"
|
RequiredError="انتخاب پکیج الزامی است"
|
||||||
Min="1"
|
Variant="Variant.Outlined"
|
||||||
Max="12"
|
AnchorOrigin="Origin.BottomCenter">
|
||||||
Variant="Variant.Outlined" />
|
@foreach (var pkg in _packages)
|
||||||
|
{
|
||||||
|
<MudSelectItem T="long" Value="pkg.Id">
|
||||||
|
@pkg.Title — @($"{pkg.Price:N0}") ریال
|
||||||
|
</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
}
|
||||||
|
|
||||||
<MudAlert Severity="Severity.Info" Dense="true">
|
<MudAlert Severity="Severity.Info" Dense="true">
|
||||||
<MudText Typo="Typo.body2">
|
<MudText Typo="Typo.body2">
|
||||||
با تایید، عضویت باشگاه برای کاربر فعال میشود.
|
با تایید، عضویت باشگاه برای کاربر فعال میشود.
|
||||||
|
<br/>
|
||||||
|
<small>عضویت باشگاه بدون محدودیت زمانی است.</small>
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudAlert>
|
</MudAlert>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@@ -37,7 +44,7 @@
|
|||||||
<MudButton Color="Color.Success"
|
<MudButton Color="Color.Success"
|
||||||
Variant="Variant.Filled"
|
Variant="Variant.Filled"
|
||||||
OnClick="Submit"
|
OnClick="Submit"
|
||||||
Disabled="@(!_formValid || _isSubmitting)">
|
Disabled="@(!_formValid || _isSubmitting || _isLoadingPackages)">
|
||||||
@if (_isSubmitting)
|
@if (_isSubmitting)
|
||||||
{
|
{
|
||||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" />
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" />
|
||||||
@@ -54,12 +61,49 @@
|
|||||||
@code {
|
@code {
|
||||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
|
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
|
||||||
[Inject] public ClubMembershipContract.ClubMembershipContractClient ClubContract { get; set; }
|
[Inject] public ClubMembershipContract.ClubMembershipContractClient ClubContract { get; set; }
|
||||||
|
[Inject] public PackageContract.PackageContractClient PackageClient { get; set; }
|
||||||
|
|
||||||
private MudForm _form;
|
private MudForm _form;
|
||||||
private bool _formValid;
|
private bool _formValid;
|
||||||
private bool _isSubmitting;
|
private bool _isSubmitting;
|
||||||
|
private bool _isLoadingPackages = true;
|
||||||
|
private List<GetAllPackageByFilterResponseModel> _packages = new();
|
||||||
private ActivateModel _model = new();
|
private ActivateModel _model = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadPackages()
|
||||||
|
{
|
||||||
|
_isLoadingPackages = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new GetAllPackageByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = 1,
|
||||||
|
PageSize = 50
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var result = await PackageClient.GetAllPackageByFilterAsync(request);
|
||||||
|
if (result?.Models != null)
|
||||||
|
{
|
||||||
|
_packages = result.Models.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در بارگذاری پکیجها: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isLoadingPackages = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Cancel() => MudDialog.Cancel();
|
private void Cancel() => MudDialog.Cancel();
|
||||||
|
|
||||||
private async Task Submit()
|
private async Task Submit()
|
||||||
@@ -67,19 +111,31 @@
|
|||||||
await _form.Validate();
|
await _form.Validate();
|
||||||
if (!_formValid) return;
|
if (!_formValid) return;
|
||||||
|
|
||||||
|
if (!_model.UserId.HasValue || _model.UserId.Value <= 0)
|
||||||
|
{
|
||||||
|
Snackbar.Add("لطفاً کاربر را انتخاب کنید.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_model.PackageId <= 0)
|
||||||
|
{
|
||||||
|
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_isSubmitting = true;
|
_isSubmitting = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = new ActivateClubMembershipRequest
|
var request = new ActivateClubMembershipRequest
|
||||||
{
|
{
|
||||||
UserId = _model.UserId,
|
UserId = _model.UserId.Value,
|
||||||
PackageId = _model.PackageId,
|
PackageId = _model.PackageId,
|
||||||
DurationMonths = _model.DurationMonths
|
ForceActivation = true
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = await ClubContract.ActivateClubMembershipAsync(request);
|
var response = await ClubContract.ActivateClubMembershipAsync(request);
|
||||||
|
|
||||||
Snackbar.Add($"عضویت با موفقیت فعال شد.", Severity.Success);
|
Snackbar.Add("عضویت با موفقیت فعال شد.", Severity.Success);
|
||||||
MudDialog.Close(DialogResult.Ok(true));
|
MudDialog.Close(DialogResult.Ok(true));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -94,8 +150,7 @@
|
|||||||
|
|
||||||
private class ActivateModel
|
private class ActivateModel
|
||||||
{
|
{
|
||||||
public long UserId { get; set; }
|
public long? UserId { get; set; }
|
||||||
public long PackageId { get; set; }
|
public long PackageId { get; set; }
|
||||||
public int DurationMonths { get; set; } = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,23 +33,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>پکیج:</strong></td>
|
<td><strong>پکیج:</strong></td>
|
||||||
<td>@Member.PackageName</td>
|
<td>@(string.IsNullOrEmpty(Member.PackageName) ? "—" : Member.PackageName)</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>کد فعالسازی:</strong></td>
|
|
||||||
<td dir="ltr">@Member.ActivationCode</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>تاریخ فعالسازی:</strong></td>
|
<td><strong>تاریخ فعالسازی:</strong></td>
|
||||||
<td>@Member.ActivatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</td>
|
<td>@(Member.ActivatedAt != null ? Member.ActivatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() : "—")</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>تاریخ انقضا:</strong></td>
|
|
||||||
<td>
|
|
||||||
<MudText Color="@(Member.IsExpired ? Color.Error : Color.Success)">
|
|
||||||
@Member.ExpiresAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()
|
|
||||||
</MudText>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>وضعیت:</strong></td>
|
<td><strong>وضعیت:</strong></td>
|
||||||
@@ -61,60 +49,17 @@
|
|||||||
</MudChip>
|
</MudChip>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td><strong>منقضی شده:</strong></td>
|
|
||||||
<td>
|
|
||||||
<MudChip T="string"
|
|
||||||
Color="@(Member.IsExpired ? Color.Warning : Color.Default)"
|
|
||||||
Size="Size.Small">
|
|
||||||
@(Member.IsExpired ? "بله" : "خیر")
|
|
||||||
</MudChip>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</MudSimpleTable>
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
<MudPaper Class="pa-3" Elevation="0">
|
|
||||||
<MudText Typo="Typo.h6" Color="Color.Primary">تاریخچه</MudText>
|
|
||||||
<MudDivider Class="my-2" />
|
|
||||||
<MudSimpleTable Dense="true">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>تاریخ ایجاد:</strong></td>
|
<td><strong>تاریخ ایجاد:</strong></td>
|
||||||
<td>@Member.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</td>
|
<td>@Member.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</MudSimpleTable>
|
</MudSimpleTable>
|
||||||
|
|
||||||
@if (_historyLoading)
|
|
||||||
{
|
|
||||||
<MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="my-2" />
|
|
||||||
}
|
|
||||||
else if (_history.Any())
|
|
||||||
{
|
|
||||||
<MudTable T="ClubMembershipHistoryModel" Items="_history" Dense="true" Hover="true" Class="mt-2">
|
|
||||||
<HeaderContent>
|
|
||||||
<MudTh>عملیات</MudTh>
|
|
||||||
<MudTh>تاریخ</MudTh>
|
|
||||||
<MudTh>دلیل</MudTh>
|
|
||||||
<MudTh>توسط</MudTh>
|
|
||||||
</HeaderContent>
|
|
||||||
<RowTemplate>
|
|
||||||
<MudTd DataLabel="عملیات">
|
|
||||||
<MudChip T="string" Size="Size.Small" Color="Color.Info">@context.Action</MudChip>
|
|
||||||
</MudTd>
|
|
||||||
<MudTd DataLabel="تاریخ">@context.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</MudTd>
|
|
||||||
<MudTd DataLabel="دلیل">@(string.IsNullOrEmpty(context.Reason) ? "-" : context.Reason)</MudTd>
|
|
||||||
<MudTd DataLabel="توسط">@(string.IsNullOrEmpty(context.PerformedBy) ? "-" : context.PerformedBy)</MudTd>
|
|
||||||
</RowTemplate>
|
|
||||||
</MudTable>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="mt-2">تاریخچهای یافت نشد.</MudText>
|
|
||||||
}
|
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text">
|
||||||
|
تاریخچه تغییرات عضویت در تب «گزارش تاریخچه» باشگاه قابل مشاهده است.
|
||||||
|
</MudAlert>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -126,39 +71,5 @@
|
|||||||
@code {
|
@code {
|
||||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
|
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
|
||||||
[Parameter] public ClubMembershipModel Member { get; set; }
|
[Parameter] public ClubMembershipModel Member { get; set; }
|
||||||
[Inject] public ClubMembershipContract.ClubMembershipContractClient ClubClient { get; set; }
|
|
||||||
|
|
||||||
private List<ClubMembershipHistoryModel> _history = new();
|
|
||||||
private bool _historyLoading;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
await LoadHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadHistory()
|
|
||||||
{
|
|
||||||
_historyLoading = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var request = new GetClubMembershipHistoryRequest
|
|
||||||
{
|
|
||||||
UserId = Member.UserId,
|
|
||||||
PageIndex = 0,
|
|
||||||
PageSize = 50
|
|
||||||
};
|
|
||||||
var response = await ClubClient.GetClubMembershipHistoryAsync(request);
|
|
||||||
_history = response.Models?.ToList() ?? new();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_history = new();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_historyLoading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Close() => MudDialog.Close();
|
private void Close() => MudDialog.Close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.ClubMembershipCycle
|
||||||
|
@using Google.Protobuf.WellKnownTypes
|
||||||
|
|
||||||
|
@inject ClubMembershipCycleContract.ClubMembershipCycleContractClient CycleClient
|
||||||
|
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||||
|
گزارش دورههای عضویت باشگاه — هر خرید پکیج یک دور جدید ایجاد میکند.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||||
|
<Filters>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudSelect T="bool?" Clearable="true" Label="دوره جاری" Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" @bind-Value="_isCurrent">
|
||||||
|
<MudSelectItem T="bool?" Value="@((bool?)true)">فقط دوره جاری</MudSelectItem>
|
||||||
|
<MudSelectItem T="bool?" Value="@((bool?)false)">دورههای قبلی</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudSelect T="int?" Clearable="true" Label="وضعیت جادویی" Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" @bind-Value="_magicState">
|
||||||
|
<MudSelectItem T="int?" Value="0">عادی</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="1">در حالت جادویی</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="2">جادویی تکمیلشده</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="4">
|
||||||
|
<DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه خرید پکیج" />
|
||||||
|
</MudItem>
|
||||||
|
</Filters>
|
||||||
|
<Content>
|
||||||
|
<MudGrid Class="mb-3" Spacing="2">
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">کل دورهها</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@_totalCount.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">دوره جاری</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@_currentCount.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">جادویی فعال</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@_magicActive.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
<MudDataGrid @ref="_grid" T="ClubMembershipCycleModel" ServerData="LoadData" ReadOnly="true"
|
||||||
|
Hover="true" Dense="true" Height="calc(100vh - 420px)">
|
||||||
|
<Columns>
|
||||||
|
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||||
|
<PropertyColumn Property="x => x.UserName" Title="کاربر" />
|
||||||
|
<PropertyColumn Property="x => x.CycleNumber" Title="شماره دور" />
|
||||||
|
<PropertyColumn Property="x => x.PackageName" Title="پکیج" />
|
||||||
|
<TemplateColumn Title="مبلغ (ریال)">
|
||||||
|
<CellTemplate>@context.Item.PackageAmount.ToString("N0")</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="روش خرید">
|
||||||
|
<CellTemplate>@ReportLabels.GetPurchaseMethodLabel(context.Item.PurchaseMethod)</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="تاریخ خرید">
|
||||||
|
<CellTemplate>@context.Item.PackagePurchasedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="وضعیت جادویی">
|
||||||
|
<CellTemplate>
|
||||||
|
@ReportLabels.GetMagicStateLabel(null,
|
||||||
|
context.Item.MagicStartedAt?.ToDateTime(),
|
||||||
|
context.Item.MagicCompletedAt?.ToDateTime())
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="دوره جاری">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="@(context.Item.IsCurrentCycle ? Color.Success : Color.Default)">
|
||||||
|
@(context.Item.IsCurrentCycle ? "بله" : "خیر")
|
||||||
|
</MudChip>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
<NoRecordsContent>
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||||
|
</NoRecordsContent>
|
||||||
|
<PagerContent>
|
||||||
|
<MudDataGridPager T="ClubMembershipCycleModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||||
|
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||||
|
RowsPerPageString="تعداد در صفحه" />
|
||||||
|
</PagerContent>
|
||||||
|
</MudDataGrid>
|
||||||
|
</Content>
|
||||||
|
</BasePageComponent>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private BasePageComponent? _basePage;
|
||||||
|
private MudDataGrid<ClubMembershipCycleModel>? _grid;
|
||||||
|
private long? _userId;
|
||||||
|
private bool? _isCurrent;
|
||||||
|
private int? _magicState;
|
||||||
|
private DateTime? _fromDate, _toDate;
|
||||||
|
private long _totalCount, _currentCount, _magicActive;
|
||||||
|
|
||||||
|
private GetAllClubMembershipCycleByFilterFilter BuildFilter() => new()
|
||||||
|
{
|
||||||
|
UserId = _userId > 0 ? _userId : null,
|
||||||
|
IsCurrentCycle = _isCurrent,
|
||||||
|
MagicState = _magicState,
|
||||||
|
PackagePurchasedFrom = _fromDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value, DateTimeKind.Utc)) : null,
|
||||||
|
PackagePurchasedTo = _toDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null
|
||||||
|
};
|
||||||
|
|
||||||
|
private async Task<GridData<ClubMembershipCycleModel>> LoadData(GridState<ClubMembershipCycleModel> state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var filter = BuildFilter();
|
||||||
|
var summary = await CycleClient.GetClubMembershipCycleSummaryAsync(new GetClubMembershipCycleSummaryRequest { Filter = filter });
|
||||||
|
_totalCount = summary.TotalCount;
|
||||||
|
_currentCount = summary.CurrentCycleCount;
|
||||||
|
_magicActive = summary.MagicActiveCount;
|
||||||
|
|
||||||
|
var result = await CycleClient.GetAllClubMembershipCycleByFilterAsync(new GetAllClubMembershipCycleByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||||
|
Filter = filter
|
||||||
|
});
|
||||||
|
return new GridData<ClubMembershipCycleModel>
|
||||||
|
{
|
||||||
|
Items = result?.Models?.ToList() ?? new(),
|
||||||
|
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
return new GridData<ClubMembershipCycleModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFilterSubmit() { if (_grid != null) await _grid.ReloadServerData(); }
|
||||||
|
private async Task OnFilterCleared()
|
||||||
|
{
|
||||||
|
_userId = null; _isCurrent = null; _magicState = null; _fromDate = _toDate = null;
|
||||||
|
if (_grid != null) await _grid.ReloadServerData();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.ClubMembership
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.ClubMembershipCycleHistory
|
||||||
|
@using Google.Protobuf.WellKnownTypes
|
||||||
|
|
||||||
|
@inject ClubMembershipContract.ClubMembershipContractClient ClubClient
|
||||||
|
@inject ClubMembershipCycleHistoryContract.ClubMembershipCycleHistoryContractClient CycleHistoryClient
|
||||||
|
|
||||||
|
<MudTabs Elevation="0" Rounded="true" Border="true" ApplyEffectsToContainer="true" PanelClass="pt-4">
|
||||||
|
<MudTabPanel Text="تغییرات عضویت" Icon="@Icons.Material.Filled.History">
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||||
|
تاریخچه فعال/غیرفعال شدن عضویت باشگاه.
|
||||||
|
</MudAlert>
|
||||||
|
<MudStack Row="true" Spacing="2" Class="mb-3">
|
||||||
|
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_memUserId" />
|
||||||
|
<MudSelect T="int?" Clearable="true" Label="عملیات" Variant="Variant.Outlined" Margin="Margin.Dense"
|
||||||
|
@bind-Value="_memAction" Style="max-width:200px;">
|
||||||
|
<MudSelectItem T="int?" Value="0">فعالسازی</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="1">غیرفعالسازی</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="2">ویرایش</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="3">اصلاح دستی</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ReloadMembership">جستجو</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
<MudDataGrid @ref="_memGrid" T="ClubMembershipHistoryModel" ServerData="LoadMembershipHistory" ReadOnly="true"
|
||||||
|
Hover="true" Dense="true" Height="calc(100vh - 400px)">
|
||||||
|
<Columns>
|
||||||
|
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||||
|
<PropertyColumn Property="x => x.UserName" Title="کاربر" />
|
||||||
|
<TemplateColumn Title="وضعیت">
|
||||||
|
<CellTemplate>@ReportLabels.FormatActiveChange(context.Item.OldIsActive, context.Item.NewIsActive)</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="مشارکت اولیه">
|
||||||
|
<CellTemplate>
|
||||||
|
@(context.Item.OldInitialContribution != null ? context.Item.OldInitialContribution.Value.ToString("N0") : "—")
|
||||||
|
→ @(context.Item.NewInitialContribution != null ? context.Item.NewInitialContribution.Value.ToString("N0") : "—")
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="عملیات">
|
||||||
|
<CellTemplate>@ReportLabels.GetClubMembershipActionLabel(context.Item.Action)</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<PropertyColumn Property="x => x.PerformedBy" Title="انجامدهنده" />
|
||||||
|
<PropertyColumn Property="x => x.Reason" Title="دلیل" />
|
||||||
|
<TemplateColumn Title="تاریخ">
|
||||||
|
<CellTemplate>@context.Item.CreatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
<NoRecordsContent>
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||||
|
</NoRecordsContent>
|
||||||
|
<PagerContent>
|
||||||
|
<MudDataGridPager T="ClubMembershipHistoryModel" PageSizeOptions="@(new int[] { 20, 50, 100 })" />
|
||||||
|
</PagerContent>
|
||||||
|
</MudDataGrid>
|
||||||
|
</MudTabPanel>
|
||||||
|
|
||||||
|
<MudTabPanel Text="تغییرات دوره" Icon="@Icons.Material.Filled.Timeline">
|
||||||
|
<MudAlert Severity="Severity.Warning" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||||
|
ردیابی تغییرات دوره از زمان deploy فعال شد. رکوردهای قدیمیتر ممکن است خالی باشند.
|
||||||
|
</MudAlert>
|
||||||
|
<MudStack Row="true" Spacing="2" Class="mb-3">
|
||||||
|
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_cycleUserId" />
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ReloadCycle">جستجو</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
<MudDataGrid @ref="_cycleGrid" T="ClubMembershipCycleHistoryModel" ServerData="LoadCycleHistory" ReadOnly="true"
|
||||||
|
Hover="true" Dense="true" Height="calc(100vh - 400px)">
|
||||||
|
<Columns>
|
||||||
|
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||||
|
<PropertyColumn Property="x => x.UserName" Title="کاربر" />
|
||||||
|
<PropertyColumn Property="x => x.CycleNumber" Title="دور" />
|
||||||
|
<TemplateColumn Title="دوره جاری">
|
||||||
|
<CellTemplate>@ReportLabels.FormatBoolChange(context.Item.OldIsCurrentCycle, context.Item.NewIsCurrentCycle)</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="عملیات">
|
||||||
|
<CellTemplate>@ReportLabels.GetClubCycleActionLabel(context.Item.Action)</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<PropertyColumn Property="x => x.PerformedBy" Title="انجامدهنده" />
|
||||||
|
<PropertyColumn Property="x => x.Reason" Title="دلیل" />
|
||||||
|
<TemplateColumn Title="تاریخ">
|
||||||
|
<CellTemplate>@context.Item.CreatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
<NoRecordsContent>
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||||
|
</NoRecordsContent>
|
||||||
|
<PagerContent>
|
||||||
|
<MudDataGridPager T="ClubMembershipCycleHistoryModel" PageSizeOptions="@(new int[] { 20, 50, 100 })" />
|
||||||
|
</PagerContent>
|
||||||
|
</MudDataGrid>
|
||||||
|
</MudTabPanel>
|
||||||
|
</MudTabs>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private MudDataGrid<ClubMembershipHistoryModel>? _memGrid;
|
||||||
|
private MudDataGrid<ClubMembershipCycleHistoryModel>? _cycleGrid;
|
||||||
|
private long? _memUserId, _cycleUserId;
|
||||||
|
private int? _memAction;
|
||||||
|
|
||||||
|
private async Task<GridData<ClubMembershipHistoryModel>> LoadMembershipHistory(GridState<ClubMembershipHistoryModel> state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await ClubClient.GetClubMembershipHistoryAsync(new GetClubMembershipHistoryRequest
|
||||||
|
{
|
||||||
|
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||||
|
Filter = new GetClubMembershipHistoryFilter
|
||||||
|
{
|
||||||
|
UserId = _memUserId > 0 ? _memUserId : null,
|
||||||
|
Action = _memAction
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new GridData<ClubMembershipHistoryModel>
|
||||||
|
{
|
||||||
|
Items = result?.Models?.ToList() ?? new(),
|
||||||
|
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
return new GridData<ClubMembershipHistoryModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<GridData<ClubMembershipCycleHistoryModel>> LoadCycleHistory(GridState<ClubMembershipCycleHistoryModel> state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await CycleHistoryClient.GetAllClubMembershipCycleHistoryByFilterAsync(new GetAllClubMembershipCycleHistoryByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||||
|
Filter = new GetAllClubMembershipCycleHistoryByFilterFilter
|
||||||
|
{
|
||||||
|
UserId = _cycleUserId > 0 ? _cycleUserId : null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new GridData<ClubMembershipCycleHistoryModel>
|
||||||
|
{
|
||||||
|
Items = result?.Models?.ToList() ?? new(),
|
||||||
|
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
return new GridData<ClubMembershipCycleHistoryModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReloadMembership() { if (_memGrid != null) await _memGrid.ReloadServerData(); }
|
||||||
|
private async Task ReloadCycle() { if (_cycleGrid != null) await _cycleGrid.ReloadServerData(); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.UserPackagePurchase
|
||||||
|
@using Google.Protobuf.WellKnownTypes
|
||||||
|
|
||||||
|
@inject UserPackagePurchaseContract.UserPackagePurchaseContractClient PurchaseClient
|
||||||
|
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||||
|
گزارش read-only خریدهای پکیج — فقط مشاهده و فیلتر.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||||
|
<Filters>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudSelect T="int?" Clearable="true" Label="روش خرید" Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" @bind-Value="_purchaseMethod">
|
||||||
|
<MudSelectItem T="int?" Value="1">دایا</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="3">دستی</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="2">درگاه</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="4">
|
||||||
|
<DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه خرید" />
|
||||||
|
</MudItem>
|
||||||
|
</Filters>
|
||||||
|
<Content>
|
||||||
|
<MudGrid Class="mb-3" Spacing="2">
|
||||||
|
<MudItem xs="6" sm="4">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">تعداد</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@_totalCount.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="4">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">جمع مبلغ (ریال)</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@_totalAmount.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
<MudDataGrid @ref="_grid" T="UserPackagePurchaseModel" ServerData="LoadData" ReadOnly="true"
|
||||||
|
Hover="true" Dense="true" Height="calc(100vh - 420px)">
|
||||||
|
<Columns>
|
||||||
|
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||||
|
<TemplateColumn Title="کاربر">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudStack Spacing="0">
|
||||||
|
<MudText Typo="Typo.body2">@context.Item.UserName</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">@context.Item.UserMobile</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<PropertyColumn Property="x => x.PackageName" Title="پکیج" />
|
||||||
|
<TemplateColumn Title="روش خرید">
|
||||||
|
<CellTemplate>@ReportLabels.GetPurchaseMethodLabel(context.Item.PurchaseMethod)</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="مبلغ (ریال)">
|
||||||
|
<CellTemplate>@context.Item.Amount.ToString("N0")</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="تاریخ خرید">
|
||||||
|
<CellTemplate>@context.Item.PurchasedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<PropertyColumn Property="x => x.OrderId" Title="OrderId" />
|
||||||
|
<PropertyColumn Property="x => x.TransactionId" Title="TransactionId" />
|
||||||
|
</Columns>
|
||||||
|
<NoRecordsContent>
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||||
|
</NoRecordsContent>
|
||||||
|
<PagerContent>
|
||||||
|
<MudDataGridPager T="UserPackagePurchaseModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||||
|
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||||
|
RowsPerPageString="تعداد در صفحه" />
|
||||||
|
</PagerContent>
|
||||||
|
</MudDataGrid>
|
||||||
|
</Content>
|
||||||
|
</BasePageComponent>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private BasePageComponent? _basePage;
|
||||||
|
private MudDataGrid<UserPackagePurchaseModel>? _grid;
|
||||||
|
private long? _userId;
|
||||||
|
private int? _purchaseMethod;
|
||||||
|
private DateTime? _fromDate, _toDate;
|
||||||
|
private long _totalCount, _totalAmount;
|
||||||
|
|
||||||
|
private GetAllUserPackagePurchaseByFilterFilter BuildFilter() => new()
|
||||||
|
{
|
||||||
|
UserId = _userId > 0 ? _userId : null,
|
||||||
|
PurchaseMethod = _purchaseMethod,
|
||||||
|
PurchasedFrom = _fromDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value, DateTimeKind.Utc)) : null,
|
||||||
|
PurchasedTo = _toDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null
|
||||||
|
};
|
||||||
|
|
||||||
|
private async Task<GridData<UserPackagePurchaseModel>> LoadData(GridState<UserPackagePurchaseModel> state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var filter = BuildFilter();
|
||||||
|
var summary = await PurchaseClient.GetUserPackagePurchaseSummaryAsync(new GetUserPackagePurchaseSummaryRequest { Filter = filter });
|
||||||
|
_totalCount = summary.TotalCount;
|
||||||
|
_totalAmount = summary.TotalAmount;
|
||||||
|
|
||||||
|
var result = await PurchaseClient.GetAllUserPackagePurchaseByFilterAsync(new GetAllUserPackagePurchaseByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||||
|
Filter = filter
|
||||||
|
});
|
||||||
|
return new GridData<UserPackagePurchaseModel>
|
||||||
|
{
|
||||||
|
Items = result?.Models?.ToList() ?? new(),
|
||||||
|
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
return new GridData<UserPackagePurchaseModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFilterSubmit() { if (_grid != null) await _grid.ReloadServerData(); }
|
||||||
|
private async Task OnFilterCleared()
|
||||||
|
{
|
||||||
|
_userId = null; _purchaseMethod = null; _fromDate = _toDate = null;
|
||||||
|
if (_grid != null) await _grid.ReloadServerData();
|
||||||
|
}
|
||||||
|
}
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
@using CMSMicroservice.Protobuf.Protos.UserPackagePurchase
|
||||||
|
@using Google.Protobuf.WellKnownTypes
|
||||||
|
@using DateTimeConverterCL
|
||||||
|
|
||||||
|
@inject UserPackagePurchaseContract.UserPackagePurchaseContractClient PurchaseClient
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<TitleContent>
|
||||||
|
<MudText Typo="Typo.h6">
|
||||||
|
جزئیات خرید پکیج — @UserName
|
||||||
|
</MudText>
|
||||||
|
</TitleContent>
|
||||||
|
<DialogContent>
|
||||||
|
@if (_loading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="my-4" />
|
||||||
|
}
|
||||||
|
else if (_items.Count == 0)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true">خریدی برای این کاربر یافت نشد.</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTable Items="_items" Dense="true" Hover="true" Breakpoint="Breakpoint.Sm">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>شناسه</MudTh>
|
||||||
|
<MudTh>پکیج</MudTh>
|
||||||
|
<MudTh>روش</MudTh>
|
||||||
|
<MudTh>مبلغ (ریال)</MudTh>
|
||||||
|
<MudTh>تاریخ خرید</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd DataLabel="شناسه">@context.Id</MudTd>
|
||||||
|
<MudTd DataLabel="پکیج">@context.PackageName</MudTd>
|
||||||
|
<MudTd DataLabel="روش">
|
||||||
|
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined"
|
||||||
|
Color="@GetMethodColor(context.PurchaseMethod)">
|
||||||
|
@ReportLabels.GetPurchaseMethodLabel(context.PurchaseMethod)
|
||||||
|
</MudChip>
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="مبلغ">@context.Amount.ToString("N0")</MudTd>
|
||||||
|
<MudTd DataLabel="تاریخ">
|
||||||
|
@(context.PurchasedAt != null
|
||||||
|
? context.PurchasedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()
|
||||||
|
: "-")
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton OnClick="Close" Variant="Variant.Text">بستن</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;
|
||||||
|
[Parameter] public long UserId { get; set; }
|
||||||
|
[Parameter] public string UserName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
private bool _loading = true;
|
||||||
|
private List<UserPackagePurchaseModel> _items = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await PurchaseClient.GetAllUserPackagePurchaseByFilterAsync(
|
||||||
|
new GetAllUserPackagePurchaseByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = 1,
|
||||||
|
PageSize = 500
|
||||||
|
},
|
||||||
|
Filter = new GetAllUserPackagePurchaseByFilterFilter
|
||||||
|
{
|
||||||
|
UserId = UserId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_items = result?.Models?
|
||||||
|
.OrderBy(x => x.PurchasedAt?.ToDateTime() ?? DateTime.MinValue)
|
||||||
|
.ToList() ?? new();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در بارگذاری جزئیات: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color GetMethodColor(int method) => method switch
|
||||||
|
{
|
||||||
|
1 => Color.Info, // دایا
|
||||||
|
2 => Color.Primary, // درگاه
|
||||||
|
3 => Color.Secondary, // دستی
|
||||||
|
_ => Color.Default
|
||||||
|
};
|
||||||
|
|
||||||
|
private void Close() => MudDialog.Close();
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
@using System.Text
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Common.Utilities
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
@using BackOffice.Pages.Club.Reports.Components
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.UserPackagePurchase
|
||||||
|
@using Google.Protobuf.WellKnownTypes
|
||||||
|
@using DateTimeConverterCL
|
||||||
|
@using Microsoft.JSInterop
|
||||||
|
|
||||||
|
@inject UserPackagePurchaseContract.UserPackagePurchaseContractClient PurchaseClient
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
@inject IJSRuntime JsRuntime
|
||||||
|
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||||
|
گزارش تجمیعی مشتریان — اولین/آخرین پکیج، تفکیک دایا / دستی / درگاه. خروجی Excel مطابق فیلتر فعال است.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||||
|
<Filters>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudSelect T="int?" Clearable="true" Label="روش خرید" Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" @bind-Value="_purchaseMethod">
|
||||||
|
<MudSelectItem T="int?" Value="1">دایا</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="3">دستی</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="2">درگاه</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="4">
|
||||||
|
<DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه خرید" />
|
||||||
|
</MudItem>
|
||||||
|
</Filters>
|
||||||
|
<Content>
|
||||||
|
<MudStack Row="true" Justify="Justify.FlexEnd" Class="mb-3">
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Success"
|
||||||
|
StartIcon="@Icons.Material.Filled.FileDownload"
|
||||||
|
OnClick="ExportToExcel" Disabled="_exporting">
|
||||||
|
خروجی Excel
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudDataGrid @ref="_grid" T="CustomerPackagePurchaseRollupModel" ServerData="LoadData" ReadOnly="true"
|
||||||
|
Hover="true" Dense="true" Height="calc(100vh - 420px)">
|
||||||
|
<Columns>
|
||||||
|
<TemplateColumn Title="مشتری">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudStack Spacing="0">
|
||||||
|
<MudText Typo="Typo.body2">@context.Item.UserName</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">@context.Item.UserMobile</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="اولین پکیج">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudStack Spacing="0">
|
||||||
|
<MudText Typo="Typo.body2">@context.Item.FirstPackageName</MudText>
|
||||||
|
<MudText Typo="Typo.caption">@context.Item.FirstAmount.ToString("N0") ریال</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
@(context.Item.FirstPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="آخرین پکیج">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudStack Spacing="0">
|
||||||
|
<MudText Typo="Typo.body2">@context.Item.LastPackageName</MudText>
|
||||||
|
<MudText Typo="Typo.caption">@context.Item.LastAmount.ToString("N0") ریال</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
@(context.Item.LastPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")
|
||||||
|
</MudText>
|
||||||
|
<MudChip T="string" Size="Size.Small" Class="mt-1" Variant="Variant.Filled"
|
||||||
|
Color="@GetMethodColor(context.Item.LastPurchaseMethod)">
|
||||||
|
@ReportLabels.GetPurchaseMethodLabel(context.Item.LastPurchaseMethod)
|
||||||
|
</MudChip>
|
||||||
|
</MudStack>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="تعداد / جمع">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudStack Spacing="0">
|
||||||
|
<MudText>@context.Item.PurchaseCount خرید</MudText>
|
||||||
|
<MudText Typo="Typo.caption">@context.Item.TotalAmount.ToString("N0") ریال</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="دایا">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudText Typo="Typo.caption">@context.Item.DayaCount عدد</MudText>
|
||||||
|
<MudText Typo="Typo.caption">@context.Item.DayaAmount.ToString("N0")</MudText>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="دستی">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudText Typo="Typo.caption">@context.Item.ManualCount عدد</MudText>
|
||||||
|
<MudText Typo="Typo.caption">@context.Item.ManualAmount.ToString("N0")</MudText>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="درگاه">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudText Typo="Typo.caption">@context.Item.GatewayCount عدد</MudText>
|
||||||
|
<MudText Typo="Typo.caption">@context.Item.GatewayAmount.ToString("N0")</MudText>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="عملیات">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudButton Size="Size.Small" Variant="Variant.Outlined" Color="Color.Primary"
|
||||||
|
OnClick="() => OpenDetails(context.Item)">
|
||||||
|
جزئیات
|
||||||
|
</MudButton>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
<NoRecordsContent>
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||||
|
</NoRecordsContent>
|
||||||
|
<PagerContent>
|
||||||
|
<MudDataGridPager T="CustomerPackagePurchaseRollupModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||||
|
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||||
|
RowsPerPageString="تعداد در صفحه" />
|
||||||
|
</PagerContent>
|
||||||
|
</MudDataGrid>
|
||||||
|
</Content>
|
||||||
|
</BasePageComponent>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private BasePageComponent? _basePage;
|
||||||
|
private MudDataGrid<CustomerPackagePurchaseRollupModel>? _grid;
|
||||||
|
private long? _userId;
|
||||||
|
private int? _purchaseMethod;
|
||||||
|
private DateTime? _fromDate, _toDate;
|
||||||
|
private bool _exporting;
|
||||||
|
|
||||||
|
private GetCustomerPackagePurchaseRollupFilter BuildFilter()
|
||||||
|
{
|
||||||
|
var filter = new GetCustomerPackagePurchaseRollupFilter();
|
||||||
|
if (_userId is > 0)
|
||||||
|
filter.UserId = _userId;
|
||||||
|
if (_purchaseMethod.HasValue)
|
||||||
|
filter.PurchaseMethod = _purchaseMethod.Value;
|
||||||
|
if (_fromDate.HasValue)
|
||||||
|
filter.PurchasedFrom = Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value.Date, DateTimeKind.Utc));
|
||||||
|
if (_toDate.HasValue)
|
||||||
|
filter.PurchasedTo = Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc));
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<GridData<CustomerPackagePurchaseRollupModel>> LoadData(GridState<CustomerPackagePurchaseRollupModel> state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await PurchaseClient.GetCustomerPackagePurchaseRollupAsync(
|
||||||
|
new GetCustomerPackagePurchaseRollupRequest
|
||||||
|
{
|
||||||
|
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = state.Page + 1,
|
||||||
|
PageSize = state.PageSize
|
||||||
|
},
|
||||||
|
Filter = BuildFilter()
|
||||||
|
});
|
||||||
|
|
||||||
|
return new GridData<CustomerPackagePurchaseRollupModel>
|
||||||
|
{
|
||||||
|
Items = result?.Models?.ToList() ?? new(),
|
||||||
|
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
return new GridData<CustomerPackagePurchaseRollupModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFilterSubmit()
|
||||||
|
{
|
||||||
|
if (_grid != null) await _grid.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFilterCleared()
|
||||||
|
{
|
||||||
|
_userId = null;
|
||||||
|
_purchaseMethod = null;
|
||||||
|
_fromDate = _toDate = null;
|
||||||
|
if (_grid != null) await _grid.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenDetails(CustomerPackagePurchaseRollupModel row)
|
||||||
|
{
|
||||||
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
{ nameof(CustomerPackagePurchaseDetailsDialog.UserId), row.UserId },
|
||||||
|
{ nameof(CustomerPackagePurchaseDetailsDialog.UserName), row.UserName }
|
||||||
|
};
|
||||||
|
var options = new DialogOptions { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true };
|
||||||
|
await DialogService.ShowAsync<CustomerPackagePurchaseDetailsDialog>("جزئیات خرید پکیج", parameters, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExportToExcel()
|
||||||
|
{
|
||||||
|
_exporting = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// PageSize=0 → همهٔ ردیفهای مطابق فیلتر، مستقل از صفحهبندی گرید
|
||||||
|
var result = await PurchaseClient.GetCustomerPackagePurchaseRollupAsync(
|
||||||
|
new GetCustomerPackagePurchaseRollupRequest
|
||||||
|
{
|
||||||
|
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = 1,
|
||||||
|
PageSize = 0
|
||||||
|
},
|
||||||
|
Filter = BuildFilter()
|
||||||
|
});
|
||||||
|
|
||||||
|
var all = result?.Models?.ToList() ?? new();
|
||||||
|
if (all.Count == 0)
|
||||||
|
{
|
||||||
|
Snackbar.Add("دادهای برای خروجی با فیلتر فعلی وجود ندارد.", Severity.Info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
"شناسه کاربر", "نام مشتری", "موبایل",
|
||||||
|
"اولین پکیج", "مبلغ اولین", "تاریخ اولین",
|
||||||
|
"آخرین پکیج", "مبلغ آخرین", "تاریخ آخرین", "روش آخرین",
|
||||||
|
"تعداد خرید", "جمع مبلغ",
|
||||||
|
"تعداد دایا", "مبلغ دایا",
|
||||||
|
"تعداد دستی", "مبلغ دستی",
|
||||||
|
"تعداد درگاه", "مبلغ درگاه"
|
||||||
|
};
|
||||||
|
|
||||||
|
var rows = all.Select(o => new object?[]
|
||||||
|
{
|
||||||
|
o.UserId,
|
||||||
|
o.UserName,
|
||||||
|
o.UserMobile,
|
||||||
|
o.FirstPackageName,
|
||||||
|
o.FirstAmount,
|
||||||
|
o.FirstPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-",
|
||||||
|
o.LastPackageName,
|
||||||
|
o.LastAmount,
|
||||||
|
o.LastPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-",
|
||||||
|
ReportLabels.GetPurchaseMethodLabel(o.LastPurchaseMethod),
|
||||||
|
o.PurchaseCount,
|
||||||
|
o.TotalAmount,
|
||||||
|
o.DayaCount,
|
||||||
|
o.DayaAmount,
|
||||||
|
o.ManualCount,
|
||||||
|
o.ManualAmount,
|
||||||
|
o.GatewayCount,
|
||||||
|
o.GatewayAmount
|
||||||
|
});
|
||||||
|
|
||||||
|
var filename = $"customer-packages-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||||
|
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||||
|
CsvExportHelper.ToExcelXmlBase64("مشتریان پکیج", headers, rows));
|
||||||
|
Snackbar.Add($"خروجی Excel آماده شد ({all.Count} مشتری).", Severity.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_exporting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color GetMethodColor(int method) => method switch
|
||||||
|
{
|
||||||
|
1 => Color.Info,
|
||||||
|
2 => Color.Primary,
|
||||||
|
3 => Color.Secondary,
|
||||||
|
_ => Color.Default
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -41,11 +41,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>ارزش هر بالانس:</strong></td>
|
<td><strong>ارزش هر بالانس:</strong></td>
|
||||||
<td>@Payout.ValuePerBalance.ToString("N0") ریال</td>
|
<td>@Payout.ValuePerBalance.ToString("N0") تومان</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>مبلغ کل:</strong></td>
|
<td><strong>مبلغ کل:</strong></td>
|
||||||
<td><strong>@Payout.TotalAmount.ToString("N0") ریال</strong></td>
|
<td><strong>@Payout.TotalAmount.ToString("N0") تومان</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>وضعیت:</strong></td>
|
<td><strong>وضعیت:</strong></td>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudAlert Severity="Severity.Warning" Dense="true">
|
<MudAlert Severity="Severity.Warning" Dense="true">
|
||||||
درخواست برداشت @Amount.ToString("N0") ریال برای @UserName رد خواهد شد.
|
درخواست برداشت @Amount.ToString("N0") تومان برای @UserName رد خواهد شد.
|
||||||
</MudAlert>
|
</MudAlert>
|
||||||
<MudTextField @bind-Value="_reason"
|
<MudTextField @bind-Value="_reason"
|
||||||
Label="دلیل رد درخواست"
|
Label="دلیل رد درخواست"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>مبلغ:</strong></td>
|
<td><strong>مبلغ:</strong></td>
|
||||||
<td><MudText Color="Color.Primary"><strong>@Request.Amount.ToString("N0") ریال</strong></MudText></td>
|
<td><MudText Color="Color.Primary"><strong>@Request.Amount.ToString("N0") تومان</strong></MudText></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>روش برداشت:</strong></td>
|
<td><strong>روش برداشت:</strong></td>
|
||||||
|
|||||||
@@ -5,116 +5,59 @@
|
|||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
@using BackOffice.Pages.AutoComplete
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
|
||||||
<div>
|
<MudStack Spacing="4">
|
||||||
<MudText Typo="Typo.h4" Class="mb-4">داشبورد کمیسیون</MudText>
|
|
||||||
|
|
||||||
@if (_isLoading)
|
@* ── عنوان صفحه ── *@
|
||||||
{
|
<div>
|
||||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
<MudText Typo="Typo.h5" Class="mb-1">داشبورد کمیسیون</MudText>
|
||||||
}
|
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||||
else
|
مبلغ پورسانت هر هفته بین اعضاء تقسیم میشود. هر پکیج استخر مستقل دارد.
|
||||||
{
|
</MudText>
|
||||||
<!-- Pool Summary Section -->
|
</div>
|
||||||
<MudGrid>
|
|
||||||
<MudItem xs="12" sm="6" md="3">
|
|
||||||
<MudCard Elevation="2">
|
|
||||||
<MudCardContent>
|
|
||||||
<MudText Typo="Typo.h6" Color="Color.Primary">استخر هفتگی</MudText>
|
|
||||||
<MudText Typo="Typo.h4">@(_poolData?.TotalPoolAmount.ToString("N0") ?? "0") ریال</MudText>
|
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">@_currentWeekDisplayText</MudText>
|
|
||||||
</MudCardContent>
|
|
||||||
</MudCard>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<MudItem xs="12" sm="6" md="3">
|
|
||||||
<MudCard Elevation="2">
|
|
||||||
<MudCardContent>
|
|
||||||
<MudText Typo="Typo.h6" Color="Color.Success">تعداد تعادلها</MudText>
|
|
||||||
<MudText Typo="Typo.h4">@(_poolData?.TotalBalances.ToString("N0") ?? "0")</MudText>
|
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">مجموع تعادل های فعال</MudText>
|
|
||||||
</MudCardContent>
|
|
||||||
</MudCard>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<MudItem xs="12" sm="6" md="3">
|
|
||||||
<MudCard Elevation="2">
|
|
||||||
<MudCardContent>
|
|
||||||
<MudText Typo="Typo.h6" Color="Color.Warning">ارزش هر تعادل</MudText>
|
|
||||||
<MudText Typo="Typo.h4">@(_poolData?.ValuePerBalance.ToString("N0") ?? "0") ریال</MudText>
|
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">قیمت واحد تعادل</MudText>
|
|
||||||
</MudCardContent>
|
|
||||||
</MudCard>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<MudItem xs="12" sm="6" md="3">
|
|
||||||
<MudCard Elevation="2">
|
|
||||||
<MudCardContent>
|
|
||||||
<MudText Typo="Typo.h6" Color="@(_poolData?.IsCalculated == true ? Color.Success : Color.Error)">
|
|
||||||
وضعیت محاسبه
|
|
||||||
</MudText>
|
|
||||||
<MudText Typo="Typo.h4">
|
|
||||||
@(_poolData?.IsCalculated == true ? "محاسبه شده ✓" : "محاسبه نشده ✗")
|
|
||||||
</MudText>
|
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
|
||||||
@if (_poolData?.CalculatedAt != null)
|
|
||||||
{
|
|
||||||
var calculatedDate = _poolData.CalculatedAt.ToDateTime().ToLocalTime();
|
|
||||||
var persianDate = PersianDateTime.ConvertToPersianDateTime(calculatedDate);
|
|
||||||
@($"در تاریخ {persianDate}")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@("در انتظار محاسبه")
|
|
||||||
}
|
|
||||||
</MudText>
|
|
||||||
</MudCardContent>
|
|
||||||
</MudCard>
|
|
||||||
</MudItem>
|
|
||||||
</MudGrid>
|
|
||||||
|
|
||||||
<!-- Week Selector -->
|
|
||||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
|
||||||
<MudText Typo="Typo.h6">انتخاب هفته:</MudText>
|
|
||||||
|
|
||||||
|
@* ── فیلتر (اول از همه) ── *@
|
||||||
|
<MudPaper Elevation="1" Class="pa-4">
|
||||||
|
<MudGrid AlignItems="AlignItems.End" Spacing="3">
|
||||||
|
<MudItem xs="12" sm="5">
|
||||||
@if (_allWeeks.Any())
|
@if (_allWeeks.Any())
|
||||||
{
|
{
|
||||||
<MudSelect T="long"
|
<MudSelect T="long"
|
||||||
Value="_currentWeekDefinitionId"
|
Value="_currentWeekDefinitionId"
|
||||||
Label="شماره هفته"
|
Label="هفته"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
Margin="Margin.Dense"
|
Margin="Margin.Dense"
|
||||||
AnchorOrigin="Origin.BottomCenter"
|
AnchorOrigin="Origin.BottomLeft"
|
||||||
Style="min-width: 400px;"
|
TransformOrigin="Origin.TopLeft"
|
||||||
ValueChanged="OnWeekChanged">
|
AdornmentIcon="@Icons.Material.Filled.CalendarToday"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
ValueChanged="OnWeekChanged">
|
||||||
|
|
||||||
@if (_availableWeeks?.CurrentWeek != null)
|
@if (_availableWeeks?.CurrentWeek != null)
|
||||||
{
|
{
|
||||||
<MudSelectItem T="long" Value="@_availableWeeks.CurrentWeek.WeekDefinitionId">
|
<MudSelectItem T="long" Value="@_availableWeeks.CurrentWeek.WeekDefinitionId">
|
||||||
<MudText>
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Today" Size="Size.Small" Class="ml-2" />
|
<MudIcon Icon="@Icons.Material.Filled.Today" Size="Size.Small" Color="Color.Primary" />
|
||||||
@_availableWeeks.CurrentWeek.DisplayText
|
<span>@_availableWeeks.CurrentWeek.DisplayText</span>
|
||||||
</MudText>
|
<MudChip T="string" Size="Size.Small" Color="Color.Primary" Variant="Variant.Text">جاری</MudChip>
|
||||||
|
</MudStack>
|
||||||
</MudSelectItem>
|
</MudSelectItem>
|
||||||
<MudDivider />
|
<MudDivider />
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (_availableWeeks?.CalculatedWeeks?.Any() == true)
|
@if (_availableWeeks?.CalculatedWeeks?.Any() == true)
|
||||||
{
|
{
|
||||||
<MudListSubheader>هفتههای محاسبه شده</MudListSubheader>
|
<MudListSubheader>تسویهشده</MudListSubheader>
|
||||||
@foreach (var week in _availableWeeks.CalculatedWeeks)
|
@foreach (var week in _availableWeeks.CalculatedWeeks)
|
||||||
{
|
{
|
||||||
<MudSelectItem T="long" Value="@week.WeekDefinitionId">
|
<MudSelectItem T="long" Value="@week.WeekDefinitionId">
|
||||||
<MudText>
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small" Class="ml-2" />
|
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Size="Size.Small" Color="Color.Success" />
|
||||||
@week.DisplayText
|
<span>@week.DisplayText</span>
|
||||||
@if (week.TotalPoolAmount > 0)
|
@if (week.TotalPoolAmount > 0)
|
||||||
{
|
{
|
||||||
<MudChip T="string" Size="Size.Small" Color="Color.Success" Class="mr-2">
|
<MudText Typo="Typo.caption" Color="Color.Secondary">(@week.TotalPoolAmount.ToString("N0") T)</MudText>
|
||||||
@week.TotalPoolAmount.ToString("N0") ریال
|
|
||||||
</MudChip>
|
|
||||||
}
|
}
|
||||||
</MudText>
|
</MudStack>
|
||||||
</MudSelectItem>
|
</MudSelectItem>
|
||||||
}
|
}
|
||||||
<MudDivider />
|
<MudDivider />
|
||||||
@@ -122,14 +65,14 @@
|
|||||||
|
|
||||||
@if (_availableWeeks?.PendingWeeks?.Any() == true)
|
@if (_availableWeeks?.PendingWeeks?.Any() == true)
|
||||||
{
|
{
|
||||||
<MudListSubheader>هفتههای محاسبه نشده</MudListSubheader>
|
<MudListSubheader>در انتظار تسویه</MudListSubheader>
|
||||||
@foreach (var week in _availableWeeks.PendingWeeks)
|
@foreach (var week in _availableWeeks.PendingWeeks)
|
||||||
{
|
{
|
||||||
<MudSelectItem T="long" Value="@week.WeekDefinitionId">
|
<MudSelectItem T="long" Value="@week.WeekDefinitionId">
|
||||||
<MudText>
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.AccessTime" Color="Color.Warning" Size="Size.Small" Class="ml-2" />
|
<MudIcon Icon="@Icons.Material.Filled.AccessTime" Size="Size.Small" Color="Color.Warning" />
|
||||||
@week.DisplayText
|
<span>@week.DisplayText</span>
|
||||||
</MudText>
|
</MudStack>
|
||||||
</MudSelectItem>
|
</MudSelectItem>
|
||||||
}
|
}
|
||||||
<MudDivider />
|
<MudDivider />
|
||||||
@@ -141,10 +84,10 @@
|
|||||||
@foreach (var week in _availableWeeks.FutureWeeks)
|
@foreach (var week in _availableWeeks.FutureWeeks)
|
||||||
{
|
{
|
||||||
<MudSelectItem T="long" Value="@week.WeekDefinitionId">
|
<MudSelectItem T="long" Value="@week.WeekDefinitionId">
|
||||||
<MudText>
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.CalendarMonth" Color="Color.Info" Size="Size.Small" Class="ml-2" />
|
<MudIcon Icon="@Icons.Material.Filled.CalendarMonth" Size="Size.Small" Color="Color.Tertiary" />
|
||||||
@week.DisplayText
|
<span>@week.DisplayText</span>
|
||||||
</MudText>
|
</MudStack>
|
||||||
</MudSelectItem>
|
</MudSelectItem>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,97 +97,222 @@
|
|||||||
{
|
{
|
||||||
<MudText Color="Color.Warning">لیست هفتهها بارگذاری نشده</MudText>
|
<MudText Color="Color.Warning">لیست هفتهها بارگذاری نشده</MudText>
|
||||||
}
|
}
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudItem xs="12" sm="5">
|
||||||
Color="Color.Primary"
|
<PackageSelect Label="پکیج"
|
||||||
OnClick="LoadPoolData"
|
SelectedPackageId="_selectedPackageId"
|
||||||
StartIcon="@Icons.Material.Filled.Refresh">
|
SelectedPackageIdChanged="OnPackageChanged" />
|
||||||
بارگذاری
|
</MudItem>
|
||||||
</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
<!-- Pool Details -->
|
<MudItem xs="12" sm="2">
|
||||||
@if (_poolData != null)
|
<MudButton Variant="Variant.Filled"
|
||||||
{
|
Color="Color.Primary"
|
||||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
FullWidth="true"
|
||||||
<MudText Typo="Typo.h6" Class="mb-3">جزئیات استخر</MudText>
|
OnClick="LoadPoolData"
|
||||||
<MudSimpleTable Hover="true" Dense="true">
|
StartIcon="@Icons.Material.Filled.Search"
|
||||||
<tbody>
|
Disabled="_isLoading">
|
||||||
<tr>
|
نمایش
|
||||||
<td><strong>شناسه استخر:</strong></td>
|
</MudButton>
|
||||||
<td>@_poolData.Id</td>
|
</MudItem>
|
||||||
</tr>
|
</MudGrid>
|
||||||
<tr>
|
</MudPaper>
|
||||||
<td><strong>شماره هفته:</strong></td>
|
|
||||||
<td>@_currentWeekDisplayName</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>مجموع استخر:</strong></td>
|
|
||||||
<td>@_poolData.TotalPoolAmount.ToString("N0") ریال</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>تعداد تعادل:</strong></td>
|
|
||||||
<td>@_poolData.TotalBalances</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>ارزش هر تعادل:</strong></td>
|
|
||||||
<td>@_poolData.ValuePerBalance.ToString("N0") ریال</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>وضعیت:</strong></td>
|
|
||||||
<td>
|
|
||||||
<MudChip T="string" Color="@(_poolData.IsCalculated ? Color.Success : Color.Error)" Size="Size.Small">
|
|
||||||
@(_poolData.IsCalculated ? "محاسبه شده" : "محاسبه نشده")
|
|
||||||
</MudChip>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>تاریخ محاسبه:</strong></td>
|
|
||||||
<td>
|
|
||||||
@if (_poolData.CalculatedAt != null)
|
|
||||||
{
|
|
||||||
@_poolData.CalculatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudText Color="Color.Secondary">-</MudText>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>تاریخ ایجاد:</strong></td>
|
|
||||||
<td>@_poolData.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</MudSimpleTable>
|
|
||||||
</MudPaper>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Quick Actions -->
|
@* ── لودینگ ── *@
|
||||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
@if (_isLoading)
|
||||||
<MudText Typo="Typo.h6" Class="mb-3">عملیات سریع</MudText>
|
{
|
||||||
<MudStack Row="true" Spacing="2">
|
<MudPaper Elevation="0" Class="pa-6 d-flex justify-center align-center">
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudStack AlignItems="AlignItems.Center" Spacing="2">
|
||||||
Color="Color.Primary"
|
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Medium" />
|
||||||
StartIcon="@Icons.Material.Filled.Payments"
|
<MudText Typo="Typo.body2" Color="Color.Secondary">در حال بارگذاری اطلاعات استخر…</MudText>
|
||||||
Href="/commission/payouts">
|
|
||||||
مشاهده پرداختها
|
|
||||||
</MudButton>
|
|
||||||
<MudButton Variant="Variant.Filled"
|
|
||||||
Color="Color.Secondary"
|
|
||||||
StartIcon="@Icons.Material.Filled.AccountBalance"
|
|
||||||
Href="/commission/withdrawals">
|
|
||||||
درخواستهای برداشت
|
|
||||||
</MudButton>
|
|
||||||
<MudButton Variant="Variant.Outlined"
|
|
||||||
Color="Color.Info"
|
|
||||||
StartIcon="@Icons.Material.Filled.Calculate"
|
|
||||||
Disabled="@(_poolData?.IsCalculated == true)"
|
|
||||||
OnClick="TriggerCalculation">
|
|
||||||
اجرای محاسبه دستی
|
|
||||||
</MudButton>
|
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
}
|
}
|
||||||
</div>
|
else
|
||||||
|
{
|
||||||
|
@* ── نوار وضعیت هفته / پکیج ── *@
|
||||||
|
<MudPaper Elevation="0" Class="pa-3 mud-background-gray rounded">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Wrap="Wrap.Wrap">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.DateRange" Color="Color.Default" Size="Size.Small" />
|
||||||
|
<MudText Typo="Typo.body1">@_currentWeekDisplayText</MudText>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(_poolData?.PackageTitle))
|
||||||
|
{
|
||||||
|
<MudDivider Vertical="true" FlexItem="true" />
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Primary" Icon="@Icons.Material.Filled.WorkspacePremium">
|
||||||
|
@_poolData.PackageTitle
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudSpacer />
|
||||||
|
|
||||||
|
@if (_poolData != null && _poolData.Id > 0)
|
||||||
|
{
|
||||||
|
@if (_poolData.IsCalculated)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Success" Size="Size.Small"
|
||||||
|
Icon="@Icons.Material.Filled.CheckCircle" Variant="Variant.Filled">
|
||||||
|
تسویه انجام شده
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Warning" Size="Size.Small"
|
||||||
|
Icon="@Icons.Material.Filled.Pending" Variant="Variant.Filled">
|
||||||
|
در انتظار تسویه
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@* ── ۳ کارت اصلی ── *@
|
||||||
|
<MudGrid Spacing="3">
|
||||||
|
@* کارت ۱: کل مبلغ استخر *@
|
||||||
|
<MudItem xs="12" sm="4">
|
||||||
|
<MudPaper Elevation="2" Class="pa-4" Style="border-top: 4px solid var(--mud-palette-primary);">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Color="Color.Primary" Size="Size.Small" />
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">کل مبلغ استخر پورسانت</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudText Typo="Typo.h5" Style="font-weight:700; direction:ltr; text-align:right;">
|
||||||
|
@((_poolData?.TotalPoolAmount ?? 0).ToString("N0"))
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">تومان — جمع کارمزد فعالسازیهای این هفته</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
@* کارت ۲: تعداد واحدهای پورسانت *@
|
||||||
|
<MudItem xs="12" sm="4">
|
||||||
|
<MudPaper Elevation="2" Class="pa-4" Style="border-top: 4px solid var(--mud-palette-success);">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.People" Color="Color.Success" Size="Size.Small" />
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">واحدهای پورسانت (بالانس)</MudText>
|
||||||
|
<MudTooltip Text="تعداد واحدهایی که پورسانت به آنها تعلق میگیرد. هر عضو بر اساس شبکهاش چند واحد دریافت میکند.">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.HelpOutline" Size="Size.Small" Color="Color.Tertiary" />
|
||||||
|
</MudTooltip>
|
||||||
|
</MudStack>
|
||||||
|
<MudText Typo="Typo.h5" Style="font-weight:700; direction:ltr; text-align:right;">
|
||||||
|
@((_poolData?.TotalBalances ?? 0).ToString("N0"))
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">واحد — مجموع همه اعضاء فعال</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
@* کارت ۳: سهم هر واحد *@
|
||||||
|
<MudItem xs="12" sm="4">
|
||||||
|
<MudPaper Elevation="2" Class="pa-4" Style="border-top: 4px solid var(--mud-palette-warning);">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.LocalAtm" Color="Color.Warning" Size="Size.Small" />
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">سهم پورسانت هر واحد</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudText Typo="Typo.h5" Style="font-weight:700; direction:ltr; text-align:right;">
|
||||||
|
@((_poolData?.ValuePerBalance ?? 0).ToString("N0"))
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
تومان — @((_poolData?.IsCalculated == true) ? "قطعی شده" : "قطعی نشده")
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
@* ── اعتبارسنجی مبلغ استخر ── *@
|
||||||
|
@if (_poolData != null && _poolData.Id > 0)
|
||||||
|
{
|
||||||
|
@if (!_poolData.ValidationIsValid)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Error" Variant="Variant.Filled" Icon="@Icons.Material.Filled.ReportProblem" NoIcon="false">
|
||||||
|
<MudText Typo="Typo.subtitle2" Class="mb-2">مبلغ استخر با تعداد فعالسازیهای دیتابیس مطابقت ندارد</MudText>
|
||||||
|
<MudGrid Spacing="2">
|
||||||
|
<MudItem xs="12" sm="4">
|
||||||
|
<MudText Typo="Typo.caption">مبلغ ثبتشده</MudText>
|
||||||
|
<MudText Typo="Typo.body2"><strong>@_poolData.TotalPoolAmount.ToString("N0") تومان</strong></MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="4">
|
||||||
|
<MudText Typo="Typo.caption">مبلغ انتظاری (@_poolData.ValidationActivationCount فعالسازی)</MudText>
|
||||||
|
<MudText Typo="Typo.body2"><strong>@_poolData.ValidationExpectedAmount.ToString("N0") تومان</strong></MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="4">
|
||||||
|
<MudText Typo="Typo.caption">اختلاف</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Style="color:var(--mud-palette-warning-lighten);">
|
||||||
|
<strong>@_poolData.ValidationDiscrepancy.ToString("N0") تومان</strong>
|
||||||
|
</MudText>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else if (_poolData.ValidationActivationCount > 0)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Success" Variant="Variant.Text" Dense="true" Icon="@Icons.Material.Filled.VerifiedUser">
|
||||||
|
مبلغ استخر صحیح است — با @_poolData.ValidationActivationCount فعالسازی در این هفته مطابقت دارد.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@* ── جزئیات تکمیلی (بدون تکرار اطلاعات کارتها) ── *@
|
||||||
|
@if (_poolData != null && _poolData.Id > 0)
|
||||||
|
{
|
||||||
|
<MudExpansionPanels Elevation="1">
|
||||||
|
<MudExpansionPanel Text="جزئیات بیشتر" Icon="@Icons.Material.Filled.ExpandMore">
|
||||||
|
<MudSimpleTable Dense="true" Hover="true" Style="max-width:540px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width:200px;"><MudText Typo="Typo.body2" Color="Color.Secondary">شناسه استخر</MudText></td>
|
||||||
|
<td><MudText Typo="Typo.body2">#@_poolData.Id</MudText></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><MudText Typo="Typo.body2" Color="Color.Secondary">تاریخ ایجاد استخر</MudText></td>
|
||||||
|
<td><MudText Typo="Typo.body2">@_poolData.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</MudText></td>
|
||||||
|
</tr>
|
||||||
|
@if (_poolData.CalculatedAt != null)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td><MudText Typo="Typo.body2" Color="Color.Secondary">تاریخ تسویه</MudText></td>
|
||||||
|
<td><MudText Typo="Typo.body2">@_poolData.CalculatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</MudText></td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</MudSimpleTable>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
</MudExpansionPanels>
|
||||||
|
}
|
||||||
|
|
||||||
|
@* ── دکمههای عملیاتی ── *@
|
||||||
|
<MudPaper Elevation="0" Class="pa-3 mud-background-gray rounded">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Wrap="Wrap.Wrap">
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.Payments"
|
||||||
|
Href="/commission/payouts"
|
||||||
|
Size="Size.Medium">
|
||||||
|
پرداختهای این هفته
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Variant="Variant.Outlined"
|
||||||
|
Color="Color.Default"
|
||||||
|
StartIcon="@Icons.Material.Filled.AccountBalance"
|
||||||
|
Href="/commission/withdrawals"
|
||||||
|
Size="Size.Medium">
|
||||||
|
درخواستهای برداشت
|
||||||
|
</MudButton>
|
||||||
|
<MudSpacer />
|
||||||
|
@if (_poolData?.IsCalculated != true)
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Warning"
|
||||||
|
StartIcon="@Icons.Material.Filled.Calculate"
|
||||||
|
OnClick="TriggerCalculation"
|
||||||
|
Size="Size.Medium">
|
||||||
|
اجرای تسویه دستی
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
</MudStack>
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ public partial class Dashboard
|
|||||||
private string _currentWeekDisplayText = string.Empty; // نمایش فارسی
|
private string _currentWeekDisplayText = string.Empty; // نمایش فارسی
|
||||||
private GetWeeklyCommissionPoolResponse? _poolData;
|
private GetWeeklyCommissionPoolResponse? _poolData;
|
||||||
|
|
||||||
|
// Package filter
|
||||||
|
private long? _selectedPackageId;
|
||||||
|
|
||||||
// Available weeks data
|
// Available weeks data
|
||||||
private CMSMicroservice.Protobuf.Protos.Commission.GetAvailableWeeksResponse? _availableWeeks;
|
private CMSMicroservice.Protobuf.Protos.Commission.GetAvailableWeeksResponse? _availableWeeks;
|
||||||
private List<CMSMicroservice.Protobuf.Protos.Commission.WeekInfo> _allWeeks = new();
|
private List<CMSMicroservice.Protobuf.Protos.Commission.WeekInfo> _allWeeks = new();
|
||||||
@@ -91,6 +94,12 @@ public partial class Dashboard
|
|||||||
await LoadPoolData();
|
await LoadPoolData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnPackageChanged(long? packageId)
|
||||||
|
{
|
||||||
|
_selectedPackageId = packageId;
|
||||||
|
await LoadPoolData();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadPoolData()
|
private async Task LoadPoolData()
|
||||||
{
|
{
|
||||||
if (_currentWeekDefinitionId <= 0)
|
if (_currentWeekDefinitionId <= 0)
|
||||||
@@ -106,7 +115,8 @@ public partial class Dashboard
|
|||||||
|
|
||||||
var request = new GetWeeklyCommissionPoolRequest
|
var request = new GetWeeklyCommissionPoolRequest
|
||||||
{
|
{
|
||||||
WeekDefinitionId = _currentWeekDefinitionId
|
WeekDefinitionId = _currentWeekDefinitionId,
|
||||||
|
PackageId = _selectedPackageId
|
||||||
};
|
};
|
||||||
|
|
||||||
_poolData = await CommissionContract.GetWeeklyCommissionPoolAsync(request);
|
_poolData = await CommissionContract.GetWeeklyCommissionPoolAsync(request);
|
||||||
@@ -126,24 +136,41 @@ public partial class Dashboard
|
|||||||
|
|
||||||
private async Task TriggerCalculation()
|
private async Task TriggerCalculation()
|
||||||
{
|
{
|
||||||
|
var packageInfo = _selectedPackageId.HasValue
|
||||||
|
? $" (فقط پکیج #{_selectedPackageId})"
|
||||||
|
: " (همه پکیجها)";
|
||||||
|
|
||||||
var confirmed = await DialogService.ShowMessageBox(
|
var confirmed = await DialogService.ShowMessageBox(
|
||||||
"تایید",
|
"تایید",
|
||||||
$"آیا از اجرای محاسبه دستی برای هفته {_currentWeekDisplayName} مطمئن هستید؟",
|
$"آیا از اجرای محاسبه دستی برای هفته {_currentWeekDisplayName}{packageInfo} مطمئن هستید؟",
|
||||||
yesText: "بله", cancelText: "خیر");
|
yesText: "بله", cancelText: "خیر");
|
||||||
|
|
||||||
if (confirmed == true)
|
if (confirmed == true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await CommissionContract.TriggerWeeklyCalculationAsync(new TriggerWeeklyCalculationRequest()
|
var req = new TriggerWeeklyCalculationRequest()
|
||||||
{
|
{
|
||||||
WeekDefinitionId = _currentWeekDefinitionId,
|
WeekDefinitionId = _currentWeekDefinitionId,
|
||||||
ForceRecalculate = true,
|
ForceRecalculate = true,
|
||||||
SkipBalances = false,
|
SkipBalances = false,
|
||||||
SkipPayouts = false,
|
SkipPayouts = false,
|
||||||
SkipPool = false,
|
SkipPool = false,
|
||||||
});
|
};
|
||||||
Snackbar.Add("محاسبه با موفقیت آغاز شد. این عملیات ممکن است چند دقیقه طول بکشد.", Severity.Info);
|
|
||||||
|
req.PackageId = _selectedPackageId;
|
||||||
|
|
||||||
|
var response = await CommissionContract.TriggerWeeklyCalculationAsync(req);
|
||||||
|
|
||||||
|
if (response.Success)
|
||||||
|
{
|
||||||
|
Snackbar.Add("محاسبه با موفقیت انجام شد.", Severity.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در محاسبه: {response.Message}", Severity.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reload data after a delay
|
// Reload data after a delay
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ public class WithdrawalRequestModel
|
|||||||
public string? BankTrackingCode { get; set; }
|
public string? BankTrackingCode { get; set; }
|
||||||
public string? PaymentFailureReason { get; set; }
|
public string? PaymentFailureReason { get; set; }
|
||||||
public string? IbanNumber { get; set; }
|
public string? IbanNumber { get; set; }
|
||||||
|
public string? PackageName { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.h4" Class="mb-4"> پرداخت های کاربران</MudText>
|
<MudText Typo="Typo.h4" Class="mb-4"> پرداخت های کاربران</MudText>
|
||||||
|
|
||||||
|
@* ── G11b: راهنمای پرداختها (Q28) ── *@
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||||
|
پاداشها بر اساس هر پکیج جداگانه محاسبه میشود. ستون «پکیج» نشاندهنده پکیج منبع پاداش است.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
<MudDataGrid T="UserCommissionPayoutModel"
|
<MudDataGrid T="UserCommissionPayoutModel"
|
||||||
ServerData="@(new Func<GridState<UserCommissionPayoutModel>, Task<GridData<UserCommissionPayoutModel>>>(ServerReload))"
|
ServerData="@(new Func<GridState<UserCommissionPayoutModel>, Task<GridData<UserCommissionPayoutModel>>>(ServerReload))"
|
||||||
Hover="true"
|
Hover="true"
|
||||||
@@ -36,12 +41,29 @@
|
|||||||
<MudSelectItem T="int?" Value="@((int?)4)">شکست خورده</MudSelectItem>
|
<MudSelectItem T="int?" Value="@((int?)4)">شکست خورده</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="@((int?)5)">لغو شده</MudSelectItem>
|
<MudSelectItem T="int?" Value="@((int?)5)">لغو شده</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
|
<PackageSelect Label="پکیج"
|
||||||
|
@bind-SelectedPackageId="_filterPackageId" />
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled"
|
||||||
Color="Color.Primary"
|
Color="Color.Primary"
|
||||||
OnClick="ApplyFilter"
|
OnClick="ApplyFilter"
|
||||||
StartIcon="@Icons.Material.Filled.Search">
|
StartIcon="@Icons.Material.Filled.Search">
|
||||||
جستجو
|
جستجو
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Success"
|
||||||
|
StartIcon="@Icons.Material.Filled.AccountBalanceWallet"
|
||||||
|
Disabled="@(!_filterWeekDefinitionId.HasValue || _isCreditingWallets)"
|
||||||
|
OnClick="BulkCreditWallets">
|
||||||
|
@if (_isCreditingWallets)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="ms-n1 me-2" />
|
||||||
|
<span>در حال واریز...</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>واریز به کیف پول</span>
|
||||||
|
}
|
||||||
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</ToolBarContent>
|
</ToolBarContent>
|
||||||
<Columns>
|
<Columns>
|
||||||
@@ -64,6 +86,14 @@
|
|||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
|
|
||||||
|
<PropertyColumn Property="x => x.PackageTitle" Title="پکیج">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudChip T="string" Color="Color.Tertiary" Size="Size.Small">
|
||||||
|
@(string.IsNullOrEmpty(context.Item.PackageTitle) ? "-" : context.Item.PackageTitle)
|
||||||
|
</MudChip>
|
||||||
|
</CellTemplate>
|
||||||
|
</PropertyColumn>
|
||||||
|
|
||||||
<PropertyColumn Property="x => x.BalancesEarned" Title="بالانسها">
|
<PropertyColumn Property="x => x.BalancesEarned" Title="بالانسها">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ public partial class UserPayouts
|
|||||||
private long? _filterUserId;
|
private long? _filterUserId;
|
||||||
private long? _filterWeekDefinitionId;
|
private long? _filterWeekDefinitionId;
|
||||||
private int? _filterStatus;
|
private int? _filterStatus;
|
||||||
|
private long? _filterPackageId;
|
||||||
|
private bool _isCreditingWallets;
|
||||||
|
|
||||||
private async Task<GridData<UserCommissionPayoutModel>> ServerReload(GridState<UserCommissionPayoutModel> state)
|
private async Task<GridData<UserCommissionPayoutModel>> ServerReload(GridState<UserCommissionPayoutModel> state)
|
||||||
{
|
{
|
||||||
@@ -44,6 +46,11 @@ public partial class UserPayouts
|
|||||||
request.Status = _filterStatus.Value;
|
request.Status = _filterStatus.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_filterPackageId.HasValue && _filterPackageId.Value > 0)
|
||||||
|
{
|
||||||
|
request.PackageId = _filterPackageId.Value;
|
||||||
|
}
|
||||||
|
|
||||||
var result = await CommissionContract.GetUserCommissionPayoutsAsync(request);
|
var result = await CommissionContract.GetUserCommissionPayoutsAsync(request);
|
||||||
|
|
||||||
if (result?.Models != null && result.Models.Any())
|
if (result?.Models != null && result.Models.Any())
|
||||||
@@ -119,7 +126,7 @@ public partial class UserPayouts
|
|||||||
{
|
{
|
||||||
var confirmed = await DialogService.ShowMessageBox(
|
var confirmed = await DialogService.ShowMessageBox(
|
||||||
"تایید برداشت",
|
"تایید برداشت",
|
||||||
$"آیا از تایید برداشت برای کاربر {payout.UserName} (مبلغ: {payout.TotalAmount:N0} ریال) مطمئن هستید؟",
|
$"آیا از تایید برداشت برای کاربر {payout.UserName} (مبلغ: {payout.TotalAmount:N0} تومان) مطمئن هستید؟",
|
||||||
yesText: "تایید", cancelText: "لغو");
|
yesText: "تایید", cancelText: "لغو");
|
||||||
|
|
||||||
if (confirmed == true)
|
if (confirmed == true)
|
||||||
@@ -141,6 +148,50 @@ public partial class UserPayouts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task BulkCreditWallets()
|
||||||
|
{
|
||||||
|
if (!_filterWeekDefinitionId.HasValue)
|
||||||
|
{
|
||||||
|
Snackbar.Add("لطفاً ابتدا یک هفته انتخاب کنید", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var confirmed = await DialogService.ShowMessageBox(
|
||||||
|
"واریز دستهجمعی به کیف پول",
|
||||||
|
$"آیا از واریز کمیسیون همه پرداختهای «در انتظار» هفته انتخابشده به کیف پول کاربران مطمئن هستید؟ این عملیات برگشتپذیر نیست.",
|
||||||
|
yesText: "بله، واریز کن", cancelText: "لغو");
|
||||||
|
|
||||||
|
if (confirmed != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isCreditingWallets = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await CommissionContract.BulkCreditPayoutsAsync(new BulkCreditPayoutsRequest
|
||||||
|
{
|
||||||
|
WeekDefinitionId = _filterWeekDefinitionId.Value
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.CreditedCount == 0)
|
||||||
|
Snackbar.Add("هیچ پرداخت در انتظاری برای این هفته یافت نشد", Severity.Info);
|
||||||
|
else
|
||||||
|
Snackbar.Add(result.Message, Severity.Success);
|
||||||
|
|
||||||
|
await ApplyFilter();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در واریز: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isCreditingWallets = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RejectWithdrawal(UserCommissionPayoutModel payout)
|
private async Task RejectWithdrawal(UserCommissionPayoutModel payout)
|
||||||
{
|
{
|
||||||
var reason = await DialogService.ShowMessageBox(
|
var reason = await DialogService.ShowMessageBox(
|
||||||
|
|||||||
@@ -67,13 +67,13 @@
|
|||||||
<PropertyColumn Property="x => x.WeekDisplayName" Title="هفته" />
|
<PropertyColumn Property="x => x.WeekDisplayName" Title="هفته" />
|
||||||
<PropertyColumn Property="x => x.TotalPoolAmount" Title="مبلغ کل استخر" Format="N0">
|
<PropertyColumn Property="x => x.TotalPoolAmount" Title="مبلغ کل استخر" Format="N0">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudText>@context.Item.TotalPoolAmount.ToString("N0") ریال</MudText>
|
<MudText>@context.Item.TotalPoolAmount.ToString("N0") تومان</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="x => x.TotalBalances" Title="مجموع موجودیها" />
|
<PropertyColumn Property="x => x.TotalBalances" Title="مجموع موجودیها" />
|
||||||
<PropertyColumn Property="x => x.ValuePerBalance" Title="ارزش هر موجودی" Format="N0">
|
<PropertyColumn Property="x => x.ValuePerBalance" Title="ارزش هر موجودی" Format="N0">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudText>@context.Item.ValuePerBalance.ToString("N0") ریال</MudText>
|
<MudText>@context.Item.ValuePerBalance.ToString("N0") تومان</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="x => x.IsCalculated" Title="وضعیت">
|
<PropertyColumn Property="x => x.IsCalculated" Title="وضعیت">
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||||
<MudText Typo="Typo.h6" Color="Color.Primary">مجموع استخرها</MudText>
|
<MudText Typo="Typo.h6" Color="Color.Primary">مجموع استخرها</MudText>
|
||||||
<MudText Typo="Typo.h4">@_totalPoolSum.ToString("N0")</MudText>
|
<MudText Typo="Typo.h4">@_totalPoolSum.ToString("N0")</MudText>
|
||||||
<MudText Typo="Typo.caption" Color="Color.Secondary">ریال</MudText>
|
<MudText Typo="Typo.caption" Color="Color.Secondary">تومان</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="3">
|
<MudItem xs="12" md="3">
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||||
<MudText Typo="Typo.h6" Color="Color.Info">میانگین ارزش</MudText>
|
<MudText Typo="Typo.h6" Color="Color.Info">میانگین ارزش</MudText>
|
||||||
<MudText Typo="Typo.h4">@_averageValue.ToString("N0")</MudText>
|
<MudText Typo="Typo.h4">@_averageValue.ToString("N0")</MudText>
|
||||||
<MudText Typo="Typo.caption" Color="Color.Secondary">ریال</MudText>
|
<MudText Typo="Typo.caption" Color="Color.Secondary">تومان</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
Margin="Margin.Dense">
|
Margin="Margin.Dense">
|
||||||
<MudSelectItem T="int?" Value="@(null as int?)">همه وضعیتها</MudSelectItem>
|
<MudSelectItem T="int?" Value="@(null as int?)">همه وضعیتها</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="0">در انتظار واریز به کیف پول</MudSelectItem>
|
<MudSelectItem T="int?" Value="0">در انتظار واریز به کیف پول</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="1">واریز شده به کیف پول طلایی</MudSelectItem>
|
<MudSelectItem T="int?" Value="1">واریز شده به پاداش های دریافتی</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="2">درخواست برداشت ثبت شده</MudSelectItem>
|
<MudSelectItem T="int?" Value="2">درخواست برداشت ثبت شده</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="3">برداشت انجام شده</MudSelectItem>
|
<MudSelectItem T="int?" Value="3">برداشت انجام شده</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="4">خطا در پرداخت بانکی</MudSelectItem>
|
<MudSelectItem T="int?" Value="4">خطا در پرداخت بانکی</MudSelectItem>
|
||||||
@@ -107,19 +107,19 @@
|
|||||||
<MudItem xs="12" md="3">
|
<MudItem xs="12" md="3">
|
||||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||||
<MudText Typo="Typo.subtitle2" Color="Color.Primary">مجموع مبلغ برداشتها</MudText>
|
<MudText Typo="Typo.subtitle2" Color="Color.Primary">مجموع مبلغ برداشتها</MudText>
|
||||||
<MudText Typo="Typo.h5">@_summary.TotalAmount.ToString("N0") ریال</MudText>
|
<MudText Typo="Typo.h5">@_summary.TotalAmount.ToString("N0") تومان</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="3">
|
<MudItem xs="12" md="3">
|
||||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||||
<MudText Typo="Typo.subtitle2" Color="Color.Success">مبلغ پرداخت شده</MudText>
|
<MudText Typo="Typo.subtitle2" Color="Color.Success">مبلغ پرداخت شده</MudText>
|
||||||
<MudText Typo="Typo.h5">@_summary.TotalPaid.ToString("N0") ریال</MudText>
|
<MudText Typo="Typo.h5">@_summary.TotalPaid.ToString("N0") تومان</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="3">
|
<MudItem xs="12" md="3">
|
||||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||||
<MudText Typo="Typo.subtitle2" Color="Color.Warning">مبلغ در انتظار</MudText>
|
<MudText Typo="Typo.subtitle2" Color="Color.Warning">مبلغ در انتظار</MudText>
|
||||||
<MudText Typo="Typo.h5">@_summary.TotalPending.ToString("N0") ریال</MudText>
|
<MudText Typo="Typo.h5">@_summary.TotalPending.ToString("N0") تومان</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="3">
|
<MudItem xs="12" md="3">
|
||||||
@@ -189,17 +189,17 @@
|
|||||||
<PropertyColumn Property="x => x.PendingCount" Title="در انتظار" />
|
<PropertyColumn Property="x => x.PendingCount" Title="در انتظار" />
|
||||||
<PropertyColumn Property="x => x.TotalAmount" Title="مبلغ کل" Format="N0">
|
<PropertyColumn Property="x => x.TotalAmount" Title="مبلغ کل" Format="N0">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.TotalAmount.ToString("N0") ریال
|
@context.Item.TotalAmount.ToString("N0") تومان
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="x => x.PaidAmount" Title="پرداخت شده" Format="N0">
|
<PropertyColumn Property="x => x.PaidAmount" Title="پرداخت شده" Format="N0">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.PaidAmount.ToString("N0") ریال
|
@context.Item.PaidAmount.ToString("N0") تومان
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="x => x.PendingAmount" Title="در انتظار پرداخت" Format="N0">
|
<PropertyColumn Property="x => x.PendingAmount" Title="در انتظار پرداخت" Format="N0">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.PendingAmount.ToString("N0") ریال
|
@context.Item.PendingAmount.ToString("N0") تومان
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
</Columns>
|
</Columns>
|
||||||
@@ -431,9 +431,9 @@
|
|||||||
|
|
||||||
sb.AppendLine("خلاصه:");
|
sb.AppendLine("خلاصه:");
|
||||||
sb.AppendLine($"تعداد کل درخواستها: {_summary.TotalRequests}");
|
sb.AppendLine($"تعداد کل درخواستها: {_summary.TotalRequests}");
|
||||||
sb.AppendLine($"مبلغ کل برداشتها: {_summary.TotalAmount:N0} ریال");
|
sb.AppendLine($"مبلغ کل برداشتها: {_summary.TotalAmount:N0} تومان");
|
||||||
sb.AppendLine($"مبلغ پرداخت شده: {_summary.TotalPaid:N0} ریال");
|
sb.AppendLine($"مبلغ پرداخت شده: {_summary.TotalPaid:N0} تومان");
|
||||||
sb.AppendLine($"مبلغ در انتظار: {_summary.TotalPending:N0} ریال");
|
sb.AppendLine($"مبلغ در انتظار: {_summary.TotalPending:N0} تومان");
|
||||||
sb.AppendLine($"تعداد کاربران یکتا: {_summary.UniqueUsers}");
|
sb.AppendLine($"تعداد کاربران یکتا: {_summary.UniqueUsers}");
|
||||||
sb.AppendLine($"نرخ موفقیت: {_summary.SuccessRate:N2}%");
|
sb.AppendLine($"نرخ موفقیت: {_summary.SuccessRate:N2}%");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
|
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudText Typo="Typo.body2" Color="Color.Primary">
|
<MudText Typo="Typo.body2" Color="Color.Primary">
|
||||||
<strong>@context.Item.Amount.ToString("N0") ریال</strong>
|
<strong>@context.Item.Amount.ToString("N0") تومان</strong>
|
||||||
</MudText>
|
</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ public partial class WithdrawalRequests
|
|||||||
BankReferenceId = grpcModel.BankReferenceId,
|
BankReferenceId = grpcModel.BankReferenceId,
|
||||||
BankTrackingCode = grpcModel.BankTrackingCode,
|
BankTrackingCode = grpcModel.BankTrackingCode,
|
||||||
PaymentFailureReason = grpcModel.PaymentFailureReason,
|
PaymentFailureReason = grpcModel.PaymentFailureReason,
|
||||||
IbanNumber = grpcModel.IbanNumber
|
IbanNumber = grpcModel.IbanNumber,
|
||||||
|
PackageName = grpcModel.PackageName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +164,7 @@ public partial class WithdrawalRequests
|
|||||||
{
|
{
|
||||||
var confirmed = await DialogService.ShowMessageBox(
|
var confirmed = await DialogService.ShowMessageBox(
|
||||||
"تایید درخواست",
|
"تایید درخواست",
|
||||||
$"آیا از تایید درخواست برداشت {request.Amount:N0} ریال برای {request.UserName} مطمئن هستید؟",
|
$"آیا از تایید درخواست برداشت {request.Amount:N0} تومان برای {request.UserName} مطمئن هستید؟",
|
||||||
yesText: "تایید", cancelText: "لغو");
|
yesText: "تایید", cancelText: "لغو");
|
||||||
|
|
||||||
if (confirmed == true)
|
if (confirmed == true)
|
||||||
@@ -222,7 +223,7 @@ public partial class WithdrawalRequests
|
|||||||
{
|
{
|
||||||
var confirmed = await DialogService.ShowMessageBox(
|
var confirmed = await DialogService.ShowMessageBox(
|
||||||
"پردازش پرداخت",
|
"پردازش پرداخت",
|
||||||
$"آیا پرداخت {request.Amount:N0} ریال به {request.UserName} انجام شده است؟",
|
$"آیا پرداخت {request.Amount:N0} تومان به {request.UserName} انجام شده است؟",
|
||||||
yesText: "بله، پردازش شد", cancelText: "لغو");
|
yesText: "بله، پردازش شد", cancelText: "لغو");
|
||||||
|
|
||||||
if (confirmed == true)
|
if (confirmed == true)
|
||||||
@@ -269,7 +270,7 @@ public partial class WithdrawalRequests
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("شناسه,کاربر,نام کاربر,مبلغ,وضعیت,روش,شماره شبا,تاریخ درخواست,تاریخ پردازش,شماره مرجع بانکی");
|
sb.AppendLine("شناسه,کاربر,نام کاربر,پکیج,مبلغ,وضعیت,روش,شماره شبا,تاریخ درخواست,تاریخ پردازش,شماره مرجع بانکی");
|
||||||
|
|
||||||
foreach (var r in result.Models)
|
foreach (var r in result.Models)
|
||||||
{
|
{
|
||||||
@@ -278,6 +279,7 @@ public partial class WithdrawalRequests
|
|||||||
mapped.Id,
|
mapped.Id,
|
||||||
mapped.UserId,
|
mapped.UserId,
|
||||||
EscapeCsv(mapped.UserName),
|
EscapeCsv(mapped.UserName),
|
||||||
|
EscapeCsv(mapped.PackageName ?? "-"),
|
||||||
mapped.Amount,
|
mapped.Amount,
|
||||||
EscapeCsv(GetStatusText(mapped.Status)),
|
EscapeCsv(GetStatusText(mapped.Status)),
|
||||||
EscapeCsv(GetMethodText(mapped.Method)),
|
EscapeCsv(GetMethodText(mapped.Method)),
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
@using BackOffice.Pages.Content.Components.SettingsModels
|
||||||
|
|
||||||
|
<MudExpansionPanels MultiExpansion="true" Elevation="0">
|
||||||
|
|
||||||
|
@* ── Vision ── *@
|
||||||
|
<MudExpansionPanel Text="چشمانداز" Icon="@Icons.Material.Filled.Visibility" IsInitiallyExpanded="true">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.VisionTitle"
|
||||||
|
Label="عنوان"
|
||||||
|
Placeholder="چشمانداز"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
<MudTextField @bind-Value="Model.VisionText"
|
||||||
|
Label="متن چشمانداز"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="3" />
|
||||||
|
<MudTextField @bind-Value="Model.VisionIcon"
|
||||||
|
Label="نام آیکون"
|
||||||
|
Placeholder="Visibility"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
HelperText="نام آیکون از مجموعه Material Icons" />
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Mission ── *@
|
||||||
|
<MudExpansionPanel Text="مأموریت" Icon="@Icons.Material.Filled.TrackChanges" IsInitiallyExpanded="true">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.MissionTitle"
|
||||||
|
Label="عنوان"
|
||||||
|
Placeholder="مأموریت"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
<MudTextField @bind-Value="Model.MissionText"
|
||||||
|
Label="متن مأموریت"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="3" />
|
||||||
|
<MudTextField @bind-Value="Model.MissionIcon"
|
||||||
|
Label="نام آیکون"
|
||||||
|
Placeholder="TrackChanges"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
HelperText="نام آیکون از مجموعه Material Icons" />
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Section Titles ── *@
|
||||||
|
<MudExpansionPanel Text="عناوین بخشها" Icon="@Icons.Material.Filled.Title">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.ValuesTitle"
|
||||||
|
Label="عنوان بخش ارزشها"
|
||||||
|
Placeholder="ارزشهای ما"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
<MudTextField @bind-Value="Model.TeamTitle"
|
||||||
|
Label="عنوان بخش تیم"
|
||||||
|
Placeholder="تیم ما"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Hint ── *@
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mt-2">
|
||||||
|
آیتمهای ارزشها و اعضای تیم از بخش «مدیریت تصاویر» قابل ویرایش هستند.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
</MudExpansionPanels>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public AboutSettingsModel Model { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
@using BackOffice.Pages.Content.Components.SettingsModels
|
||||||
|
|
||||||
|
<MudExpansionPanels MultiExpansion="true" Elevation="0">
|
||||||
|
|
||||||
|
@* ── Contact Info ── *@
|
||||||
|
<MudExpansionPanel Text="اطلاعات تماس" Icon="@Icons.Material.Filled.ContactPhone" IsInitiallyExpanded="true">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.Address"
|
||||||
|
Label="آدرس"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="2"
|
||||||
|
Placeholder="کرج مهرویلا میدان مادر..." />
|
||||||
|
<MudGrid Spacing="2">
|
||||||
|
<MudItem xs="12" md="6">
|
||||||
|
<MudTextField @bind-Value="Model.Phone"
|
||||||
|
Label="شماره تلفن"
|
||||||
|
Placeholder="026-34233563"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" md="6">
|
||||||
|
<MudTextField @bind-Value="Model.Email"
|
||||||
|
Label="ایمیل"
|
||||||
|
Placeholder="info@kbs1.co"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
<MudTextField @bind-Value="Model.WorkingHours"
|
||||||
|
Label="ساعات کاری"
|
||||||
|
Placeholder="شنبه تا پنجشنبه — ۹ صبح تا ۶ عصر"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Social Media ── *@
|
||||||
|
<MudExpansionPanel Text="شبکههای اجتماعی" Icon="@Icons.Material.Filled.Share" IsInitiallyExpanded="true">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.TelegramUrl"
|
||||||
|
Label="تلگرام"
|
||||||
|
Placeholder="https://t.me/kbs1"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Custom.Brands.Telegram" />
|
||||||
|
<MudTextField @bind-Value="Model.InstagramUrl"
|
||||||
|
Label="اینستاگرام"
|
||||||
|
Placeholder="https://instagram.com/kbs1"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Custom.Brands.Instagram" />
|
||||||
|
<MudTextField @bind-Value="Model.LinkedinUrl"
|
||||||
|
Label="لینکدین"
|
||||||
|
Placeholder="https://linkedin.com/company/kbs1"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Custom.Brands.LinkedIn" />
|
||||||
|
<MudTextField @bind-Value="Model.WhatsappUrl"
|
||||||
|
Label="واتساپ"
|
||||||
|
Placeholder="https://wa.me/989123456789"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Custom.Brands.WhatsApp" />
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
</MudExpansionPanels>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public ContactSettingsModel Model { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
@using BackOffice.Pages.Content.Components.SettingsModels
|
||||||
|
|
||||||
|
<MudExpansionPanels MultiExpansion="true" Elevation="0">
|
||||||
|
|
||||||
|
@* ── Hero Buttons ── *@
|
||||||
|
<MudExpansionPanel Text="دکمههای هیرو" Icon="@Icons.Material.Filled.TouchApp" IsInitiallyExpanded="true">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.HeroButtonPrimaryText"
|
||||||
|
Label="متن دکمه اصلی"
|
||||||
|
Placeholder="شروع رایگان"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
<MudTextField @bind-Value="Model.HeroButtonSecondaryText"
|
||||||
|
Label="متن دکمه ثانویه"
|
||||||
|
Placeholder="آخرین اخبار"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Trust Badges ── *@
|
||||||
|
<MudExpansionPanel Text="@($"نشانهای اعتماد ({Model.TrustBadges.Count})")" Icon="@Icons.Material.Filled.Verified">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
@for (var i = 0; i < Model.TrustBadges.Count; i++)
|
||||||
|
{
|
||||||
|
var index = i;
|
||||||
|
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
|
||||||
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="min-width:20px;">@(index + 1)</MudText>
|
||||||
|
<MudTextField @bind-Value="Model.TrustBadges[index].IconName"
|
||||||
|
Label="نام آیکون"
|
||||||
|
Placeholder="Timer"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:1;" />
|
||||||
|
<MudTextField @bind-Value="Model.TrustBadges[index].Text"
|
||||||
|
Label="متن"
|
||||||
|
Placeholder="ثبتنام زیر ۲ دقیقه"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:2;" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Color="Color.Error"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="() => Model.TrustBadges.RemoveAt(index)" />
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Variant="Variant.Text"
|
||||||
|
OnClick="() => Model.TrustBadges.Add(new TrustBadgeModel())">
|
||||||
|
افزودن نشان
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Steps ── *@
|
||||||
|
<MudExpansionPanel Text="@($"مراحل ({Model.Steps.Count})")" Icon="@Icons.Material.Filled.FormatListNumbered">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.StepsTitle"
|
||||||
|
Label="عنوان بخش مراحل"
|
||||||
|
Placeholder="سه گام تا شروع"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
@for (var i = 0; i < Model.Steps.Count; i++)
|
||||||
|
{
|
||||||
|
var index = i;
|
||||||
|
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
|
<MudAvatar Size="Size.Small" Color="Color.Primary">
|
||||||
|
<MudText Typo="Typo.caption" Style="color:#fff;">@(index + 1)</MudText>
|
||||||
|
</MudAvatar>
|
||||||
|
<MudTextField @bind-Value="Model.Steps[index].Title"
|
||||||
|
Label="عنوان مرحله"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:1;" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Color="Color.Error"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="() => Model.Steps.RemoveAt(index)" />
|
||||||
|
</MudStack>
|
||||||
|
<MudTextField @bind-Value="Model.Steps[index].Description"
|
||||||
|
Label="توضیحات"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Lines="2" />
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Variant="Variant.Text"
|
||||||
|
OnClick="() => Model.Steps.Add(new StepModel())">
|
||||||
|
افزودن مرحله
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Features ── *@
|
||||||
|
<MudExpansionPanel Text="@($"ویژگیها ({Model.Features.Count})")" Icon="@Icons.Material.Filled.Star">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.FeaturesTitle"
|
||||||
|
Label="عنوان بخش ویژگیها"
|
||||||
|
Placeholder="چرا کارا بازار سلامت؟"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
@for (var i = 0; i < Model.Features.Count; i++)
|
||||||
|
{
|
||||||
|
var index = i;
|
||||||
|
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="min-width:20px;">@(index + 1)</MudText>
|
||||||
|
<MudTextField @bind-Value="Model.Features[index].IconName"
|
||||||
|
Label="نام آیکون"
|
||||||
|
Placeholder="VerifiedUser"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:1;" />
|
||||||
|
<MudTextField @bind-Value="Model.Features[index].Title"
|
||||||
|
Label="عنوان"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:2;" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Color="Color.Error"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="() => Model.Features.RemoveAt(index)" />
|
||||||
|
</MudStack>
|
||||||
|
<MudTextField @bind-Value="Model.Features[index].Description"
|
||||||
|
Label="توضیحات"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Lines="2" />
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Variant="Variant.Text"
|
||||||
|
OnClick="() => Model.Features.Add(new FeatureModel())">
|
||||||
|
افزودن ویژگی
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Stats ── *@
|
||||||
|
<MudExpansionPanel Text="@($"آمار ({Model.Stats.Count})")" Icon="@Icons.Material.Filled.BarChart">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.StatsTitle"
|
||||||
|
Label="عنوان بخش آمار"
|
||||||
|
Placeholder="آمار پلتفرم"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
@for (var i = 0; i < Model.Stats.Count; i++)
|
||||||
|
{
|
||||||
|
var index = i;
|
||||||
|
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
|
||||||
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="min-width:20px;">@(index + 1)</MudText>
|
||||||
|
<MudTextField @bind-Value="Model.Stats[index].Label"
|
||||||
|
Label="عنوان"
|
||||||
|
Placeholder="رشد میانگین تیم"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:2;" />
|
||||||
|
<MudNumericField @bind-Value="Model.Stats[index].Value"
|
||||||
|
Label="مقدار"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:1;" />
|
||||||
|
<MudTextField @bind-Value="Model.Stats[index].Suffix"
|
||||||
|
Label="پسوند"
|
||||||
|
Placeholder="%+"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:1;" />
|
||||||
|
<MudSelect @bind-Value="Model.Stats[index].Color"
|
||||||
|
Label="رنگ"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:1;">
|
||||||
|
<MudSelectItem Value="@("Primary")">آبی</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@("Secondary")">بنفش</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@("Success")">سبز</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@("Warning")">زرد</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@("Error")">قرمز</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@("Info")">آبی روشن</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Color="Color.Error"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="() => Model.Stats.RemoveAt(index)" />
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Variant="Variant.Text"
|
||||||
|
OnClick="() => Model.Stats.Add(new StatModel())">
|
||||||
|
افزودن آمار
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Testimonials ── *@
|
||||||
|
<MudExpansionPanel Text="@($"نظرات مشتریان ({Model.Testimonials.Count})")" Icon="@Icons.Material.Filled.FormatQuote">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.TestimonialsTitle"
|
||||||
|
Label="عنوان بخش نظرات"
|
||||||
|
Placeholder="اعتماد مشتریان"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
@for (var i = 0; i < Model.Testimonials.Count; i++)
|
||||||
|
{
|
||||||
|
var index = i;
|
||||||
|
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="min-width:20px;">@(index + 1)</MudText>
|
||||||
|
<MudTextField @bind-Value="Model.Testimonials[index].Name"
|
||||||
|
Label="نام"
|
||||||
|
Placeholder="شرکت سینا نت"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:1;" />
|
||||||
|
<MudTextField @bind-Value="Model.Testimonials[index].Role"
|
||||||
|
Label="سمت"
|
||||||
|
Placeholder="مدیر عملیات"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:1;" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Color="Color.Error"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="() => Model.Testimonials.RemoveAt(index)" />
|
||||||
|
</MudStack>
|
||||||
|
<MudTextField @bind-Value="Model.Testimonials[index].Quote"
|
||||||
|
Label="متن نظر"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Lines="2" />
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Variant="Variant.Text"
|
||||||
|
OnClick="() => Model.Testimonials.Add(new TestimonialModel())">
|
||||||
|
افزودن نظر
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── FAQs ── *@
|
||||||
|
<MudExpansionPanel Text="@($"سوالات متداول ({Model.Faqs.Count})")" Icon="@Icons.Material.Filled.QuestionAnswer">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.FaqTitle"
|
||||||
|
Label="عنوان بخش سوالات"
|
||||||
|
Placeholder="سوالات متداول"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
@for (var i = 0; i < Model.Faqs.Count; i++)
|
||||||
|
{
|
||||||
|
var index = i;
|
||||||
|
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="min-width:20px;">@(index + 1)</MudText>
|
||||||
|
<MudTextField @bind-Value="Model.Faqs[index].Question"
|
||||||
|
Label="سوال"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Style="flex:1;" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Color="Color.Error"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="() => Model.Faqs.RemoveAt(index)" />
|
||||||
|
</MudStack>
|
||||||
|
<MudTextField @bind-Value="Model.Faqs[index].Answer"
|
||||||
|
Label="پاسخ"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Lines="2" />
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Variant="Variant.Text"
|
||||||
|
OnClick="() => Model.Faqs.Add(new FaqModel())">
|
||||||
|
افزودن سوال
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── CTA Section ── *@
|
||||||
|
<MudExpansionPanel Text="بنر فراخوان (CTA)" Icon="@Icons.Material.Filled.Campaign">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.CtaTitle"
|
||||||
|
Label="عنوان CTA"
|
||||||
|
Placeholder="آماده شروع هستید؟"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
<MudTextField @bind-Value="Model.CtaDescription"
|
||||||
|
Label="توضیحات CTA"
|
||||||
|
Placeholder="همین الان ثبتنام کنید..."
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="2" />
|
||||||
|
<MudTextField @bind-Value="Model.CtaButtonText"
|
||||||
|
Label="متن دکمه CTA"
|
||||||
|
Placeholder="شروع رایگان"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Blog Section ── *@
|
||||||
|
<MudExpansionPanel Text="بخش بلاگ" Icon="@Icons.Material.Filled.Article">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudSwitch T="bool" @bind-Value="_featuredBlogEnabled" Color="Color.Primary" />
|
||||||
|
<MudText Typo="Typo.body2">نمایش آخرین مطالب بلاگ</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
</MudExpansionPanels>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public LandingSettingsModel Model { get; set; } = new();
|
||||||
|
|
||||||
|
private bool _featuredBlogEnabled
|
||||||
|
{
|
||||||
|
get => Model.FeaturedBlogEnabled ?? true;
|
||||||
|
set => Model.FeaturedBlogEnabled = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
@using BackOffice.Pages.Content.Components.SettingsModels
|
||||||
|
|
||||||
|
<MudExpansionPanels MultiExpansion="true" Elevation="0">
|
||||||
|
|
||||||
|
@* ── Page Settings ── *@
|
||||||
|
<MudExpansionPanel Text="تنظیمات صفحه" Icon="@Icons.Material.Filled.Settings" IsInitiallyExpanded="true">
|
||||||
|
<MudStack Spacing="2" Class="pa-2">
|
||||||
|
<MudTextField @bind-Value="Model.PageDescription"
|
||||||
|
Label="توضیحات صفحه"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="3"
|
||||||
|
Placeholder="مجوزها و نمادهای اعتماد الکترونیکی ما..." />
|
||||||
|
<MudSelect @bind-Value="Model.DisplayStyle"
|
||||||
|
Label="نوع نمایش"
|
||||||
|
Variant="Variant.Outlined">
|
||||||
|
<MudSelectItem Value="@("grid")">گرید (Grid)</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@("list")">لیست (List)</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@("carousel")">اسلایدر (Carousel)</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
</MudStack>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
|
||||||
|
@* ── Hint ── *@
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mt-2">
|
||||||
|
تصاویر مجوزها از بخش «مدیریت تصاویر» قابل افزودن و ویرایش هستند.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
</MudExpansionPanels>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public LicensesSettingsModel Model { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
@using BackOffice.Services.Content
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudForm @ref="_form" @bind-IsValid="@_isValid">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
|
||||||
|
<MudSelect T="string" @bind-Value="Model.ImageGroup"
|
||||||
|
Label="گروه تصویر"
|
||||||
|
Required="true"
|
||||||
|
Variant="Variant.Outlined">
|
||||||
|
@foreach (var group in GetAvailableGroups())
|
||||||
|
{
|
||||||
|
<MudSelectItem T="string" Value="@group.Key">@group.Value</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.Title"
|
||||||
|
Label="عنوان"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.Subtitle"
|
||||||
|
Label="زیرعنوان"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.Description"
|
||||||
|
Label="توضیحات"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="3" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.LinkUrl"
|
||||||
|
Label="لینک"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Placeholder="https://..." />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.IconName"
|
||||||
|
Label="نام آیکون"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
HelperText="نام آیکون Material Design — مثلاً: Verified, Star, Group" />
|
||||||
|
|
||||||
|
<MudNumericField @bind-Value="Model.SortOrder"
|
||||||
|
Label="ترتیب نمایش"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Min="0" />
|
||||||
|
|
||||||
|
<MudText Typo="Typo.subtitle2">تصویر</MudText>
|
||||||
|
<MudStack Justify="Justify.Center" AlignItems="AlignItems.Center">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_imagePreview))
|
||||||
|
{
|
||||||
|
<Image Src="@_imagePreview" Width="200" Height="150" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(ExistingImagePath))
|
||||||
|
{
|
||||||
|
<Image Src="@ExistingImagePath" Width="200" Height="150" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudPaper Class="d-flex align-center justify-center" Style="width:200px;height:150px;">
|
||||||
|
<MudText Typo="Typo.caption">تصویری انتخاب نشده است</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnImageSelected">
|
||||||
|
<ActivatorContent>
|
||||||
|
<MudButton HtmlTag="label"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
ButtonType="ButtonType.Button"
|
||||||
|
StartIcon="@Icons.Material.Filled.Image">
|
||||||
|
انتخاب تصویر
|
||||||
|
</MudButton>
|
||||||
|
</ActivatorContent>
|
||||||
|
<SelectedTemplate>
|
||||||
|
@if (context != null)
|
||||||
|
{
|
||||||
|
<MudText Class="mt-1" Typo="Typo.caption">@context.Name</MudText>
|
||||||
|
}
|
||||||
|
</SelectedTemplate>
|
||||||
|
</MudFileUpload>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudSwitch T="bool" @bind-Value="Model.IsActive" Color="Color.Primary" />
|
||||||
|
<MudText Typo="Typo.body2">فعال</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudForm>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="SubmitAsync"
|
||||||
|
Disabled="@(!_isValid || _loading)">
|
||||||
|
@(IsNew ? "افزودن" : "ذخیره تغییرات")
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Color="Color.Default" Variant="Variant.Text" OnClick="Cancel">
|
||||||
|
انصراف
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using BackOffice.Services.Content;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Content.Components;
|
||||||
|
|
||||||
|
public partial class PageImageEditDialog
|
||||||
|
{
|
||||||
|
[CascadingParameter] IMudDialogInstance DialogInstance { get; set; } = default!;
|
||||||
|
[Inject] public ISitePageSettingsService PageSettingsService { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public SavePageImageDto Model { get; set; } = new();
|
||||||
|
[Parameter] public string PageKey { get; set; } = string.Empty;
|
||||||
|
[Parameter] public bool IsNew { get; set; }
|
||||||
|
[Parameter] public string? ExistingImagePath { get; set; }
|
||||||
|
|
||||||
|
private MudForm? _form;
|
||||||
|
private bool _isValid = true;
|
||||||
|
private bool _loading;
|
||||||
|
|
||||||
|
private IBrowserFile? _imageFile;
|
||||||
|
private byte[]? _imageBuffer;
|
||||||
|
private string? _imagePreview;
|
||||||
|
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
|
||||||
|
|
||||||
|
private async Task OnImageSelected(IBrowserFile? file)
|
||||||
|
{
|
||||||
|
_imageFile = file;
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
await file.OpenReadStream(_maxAllowedSize).CopyToAsync(ms);
|
||||||
|
_imageBuffer = ms.ToArray();
|
||||||
|
_imagePreview = $"data:{file.ContentType};base64," + Convert.ToBase64String(_imageBuffer);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SubmitAsync()
|
||||||
|
{
|
||||||
|
if (_form != null)
|
||||||
|
{
|
||||||
|
await _form.Validate();
|
||||||
|
if (!_isValid) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loading = true;
|
||||||
|
|
||||||
|
if (_imageFile != null && _imageBuffer != null)
|
||||||
|
{
|
||||||
|
Model.ImageFile = _imageBuffer;
|
||||||
|
Model.ImageMime = _imageFile.ContentType;
|
||||||
|
Model.ImageFileName = Path.GetFileNameWithoutExtension(_imageFile.Name);
|
||||||
|
}
|
||||||
|
else if (IsNew && _imageBuffer == null)
|
||||||
|
{
|
||||||
|
Snackbar.Add("لطفاً یک تصویر انتخاب کنید", Severity.Warning);
|
||||||
|
_loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await PageSettingsService.SaveImageAsync(Model);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
DialogInstance.Close(DialogResult.Ok(true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در ذخیره تصویر", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, string> GetAvailableGroups() => PageKey switch
|
||||||
|
{
|
||||||
|
"about" => new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "team", "اعضای تیم" },
|
||||||
|
{ "values", "ارزشها" }
|
||||||
|
},
|
||||||
|
"licenses" => new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "licenses", "مجوزها" }
|
||||||
|
},
|
||||||
|
"landing" => new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "values", "ارزشها" }
|
||||||
|
},
|
||||||
|
_ => new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "values", "ارزشها" },
|
||||||
|
{ "team", "اعضای تیم" },
|
||||||
|
{ "licenses", "مجوزها" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void Cancel()
|
||||||
|
{
|
||||||
|
DialogInstance.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
@using BackOffice.Services.Content
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
|
<MudText Typo="Typo.subtitle1">تصاویر صفحه «@PageTitle»</MudText>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
OnClick="AddImage">
|
||||||
|
افزودن تصویر
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
@if (!Images.Any())
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">
|
||||||
|
تصویری ثبت نشده است.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudDataGrid T="PageImageItemDto"
|
||||||
|
Items="Images"
|
||||||
|
Hover="true"
|
||||||
|
Dense="true"
|
||||||
|
Elevation="0">
|
||||||
|
<Columns>
|
||||||
|
<TemplateColumn Title="تصویر" Sortable="false">
|
||||||
|
<CellTemplate>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(context.Item.ThumbnailPath))
|
||||||
|
{
|
||||||
|
<Image Src="@context.Item.ThumbnailPath" Width="48" Height="48" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(context.Item.ImagePath))
|
||||||
|
{
|
||||||
|
<Image Src="@context.Item.ImagePath" Width="48" Height="48" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.ImageNotSupported" Size="Size.Medium" />
|
||||||
|
}
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<PropertyColumn Property="x => x.ImageGroup" Title="گروه" />
|
||||||
|
<PropertyColumn Property="x => x.Title" Title="عنوان" />
|
||||||
|
<PropertyColumn Property="x => x.SortOrder" Title="ترتیب" />
|
||||||
|
<PropertyColumn Property="x => x.IsActive" Title="وضعیت">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudChip T="string"
|
||||||
|
Color="@(context.Item.IsActive ? Color.Success : Color.Error)"
|
||||||
|
Size="Size.Small">
|
||||||
|
@(context.Item.IsActive ? "فعال" : "غیرفعال")
|
||||||
|
</MudChip>
|
||||||
|
</CellTemplate>
|
||||||
|
</PropertyColumn>
|
||||||
|
<TemplateColumn Title="عملیات" Sortable="false">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudStack Row="true" Spacing="1">
|
||||||
|
<MudTooltip Text="ویرایش">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="Color.Primary"
|
||||||
|
OnClick="@(() => EditImage(context.Item))" />
|
||||||
|
</MudTooltip>
|
||||||
|
<MudTooltip Text="حذف">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="Color.Error"
|
||||||
|
OnClick="@(() => DeleteImage(context.Item))" />
|
||||||
|
</MudTooltip>
|
||||||
|
</MudStack>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
</MudDataGrid>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton Color="Color.Default" Variant="Variant.Text" OnClick="Close">
|
||||||
|
بستن
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using BackOffice.Services.Content;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Content.Components;
|
||||||
|
|
||||||
|
public partial class PageImagesDialog
|
||||||
|
{
|
||||||
|
[CascadingParameter] IMudDialogInstance DialogInstance { get; set; } = default!;
|
||||||
|
[Inject] public ISitePageSettingsService PageSettingsService { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public long PageId { get; set; }
|
||||||
|
[Parameter] public string PageKey { get; set; } = string.Empty;
|
||||||
|
[Parameter] public string PageTitle { get; set; } = string.Empty;
|
||||||
|
[Parameter] public List<PageImageItemDto> Images { get; set; } = new();
|
||||||
|
|
||||||
|
private bool _hasChanges;
|
||||||
|
|
||||||
|
private async Task AddImage()
|
||||||
|
{
|
||||||
|
var dto = new SavePageImageDto
|
||||||
|
{
|
||||||
|
SitePageSettingsId = PageId,
|
||||||
|
ImageGroup = GetDefaultImageGroup(),
|
||||||
|
IsActive = true,
|
||||||
|
SortOrder = Images.Count + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<PageImageEditDialog>
|
||||||
|
{
|
||||||
|
{ nameof(PageImageEditDialog.Model), dto },
|
||||||
|
{ nameof(PageImageEditDialog.PageKey), PageKey },
|
||||||
|
{ nameof(PageImageEditDialog.IsNew), true }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<PageImageEditDialog>(
|
||||||
|
"افزودن تصویر", parameters,
|
||||||
|
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true });
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
Snackbar.Add("تصویر با موفقیت اضافه شد", Severity.Success);
|
||||||
|
_hasChanges = true;
|
||||||
|
await ReloadImages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EditImage(PageImageItemDto item)
|
||||||
|
{
|
||||||
|
var dto = new SavePageImageDto
|
||||||
|
{
|
||||||
|
Id = item.Id,
|
||||||
|
SitePageSettingsId = PageId,
|
||||||
|
ImageGroup = item.ImageGroup,
|
||||||
|
Title = item.Title,
|
||||||
|
Subtitle = item.Subtitle,
|
||||||
|
Description = item.Description,
|
||||||
|
LinkUrl = item.LinkUrl,
|
||||||
|
IconName = item.IconName,
|
||||||
|
SortOrder = item.SortOrder,
|
||||||
|
IsActive = item.IsActive
|
||||||
|
};
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<PageImageEditDialog>
|
||||||
|
{
|
||||||
|
{ nameof(PageImageEditDialog.Model), dto },
|
||||||
|
{ nameof(PageImageEditDialog.PageKey), PageKey },
|
||||||
|
{ nameof(PageImageEditDialog.IsNew), false },
|
||||||
|
{ nameof(PageImageEditDialog.ExistingImagePath), item.ImagePath }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<PageImageEditDialog>(
|
||||||
|
"ویرایش تصویر", parameters,
|
||||||
|
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true });
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
Snackbar.Add("تصویر با موفقیت ویرایش شد", Severity.Success);
|
||||||
|
_hasChanges = true;
|
||||||
|
await ReloadImages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteImage(PageImageItemDto item)
|
||||||
|
{
|
||||||
|
var confirm = await DialogService.ShowMessageBox(
|
||||||
|
"تایید حذف",
|
||||||
|
$"آیا از حذف تصویر «{item.Title ?? item.ImageGroup}» اطمینان دارید؟",
|
||||||
|
yesText: "بله، حذف شود",
|
||||||
|
cancelText: "انصراف");
|
||||||
|
|
||||||
|
if (confirm != true) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await PageSettingsService.DeleteImageAsync(item.Id);
|
||||||
|
Snackbar.Add("تصویر با موفقیت حذف شد", Severity.Success);
|
||||||
|
_hasChanges = true;
|
||||||
|
await ReloadImages();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در حذف تصویر: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReloadImages()
|
||||||
|
{
|
||||||
|
var details = await PageSettingsService.GetByKeyAsync(PageKey);
|
||||||
|
if (details != null)
|
||||||
|
{
|
||||||
|
Images = details.Images;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDefaultImageGroup() => PageKey switch
|
||||||
|
{
|
||||||
|
"about" => "team",
|
||||||
|
"licenses" => "licenses",
|
||||||
|
_ => "values"
|
||||||
|
};
|
||||||
|
|
||||||
|
private void Close()
|
||||||
|
{
|
||||||
|
if (_hasChanges)
|
||||||
|
DialogInstance.Close(DialogResult.Ok(true));
|
||||||
|
else
|
||||||
|
DialogInstance.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
@using BackOffice.Services.Content
|
||||||
|
@using BackOffice.Pages.Content.Components.SettingsModels
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudForm @ref="_form" @bind-IsValid="@_isValid">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudTextField @bind-Value="Model.Title"
|
||||||
|
Label="عنوان صفحه"
|
||||||
|
Required="true"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.MetaDescription"
|
||||||
|
Label="توضیحات متا (SEO)"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="2" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.HeroTitle"
|
||||||
|
Label="عنوان هیرو"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="Model.HeroSubtitle"
|
||||||
|
Label="زیرعنوان هیرو"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="2" />
|
||||||
|
|
||||||
|
<MudText Typo="Typo.subtitle2">تصویر هیرو</MudText>
|
||||||
|
<MudStack Justify="Justify.Center" AlignItems="AlignItems.Center">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_imagePreview))
|
||||||
|
{
|
||||||
|
<Image Src="@_imagePreview" Width="280" Height="160" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(ExistingHeroImagePath))
|
||||||
|
{
|
||||||
|
<Image Src="@ExistingHeroImagePath" Width="280" Height="160" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudPaper Class="d-flex align-center justify-center" Style="width:280px;height:160px;">
|
||||||
|
<MudText Typo="Typo.caption">تصویری انتخاب نشده است</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnImageSelected">
|
||||||
|
<ActivatorContent>
|
||||||
|
<MudButton HtmlTag="label"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
ButtonType="ButtonType.Button"
|
||||||
|
StartIcon="@Icons.Material.Filled.Image">
|
||||||
|
انتخاب تصویر هیرو
|
||||||
|
</MudButton>
|
||||||
|
</ActivatorContent>
|
||||||
|
<SelectedTemplate>
|
||||||
|
@if (context != null)
|
||||||
|
{
|
||||||
|
<MudText Class="mt-1" Typo="Typo.caption">@context.Name</MudText>
|
||||||
|
}
|
||||||
|
</SelectedTemplate>
|
||||||
|
</MudFileUpload>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudDivider Class="my-2" />
|
||||||
|
|
||||||
|
<MudText Typo="Typo.subtitle1" Class="mb-1">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Tune" Size="Size.Small" Class="ml-1" />
|
||||||
|
تنظیمات اختصاصی «@PageTitle»
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
@* ── Typed Settings Editors ── *@
|
||||||
|
@switch (Model.PageKey)
|
||||||
|
{
|
||||||
|
case "landing":
|
||||||
|
<LandingSettingsEditor Model="_landingSettings" />
|
||||||
|
break;
|
||||||
|
case "about":
|
||||||
|
<AboutSettingsEditor Model="_aboutSettings" />
|
||||||
|
break;
|
||||||
|
case "contact":
|
||||||
|
<ContactSettingsEditor Model="_contactSettings" />
|
||||||
|
break;
|
||||||
|
case "licenses":
|
||||||
|
<LicensesSettingsEditor Model="_licensesSettings" />
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
<MudTextField @bind-Value="Model.SettingsJson"
|
||||||
|
Label="تنظیمات JSON"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="8"
|
||||||
|
HelperText="تنظیمات اختصاصی صفحه (JSON)" />
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudSwitch T="bool" @bind-Value="Model.IsActive" Color="Color.Primary" />
|
||||||
|
<MudText Typo="Typo.body2">فعال</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudForm>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="SubmitAsync" Disabled="@(!_isValid || _loading)">
|
||||||
|
ذخیره تغییرات
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Color="Color.Default" Variant="Variant.Text" OnClick="Cancel">
|
||||||
|
انصراف
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using BackOffice.Pages.Content.Components.SettingsModels;
|
||||||
|
using BackOffice.Services.Content;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Content.Components;
|
||||||
|
|
||||||
|
public partial class PageSettingsEditDialog
|
||||||
|
{
|
||||||
|
[CascadingParameter] IMudDialogInstance DialogInstance { get; set; } = default!;
|
||||||
|
[Inject] public ISitePageSettingsService PageSettingsService { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public SavePageSettingsDto Model { get; set; } = new();
|
||||||
|
[Parameter] public string? ExistingHeroImagePath { get; set; }
|
||||||
|
[Parameter] public string PageTitle { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
private MudForm? _form;
|
||||||
|
private bool _isValid = true;
|
||||||
|
private bool _loading;
|
||||||
|
|
||||||
|
private IBrowserFile? _imageFile;
|
||||||
|
private byte[]? _imageBuffer;
|
||||||
|
private string? _imagePreview;
|
||||||
|
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
|
||||||
|
|
||||||
|
// ── Typed settings models ──
|
||||||
|
private LandingSettingsModel _landingSettings = new();
|
||||||
|
private AboutSettingsModel _aboutSettings = new();
|
||||||
|
private ContactSettingsModel _contactSettings = new();
|
||||||
|
private LicensesSettingsModel _licensesSettings = new();
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
WriteIndented = true,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
DeserializeSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse existing SettingsJson into the typed model based on PageKey
|
||||||
|
/// </summary>
|
||||||
|
private void DeserializeSettings()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(Model.SettingsJson)) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (Model.PageKey)
|
||||||
|
{
|
||||||
|
case "landing":
|
||||||
|
_landingSettings = JsonSerializer.Deserialize<LandingSettingsModel>(Model.SettingsJson, JsonOptions) ?? new();
|
||||||
|
break;
|
||||||
|
case "about":
|
||||||
|
_aboutSettings = JsonSerializer.Deserialize<AboutSettingsModel>(Model.SettingsJson, JsonOptions) ?? new();
|
||||||
|
break;
|
||||||
|
case "contact":
|
||||||
|
_contactSettings = JsonSerializer.Deserialize<ContactSettingsModel>(Model.SettingsJson, JsonOptions) ?? new();
|
||||||
|
break;
|
||||||
|
case "licenses":
|
||||||
|
_licensesSettings = JsonSerializer.Deserialize<LicensesSettingsModel>(Model.SettingsJson, JsonOptions) ?? new();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If JSON is malformed, start with empty model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize the typed model back to JSON before saving
|
||||||
|
/// </summary>
|
||||||
|
private void SerializeSettings()
|
||||||
|
{
|
||||||
|
Model.SettingsJson = Model.PageKey switch
|
||||||
|
{
|
||||||
|
"landing" => JsonSerializer.Serialize(_landingSettings, JsonOptions),
|
||||||
|
"about" => JsonSerializer.Serialize(_aboutSettings, JsonOptions),
|
||||||
|
"contact" => JsonSerializer.Serialize(_contactSettings, JsonOptions),
|
||||||
|
"licenses" => JsonSerializer.Serialize(_licensesSettings, JsonOptions),
|
||||||
|
_ => Model.SettingsJson // keep raw JSON for unknown pages
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnImageSelected(IBrowserFile? file)
|
||||||
|
{
|
||||||
|
_imageFile = file;
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
await file.OpenReadStream(_maxAllowedSize).CopyToAsync(ms);
|
||||||
|
_imageBuffer = ms.ToArray();
|
||||||
|
_imagePreview = $"data:{file.ContentType};base64," + Convert.ToBase64String(_imageBuffer);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SubmitAsync()
|
||||||
|
{
|
||||||
|
if (_form != null)
|
||||||
|
{
|
||||||
|
await _form.Validate();
|
||||||
|
if (!_isValid) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loading = true;
|
||||||
|
|
||||||
|
// Serialize typed model → JSON before saving
|
||||||
|
SerializeSettings();
|
||||||
|
|
||||||
|
if (_imageFile != null && _imageBuffer != null)
|
||||||
|
{
|
||||||
|
Model.ImageFile = _imageBuffer;
|
||||||
|
Model.ImageMime = _imageFile.ContentType;
|
||||||
|
Model.ImageFileName = Path.GetFileNameWithoutExtension(_imageFile.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await PageSettingsService.SaveSettingsAsync(Model);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
DialogInstance.Close(DialogResult.Ok(true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در ذخیره تنظیمات", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel()
|
||||||
|
{
|
||||||
|
DialogInstance.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
namespace BackOffice.Pages.Content.Components.SettingsModels;
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// LANDING PAGE
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class LandingSettingsModel
|
||||||
|
{
|
||||||
|
public string? HeroButtonPrimaryText { get; set; }
|
||||||
|
public string? HeroButtonSecondaryText { get; set; }
|
||||||
|
public List<TrustBadgeModel> TrustBadges { get; set; } = new();
|
||||||
|
public string? StepsTitle { get; set; }
|
||||||
|
public List<StepModel> Steps { get; set; } = new();
|
||||||
|
public string? FeaturesTitle { get; set; }
|
||||||
|
public List<FeatureModel> Features { get; set; } = new();
|
||||||
|
public string? StatsTitle { get; set; }
|
||||||
|
public List<StatModel> Stats { get; set; } = new();
|
||||||
|
public string? TestimonialsTitle { get; set; }
|
||||||
|
public List<TestimonialModel> Testimonials { get; set; } = new();
|
||||||
|
public string? FaqTitle { get; set; }
|
||||||
|
public List<FaqModel> Faqs { get; set; } = new();
|
||||||
|
public string? CtaTitle { get; set; }
|
||||||
|
public string? CtaDescription { get; set; }
|
||||||
|
public string? CtaButtonText { get; set; }
|
||||||
|
public bool? FeaturedBlogEnabled { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TrustBadgeModel
|
||||||
|
{
|
||||||
|
public string? IconName { get; set; }
|
||||||
|
public string? Text { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StepModel
|
||||||
|
{
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FeatureModel
|
||||||
|
{
|
||||||
|
public string? IconName { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatModel
|
||||||
|
{
|
||||||
|
public string? Label { get; set; }
|
||||||
|
public double Value { get; set; }
|
||||||
|
public string? Suffix { get; set; }
|
||||||
|
public string? Color { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestimonialModel
|
||||||
|
{
|
||||||
|
public string? Quote { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Role { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FaqModel
|
||||||
|
{
|
||||||
|
public string? Question { get; set; }
|
||||||
|
public string? Answer { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// ABOUT PAGE
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class AboutSettingsModel
|
||||||
|
{
|
||||||
|
public string? VisionTitle { get; set; }
|
||||||
|
public string? VisionText { get; set; }
|
||||||
|
public string? VisionIcon { get; set; }
|
||||||
|
public string? MissionTitle { get; set; }
|
||||||
|
public string? MissionText { get; set; }
|
||||||
|
public string? MissionIcon { get; set; }
|
||||||
|
public string? ValuesTitle { get; set; }
|
||||||
|
public string? TeamTitle { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// CONTACT PAGE
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class ContactSettingsModel
|
||||||
|
{
|
||||||
|
public string? Address { get; set; }
|
||||||
|
public string? Phone { get; set; }
|
||||||
|
public string? Email { get; set; }
|
||||||
|
public string? WorkingHours { get; set; }
|
||||||
|
public string? TelegramUrl { get; set; }
|
||||||
|
public string? InstagramUrl { get; set; }
|
||||||
|
public string? LinkedinUrl { get; set; }
|
||||||
|
public string? WhatsappUrl { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// LICENSES PAGE
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public class LicensesSettingsModel
|
||||||
|
{
|
||||||
|
public string? PageDescription { get; set; }
|
||||||
|
public string? DisplayStyle { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
@page "/content/page-settings"
|
||||||
|
|
||||||
|
@using BackOffice.Services.Content
|
||||||
|
|
||||||
|
@inject ISitePageSettingsService PageSettingsService
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudText Typo="Typo.h5">مدیریت تنظیمات صفحات</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Secondary">تنظیمات ۴ صفحه ثابت سایت: صفحه اصلی، درباره ما، تماس با ما و مجوزها</MudText>
|
||||||
|
|
||||||
|
@if (_loading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudGrid>
|
||||||
|
@foreach (var item in _pages)
|
||||||
|
{
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudCard Elevation="2" Class="pa-2">
|
||||||
|
<MudCardHeader>
|
||||||
|
<CardHeaderContent>
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudIcon Icon="@GetPageIcon(item.PageKey)" Size="Size.Large" Color="Color.Primary" />
|
||||||
|
<MudStack Spacing="0">
|
||||||
|
<MudText Typo="Typo.h6">@item.Title</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">@item.PageKey</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</CardHeaderContent>
|
||||||
|
<CardHeaderActions>
|
||||||
|
<MudChip T="string"
|
||||||
|
Color="@(item.IsActive ? Color.Success : Color.Error)"
|
||||||
|
Size="Size.Small">
|
||||||
|
@(item.IsActive ? "فعال" : "غیرفعال")
|
||||||
|
</MudChip>
|
||||||
|
</CardHeaderActions>
|
||||||
|
</MudCardHeader>
|
||||||
|
<MudCardContent>
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Image" Size="Size.Small" Class="ml-1" />
|
||||||
|
@item.ImageCount تصویر
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
آخرین ویرایش: @item.LastModified.MiladiToJalaliWithTime()
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudCardContent>
|
||||||
|
<MudCardActions>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.Edit"
|
||||||
|
OnClick="@(() => EditPageSettings(item))">
|
||||||
|
ویرایش
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Variant="Variant.Outlined"
|
||||||
|
Color="Color.Info"
|
||||||
|
Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.Collections"
|
||||||
|
OnClick="@(() => ManageImages(item))">
|
||||||
|
تصاویر
|
||||||
|
</MudButton>
|
||||||
|
</MudCardActions>
|
||||||
|
</MudCard>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudGrid>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using BackOffice.Services.Content;
|
||||||
|
using BackOffice.Pages.Content.Components;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Content;
|
||||||
|
|
||||||
|
public partial class PageSettingsManagementPage
|
||||||
|
{
|
||||||
|
private List<PageSettingsSummaryDto> _pages = new();
|
||||||
|
private bool _loading = true;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadPagesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadPagesAsync()
|
||||||
|
{
|
||||||
|
_loading = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pages = await PageSettingsService.GetAllAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در دریافت صفحات: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EditPageSettings(PageSettingsSummaryDto summary)
|
||||||
|
{
|
||||||
|
var details = await PageSettingsService.GetByKeyAsync(summary.PageKey);
|
||||||
|
if (details == null)
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در دریافت تنظیمات صفحه", Severity.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dto = new SavePageSettingsDto
|
||||||
|
{
|
||||||
|
PageKey = details.PageKey,
|
||||||
|
Title = details.Title,
|
||||||
|
MetaDescription = details.MetaDescription,
|
||||||
|
HeroTitle = details.HeroTitle,
|
||||||
|
HeroSubtitle = details.HeroSubtitle,
|
||||||
|
IsActive = details.IsActive,
|
||||||
|
SettingsJson = details.SettingsJson
|
||||||
|
};
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<PageSettingsEditDialog>
|
||||||
|
{
|
||||||
|
{ nameof(PageSettingsEditDialog.Model), dto },
|
||||||
|
{ nameof(PageSettingsEditDialog.ExistingHeroImagePath), details.HeroImagePath },
|
||||||
|
{ nameof(PageSettingsEditDialog.PageTitle), GetPageDisplayName(summary.PageKey) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<PageSettingsEditDialog>(
|
||||||
|
$"ویرایش تنظیمات «{GetPageDisplayName(summary.PageKey)}»", parameters,
|
||||||
|
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true });
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
Snackbar.Add("تنظیمات صفحه با موفقیت ذخیره شد", Severity.Success);
|
||||||
|
await LoadPagesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ManageImages(PageSettingsSummaryDto summary)
|
||||||
|
{
|
||||||
|
var details = await PageSettingsService.GetByKeyAsync(summary.PageKey);
|
||||||
|
if (details == null)
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در دریافت اطلاعات صفحه", Severity.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<PageImagesDialog>
|
||||||
|
{
|
||||||
|
{ nameof(PageImagesDialog.PageId), details.Id },
|
||||||
|
{ nameof(PageImagesDialog.PageKey), details.PageKey },
|
||||||
|
{ nameof(PageImagesDialog.PageTitle), GetPageDisplayName(summary.PageKey) },
|
||||||
|
{ nameof(PageImagesDialog.Images), details.Images }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<PageImagesDialog>(
|
||||||
|
$"مدیریت تصاویر «{GetPageDisplayName(summary.PageKey)}»", parameters,
|
||||||
|
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Large, FullWidth = true });
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
await LoadPagesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetPageIcon(string pageKey) => pageKey switch
|
||||||
|
{
|
||||||
|
"landing" => Icons.Material.Filled.Home,
|
||||||
|
"about" => Icons.Material.Filled.Info,
|
||||||
|
"contact" => Icons.Material.Filled.ContactMail,
|
||||||
|
"licenses" => Icons.Material.Filled.VerifiedUser,
|
||||||
|
_ => Icons.Material.Filled.Article
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string GetPageDisplayName(string pageKey) => pageKey switch
|
||||||
|
{
|
||||||
|
"landing" => "صفحه اصلی",
|
||||||
|
"about" => "درباره ما",
|
||||||
|
"contact" => "تماس با ما",
|
||||||
|
"licenses" => "مجوزها",
|
||||||
|
_ => pageKey
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -32,10 +32,10 @@
|
|||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">مجموع فروش (۷ روز)</MudText>
|
<MudText Typo="Typo.body2" Color="Color.Secondary">مجموع فروش (۷ روز)</MudText>
|
||||||
<MudText Typo="Typo.h5" Color="Color.Primary">
|
<MudText Typo="Typo.h5" Color="Color.Primary">
|
||||||
@_stats.TotalSalesLast7Days.ToString("N0") ریال
|
@_stats.TotalSalesLast7Days.ToString("N0") تومان
|
||||||
</MudText>
|
</MudText>
|
||||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
امروز: @_stats.TodaySales.ToString("N0") ریال
|
امروز: @_stats.TodaySales.ToString("N0") تومان
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">میانگین مبلغ هر سفارش</MudText>
|
<MudText Typo="Typo.body2" Color="Color.Secondary">میانگین مبلغ هر سفارش</MudText>
|
||||||
<MudText Typo="Typo.h5">
|
<MudText Typo="Typo.h5">
|
||||||
@_stats.AverageOrderAmount.ToString("N0") ریال
|
@_stats.AverageOrderAmount.ToString("N0") تومان
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Processing">در حال آمادهسازی</MudSelectItem>
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Processing">در حال آمادهسازی</MudSelectItem>
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Delivered">تحویل داده شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Delivered">تحویل داده شده</MudSelectItem>
|
||||||
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.ReadyForOfficePickup">آماده تحویل در دفتر</MudSelectItem>
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
@@ -55,6 +56,15 @@
|
|||||||
</MudItem>
|
</MudItem>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (Model.Status == OrderStatus.ReadyForOfficePickup)
|
||||||
|
{
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudAlert Severity="Severity.Info">
|
||||||
|
<MudText>سفارش برای تحویل حضوری در دفتر آماده است.</MudText>
|
||||||
|
</MudAlert>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
|
||||||
@if (Model.Status == OrderStatus.Shipped)
|
@if (Model.Status == OrderStatus.Shipped)
|
||||||
{
|
{
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
@@ -126,8 +136,9 @@
|
|||||||
OrderStatus.Processing => Color.Primary,
|
OrderStatus.Processing => Color.Primary,
|
||||||
OrderStatus.Shipped => Color.Secondary,
|
OrderStatus.Shipped => Color.Secondary,
|
||||||
OrderStatus.Delivered => Color.Success,
|
OrderStatus.Delivered => Color.Success,
|
||||||
OrderStatus.Cancelled => Color.Error,
|
OrderStatus.ReadyForOfficePickup => Color.Primary,
|
||||||
OrderStatus.Returned => Color.Dark,
|
OrderStatus.Cancelled => Color.Dark,
|
||||||
|
OrderStatus.Returned => Color.Error,
|
||||||
_ => Color.Default
|
_ => Color.Default
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -141,6 +152,7 @@
|
|||||||
OrderStatus.Processing => "در حال آمادهسازی",
|
OrderStatus.Processing => "در حال آمادهسازی",
|
||||||
OrderStatus.Shipped => "ارسال شده",
|
OrderStatus.Shipped => "ارسال شده",
|
||||||
OrderStatus.Delivered => "تحویل داده شده",
|
OrderStatus.Delivered => "تحویل داده شده",
|
||||||
|
OrderStatus.ReadyForOfficePickup => "آماده تحویل در دفتر",
|
||||||
OrderStatus.Cancelled => "لغو شده",
|
OrderStatus.Cancelled => "لغو شده",
|
||||||
OrderStatus.Returned => "مرجوع شده",
|
OrderStatus.Returned => "مرجوع شده",
|
||||||
_ => "نامشخص"
|
_ => "نامشخص"
|
||||||
|
|||||||
@@ -1,36 +1,72 @@
|
|||||||
@using BackOffice.Services.DiscountOrder
|
@using BackOffice.Services.DiscountOrder
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<TitleContent>
|
|
||||||
<MudText Typo="Typo.h6">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.ShoppingCart" Class="ml-2" />
|
|
||||||
جزئیات سفارش #@Order.OrderNumber
|
|
||||||
</MudText>
|
|
||||||
</TitleContent>
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<MudGrid>
|
<MudStack Spacing="2">
|
||||||
<!-- اطلاعات کاربر -->
|
<MudText Typo="Typo.h6">جزئیات سفارش شماره @(string.IsNullOrWhiteSpace(Order.OrderNumber) ? Order.Id.ToString() : Order.OrderNumber)</MudText>
|
||||||
<MudItem xs="12">
|
<MudText Typo="Typo.body2">
|
||||||
<MudPaper Class="pa-4" Elevation="1">
|
کاربر:
|
||||||
<MudText Typo="Typo.h6" GutterBottom="true">اطلاعات سفارش</MudText>
|
@if (!string.IsNullOrWhiteSpace(Order.UserFullName))
|
||||||
<MudGrid>
|
{
|
||||||
<MudItem xs="6">
|
@Order.UserFullName
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">شماره سفارش:</MudText>
|
}
|
||||||
<MudText Typo="Typo.body1"><strong>@Order.OrderNumber</strong></MudText>
|
else
|
||||||
</MudItem>
|
{
|
||||||
<MudItem xs="6">
|
<text>#@Order.UserId</text>
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">شناسه کاربر:</MudText>
|
}
|
||||||
<MudText Typo="Typo.body1">@Order.UserId</MudText>
|
@if (!string.IsNullOrWhiteSpace(DisplayMobile))
|
||||||
</MudItem>
|
{
|
||||||
</MudGrid>
|
<text> (@DisplayMobile)</text>
|
||||||
</MudPaper>
|
}
|
||||||
</MudItem>
|
</MudText>
|
||||||
|
|
||||||
<!-- Timeline وضعیت -->
|
<MudPaper Class="pa-3">
|
||||||
<MudItem xs="12">
|
<MudStack Spacing="2">
|
||||||
<MudPaper Class="pa-4" Elevation="1">
|
<MudText Typo="Typo.subtitle2">خلاصه سفارش</MudText>
|
||||||
<MudText Typo="Typo.subtitle2" GutterBottom="true">Timeline وضعیت</MudText>
|
<MudDivider />
|
||||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center" Justify="Justify.Center">
|
<MudText Typo="Typo.body2">مبلغ کل: @Order.TotalPrice.ToString("N0") تومان</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Success">
|
||||||
|
از کیف پول اعتباری: @Order.DiscountBalanceUsed.ToString("N0") تومان
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
مبلغ پرداختی از درگاه: @Order.GatewayAmount.ToString("N0") تومان
|
||||||
|
</MudText>
|
||||||
|
@if (!string.IsNullOrEmpty(Order.TransactionId))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">شناسه پرداخت: @Order.TransactionId</MudText>
|
||||||
|
}
|
||||||
|
@if (Order.Created.HasValue)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
تاریخ ثبت: @Order.Created.Value.MiladiToJalaliWithTime()
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
وضعیت پرداخت:
|
||||||
|
<MudChip T="string"
|
||||||
|
Color="@GetPaymentStatusColor(Order.PaymentStatusValue)"
|
||||||
|
Size="Size.Small"
|
||||||
|
Icon="@GetPaymentStatusIcon(Order.PaymentStatusValue)">
|
||||||
|
@GetPaymentStatusText(Order.PaymentStatusValue)
|
||||||
|
</MudChip>
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
@if (Order.VatAmount > 0)
|
||||||
|
{
|
||||||
|
<MudDivider Class="my-2" />
|
||||||
|
<MudText Typo="Typo.subtitle2">جزئیات مالیات بر ارزش افزوده</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
مالیات: @Order.VatAmount.ToString("N0") تومان
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
مبلغ نهایی (شامل مالیات): @Order.TotalPrice.ToString("N0") تومان
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudDivider Class="my-2" />
|
||||||
|
<MudText Typo="Typo.subtitle2">Timeline وضعیت</MudText>
|
||||||
|
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
||||||
@foreach (var step in _timelineSteps)
|
@foreach (var step in _timelineSteps)
|
||||||
{
|
{
|
||||||
<MudStack Spacing="0" AlignItems="AlignItems.Center">
|
<MudStack Spacing="0" AlignItems="AlignItems.Center">
|
||||||
@@ -41,218 +77,155 @@
|
|||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<!-- وضعیت سفارش -->
|
<MudDivider Class="my-2" />
|
||||||
<MudItem xs="12" sm="6">
|
<MudText Typo="Typo.body2">
|
||||||
<MudPaper Class="pa-4" Elevation="1">
|
وضعیت ارسال:
|
||||||
<MudText Typo="Typo.h6" GutterBottom="true">وضعیت سفارش</MudText>
|
<MudSelect T="OrderStatus"
|
||||||
<MudChip T="string" Color="@GetStatusColor(Order.Status)" Size="Size.Large">
|
Label="وضعیت ارسال"
|
||||||
@GetStatusText(Order.Status)
|
Dense="true"
|
||||||
</MudChip>
|
Variant="Variant.Outlined"
|
||||||
@if (Order.Created.HasValue)
|
@bind-Value="_newStatus">
|
||||||
{
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Pending">در انتظار پرداخت</MudSelectItem>
|
||||||
<MudText Typo="Typo.body2" Class="mt-2">
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Paid">پرداخت شده</MudSelectItem>
|
||||||
تاریخ ثبت: @Order.Created.Value.MiladiToJalaliWithTime()
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Processing">در حال آمادهسازی</MudSelectItem>
|
||||||
</MudText>
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
||||||
}
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Delivered">تحویل داده شده</MudSelectItem>
|
||||||
</MudPaper>
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.ReadyForOfficePickup">آماده تحویل در دفتر</MudSelectItem>
|
||||||
</MudItem>
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
||||||
|
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
||||||
<!-- وضعیت پرداخت -->
|
</MudSelect>
|
||||||
<MudItem xs="12" sm="6">
|
</MudText>
|
||||||
<MudPaper Class="pa-4" Elevation="1">
|
<MudTextField T="string"
|
||||||
<MudText Typo="Typo.h6" GutterBottom="true">وضعیت پرداخت</MudText>
|
Label="کد رهگیری"
|
||||||
@if (Order.PaymentCompleted)
|
Variant="Variant.Outlined"
|
||||||
{
|
Dense="true"
|
||||||
<MudChip T="string" Color="Color.Success" Size="Size.Large" Icon="@Icons.Material.Filled.CheckCircle">
|
@bind-Value="_trackingCode" />
|
||||||
پرداخت شده
|
<MudTextField T="string"
|
||||||
</MudChip>
|
Label="یادداشت ادمین"
|
||||||
@if (!string.IsNullOrEmpty(Order.TransactionId))
|
Variant="Variant.Outlined"
|
||||||
{
|
Dense="true"
|
||||||
<MudText Typo="Typo.body2" Class="mt-2">
|
Lines="3"
|
||||||
کد تراکنش: @Order.TransactionId
|
HelperText="دلیل تغییر وضعیت یا توضیحات اضافی"
|
||||||
</MudText>
|
@bind-Value="_adminNotes" />
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudChip T="string" Color="Color.Warning" Size="Size.Large" Icon="@Icons.Material.Filled.Schedule">
|
|
||||||
در انتظار پرداخت
|
|
||||||
</MudChip>
|
|
||||||
}
|
|
||||||
</MudPaper>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<!-- آدرس ارسال -->
|
|
||||||
@if (Order.Address != null)
|
|
||||||
{
|
|
||||||
<MudItem xs="12">
|
|
||||||
<MudPaper Class="pa-4" Elevation="1">
|
|
||||||
<MudText Typo="Typo.h6" GutterBottom="true">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.LocationOn" Size="Size.Small" />
|
|
||||||
آدرس ارسال
|
|
||||||
</MudText>
|
|
||||||
<MudText Typo="Typo.body1"><strong>@Order.Address.Title</strong></MudText>
|
|
||||||
<MudText Typo="Typo.body2">@Order.Address.Address</MudText>
|
|
||||||
<MudText Typo="Typo.body2">کد پستی: @Order.Address.PostalCode</MudText>
|
|
||||||
@if (!string.IsNullOrEmpty(Order.Address.Phone))
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.body2">تلفن: @Order.Address.Phone</MudText>
|
|
||||||
}
|
|
||||||
</MudPaper>
|
|
||||||
</MudItem>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- آیتمهای سفارش -->
|
|
||||||
<MudItem xs="12">
|
|
||||||
<MudPaper Class="pa-4" Elevation="1">
|
|
||||||
<MudText Typo="Typo.h6" GutterBottom="true">آیتمهای سفارش</MudText>
|
|
||||||
<MudTable Items="@Order.Items" Hover="true" Dense="true" Elevation="0">
|
|
||||||
<HeaderContent>
|
|
||||||
<MudTh>محصول</MudTh>
|
|
||||||
<MudTh>تعداد</MudTh>
|
|
||||||
<MudTh>قیمت واحد</MudTh>
|
|
||||||
<MudTh>تخفیف</MudTh>
|
|
||||||
<MudTh>قیمت نهایی</MudTh>
|
|
||||||
</HeaderContent>
|
|
||||||
<RowTemplate>
|
|
||||||
<MudTd>
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
|
||||||
@if (!string.IsNullOrWhiteSpace(context.ThumbnailPath))
|
|
||||||
{
|
|
||||||
<MudImage Src="@context.ThumbnailPath" Width="40" Height="40"
|
|
||||||
ObjectFit="ObjectFit.Cover" Class="rounded" />
|
|
||||||
}
|
|
||||||
<MudText>@context.ProductTitle</MudText>
|
|
||||||
</MudStack>
|
|
||||||
</MudTd>
|
|
||||||
<MudTd>@context.Count</MudTd>
|
|
||||||
<MudTd>@context.UnitPrice.ToString("N0") ریال</MudTd>
|
|
||||||
<MudTd>
|
|
||||||
<MudChip T="string" Size="Size.Small" Color="Color.Success">@context.MaxDiscountPercent%</MudChip>
|
|
||||||
</MudTd>
|
|
||||||
<MudTd><strong>@context.FinalPrice.ToString("N0") ریال</strong></MudTd>
|
|
||||||
</RowTemplate>
|
|
||||||
</MudTable>
|
|
||||||
</MudPaper>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<!-- خلاصه مالی -->
|
|
||||||
<MudItem xs="12">
|
|
||||||
<MudPaper Class="pa-4" Elevation="2">
|
|
||||||
<MudText Typo="Typo.h6" GutterBottom="true">خلاصه مالی</MudText>
|
|
||||||
<MudGrid>
|
|
||||||
<MudItem xs="6">
|
|
||||||
<MudText Color="Color.Secondary">مبلغ کل:</MudText>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem xs="6" Class="text-left">
|
|
||||||
<MudText>@Order.TotalPrice.ToString("N0") ریال</MudText>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<MudItem xs="6">
|
|
||||||
<MudText Color="Color.Success">از کیف پول تخفیف:</MudText>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem xs="6" Class="text-left">
|
|
||||||
<MudText Color="Color.Success">@Order.DiscountBalanceUsed.ToString("N0") ریال</MudText>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<MudItem xs="12"><MudDivider /></MudItem>
|
|
||||||
|
|
||||||
<MudItem xs="6">
|
|
||||||
<MudText Typo="Typo.h6">مبلغ پرداختی از درگاه:</MudText>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem xs="6" Class="text-left">
|
|
||||||
<MudText Typo="Typo.h6" Color="Color.Primary">
|
|
||||||
<strong>@Order.GatewayAmount.ToString("N0") ریال</strong>
|
|
||||||
</MudText>
|
|
||||||
</MudItem>
|
|
||||||
</MudGrid>
|
|
||||||
</MudPaper>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<!-- ویرایش وضعیت ارسال -->
|
|
||||||
<MudItem xs="12">
|
|
||||||
<MudPaper Class="pa-4" Elevation="1">
|
|
||||||
<MudText Typo="Typo.subtitle2" GutterBottom="true">مدیریت وضعیت ارسال</MudText>
|
|
||||||
<MudDivider Class="mb-3" />
|
|
||||||
<MudGrid>
|
|
||||||
<MudItem xs="12" sm="6">
|
|
||||||
<MudSelect T="OrderStatus"
|
|
||||||
Label="وضعیت ارسال"
|
|
||||||
Dense="true"
|
|
||||||
Variant="Variant.Outlined"
|
|
||||||
@bind-Value="_newStatus">
|
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Pending">در انتظار پرداخت</MudSelectItem>
|
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Paid">پرداخت شده</MudSelectItem>
|
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Processing">در حال آمادهسازی</MudSelectItem>
|
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Delivered">تحویل داده شده</MudSelectItem>
|
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
|
||||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
|
||||||
</MudSelect>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem xs="12" sm="6">
|
|
||||||
<MudTextField T="string"
|
|
||||||
Label="کد رهگیری"
|
|
||||||
Variant="Variant.Outlined"
|
|
||||||
Dense="true"
|
|
||||||
@bind-Value="_trackingCode" />
|
|
||||||
</MudItem>
|
|
||||||
<MudItem xs="12">
|
|
||||||
<MudTextField T="string"
|
|
||||||
Label="یادداشت ادمین"
|
|
||||||
Variant="Variant.Outlined"
|
|
||||||
Dense="true"
|
|
||||||
Lines="3"
|
|
||||||
HelperText="دلیل تغییر وضعیت یا توضیحات اضافی"
|
|
||||||
@bind-Value="_adminNotes" />
|
|
||||||
</MudItem>
|
|
||||||
</MudGrid>
|
|
||||||
|
|
||||||
@if (_newStatus == OrderStatus.Cancelled || _newStatus == OrderStatus.Returned)
|
@if (_newStatus == OrderStatus.Cancelled || _newStatus == OrderStatus.Returned)
|
||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Warning" Class="mt-2">
|
<MudAlert Severity="Severity.Warning" Dense="true">
|
||||||
توجه: در صورت لغو یا مرجوعی سفارش، موجودی محصولات به انبار بازگردانده میشود.
|
توجه: در صورت لغو یا مرجوعی سفارش، موجودی محصولات به انبار بازگردانده میشود.
|
||||||
</MudAlert>
|
</MudAlert>
|
||||||
}
|
}
|
||||||
|
@if (_newStatus == OrderStatus.ReadyForOfficePickup)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true">
|
||||||
|
سفارش برای تحویل حضوری در دفتر آماده است.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
@if (_newStatus == OrderStatus.Shipped)
|
@if (_newStatus == OrderStatus.Shipped)
|
||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Info" Class="mt-2">
|
<MudAlert Severity="Severity.Info" Dense="true">
|
||||||
پس از تغییر وضعیت به "ارسال شده"، اطلاعرسانی به کاربر ارسال خواهد شد.
|
پس از تغییر وضعیت به «ارسال شده»، اطلاعرسانی به کاربر ارسال خواهد شد.
|
||||||
</MudAlert>
|
</MudAlert>
|
||||||
}
|
}
|
||||||
</MudPaper>
|
</MudStack>
|
||||||
</MudItem>
|
</MudPaper>
|
||||||
|
|
||||||
<!-- یادداشتها -->
|
<MudPaper Class="pa-3">
|
||||||
@if (!string.IsNullOrEmpty(Order.Notes) || !string.IsNullOrEmpty(Order.AdminNotes))
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">آدرس تحویل</MudText>
|
||||||
|
<MudDivider />
|
||||||
|
@if (Order.Address != null && !string.IsNullOrWhiteSpace(Order.Address.Address))
|
||||||
|
{
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Order.Address.Title))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2"><strong>@Order.Address.Title</strong></MudText>
|
||||||
|
}
|
||||||
|
<MudText Typo="Typo.body2">@Order.Address.Address</MudText>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Order.Address.PostalCode))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">کد پستی: @Order.Address.PostalCode</MudText>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(DisplayMobile))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">تلفن سفارشدهنده: @DisplayMobile</MudText>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption">آدرسی ثبت نشده است.</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-3">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">اقلام فاکتور</MudText>
|
||||||
|
<MudDivider />
|
||||||
|
@if (Order.Items.Count > 0)
|
||||||
|
{
|
||||||
|
<MudTable Items="Order.Items" Dense="true">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>تصویر</MudTh>
|
||||||
|
<MudTh>محصول</MudTh>
|
||||||
|
<MudTh>قیمت واحد</MudTh>
|
||||||
|
<MudTh>تعداد</MudTh>
|
||||||
|
<MudTh>تخفیف</MudTh>
|
||||||
|
<MudTh>قیمت نهایی</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(context.ThumbnailPath))
|
||||||
|
{
|
||||||
|
<Image Src="@context.ThumbnailPath" Height="50" Width="50" />
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
<MudTd>@context.ProductTitle</MudTd>
|
||||||
|
<MudTd>@context.UnitPrice.ToString("N0")</MudTd>
|
||||||
|
<MudTd>@context.Count</MudTd>
|
||||||
|
<MudTd>
|
||||||
|
@if (context.MaxDiscountPercent > 0)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Success">@context.MaxDiscountPercent%</MudChip>
|
||||||
|
}
|
||||||
|
else if (context.DiscountAmount > 0)
|
||||||
|
{
|
||||||
|
@context.DiscountAmount.ToString("N0")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<text>-</text>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
<MudTd>@context.FinalPrice.ToString("N0")</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption">آیتمی برای این سفارش ثبت نشده است.</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Order.Notes))
|
||||||
{
|
{
|
||||||
<MudItem xs="12">
|
<MudPaper Class="pa-3">
|
||||||
<MudPaper Class="pa-4" Elevation="1">
|
<MudStack Spacing="1">
|
||||||
@if (!string.IsNullOrEmpty(Order.Notes))
|
<MudText Typo="Typo.subtitle2">یادداشت کاربر</MudText>
|
||||||
{
|
<MudDivider />
|
||||||
<MudText Typo="Typo.h6" GutterBottom="true">
|
<MudText Typo="Typo.body2">@Order.Notes</MudText>
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Comment" Size="Size.Small" />
|
</MudStack>
|
||||||
یادداشت کاربر
|
</MudPaper>
|
||||||
</MudText>
|
|
||||||
<MudText Typo="Typo.body1" Class="mb-4">@Order.Notes</MudText>
|
|
||||||
}
|
|
||||||
@if (!string.IsNullOrEmpty(Order.AdminNotes))
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.h6" GutterBottom="true">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Note" Size="Size.Small" />
|
|
||||||
یادداشت ادمین
|
|
||||||
</MudText>
|
|
||||||
<MudText Typo="Typo.body1">@Order.AdminNotes</MudText>
|
|
||||||
}
|
|
||||||
</MudPaper>
|
|
||||||
</MudItem>
|
|
||||||
}
|
}
|
||||||
</MudGrid>
|
</MudStack>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Secondary"
|
||||||
|
StartIcon="@Icons.Material.Filled.Print"
|
||||||
|
OnClick="PrintInvoiceAsync">
|
||||||
|
چاپ فاکتور
|
||||||
|
</MudButton>
|
||||||
|
<MudSpacer />
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Default" OnClick="Close">
|
<MudButton Variant="Variant.Outlined" Color="Color.Default" OnClick="Close">
|
||||||
بستن
|
بستن
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using BackOffice.Common.Utilities;
|
||||||
using BackOffice.Services.DiscountOrder;
|
using BackOffice.Services.DiscountOrder;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
|
|
||||||
namespace BackOffice.Pages.DiscountShop.Components;
|
namespace BackOffice.Pages.DiscountShop.Components;
|
||||||
@@ -8,6 +10,7 @@ public partial class OrderDetailsDialog
|
|||||||
{
|
{
|
||||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;
|
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;
|
||||||
[Inject] public IDiscountOrderService OrderService { get; set; } = default!;
|
[Inject] public IDiscountOrderService OrderService { get; set; } = default!;
|
||||||
|
[Inject] private IJSRuntime JsRuntime { get; set; } = default!;
|
||||||
|
|
||||||
[Parameter] public DiscountOrderDetailsDto Order { get; set; } = null!;
|
[Parameter] public DiscountOrderDetailsDto Order { get; set; } = null!;
|
||||||
|
|
||||||
@@ -17,6 +20,9 @@ public partial class OrderDetailsDialog
|
|||||||
private bool _isSaving;
|
private bool _isSaving;
|
||||||
private List<TimelineStep> _timelineSteps = new();
|
private List<TimelineStep> _timelineSteps = new();
|
||||||
|
|
||||||
|
private string? DisplayMobile =>
|
||||||
|
!string.IsNullOrWhiteSpace(Order.UserMobile) ? Order.UserMobile : Order.Address?.Phone;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
_newStatus = Order.Status;
|
_newStatus = Order.Status;
|
||||||
@@ -127,34 +133,69 @@ public partial class OrderDetailsDialog
|
|||||||
|
|
||||||
private void Close() => MudDialog.Close();
|
private void Close() => MudDialog.Close();
|
||||||
|
|
||||||
private Color GetStatusColor(OrderStatus status)
|
private Color GetPaymentStatusColor(int status) => status switch
|
||||||
{
|
{
|
||||||
return status switch
|
0 => Color.Warning,
|
||||||
{
|
1 => Color.Success,
|
||||||
OrderStatus.Pending => Color.Warning,
|
2 => Color.Error,
|
||||||
OrderStatus.Paid => Color.Info,
|
3 => Color.Info,
|
||||||
OrderStatus.Processing => Color.Primary,
|
_ => Color.Default
|
||||||
OrderStatus.Shipped => Color.Secondary,
|
};
|
||||||
OrderStatus.Delivered => Color.Success,
|
|
||||||
OrderStatus.Cancelled => Color.Error,
|
|
||||||
OrderStatus.Returned => Color.Dark,
|
|
||||||
_ => Color.Default
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetStatusText(OrderStatus status)
|
private string GetPaymentStatusText(int status) => status switch
|
||||||
{
|
{
|
||||||
return status switch
|
0 => "در انتظار پرداخت",
|
||||||
|
1 => "پرداخت شده",
|
||||||
|
2 => "ناموفق",
|
||||||
|
3 => "بازپرداخت شده",
|
||||||
|
_ => "نامشخص"
|
||||||
|
};
|
||||||
|
|
||||||
|
private string GetPaymentStatusIcon(int status) => status switch
|
||||||
|
{
|
||||||
|
1 => Icons.Material.Filled.CheckCircle,
|
||||||
|
2 => Icons.Material.Filled.Cancel,
|
||||||
|
3 => Icons.Material.Filled.Replay,
|
||||||
|
_ => Icons.Material.Filled.Schedule
|
||||||
|
};
|
||||||
|
|
||||||
|
private async Task PrintInvoiceAsync()
|
||||||
|
{
|
||||||
|
var lines = Order.Items.Select(i => new OrderInvoicePrint.InvoiceLine
|
||||||
{
|
{
|
||||||
OrderStatus.Pending => "در انتظار پرداخت",
|
Title = i.ProductTitle,
|
||||||
OrderStatus.Paid => "پرداخت شده",
|
Quantity = i.Count,
|
||||||
OrderStatus.Processing => "در حال آمادهسازی",
|
UnitPrice = i.UnitPrice,
|
||||||
OrderStatus.Shipped => "ارسال شده",
|
Discount = i.DiscountAmount,
|
||||||
OrderStatus.Delivered => "تحویل داده شده",
|
LineTotal = i.FinalPrice > 0 ? i.FinalPrice : i.UnitPrice * i.Count
|
||||||
OrderStatus.Cancelled => "لغو شده",
|
}).ToList();
|
||||||
OrderStatus.Returned => "مرجوع شده",
|
|
||||||
_ => "نامشخص"
|
var mobile = !string.IsNullOrWhiteSpace(Order.UserMobile)
|
||||||
};
|
? Order.UserMobile
|
||||||
|
: Order.Address?.Phone;
|
||||||
|
|
||||||
|
var html = OrderInvoicePrint.BuildHtml(new OrderInvoicePrint.InvoiceModel
|
||||||
|
{
|
||||||
|
ShopTitle = "فروشگاه اعتباری — کارا بازار سلامت",
|
||||||
|
OrderId = string.IsNullOrWhiteSpace(Order.OrderNumber) ? Order.Id.ToString() : Order.OrderNumber,
|
||||||
|
CustomerName = Order.UserFullName,
|
||||||
|
CustomerMobile = mobile,
|
||||||
|
Address = Order.Address?.Address,
|
||||||
|
PostalCode = Order.Address?.PostalCode,
|
||||||
|
PaymentStatus = GetPaymentStatusText(Order.PaymentStatusValue),
|
||||||
|
TransactionId = Order.TransactionId,
|
||||||
|
TrackingCode = Order.TrackingCode,
|
||||||
|
Created = Order.Created,
|
||||||
|
Subtotal = lines.Sum(l => l.UnitPrice * l.Quantity),
|
||||||
|
DiscountTotal = lines.Sum(l => l.Discount),
|
||||||
|
CreditUsed = Order.DiscountBalanceUsed,
|
||||||
|
GatewayAmount = Order.GatewayAmount,
|
||||||
|
VatAmount = Order.VatAmount,
|
||||||
|
TotalAmount = Order.TotalPrice,
|
||||||
|
Lines = lines
|
||||||
|
});
|
||||||
|
|
||||||
|
await JsRuntime.InvokeVoidAsync("jsHtmlToPdf", html, $"فاکتور سفارش {Order.Id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum StepState
|
private enum StepState
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
Format="N0"
|
Format="N0"
|
||||||
Immediate="true"
|
Immediate="true"
|
||||||
Disabled="_loading"
|
Disabled="_loading"
|
||||||
Label="قیمت (ریال) *"
|
Label="قیمت (تومان) *"
|
||||||
Required="true"
|
Required="true"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
Margin="Margin.Dense" />
|
Margin="Margin.Dense" />
|
||||||
|
|||||||
@@ -20,14 +20,14 @@
|
|||||||
<MudList T="string" Dense="true">
|
<MudList T="string" Dense="true">
|
||||||
<MudListItem T="string"
|
<MudListItem T="string"
|
||||||
OnClick="@(() => OnTreeNodeSelected(null))"
|
OnClick="@(() => OnTreeNodeSelected(null))"
|
||||||
Selected="@(_selectedParentId == null)">
|
Class="@(_selectedParentId == null ? "mud-selected-item" : "")">
|
||||||
<MudText Typo="Typo.body2">همه دستهبندیها</MudText>
|
<MudText Typo="Typo.body2">همه دستهبندیها</MudText>
|
||||||
</MudListItem>
|
</MudListItem>
|
||||||
@foreach (var node in _treeNodes)
|
@foreach (var node in _treeNodes)
|
||||||
{
|
{
|
||||||
<MudListItem T="string"
|
<MudListItem T="string"
|
||||||
OnClick="@(() => OnTreeNodeSelected(node.Id))"
|
OnClick="@(() => OnTreeNodeSelected(node.Id))"
|
||||||
Selected="@(_selectedParentId == node.Id)">
|
Class="@(_selectedParentId == node.Id ? "mud-selected-item" : "")">
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
<div style="width:@(node.Depth * 16)px;"></div>
|
<div style="width:@(node.Depth * 16)px;"></div>
|
||||||
<MudText Typo="Typo.body2">@node.Title</MudText>
|
<MudText Typo="Typo.body2">@node.Title</MudText>
|
||||||
|
|||||||
@@ -23,11 +23,11 @@
|
|||||||
@bind-Value="@_trackingCodeFilter" Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
@bind-Value="@_trackingCodeFilter" Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6" md="3">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<MudDatePicker T="DateTime?" Label="از تاریخ" @bind-Date="_fromDateFilter"
|
<MudDatePicker Label="از تاریخ" @bind-Date="_fromDateFilter"
|
||||||
Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6" md="3">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<MudDatePicker T="DateTime?" Label="تا تاریخ" @bind-Date="_toDateFilter"
|
<MudDatePicker Label="تا تاریخ" @bind-Date="_toDateFilter"
|
||||||
Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6" md="3">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
@@ -49,6 +49,8 @@
|
|||||||
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
||||||
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Delivered">تحویل داده شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Delivered">تحویل داده شده</MudSelectItem>
|
||||||
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
||||||
|
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.ReadyForOfficePickup">آماده تحویل در دفتر</MudSelectItem>
|
||||||
|
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</Filters>
|
</Filters>
|
||||||
@@ -97,20 +99,20 @@
|
|||||||
|
|
||||||
<TemplateColumn Title="مبلغ کل">
|
<TemplateColumn Title="مبلغ کل">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudText>@context.Item.TotalPrice.ToString("N0") ریال</MudText>
|
<MudText>@context.Item.TotalPrice.ToString("N0") تومان</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
|
|
||||||
<TemplateColumn Title="تخفیف کیفپول">
|
<TemplateColumn Title="کیف پول اعتباری">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudText Color="Color.Success">@context.Item.DiscountBalanceUsed.ToString("N0") ریال</MudText>
|
<MudText Color="Color.Success">@context.Item.DiscountBalanceUsed.ToString("N0") تومان</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
|
|
||||||
<TemplateColumn Title="پرداخت درگاه">
|
<TemplateColumn Title="پرداخت درگاه">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudText Typo="Typo.body2">
|
<MudText Typo="Typo.body2">
|
||||||
<strong>@context.Item.GatewayAmount.ToString("N0") ریال</strong>
|
<strong>@context.Item.GatewayAmount.ToString("N0") تومان</strong>
|
||||||
</MudText>
|
</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using BackOffice.Common.BaseComponents;
|
using BackOffice.Common.BaseComponents;
|
||||||
|
using BackOffice.Common.Utilities;
|
||||||
using BackOffice.Services.DiscountOrder;
|
using BackOffice.Services.DiscountOrder;
|
||||||
using BackOffice.Pages.DiscountShop.Components;
|
using BackOffice.Pages.DiscountShop.Components;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
@@ -74,6 +75,15 @@ public partial class DiscountOrdersMainPage
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// تکمیل فیلدهای فاکتور از ردیف لیست (جزئیات API نام/موبایل/VAT ندارد)
|
||||||
|
var listRow = _orders?.FirstOrDefault(o => o.Id == orderId);
|
||||||
|
if (listRow != null)
|
||||||
|
{
|
||||||
|
order.UserFullName = listRow.UserFullName;
|
||||||
|
order.UserMobile = listRow.UserMobile;
|
||||||
|
order.VatAmount = listRow.VatAmount;
|
||||||
|
}
|
||||||
|
|
||||||
var parameters = new DialogParameters<OrderDetailsDialog> { { x => x.Order, order } };
|
var parameters = new DialogParameters<OrderDetailsDialog> { { x => x.Order, order } };
|
||||||
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large, FullWidth = true };
|
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large, FullWidth = true };
|
||||||
var dialog = await DialogService.ShowAsync<OrderDetailsDialog>("جزئیات سفارش", parameters, options);
|
var dialog = await DialogService.ShowAsync<OrderDetailsDialog>("جزئیات سفارش", parameters, options);
|
||||||
@@ -178,8 +188,9 @@ public partial class DiscountOrdersMainPage
|
|||||||
OrderStatus.Processing => Color.Primary,
|
OrderStatus.Processing => Color.Primary,
|
||||||
OrderStatus.Shipped => Color.Secondary,
|
OrderStatus.Shipped => Color.Secondary,
|
||||||
OrderStatus.Delivered => Color.Success,
|
OrderStatus.Delivered => Color.Success,
|
||||||
OrderStatus.Cancelled => Color.Error,
|
OrderStatus.Cancelled => Color.Dark,
|
||||||
OrderStatus.Returned => Color.Dark,
|
OrderStatus.ReadyForOfficePickup => Color.Primary,
|
||||||
|
OrderStatus.Returned => Color.Error,
|
||||||
_ => Color.Default
|
_ => Color.Default
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -194,6 +205,7 @@ public partial class DiscountOrdersMainPage
|
|||||||
OrderStatus.Shipped => "ارسال شده",
|
OrderStatus.Shipped => "ارسال شده",
|
||||||
OrderStatus.Delivered => "تحویل داده شده",
|
OrderStatus.Delivered => "تحویل داده شده",
|
||||||
OrderStatus.Cancelled => "لغو شده",
|
OrderStatus.Cancelled => "لغو شده",
|
||||||
|
OrderStatus.ReadyForOfficePickup => "آماده تحویل در دفتر",
|
||||||
OrderStatus.Returned => "مرجوع شده",
|
OrderStatus.Returned => "مرجوع شده",
|
||||||
_ => "نامشخص"
|
_ => "نامشخص"
|
||||||
};
|
};
|
||||||
@@ -233,44 +245,36 @@ public partial class DiscountOrdersMainPage
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var headers = new[]
|
||||||
sb.AppendLine("شناسه,نام کاربر,موبایل,مبلغ کل,تخفیف کیفپول,پرداخت درگاه,تعداد آیتم,وضعیت پرداخت,وضعیت ارسال,تاریخ پرداخت,تاریخ ثبت,آدرس");
|
|
||||||
|
|
||||||
foreach (var o in _orders)
|
|
||||||
{
|
{
|
||||||
sb.AppendLine(string.Join(",",
|
"شناسه", "نام مشتری", "موبایل", "مبلغ کل", "کیف پول اعتباری", "پرداخت درگاه",
|
||||||
o.Id,
|
"تعداد آیتم", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت", "تاریخ ثبت", "آدرس"
|
||||||
EscapeCsv(o.UserFullName),
|
};
|
||||||
EscapeCsv(o.UserMobile),
|
|
||||||
o.TotalPrice.ToString("N0"),
|
|
||||||
o.DiscountBalanceUsed.ToString("N0"),
|
|
||||||
o.GatewayAmount.ToString("N0"),
|
|
||||||
o.ItemsCount,
|
|
||||||
EscapeCsv(GetPaymentStatusText(o.PaymentStatusValue)),
|
|
||||||
EscapeCsv(GetStatusText(o.Status)),
|
|
||||||
o.PaymentDate?.MiladiToJalaliWithTime() ?? "-",
|
|
||||||
o.Created?.MiladiToJalaliWithTime() ?? "-",
|
|
||||||
EscapeCsv(o.ShippingAddress ?? "-")));
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
var rows = _orders.Select(o => new object?[]
|
||||||
var base64 = Convert.ToBase64String(bytes);
|
{
|
||||||
var filename = $"discount-orders-{DateTime.Now:yyyyMMddHHmmss}.csv";
|
o.Id,
|
||||||
|
CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
|
||||||
|
o.UserMobile ?? "",
|
||||||
|
o.TotalPrice,
|
||||||
|
o.DiscountBalanceUsed,
|
||||||
|
o.GatewayAmount,
|
||||||
|
o.ItemsCount,
|
||||||
|
GetPaymentStatusText(o.PaymentStatusValue),
|
||||||
|
GetStatusText(o.Status),
|
||||||
|
o.PaymentDate?.MiladiToJalaliWithTime() ?? "-",
|
||||||
|
o.Created?.MiladiToJalaliWithTime() ?? "-",
|
||||||
|
o.ShippingAddress ?? "-"
|
||||||
|
});
|
||||||
|
|
||||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
|
var filename = $"discount-orders-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||||
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
|
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||||
|
CsvExportHelper.ToExcelXmlBase64("سفارشهای اعتباری", headers, rows));
|
||||||
|
Snackbar.Add("خروجی Excel آماده شد.", Severity.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error);
|
Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string EscapeCsv(string value)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(value)) return "";
|
|
||||||
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
|
|
||||||
return $"\"{value.Replace("\"", "\"\"")}\"";
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
|
|
||||||
<TemplateColumn Title="قیمت" CellStyle="text-wrap: nowrap;">
|
<TemplateColumn Title="قیمت" CellStyle="text-wrap: nowrap;">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudText>@context.Item.Price.ToString("N0") ریال</MudText>
|
<MudText>@context.Item.Price.ToString("N0") تومان</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Processing">در حال پردازش</MudSelectItem>
|
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Processing">در حال پردازش</MudSelectItem>
|
||||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
||||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Delivered">تحویل شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Delivered">تحویل شده</MudSelectItem>
|
||||||
|
<MudSelectItem T="OrderStatus?" Value="OrderStatus.ReadyForOfficePickup">آماده تحویل در دفتر</MudSelectItem>
|
||||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
||||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
@@ -113,19 +114,19 @@
|
|||||||
<MudItem xs="12" md="3">
|
<MudItem xs="12" md="3">
|
||||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||||
<MudText Typo="Typo.subtitle2" Color="Color.Success">مجموع فروش (مبلغ نهایی)</MudText>
|
<MudText Typo="Typo.subtitle2" Color="Color.Success">مجموع فروش (مبلغ نهایی)</MudText>
|
||||||
<MudText Typo="Typo.h5">@_totalSales.ToString("N0") ریال</MudText>
|
<MudText Typo="Typo.h5">@_totalSales.ToString("N0") تومان</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="3">
|
<MudItem xs="12" md="3">
|
||||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||||
<MudText Typo="Typo.subtitle2" Color="Color.Warning">مجموع تخفیف</MudText>
|
<MudText Typo="Typo.subtitle2" Color="Color.Warning">مجموع تخفیف</MudText>
|
||||||
<MudText Typo="Typo.h5">@_totalDiscount.ToString("N0") ریال</MudText>
|
<MudText Typo="Typo.h5">@_totalDiscount.ToString("N0") تومان</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="3">
|
<MudItem xs="12" md="3">
|
||||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||||
<MudText Typo="Typo.subtitle2" Color="Color.Info">میانگین مبلغ سفارش</MudText>
|
<MudText Typo="Typo.subtitle2" Color="Color.Info">میانگین مبلغ سفارش</MudText>
|
||||||
<MudText Typo="Typo.h5">@_averageOrder.ToString("N0") ریال</MudText>
|
<MudText Typo="Typo.h5">@_averageOrder.ToString("N0") تومان</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
@@ -242,12 +243,12 @@
|
|||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="مبلغ نهایی">
|
<TemplateColumn Title="مبلغ نهایی">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.GatewayAmount.ToString("N0") ریال
|
@context.Item.GatewayAmount.ToString("N0") تومان
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="تخفیف">
|
<TemplateColumn Title="تخفیف">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.DiscountBalanceUsed.ToString("N0") ریال
|
@context.Item.DiscountBalanceUsed.ToString("N0") تومان
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="وضعیت">
|
<TemplateColumn Title="وضعیت">
|
||||||
@@ -498,7 +499,8 @@
|
|||||||
OrderStatus.Processing => Color.Info,
|
OrderStatus.Processing => Color.Info,
|
||||||
OrderStatus.Shipped => Color.Info,
|
OrderStatus.Shipped => Color.Info,
|
||||||
OrderStatus.Delivered => Color.Success,
|
OrderStatus.Delivered => Color.Success,
|
||||||
OrderStatus.Cancelled => Color.Error,
|
OrderStatus.ReadyForOfficePickup => Color.Primary,
|
||||||
|
OrderStatus.Cancelled => Color.Dark,
|
||||||
OrderStatus.Returned => Color.Error,
|
OrderStatus.Returned => Color.Error,
|
||||||
_ => Color.Default
|
_ => Color.Default
|
||||||
};
|
};
|
||||||
@@ -513,6 +515,7 @@
|
|||||||
OrderStatus.Processing => "در حال پردازش",
|
OrderStatus.Processing => "در حال پردازش",
|
||||||
OrderStatus.Shipped => "ارسال شده",
|
OrderStatus.Shipped => "ارسال شده",
|
||||||
OrderStatus.Delivered => "تحویل شده",
|
OrderStatus.Delivered => "تحویل شده",
|
||||||
|
OrderStatus.ReadyForOfficePickup => "آماده تحویل در دفتر",
|
||||||
OrderStatus.Cancelled => "لغو شده",
|
OrderStatus.Cancelled => "لغو شده",
|
||||||
OrderStatus.Returned => "مرجوع شده",
|
OrderStatus.Returned => "مرجوع شده",
|
||||||
_ => "نامشخص"
|
_ => "نامشخص"
|
||||||
@@ -527,28 +530,31 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var headers = new[]
|
||||||
sb.AppendLine("شناسه,نام کاربر,موبایل,تاریخ ثبت,تعداد آیتم,مبلغ نهایی,تخفیف,وضعیت");
|
|
||||||
|
|
||||||
foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created))
|
|
||||||
{
|
{
|
||||||
sb.AppendLine(string.Join(",",
|
"شناسه", "نام مشتری", "موبایل", "تاریخ ثبت", "تعداد آیتم", "مبلغ نهایی", "تخفیف", "وضعیت"
|
||||||
|
};
|
||||||
|
|
||||||
|
var rows = _orders
|
||||||
|
.OrderBy(o => o.Created ?? o.PaymentDate ?? DateTime.MaxValue)
|
||||||
|
.Select(o => new object?[]
|
||||||
|
{
|
||||||
o.Id,
|
o.Id,
|
||||||
EscapeCsv(o.UserFullName),
|
CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
|
||||||
EscapeCsv(o.UserMobile),
|
o.UserMobile ?? "",
|
||||||
o.Created?.MiladiToJalaliWithTime() ?? "-",
|
o.Created?.MiladiToJalaliWithTime()
|
||||||
|
?? o.PaymentDate?.MiladiToJalaliWithTime()
|
||||||
|
?? "-",
|
||||||
o.ItemsCount,
|
o.ItemsCount,
|
||||||
o.GatewayAmount,
|
o.GatewayAmount,
|
||||||
o.DiscountBalanceUsed,
|
o.DiscountBalanceUsed,
|
||||||
GetStatusText(o.Status)));
|
GetStatusText(o.Status)
|
||||||
}
|
});
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
var filename = $"discount-sales-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||||
var base64 = Convert.ToBase64String(bytes);
|
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||||
var filename = $"discount-sales-{DateTime.Now:yyyyMMddHHmmss}.csv";
|
CsvExportHelper.ToExcelXmlBase64("گزارش فروش اعتباری", headers, rows));
|
||||||
|
Snackbar.Add("خروجی Excel آماده شد.", Severity.Success);
|
||||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
|
|
||||||
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExportToPdf()
|
private async Task ExportToPdf()
|
||||||
@@ -566,18 +572,18 @@
|
|||||||
|
|
||||||
sb.AppendLine("خلاصه:");
|
sb.AppendLine("خلاصه:");
|
||||||
sb.AppendLine($"تعداد سفارشها: {_totalOrders}");
|
sb.AppendLine($"تعداد سفارشها: {_totalOrders}");
|
||||||
sb.AppendLine($"مجموع فروش (مبلغ نهایی): {_totalSales:N0} ریال");
|
sb.AppendLine($"مجموع فروش (مبلغ نهایی): {_totalSales:N0} تومان");
|
||||||
sb.AppendLine($"مجموع تخفیف: {_totalDiscount:N0} ریال");
|
sb.AppendLine($"مجموع تخفیف: {_totalDiscount:N0} تومان");
|
||||||
sb.AppendLine($"میانگین مبلغ سفارش: {_averageOrder:N0} ریال");
|
sb.AppendLine($"میانگین مبلغ سفارش: {_averageOrder:N0} تومان");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
|
|
||||||
sb.AppendLine("جزئیات سفارشها:");
|
sb.AppendLine("جزئیات سفارشها:");
|
||||||
sb.AppendLine("شناسه | نام کاربر | تاریخ | تعداد آیتم | مبلغ نهایی | تخفیف | وضعیت");
|
sb.AppendLine("شناسه | نام مشتری | تاریخ | تعداد آیتم | مبلغ نهایی | تخفیف | وضعیت");
|
||||||
|
|
||||||
foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created))
|
foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created))
|
||||||
{
|
{
|
||||||
sb.AppendLine(
|
sb.AppendLine(
|
||||||
$"{o.Id} | {o.UserFullName} | {o.Created?.MiladiToJalaliWithTime() ?? "-"} | {o.ItemsCount} | {o.GatewayAmount:N0} | {o.DiscountBalanceUsed:N0} | {GetStatusText(o.Status)}");
|
$"{o.Id} | {CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId)} | {o.Created?.MiladiToJalaliWithTime() ?? "-"} | {o.ItemsCount} | {o.GatewayAmount:N0} | {o.DiscountBalanceUsed:N0} | {GetStatusText(o.Status)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
||||||
@@ -587,14 +593,4 @@
|
|||||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
|
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
|
||||||
Snackbar.Add("خروجی PDF (نسخه متنی) آماده شد.", Severity.Success);
|
Snackbar.Add("خروجی PDF (نسخه متنی) آماده شد.", Severity.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string EscapeCsv(string value)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(value))
|
|
||||||
return "";
|
|
||||||
|
|
||||||
var needsQuotes = value.Contains(',') || value.Contains('"') || value.Contains('\n');
|
|
||||||
var escaped = value.Replace("\"", "\"\"");
|
|
||||||
return needsQuotes ? $"\"{escaped}\"" : escaped;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
<MudStack Spacing="0">
|
<MudStack Spacing="0">
|
||||||
<MudText Typo="Typo.caption" Color="Color.Secondary">مبلغ کل استخر</MudText>
|
<MudText Typo="Typo.caption" Color="Color.Secondary">مبلغ کل استخر</MudText>
|
||||||
<MudText Typo="Typo.h6" Color="Color.Primary">
|
<MudText Typo="Typo.h6" Color="Color.Primary">
|
||||||
@(_currentPool?.TotalPoolAmount.ToString("N0") ?? "0") ریال
|
@(_currentPool?.TotalPoolAmount.ToString("N0") ?? "0") تومان
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@using CMSMicroservice.Protobuf.Protos.Inventory
|
@using CMSMicroservice.Protobuf.Protos.Inventory
|
||||||
@using Google.Protobuf.WellKnownTypes
|
@using Google.Protobuf.WellKnownTypes
|
||||||
@using InventoryProductType = CMSMicroservice.Protobuf.Protos.Inventory.ProductType
|
@using InventoryProductType = CMSMicroservice.Protobuf.Protos.Inventory.ProductType
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -19,12 +20,16 @@
|
|||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudNumericField T="long" @bind-Value="_productId"
|
@if (_productType == InventoryProductType.RegularProduct)
|
||||||
Label="شناسه محصول"
|
{
|
||||||
Variant="Variant.Outlined"
|
<ProductsAutoComplete @bind-Value="_productId"
|
||||||
Required="true"
|
Label="انتخاب محصول عادی" />
|
||||||
RequiredError="شناسه محصول الزامی است"
|
}
|
||||||
Min="1" />
|
else
|
||||||
|
{
|
||||||
|
<DiscountProductsAutoComplete @bind-Value="_discountProductId"
|
||||||
|
Label="انتخاب محصول تخفیفی" />
|
||||||
|
}
|
||||||
</MudItem>
|
</MudItem>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -103,7 +108,8 @@
|
|||||||
private bool _isSubmitting;
|
private bool _isSubmitting;
|
||||||
|
|
||||||
private InventoryProductType _productType = InventoryProductType.RegularProduct;
|
private InventoryProductType _productType = InventoryProductType.RegularProduct;
|
||||||
private long _productId;
|
private long? _productId;
|
||||||
|
private long? _discountProductId;
|
||||||
private long? _warehouseId;
|
private long? _warehouseId;
|
||||||
private int _quantity = 1;
|
private int _quantity = 1;
|
||||||
private string _referenceNumber = string.Empty;
|
private string _referenceNumber = string.Empty;
|
||||||
@@ -111,9 +117,12 @@
|
|||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
if (ProductIdParam.HasValue)
|
if (ProductIdParam.HasValue && ProductTypeParam.HasValue)
|
||||||
{
|
{
|
||||||
_productId = ProductIdParam.Value;
|
if (ProductTypeParam.Value == InventoryProductType.DiscountProduct)
|
||||||
|
_discountProductId = ProductIdParam.Value;
|
||||||
|
else
|
||||||
|
_productId = ProductIdParam.Value;
|
||||||
}
|
}
|
||||||
if (ProductTypeParam.HasValue)
|
if (ProductTypeParam.HasValue)
|
||||||
{
|
{
|
||||||
@@ -144,9 +153,19 @@
|
|||||||
_isSubmitting = true;
|
_isSubmitting = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var selectedProductId = ProductIdParam
|
||||||
|
?? (_productType == InventoryProductType.DiscountProduct ? _discountProductId : _productId);
|
||||||
|
|
||||||
|
if (!selectedProductId.HasValue || selectedProductId.Value <= 0)
|
||||||
|
{
|
||||||
|
Snackbar.Add("لطفاً یک محصول انتخاب کنید", Severity.Warning);
|
||||||
|
_isSubmitting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var request = new AddStockRequest
|
var request = new AddStockRequest
|
||||||
{
|
{
|
||||||
ProductId = ProductIdParam ?? _productId,
|
ProductId = selectedProductId.Value,
|
||||||
ProductType = ProductTypeParam ?? _productType,
|
ProductType = ProductTypeParam ?? _productType,
|
||||||
Quantity = _quantity,
|
Quantity = _quantity,
|
||||||
ReferenceNumber = _referenceNumber ?? string.Empty,
|
ReferenceNumber = _referenceNumber ?? string.Empty,
|
||||||
|
|||||||
@@ -33,10 +33,8 @@
|
|||||||
Min="0" />
|
Min="0" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="2">
|
<MudItem xs="12" md="2">
|
||||||
<MudNumericField @bind-Value="_maxBalance"
|
<PackageSelect Label="پکیج"
|
||||||
Label="حداکثر موجودی"
|
@bind-SelectedPackageId="_filterPackageId" />
|
||||||
Variant="Variant.Outlined"
|
|
||||||
Min="0" />
|
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="2" Class="d-flex align-center">
|
<MudItem xs="12" md="2" Class="d-flex align-center">
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled"
|
||||||
@@ -72,6 +70,14 @@
|
|||||||
<PropertyColumn Property="x => x.UserFullName" Title="نام کاربر" />
|
<PropertyColumn Property="x => x.UserFullName" Title="نام کاربر" />
|
||||||
<PropertyColumn Property="x => x.WeekDisplayName" Title="هفته" />
|
<PropertyColumn Property="x => x.WeekDisplayName" Title="هفته" />
|
||||||
|
|
||||||
|
<PropertyColumn Property="x => x.PackageTitle" Title="پکیج">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudChip T="string" Color="Color.Tertiary" Size="Size.Small">
|
||||||
|
@(string.IsNullOrEmpty(context.Item.PackageTitle) ? "-" : context.Item.PackageTitle)
|
||||||
|
</MudChip>
|
||||||
|
</CellTemplate>
|
||||||
|
</PropertyColumn>
|
||||||
|
|
||||||
<TemplateColumn Title="چپ (جدید + قبلی = مجموع)">
|
<TemplateColumn Title="چپ (جدید + قبلی = مجموع)">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||||
@@ -129,7 +135,7 @@
|
|||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="x => x.PoolContribution" Title="سهم از استخر">
|
<PropertyColumn Property="x => x.PoolContribution" Title="سهم از استخر">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.PoolContribution.ToString("N0") ریال
|
@context.Item.PoolContribution.ToString("N0") تومان
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="x => x.IsExpired" Title="وضعیت">
|
<PropertyColumn Property="x => x.IsExpired" Title="وضعیت">
|
||||||
@@ -193,6 +199,7 @@
|
|||||||
private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
||||||
private long? _filterUserId = null;
|
private long? _filterUserId = null;
|
||||||
private long? _filterWeekDefinitionId = null;
|
private long? _filterWeekDefinitionId = null;
|
||||||
|
private long? _filterPackageId = null;
|
||||||
private int? _minBalance = null;
|
private int? _minBalance = null;
|
||||||
private int? _maxBalance = null;
|
private int? _maxBalance = null;
|
||||||
private bool _onlyActive = false;
|
private bool _onlyActive = false;
|
||||||
@@ -222,6 +229,11 @@ private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
|||||||
request.WeekDefinitionId = _filterWeekDefinitionId.Value;
|
request.WeekDefinitionId = _filterWeekDefinitionId.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_filterPackageId.HasValue && _filterPackageId.Value > 0)
|
||||||
|
{
|
||||||
|
request.PackageId = _filterPackageId.Value;
|
||||||
|
}
|
||||||
|
|
||||||
var response = await CommissionClient.GetUserWeeklyBalancesAsync(request);
|
var response = await CommissionClient.GetUserWeeklyBalancesAsync(request);
|
||||||
|
|
||||||
var items = response.Models.Select(b => new UserWeeklyBalanceModel
|
var items = response.Models.Select(b => new UserWeeklyBalanceModel
|
||||||
@@ -238,7 +250,8 @@ private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
|||||||
MatchedBalance = b.TotalBalances,
|
MatchedBalance = b.TotalBalances,
|
||||||
PoolContribution = b.WeeklyPoolContribution,
|
PoolContribution = b.WeeklyPoolContribution,
|
||||||
CalculatedAt = b.CalculatedAt?.ToDateTime(),
|
CalculatedAt = b.CalculatedAt?.ToDateTime(),
|
||||||
IsExpired = b.IsExpired
|
IsExpired = b.IsExpired,
|
||||||
|
PackageTitle = b.PackageTitle
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
// Apply balance range filter if specified
|
// Apply balance range filter if specified
|
||||||
@@ -308,5 +321,6 @@ private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
|||||||
public long PoolContribution { get; set; }
|
public long PoolContribution { get; set; }
|
||||||
public DateTime? CalculatedAt { get; set; }
|
public DateTime? CalculatedAt { get; set; }
|
||||||
public bool IsExpired { get; set; }
|
public bool IsExpired { get; set; }
|
||||||
|
public string PackageTitle { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
@using CMSMicroservice.Protobuf.Protos.NetworkMembership
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<TitleContent>
|
||||||
|
<MudText Typo="Typo.h6">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.SwapHoriz" Class="ml-1" />
|
||||||
|
تغییر والد در شبکه
|
||||||
|
</MudText>
|
||||||
|
</TitleContent>
|
||||||
|
<DialogContent>
|
||||||
|
<MudAlert Severity="Severity.Warning" Dense="true" Class="mb-4">
|
||||||
|
⚠️ این عملیات حساس است و ساختار درخت شبکه را تغییر میدهد.
|
||||||
|
تاریخچه تغییر ثبت خواهد شد.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
<MudForm @ref="_form">
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudTextField T="string"
|
||||||
|
Label="کاربر فعلی"
|
||||||
|
Value="@UserDisplayName"
|
||||||
|
ReadOnly="true"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Person" />
|
||||||
|
|
||||||
|
<MudNumericField T="long"
|
||||||
|
@bind-Value="_newParentId"
|
||||||
|
Label="شناسه والد جدید"
|
||||||
|
Required="true"
|
||||||
|
RequiredError="شناسه والد جدید الزامی است"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.PersonSearch"
|
||||||
|
Min="1" />
|
||||||
|
|
||||||
|
<MudSelect T="int"
|
||||||
|
@bind-Value="_newLeg"
|
||||||
|
Label="ساق جدید"
|
||||||
|
Required="true"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.AccountTree">
|
||||||
|
<MudSelectItem T="int" Value="0">چپ (تیم اول)</MudSelectItem>
|
||||||
|
<MudSelectItem T="int" Value="1">راست (تیم دوم)</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
|
||||||
|
<MudTextField T="string"
|
||||||
|
@bind-Value="_reason"
|
||||||
|
Label="دلیل تغییر"
|
||||||
|
Required="true"
|
||||||
|
RequiredError="دلیل تغییر الزامی است"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="3"
|
||||||
|
Placeholder="دلیل جابجایی کاربر در شبکه را وارد کنید..."
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Description" />
|
||||||
|
</MudStack>
|
||||||
|
</MudForm>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton OnClick="Cancel" Color="Color.Default">انصراف</MudButton>
|
||||||
|
<MudButton OnClick="Submit"
|
||||||
|
Color="Color.Error"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Disabled="_isSubmitting"
|
||||||
|
StartIcon="@Icons.Material.Filled.SwapHoriz">
|
||||||
|
@if (_isSubmitting)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="ml-2" />
|
||||||
|
<span>در حال انجام...</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>تغییر والد</span>
|
||||||
|
}
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Network.Components;
|
||||||
|
|
||||||
|
public partial class ChangeParentDialog
|
||||||
|
{
|
||||||
|
[Parameter] public long UserId { get; set; }
|
||||||
|
[Parameter] public string UserDisplayName { get; set; } = "";
|
||||||
|
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||||
|
|
||||||
|
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; } = default!;
|
||||||
|
|
||||||
|
private MudForm _form = default!;
|
||||||
|
private long _newParentId;
|
||||||
|
private int _newLeg;
|
||||||
|
private string _reason = "";
|
||||||
|
private bool _isSubmitting;
|
||||||
|
|
||||||
|
private async Task Submit()
|
||||||
|
{
|
||||||
|
await _form.Validate();
|
||||||
|
if (!_form.IsValid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_newParentId == UserId)
|
||||||
|
{
|
||||||
|
Snackbar.Add("والد جدید نمیتواند خود کاربر باشد.", Severity.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isSubmitting = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new ChangeNetworkParentRequest
|
||||||
|
{
|
||||||
|
UserId = UserId,
|
||||||
|
NewParentId = _newParentId,
|
||||||
|
NewLeg = _newLeg,
|
||||||
|
Reason = _reason
|
||||||
|
};
|
||||||
|
|
||||||
|
await NetworkContract.ChangeNetworkParentAsync(request);
|
||||||
|
Snackbar.Add("والد کاربر با موفقیت تغییر کرد.", Severity.Success);
|
||||||
|
MudDialog.Close(DialogResult.Ok(true));
|
||||||
|
}
|
||||||
|
catch (Grpc.Core.RpcException ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Status.Detail}", Severity.Error);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در تغییر والد: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isSubmitting = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel() => MudDialog.Cancel();
|
||||||
|
}
|
||||||
@@ -169,6 +169,42 @@
|
|||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
|
|
||||||
|
<TemplateColumn Title="نوع فعالسازی" Sortable="false">
|
||||||
|
<CellTemplate>
|
||||||
|
@if (context.Item.IsNewActivation == null)
|
||||||
|
{
|
||||||
|
<span style="color: #bbb;">-</span>
|
||||||
|
}
|
||||||
|
else if (context.Item.IsNewActivation == true)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Success" Icon="@Icons.Material.Filled.FiberNew">
|
||||||
|
عضو جدید
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Secondary" Icon="@Icons.Material.Filled.Refresh">
|
||||||
|
خرید مجدد
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
|
||||||
|
<TemplateColumn Title="پکیج" Sortable="false">
|
||||||
|
<CellTemplate>
|
||||||
|
@if (!string.IsNullOrEmpty(context.Item.PackageName))
|
||||||
|
{
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Dark">
|
||||||
|
@context.Item.PackageName
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span style="color: #bbb;">-</span>
|
||||||
|
}
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
|
||||||
<TemplateColumn Title="عملیات" Sortable="false">
|
<TemplateColumn Title="عملیات" Sortable="false">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudButtonGroup Size="Size.Small" Variant="Variant.Text">
|
<MudButtonGroup Size="Size.Small" Variant="Variant.Text">
|
||||||
@@ -337,7 +373,9 @@
|
|||||||
isClubActive = n.IsClubActive,
|
isClubActive = n.IsClubActive,
|
||||||
isActivatedInTargetWeek = n.IsActivatedInTargetWeek,
|
isActivatedInTargetWeek = n.IsActivatedInTargetWeek,
|
||||||
activationWeekDefinitionId = n.ActivationWeekDefinitionId,
|
activationWeekDefinitionId = n.ActivationWeekDefinitionId,
|
||||||
clubActivatedAt = n.ClubActivatedAt?.ToDateTime().ToLocalTime().MiladiToJalali() ?? ""
|
clubActivatedAt = n.ClubActivatedAt?.ToDateTime().ToLocalTime().MiladiToJalali() ?? "",
|
||||||
|
isNewActivation = n.IsNewActivation,
|
||||||
|
packageName = n.PackageName ?? ""
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
var options = new { filterWeek = _activationWeekFilter };
|
var options = new { filterWeek = _activationWeekFilter };
|
||||||
|
|||||||
@@ -254,7 +254,7 @@
|
|||||||
<td><strong>کل کمیسیون کسب شده:</strong></td>
|
<td><strong>کل کمیسیون کسب شده:</strong></td>
|
||||||
<td>
|
<td>
|
||||||
<MudText Typo="Typo.body1" Color="Color.Primary">
|
<MudText Typo="Typo.body1" Color="Color.Primary">
|
||||||
<strong>@_userInfo.TotalEarnedCommission.ToString("N0") ریال</strong>
|
<strong>@_userInfo.TotalEarnedCommission.ToString("N0") تومان</strong>
|
||||||
</MudText>
|
</MudText>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
<td><strong>کمیسیون پرداخت شده:</strong></td>
|
<td><strong>کمیسیون پرداخت شده:</strong></td>
|
||||||
<td>
|
<td>
|
||||||
<MudText Typo="Typo.body1" Color="Color.Success">
|
<MudText Typo="Typo.body1" Color="Color.Success">
|
||||||
@_userInfo.TotalPaidCommission.ToString("N0") ریال
|
@_userInfo.TotalPaidCommission.ToString("N0") تومان
|
||||||
</MudText>
|
</MudText>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -270,7 +270,7 @@
|
|||||||
<td><strong>کمیسیون در انتظار:</strong></td>
|
<td><strong>کمیسیون در انتظار:</strong></td>
|
||||||
<td>
|
<td>
|
||||||
<MudText Typo="Typo.body1" Color="Color.Warning">
|
<MudText Typo="Typo.body1" Color="Color.Warning">
|
||||||
@_userInfo.PendingCommission.ToString("N0") ریال
|
@_userInfo.PendingCommission.ToString("N0") تومان
|
||||||
</MudText>
|
</MudText>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -300,9 +300,9 @@
|
|||||||
<MudSimpleTable Dense="true">
|
<MudSimpleTable Dense="true">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>پکیج طلایی:</strong></td>
|
<td><strong>خرید پکیج:</strong></td>
|
||||||
<td>
|
<td>
|
||||||
@if (_userInfo.HasPurchasedGoldenPackage)
|
@if (_userInfo.HasPurchasedPackage)
|
||||||
{
|
{
|
||||||
<MudChip T="string" Color="Color.Success" Size="Size.Small" Icon="@Icons.Material.Filled.CheckCircle">
|
<MudChip T="string" Color="Color.Success" Size="Size.Small" Icon="@Icons.Material.Filled.CheckCircle">
|
||||||
خریداری شده
|
خریداری شده
|
||||||
@@ -434,6 +434,12 @@
|
|||||||
OnClick="@(() => NavigationManager.NavigateTo($"/commission/payouts?userId={UserId}"))">
|
OnClick="@(() => NavigationManager.NavigateTo($"/commission/payouts?userId={UserId}"))">
|
||||||
پرداختهای کاربر
|
پرداختهای کاربر
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
<MudButton Variant="Variant.Outlined"
|
||||||
|
Color="Color.Warning"
|
||||||
|
StartIcon="@Icons.Material.Filled.SwapHoriz"
|
||||||
|
OnClick="OpenChangeParentDialog">
|
||||||
|
تغییر والد
|
||||||
|
</MudButton>
|
||||||
<MudButton Variant="Variant.Outlined"
|
<MudButton Variant="Variant.Outlined"
|
||||||
Color="Color.Secondary"
|
Color="Color.Secondary"
|
||||||
StartIcon="@Icons.Material.Filled.Refresh"
|
StartIcon="@Icons.Material.Filled.Refresh"
|
||||||
@@ -537,4 +543,28 @@
|
|||||||
_ => "نامشخص"
|
_ => "نامشخص"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OpenChangeParentDialog()
|
||||||
|
{
|
||||||
|
var displayName = $"{_userInfo.UserName} (ID: {UserId})";
|
||||||
|
var parameters = new DialogParameters<Network.Components.ChangeParentDialog>
|
||||||
|
{
|
||||||
|
{ x => x.UserId, UserId },
|
||||||
|
{ x => x.UserDisplayName, displayName }
|
||||||
|
};
|
||||||
|
var options = new DialogOptions
|
||||||
|
{
|
||||||
|
MaxWidth = MaxWidth.Small,
|
||||||
|
FullWidth = true,
|
||||||
|
CloseOnEscapeKey = true
|
||||||
|
};
|
||||||
|
var dialog = await DialogService.ShowAsync<Network.Components.ChangeParentDialog>("تغییر والد شبکه", parameters, options);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
await LoadUserInfo();
|
||||||
|
await LoadNetworkHistory();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@using CMSMicroservice.Protobuf.Protos.Package
|
@using CMSMicroservice.Protobuf.Protos.Package
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.Configuration
|
||||||
@using BackOffice.Common.BaseComponents
|
@using BackOffice.Common.BaseComponents
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Tizzani.MudBlazor.HtmlEditor
|
@using Tizzani.MudBlazor.HtmlEditor
|
||||||
@@ -41,12 +42,77 @@
|
|||||||
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" />
|
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="6">
|
<MudItem xs="6">
|
||||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت" Variant="Variant.Outlined" />
|
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت (ریال)" Variant="Variant.Outlined" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.SortOrder" Disabled="_isLoading" Label="ترتیب نمایش" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.ActivationFee" Format="N0" Immediate="true" Disabled="_isLoading" Label="هزینه فعالسازی (ریال)" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="double" HideSpinButtons="true" @bind-Value="Model.DiscountMultiplier" Disabled="_isLoading" Label="ضریب تخفیف" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="double" HideSpinButtons="true" @bind-Value="Model.MagicWalletMultiplier" Disabled="_isLoading" Label="ضریب کیف پول جادویی" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.MagicWalletMaxDeposit" Format="N0" Immediate="true" Disabled="_isLoading" Label="سقف واریز جادویی" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.MagicWalletMaxCredit" Format="N0" Immediate="true" Disabled="_isLoading" Label="سقف اعتبار جادویی" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
<MudItem xs="6">
|
||||||
|
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.MaxBalancesPerLeg" Disabled="_isLoading" Label="حداکثر تعادل هر پا" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6">
|
||||||
|
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.MaxNetworkLevel" Disabled="_isLoading" Label="حداکثر سطح شبکه" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudCheckBox T="bool" @bind-Value="Model.IsActive" Disabled="_isLoading" Label="فعال" Color="Color.Success" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudCheckBox T="bool" @bind-Value="Model.IsBasePackage" Disabled="_isLoading" Label="پکیج پایه" Color="Color.Primary" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudCheckBox T="bool" @bind-Value="Model.SupportsDirectPurchase" Disabled="_isLoading" Label="پرداخت مستقیم" Color="Color.Info" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudCheckBox T="bool" @bind-Value="Model.SupportsDayaPurchase" Disabled="_isLoading" Label="اعتبار دایا" Color="Color.Warning" />
|
||||||
|
</MudItem>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
@if (_features.Any())
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.subtitle1" Class="mt-4 mb-2"><strong>ویژگیهای پکیج</strong></MudText>
|
||||||
|
<MudStack Row="true" Wrap="Wrap.Wrap">
|
||||||
|
@foreach (var feature in _features)
|
||||||
|
{
|
||||||
|
<MudCheckBox T="bool"
|
||||||
|
Value="@(_selectedFeatureIds.Contains(feature.Id))"
|
||||||
|
ValueChanged="@((bool v) => ToggleFeature(feature.Id, v))"
|
||||||
|
Disabled="_isLoading"
|
||||||
|
Label="@feature.Title"
|
||||||
|
Color="Color.Primary" />
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudHtmlEditor @bind-Html="Model.Description" MinHeight="300px">
|
<MudHtmlEditor @bind-Html="Model.Description" MinHeight="300px">
|
||||||
<MudHtmlToolbarOptions InsertImage="false" /> <!-- This will exclude the "insert image" toolbar option -->
|
<MudHtmlToolbarOptions InsertImage="false" />
|
||||||
</MudHtmlEditor>
|
</MudHtmlEditor>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,5 @@
|
|||||||
@using CMSMicroservice.Protobuf.Protos.Package
|
@using CMSMicroservice.Protobuf.Protos.Package
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.Configuration
|
||||||
@using BackOffice.Common.BaseComponents
|
@using BackOffice.Common.BaseComponents
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Tizzani.MudBlazor.HtmlEditor
|
@using Tizzani.MudBlazor.HtmlEditor
|
||||||
@@ -42,12 +43,77 @@
|
|||||||
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" />
|
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="6">
|
<MudItem xs="6">
|
||||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Disabled="_isLoading" Label="قیمت" Variant="Variant.Outlined" />
|
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Disabled="_isLoading" Label="قیمت (ریال)" Variant="Variant.Outlined" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.SortOrder" Disabled="_isLoading" Label="ترتیب نمایش" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.ActivationFee" Format="N0" Immediate="true" Disabled="_isLoading" Label="هزینه فعالسازی (ریال)" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="double" HideSpinButtons="true" @bind-Value="Model.DiscountMultiplier" Disabled="_isLoading" Label="ضریب تخفیف" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="double" HideSpinButtons="true" @bind-Value="Model.MagicWalletMultiplier" Disabled="_isLoading" Label="ضریب کیف پول جادویی" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.MagicWalletMaxDeposit" Format="N0" Immediate="true" Disabled="_isLoading" Label="سقف واریز جادویی" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4">
|
||||||
|
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.MagicWalletMaxCredit" Format="N0" Immediate="true" Disabled="_isLoading" Label="سقف اعتبار جادویی" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
<MudItem xs="6">
|
||||||
|
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.MaxBalancesPerLeg" Disabled="_isLoading" Label="حداکثر تعادل هر پا" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6">
|
||||||
|
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.MaxNetworkLevel" Disabled="_isLoading" Label="حداکثر سطح شبکه" Variant="Variant.Outlined" />
|
||||||
|
</MudItem>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudCheckBox T="bool" @bind-Value="Model.IsActive" Disabled="_isLoading" Label="فعال" Color="Color.Success" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudCheckBox T="bool" @bind-Value="Model.IsBasePackage" Disabled="_isLoading" Label="پکیج پایه" Color="Color.Primary" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudCheckBox T="bool" @bind-Value="Model.SupportsDirectPurchase" Disabled="_isLoading" Label="پرداخت مستقیم" Color="Color.Info" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="3">
|
||||||
|
<MudCheckBox T="bool" @bind-Value="Model.SupportsDayaPurchase" Disabled="_isLoading" Label="اعتبار دایا" Color="Color.Warning" />
|
||||||
|
</MudItem>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
@if (_features.Any())
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.subtitle1" Class="mt-4 mb-2"><strong>ویژگیهای پکیج</strong></MudText>
|
||||||
|
<MudStack Row="true" Wrap="Wrap.Wrap">
|
||||||
|
@foreach (var feature in _features)
|
||||||
|
{
|
||||||
|
<MudCheckBox T="bool"
|
||||||
|
Value="@(_selectedFeatureIds.Contains(feature.Id))"
|
||||||
|
ValueChanged="@((bool v) => ToggleFeature(feature.Id, v))"
|
||||||
|
Disabled="_isLoading"
|
||||||
|
Label="@feature.Title"
|
||||||
|
Color="Color.Primary" />
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudHtmlEditor @bind-Html="Model.Description" MinHeight="300px">
|
<MudHtmlEditor @bind-Html="Model.Description" MinHeight="300px">
|
||||||
<MudHtmlToolbarOptions InsertImage="false" /> <!-- This will exclude the "insert image" toolbar option -->
|
<MudHtmlToolbarOptions InsertImage="false" />
|
||||||
</MudHtmlEditor>
|
</MudHtmlEditor>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -12,13 +12,21 @@
|
|||||||
</MudItem>
|
</MudItem>
|
||||||
</Filters>
|
</Filters>
|
||||||
<Content>
|
<Content>
|
||||||
|
@* ── G8: راهنمای مدیریت پکیج (Q28) ── *@
|
||||||
|
<MudAlert Severity="Severity.Warning" Variant="Variant.Text" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Warning">
|
||||||
|
تغییر قیمت روی کاربران فعلی اثر ندارد — فقط خریدهای جدید. هر پکیج Pool پورسانت مستقل دارد.
|
||||||
|
</MudAlert>
|
||||||
<MudDataGrid T="DataModel" ServerData="@(new Func<GridState<DataModel>, Task<GridData<DataModel>>>(ServerReload))"
|
<MudDataGrid T="DataModel" ServerData="@(new Func<GridState<DataModel>, Task<GridData<DataModel>>>(ServerReload))"
|
||||||
Hover="true" @ref="_gridData" Height="calc(100vh - 240px)">
|
Hover="true" @ref="_gridData" Height="calc(100vh - 240px)">
|
||||||
<ColGroup>
|
<ColGroup>
|
||||||
<col />
|
<col />
|
||||||
<col />
|
<col />
|
||||||
<col />
|
<col />
|
||||||
<col style="width: 58px;" />
|
<col />
|
||||||
|
<col />
|
||||||
|
<col />
|
||||||
|
<col />
|
||||||
|
<col style="width: 80px;" />
|
||||||
</ColGroup>
|
</ColGroup>
|
||||||
<ToolBarContent>
|
<ToolBarContent>
|
||||||
<MudText>مدیریت پکیج</MudText>
|
<MudText>مدیریت پکیج</MudText>
|
||||||
@@ -51,31 +59,42 @@
|
|||||||
|
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="x => x.Description" Title="توضیحات" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
<PropertyColumn Property="x => x.Price" Title="قیمت" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@if (string.IsNullOrWhiteSpace(context.Item.Description))
|
<MudText Typo="Typo.inherit">@context.Item.Price.ToString("N0")</MudText>
|
||||||
|
</CellTemplate>
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="x => x.SortOrder" Title="ترتیب" />
|
||||||
|
<PropertyColumn Property="x => x.IsActive" Title="وضعیت" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||||
|
<CellTemplate>
|
||||||
|
@if (context.Item.IsActive)
|
||||||
{
|
{
|
||||||
<MudText Typo="Typo.inherit">-</MudText>
|
<MudChip T="string" Color="Color.Success" Variant="Variant.Filled" Size="Size.Small">فعال</MudChip>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<MudChip T="string" Color="Color.Default" Variant="Variant.Filled" Size="Size.Small">غیرفعال</MudChip>
|
||||||
<MudTooltip Text="@(context.Item.Description.HtmlToText())" Arrow="true" Style="@($"{(context.Item.Description.Length < 70 ? string.Empty : "width:600px;")}")">
|
|
||||||
<MudText Typo="Typo.inherit" Style="text-wrap: nowrap;">
|
|
||||||
@(context.Item.Description.HtmlToText().Truncate(20, true))
|
|
||||||
</MudText>
|
|
||||||
</MudTooltip>
|
|
||||||
}
|
}
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="x => x.IsBasePackage" Title="نوع" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||||
<TemplateColumn StickyLeft="true" Title="عملیات" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
@if (context.Item.IsBasePackage)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Primary" Variant="Variant.Filled" Size="Size.Small">پایه</MudChip>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined" Size="Size.Small">عادی</MudChip>
|
||||||
|
}
|
||||||
|
</CellTemplate>
|
||||||
|
</PropertyColumn>
|
||||||
|
<TemplateColumn Title="">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
<MudTooltip Text="ویرایش">
|
<MudTooltip Text="ویرایش">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.EditNote" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => Update(context.Item.Adapt<DataModel>()))" />
|
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => Update(context.Item))" />
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
|
|
||||||
<MudTooltip Text="آرشیو">
|
<MudTooltip Text="آرشیو">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OnDelete(context.Item))" />
|
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OnDelete(context.Item))" />
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public partial class PackageMainPage
|
|||||||
{
|
{
|
||||||
var parameters = new DialogParameters<UpdateDialog> { { x => x.Model, model.Adapt<UpdatePackageRequest>() } };
|
var parameters = new DialogParameters<UpdateDialog> { { x => x.Model, model.Adapt<UpdatePackageRequest>() } };
|
||||||
|
|
||||||
var dialog = await DialogService.ShowAsync<UpdateDialog>($"ویرایش پکیج", parameters, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
|
var dialog = await DialogService.ShowAsync<UpdateDialog>($"ویرایش پکیج", parameters, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Medium });
|
||||||
var result = await dialog.Result;
|
var result = await dialog.Result;
|
||||||
|
|
||||||
if (!result.Canceled)
|
if (!result.Canceled)
|
||||||
@@ -70,7 +70,7 @@ public partial class PackageMainPage
|
|||||||
|
|
||||||
public async Task CreateNew()
|
public async Task CreateNew()
|
||||||
{
|
{
|
||||||
var dialog = await DialogService.ShowAsync<CreateDialog>($"افزودن پکیج", new DialogParameters<CreateDialog>() { { x => x.Model, new CreateNewPackageRequest() } }, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
|
var dialog = await DialogService.ShowAsync<CreateDialog>($"افزودن پکیج", new DialogParameters<CreateDialog>() { { x => x.Model, new CreateNewPackageRequest() { IsActive = true, SupportsDirectPurchase = true, DiscountMultiplier = 2.0, MagicWalletMultiplier = 2.5, MaxBalancesPerLeg = 300, MaxNetworkLevel = 15, MagicWalletMaxDeposit = 1_000_000_000, MagicWalletMaxCredit = 2_500_000_000 } } }, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Medium });
|
||||||
var result = await dialog.Result;
|
var result = await dialog.Result;
|
||||||
if (!result.Canceled)
|
if (!result.Canceled)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.UserWallet
|
||||||
|
|
||||||
|
@inject UserWalletContract.UserWalletContractClient WalletClient
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudAlert Severity="Severity.Warning" Dense="true" Variant="Variant.Outlined">
|
||||||
|
مبلغ مستقیماً به کیف پول اصلی (Balance) کاربر اضافه میشود و در دفتر مالی با نوع «شارژ دستی کیف اصلی» ثبت میگردد.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
|
||||||
|
|
||||||
|
<MudNumericField T="long" @bind-Value="_amount"
|
||||||
|
Label="مبلغ (تومان)"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Min="10000"
|
||||||
|
HelperText="حداقل ۱۰,۰۰۰ تومان" />
|
||||||
|
|
||||||
|
<MudTextField T="string" @bind-Value="_note"
|
||||||
|
Label="توضیحات ادمین (اختیاری)"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="2" />
|
||||||
|
</MudStack>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton OnClick="Cancel" Disabled="_isSubmitting">انصراف</MudButton>
|
||||||
|
<MudButton Color="Color.Primary" Variant="Variant.Filled"
|
||||||
|
OnClick="Submit" Disabled="_isSubmitting || !_userId.HasValue || _amount < 10000">
|
||||||
|
@if (_isSubmitting)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||||||
|
}
|
||||||
|
ثبت شارژ
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||||
|
|
||||||
|
private long? _userId;
|
||||||
|
private long _amount = 10000;
|
||||||
|
private string? _note;
|
||||||
|
private bool _isSubmitting;
|
||||||
|
|
||||||
|
private void Cancel() => MudDialog.Cancel();
|
||||||
|
|
||||||
|
private async Task Submit()
|
||||||
|
{
|
||||||
|
if (!_userId.HasValue || _userId.Value <= 0)
|
||||||
|
{
|
||||||
|
Snackbar.Add("لطفاً کاربر را انتخاب کنید", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_amount < 10000)
|
||||||
|
{
|
||||||
|
Snackbar.Add("حداقل مبلغ شارژ ۱۰,۰۰۰ تومان است", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool? confirm = await DialogService.ShowMessageBox(
|
||||||
|
"تأیید شارژ دستی",
|
||||||
|
$"آیا از شارژ {_amount:N0} تومان به کیف پول اصلی کاربر #{_userId.Value} مطمئن هستید؟",
|
||||||
|
yesText: "بله، شارژ شود", cancelText: "لغو");
|
||||||
|
|
||||||
|
if (confirm != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isSubmitting = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new AdminManualCreditChargeRequest
|
||||||
|
{
|
||||||
|
UserId = _userId.Value,
|
||||||
|
Amount = _amount
|
||||||
|
};
|
||||||
|
if (!string.IsNullOrWhiteSpace(_note))
|
||||||
|
request.Note = _note.Trim();
|
||||||
|
|
||||||
|
var response = await WalletClient.AdminManualCreditChargeAsync(request);
|
||||||
|
|
||||||
|
if (response.Success)
|
||||||
|
{
|
||||||
|
Snackbar.Add(
|
||||||
|
$"شارژ موفق — تراکنش #{response.TransactionId}، موجودی جدید: {response.NewBalance:N0}",
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@using CMSMicroservice.Protobuf.Protos.ManualPayment
|
@using CMSMicroservice.Protobuf.Protos.ManualPayment
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.Package
|
||||||
@using BackOffice.Pages.Payment.Components
|
@using BackOffice.Pages.Payment.Components
|
||||||
@using BackOffice.Pages.AutoComplete
|
@using BackOffice.Pages.AutoComplete
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@@ -13,21 +14,36 @@
|
|||||||
<UserAutoComplete Label="انتخاب کاربر"
|
<UserAutoComplete Label="انتخاب کاربر"
|
||||||
@bind-SelectedUserId="_selectedUserId" />
|
@bind-SelectedUserId="_selectedUserId" />
|
||||||
|
|
||||||
<MudNumericField T="long"
|
<MudSelect T="long?"
|
||||||
Label="مبلغ"
|
Label="انتخاب پکیج"
|
||||||
Variant="Variant.Outlined"
|
Value="@_selectedPackageId"
|
||||||
Adornment="Adornment.End"
|
ValueChanged="OnPackageChanged"
|
||||||
AdornmentText="تومان"
|
Variant="Variant.Outlined"
|
||||||
Disabled="true"
|
Margin="Margin.Dense"
|
||||||
@bind-Value="_createModel.Amount" />
|
Required="true"
|
||||||
|
RequiredError="انتخاب پکیج الزامی است">
|
||||||
|
@foreach (var pkg in _packages)
|
||||||
|
{
|
||||||
|
<MudSelectItem T="long?" Value="@((long?)pkg.Id)">
|
||||||
|
@pkg.Title — @pkg.Price.ToString("N0") تومان
|
||||||
|
</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
|
||||||
|
@if (_selectedPackagePrice.HasValue)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text">
|
||||||
|
مبلغ پکیج: <strong>@_selectedPackagePrice.Value.ToString("N0") تومان</strong> — این مبلغ به کیفپول کاربر اضافه میشود.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
<MudSelect T="int"
|
<MudSelect T="int"
|
||||||
Label="نوع پرداخت"
|
Label="نوع پرداخت"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
@bind-Value="_selectedType">
|
@bind-Value="_selectedType">
|
||||||
<MudSelectItem T="int" Value="1">واریز نقدی</MudSelectItem>
|
<MudSelectItem T="int" Value="1">واریز نقدی</MudSelectItem>
|
||||||
<MudSelectItem T="int" Value="2">شارژ کیف پول تخفیف</MudSelectItem>
|
<MudSelectItem T="int" Value="2">شارژ کیف پول اعتباری</MudSelectItem>
|
||||||
<MudSelectItem T="int" Value="3">شارژ کیف پول شبکه</MudSelectItem>
|
<MudSelectItem T="int" Value="3">شارژ پاداش های دریافتی</MudSelectItem>
|
||||||
<MudSelectItem T="int" Value="4">تسویه حساب</MudSelectItem>
|
<MudSelectItem T="int" Value="4">تسویه حساب</MudSelectItem>
|
||||||
<MudSelectItem T="int" Value="5">اصلاح خطا</MudSelectItem>
|
<MudSelectItem T="int" Value="5">اصلاح خطا</MudSelectItem>
|
||||||
<MudSelectItem T="int" Value="6">بازپرداخت</MudSelectItem>
|
<MudSelectItem T="int" Value="6">بازپرداخت</MudSelectItem>
|
||||||
@@ -83,7 +99,7 @@
|
|||||||
<MudText Typo="Typo.h6">جزئیات فعالسازی دستی</MudText>
|
<MudText Typo="Typo.h6">جزئیات فعالسازی دستی</MudText>
|
||||||
<MudText Typo="Typo.body2">شناسه: @Model.Id</MudText>
|
<MudText Typo="Typo.body2">شناسه: @Model.Id</MudText>
|
||||||
<MudText Typo="Typo.body2">کاربر: @Model.UserFullName (@Model.UserId)</MudText>
|
<MudText Typo="Typo.body2">کاربر: @Model.UserFullName (@Model.UserId)</MudText>
|
||||||
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") ریال</MudText>
|
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") تومان</MudText>
|
||||||
<MudText Typo="Typo.body2">نوع: @Model.TypeDisplay</MudText>
|
<MudText Typo="Typo.body2">نوع: @Model.TypeDisplay</MudText>
|
||||||
<MudText Typo="Typo.body2">وضعیت: @Model.StatusDisplay</MudText>
|
<MudText Typo="Typo.body2">وضعیت: @Model.StatusDisplay</MudText>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using CMSMicroservice.Protobuf.Protos.ManualPayment;
|
using CMSMicroservice.Protobuf.Protos.ManualPayment;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.Package;
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.Forms;
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
@@ -24,19 +25,23 @@ public partial class ManualPaymentDialog
|
|||||||
{
|
{
|
||||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||||
[Inject] public ManualPaymentContract.ManualPaymentContractClient ManualPaymentClient { get; set; } = default!;
|
[Inject] public ManualPaymentContract.ManualPaymentContractClient ManualPaymentClient { get; set; } = default!;
|
||||||
|
[Inject] public PackageContract.PackageContractClient PackageClient { get; set; } = default!;
|
||||||
// Snackbar is injected via _Imports.razor
|
// Snackbar is injected via _Imports.razor
|
||||||
|
|
||||||
[Parameter] public ManualPaymentDialogMode Mode { get; set; }
|
[Parameter] public ManualPaymentDialogMode Mode { get; set; }
|
||||||
[Parameter] public ManualPaymentModel? Model { get; set; }
|
[Parameter] public ManualPaymentModel? Model { get; set; }
|
||||||
|
|
||||||
private ManualPaymentModel _createModel = new(){
|
private ManualPaymentModel _createModel = new(){ Amount = 0 };
|
||||||
Amount = 56000000,
|
|
||||||
};
|
|
||||||
private string? _adminNote;
|
private string? _adminNote;
|
||||||
|
|
||||||
// User selection
|
// User selection
|
||||||
private long? _selectedUserId;
|
private long? _selectedUserId;
|
||||||
|
|
||||||
|
// Package selection
|
||||||
|
private long? _selectedPackageId;
|
||||||
|
private long? _selectedPackagePrice;
|
||||||
|
private List<GetAllPackageByFilterResponseModel> _packages = new();
|
||||||
|
|
||||||
// Type and Description
|
// Type and Description
|
||||||
private int _selectedType = 1; // Default: CashDeposit
|
private int _selectedType = 1; // Default: CashDeposit
|
||||||
private string _description = "عضویت دستی باشگاه مشتریان";
|
private string _description = "عضویت دستی باشگاه مشتریان";
|
||||||
@@ -48,6 +53,46 @@ public partial class ManualPaymentDialog
|
|||||||
|
|
||||||
private bool _isSubmitting;
|
private bool _isSubmitting;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadPackages()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new GetAllPackageByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new() { PageNumber = 1, PageSize = 100 },
|
||||||
|
Filter = new()
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await PackageClient.GetAllPackageByFilterAsync(request);
|
||||||
|
_packages = result.Models?.OrderBy(p => p.SortOrder).ToList()
|
||||||
|
?? new List<GetAllPackageByFilterResponseModel>();
|
||||||
|
|
||||||
|
// اگر پکیج پایه وجود دارد، پیشفرض انتخاب کن
|
||||||
|
var basePackage = _packages.FirstOrDefault(p => p.IsBasePackage);
|
||||||
|
if (basePackage != null)
|
||||||
|
{
|
||||||
|
_selectedPackageId = basePackage.Id;
|
||||||
|
_selectedPackagePrice = basePackage.Price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_packages = new List<GetAllPackageByFilterResponseModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPackageChanged(long? packageId)
|
||||||
|
{
|
||||||
|
_selectedPackageId = packageId;
|
||||||
|
var pkg = _packages.FirstOrDefault(p => p.Id == packageId);
|
||||||
|
_selectedPackagePrice = pkg?.Price;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnImageFileSelected(IBrowserFile? file)
|
private async Task OnImageFileSelected(IBrowserFile? file)
|
||||||
{
|
{
|
||||||
if (file == null)
|
if (file == null)
|
||||||
@@ -81,6 +126,12 @@ public partial class ManualPaymentDialog
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_selectedPackageId.HasValue || _selectedPackageId.Value <= 0)
|
||||||
|
{
|
||||||
|
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_selectedType <= 0)
|
if (_selectedType <= 0)
|
||||||
{
|
{
|
||||||
Snackbar.Add("لطفاً نوع پرداخت را انتخاب کنید.", Severity.Warning);
|
Snackbar.Add("لطفاً نوع پرداخت را انتخاب کنید.", Severity.Warning);
|
||||||
@@ -101,7 +152,8 @@ public partial class ManualPaymentDialog
|
|||||||
var request = new CreateManualPaymentRequest
|
var request = new CreateManualPaymentRequest
|
||||||
{
|
{
|
||||||
UserId = _selectedUserId.Value,
|
UserId = _selectedUserId.Value,
|
||||||
Amount = _createModel.Amount,
|
Amount = 0, // deprecated — مبلغ از پکیج خوانده میشود
|
||||||
|
PackageId = _selectedPackageId.Value,
|
||||||
Type = (ManualPaymentType)_selectedType,
|
Type = (ManualPaymentType)_selectedType,
|
||||||
Description = _description
|
Description = _description
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<MudText Typo="Typo.h6">جزئیات تراکنش</MudText>
|
<MudText Typo="Typo.h6">جزئیات تراکنش</MudText>
|
||||||
<MudText Typo="Typo.body2">شناسه: @Model.Id</MudText>
|
<MudText Typo="Typo.body2">شناسه: @Model.Id</MudText>
|
||||||
<MudText Typo="Typo.body2">RefId: @Model.RefId</MudText>
|
<MudText Typo="Typo.body2">RefId: @Model.RefId</MudText>
|
||||||
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") ریال</MudText>
|
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") تومان</MudText>
|
||||||
<MudText Typo="Typo.body2">وضعیت: @StatusText</MudText>
|
<MudText Typo="Typo.body2">وضعیت: @StatusText</MudText>
|
||||||
<MudText Typo="Typo.body2">تاریخ پرداخت: @Model.PaymentDate.ToLocalTime().MiladiToJalaliWithTime()</MudText>
|
<MudText Typo="Typo.body2">تاریخ پرداخت: @Model.PaymentDate.ToLocalTime().MiladiToJalaliWithTime()</MudText>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,247 @@
|
|||||||
|
@page "/payment/ledger-transactions"
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using CMSMicroservice.Protobuf.Protos
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.Transactions
|
||||||
|
@using Google.Protobuf.WellKnownTypes
|
||||||
|
|
||||||
|
@inject TransactionsContract.TransactionsContractClient TransactionsClient
|
||||||
|
|
||||||
|
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||||
|
<Filters>
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true">
|
||||||
|
گزارش read-only دفتر تراکنشهای مالی (جدول Transactions). برای جزئیات درگاه پرداخت به «گزارش تراکنش درگاه» مراجعه کنید.
|
||||||
|
</MudAlert>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudTextField T="long?" Label="شناسه" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_id" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudTextField T="string" Label="RefId" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_refId" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudTextField T="string" Label="توضیحات" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_description" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudSelect T="PaymentStatus?" Clearable="true" Label="وضعیت پرداخت" Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" @bind-Value="_paymentStatus">
|
||||||
|
<MudSelectItem T="PaymentStatus?" Value="PaymentStatus.Success">موفق</MudSelectItem>
|
||||||
|
<MudSelectItem T="PaymentStatus?" Value="PaymentStatus.Pending">در انتظار</MudSelectItem>
|
||||||
|
<MudSelectItem T="PaymentStatus?" Value="PaymentStatus.Reject">رد شده</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudSelect T="TransactionType?" Clearable="true" Label="نوع تراکنش" Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" @bind-Value="_type">
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.Buy">خرید</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.DepositIpg">واریز درگاه</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.DepositExternal1">واریز خارجی</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.Withdraw">برداشت</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.NetworkCommission">کمیسیون شبکه</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.ClubActivation">فعالسازی باشگاه</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.DiscountWalletCharge">شارژ کیف تخفیف</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.DiscountShopPurchase">خرید فروشگاه تخفیف</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.MagicWalletDeposit">واریز کیف جادویی</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.MagicWalletBonus">بونوس کیف جادویی</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.CreditWalletCharge">شارژ کیف اصلی</MudSelectItem>
|
||||||
|
<MudSelectItem T="TransactionType?" Value="TransactionType.ManualCreditWalletCharge">شارژ دستی کیف اصلی</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="4">
|
||||||
|
<DateRangePicker @bind-From="_createdFrom" @bind-To="_createdTo" Label="بازه ثبت" />
|
||||||
|
</MudItem>
|
||||||
|
</Filters>
|
||||||
|
<Content>
|
||||||
|
<MudGrid Class="mb-3" Spacing="2">
|
||||||
|
<MudItem xs="6" sm="3">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">تعداد</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@_summaryTotalCount.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="3">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">جمع مبلغ (تومان)</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@_summaryTotalAmount.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="2">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">موفق</MudText>
|
||||||
|
<MudText Typo="Typo.h6" Color="Color.Success">@_summarySuccess.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="2">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">در انتظار</MudText>
|
||||||
|
<MudText Typo="Typo.h6" Color="Color.Warning">@_summaryPending.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="2">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">رد شده</MudText>
|
||||||
|
<MudText Typo="Typo.h6" Color="Color.Error">@_summaryReject.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
<MudDataGrid @ref="_dataGrid" T="GetAllTransactionsByFilterResponseModel"
|
||||||
|
ServerData="LoadData" ReadOnly="true"
|
||||||
|
Hover="true" Dense="true" Height="calc(100vh - 400px)">
|
||||||
|
<ToolBarContent>
|
||||||
|
<MudText Typo="Typo.subtitle1">دفتر تراکنشهای مالی</MudText>
|
||||||
|
<MudSpacer />
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Info" Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.FileDownload" OnClick="ExportCsv">CSV</MudButton>
|
||||||
|
</ToolBarContent>
|
||||||
|
<Columns>
|
||||||
|
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||||
|
<TemplateColumn Title="نوع">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudChip T="string" Size="Size.Small" Variant="Variant.Text">
|
||||||
|
@ReportLabels.GetTransactionTypeLabel(context.Item.HasType ? context.Item.Type : null)
|
||||||
|
</MudChip>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="مبلغ (تومان)">
|
||||||
|
<CellTemplate>@context.Item.Amount.ToString("N0")</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<PropertyColumn Property="x => x.Description" Title="توضیحات" />
|
||||||
|
<TemplateColumn Title="وضعیت">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="@ReportLabels.GetTransactionPaymentStatusColor(context.Item.HasPaymentStatus ? context.Item.PaymentStatus : null)">
|
||||||
|
@ReportLabels.GetTransactionPaymentStatusLabel(context.Item.HasPaymentStatus ? context.Item.PaymentStatus : null)
|
||||||
|
</MudChip>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<PropertyColumn Property="x => x.RefId" Title="RefId" />
|
||||||
|
<TemplateColumn Title="تاریخ پرداخت">
|
||||||
|
<CellTemplate>
|
||||||
|
@(context.Item.PaymentDate != null
|
||||||
|
? context.Item.PaymentDate.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()
|
||||||
|
: "—")
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="تاریخ ثبت">
|
||||||
|
<CellTemplate>@context.Item.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
<NoRecordsContent>
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">
|
||||||
|
با فیلتر فعلی موردی یافت نشد — بازه تاریخ یا فیلترها را تغییر دهید.
|
||||||
|
</MudAlert>
|
||||||
|
</NoRecordsContent>
|
||||||
|
<PagerContent>
|
||||||
|
<MudDataGridPager T="GetAllTransactionsByFilterResponseModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||||
|
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||||
|
RowsPerPageString="تعداد در صفحه" />
|
||||||
|
</PagerContent>
|
||||||
|
</MudDataGrid>
|
||||||
|
</Content>
|
||||||
|
</BasePageComponent>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private BasePageComponent? _basePage;
|
||||||
|
private MudDataGrid<GetAllTransactionsByFilterResponseModel>? _dataGrid;
|
||||||
|
private long? _id;
|
||||||
|
private string? _refId, _description;
|
||||||
|
private PaymentStatus? _paymentStatus;
|
||||||
|
private TransactionType? _type;
|
||||||
|
private DateTime? _createdFrom, _createdTo;
|
||||||
|
private long _summaryTotalCount, _summaryTotalAmount, _summarySuccess, _summaryPending, _summaryReject;
|
||||||
|
|
||||||
|
private GetAllTransactionsByFilterFilter BuildFilter()
|
||||||
|
{
|
||||||
|
var filter = new GetAllTransactionsByFilterFilter
|
||||||
|
{
|
||||||
|
Id = _id > 0 ? _id : null,
|
||||||
|
RefId = string.IsNullOrWhiteSpace(_refId) ? null : _refId,
|
||||||
|
Description = string.IsNullOrWhiteSpace(_description) ? null : _description,
|
||||||
|
CreatedFrom = _createdFrom.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_createdFrom.Value, DateTimeKind.Utc)) : null,
|
||||||
|
CreatedTo = _createdTo.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_createdTo.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_paymentStatus.HasValue)
|
||||||
|
filter.PaymentStatus = _paymentStatus.Value;
|
||||||
|
else
|
||||||
|
filter.ClearPaymentStatusItem();
|
||||||
|
|
||||||
|
if (_type.HasValue)
|
||||||
|
filter.Type = _type.Value;
|
||||||
|
else
|
||||||
|
filter.ClearTypeItem();
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadSummary()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var s = await TransactionsClient.GetTransactionSummaryAsync(new GetTransactionSummaryRequest { Filter = BuildFilter() });
|
||||||
|
_summaryTotalCount = s.TotalCount;
|
||||||
|
_summaryTotalAmount = s.TotalAmount;
|
||||||
|
_summarySuccess = s.SuccessCount;
|
||||||
|
_summaryPending = s.PendingCount;
|
||||||
|
_summaryReject = s.RejectCount;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<GridData<GetAllTransactionsByFilterResponseModel>> LoadData(GridState<GetAllTransactionsByFilterResponseModel> state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LoadSummary();
|
||||||
|
var result = await TransactionsClient.GetAllTransactionsByFilterAsync(new GetAllTransactionsByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||||
|
SortBy = "-Created",
|
||||||
|
Filter = BuildFilter()
|
||||||
|
});
|
||||||
|
return new GridData<GetAllTransactionsByFilterResponseModel>
|
||||||
|
{
|
||||||
|
Items = result?.Models?.ToList() ?? new(),
|
||||||
|
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
return new GridData<GetAllTransactionsByFilterResponseModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFilterSubmit() { if (_dataGrid != null) await _dataGrid.ReloadServerData(); }
|
||||||
|
private async Task OnFilterCleared()
|
||||||
|
{
|
||||||
|
_id = null; _refId = _description = null;
|
||||||
|
_paymentStatus = null; _type = null;
|
||||||
|
_createdFrom = _createdTo = null;
|
||||||
|
if (_dataGrid != null) await _dataGrid.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExportCsv()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await TransactionsClient.GetAllTransactionsByFilterAsync(new GetAllTransactionsByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new PaginationState { PageNumber = 1, PageSize = 5000 },
|
||||||
|
SortBy = "-Created",
|
||||||
|
Filter = BuildFilter()
|
||||||
|
});
|
||||||
|
var sb = new System.Text.StringBuilder();
|
||||||
|
sb.AppendLine("Id,Type,Amount,Description,Status,RefId,PaymentDate,Created");
|
||||||
|
foreach (var r in result.Models)
|
||||||
|
{
|
||||||
|
var type = r.HasType ? ReportLabels.GetTransactionTypeLabel(r.Type) : "";
|
||||||
|
var status = r.HasPaymentStatus ? ReportLabels.GetTransactionPaymentStatusLabel(r.PaymentStatus) : "";
|
||||||
|
var paymentDate = r.PaymentDate != null ? r.PaymentDate.ToDateTime().ToString("yyyy-MM-dd HH:mm") : "";
|
||||||
|
sb.AppendLine($"{r.Id},{type},{r.Amount},\"{r.Description}\",{status},{r.RefId},{paymentDate},{r.Created.ToDateTime():yyyy-MM-dd HH:mm}");
|
||||||
|
}
|
||||||
|
await jsRuntime.InvokeVoidAsync("jsSaveAsFile", "ledger-transactions.csv", sb.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Snackbar.Add($"خطا در export: {ex.Message}", Severity.Error); }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
@page "/payment/manual-credit-charges"
|
||||||
|
@attribute [Authorize(Roles = "Administrator")]
|
||||||
|
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
@using BackOffice.Pages.Payment.Components
|
||||||
|
@using CMSMicroservice.Protobuf.Protos
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.UserWallet
|
||||||
|
@using DateTimeConverterCL
|
||||||
|
@using Google.Protobuf.WellKnownTypes
|
||||||
|
|
||||||
|
@inject UserWalletContract.UserWalletContractClient WalletClient
|
||||||
|
|
||||||
|
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||||
|
<Filters>
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true">
|
||||||
|
شارژ دستی کیف پول اصلی (Balance) — بدون درگاه. جدا از «پرداختهای دستی» عضویت باشگاه.
|
||||||
|
</MudAlert>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<UserAutoComplete Label="جستجوی کاربر" @bind-SelectedUserId="_userIdFilter" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudTextField T="long?" Label="شناسه تراکنش" Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" @bind-Value="_transactionIdFilter" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudTextField T="string" Label="RefId" Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" @bind-Value="_refIdFilter" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="4">
|
||||||
|
<DateRangePicker @bind-From="_createdFrom" @bind-To="_createdTo" Label="بازه ثبت" />
|
||||||
|
</MudItem>
|
||||||
|
</Filters>
|
||||||
|
<Content>
|
||||||
|
<MudDataGrid @ref="_dataGrid" T="ManualCreditChargeModel"
|
||||||
|
ServerData="LoadData"
|
||||||
|
Hover="true" Dense="true"
|
||||||
|
Height="calc(100vh - 240px)">
|
||||||
|
<ToolBarContent>
|
||||||
|
<MudText Typo="Typo.subtitle1">شارژ دستی کیف پول اصلی</MudText>
|
||||||
|
<MudSpacer />
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.Add" OnClick="OpenCreateDialog">
|
||||||
|
شارژ دستی جدید
|
||||||
|
</MudButton>
|
||||||
|
</ToolBarContent>
|
||||||
|
<Columns>
|
||||||
|
<PropertyColumn Property="x => x.TransactionId" Title="شناسه تراکنش" />
|
||||||
|
<PropertyColumn Property="x => x.UserId" Title="شناسه کاربر" />
|
||||||
|
<PropertyColumn Property="x => x.UserName" Title="نام کاربر" />
|
||||||
|
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
|
||||||
|
<CellTemplate>
|
||||||
|
@context.Item.Amount.ToString("N0")
|
||||||
|
</CellTemplate>
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="x => x.NewBalance" Title="موجودی بعد از شارژ">
|
||||||
|
<CellTemplate>
|
||||||
|
@context.Item.NewBalance.ToString("N0")
|
||||||
|
</CellTemplate>
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="x => x.RefId" Title="RefId" />
|
||||||
|
<PropertyColumn Property="x => x.Description" Title="توضیحات" />
|
||||||
|
<TemplateColumn Title="تاریخ">
|
||||||
|
<CellTemplate>
|
||||||
|
@(context.Item.Created?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
<NoRecordsContent>
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||||
|
</NoRecordsContent>
|
||||||
|
<PagerContent>
|
||||||
|
<MudDataGridPager T="ManualCreditChargeModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||||
|
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||||
|
RowsPerPageString="تعداد در صفحه" />
|
||||||
|
</PagerContent>
|
||||||
|
</MudDataGrid>
|
||||||
|
</Content>
|
||||||
|
</BasePageComponent>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private BasePageComponent? _basePage;
|
||||||
|
private MudDataGrid<ManualCreditChargeModel>? _dataGrid;
|
||||||
|
private long? _userIdFilter;
|
||||||
|
private long? _transactionIdFilter;
|
||||||
|
private string? _refIdFilter;
|
||||||
|
private DateTime? _createdFrom;
|
||||||
|
private DateTime? _createdTo;
|
||||||
|
|
||||||
|
private async Task<GridData<ManualCreditChargeModel>> LoadData(GridState<ManualCreditChargeModel> state)
|
||||||
|
{
|
||||||
|
var request = new GetManualCreditChargesRequest
|
||||||
|
{
|
||||||
|
PaginationState = new PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = state.Page + 1,
|
||||||
|
PageSize = state.PageSize
|
||||||
|
},
|
||||||
|
Filter = new GetManualCreditChargesFilter()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_userIdFilter.HasValue)
|
||||||
|
request.Filter.UserId = _userIdFilter.Value;
|
||||||
|
if (_transactionIdFilter.HasValue)
|
||||||
|
request.Filter.TransactionId = _transactionIdFilter.Value;
|
||||||
|
if (!string.IsNullOrWhiteSpace(_refIdFilter))
|
||||||
|
request.Filter.RefId = _refIdFilter.Trim();
|
||||||
|
if (_createdFrom.HasValue)
|
||||||
|
request.Filter.CreatedFrom = Timestamp.FromDateTime(DateTime.SpecifyKind(_createdFrom.Value, DateTimeKind.Utc));
|
||||||
|
if (_createdTo.HasValue)
|
||||||
|
request.Filter.CreatedTo = Timestamp.FromDateTime(DateTime.SpecifyKind(_createdTo.Value, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
var response = await WalletClient.GetManualCreditChargesAsync(request);
|
||||||
|
|
||||||
|
return new GridData<ManualCreditChargeModel>
|
||||||
|
{
|
||||||
|
Items = response.Models.ToList(),
|
||||||
|
TotalItems = (int)(response.MetaData?.TotalCount ?? 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFilterSubmit()
|
||||||
|
{
|
||||||
|
if (_dataGrid != null)
|
||||||
|
await _dataGrid.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFilterCleared()
|
||||||
|
{
|
||||||
|
_userIdFilter = null;
|
||||||
|
_transactionIdFilter = null;
|
||||||
|
_refIdFilter = null;
|
||||||
|
_createdFrom = null;
|
||||||
|
_createdTo = null;
|
||||||
|
if (_dataGrid != null)
|
||||||
|
await _dataGrid.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenCreateDialog()
|
||||||
|
{
|
||||||
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true };
|
||||||
|
var dialog = DialogService.Show<ManualCreditChargeDialog>("شارژ دستی کیف پول اصلی", options);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false } && _dataGrid != null)
|
||||||
|
await _dataGrid.ReloadServerData();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,12 @@
|
|||||||
|
|
||||||
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||||
<Filters>
|
<Filters>
|
||||||
|
@* ── G10: هشدار پرداخت دستی (Q28) ── *@
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudAlert Severity="Severity.Warning" Variant="Variant.Text" Dense="true" Icon="@Icons.Material.Filled.Warning">
|
||||||
|
مبلغ پرداخت باید مطابق قیمت پکیج انتخابی باشد. پکیجهای مختلف قیمت متفاوتی دارند.
|
||||||
|
</MudAlert>
|
||||||
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6" md="3">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<UserAutoComplete Label="جستجوی کاربر" @bind-SelectedUserId="_userIdFilter" />
|
<UserAutoComplete Label="جستجوی کاربر" @bind-SelectedUserId="_userIdFilter" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
@@ -26,8 +32,8 @@
|
|||||||
<MudSelect T="int?" Clearable="true" Label="نوع" Variant="Variant.Outlined"
|
<MudSelect T="int?" Clearable="true" Label="نوع" Variant="Variant.Outlined"
|
||||||
Margin="Margin.Dense" @bind-Value="_typeFilter">
|
Margin="Margin.Dense" @bind-Value="_typeFilter">
|
||||||
<MudSelectItem T="int?" Value="@(1)">واریز نقدی</MudSelectItem>
|
<MudSelectItem T="int?" Value="@(1)">واریز نقدی</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="@(2)">شارژ کیفپول تخفیف</MudSelectItem>
|
<MudSelectItem T="int?" Value="@(2)">شارژ کیف پول اعتباری</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="@(3)">شارژ کیفپول شبکه</MudSelectItem>
|
<MudSelectItem T="int?" Value="@(3)">شارژ پاداش های دریافتی</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="@(4)">تسویه حساب</MudSelectItem>
|
<MudSelectItem T="int?" Value="@(4)">تسویه حساب</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="@(5)">اصلاح خطا</MudSelectItem>
|
<MudSelectItem T="int?" Value="@(5)">اصلاح خطا</MudSelectItem>
|
||||||
<MudSelectItem T="int?" Value="@(6)">بازگشت وجه</MudSelectItem>
|
<MudSelectItem T="int?" Value="@(6)">بازگشت وجه</MudSelectItem>
|
||||||
@@ -219,8 +225,8 @@
|
|||||||
return type switch
|
return type switch
|
||||||
{
|
{
|
||||||
1 => "واریز نقدی",
|
1 => "واریز نقدی",
|
||||||
2 => "شارژ کیفپول تخفیف",
|
2 => "شارژ کیف پول اعتباری",
|
||||||
3 => "شارژ کیفپول شبکه",
|
3 => "شارژ پاداش های دریافتی",
|
||||||
4 => "تسویه حساب",
|
4 => "تسویه حساب",
|
||||||
5 => "اصلاح خطا",
|
5 => "اصلاح خطا",
|
||||||
6 => "بازگشت وجه",
|
6 => "بازگشت وجه",
|
||||||
@@ -257,7 +263,7 @@
|
|||||||
{
|
{
|
||||||
bool? confirm = await DialogService.ShowMessageBox(
|
bool? confirm = await DialogService.ShowMessageBox(
|
||||||
"تایید پرداخت",
|
"تایید پرداخت",
|
||||||
$"آیا از تایید پرداخت دستی #{model.Id} به مبلغ {model.Amount:N0} ریال مطمئن هستید؟",
|
$"آیا از تایید پرداخت دستی #{model.Id} به مبلغ {model.Amount:N0} تومان مطمئن هستید؟",
|
||||||
yesText: "تایید", cancelText: "لغو");
|
yesText: "تایید", cancelText: "لغو");
|
||||||
if (confirm == true)
|
if (confirm == true)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public partial class ManualPayments
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("شناسه,شناسه کاربر,نام کاربر,مبلغ,نوع,وضعیت,تاریخ ایجاد");
|
sb.AppendLine("شناسه,شناسه کاربر,نام کاربر,پکیج,مبلغ,نوع,وضعیت,تاریخ ایجاد");
|
||||||
|
|
||||||
foreach (var m in response.Models)
|
foreach (var m in response.Models)
|
||||||
{
|
{
|
||||||
@@ -45,6 +45,7 @@ public partial class ManualPayments
|
|||||||
m.Id,
|
m.Id,
|
||||||
m.UserId,
|
m.UserId,
|
||||||
EscapeCsv(m.UserFullName),
|
EscapeCsv(m.UserFullName),
|
||||||
|
EscapeCsv(m.PackageName ?? "-"),
|
||||||
m.Amount.ToString("N0"),
|
m.Amount.ToString("N0"),
|
||||||
EscapeCsv(GetTypeText((int)m.Type)),
|
EscapeCsv(GetTypeText((int)m.Type)),
|
||||||
EscapeCsv(GetStatusText((int)m.Status)),
|
EscapeCsv(GetStatusText((int)m.Status)),
|
||||||
|
|||||||
@@ -1,78 +1,115 @@
|
|||||||
@page "/payment/transactions"
|
@page "/payment/transactions"
|
||||||
@using BackOffice.Common.BaseComponents
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.PaymentTransaction
|
||||||
|
@using Google.Protobuf.WellKnownTypes
|
||||||
|
|
||||||
|
@inject PaymentTransactionContract.PaymentTransactionContractClient PaymentTxClient
|
||||||
|
|
||||||
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||||
<Filters>
|
<Filters>
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudItem xs="12">
|
||||||
<BackOffice.Common.BaseComponents.DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه تاریخی" />
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true">
|
||||||
|
گزارش read-only تراکنشهای درگاه پرداخت. برای ثبت پرداخت دستی به «پرداختهای دستی» مراجعه کنید.
|
||||||
|
</MudAlert>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<MudSelect T="int?" Clearable="true" Label="وضعیت پرداخت" Variant="Variant.Outlined"
|
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
|
||||||
Margin="Margin.Dense" @bind-Value="_statusFilter">
|
</MudItem>
|
||||||
<MudSelectItem T="int?" Value="@(0)">در انتظار</MudSelectItem>
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<MudSelectItem T="int?" Value="@(1)">موفق</MudSelectItem>
|
<MudTextField T="string" Label="موبایل" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_mobile" />
|
||||||
<MudSelectItem T="int?" Value="@(2)">ناموفق</MudSelectItem>
|
</MudItem>
|
||||||
<MudSelectItem T="int?" Value="@(3)">بازگشت وجه</MudSelectItem>
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudTextField T="string" Label="Authority" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_authority" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudTextField T="string" Label="RefId" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_refId" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudTextField T="string" Label="OrderId" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_orderId" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudTextField T="string" Label="درگاه" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_gateway" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudSelect T="bool?" Clearable="true" Label="وضعیت پرداخت" Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" @bind-Value="_paymentStatus">
|
||||||
|
<MudSelectItem T="bool?" Value="@((bool?)true)">موفق</MudSelectItem>
|
||||||
|
<MudSelectItem T="bool?" Value="@((bool?)false)">ناموفق / در انتظار</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudItem xs="12" sm="6" md="4">
|
||||||
<MudSelect T="int?" Clearable="true" Label="نوع تراکنش" Variant="Variant.Outlined"
|
<DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه تاریخ" />
|
||||||
Margin="Margin.Dense" @bind-Value="_typeFilter">
|
|
||||||
<MudSelectItem T="int?" Value="@(0)">نامشخص</MudSelectItem>
|
|
||||||
<MudSelectItem T="int?" Value="@(1)">خرید پکیج</MudSelectItem>
|
|
||||||
<MudSelectItem T="int?" Value="@(2)">پرداخت دستی</MudSelectItem>
|
|
||||||
<MudSelectItem T="int?" Value="@(3)">شارژ کیفپول</MudSelectItem>
|
|
||||||
<MudSelectItem T="int?" Value="@(4)">بازگشت وجه</MudSelectItem>
|
|
||||||
</MudSelect>
|
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</Filters>
|
</Filters>
|
||||||
<Content>
|
<Content>
|
||||||
<MudDataGrid @ref="_dataGrid" T="TransactionModel"
|
<MudGrid Class="mb-3" Spacing="2">
|
||||||
ServerData="LoadData"
|
<MudItem xs="6" sm="3">
|
||||||
Hover="true" Dense="true"
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
Height="calc(100vh - 240px)">
|
<MudText Typo="Typo.caption" Color="Color.Secondary">تعداد</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@_summaryTotalCount.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="3">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">جمع مبلغ (تومان)</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@_summaryTotalAmount.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="3">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">موفق</MudText>
|
||||||
|
<MudText Typo="Typo.h6" Color="Color.Success">@_summarySuccessful.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="3">
|
||||||
|
<MudPaper Class="pa-3" Outlined="true">
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">در انتظار</MudText>
|
||||||
|
<MudText Typo="Typo.h6" Color="Color.Warning">@_summaryPending.ToString("N0")</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
<MudDataGrid @ref="_dataGrid" T="PaymentTransactionModel"
|
||||||
|
ServerData="LoadData" ReadOnly="true"
|
||||||
|
Hover="true" Dense="true" Height="calc(100vh - 380px)">
|
||||||
|
<ToolBarContent>
|
||||||
|
<MudText Typo="Typo.subtitle1">گزارش تراکنش درگاه</MudText>
|
||||||
|
<MudSpacer />
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Info" Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.FileDownload" OnClick="ExportCsv">CSV</MudButton>
|
||||||
|
</ToolBarContent>
|
||||||
<Columns>
|
<Columns>
|
||||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||||
<PropertyColumn Property="x => x.RefId" Title="شماره مرجع" />
|
<PropertyColumn Property="x => x.UserId" Title="کاربر" />
|
||||||
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
|
<PropertyColumn Property="x => x.Mobile" Title="موبایل" />
|
||||||
<CellTemplate>
|
<PropertyColumn Property="x => x.GatewayProvider" Title="درگاه" />
|
||||||
@context.Item.Amount.ToString("N0")
|
<TemplateColumn Title="مبلغ (تومان)">
|
||||||
</CellTemplate>
|
<CellTemplate>@context.Item.Amount.ToString("N0")</CellTemplate>
|
||||||
</PropertyColumn>
|
</TemplateColumn>
|
||||||
|
<PropertyColumn Property="x => x.Authority" Title="Authority" />
|
||||||
|
<PropertyColumn Property="x => x.RefId" Title="RefId" />
|
||||||
|
<PropertyColumn Property="x => x.CardPan" Title="کارت" />
|
||||||
<TemplateColumn Title="وضعیت">
|
<TemplateColumn Title="وضعیت">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudChip T="string"
|
<MudChip T="string" Size="Size.Small" Color="@(context.Item.PaymentStatus ? Color.Success : Color.Warning)">
|
||||||
Size="Size.Small"
|
@ReportLabels.GetPaymentStatusLabel(context.Item.PaymentStatus)
|
||||||
Color="@GetStatusColor(context.Item.PaymentStatus)">
|
|
||||||
@GetStatusText(context.Item.PaymentStatus)
|
|
||||||
</MudChip>
|
</MudChip>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="تاریخ پرداخت">
|
<PropertyColumn Property="x => x.VerificationStatusCode" Title="کد Verify" />
|
||||||
<CellTemplate>
|
<PropertyColumn Property="x => x.OrderId" Title="OrderId" />
|
||||||
@context.Item.PaymentDate.ToLocalTime().MiladiToJalaliWithTime()
|
<TemplateColumn Title="تاریخ">
|
||||||
</CellTemplate>
|
<CellTemplate>@context.Item.CreatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||||
</TemplateColumn>
|
|
||||||
<TemplateColumn Title="نوع">
|
|
||||||
<CellTemplate>
|
|
||||||
@GetTypeText(context.Item.Type)
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
<TemplateColumn Title="عملیات">
|
|
||||||
<CellTemplate>
|
|
||||||
<MudTooltip Text="جزئیات">
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Info"
|
|
||||||
Size="Size.Small"
|
|
||||||
OnClick="@(() => OpenDetails(context.Item))" />
|
|
||||||
</MudTooltip>
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
</Columns>
|
</Columns>
|
||||||
<NoRecordsContent>
|
<NoRecordsContent>
|
||||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">
|
||||||
|
با فیلتر فعلی موردی یافت نشد — بازه تاریخ یا کاربر را تغییر دهید.
|
||||||
|
</MudAlert>
|
||||||
</NoRecordsContent>
|
</NoRecordsContent>
|
||||||
<PagerContent>
|
<PagerContent>
|
||||||
<MudDataGridPager T="TransactionModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
<MudDataGridPager T="PaymentTransactionModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||||
RowsPerPageString="تعداد در صفحه" />
|
RowsPerPageString="تعداد در صفحه" />
|
||||||
</PagerContent>
|
</PagerContent>
|
||||||
@@ -80,3 +117,87 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</BasePageComponent>
|
</BasePageComponent>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private BasePageComponent? _basePage;
|
||||||
|
private MudDataGrid<PaymentTransactionModel>? _dataGrid;
|
||||||
|
private long? _userId;
|
||||||
|
private string? _mobile, _authority, _refId, _orderId, _gateway;
|
||||||
|
private bool? _paymentStatus;
|
||||||
|
private DateTime? _fromDate, _toDate;
|
||||||
|
private long _summaryTotalCount, _summaryTotalAmount, _summarySuccessful, _summaryPending;
|
||||||
|
|
||||||
|
private GetAllPaymentTransactionByFilterFilter BuildFilter() => new()
|
||||||
|
{
|
||||||
|
UserId = _userId > 0 ? _userId : null,
|
||||||
|
Mobile = string.IsNullOrWhiteSpace(_mobile) ? null : _mobile,
|
||||||
|
Authority = string.IsNullOrWhiteSpace(_authority) ? null : _authority,
|
||||||
|
RefId = string.IsNullOrWhiteSpace(_refId) ? null : _refId,
|
||||||
|
OrderId = string.IsNullOrWhiteSpace(_orderId) ? null : _orderId,
|
||||||
|
GatewayProvider = string.IsNullOrWhiteSpace(_gateway) ? null : _gateway,
|
||||||
|
PaymentStatus = _paymentStatus,
|
||||||
|
CreatedFrom = _fromDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value, DateTimeKind.Utc)) : null,
|
||||||
|
CreatedTo = _toDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null
|
||||||
|
};
|
||||||
|
|
||||||
|
private async Task LoadSummary()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var s = await PaymentTxClient.GetPaymentTransactionSummaryAsync(new GetPaymentTransactionSummaryRequest { Filter = BuildFilter() });
|
||||||
|
_summaryTotalCount = s.TotalCount;
|
||||||
|
_summaryTotalAmount = s.TotalAmount;
|
||||||
|
_summarySuccessful = s.SuccessfulCount;
|
||||||
|
_summaryPending = s.PendingCount;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<GridData<PaymentTransactionModel>> LoadData(GridState<PaymentTransactionModel> state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LoadSummary();
|
||||||
|
var result = await PaymentTxClient.GetAllPaymentTransactionByFilterAsync(new GetAllPaymentTransactionByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||||
|
Filter = BuildFilter()
|
||||||
|
});
|
||||||
|
return new GridData<PaymentTransactionModel>
|
||||||
|
{
|
||||||
|
Items = result?.Models?.ToList() ?? new(),
|
||||||
|
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
return new GridData<PaymentTransactionModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFilterSubmit() { if (_dataGrid != null) await _dataGrid.ReloadServerData(); }
|
||||||
|
private async Task OnFilterCleared()
|
||||||
|
{
|
||||||
|
_userId = null; _mobile = _authority = _refId = _orderId = _gateway = null;
|
||||||
|
_paymentStatus = null; _fromDate = _toDate = null;
|
||||||
|
if (_dataGrid != null) await _dataGrid.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExportCsv()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await PaymentTxClient.GetAllPaymentTransactionByFilterAsync(new GetAllPaymentTransactionByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = 1, PageSize = 5000 },
|
||||||
|
Filter = BuildFilter()
|
||||||
|
});
|
||||||
|
var sb = new System.Text.StringBuilder();
|
||||||
|
sb.AppendLine("Id,UserId,Mobile,Gateway,Amount,Authority,RefId,Status,OrderId,Created");
|
||||||
|
foreach (var r in result.Models)
|
||||||
|
sb.AppendLine($"{r.Id},{r.UserId},{r.Mobile},{r.GatewayProvider},{r.Amount},{r.Authority},{r.RefId},{r.PaymentStatus},{r.OrderId},{r.CreatedAt.ToDateTime():yyyy-MM-dd HH:mm}");
|
||||||
|
await jsRuntime.InvokeVoidAsync("jsSaveAsFile", "payment-transactions.csv", sb.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Snackbar.Add($"خطا در export: {ex.Message}", Severity.Error); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
using BackOffice.Common.BaseComponents;
|
|
||||||
using MudBlazor;
|
|
||||||
|
|
||||||
namespace BackOffice.Pages.Payment;
|
|
||||||
|
|
||||||
public partial class Transactions
|
|
||||||
{
|
|
||||||
private BasePageComponent? _basePage;
|
|
||||||
private MudDataGrid<TransactionModel>? _dataGrid;
|
|
||||||
|
|
||||||
private DateTime? _fromDate;
|
|
||||||
private DateTime? _toDate;
|
|
||||||
private int? _statusFilter;
|
|
||||||
private int? _typeFilter;
|
|
||||||
|
|
||||||
private async Task<GridData<TransactionModel>> LoadData(GridState<TransactionModel> state)
|
|
||||||
{
|
|
||||||
// TODO: Connect to BackOffice.BFF Transactions when API is ready
|
|
||||||
await Task.CompletedTask;
|
|
||||||
|
|
||||||
return new GridData<TransactionModel>
|
|
||||||
{
|
|
||||||
Items = Array.Empty<TransactionModel>(),
|
|
||||||
TotalItems = 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnFilterSubmit()
|
|
||||||
{
|
|
||||||
if (_dataGrid != null)
|
|
||||||
await _dataGrid.ReloadServerData();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnFilterCleared()
|
|
||||||
{
|
|
||||||
_fromDate = null;
|
|
||||||
_toDate = null;
|
|
||||||
_statusFilter = null;
|
|
||||||
_typeFilter = null;
|
|
||||||
if (_dataGrid != null)
|
|
||||||
await _dataGrid.ReloadServerData();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OpenDetails(TransactionModel model)
|
|
||||||
{
|
|
||||||
// TODO: Open TransactionDetailsDialog with model
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetStatusText(int status)
|
|
||||||
{
|
|
||||||
return status switch
|
|
||||||
{
|
|
||||||
0 => "در انتظار",
|
|
||||||
1 => "موفق",
|
|
||||||
2 => "ناموفق",
|
|
||||||
3 => "بازگشت وجه",
|
|
||||||
_ => "نامشخص"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Color GetStatusColor(int status)
|
|
||||||
{
|
|
||||||
return status switch
|
|
||||||
{
|
|
||||||
0 => Color.Warning,
|
|
||||||
1 => Color.Success,
|
|
||||||
2 => Color.Error,
|
|
||||||
3 => Color.Info,
|
|
||||||
_ => Color.Default
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetTypeText(int type)
|
|
||||||
{
|
|
||||||
return type switch
|
|
||||||
{
|
|
||||||
1 => "خرید پکیج",
|
|
||||||
2 => "پرداخت دستی",
|
|
||||||
3 => "شارژ کیفپول",
|
|
||||||
4 => "بازگشت وجه",
|
|
||||||
_ => "نامشخص"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TransactionModel
|
|
||||||
{
|
|
||||||
public long Id { get; set; }
|
|
||||||
public string? RefId { get; set; }
|
|
||||||
public long Amount { get; set; }
|
|
||||||
public int PaymentStatus { get; set; }
|
|
||||||
public DateTime PaymentDate { get; set; }
|
|
||||||
public int Type { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
|
|
||||||
<MudNumericField T="long?"
|
<MudNumericField T="long?"
|
||||||
HideSpinButtons="true"
|
HideSpinButtons="true"
|
||||||
Label="قیمت جدید (ریال)"
|
Label="قیمت جدید (تومان)"
|
||||||
@bind-Value="_newPrice"
|
@bind-Value="_newPrice"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
Margin="Margin.Dense" />
|
Margin="Margin.Dense" />
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="6">
|
<MudItem xs="6">
|
||||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت (ریال)" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت (تومان)" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="6">
|
<MudItem xs="6">
|
||||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت (ریال)" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت (تومان)" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@
|
|||||||
|
|
||||||
<TemplateColumn Title="قیمت" CellStyle="text-wrap: nowrap;">
|
<TemplateColumn Title="قیمت" CellStyle="text-wrap: nowrap;">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@context.Item.Price.ToString("N0") ریال
|
@context.Item.Price.ToString("N0") تومان
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
|
|
||||||
|
|||||||
@@ -174,8 +174,7 @@
|
|||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudSwitch T="bool" @bind-Value="_networkConfig.SpilloverEnabled"
|
<MudSwitch T="bool" @bind-Value="_networkConfig.SpilloverEnabled"
|
||||||
Color="Color.Primary"
|
Color="Color.Primary"
|
||||||
Label="Spillover فعال باشد"
|
Label="Spillover فعال باشد — اعضای اضافی به پایین درخت منتقل شوند" />
|
||||||
HelperText="اعضای اضافی به پایین درخت منتقل شوند" />
|
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
|
|
||||||
@@ -340,8 +339,7 @@
|
|||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudSwitch @bind-Value="_systemConfig.MaintenanceMode"
|
<MudSwitch @bind-Value="_systemConfig.MaintenanceMode"
|
||||||
Color="Color.Warning"
|
Color="Color.Warning"
|
||||||
Label="حالت تعمیر و نگهداری"
|
Label="حالت تعمیر و نگهداری — غیرفعال کردن موقت سیستم برای بهروزرسانی" />
|
||||||
HelperText="غیرفعال کردن موقت سیستم برای بهروزرسانی" />
|
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
</MudText>
|
</MudText>
|
||||||
|
|
||||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
مبلغ تخفیف بهصورت ریالی وارد شود.
|
مبلغ تخفیف بهصورت تومانی وارد شود.
|
||||||
</MudText>
|
</MudText>
|
||||||
|
|
||||||
<MudNumericField T="long?"
|
<MudNumericField T="long?"
|
||||||
@bind-Value="_discountAmount"
|
@bind-Value="_discountAmount"
|
||||||
Label="مبلغ تخفیف (ریال)"
|
Label="مبلغ تخفیف (تومان)"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
HideSpinButtons="true" />
|
HideSpinButtons="true" />
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public partial class ApplyDiscountDialog
|
|||||||
|
|
||||||
if (response.Success)
|
if (response.Success)
|
||||||
{
|
{
|
||||||
Snackbar.Add($"تخفیف با موفقیت اعمال شد. مبلغ نهایی: {response.FinalAmount.ToString("N0")} ریال", Severity.Success);
|
Snackbar.Add($"تخفیف با موفقیت اعمال شد. مبلغ نهایی: {response.FinalAmount.ToString("N0")} تومان", Severity.Success);
|
||||||
MudDialog.Close(DialogResult.Ok(true));
|
MudDialog.Close(DialogResult.Ok(true));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
<MudSelectItem T="int" Value="2">تحویل پست</MudSelectItem>
|
<MudSelectItem T="int" Value="2">تحویل پست</MudSelectItem>
|
||||||
<MudSelectItem T="int" Value="3">تحویل به مشتری</MudSelectItem>
|
<MudSelectItem T="int" Value="3">تحویل به مشتری</MudSelectItem>
|
||||||
<MudSelectItem T="int" Value="4">مرجوع شده</MudSelectItem>
|
<MudSelectItem T="int" Value="4">مرجوع شده</MudSelectItem>
|
||||||
|
<MudSelectItem T="int" Value="5">لغو شده</MudSelectItem>
|
||||||
|
<MudSelectItem T="int" Value="6">آماده تحویل در دفتر</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
@@ -56,6 +58,24 @@
|
|||||||
</MudItem>
|
</MudItem>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (_newStatus == 5)
|
||||||
|
{
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudAlert Severity="Severity.Warning">
|
||||||
|
توجه: وضعیت «لغو شده» یعنی ارسال این سفارش متوقف شده است.
|
||||||
|
</MudAlert>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_newStatus == 6)
|
||||||
|
{
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudAlert Severity="Severity.Info">
|
||||||
|
سفارش برای تحویل حضوری در دفتر آماده است.
|
||||||
|
</MudAlert>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
|
||||||
@if (_newStatus == 2)
|
@if (_newStatus == 2)
|
||||||
{
|
{
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public partial class ChangeOrderStatusDialog
|
|||||||
2 => "تحویل پست",
|
2 => "تحویل پست",
|
||||||
3 => "تحویل به مشتری",
|
3 => "تحویل به مشتری",
|
||||||
4 => "مرجوع شده",
|
4 => "مرجوع شده",
|
||||||
|
5 => "لغو شده",
|
||||||
|
6 => "آماده تحویل در دفتر",
|
||||||
_ => "بدون ارسال / نامشخص"
|
_ => "بدون ارسال / نامشخص"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -49,6 +51,8 @@ public partial class ChangeOrderStatusDialog
|
|||||||
2 => Color.Info,
|
2 => Color.Info,
|
||||||
3 => Color.Success,
|
3 => Color.Success,
|
||||||
4 => Color.Error,
|
4 => Color.Error,
|
||||||
|
5 => Color.Dark,
|
||||||
|
6 => Color.Primary,
|
||||||
_ => Color.Default
|
_ => Color.Default
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,8 @@
|
|||||||
<MudSelectItem T="int" Value="2">تحویل پست</MudSelectItem>
|
<MudSelectItem T="int" Value="2">تحویل پست</MudSelectItem>
|
||||||
<MudSelectItem T="int" Value="3">تحویل به مشتری</MudSelectItem>
|
<MudSelectItem T="int" Value="3">تحویل به مشتری</MudSelectItem>
|
||||||
<MudSelectItem T="int" Value="4">مرجوع شده</MudSelectItem>
|
<MudSelectItem T="int" Value="4">مرجوع شده</MudSelectItem>
|
||||||
|
<MudSelectItem T="int" Value="5">لغو شده</MudSelectItem>
|
||||||
|
<MudSelectItem T="int" Value="6">آماده تحویل در دفتر</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudText>
|
</MudText>
|
||||||
<MudTextField T="string"
|
<MudTextField T="string"
|
||||||
@@ -123,6 +125,14 @@
|
|||||||
{
|
{
|
||||||
<MudText Typo="Typo.caption">آدرسی ثبت نشده است.</MudText>
|
<MudText Typo="Typo.caption">آدرسی ثبت نشده است.</MudText>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_model.PostalCode))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">کد پستی: @_model.PostalCode</MudText>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_model.UserMobile))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">تلفن سفارشدهنده: @_model.UserMobile</MudText>
|
||||||
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
@@ -162,6 +172,13 @@
|
|||||||
</MudStack>
|
</MudStack>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Secondary"
|
||||||
|
StartIcon="@Icons.Material.Filled.Print"
|
||||||
|
OnClick="PrintInvoiceAsync"
|
||||||
|
Disabled="_model is null || _isLoading">
|
||||||
|
چاپ فاکتور
|
||||||
|
</MudButton>
|
||||||
|
<MudSpacer />
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Default" OnClick="Close">
|
<MudButton Variant="Variant.Outlined" Color="Color.Default" OnClick="Close">
|
||||||
بستن
|
بستن
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
using BackOffice.Common.Utilities;
|
||||||
using CMSMicroservice.Protobuf.Protos;
|
using CMSMicroservice.Protobuf.Protos;
|
||||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
|
|
||||||
namespace BackOffice.Pages.UserOrder.Components;
|
namespace BackOffice.Pages.UserOrder.Components;
|
||||||
@@ -9,6 +11,7 @@ public partial class UserOrderDetailsDialog
|
|||||||
{
|
{
|
||||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||||
[Inject] public UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
|
[Inject] public UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
|
||||||
|
[Inject] private IJSRuntime JsRuntime { get; set; } = default!;
|
||||||
|
|
||||||
[Parameter] public long OrderId { get; set; }
|
[Parameter] public long OrderId { get; set; }
|
||||||
|
|
||||||
@@ -56,6 +59,8 @@ public partial class UserOrderDetailsDialog
|
|||||||
2 => Color.Info, // InTransit
|
2 => Color.Info, // InTransit
|
||||||
3 => Color.Success, // Delivered
|
3 => Color.Success, // Delivered
|
||||||
4 => Color.Error, // Returned
|
4 => Color.Error, // Returned
|
||||||
|
5 => Color.Dark, // Cancelled
|
||||||
|
6 => Color.Primary, // ReadyForOfficePickup
|
||||||
_ => Color.Default // None / Unknown
|
_ => Color.Default // None / Unknown
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -68,6 +73,8 @@ public partial class UserOrderDetailsDialog
|
|||||||
2 => "تحویل پست",
|
2 => "تحویل پست",
|
||||||
3 => "تحویل به مشتری",
|
3 => "تحویل به مشتری",
|
||||||
4 => "مرجوع شده",
|
4 => "مرجوع شده",
|
||||||
|
5 => "لغو شده",
|
||||||
|
6 => "آماده تحویل در دفتر",
|
||||||
_ => "بدون ارسال / نامشخص"
|
_ => "بدون ارسال / نامشخص"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -178,5 +185,61 @@ public partial class UserOrderDetailsDialog
|
|||||||
MudDialog.Close(DialogResult.Ok(true));
|
MudDialog.Close(DialogResult.Ok(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task PrintInvoiceAsync()
|
||||||
|
{
|
||||||
|
if (_model is null) return;
|
||||||
|
|
||||||
|
var vat = _model.VatInfo;
|
||||||
|
var lines = _model.FactorDetails.Select(f =>
|
||||||
|
{
|
||||||
|
var unit = f.UnitPrice ?? 0;
|
||||||
|
var count = f.Count ?? 0;
|
||||||
|
var discountUnit = f.UnitDiscountPrice ?? 0;
|
||||||
|
var lineDiscount = Math.Max(0, (unit - discountUnit) * count);
|
||||||
|
var lineTotal = discountUnit > 0 ? discountUnit * count : unit * count;
|
||||||
|
return new OrderInvoicePrint.InvoiceLine
|
||||||
|
{
|
||||||
|
Title = f.ProductTitle ?? "",
|
||||||
|
Quantity = count,
|
||||||
|
UnitPrice = unit,
|
||||||
|
Discount = lineDiscount,
|
||||||
|
LineTotal = lineTotal
|
||||||
|
};
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var subtotal = lines.Sum(l => l.UnitPrice * l.Quantity);
|
||||||
|
var discountTotal = lines.Sum(l => l.Discount);
|
||||||
|
var total = vat is { TotalAmount: > 0 } ? vat.TotalAmount : _model.Amount;
|
||||||
|
|
||||||
|
var html = OrderInvoicePrint.BuildHtml(new OrderInvoicePrint.InvoiceModel
|
||||||
|
{
|
||||||
|
ShopTitle = "فروشگاه عادی — کارا بازار سلامت",
|
||||||
|
OrderId = _model.Id.ToString(),
|
||||||
|
CustomerName = _model.UserFullName,
|
||||||
|
CustomerMobile = _model.UserMobile,
|
||||||
|
CustomerNationalCode = _model.UserNationalCode,
|
||||||
|
Address = _model.UserAddressText,
|
||||||
|
PostalCode = _model.PostalCode,
|
||||||
|
PaymentStatus = _model.PaymentStatus switch
|
||||||
|
{
|
||||||
|
PaymentStatus.Success => "پرداخت شده",
|
||||||
|
PaymentStatus.Pending => "در انتظار پرداخت",
|
||||||
|
_ => "پرداخت نشده"
|
||||||
|
},
|
||||||
|
PaymentMethod = GetPaymentMethodText(_model.PaymentMethod.GetHashCode()),
|
||||||
|
TransactionId = _model.TransactionId?.ToString(),
|
||||||
|
TrackingCode = _trackingCode ?? _model.TrackingCode,
|
||||||
|
Created = null,
|
||||||
|
PaymentDate = _model.PaymentDate?.ToDateTime(),
|
||||||
|
Subtotal = subtotal,
|
||||||
|
DiscountTotal = discountTotal,
|
||||||
|
VatAmount = vat?.VatAmount ?? 0,
|
||||||
|
TotalAmount = total,
|
||||||
|
Lines = lines
|
||||||
|
});
|
||||||
|
|
||||||
|
await JsRuntime.InvokeVoidAsync("jsHtmlToPdf", html, $"فاکتور سفارش {_model.Id}");
|
||||||
|
}
|
||||||
|
|
||||||
private void Close() => MudDialog.Close();
|
private void Close() => MudDialog.Close();
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user