Compare commits
69 Commits
| 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 | |||
| ea7414dc7f | |||
| a450d9a001 | |||
| 60c91438ec | |||
| 1d8f34ae6c | |||
| bdea2e8aef | |||
| e541aec115 | |||
| 743403e4d4 | |||
| 44fd5f29d9 | |||
| fac3d2c85e | |||
| 51c20116a7 | |||
| 4454b8dd3a | |||
| 2bbb1ba4c7 | |||
| 978475360c | |||
| 230453e491 |
@@ -1,46 +0,0 @@
|
|||||||
|
|
||||||
name: Push nuget and docker image Actions Workflow
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- stage-new
|
|
||||||
jobs:
|
|
||||||
Deploy:
|
|
||||||
runs-on: windows
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: https://git.afrino.co/actions/checkout@v3
|
|
||||||
- name: Setup dotnet
|
|
||||||
uses: https://git.afrino.co/actions/setup-dotnet@v3
|
|
||||||
with:
|
|
||||||
dotnet-version: 7.0.x
|
|
||||||
|
|
||||||
- name: Remove Package Source
|
|
||||||
run: dotnet nuget remove source FourSat
|
|
||||||
continue-on-error: true
|
|
||||||
- name: Add Package Source
|
|
||||||
run: dotnet nuget add source --name FourSat --username systemuser --password sZSA7PTiv3pUSQZ https://git.afrino.co/api/packages/FourSat/nuget/index.json --store-password-in-clear-text
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: dotnet restore ".\src\BackOffice\BackOffice.csproj"
|
|
||||||
- name: Build
|
|
||||||
run: dotnet build ".\src\BackOffice\BackOffice.csproj" --configuration Release --no-restore
|
|
||||||
- name: Test
|
|
||||||
run: dotnet test ".\src\BackOffice\BackOffice.csproj" --no-restore --verbosity normal
|
|
||||||
- name: Recycle Apppool
|
|
||||||
run: |
|
|
||||||
& "C:\Windows\System32\inetsrv\appcmd.exe" recycle apppool /apppool.name:admin.kbs1.ir
|
|
||||||
shell: powershell
|
|
||||||
- name: Stop Website
|
|
||||||
run: |
|
|
||||||
& "C:\Windows\System32\inetsrv\appcmd.exe" stop site /site.name:admin.kbs1.ir
|
|
||||||
shell: powershell
|
|
||||||
- name: Publish
|
|
||||||
run: dotnet publish ".\src\BackOffice\BackOffice.csproj" -c Release -o publish
|
|
||||||
- name: Copy Publish To IIS Directory
|
|
||||||
run: Get-ChildItem -Path "publish\*" | Copy-Item -Destination "E:\kbs1.ir\admin.kbs1.ir\" -Recurse -Force
|
|
||||||
- name: Start Website
|
|
||||||
run: |
|
|
||||||
& "C:\Windows\System32\inetsrv\appcmd.exe" start site /site.name:admin.kbs1.ir
|
|
||||||
shell: powershell
|
|
||||||
@@ -67,8 +67,15 @@ jobs:
|
|||||||
- name: Deploy to Kubernetes
|
- name: Deploy to Kubernetes
|
||||||
run: |
|
run: |
|
||||||
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
|
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 }} "
|
sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} "
|
||||||
kubectl rollout restart deployment/backoffice
|
kubectl apply -f /tmp/backoffice-deployment.yaml &&
|
||||||
kubectl rollout status deployment/backoffice --timeout=180s
|
kubectl rollout restart deployment/backoffice &&
|
||||||
|
kubectl rollout status deployment/backoffice --timeout=180s &&
|
||||||
|
rm -f /tmp/backoffice-deployment.yaml
|
||||||
"
|
"
|
||||||
echo "✅ Deployed!"
|
echo "✅ Deployed!"
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
if ! docker info >/dev/null 2>&1; then
|
if ! docker info >/dev/null 2>&1; then
|
||||||
echo "❌ Docker daemon failed to start after 3 minutes"
|
echo "❌ Docker daemon failed to start"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -56,9 +56,10 @@ jobs:
|
|||||||
- name: Build Docker Image
|
- name: Build Docker Image
|
||||||
run: |
|
run: |
|
||||||
cd src
|
cd src
|
||||||
DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
|
DOCKER_BUILDKIT=0 docker build --network host -f BackOffice/Dockerfile \
|
||||||
|
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
|
||||||
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod \
|
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod \
|
||||||
-f BackOffice/Dockerfile .
|
.
|
||||||
|
|
||||||
- name: Push to Registry
|
- name: Push to Registry
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
# Docs moved to totalDoc
|
# Docs moved to totalDoc
|
||||||
|
|
||||||
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
|
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
|
||||||
|
|
||||||
@@ -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.192" />
|
<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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ 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.UserWalletHistory;
|
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;
|
||||||
@@ -169,6 +174,13 @@ public static class ConfigureServices
|
|||||||
services.AddTransient(sp => new UserWalletContract.UserWalletContractClient(sp.GetRequiredService<CallInvoker>()));
|
services.AddTransient(sp => new UserWalletContract.UserWalletContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||||
services.AddTransient(sp => new UserWalletHistoryContract.UserWalletHistoryContractClient(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 ? "بله" : "خیر")}";
|
||||||
|
}
|
||||||
@@ -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"]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<clear />
|
||||||
<!-- Local packages (dev builds) -->
|
<!-- Local packages (dev builds) -->
|
||||||
<add key="Local" value="/home/masoud/Apps/project/FourSat/nupkg" />
|
<!-- <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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,121 +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>
|
|
||||||
|
|
||||||
@* ── G11: راهنمای کمیسیون (Q28) ── *@
|
@* ── عنوان صفحه ── *@
|
||||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
<div>
|
||||||
هر پکیج Pool پورسانت مستقل دارد. مبالغ ActivationFee به Pool پکیج مربوطه واریز میشود.
|
<MudText Typo="Typo.h5" Class="mb-1">داشبورد کمیسیون</MudText>
|
||||||
</MudAlert>
|
|
||||||
|
|
||||||
@if (_isLoading)
|
|
||||||
{
|
|
||||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<!-- Pool Summary Section -->
|
|
||||||
<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">
|
<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>
|
</MudText>
|
||||||
</MudCardContent>
|
</div>
|
||||||
</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"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.CalendarToday"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
ValueChanged="OnWeekChanged">
|
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 />
|
||||||
@@ -127,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 />
|
||||||
@@ -146,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>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,97 +97,222 @@
|
|||||||
{
|
{
|
||||||
<MudText Color="Color.Warning">لیست هفتهها بارگذاری نشده</MudText>
|
<MudText Color="Color.Warning">لیست هفتهها بارگذاری نشده</MudText>
|
||||||
}
|
}
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" sm="5">
|
||||||
|
<PackageSelect Label="پکیج"
|
||||||
|
SelectedPackageId="_selectedPackageId"
|
||||||
|
SelectedPackageIdChanged="OnPackageChanged" />
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" sm="2">
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled"
|
||||||
Color="Color.Primary"
|
Color="Color.Primary"
|
||||||
|
FullWidth="true"
|
||||||
OnClick="LoadPoolData"
|
OnClick="LoadPoolData"
|
||||||
StartIcon="@Icons.Material.Filled.Refresh">
|
StartIcon="@Icons.Material.Filled.Search"
|
||||||
بارگذاری
|
Disabled="_isLoading">
|
||||||
|
نمایش
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudStack>
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
<!-- Pool Details -->
|
@* ── لودینگ ── *@
|
||||||
@if (_poolData != null)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
<MudPaper Elevation="0" Class="pa-6 d-flex justify-center align-center">
|
||||||
<MudText Typo="Typo.h6" Class="mb-3">جزئیات استخر</MudText>
|
<MudStack AlignItems="AlignItems.Center" Spacing="2">
|
||||||
<MudSimpleTable Hover="true" Dense="true">
|
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Medium" />
|
||||||
<tbody>
|
<MudText Typo="Typo.body2" Color="Color.Secondary">در حال بارگذاری اطلاعات استخر…</MudText>
|
||||||
<tr>
|
</MudStack>
|
||||||
<td><strong>شناسه استخر:</strong></td>
|
</MudPaper>
|
||||||
<td>@_poolData.Id</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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
|
else
|
||||||
{
|
{
|
||||||
<MudText Color="Color.Secondary">-</MudText>
|
@* ── نوار وضعیت هفته / پکیج ── *@
|
||||||
}
|
<MudPaper Elevation="0" Class="pa-3 mud-background-gray rounded">
|
||||||
</td>
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Wrap="Wrap.Wrap">
|
||||||
</tr>
|
<MudIcon Icon="@Icons.Material.Filled.DateRange" Color="Color.Default" Size="Size.Small" />
|
||||||
<tr>
|
<MudText Typo="Typo.body1">@_currentWeekDisplayText</MudText>
|
||||||
<td><strong>تاریخ ایجاد:</strong></td>
|
|
||||||
<td>@_poolData.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</td>
|
@if (!string.IsNullOrEmpty(_poolData?.PackageTitle))
|
||||||
</tr>
|
{
|
||||||
</tbody>
|
<MudDivider Vertical="true" FlexItem="true" />
|
||||||
</MudSimpleTable>
|
<MudChip T="string" Size="Size.Small" Color="Color.Primary" Icon="@Icons.Material.Filled.WorkspacePremium">
|
||||||
</MudPaper>
|
@_poolData.PackageTitle
|
||||||
|
</MudChip>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Quick Actions -->
|
<MudSpacer />
|
||||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
|
||||||
<MudText Typo="Typo.h6" Class="mb-3">عملیات سریع</MudText>
|
@if (_poolData != null && _poolData.Id > 0)
|
||||||
<MudStack Row="true" Spacing="2">
|
{
|
||||||
|
@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"
|
<MudButton Variant="Variant.Filled"
|
||||||
Color="Color.Primary"
|
Color="Color.Primary"
|
||||||
StartIcon="@Icons.Material.Filled.Payments"
|
StartIcon="@Icons.Material.Filled.Payments"
|
||||||
Href="/commission/payouts">
|
Href="/commission/payouts"
|
||||||
مشاهده پرداختها
|
Size="Size.Medium">
|
||||||
</MudButton>
|
پرداختهای این هفته
|
||||||
<MudButton Variant="Variant.Filled"
|
|
||||||
Color="Color.Secondary"
|
|
||||||
StartIcon="@Icons.Material.Filled.AccountBalance"
|
|
||||||
Href="/commission/withdrawals">
|
|
||||||
درخواستهای برداشت
|
|
||||||
</MudButton>
|
</MudButton>
|
||||||
<MudButton Variant="Variant.Outlined"
|
<MudButton Variant="Variant.Outlined"
|
||||||
Color="Color.Info"
|
Color="Color.Default"
|
||||||
StartIcon="@Icons.Material.Filled.Calculate"
|
StartIcon="@Icons.Material.Filled.AccountBalance"
|
||||||
Disabled="@(_poolData?.IsCalculated == true)"
|
Href="/commission/withdrawals"
|
||||||
OnClick="TriggerCalculation">
|
Size="Size.Medium">
|
||||||
اجرای محاسبه دستی
|
درخواستهای برداشت
|
||||||
</MudButton>
|
</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>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
|
</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);
|
||||||
|
|||||||
@@ -49,6 +49,21 @@
|
|||||||
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>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public partial class UserPayouts
|
|||||||
private long? _filterWeekDefinitionId;
|
private long? _filterWeekDefinitionId;
|
||||||
private int? _filterStatus;
|
private int? _filterStatus;
|
||||||
private long? _filterPackageId;
|
private long? _filterPackageId;
|
||||||
|
private bool _isCreditingWallets;
|
||||||
|
|
||||||
private async Task<GridData<UserCommissionPayoutModel>> ServerReload(GridState<UserCommissionPayoutModel> state)
|
private async Task<GridData<UserCommissionPayoutModel>> ServerReload(GridState<UserCommissionPayoutModel> state)
|
||||||
{
|
{
|
||||||
@@ -147,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(
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,144 +77,10 @@
|
|||||||
</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>
|
|
||||||
<MudChip T="string" Color="@GetStatusColor(Order.Status)" Size="Size.Large">
|
|
||||||
@GetStatusText(Order.Status)
|
|
||||||
</MudChip>
|
|
||||||
@if (Order.Created.HasValue)
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.body2" Class="mt-2">
|
|
||||||
تاریخ ثبت: @Order.Created.Value.MiladiToJalaliWithTime()
|
|
||||||
</MudText>
|
|
||||||
}
|
|
||||||
</MudPaper>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<!-- وضعیت پرداخت -->
|
|
||||||
<MudItem xs="12" sm="6">
|
|
||||||
<MudPaper Class="pa-4" Elevation="1">
|
|
||||||
<MudText Typo="Typo.h6" GutterBottom="true">وضعیت پرداخت</MudText>
|
|
||||||
@if (Order.PaymentCompleted)
|
|
||||||
{
|
|
||||||
<MudChip T="string" Color="Color.Success" Size="Size.Large" Icon="@Icons.Material.Filled.CheckCircle">
|
|
||||||
پرداخت شده
|
|
||||||
</MudChip>
|
|
||||||
@if (!string.IsNullOrEmpty(Order.TransactionId))
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.body2" Class="mt-2">
|
|
||||||
کد تراکنش: @Order.TransactionId
|
|
||||||
</MudText>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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"
|
<MudSelect T="OrderStatus"
|
||||||
Label="وضعیت ارسال"
|
Label="وضعیت ارسال"
|
||||||
Dense="true"
|
Dense="true"
|
||||||
@@ -189,18 +91,16 @@
|
|||||||
<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>
|
||||||
</MudItem>
|
</MudText>
|
||||||
<MudItem xs="12" sm="6">
|
|
||||||
<MudTextField T="string"
|
<MudTextField T="string"
|
||||||
Label="کد رهگیری"
|
Label="کد رهگیری"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
Dense="true"
|
Dense="true"
|
||||||
@bind-Value="_trackingCode" />
|
@bind-Value="_trackingCode" />
|
||||||
</MudItem>
|
|
||||||
<MudItem xs="12">
|
|
||||||
<MudTextField T="string"
|
<MudTextField T="string"
|
||||||
Label="یادداشت ادمین"
|
Label="یادداشت ادمین"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
@@ -208,51 +108,124 @@
|
|||||||
Lines="3"
|
Lines="3"
|
||||||
HelperText="دلیل تغییر وضعیت یا توضیحات اضافی"
|
HelperText="دلیل تغییر وضعیت یا توضیحات اضافی"
|
||||||
@bind-Value="_adminNotes" />
|
@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>
|
||||||
}
|
}
|
||||||
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
<!-- یادداشتها -->
|
<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))
|
||||||
{
|
{
|
||||||
<MudItem xs="12">
|
@if (!string.IsNullOrWhiteSpace(Order.Address.Title))
|
||||||
<MudPaper Class="pa-4" Elevation="1">
|
{
|
||||||
|
<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))
|
@if (!string.IsNullOrEmpty(Order.Notes))
|
||||||
{
|
{
|
||||||
<MudText Typo="Typo.h6" GutterBottom="true">
|
<MudPaper Class="pa-3">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Comment" Size="Size.Small" />
|
<MudStack Spacing="1">
|
||||||
یادداشت کاربر
|
<MudText Typo="Typo.subtitle2">یادداشت کاربر</MudText>
|
||||||
</MudText>
|
<MudDivider />
|
||||||
<MudText Typo="Typo.body1" Class="mb-4">@Order.Notes</MudText>
|
<MudText Typo="Typo.body2">@Order.Notes</MudText>
|
||||||
}
|
</MudStack>
|
||||||
@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>
|
</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,
|
|
||||||
OrderStatus.Shipped => Color.Secondary,
|
|
||||||
OrderStatus.Delivered => Color.Success,
|
|
||||||
OrderStatus.Cancelled => Color.Error,
|
|
||||||
OrderStatus.Returned => Color.Dark,
|
|
||||||
_ => Color.Default
|
_ => Color.Default
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
private string GetStatusText(OrderStatus status)
|
private string GetPaymentStatusText(int status) => status switch
|
||||||
{
|
{
|
||||||
return status switch
|
0 => "در انتظار پرداخت",
|
||||||
{
|
1 => "پرداخت شده",
|
||||||
OrderStatus.Pending => "در انتظار پرداخت",
|
2 => "ناموفق",
|
||||||
OrderStatus.Paid => "پرداخت شده",
|
3 => "بازپرداخت شده",
|
||||||
OrderStatus.Processing => "در حال آمادهسازی",
|
|
||||||
OrderStatus.Shipped => "ارسال شده",
|
|
||||||
OrderStatus.Delivered => "تحویل داده شده",
|
|
||||||
OrderStatus.Cancelled => "لغو شده",
|
|
||||||
OrderStatus.Returned => "مرجوع شده",
|
|
||||||
_ => "نامشخص"
|
_ => "نامشخص"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
Title = i.ProductTitle,
|
||||||
|
Quantity = i.Count,
|
||||||
|
UnitPrice = i.UnitPrice,
|
||||||
|
Discount = i.DiscountAmount,
|
||||||
|
LineTotal = i.FinalPrice > 0 ? i.FinalPrice : i.UnitPrice * i.Count
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
"تعداد آیتم", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت", "تاریخ ثبت", "آدرس"
|
||||||
|
};
|
||||||
|
|
||||||
|
var rows = _orders.Select(o => new object?[]
|
||||||
{
|
{
|
||||||
sb.AppendLine(string.Join(",",
|
|
||||||
o.Id,
|
o.Id,
|
||||||
EscapeCsv(o.UserFullName),
|
CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
|
||||||
EscapeCsv(o.UserMobile),
|
o.UserMobile ?? "",
|
||||||
o.TotalPrice.ToString("N0"),
|
o.TotalPrice,
|
||||||
o.DiscountBalanceUsed.ToString("N0"),
|
o.DiscountBalanceUsed,
|
||||||
o.GatewayAmount.ToString("N0"),
|
o.GatewayAmount,
|
||||||
o.ItemsCount,
|
o.ItemsCount,
|
||||||
EscapeCsv(GetPaymentStatusText(o.PaymentStatusValue)),
|
GetPaymentStatusText(o.PaymentStatusValue),
|
||||||
EscapeCsv(GetStatusText(o.Status)),
|
GetStatusText(o.Status),
|
||||||
o.PaymentDate?.MiladiToJalaliWithTime() ?? "-",
|
o.PaymentDate?.MiladiToJalaliWithTime() ?? "-",
|
||||||
o.Created?.MiladiToJalaliWithTime() ?? "-",
|
o.Created?.MiladiToJalaliWithTime() ?? "-",
|
||||||
EscapeCsv(o.ShippingAddress ?? "-")));
|
o.ShippingAddress ?? "-"
|
||||||
}
|
});
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
var filename = $"discount-orders-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||||
var base64 = Convert.ToBase64String(bytes);
|
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||||
var filename = $"discount-orders-{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);
|
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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))
|
};
|
||||||
|
|
||||||
|
var rows = _orders
|
||||||
|
.OrderBy(o => o.Created ?? o.PaymentDate ?? DateTime.MaxValue)
|
||||||
|
.Select(o => new object?[]
|
||||||
{
|
{
|
||||||
sb.AppendLine(string.Join(",",
|
|
||||||
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()
|
||||||
@@ -572,12 +578,12 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
@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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
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>
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
{
|
{
|
||||||
1 => "واریز نقدی",
|
1 => "واریز نقدی",
|
||||||
2 => "شارژ کیف پول اعتباری",
|
2 => "شارژ کیف پول اعتباری",
|
||||||
3 => "شارژ پاداش تیمی",
|
3 => "شارژ پاداش های دریافتی",
|
||||||
4 => "تسویه حساب",
|
4 => "تسویه حساب",
|
||||||
5 => "اصلاح خطا",
|
5 => "اصلاح خطا",
|
||||||
6 => "بازگشت وجه",
|
6 => "بازگشت وجه",
|
||||||
|
|||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
<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>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6" md="3">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
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;
|
||||||
@@ -215,7 +216,8 @@ public partial class OrderSalesReports
|
|||||||
2 => Color.Info,
|
2 => Color.Info,
|
||||||
3 => Color.Success,
|
3 => Color.Success,
|
||||||
4 => Color.Error,
|
4 => Color.Error,
|
||||||
5 => Color.Error,
|
5 => Color.Dark,
|
||||||
|
6 => Color.Primary,
|
||||||
_ => Color.Default
|
_ => Color.Default
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -229,6 +231,7 @@ public partial class OrderSalesReports
|
|||||||
3 => "تحویل به مشتری",
|
3 => "تحویل به مشتری",
|
||||||
4 => "مرجوع شده",
|
4 => "مرجوع شده",
|
||||||
5 => "لغو شده",
|
5 => "لغو شده",
|
||||||
|
6 => "آماده تحویل در دفتر",
|
||||||
_ => "بدون ارسال / نامشخص"
|
_ => "بدون ارسال / نامشخص"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -241,27 +244,26 @@ public partial class OrderSalesReports
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var headers = new[]
|
||||||
sb.AppendLine("شناسه,کاربر,نام کاربر,مبلغ,وضعیت پرداخت,وضعیت ارسال,تاریخ پرداخت");
|
{
|
||||||
|
"شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت"
|
||||||
foreach (var o in _orders)
|
};
|
||||||
|
|
||||||
|
var rows = _orders.Select(o => new object?[]
|
||||||
{
|
{
|
||||||
sb.AppendLine(string.Join(",",
|
|
||||||
o.Id,
|
o.Id,
|
||||||
o.UserId,
|
o.UserId,
|
||||||
EscapeCsv(o.UserFullName),
|
CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
|
||||||
o.Amount,
|
o.Amount,
|
||||||
EscapeCsv(o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده"),
|
o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده",
|
||||||
EscapeCsv(GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())),
|
GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
|
||||||
o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-"));
|
o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-"
|
||||||
}
|
});
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
var filename = $"store-sales-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||||
var base64 = Convert.ToBase64String(bytes);
|
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||||
var filename = $"store-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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task ExportToPdf()
|
protected async Task ExportToPdf()
|
||||||
@@ -290,7 +292,7 @@ public partial class OrderSalesReports
|
|||||||
foreach (var o in _orders)
|
foreach (var o in _orders)
|
||||||
{
|
{
|
||||||
sb.AppendLine(
|
sb.AppendLine(
|
||||||
$"{o.Id} | {o.UserFullName} | {o.Amount:N0} | {(o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده")} | {GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())} | {(o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-")}");
|
$"{o.Id} | {CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId)} | {o.Amount:N0} | {(o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده")} | {GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())} | {(o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-")}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
||||||
@@ -349,12 +351,4 @@ public partial class OrderSalesReports
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string EscapeCsv(string value)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(value)) return "";
|
|
||||||
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
|
|
||||||
return $"\"{value.Replace("\"", "\"\"")}\"";
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,11 @@
|
|||||||
@bind-Value="@_request.Filter.Amount" Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
@bind-Value="@_request.Filter.Amount" 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="_paymentDateFrom"
|
<MudDatePicker Label="از تاریخ پرداخت" @bind-Date="_paymentDateFrom"
|
||||||
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="_paymentDateTo"
|
<MudDatePicker Label="تا تاریخ پرداخت" @bind-Date="_paymentDateTo"
|
||||||
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">
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
<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>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</Filters>
|
</Filters>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.Text.Json;
|
using System.Text;
|
||||||
using System.Text;
|
using System.Text.Json;
|
||||||
|
using BackOffice.Common.BaseComponents;
|
||||||
|
using BackOffice.Common.Utilities;
|
||||||
using CMSMicroservice.Protobuf.Protos;
|
using CMSMicroservice.Protobuf.Protos;
|
||||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||||
using BackOffice.Common.BaseComponents;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
@@ -213,6 +214,8 @@ public partial class UserOrderMainPage
|
|||||||
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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -225,6 +228,8 @@ public partial class UserOrderMainPage
|
|||||||
2 => "تحویل پست",
|
2 => "تحویل پست",
|
||||||
3 => "تحویل به مشتری",
|
3 => "تحویل به مشتری",
|
||||||
4 => "مرجوع شده",
|
4 => "مرجوع شده",
|
||||||
|
5 => "لغو شده",
|
||||||
|
6 => "آماده تحویل در دفتر",
|
||||||
_ => "بدون ارسال / نامشخص"
|
_ => "بدون ارسال / نامشخص"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -238,6 +243,15 @@ public partial class UserOrderMainPage
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Domain PaymentStatus: Success=0, Reject=1, Pending=2
|
||||||
|
private static string GetPaymentStatusText(int status) => status switch
|
||||||
|
{
|
||||||
|
0 => "پرداخت شده",
|
||||||
|
1 => "پرداخت ناموفق",
|
||||||
|
2 => "در انتظار پرداخت",
|
||||||
|
_ => "نامشخص"
|
||||||
|
};
|
||||||
|
|
||||||
private async Task OpenChangeStatus(DataModel model)
|
private async Task OpenChangeStatus(DataModel model)
|
||||||
{
|
{
|
||||||
var parameters = new DialogParameters
|
var parameters = new DialogParameters
|
||||||
@@ -315,40 +329,31 @@ public partial class UserOrderMainPage
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var headers = new[]
|
||||||
sb.AppendLine("شناسه,کاربر,نام کاربر,مبلغ,وضعیت پرداخت,وضعیت ارسال,روش پرداخت,تاریخ پرداخت");
|
{
|
||||||
|
"شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "روش پرداخت", "تاریخ پرداخت"
|
||||||
foreach (var o in result.Models)
|
};
|
||||||
|
|
||||||
|
var rows = result.Models.Select(o => new object?[]
|
||||||
{
|
{
|
||||||
sb.AppendLine(string.Join(",",
|
|
||||||
o.Id,
|
o.Id,
|
||||||
o.UserId,
|
o.UserId,
|
||||||
EscapeCsv(o.UserFullName),
|
CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
|
||||||
o.Amount,
|
o.Amount,
|
||||||
EscapeCsv(o.PaymentStatus.ToString()),
|
GetPaymentStatusText(o.PaymentStatus.GetHashCode()),
|
||||||
EscapeCsv(GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())),
|
GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
|
||||||
EscapeCsv(GetPaymentMethodText(o.PaymentMethod.GetHashCode())),
|
GetPaymentMethodText(o.PaymentMethod.GetHashCode()),
|
||||||
o.PaymentDate?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-"));
|
o.PaymentDate?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-"
|
||||||
}
|
});
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
var filename = $"user-orders-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||||
var base64 = Convert.ToBase64String(bytes);
|
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||||
var filename = $"user-orders-{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);
|
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,11 +43,16 @@
|
|||||||
<MudText Color="Color.Primary"><strong>@context.Item.Balance.ToString("N0")</strong> تومان</MudText>
|
<MudText Color="Color.Primary"><strong>@context.Item.Balance.ToString("N0")</strong> تومان</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="پاداش تیمی">
|
<TemplateColumn Title="پاداش های دریافتی">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudText Color="Color.Success">@context.Item.NetworkBalance.ToString("N0") تومان</MudText>
|
<MudText Color="Color.Success">@context.Item.NetworkBalance.ToString("N0") تومان</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="موجودی اعتباری">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudText Color="Color.Tertiary">@context.Item.DiscountBalance.ToString("N0") تومان</MudText>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="عملیات" Sortable="false">
|
<TemplateColumn Title="عملیات" Sortable="false">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudTooltip Text="تاریخچه تغییرات">
|
<MudTooltip Text="تاریخچه تغییرات">
|
||||||
@@ -110,16 +115,26 @@
|
|||||||
<MudText Color="Color.Primary">@context.Item.CurrentBalance.ToString("N0")</MudText>
|
<MudText Color="Color.Primary">@context.Item.CurrentBalance.ToString("N0")</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="تغییر پاداش تیمی">
|
<TemplateColumn Title="تغییر پاداش های دریافتی">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudText>@context.Item.ChangeNerworkValue.ToString("N0")</MudText>
|
<MudText>@context.Item.ChangeNerworkValue.ToString("N0")</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="پاداش تیمی فعلی">
|
<TemplateColumn Title="پاداش های دریافتی فعلی">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudText Color="Color.Success">@context.Item.CurrentNetworkBalance.ToString("N0")</MudText>
|
<MudText Color="Color.Success">@context.Item.CurrentNetworkBalance.ToString("N0")</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="تغییر موجودی اعتباری">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudText>@context.Item.ChangeDiscountValue.ToString("N0")</MudText>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="موجودی اعتباری">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudText Color="Color.Tertiary">@context.Item.CurrentDiscountBalance.ToString("N0")</MudText>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="شناسه مرجع">
|
<TemplateColumn Title="شناسه مرجع">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@(context.Item.RefrenceId?.ToString() ?? "—")
|
@(context.Item.RefrenceId?.ToString() ?? "—")
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ public class DiscountOrderService : IDiscountOrderService
|
|||||||
DiscountBalanceUsed = response.DiscountBalanceUsed,
|
DiscountBalanceUsed = response.DiscountBalanceUsed,
|
||||||
GatewayAmount = response.GatewayAmount,
|
GatewayAmount = response.GatewayAmount,
|
||||||
PaymentCompleted = response.PaymentCompleted,
|
PaymentCompleted = response.PaymentCompleted,
|
||||||
|
PaymentStatusValue = (int)response.PaymentStatus,
|
||||||
TransactionId = response.TransactionId,
|
TransactionId = response.TransactionId,
|
||||||
Status = MapFromProtoStatus(response.DeliveryStatus, response.PaymentCompleted),
|
Status = MapFromProtoStatus(response.DeliveryStatus, response.PaymentCompleted),
|
||||||
TrackingCode = response.TrackingCode,
|
TrackingCode = response.TrackingCode,
|
||||||
@@ -200,12 +201,13 @@ public class DiscountOrderService : IDiscountOrderService
|
|||||||
return status switch
|
return status switch
|
||||||
{
|
{
|
||||||
OrderStatus.Pending => ProtoDeliveryStatus.DeliveryPending,
|
OrderStatus.Pending => ProtoDeliveryStatus.DeliveryPending,
|
||||||
OrderStatus.Paid => ProtoDeliveryStatus.DeliveryPending, // Paid but not processed yet
|
OrderStatus.Paid => ProtoDeliveryStatus.DeliveryPending,
|
||||||
OrderStatus.Processing => ProtoDeliveryStatus.DeliveryProcessing,
|
OrderStatus.Processing => ProtoDeliveryStatus.DeliveryProcessing,
|
||||||
OrderStatus.Shipped => ProtoDeliveryStatus.DeliveryShipped,
|
OrderStatus.Shipped => ProtoDeliveryStatus.DeliveryShipped,
|
||||||
OrderStatus.Delivered => ProtoDeliveryStatus.DeliveryDelivered,
|
OrderStatus.Delivered => ProtoDeliveryStatus.DeliveryDelivered,
|
||||||
OrderStatus.Cancelled => ProtoDeliveryStatus.DeliveryCancelled,
|
OrderStatus.Cancelled => ProtoDeliveryStatus.DeliveryCancelled,
|
||||||
OrderStatus.Returned => ProtoDeliveryStatus.DeliveryCancelled, // Treat as cancelled
|
OrderStatus.ReadyForOfficePickup => ProtoDeliveryStatus.DeliveryReadyForOffice,
|
||||||
|
OrderStatus.Returned => ProtoDeliveryStatus.DeliveryReturned,
|
||||||
_ => ProtoDeliveryStatus.DeliveryPending
|
_ => ProtoDeliveryStatus.DeliveryPending
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -221,6 +223,8 @@ public class DiscountOrderService : IDiscountOrderService
|
|||||||
ProtoDeliveryStatus.DeliveryShipped => OrderStatus.Shipped,
|
ProtoDeliveryStatus.DeliveryShipped => OrderStatus.Shipped,
|
||||||
ProtoDeliveryStatus.DeliveryDelivered => OrderStatus.Delivered,
|
ProtoDeliveryStatus.DeliveryDelivered => OrderStatus.Delivered,
|
||||||
ProtoDeliveryStatus.DeliveryCancelled => OrderStatus.Cancelled,
|
ProtoDeliveryStatus.DeliveryCancelled => OrderStatus.Cancelled,
|
||||||
|
ProtoDeliveryStatus.DeliveryReadyForOffice => OrderStatus.ReadyForOfficePickup,
|
||||||
|
ProtoDeliveryStatus.DeliveryReturned => OrderStatus.Returned,
|
||||||
_ => OrderStatus.Pending
|
_ => OrderStatus.Pending
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -235,6 +239,8 @@ public class DiscountOrderService : IDiscountOrderService
|
|||||||
ProtoDeliveryStatus.DeliveryShipped => OrderStatus.Shipped,
|
ProtoDeliveryStatus.DeliveryShipped => OrderStatus.Shipped,
|
||||||
ProtoDeliveryStatus.DeliveryDelivered => OrderStatus.Delivered,
|
ProtoDeliveryStatus.DeliveryDelivered => OrderStatus.Delivered,
|
||||||
ProtoDeliveryStatus.DeliveryCancelled => OrderStatus.Cancelled,
|
ProtoDeliveryStatus.DeliveryCancelled => OrderStatus.Cancelled,
|
||||||
|
ProtoDeliveryStatus.DeliveryReadyForOffice => OrderStatus.ReadyForOfficePickup,
|
||||||
|
ProtoDeliveryStatus.DeliveryReturned => OrderStatus.Returned,
|
||||||
_ => OrderStatus.Pending
|
_ => OrderStatus.Pending
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ public enum OrderStatus
|
|||||||
Shipped = 3, // DELIVERY_SHIPPED
|
Shipped = 3, // DELIVERY_SHIPPED
|
||||||
Delivered = 4, // DELIVERY_DELIVERED
|
Delivered = 4, // DELIVERY_DELIVERED
|
||||||
Cancelled = 5, // DELIVERY_CANCELLED
|
Cancelled = 5, // DELIVERY_CANCELLED
|
||||||
Returned = 6 // Not in proto - handle in service
|
Returned = 6, // DELIVERY_RETURNED
|
||||||
|
ReadyForOfficePickup = 7 // DELIVERY_READY_FOR_OFFICE
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DiscountOrderDto
|
public class DiscountOrderDto
|
||||||
@@ -62,11 +63,17 @@ public class DiscountOrderDetailsDto
|
|||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
public long UserId { get; set; }
|
public long UserId { get; set; }
|
||||||
public string OrderNumber { get; set; } = string.Empty;
|
public string OrderNumber { get; set; } = string.Empty;
|
||||||
|
/// <summary>از ردیف لیست پر میشود (GetOrderById این فیلدها را ندارد)</summary>
|
||||||
|
public string UserFullName { get; set; } = string.Empty;
|
||||||
|
public string UserMobile { get; set; } = string.Empty;
|
||||||
public AddressInfoDto? Address { get; set; }
|
public AddressInfoDto? Address { get; set; }
|
||||||
public long TotalPrice { get; set; }
|
public long TotalPrice { get; set; }
|
||||||
public long DiscountBalanceUsed { get; set; }
|
public long DiscountBalanceUsed { get; set; }
|
||||||
public long GatewayAmount { get; set; }
|
public long GatewayAmount { get; set; }
|
||||||
|
public long VatAmount { get; set; }
|
||||||
public bool PaymentCompleted { get; set; }
|
public bool PaymentCompleted { get; set; }
|
||||||
|
/// <summary>مقدار proto PaymentStatus: 0 Pending, 1 Completed, 2 Failed, 3 Refunded</summary>
|
||||||
|
public int PaymentStatusValue { get; set; }
|
||||||
public string? TransactionId { get; set; }
|
public string? TransactionId { get; set; }
|
||||||
public OrderStatus Status { get; set; }
|
public OrderStatus Status { get; set; }
|
||||||
public string? TrackingCode { get; set; }
|
public string? TrackingCode { get; set; }
|
||||||
|
|||||||
@@ -96,6 +96,7 @@
|
|||||||
// Payment
|
// Payment
|
||||||
"payment" => "پرداخت",
|
"payment" => "پرداخت",
|
||||||
"transactions" => "تراکنشها",
|
"transactions" => "تراکنشها",
|
||||||
|
"ledger-transactions" => "دفتر تراکنشهای مالی",
|
||||||
"manual-payments" => "پرداخت دستی",
|
"manual-payments" => "پرداخت دستی",
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
|
|||||||
@@ -75,7 +75,12 @@
|
|||||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||||
Href="/club/members"
|
Href="/club/members"
|
||||||
Icon="@Icons.Material.Filled.Groups">
|
Icon="@Icons.Material.Filled.Groups">
|
||||||
اعضا و آمار
|
اعضا و گزارشها
|
||||||
|
</MudNavLink>
|
||||||
|
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||||
|
Href="/club/customer-packages"
|
||||||
|
Icon="@Icons.Material.Filled.PeopleAlt">
|
||||||
|
گزارش مشتریان پکیج
|
||||||
</MudNavLink>
|
</MudNavLink>
|
||||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||||
Href="/club/features"
|
Href="/club/features"
|
||||||
@@ -200,6 +205,24 @@
|
|||||||
</MudNavLink>
|
</MudNavLink>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||||
|
Href="/payment/manual-credit-charges"
|
||||||
|
Icon="@Icons.Material.Filled.AddCard">
|
||||||
|
شارژ دستی کیف پول
|
||||||
|
</MudNavLink>
|
||||||
|
|
||||||
|
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||||
|
Href="/payment/transactions"
|
||||||
|
Icon="@Icons.Material.Filled.ReceiptLong">
|
||||||
|
گزارش تراکنش درگاه
|
||||||
|
</MudNavLink>
|
||||||
|
|
||||||
|
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||||
|
Href="/payment/ledger-transactions"
|
||||||
|
Icon="@Icons.Material.Filled.AccountBalance">
|
||||||
|
دفتر تراکنشهای مالی
|
||||||
|
</MudNavLink>
|
||||||
|
|
||||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||||
Href="/wallets"
|
Href="/wallets"
|
||||||
Icon="@Icons.Material.Filled.AccountBalanceWallet">
|
Icon="@Icons.Material.Filled.AccountBalanceWallet">
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Generate nginx config with blazor-environment header
|
||||||
|
# BLAZOR_ENVIRONMENT defaults to "Staging" if not set
|
||||||
|
BLAZOR_ENV="${BLAZOR_ENVIRONMENT:-Staging}"
|
||||||
|
|
||||||
|
cat > /etc/nginx/conf.d/default.conf <<NGINX_CONF
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
try_files \$uri \$uri/ /index.html;
|
||||||
|
add_header blazor-environment $BLAZOR_ENV;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NGINX_CONF
|
||||||
|
|
||||||
|
# Inject GW_URL into all appsettings JSON files
|
||||||
|
# This overrides GwUrl regardless of which environment Blazor detects,
|
||||||
|
# which is critical because the service worker cache strips the
|
||||||
|
# blazor-environment header and Blazor falls back to Production.
|
||||||
|
WWWROOT="/usr/share/nginx/html"
|
||||||
|
if [ -n "$GW_URL" ]; then
|
||||||
|
echo "[entrypoint] Setting GwUrl to $GW_URL in all appsettings files"
|
||||||
|
for f in "$WWWROOT"/appsettings*.json; do
|
||||||
|
[ -f "$f" ] && sed -i "s|\"GwUrl\":\s*\"[^\"]*\"|\"GwUrl\": \"$GW_URL\"|g" "$f"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec nginx -g 'daemon off;'
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"GwUrl": "https://cms.kbs1.ir",
|
"GwUrl": "https://cms.kbs2.ir",
|
||||||
"Authentication": {
|
"Authentication": {
|
||||||
"Authority": "https://ids.afrino.co/",
|
"Authority": "https://ids.afrino.co/",
|
||||||
"ClientId": "client_backoffice_spa"
|
"ClientId": "client_backoffice_spa"
|
||||||
|
|||||||
@@ -94,8 +94,9 @@
|
|||||||
.admin-node-card .node-meta {
|
.admin-node-card .node-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 4px;
|
||||||
margin-top: 2px;
|
margin-top: 3px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-node-card .level-badge {
|
.admin-node-card .level-badge {
|
||||||
@@ -124,8 +125,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 6px;
|
margin-top: 5px;
|
||||||
padding-top: 6px;
|
padding-top: 5px;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1px solid #f0f0f0;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
@@ -204,6 +205,48 @@
|
|||||||
animation: pulse-highlight 1.5s ease-in-out infinite;
|
animation: pulse-highlight 1.5s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* نوع فعالسازی - inline pill */
|
||||||
|
.admin-node-card .activation-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-node-card .new-member-pill {
|
||||||
|
background: #e8f5e9;
|
||||||
|
color: #2e7d32;
|
||||||
|
border: 1px solid #a5d6a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-node-card .renewal-pill {
|
||||||
|
background: #ede7f6;
|
||||||
|
color: #5e35b1;
|
||||||
|
border: 1px solid #b39ddb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* نام پکیج badge */
|
||||||
|
.admin-node-card .package-name-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 14px;
|
||||||
|
padding: 0 6px;
|
||||||
|
margin-top: 2px;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 9px;
|
||||||
|
color: #546e7a;
|
||||||
|
background: #eceff1;
|
||||||
|
border: 1px solid #b0bec5;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulse-highlight {
|
@keyframes pulse-highlight {
|
||||||
0%, 100% { transform: scale(1); opacity: 1; }
|
0%, 100% { transform: scale(1); opacity: 1; }
|
||||||
50% { transform: scale(1.2); opacity: 0.8; }
|
50% { transform: scale(1.2); opacity: 0.8; }
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ window.AdminOrgChart = {
|
|||||||
this.chart = new d3.OrgChart()
|
this.chart = new d3.OrgChart()
|
||||||
.container('#' + containerId)
|
.container('#' + containerId)
|
||||||
.data(data)
|
.data(data)
|
||||||
.nodeWidth((d) => 160)
|
.nodeWidth((d) => 178)
|
||||||
.nodeHeight((d) => 80)
|
.nodeHeight((d) => 92)
|
||||||
.childrenMargin((d) => 60)
|
.childrenMargin((d) => 60)
|
||||||
.compactMarginBetween((d) => 20)
|
.compactMarginBetween((d) => 20)
|
||||||
.compactMarginPair((d) => 20)
|
.compactMarginPair((d) => 20)
|
||||||
@@ -83,12 +83,19 @@ window.AdminOrgChart = {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight badge for target week matches
|
// نوع فعالسازی - inline pill در meta row
|
||||||
let highlightBadge = '';
|
let activationTypePill = '';
|
||||||
if (weekFilterActive && isTargetWeek) {
|
if (data.isNewActivation === true) {
|
||||||
highlightBadge = '<div class="target-week-highlight">✨</div>';
|
activationTypePill = '<span class="activation-pill new-member-pill">عضو جدید</span>';
|
||||||
|
} else if (data.isNewActivation === false) {
|
||||||
|
activationTypePill = '<span class="activation-pill renewal-pill">تمدید</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// نام پکیج badge
|
||||||
|
const packageBadge = data.packageName
|
||||||
|
? `<div class="package-name-badge">${data.packageName}</div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
// Avatar - first letter of name
|
// Avatar - first letter of name
|
||||||
const firstChar = data.userName ? data.userName.charAt(0).toUpperCase() : '?';
|
const firstChar = data.userName ? data.userName.charAt(0).toUpperCase() : '?';
|
||||||
|
|
||||||
@@ -114,6 +121,14 @@ window.AdminOrgChart = {
|
|||||||
if (data.activationWeekDefinitionId) {
|
if (data.activationWeekDefinitionId) {
|
||||||
tooltipLines.push(`📆 هفته: ${data.activationWeekDefinitionId}`);
|
tooltipLines.push(`📆 هفته: ${data.activationWeekDefinitionId}`);
|
||||||
}
|
}
|
||||||
|
if (data.packageName) {
|
||||||
|
tooltipLines.push(`📦 پکیج: ${data.packageName}`);
|
||||||
|
}
|
||||||
|
if (data.isNewActivation === true) {
|
||||||
|
tooltipLines.push('🆕 نوع: اولین فعالسازی');
|
||||||
|
} else if (data.isNewActivation === false) {
|
||||||
|
tooltipLines.push('🔄 نوع: خرید مجدد');
|
||||||
|
}
|
||||||
if (weekFilterActive) {
|
if (weekFilterActive) {
|
||||||
tooltipLines.push(isTargetWeek ? '🎯 فعال در هفته انتخابی' : '⚪ خارج از هفته انتخابی');
|
tooltipLines.push(isTargetWeek ? '🎯 فعال در هفته انتخابی' : '⚪ خارج از هفته انتخابی');
|
||||||
}
|
}
|
||||||
@@ -125,14 +140,15 @@ window.AdminOrgChart = {
|
|||||||
data-user-id="${data.userId}"
|
data-user-id="${data.userId}"
|
||||||
title="${tooltipText}">
|
title="${tooltipText}">
|
||||||
${weekIndicator}
|
${weekIndicator}
|
||||||
${highlightBadge}
|
|
||||||
<div class="node-header">
|
<div class="node-header">
|
||||||
<div class="node-avatar">${firstChar}</div>
|
<div class="node-avatar">${firstChar}</div>
|
||||||
<div class="node-main-info">
|
<div class="node-main-info">
|
||||||
<div class="node-name">${data.userName || 'کاربر ' + data.userId}</div>
|
<div class="node-name">${data.userName || 'کاربر ' + data.userId}</div>
|
||||||
|
${packageBadge}
|
||||||
<div class="node-meta">
|
<div class="node-meta">
|
||||||
${levelBadge}
|
${levelBadge}
|
||||||
${legText ? `<span class="${legClass}">${legText}</span>` : ''}
|
${legText ? `<span class="${legClass}">${legText}</span>` : ''}
|
||||||
|
${activationTypePill}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
|
|||||||
const cacheNamePrefix = 'offline-cache-';
|
const cacheNamePrefix = 'offline-cache-';
|
||||||
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
|
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
|
||||||
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
|
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
|
||||||
const offlineAssetsExclude = [ /^service-worker\.js$/ ];
|
const offlineAssetsExclude = [ /^service-worker\.js$/, /^appsettings.*\.json$/ ];
|
||||||
|
|
||||||
async function onInstall(event) {
|
async function onInstall(event) {
|
||||||
console.info('Service worker: Install');
|
console.info('Service worker: Install');
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "9.0.306",
|
||||||
|
"rollForward": "latestPatch"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user