Compare commits
78 Commits
e541aec115
...
production
| Author | SHA1 | Date | |
|---|---|---|---|
| 3eff5b6dfa | |||
| aa97d25302 | |||
| 24ae4ec15c | |||
| c3ee46ff62 | |||
| e9f8328404 | |||
| 6a9d142dc6 | |||
| 1d78633e17 | |||
| cbc61ad0f5 | |||
| 68d0aed468 | |||
| a9aa03d6a8 | |||
| e63b80fe47 | |||
| 4115f64fff | |||
| 7d892c2f6a | |||
| d757c8220c | |||
| b53d9facc9 | |||
| 5a004e5c88 | |||
| 5d83eb4e05 | |||
| 6126ecf023 | |||
| 31f0fc4860 | |||
| 0a2c5f913b | |||
| 2f040cab02 | |||
| 933cdb1094 | |||
| 4596296d97 | |||
| f2316eafe4 | |||
| 9c6149bbb9 | |||
| 8afc684098 | |||
| c17ad68c30 | |||
| 9378b7474c | |||
| 68d7647cb1 | |||
| c4712a00ac | |||
| 3b4562665d | |||
| 52c8965704 | |||
| 7fb2062721 | |||
| 797bfef964 | |||
| 6d8d681046 | |||
| b7d1fe13c7 | |||
| 67f57ce99f | |||
| c4cbe94ed7 | |||
| ed24f505d3 | |||
| 3c2a0edca8 | |||
| aed6258fb3 | |||
| e6d1d833ec | |||
| a8c52db544 | |||
| c0c7bb3ac5 | |||
| d0fe99492d | |||
| d38f50584c | |||
| 1c67d7798a | |||
| 2a7bb0592d | |||
| 9699833ac9 | |||
| b3e6066c90 | |||
| 148b8e4011 | |||
| 1ef5edf939 | |||
| 76dbee7d41 | |||
| 148a9e0493 | |||
| 851a1bbc4f | |||
| aa1d21a616 | |||
| 7147e9930f | |||
| 85208da1b5 | |||
| ea7414dc7f | |||
| a450d9a001 | |||
| 92c992249e | |||
| 6939780cd1 | |||
| e6cf90e3fa | |||
| e0203541bf | |||
| 8be98ae33e | |||
| c96377a674 | |||
| 89f5241680 | |||
| f1b0085181 | |||
| 60c91438ec | |||
| 172101d0ab | |||
| 1d8f34ae6c | |||
| 76b7211a5c | |||
| fe72774d08 | |||
| bdea2e8aef | |||
| 0386a2b1aa | |||
| 8f29fbaf71 | |||
| 7a62e3caac | |||
| 4eaca9bc4c |
@@ -0,0 +1,2 @@
|
||||
# Keep production config from current branch during merges — never overwrite
|
||||
src/BackOffice/wwwroot/appsettings.Production.json merge=ours
|
||||
@@ -0,0 +1,81 @@
|
||||
name: Build and Deploy to Kubernetes
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- kub-stage
|
||||
|
||||
env:
|
||||
REGISTRY: 194.5.195.53:30080
|
||||
IMAGE_NAME: admin/backoffice
|
||||
K8S_SERVER: 194.5.195.53
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: 194.5.195.53:32082/docker-sshpass:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- name: Start Docker daemon
|
||||
run: |
|
||||
mkdir -p /etc/docker
|
||||
cat > /etc/docker/daemon.json << 'DAEMON'
|
||||
{
|
||||
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"]
|
||||
}
|
||||
DAEMON
|
||||
echo "🚀 Starting Docker daemon..."
|
||||
dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs &
|
||||
|
||||
# Wait up to 3 minutes for Docker to be ready
|
||||
for i in $(seq 1 90); do
|
||||
if docker info >/dev/null 2>&1; then
|
||||
echo "✅ Docker daemon is ready (attempt $i)"
|
||||
docker version
|
||||
break
|
||||
else
|
||||
echo "⏳ Waiting for Docker daemon... (attempt $i/90)"
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
|
||||
# Final check
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "❌ Docker daemon failed to start after 3 minutes"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout code
|
||||
run: |
|
||||
git clone --depth 1 --branch kub-stage http://gitea-svc:3000/admin/BackOffice.git .
|
||||
|
||||
- name: Login to Docker registries
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin
|
||||
|
||||
- name: Build Docker Image
|
||||
run: |
|
||||
cd src
|
||||
DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -f BackOffice/Dockerfile .
|
||||
|
||||
- name: Push to Registry
|
||||
run: |
|
||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
- name: Deploy to Kubernetes
|
||||
run: |
|
||||
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
|
||||
|
||||
# Copy K8s manifests to server
|
||||
sshpass -e scp -o StrictHostKeyChecking=no k8s/staging/backoffice-deployment.yaml root@${{ env.K8S_SERVER }}:/tmp/backoffice-deployment.yaml
|
||||
|
||||
# Apply manifests and restart
|
||||
sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} "
|
||||
kubectl apply -f /tmp/backoffice-deployment.yaml &&
|
||||
kubectl rollout restart deployment/backoffice &&
|
||||
kubectl rollout status deployment/backoffice --timeout=180s &&
|
||||
rm -f /tmp/backoffice-deployment.yaml
|
||||
"
|
||||
echo "✅ Deployed!"
|
||||
@@ -0,0 +1,421 @@
|
||||
# سرویسهای BackOffice (پنل ادمین)
|
||||
|
||||
این سند تمام سرویسهایی را که **BackOffice** (فرانت ادمین) به **CMS** از طریق gRPC Gateway (`GwUrl`) فراخوانی میکند، به ترتیب **Use Case** فهرست میکند.
|
||||
|
||||
- **مسیر wrapper سرویسها:** `BackOffice/src/BackOffice/Services/`
|
||||
- **ثبت gRPC:** `BackOffice/src/BackOffice/Common/Configure/ConfigureService.cs`
|
||||
- **تاریخ:** ۱۴۰۵/۰۳/۱۷ (بروز: خرداد ۱۴۰۵ — گزارشهای read-only + proto 0.0.201؛ **بروز ۱۴۰۵/۰۳/۱۸ — دفتر تراکنشهای مالی proto 0.0.203**)
|
||||
|
||||
---
|
||||
|
||||
## ۱. ورود و احراز هویت ادمین
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 1.1 | `OtpTokenContract.CreateNewOtpToken` | `OtpTokenContract` | `/Login/` (`LoginPage`) | ارسال OTP ورود ادمین |
|
||||
| 1.2 | `UserContract.VerifyOtpToken` | `UserContract` | `/VerifyCodePage/{Mobile}` | تأیید OTP و دریافت JWT |
|
||||
| 1.3 | `OtpTokenContract.CreateNewOtpToken` (resend) | `OtpTokenContract` | `/VerifyCodePage/{Mobile}` | ارسال مجدد OTP |
|
||||
| 1.4 | `AuthorizationService.HasPermissionAsync` | — (JWT Role/Claim) | `NavMenu`, صفحات محافظتشده | کنترل نمایش منو بر اساس نقش (فعلاً role-based، نه API) |
|
||||
| 1.5 | `AppTokenProvider` / `TokenHandler` | — (localStorage) | سراسری | attach کردن Bearer token به همه gRPC callها |
|
||||
|
||||
---
|
||||
|
||||
## ۲. داشبورد
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 2.1 | `UserContract.GetAllUserByFilter` | `UserContract` | `/` (Index) | شمارش کاربران |
|
||||
| 2.2 | `ProductsContract.GetAllProductsByFilter` | `ProductsContract` | `/` | شمارش محصولات |
|
||||
| 2.3 | `PackageContract.GetAllPackageByFilter` | `PackageContract` | `/` | شمارش پکیجها |
|
||||
| 2.4 | `CategoryContract.GetAllCategoryByFilter` | `CategoryContract` | `/` | شمارش دستهبندیها |
|
||||
| 2.5 | `CommissionContract.GetAvailableWeeks` | `CommissionContract` | `/` | هفته جاری کمیسیون |
|
||||
| 2.6 | `CommissionContract.GetWeeklyCommissionPool` | `CommissionContract` | `/` | وضعیت pool هفته جاری |
|
||||
| 2.7 | `ClubMembershipContract.GetAllClubMemberships` | `ClubMembershipContract` | `/` | آمار اعضای باشگاه |
|
||||
| 2.8 | `UserOrderContract.GetAllUserOrderByFilter` | `UserOrderContract` | `/` | سفارشهای اخیر |
|
||||
| 2.9 | `IDiscountOrderService.GetOrdersAsync` | `DiscountOrderContract` | `Dashboard/DiscountShopWidget` | ویجت سفارشهای فروشگاه تخفیفی |
|
||||
| 2.10 | `CommissionContract.GetWithdrawalRequests` | `CommissionContract` | `NavMenu` | badge درخواستهای برداشت معلق |
|
||||
| 2.11 | `GlobalSearch` — `UserContract.GetAllUserByFilter` | `UserContract` | `MainLayout/GlobalSearch` | جستجوی سریع کاربر + ناوبری صفحات |
|
||||
|
||||
---
|
||||
|
||||
## ۳. مدیریت کاربران
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 3.1 | `UserContract.GetAllUserByFilter` | `UserContract` | `/UserPage/`, `UserAutoComplete`, GlobalSearch | لیست/جستجوی کاربران |
|
||||
| 3.2 | `UserContract.DeleteUser` | `UserContract` | `/UserPage/` | حذف کاربر |
|
||||
| 3.3 | Export users | `UserContract.GetAllUserByFilter` | `/UserPage/` | خروجی CSV کاربران |
|
||||
|
||||
---
|
||||
|
||||
## ۴. نقشها و دسترسیها
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 4.1 | `RoleContract.GetAllRoleByFilter` | `RoleContract` | `/RolePage/`, `RoleAutoComplete` | لیست نقشها |
|
||||
| 4.2 | `RoleContract.CreateNewRole` | `RoleContract` | `Role/Components/CreateDialog` | ایجاد نقش |
|
||||
| 4.3 | `RoleContract.UpdateRole` | `RoleContract` | `Role/Components/UpdateDialog` | ویرایش نقش |
|
||||
| 4.4 | `RoleContract.DeleteRole` | `RoleContract` | `/RolePage/` | حذف نقش |
|
||||
| 4.5 | `RolePermissionsDialog` | — (UI فقط) | `/RolePage/` | ویرایش permissionها — **هنوز به API وصل نشده** |
|
||||
|
||||
---
|
||||
|
||||
## ۵. نقش کاربر و آدرس
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 5.1 | `UserRoleContract.GetAllUserRoleByFilter` | `UserRoleContract` | `/UserRolePage/{UserId}` | نقشهای یک کاربر |
|
||||
| 5.2 | `UserRoleContract.CreateNewUserRole` | `UserRoleContract` | CreateDialog, `UserRoleDialog` | تخصیص نقش |
|
||||
| 5.3 | `UserRoleContract.UpdateUserRole` | `UserRoleContract` | UpdateDialog | ویرایش نقش کاربر |
|
||||
| 5.4 | `UserRoleContract.DeleteUserRole` | `UserRoleContract` | `/UserRolePage/{UserId}` | حذف نقش کاربر |
|
||||
| 5.5 | `RoleContract.GetAllRoleByFilter` | `RoleContract` | `UserRoleDialog`, `RoleTitleColumn` | نمایش عنوان نقش |
|
||||
| 5.6 | `UserAddressContract.GetAllUserAddressByFilter` | `UserAddressContract` | `/UserAddressPage/{UserId}` | آدرسهای کاربر |
|
||||
| 5.7 | `UserAddressContract.CreateNewUserAddress` | `UserAddressContract` | CreateDialog | ثبت آدرس |
|
||||
| 5.8 | `UserAddressContract.UpdateUserAddress` | `UserAddressContract` | UpdateDialog | ویرایش آدرس |
|
||||
| 5.9 | `UserAddressContract.DeleteUserAddress` | `UserAddressContract` | `/UserAddressPage/{UserId}` | حذف آدرس |
|
||||
|
||||
---
|
||||
|
||||
## ۶. پکیجها
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 6.1 | `PackageContract.GetAllPackageByFilter` | `PackageContract` | `/PackagePage/`, `PackageSelect`, ManualPaymentDialog | لیست پکیجها |
|
||||
| 6.2 | `PackageContract.CreateNewPackage` | `PackageContract` | `Package/Components/CreateDialog` | ایجاد پکیج |
|
||||
| 6.3 | `PackageContract.UpdatePackage` | `PackageContract` | `Package/Components/UpdateDialog` | ویرایش پکیج |
|
||||
| 6.4 | `PackageContract.DeletePackage` | `PackageContract` | `/PackagePage/` | حذف پکیج |
|
||||
| 6.5 | `ConfigurationContract.GetClubFeatures` | `ConfigurationContract` | Create/Update Dialog | انتخاب featureهای باشگاه مرتبط با پکیج |
|
||||
|
||||
---
|
||||
|
||||
## ۷. فروشگاه اصلی — محصولات و دستهبندی
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 7.1 | `ProductsContract.GetAllProductsByFilter` | `ProductsContract` | `/ProductsPage/`, BulkEdit, AutoComplete | لیست محصولات |
|
||||
| 7.2 | `ProductsContract.CreateNewProducts` | `ProductsContract` | `Products/Components/CreateDialog` | ایجاد محصول |
|
||||
| 7.3 | `ProductsContract.UpdateProducts` | `ProductsContract` | UpdateDialog | ویرایش محصول |
|
||||
| 7.4 | `ProductsContract.DeleteProducts` | `ProductsContract` | `/ProductsPage/` | حذف محصول |
|
||||
| 7.5 | `ProductsContract.ToggleProductStatus` | `ProductsContract` | `/ProductsPage/`, BulkEdit | فعال/غیرفعال |
|
||||
| 7.6 | `ProductsContract.BulkUpdateProductPrices` | `ProductsContract` | `/ProductsBulkEditPage/` | ویرایش گروهی قیمت |
|
||||
| 7.7 | `ProductsContract.GetProductGallery` / Add/Remove Image | `ProductsContract` | `GalleryDialog` | گالری تصاویر محصول |
|
||||
| 7.8 | `ProductsContract.GetProducts` / `UpdateProductCategories` | `ProductsContract` | `/ProductCategoriesPage/{ProductId}` | drag-drop دستهبندی محصول |
|
||||
| 7.9 | `CategoryContract.GetAllCategoryByFilter` | `CategoryContract` | `/CategoryPage/`, AutoComplete | لیست دستهها |
|
||||
| 7.10 | `CategoryContract.GetCategory` / Create / Update / Delete | `CategoryContract` | CategoryMainPage, Dialogs | CRUD دستهبندی |
|
||||
| 7.11 | `ProductsContract.GetProductsForCategory` / `UpdateCategoryProducts` | `ProductsContract` | `/CategoryProductsPage/{CategoryId}` | drag-drop محصولات یک دسته |
|
||||
|
||||
---
|
||||
|
||||
## ۸. انبارداری (Inventory)
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 8.1 | `InventoryContract.GetAllWarehouses` | `InventoryContract` | `/InventoryPage/`, Warehouses, LowStock | لیست انبارها |
|
||||
| 8.2 | `InventoryContract.GetAllInventoryItems` | `InventoryContract` | `/InventoryPage/` | موجودی کالا |
|
||||
| 8.3 | `InventoryContract.GetLowStockItems` | `InventoryContract` | `/InventoryPage/`, `/InventoryLowStockPage/` | اقلام کمموجود |
|
||||
| 8.4 | `InventoryContract.AddStock` | `InventoryContract` | `AddStockDialog` | ورود کالا به انبار |
|
||||
| 8.5 | `InventoryContract.AdjustStock` | `InventoryContract` | `AdjustStockDialog` | تعدیل موجودی |
|
||||
| 8.6 | `InventoryContract.RecordLoss` | `InventoryContract` | `RecordLossDialog` | ثبت ضایعات |
|
||||
| 8.7 | `InventoryContract.CreateWarehouse` | `InventoryContract` | `CreateWarehouseDialog` | ایجاد انبار |
|
||||
| 8.8 | `InventoryContract.UpdateInventorySettings` | `InventoryContract` | `InventorySettingsDialog` | تنظیمات انبار |
|
||||
| 8.9 | `InventoryContract.GetStockMovements` | `InventoryContract` | `/InventoryMovementsPage/` | تاریخچه جابجایی موجودی |
|
||||
|
||||
---
|
||||
|
||||
## ۹. سفارشهای فروشگاه اصلی
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 9.1 | `UserOrderContract.GetAllUserOrderByFilter` | `UserOrderContract` | `/OrdersPage/`, `/UserOrderPage/{UserId}`, SalesReports | لیست سفارشها |
|
||||
| 9.2 | `UserOrderContract.GetUserOrder` | `UserOrderContract` | OrderDetailsDialog, Index | جزئیات سفارش |
|
||||
| 9.3 | `UserOrderContract.UpdateUserOrder` | `UserOrderContract` | UserOrderDetailsDialog, ChangeOrderStatus | ویرایش سفارش / tracking |
|
||||
| 9.4 | `UserOrderContract.UpdateOrderStatus` | `UserOrderContract` | ChangeOrderStatusDialog | تغییر وضعیت تحویل |
|
||||
| 9.5 | `UserOrderContract.CancelOrder` | `UserOrderContract` | CancelOrderDialog | لغو سفارش |
|
||||
| 9.6 | `UserOrderContract.DeleteUserOrder` | `UserOrderContract` | `/UserOrderPage/{UserId}` | حذف سفارش |
|
||||
| 9.7 | `UserOrderContract.ApplyDiscountToOrder` | `UserOrderContract` | ApplyDiscountDialog | اعمال تخفیف روی سفارش |
|
||||
|
||||
---
|
||||
|
||||
## ۱۰. پرداخت دستی و تراکنشها
|
||||
|
||||
> **دو گزارش تراکنش — اشتباه نگیرید:**
|
||||
>
|
||||
> | مسیر | جدول DB | کاربرد |
|
||||
> |------|---------|--------|
|
||||
> | `/payment/transactions` | `PaymentTransactions` | ممیزی درگاه (Authority، ZarinPal، کارت) |
|
||||
> | `/payment/ledger-transactions` | `Transactions` | دفتر مالی داخلی (نوع تراکنش، وضعیت، RefId) |
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 10.1 | `ManualPaymentContract.GetAllManualPayments` | `ManualPaymentContract` | `/payment/manual-payments` | لیست پرداختهای دستی |
|
||||
| 10.2 | `ManualPaymentContract.CreateManualPayment` | `ManualPaymentContract` | `ManualPaymentDialog` | ثبت پرداخت دستی (مثلاً Daya) |
|
||||
| 10.3 | `ManualPaymentContract.ApproveManualPayment` | `ManualPaymentContract` | ManualPayments, Dialog | تأیید پرداخت |
|
||||
| 10.4 | `ManualPaymentContract.RejectManualPayment` | `ManualPaymentContract` | ManualPayments, Dialog | رد پرداخت |
|
||||
| 10.5 | `PaymentTransactionContract.GetAllPaymentTransactionByFilter` | `PaymentTransactionContract` | `/payment/transactions` | گزارش read-only تراکنش **درگاه** (`PaymentTransactions`) |
|
||||
| 10.5b | `PaymentTransactionContract.GetPaymentTransactionSummary` | `PaymentTransactionContract` | `/payment/transactions` | کارت خلاصه گزارش درگاه |
|
||||
| 10.6 | `TransactionsContract.GetAllTransactionsByFilter` | `TransactionsContract` | `/payment/ledger-transactions` | گزارش read-only **دفتر مالی** (`Transactions`) |
|
||||
| 10.7 | `TransactionsContract.GetTransactionSummary` | `TransactionsContract` | `/payment/ledger-transactions` | کارت خلاصه دفتر تراکنش |
|
||||
| 10.8 | `UserPackagePurchaseContract.GetAllUserPackagePurchaseByFilter` | `UserPackagePurchaseContract` | `/club/purchases` | گزارش خرید پکیج |
|
||||
| 10.9 | `ClubMembershipCycleContract.GetAllClubMembershipCycleByFilter` | `ClubMembershipCycleContract` | `/club/cycles` | گزارش دوره عضویت |
|
||||
| 10.10 | `ClubMembershipCycleHistoryContract.GetAllClubMembershipCycleHistoryByFilter` | `ClubMembershipCycleHistoryContract` | `/club/history` (زیرتب دوره) | تاریخچه تغییرات دوره |
|
||||
|
||||
---
|
||||
|
||||
## ۱۱. کیف پول و تاریخچه
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 11.1 | `UserWalletContract.GetAllUserWalletByFilter` | `UserWalletContract` | `/wallets` | لیست کیف پول کاربران |
|
||||
| 11.2 | `UserWalletHistoryContract.GetAllUserWalletHistoryByFilter` | `UserWalletHistoryContract` | `/wallets` (تب تاریخچه) | لاگ تغییرات کیف پول |
|
||||
| 11.3 | `UserWalletContract.AdminManualCreditCharge` | `UserWalletContract` | `/payment/manual-credit-charges` | شارژ دستی کیف پول اصلی (Balance) |
|
||||
| 11.4 | `UserWalletContract.GetManualCreditCharges` | `UserWalletContract` | `/payment/manual-credit-charges` | لیست شارژهای دستی (Type=17) |
|
||||
|
||||
---
|
||||
|
||||
## ۱۲. قراردادهای کاربران
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 12.1 | `UserContractContract.GetAllUserContractByFilter` | `UserContractContract` | `/contracts` | لیست قراردادهای امضاشده (اصلی / باشگاه) |
|
||||
| 12.2 | Download PDF | — (local JS) | `/contracts` | دانلود PDF قرارداد ذخیرهشده |
|
||||
|
||||
---
|
||||
|
||||
## ۱۳. کمیسیون و پاداش
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 13.1 | `CommissionContract.GetAvailableWeeks` | `CommissionContract` | `/commission/dashboard`, WeekNumberPicker | انتخاب هفته |
|
||||
| 13.2 | `CommissionContract.GetWeeklyCommissionPool` | `CommissionContract` | `/commission/dashboard` | جزئیات pool هفتگی |
|
||||
| 13.3 | `CommissionContract.TriggerWeeklyCalculation` | `CommissionContract` | Dashboard, WorkerControl | اجرای دستی محاسبه هفتگی |
|
||||
| 13.4 | `CommissionContract.GetAllWeeklyPools` | `CommissionContract` | `/commission/reports` | گزارش poolهای هفتگی |
|
||||
| 13.5 | `CommissionContract.GetUserCommissionPayouts` | `CommissionContract` | `/commission/payouts` | payoutهای کاربران |
|
||||
| 13.6 | `CommissionContract.ProcessWithdrawal` | `CommissionContract` | UserPayouts, WithdrawalRequests | پردازش/واریز برداشت |
|
||||
| 13.7 | `CommissionContract.BulkCreditPayouts` | `CommissionContract` | `/commission/payouts` | واریز گروهی payoutها |
|
||||
| 13.8 | `CommissionContract.GetWithdrawalRequests` | `CommissionContract` | `/commission/withdrawals`, NavMenu | درخواستهای برداشت کاربران |
|
||||
| 13.9 | `CommissionContract.ApproveWithdrawal` | `CommissionContract` | WithdrawalRequests | تأیید درخواست برداشت |
|
||||
| 13.10 | `CommissionContract.RejectWithdrawal` | `CommissionContract` | WithdrawalRequests | رد درخواست برداشت |
|
||||
| 13.11 | `CommissionContract.GetWithdrawalReports` | `CommissionContract` | `/commission/withdrawal-reports` | گزارش برداشتها |
|
||||
| 13.12 | `CommissionContract.GetUserWeeklyBalances` | `CommissionContract` | `/network/balances` | گزارش بالانس هفتگی کاربران |
|
||||
| 13.13 | `CommissionContract.GetWorkerStatus` | `CommissionContract` | `/system/worker-control` | وضعیت worker محاسبه |
|
||||
| 13.14 | `CommissionContract.GetWorkerExecutionLogs` | `CommissionContract` | `/system/worker-control` | لاگ اجرای worker |
|
||||
|
||||
---
|
||||
|
||||
## ۱۴. شبکه فروش (MLM)
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 14.1 | `NetworkMembershipContract.GetNetworkTree` | `NetworkMembershipContract` | `/network/tree` | درخت شبکه (admin view) |
|
||||
| 14.2 | `NetworkMembershipContract.GetUserNetwork` | `NetworkMembershipContract` | `/network/user-info/{UserId}` | اطلاعات شبکه یک کاربر |
|
||||
| 14.3 | `NetworkMembershipContract.GetNetworkMembershipHistory` | `NetworkMembershipContract` | UserNetworkInfo | تاریخچه عضویت شبکه |
|
||||
| 14.4 | `NetworkMembershipContract.ChangeNetworkParent` | `NetworkMembershipContract` | `ChangeParentDialog` | تغییر والد در درخت |
|
||||
| 14.5 | `NetworkMembershipContract.GetNetworkStatistics` | `NetworkMembershipContract` | `/network/statistics` | آمار کلی شبکه (admin) |
|
||||
|
||||
---
|
||||
|
||||
## ۱۵. باشگاه مشتریان
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 15.1 | `ClubMembershipContract.GetAllClubMemberships` | `ClubMembershipContract` | `/club/members`, Index | لیست اعضا |
|
||||
| 15.2 | `ClubMembershipContract.ActivateClubMembership` | `ClubMembershipContract` | `ActivateClubDialog` | فعالسازی دستی عضویت |
|
||||
| 15.3 | `ClubMembershipContract.DeactivateClubMembership` | `ClubMembershipContract` | `DeactivateClubDialog` | غیرفعالسازی |
|
||||
| 15.4 | `ClubMembershipContract.GetClubMembershipHistory` | `ClubMembershipContract` | `/club/history` (زیرتب عضویت) | گزارش تاریخچه عضویت |
|
||||
| 15.5 | `ClubMembershipContract.GetClubStatistics` | `ClubMembershipContract` | `/club/statistics` | آمار باشگاه |
|
||||
| 15.6 | `ConfigurationContract.GetClubFeatures` | `ConfigurationContract` | `/club/features` | لیست featureهای باشگاه |
|
||||
| 15.7 | `ClubMembershipContract.GetUserClubFeatures` | `ClubMembershipContract` | `/club/features` | featureهای یک کاربر |
|
||||
| 15.8 | `ClubMembershipContract.ToggleUserClubFeature` | `ClubMembershipContract` | `/club/features` | فعال/غیرفعال feature برای کاربر |
|
||||
|
||||
---
|
||||
|
||||
## ۱۶. فروشگاه تخفیفی (Discount Shop)
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 16.1 | `IDiscountProductService` — CRUD | `DiscountProductContract` | `/discount-products` | مدیریت محصولات تخفیفی |
|
||||
| 16.2 | `IDiscountCategoryService` — CRUD | `DiscountCategoryContract` | `/discount-categories` | مدیریت دستهبندی تخفیفی |
|
||||
| 16.3 | `IDiscountOrderService.GetOrdersAsync` | `DiscountOrderContract.GetAllDiscountOrders` | `/discount-orders`, Dashboard widget | لیست سفارشها (admin) |
|
||||
| 16.4 | `IDiscountOrderService.GetByIdAsync` | `DiscountOrderContract` | OrderDetailsDialog | جزئیات سفارش |
|
||||
| 16.5 | `IDiscountOrderService.UpdateStatusAsync` | `DiscountOrderContract` | Orders page, OrderDetailsDialog | تغییر وضعیت پرداخت/ارسال |
|
||||
| 16.6 | `IDiscountOrderService.GetSalesReportAsync` | `DiscountOrderContract` | `/discount-sales-reports` | گزارش فروش |
|
||||
| 16.7 | `DiscountProductContract.GetDiscountProducts` | `DiscountProductContract` | `DiscountProductsAutoComplete` | autocomplete محصول |
|
||||
|
||||
---
|
||||
|
||||
## ۱۷. تگ محصولات
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 17.1 | `ITagService.GetTagsAsync` | `TagContract` | `/tags` | لیست تگها |
|
||||
| 17.2 | `ITagService.CreateAsync` / `UpdateAsync` / `DeleteAsync` | `TagContract` | TagEditDialog, TagManagement | CRUD تگ |
|
||||
| 17.3 | `ITagService.GetProductTagsAsync` | `ProductTagContract` | AssignTagsDialog | تگهای یک محصول |
|
||||
| 17.4 | `ITagService.AssignToProductAsync` / `RemoveProductTagAsync` | `ProductTagContract` | AssignTagsDialog, Discount products | تخصیص/حذف تگ |
|
||||
|
||||
---
|
||||
|
||||
## ۱۸. پیامهای عمومی
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 18.1 | `IPublicMessageService.GetMessagesAsync` | `PublicMessageContract` | `/public-messages` | لیست پیامها |
|
||||
| 18.2 | `IPublicMessageService.GetByIdAsync` | `PublicMessageContract` | PublicMessages | جزئیات |
|
||||
| 18.3 | `IPublicMessageService.CreateAsync` / `UpdateAsync` | `PublicMessageContract` | MessageFormDialog | ایجاد/ویرایش |
|
||||
| 18.4 | `IPublicMessageService.PublishAsync` / `ArchiveAsync` | `PublicMessageContract` | PublicMessages | انتشار/بایگانی |
|
||||
| 18.5 | `IPublicMessageService.DeleteAsync` | `PublicMessageContract` | PublicMessages | حذف |
|
||||
|
||||
---
|
||||
|
||||
## ۱۹. بلاگ
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 19.1 | `IBlogPostService.GetAllAsync` | `BlogPostContract` | `/blog/posts` | لیست پستها |
|
||||
| 19.2 | `IBlogPostService.GetByIdAsync` | `BlogPostContract` | BlogPostEditDialog | جزئیات پست |
|
||||
| 19.3 | `IBlogPostService.CreateAsync` / `UpdateAsync` | `BlogPostContract` | BlogPostEditDialog | ایجاد/ویرایش |
|
||||
| 19.4 | `IBlogPostService.DeleteAsync` | `BlogPostContract` | BlogPostManagement | حذف |
|
||||
| 19.5 | `IBlogPostService.PublishAsync` / `ArchiveAsync` | `BlogPostContract` | BlogPostManagement | انتشار/بایگانی |
|
||||
| 19.6 | `IBlogCategoryService` — CRUD | `BlogCategoryContract` | `/blog/categories` | مدیریت دستهبندی بلاگ |
|
||||
|
||||
---
|
||||
|
||||
## ۲۰. مدیریت محتوای سایت
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 20.1 | `ISitePageSettingsService.GetAllAsync` | `SitePageSettingsContract` | `/content/page-settings` | لیست صفحات (landing, about, contact, licenses) |
|
||||
| 20.2 | `ISitePageSettingsService.GetByKeyAsync` | `SitePageSettingsContract` | PageSettings dialogs | جزئیات یک صفحه |
|
||||
| 20.3 | `ISitePageSettingsService.SaveSettingsAsync` | `SitePageSettingsContract` | PageSettingsEditDialog | ذخیره hero + JSON تنظیمات |
|
||||
| 20.4 | `ISitePageSettingsService.SaveImageAsync` / `DeleteImageAsync` | `SitePageSettingsContract` | PageImagesDialog | مدیریت تصاویر گروهبندیشده |
|
||||
| 20.5 | `ISitePageService` — CRUD sections | `SitePageContract` | `/content/pages` | صفحات داینامیک قدیمی (section-based) |
|
||||
|
||||
---
|
||||
|
||||
## ۲۱. تنظیمات سیستم و سلامت
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 21.1 | `ConfigurationContract.GetAllConfigurations` | `ConfigurationContract` | `/system/configuration` | خواندن key-value تنظیمات (کمیسیون، شبکه، باشگاه، VAT…) |
|
||||
| 21.2 | `ConfigurationContract.CreateOrUpdateConfiguration` | `ConfigurationContract` | `/system/configuration` | ذخیره تنظیمات |
|
||||
| 21.3 | `HealthContract.GetSystemHealth` | `HealthContract` | `/system/health` | وضعیت سلامت سرویسها |
|
||||
| 21.4 | `AlertsMonitoring` | — (mock) | `/system/alerts` | **TODO — API هشدار هنوز وصل نشده** |
|
||||
|
||||
---
|
||||
|
||||
## ۲۲. نسخه اپلیکیشن
|
||||
|
||||
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||
|---|-------------|---------------|-----------------|--------|
|
||||
| 22.1 | `IAppVersionService.GetAllAsync` | `AppVersionContract` | `/settings/app-versions` | لیست نسخه FrontOffice/App |
|
||||
| 22.2 | `IAppVersionService.GetByNameAsync` | `AppVersionContract` | AppVersions | جزئیات یک app |
|
||||
| 22.3 | `IAppVersionService.UpdateAsync` | `AppVersionContract.UpdateAppVersion` | AppVersions dialog | بهروزرسانی نسخه + force update |
|
||||
|
||||
---
|
||||
|
||||
## ۲۳. کامپوننتهای مشترک (AutoComplete / Hub)
|
||||
|
||||
| کامپوننت | Contract | کاربرد |
|
||||
|----------|----------|--------|
|
||||
| `UserAutoComplete` | `UserContract` | جستجوی کاربر در فرمها |
|
||||
| `ProductsAutoComplete` | `ProductsContract` | جستجوی محصول |
|
||||
| `CategoryAutoComplete` / MultiSelect | `CategoryContract` | انتخاب دسته |
|
||||
| `PackageSelect` | `PackageContract` | انتخاب پکیج |
|
||||
| `WeekNumberPicker` | `CommissionContract` | انتخاب هفته |
|
||||
| `DiscountProductsAutoComplete` | `DiscountProductContract` | جستجوی محصول تخفیفی |
|
||||
| Hub pages (`ClubHub`, `DiscountShopHub`, `SystemHub`, `OrdersHub`, `BlogHub`) | — | مسیریابی tab-based؛ RPC در child component |
|
||||
|
||||
---
|
||||
|
||||
## ۲۴. سرویسهای ثبتشده ولی بدون استفاده / ناقص
|
||||
|
||||
| سرویس / صفحه | وضعیت |
|
||||
|--------------|--------|
|
||||
| `IBlogPostImageService` (`BlogPostImageContract`) | ثبت در DI؛ **هیچ صفحهای inject نمیکند** |
|
||||
| `RolePermissionsDialog` | UI permission؛ **ذخیره در backend TODO** |
|
||||
| `AuthorizationService` | فقط JWT role؛ **Permission API از CMS/BFF TODO** |
|
||||
| `/system/alerts` | داده mock؛ **AlertContract وجود ندارد** |
|
||||
|
||||
---
|
||||
|
||||
## نمودار جریان ورود ادمین
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[LoginPage] -->|CreateNewOtpToken| B[SMS OTP]
|
||||
B --> C[VerifyCodePage]
|
||||
C -->|VerifyOtpToken| D[JWT in localStorage]
|
||||
D --> E[All gRPC via TokenHandler]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## فهرست gRPC Contractهای فعال BackOffice
|
||||
|
||||
| Contract | Wrapper / استفاده مستقیم |
|
||||
|----------|---------------------------|
|
||||
| `OtpTokenContract` | Login |
|
||||
| `UserContract` | Login, Users, Dashboard, Search |
|
||||
| `RoleContract` | Roles, UserRoles |
|
||||
| `UserRoleContract` | UserRole pages |
|
||||
| `UserAddressContract` | UserAddress pages |
|
||||
| `PackageContract` | Packages, ManualPayment |
|
||||
| `ProductsContract` | Products, Categories drag-drop |
|
||||
| `CategoryContract` | Categories |
|
||||
| `UserOrderContract` | Orders |
|
||||
| `ManualPaymentContract` | Manual payments |
|
||||
| `PaymentTransactionContract` | Gateway transaction report (`/payment/transactions`) |
|
||||
| `TransactionsContract` | Ledger transaction report (`/payment/ledger-transactions`) |
|
||||
| `UserPackagePurchaseContract` | Club package purchase report |
|
||||
| `ClubMembershipCycleContract` | Club cycle report |
|
||||
| `ClubMembershipCycleHistoryContract` | Club cycle history report |
|
||||
| `UserWalletContract` | Wallet management |
|
||||
| `UserWalletHistoryContract` | Wallet change log |
|
||||
| `UserContractContract` | Signed contracts |
|
||||
| `CommissionContract` | Commission, Network balances, Worker |
|
||||
| `NetworkMembershipContract` | Network tree/info |
|
||||
| `ClubMembershipContract` | Club members/features |
|
||||
| `ConfigurationContract` | System config, Club features, Package |
|
||||
| `DiscountProductContract` | DiscountProductService |
|
||||
| `DiscountCategoryContract` | DiscountCategoryService |
|
||||
| `DiscountOrderContract` | DiscountOrderService |
|
||||
| `TagContract` / `ProductTagContract` | TagService |
|
||||
| `PublicMessageContract` | PublicMessageService |
|
||||
| `InventoryContract` | Inventory pages |
|
||||
| `BlogPostContract` | BlogPostService |
|
||||
| `BlogCategoryContract` | BlogCategoryService |
|
||||
| `BlogPostImageContract` | BlogPostImageService *(unused)* |
|
||||
| `SitePageSettingsContract` | SitePageSettingsService |
|
||||
| `SitePageContract` | SitePageService |
|
||||
| `AppVersionContract` | AppVersionService |
|
||||
| `HealthContract` | Health dashboard |
|
||||
|
||||
---
|
||||
|
||||
## مقایسه با FrontOffice
|
||||
|
||||
| موضوع | FrontOffice (مشتری) | BackOffice (ادمین) |
|
||||
|-------|----------------------|-------------------|
|
||||
| Auth | OTP + JWT (UserContract) | OTP + JWT (همان flow) |
|
||||
| Orders | Customer RPC | Admin filter + status management |
|
||||
| Discount Shop | User orders + checkout | Full CRUD + admin order management |
|
||||
| Commission | Read-only (my payouts) | Full admin (calculation, approval, reports) |
|
||||
| Network | My tree only | Full tree + change parent |
|
||||
| Content | Read page settings | Edit page settings + blog + public messages |
|
||||
| Wallet | My balances | All users wallet + history log |
|
||||
|
||||
مرجع مشابه برای فرانت مشتری: [`FrontOffice/docs/CUSTOMER-SERVICES.md`](../FrontOffice/docs/CUSTOMER-SERVICES.md)
|
||||
|
||||
---
|
||||
|
||||
## یادداشت deploy (خرداد ۱۴۰۵)
|
||||
|
||||
| گام | جزئیات |
|
||||
|-----|--------|
|
||||
| Proto | `Foursat.CMSMicroservice.Protobuf` **0.0.203** (گزارش دفتر تراکنش) |
|
||||
| CMS | deploy WebApi (شامل `GetTransactionSummary` + فیلتر تاریخ) |
|
||||
| BackOffice | NuGet 0.0.203 یا ProjectReference در monorepo |
|
||||
| FrontOffice | **redeploy لازم نیست** برای گزارش ledger؛ برای شارژ کیف اصلی proto **0.0.202** لازم است |
|
||||
|
||||
گزارشهای read-only (بخش ۱۰ و ۱۵): فقط فیلتر + grid + summary + CSV — بدون CRUD یا detail dialog.
|
||||
|
||||
مستندات فنی کامل: [`totalDoc/technical/TECH-08-SESSION-LOG-202606.md`](../../totalDoc/technical/TECH-08-SESSION-LOG-202606.md) — §10 Ledger transactions
|
||||
+2
-1008
File diff suppressed because it is too large
Load Diff
@@ -1,792 +1,3 @@
|
||||
# 📝 BackOffice — Changelog
|
||||
# Docs moved to totalDoc
|
||||
|
||||
**آخرین بروزرسانی:** اسفند ۱۴۰۴ (February 17, 2026)
|
||||
|
||||
---
|
||||
|
||||
## 🏷 فاز ۱۵ — Production Deploy + فیکس CI/CD Cross-Deploy (اسفند ۱۴۰۴)
|
||||
|
||||
> **هدف:** دیپلوی به سرور Production و رفع باگ cross-deploy
|
||||
|
||||
### ۱۵.۱ — Merge به برنچ production ✅
|
||||
|
||||
- **کامیت:** `4454b8d` — merge `kub-stage` → `production`
|
||||
- **ریموت:** `kub-stage` (Gitea)
|
||||
|
||||
### ۱۵.۲ — فیکس باگ Cross-Deploy ✅
|
||||
|
||||
- **مشکل:** Gitea Act Runner همه workflowهای `.gitea/workflows/` را اجرا میکرد — بدون توجه به `branches:` filter
|
||||
- **عارضه:** push به `production` باعث دیپلوی همزمان به staging و production میشد
|
||||
- **رفع:** حذف فایلهای workflow staging از برنچ production
|
||||
- **فایل حذف شده:** `.gitea/workflows/deploy.yml` (staging) از برنچ production
|
||||
|
||||
### ۱۵.۳ — بروزرسانی Proto به v0.0.179 ✅
|
||||
|
||||
- **تغییر:** `Foursat.CMSMicroservice.Protobuf` → `0.0.179`
|
||||
- **فایل:** `BackOffice.csproj`
|
||||
|
||||
### ۱۵.۴ — فیکس K8S_SERVER در prod-deploy.yml ✅
|
||||
|
||||
- **مشکل:** `K8S_SERVER` هنوز `194.5.195.53` (staging) بود
|
||||
- **رفع:** تغییر به `45.149.79.127` (production)
|
||||
- **فیکس اضافی:** `kubectl rollout restart` → `kubectl set image` (تا ایمیج SHA-tagged واقعاً set بشه)
|
||||
|
||||
### ۱۵.۵ — ساخت appsettings.Production.json ✅
|
||||
|
||||
- **مشکل:** فایل `appsettings.Production.json` وجود نداشت → `GwUrl` fallback به `localhost`
|
||||
- **رفع:** ساخت `wwwroot/appsettings.Production.json` با `GwUrl=https://cms.kbs1.ir`
|
||||
|
||||
### ۱۵.۶ — فیکس nginx image path در Dockerfile (production branch) ✅
|
||||
|
||||
- **مشکل:** Dockerfile روی برنچ production از `library/nginx:alpine` استفاده میکرد — وجود نداشت
|
||||
- **ارور:** `manifest for 194.5.195.53:32082/library/nginx:alpine not found`
|
||||
- **رفع:** تغییر به `194.5.195.53:32082/nginx:alpine`
|
||||
- **کامیت:** `743403e` (مستقیم روی برنچ production)
|
||||
|
||||
---
|
||||
|
||||
## 🏷 فاز ۱۴ — فیکس Docker Build + Deployment (بهمن ۱۴۰۴)
|
||||
|
||||
> **هدف:** رفع مشکلات Docker build و CI/CD pipeline
|
||||
|
||||
### ۱۴.۱ — تغییر به NuGet Package ✅
|
||||
|
||||
- **مشکل:** `ProjectReference` به `../../../CMS/src/CMSMicroservice.Protobuf/` خارج از Docker build context بود
|
||||
- **رفع:** تغییر به `PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.178"`
|
||||
- **فایل:** `BackOffice.csproj`
|
||||
|
||||
### ۱۴.۲ — حذف COPY libs/ از Dockerfile ✅
|
||||
|
||||
- **مشکل:** `COPY ["libs/", "libs/"]` فیل میشد — `libs/` خارج از build context
|
||||
- **رفع:** حذف خط — دیگه نیازی به DLLهای BFF نیست
|
||||
- **فایل:** `Dockerfile`
|
||||
|
||||
### ۱۴.۳ — تغییر nginx:alpine به رجیستری لوکال ✅
|
||||
|
||||
- **مشکل:** `FROM nginx:alpine` از Docker Hub → TLS handshake timeout
|
||||
- **رفع:** `FROM 194.5.195.53:32082/nginx:alpine`
|
||||
- **فایل:** `Dockerfile`
|
||||
|
||||
### ۱۴.۴ — اضافه شدن SERVER_PASSWORD secret ✅
|
||||
|
||||
- **مشکل:** مرحله Deploy to Kubernetes با `Permission denied` فیل میشد
|
||||
- **رفع:** سکرت `SERVER_PASSWORD` به ریپو Gitea اضافه شد (BackOffice + FrontOffice)
|
||||
|
||||
---
|
||||
|
||||
## 🏷 فاز ۱۳ — بهبودهای UI بکآفیس (بهمن ۱۴۰۴)
|
||||
|
||||
> **هدف:** رفع مشکلات بصری، بهبود UX فرمها و ناوبری
|
||||
|
||||
### ۱۳.۱ — فیکس کامپوننت Image (پیشوند `/uploads/`) ✅
|
||||
|
||||
- **مشکل:** تصاویر آپلودشده (بلاگ، صفحات سایت، سکشنها) در بکآفیس نمایش داده نمیشدند — مسیر `GwUrl + Src` با مسیر `UploadsController` هماهنگ نبود
|
||||
- **رفع:** اضافه شدن `/uploads/` بین `FileBaseUrl` و مسیر نسبی تصویر
|
||||
- **فایل:** `BackOffice/Common/BaseComponents/Image.razor.cs`
|
||||
|
||||
### ۱۳.۲ — فیکس فیلتر هفته در گزارشهای هفتگی ✅
|
||||
|
||||
- **مشکل:** فیلتر هفته از `MudTextField` (ورودی متنی) استفاده میکرد — کاربر باید عدد حدس میزد
|
||||
- **رفع:** جایگزینی با `WeekNumberPicker` — انتخاب هفته از لیست با بایندینگ `WeekDefinitionId`
|
||||
- **فایلها:**
|
||||
- `CMS/src/CMSMicroservice.Protobuf/Protos/commission.proto` — تغییر فیلدها از `StringValue` به `Int64Value`
|
||||
- `CMS/.../CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQuery.cs` + `Handler.cs`
|
||||
- `BackOffice/Pages/Commission/WeeklyReports.razor`
|
||||
|
||||
### ۱۳.۳ — فیکس دانلود PDF قراردادها ✅
|
||||
|
||||
- **مشکل:** فیلد `SignedPdfFile` محتوای HTML کامل دارد (نه URL فایل PDF) — دکمه دانلود کار نمیکرد
|
||||
- **رفع:** تابع JS جدید `jsHtmlToPdf()` برای باز کردن HTML در پنجره جدید و چاپ/ذخیره بهعنوان PDF
|
||||
- **فایلها:**
|
||||
- `BackOffice/wwwroot/js/main.js` — تابع `jsHtmlToPdf`
|
||||
- `BackOffice/Pages/Contract/UserContractPage.razor`
|
||||
- `BackOffice/Pages/Contract/ContractDetailsDialog.razor`
|
||||
|
||||
### ۱۳.۴ — یکسانسازی ارتفاع ویرایشگر HTML ✅
|
||||
|
||||
- **مشکل:** ارتفاع `MudHtmlEditor` در صفحات مختلف متفاوت بود — برخی خیلی کوتاه
|
||||
- **رفع:** اضافه شدن `MinHeight="300px"` به تمام ۷ ویرایشگر HTML
|
||||
- **فایلها:** `BlogPostEditDialog`, `SitePageSectionEditDialog`, `DiscountShop/ProductFormDialog`, `Package/CreateDialog`, `Package/UpdateDialog`, `Products/CreateDialog`, `Products/UpdateDialog`
|
||||
|
||||
### ۱۳.۵ — تولید خودکار اسلاگ بلاگ ✅
|
||||
|
||||
- **مشکل:** کاربر باید اسلاگ URL را دستی وارد میکرد — غیرضروری و مستعد خطا
|
||||
- **رفع:** حذف فیلد اسلاگ از فرم، تولید خودکار `post-{GUID}` در بکاند (حداکثر ۲۰ کاراکتر)
|
||||
- **فایلها:**
|
||||
- `CMS/.../BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandHandler.cs`
|
||||
- `CMS/.../BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandHandler.cs`
|
||||
- `BackOffice/Pages/Blog/Components/BlogPostEditDialog.razor`
|
||||
|
||||
### ۱۳.۶ — انتقال تگها به منوی جداگانه ✅
|
||||
|
||||
- **مشکل:** تگها فقط از تب سوم بلاگ قابل دسترسی بودند — ولی تگها بین محصولات و بلاگ مشترکند
|
||||
- **رفع:** اضافه شدن مسیر `/tags` به `TagManagementPage`، لینک جدید «مدیریت تگها» در منوی ناوبری، حذف تب تگها از `BlogHub`
|
||||
- **فایلها:**
|
||||
- `BackOffice/Pages/Tag/TagManagementPage.razor`
|
||||
- `BackOffice/Shared/NavMenu.razor`
|
||||
- `BackOffice/Pages/Blog/BlogHub.razor`
|
||||
|
||||
### ۱۳.۷ — تولید خودکار کلید سکشن ✅
|
||||
|
||||
- **مشکل:** کاربر باید کلید سکشن صفحه سایت را دستی وارد میکرد (مثلاً `about-us-section`)
|
||||
- **رفع:** فیلد اختیاری شد، تولید خودکار `section-{GUID}` (حداکثر ۲۰ کاراکتر) در هندلر CMS
|
||||
- **فایلها:**
|
||||
- `CMS/.../SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandHandler.cs`
|
||||
- `CMS/.../SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandValidator.cs`
|
||||
- `BackOffice/Pages/Content/Components/SitePageSectionEditDialog.razor`
|
||||
|
||||
---
|
||||
|
||||
## 🏷 فاز ۱۲ — فیکس فلوی ثبتنام / ورود FrontOffice (بهمن ۱۴۰۴)
|
||||
|
||||
> **هدف:** رفع ۳ باگ بحرانی در فلوی ثبتنام و ورود FrontOffice
|
||||
> **مستند کامل:** [`totalDoc/cms/REGISTRATION-FLOW-FIXES.md`](../../totalDoc/cms/REGISTRATION-FLOW-FIXES.md)
|
||||
|
||||
### ۱۲.۱ — رفع ایجاد کاربر جدید در VerifyOtpToken 🔴 ✅
|
||||
|
||||
- **مشکل:** `UserCQ/VerifyOtpTokenCommandHandler` کاربران جدید ایجاد نمیکرد — فقط خطای «کاربر یافت نشد»
|
||||
- **رفع:** اضافه شدن لاجیک کامل: اعتبارسنجی کد معرف، بررسی ظرفیت درخت باینری (حداکثر ۲ فرزند)، تعیین شاخه (چپ/راست)، ایجاد User + UserRole + UserWallet، رویدادهای دامنه
|
||||
- **فایل:** `CMS/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs`
|
||||
|
||||
### ۱۲.۲ — رفع جستجوی موبایل در AcceptContract 🔴 ✅
|
||||
|
||||
- **مشکل:** `_currentUserService.Username` = `"{FirstName} {LastName}"` — نه شماره موبایل! در نتیجه OTP و کاربر هیچوقت پیدا نمیشد
|
||||
- **رفع:** ابتدا کاربر از `UserId` پیدا شود، سپس `user.Mobile` برای جستجوی OTP استفاده شود
|
||||
- **فایل:** `CMS/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs`
|
||||
|
||||
### ۱۲.۳ — رفع `IsCompleteRegister()` در FrontOffice 🟡 ✅
|
||||
|
||||
- **مشکل:** `InitUserAuthInfo().GetAwaiter()` بدون `.GetResult()` — عملیات async اجرا نمیشود، منوی کناری وضعیت نادرست
|
||||
- **رفع:** تغییر به `InitUserAuthInfo().GetAwaiter().GetResult()`
|
||||
- **فایل:** `FrontOffice/src/FrontOffice.Main/Utilities/AuthService.cs`
|
||||
|
||||
---
|
||||
|
||||
## 🏷 فاز ۱۱ — رفع خطای صفحات + سرویس عمومی تصاویر (بهمن ۱۴۰۴)
|
||||
|
||||
> **هدف:** رفع خطای ۳ صفحه BackOffice + ایجاد اندپوینت HTTP عمومی برای سرو تصاویر
|
||||
|
||||
### ۱۱.۱ — سرویس عمومی سرو تصاویر (CMS) ✅
|
||||
|
||||
- **`UploadsController`** — اندپوینت `GET /uploads/{path}` برای سرو مستقیم فایلها
|
||||
- `[AllowAnonymous]` — بدون نیاز به احراز هویت
|
||||
- کش مرورگر ۲۴ ساعته (`ResponseCache`)
|
||||
- پشتیبانی از Range Requests
|
||||
- **FMS Fallback:** اگر فایل محلی نباشد، از `dl.afrino.co` دانلود و کش میشود
|
||||
- `IHttpClientFactory` با named client `"FMS"` (timeout: 30s)
|
||||
- **فایلها:** `CMS/WebApi/Controllers/UploadsController.cs`, `CMS/WebApi/Program.cs`
|
||||
|
||||
### ۱۱.۲ — رفع صفحه «صفحات سایت» ✅
|
||||
|
||||
- اضافه شدن **دکمه «افزودن صفحه جدید»** به بالای صفحه
|
||||
- اضافه شدن **دکمه «حذف»** (آیکون قرمز) برای هر ردیف
|
||||
- ایجاد **`SitePageCreateDialog`** — دیالوگ ایجاد صفحه جدید با فیلدها:
|
||||
- کلید صفحه (PageKey)، عنوان، توضیحات متا، عنوان هیرو، زیرعنوان هیرو، تصویر هیرو، وضعیت
|
||||
- Proto: اضافه شدن `CreateSitePage` و `DeleteSitePage` RPC
|
||||
- CQRS: `CreateSitePageCommand/Handler` و `DeleteSitePageCommand/Handler`
|
||||
- **فایلها:**
|
||||
- `sitepage.proto` — 2 RPC جدید + پیامهای Request/Response
|
||||
- `SitePageCQ/Commands/CreateSitePage/` — Command + Handler
|
||||
- `SitePageCQ/Commands/DeleteSitePage/` — Command + Handler
|
||||
- `CMS/WebApi/Services/SitePageService.cs` — 2 override جدید
|
||||
- `BackOffice/Services/Content/ISitePageService.cs` — `CreateAsync` + `DeleteAsync` + `SitePageCreateDto`
|
||||
- `BackOffice/Services/Content/SitePageService.cs` — پیادهسازی Create/Delete
|
||||
- `BackOffice/Pages/Content/SitePageManagementPage.razor` — دکمههای Add/Delete
|
||||
- `BackOffice/Pages/Content/SitePageManagementPage.razor.cs` — متدهای CreatePage/DeletePage
|
||||
- `BackOffice/Pages/Content/Components/SitePageCreateDialog.razor` + `.razor.cs`
|
||||
|
||||
### ۱۱.۳ — رفع صفحه «قراردادها» (Contracts) ✅
|
||||
|
||||
- **مشکل:** صفحه `/contracts` اصلاً بارگذاری نمیشد — crash در DI
|
||||
- **علت:** `UserContractContract.UserContractContractClient` در DI ثبت نشده بود
|
||||
- **رفع:** اضافه شدن `using CMSMicroservice.Protobuf.Protos.UserContract` و ثبت gRPC client
|
||||
- **فایل:** `BackOffice/Common/Configure/ConfigureService.cs`
|
||||
|
||||
### ۱۱.۴ — رفع صفحه «کیف پول کاربران» (Wallets) ✅
|
||||
|
||||
- **مشکل:** صفحه `/wallets` اصلاً بارگذاری نمیشد — crash در DI
|
||||
- **علت:** دو سرویس ثبت نشده بود:
|
||||
- `UserWalletContract.UserWalletContractClient`
|
||||
- `UserWalletChangeLogContract.UserWalletChangeLogContractClient`
|
||||
- **رفع:** اضافه شدن usingها و ثبت هر دو gRPC client
|
||||
- **فایل:** `BackOffice/Common/Configure/ConfigureService.cs`
|
||||
|
||||
---
|
||||
|
||||
## 🏷 فاز ۱۰ — بهبود مدیریت محتوا: بلاگ + صفحات سایت (بهمن ۱۴۰۴)
|
||||
|
||||
> **هدف:** فعالسازی آپلود تصویر باینری و ادیتور HTML در صفحات بلاگ و صفحات سایت
|
||||
|
||||
### ۱۰.۱ — بهبود ادیتور پست بلاگ ✅
|
||||
|
||||
- **`MudTextField Lines="10"`** → **`MudHtmlEditor`** برای ویرایش محتوای HTML
|
||||
- **فیلد متنی مسیر تصویر** → **`MudFileUpload`** با پیشنمایش تصویر (`Image` component)
|
||||
- حذف فیلد `FeaturedImageThumbnailPath` (تولید خودکار توسط بکاند)
|
||||
- اضافه شدن `MudForm` با اعتبارسنجی + حالت بارگذاری (`_loading`)
|
||||
- مدیریت خطا: try-catch + Snackbar
|
||||
- **فایلها:** `Pages/Blog/Components/BlogPostEditDialog.razor` + `.razor.cs`
|
||||
|
||||
### ۱۰.۲ — بهبود ادیتور صفحه سایت ✅
|
||||
|
||||
- **فیلد متنی Hero Image** → **`MudFileUpload`** با پیشنمایش تصویر
|
||||
- اضافه شدن `MudForm` + حالت آپلود + مدیریت خطا
|
||||
- **فایلها:** `Pages/Content/Components/SitePageEditDialog.razor` + `.razor.cs`
|
||||
|
||||
### ۱۰.۳ — بهبود ادیتور بخش صفحه سایت (Section) ✅
|
||||
|
||||
- **`MudTextField Lines="8"`** → **`MudHtmlEditor`** برای ویرایش محتوای HTML بخشها
|
||||
- **فیلدهای متنی تصویر/thumbnail** → **`MudFileUpload`** با پیشنمایش
|
||||
- اضافه شدن `MudForm` + حالت آپلود + مدیریت خطا
|
||||
- **فایلها:** `Pages/Content/Components/SitePageSectionEditDialog.razor` + `.razor.cs`
|
||||
|
||||
### ۱۰.۴ — تغییرات Proto ✅
|
||||
|
||||
| Proto | تغییر |
|
||||
|-------|-------|
|
||||
| `blogpost.proto` | اضافه شدن `BlogImageFileModel` + فیلد `image_file` در Create/Update |
|
||||
| `sitepage.proto` | اضافه شدن `SitePageImageFileModel` + فیلد `image_file` در Update/CreateSection/UpdateSection |
|
||||
|
||||
### ۱۰.۵ — تغییرات CQRS (بکاند CMS) ✅
|
||||
|
||||
- **Commands:** اضافه شدن `ImageFileBytes`, `ImageMime`, `ImageFileName` به:
|
||||
- `CreateBlogPostCommand`, `UpdateBlogPostCommand`
|
||||
- `UpdateSitePageCommand`, `CreateSitePageSectionCommand`, `UpdateSitePageSectionCommand`
|
||||
- **Handlers:** تزریق `IFileManager` + فراخوانی `UploadImageAsync`:
|
||||
- BlogPost → `Images/BlogPosts/`
|
||||
- SitePage → `Images/SitePages/`
|
||||
- SitePageSection → `Images/SitePageSections/`
|
||||
|
||||
### ۱۰.۶ — تغییرات سرویسهای BackOffice ✅
|
||||
|
||||
- `BlogPostService.cs` — ارسال `BlogImageFileModel` با `ByteString.CopyFrom(dto.ImageFile)`
|
||||
- `SitePageService.cs` — ارسال `SitePageImageFileModel` در Update/CreateSection/UpdateSection
|
||||
- **DTOs:** اضافه شدن `ImageFile`, `ImageMime`, `ImageFileName` به `BlogPostEditDto`, `SitePageEditDto`, `SitePageSectionEditDto`
|
||||
|
||||
### خلاصه آمار فاز ۱۰
|
||||
| معیار | تعداد |
|
||||
|---|---|
|
||||
| فایلهای proto تغییریافته | 2 |
|
||||
| Commandهای بروزشده | 5 |
|
||||
| Handlerهای بروزشده | 5 |
|
||||
| سرویسهای gRPC تغییریافته | 2 (BlogPostService + SitePageService) |
|
||||
| دیالوگهای UI بازنویسیشده | 3 (BlogPost, SitePage, SitePageSection) |
|
||||
| سرویسهای BackOffice بروزشده | 2 |
|
||||
| DTOهای بروزشده | 3 |
|
||||
| Build | 0 Error ✅ (هر ۳ پروژه) |
|
||||
|
||||
---
|
||||
|
||||
## 🏷 فاز ۹ — معماری مدیریت فایل + رفع سفارشات + مهاجرت DB (بهمن ۱۴۰۴)
|
||||
|
||||
> **مستند کامل معماری فایل:** [`totalDoc/cms/FILE-MANAGEMENT-ARCHITECTURE.md`](../../totalDoc/cms/FILE-MANAGEMENT-ARCHITECTURE.md)
|
||||
|
||||
### ۹.۱ — حذف کد مرده FMS ✅
|
||||
- حذف ۳ فایل غیرقابل استفاده مرتبط با FMS خارجی (`https://dl.afrino.co`):
|
||||
|
||||
| فایل حذفشده | شرح |
|
||||
|-------------|------|
|
||||
| `Infrastructure/Services/FmsFileManager.cs` | پیادهسازی HTTP upload به FMS |
|
||||
| `Application/Common/FileManager/FileManagementService.cs` | سرویس قدیمی مدیریت فایل |
|
||||
| `Application/Common/FileManager/IFileManagementService.cs` | اینترفیس قدیمی |
|
||||
|
||||
### ۹.۲ — رفع صفحه سفارشات (Orders) ✅
|
||||
|
||||
**مشکل ۱ — صفحه خالی (بدون پیغام خطا)**
|
||||
- **علت:** `ServerReload` بدون try-catch — exceptionهای gRPC ساکت میشدند
|
||||
- **رفع:** اضافه شدن try-catch + نمایش خطا با Snackbar
|
||||
- **فایل:** `Pages/UserOrder/UserOrderMainPage.razor.cs`
|
||||
|
||||
**مشکل ۲ — Shadow FK تکراری در OrderVAT**
|
||||
- **علت:** `.WithOne()` بدون inverse navigation → EF Core یک shadow FK اضافی میساخت
|
||||
- **رفع:** `.WithOne()` → `.WithOne(x => x.OrderVAT)`
|
||||
- **فایل:** `Persistence/Configurations/OrderVATConfiguration.cs`
|
||||
|
||||
**مشکل ۳ — فیلترهای oneof همیشه فعال**
|
||||
- **علت:** فیلدهای proto `oneof` مقدار پیشفرض enum دارند (هیچوقت null نیستند)، پس `PaymentStatus != null` همیشه true بود
|
||||
- **رفع:** `PaymentStatus != null` → `HasPaymentStatus == true` (و مشابه برای DeliveryStatus و PaymentMethod)
|
||||
- **فایلها:** `UserOrderService.cs` — هر دو متد `GetAllUserOrderByFilter` و `GetCustomerOrders`
|
||||
|
||||
### ۹.۳ — مهاجرت DB: حذف محدودیت طول ستونهای تصویر ✅
|
||||
|
||||
- **علت:** data URIهای base64 بزرگتر از `nvarchar(500)` بودند → truncation خاموش
|
||||
- **رفع:** حذف `HasMaxLength(500)` از تمام ستونهای image path
|
||||
- **Migration:** `RemoveImagePathMaxLength`
|
||||
|
||||
| Entity | ستونها |
|
||||
|--------|---------|
|
||||
| Product | `ImagePath`, `ImageThumbnailPath` |
|
||||
| DiscountProduct | `ImagePath`, `ImageThumbnailPath` |
|
||||
| Category | `ImagePath` |
|
||||
| DiscountCategory | `ImagePath` |
|
||||
| BlogPost | `FeaturedImagePath`, `FeaturedImageThumbnailPath` |
|
||||
| SitePage | `HeroImagePath` |
|
||||
| SitePageSection | `ImagePath`, `ImageThumbnailPath` |
|
||||
|
||||
### ۹.۴ — بازنویسی کامل مدیریت فایل: ذخیره دیسکی ✅
|
||||
|
||||
**معماری قبلی:** ذخیره data URI مستقیم در DB (ناکارآمد + حجم بالا)
|
||||
|
||||
**معماری جدید:**
|
||||
1. فایلها در دیسک ذخیره میشوند: `Uploads/Images/{folder}/{guid}.jpg`
|
||||
2. مسیر نسبی در DB ذخیره میشود
|
||||
3. `ImagePathResolverInterceptor` هنگام serve تبدیل به base64 میکند
|
||||
|
||||
**فایلهای اصلی:**
|
||||
|
||||
| فایل | شرح |
|
||||
|------|------|
|
||||
| `Application/Common/FileManager/IFileManager.cs` | اینترفیس: Upload, UploadImage, Delete, ResolveImageUrl |
|
||||
| `Infrastructure/Services/LocalFileManager.cs` | پیادهسازی: ImageSharp, main 1200×1200 + thumb 300×300, JPEG Q75 |
|
||||
| `WebApi/Interceptors/ImagePathResolverInterceptor.cs` | gRPC Interceptor: walk بازگشتی response → تبدیل path به data URI |
|
||||
|
||||
**تنظیمات:**
|
||||
- `FileStorage:UploadPath` → پوشه ریشه آپلود (پیشفرض: `AppContext.BaseDirectory/Uploads`)
|
||||
- gRPC MaxReceiveMessageSize: 50MB
|
||||
|
||||
### ۹.۵ — پاکسازی LoggingBehaviour ✅
|
||||
|
||||
- **قبل:** فیلدهای باینری (byte[] تصاویر) کامل در لاگ چاپ میشدند → لاگهای چند مگابایتی
|
||||
- **بعد:**
|
||||
- فرمت: `JsonFormatter.Default.Format()` به جای `{@Request}`
|
||||
- Regex: حذف فیلدهای باینری (`File`, `ImageFile`, `image_file`, `file`)
|
||||
- محدودیت: حداکثر 2000 کاراکتر
|
||||
- **فایل:** `WebApi/Common/Behaviours/LoggingBehaviour.cs`
|
||||
|
||||
### خلاصه آمار فاز ۹
|
||||
| معیار | تعداد |
|
||||
|---|---|
|
||||
| فایلهای حذفشده (کد مرده) | 3 |
|
||||
| فایلهای جدید | 2 (LocalFileManager بازنویسی + ImagePathResolverInterceptor) |
|
||||
| فایلهای ویرایششده | ~12 |
|
||||
| باگهای رفعشده | 4 (Orders page ×3 + DB truncation) |
|
||||
| Migrationهای جدید | 1 (RemoveImagePathMaxLength) |
|
||||
| ستونهای DB تغییریافته | 9 (nvarchar(500) → nvarchar(max)) |
|
||||
| Build | 0 Error ✅ (هر ۳ پروژه) |
|
||||
|
||||
---
|
||||
|
||||
## 🏷 فاز ۸ — یکسانسازی فروشگاه عادی و تخفیفی (بهمن ۱۴۰۴)
|
||||
|
||||
> **مستند کامل:** [`totalDoc/ui-modernization/BACKOFFICE-STORE-UNIFICATION.md`](../../totalDoc/ui-modernization/BACKOFFICE-STORE-UNIFICATION.md)
|
||||
|
||||
### ۸.۱ — بازسازی NavMenu ✅
|
||||
- ایجاد دو NavGroup مجزا: «فروشگاه عادی» و «فروشگاه تخفیفی»
|
||||
- حذف «ویرایش دستهجمعی» از منو
|
||||
- انبارداری → گروه مستقل | پکیجها → آیتم مستقل
|
||||
- **فایل:** `Shared/NavMenu.razor`
|
||||
|
||||
### ۸.۲ — حذف گزارشهای کوچک از سفارشات ✅
|
||||
- حذف کارتهای آماری (TotalOrders + TotalAmount) و نمودار Bar ارسال
|
||||
- حذف فیلدها: `_stats`, `_statusChartLabels`, `_statusChartSeries`
|
||||
- حذف متد `UpdateStats()` و کلاس `OrderStatsViewModel`
|
||||
- عنوان: «سفارشهای کاربر» → «لیست سفارشات»
|
||||
- **فایلها:** `Pages/UserOrder/UserOrderMainPage.razor` + `.razor.cs`
|
||||
|
||||
### ۸.۳ — رفع باگ لیست خالی سفارشات ✅
|
||||
- **علت:** `PaymentDate.ToDateTime()` بدون null check → exception ساکت در WASM
|
||||
- **رفع:** `@if (context.Item.PaymentDate != null)` + fallback "-"
|
||||
- **فایل:** `Pages/UserOrder/UserOrderMainPage.razor`
|
||||
|
||||
### ۸.۴ — بازنویسی صفحه محصولات تخفیفی ✅
|
||||
- تبدیل به `BasePageComponent` (مطابق فروشگاه عادی)
|
||||
- فیلترهای جدید: وضعیت + موجودی
|
||||
- ستونهای جدید: تصویر inline + عنوان truncate + چیپ رنگی موجودی + چیپ وضعیت
|
||||
- **فایلها:** `Pages/DiscountShop/DiscountProductsMainPage.razor` + `.razor.cs` (بازنویسی کامل)
|
||||
|
||||
### ۸.۵ — بازنویسی صفحه دستهبندیهای تخفیفی ✅
|
||||
- تبدیل به `BasePageComponent` (مطابق فروشگاه عادی)
|
||||
- ستونهای جدید: نام لاتین، دستهبندی والد (resolve شده)، تعداد محصولات، ترتیب
|
||||
- code-behind جدید (`@code` درونخطی → `.razor.cs`)
|
||||
- **فایلها:** `Pages/DiscountShop/DiscountCategoriesMainPage.razor` + `.razor.cs` (بازنویسی کامل)
|
||||
|
||||
### خلاصه آمار فاز ۸
|
||||
| معیار | تعداد |
|
||||
|---|---|
|
||||
| فایلهای بازنویسیشده | 4 |
|
||||
| فایلهای ویرایششده | 3 |
|
||||
| باگهای رفعشده | 2 (PaymentDate null + لیست خالی) |
|
||||
| Build | 0 Error ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 🏷 فاز ۷ — پس از اجرای Audit v7.0 (تیر ۱۴۰۴)
|
||||
|
||||
### خلاصه تغییرات
|
||||
- **۵۷ مورد** از ۵۸ یافته audit اجرا شد (≈98%)
|
||||
- **تمام ۱۶ مورد بحرانی 🔴** حل شد
|
||||
- **تمام ۱۸ مورد جزئی 🟢** حل شد
|
||||
- **فازهای ۱ تا ۶ تکمیل شدند** (بهجز DayaLoan و Audit Trail که نیاز به بکاند دارند)
|
||||
- **فاز ۷** — Global Search, Notification Bell, NavMenu Badge, Export Excel 4 صفحه جدید
|
||||
- **۱۳+ فایل جدید** ساخته شد
|
||||
- **۹ صفحه** ادغام شدند (۱۴ صفحه → ۵ صفحه)
|
||||
- **Build**: 0 error ✅
|
||||
|
||||
---
|
||||
|
||||
## فاز ۷ — Global Search + Notifications + Export All + NavMenu Badge (v7.0)
|
||||
|
||||
### 6.5 — Badge Pending در NavMenu — ✅
|
||||
- **`NavMenu.razor`**: اضافه شدن `MudBadge` روی لینک «درخواستهای برداشت»
|
||||
- نمایش تعداد درخواستهای Pending بصورت real-time از gRPC (`CommissionContract.GetWithdrawalRequestsAsync` با `Status=0`)
|
||||
- fire-and-forget در `OnInitializedAsync` با try/catch silent
|
||||
|
||||
### 6.2 — Global Search در AppBar — ✅
|
||||
- **فایل جدید**: `Shared/GlobalSearch.razor`
|
||||
- `MudAutocomplete<SearchResultItem>` با Debounce 400ms
|
||||
- دو نوع جستجو: ۱) نام صفحات (۱۵ صفحه اصلی) ۲) کاربران از gRPC (`UserContract.GetAllUserByFilterAsync`)
|
||||
- Template سفارشی با آیکون + عنوان + زیرنویس
|
||||
- ناوبری خودکار با `Navigation.NavigateTo`
|
||||
- اضافه شده در `MainLayout.razor` داخل `<AuthorizeView>`
|
||||
- CSS در `app.css` — rounded border-radius 24px, compact padding
|
||||
|
||||
### 6.1 — Notification Bell (placeholder) — ✅
|
||||
- **`MainLayout.razor`**: اضافه شدن `MudMenu` + `MudBadge` (Dot) + آیکون Notifications
|
||||
- Dropdown با هدر «اعلانها» + placeholder «اعلان جدیدی وجود ندارد»
|
||||
- آماده اتصال به بکاند — فقط `Visible=true` و populate items
|
||||
|
||||
### 6.3 — Export Excel صفحات جدید — ✅ (۱۰ صفحه کامل)
|
||||
- **۴ صفحه جدید** اضافه شدند به ۶ صفحه قبلی:
|
||||
1. **UserMainPage** — خروجی CSV: شناسه, موبایل, نام, نام خانوادگی, کدملی
|
||||
2. **DiscountOrdersMainPage** — خروجی CSV: شماره سفارش, تاریخ, مبلغ کل, تخفیف, قابل پرداخت, تعداد, وضعیت, پرداخت
|
||||
3. **ManualPayments** — خروجی CSV: شناسه, شناسه کاربر, نام کاربر, مبلغ, نوع, وضعیت, تاریخ
|
||||
4. **InventoryMainPage** — خروجی CSV: شناسه, محصول, انبار, موجودی, رزرو, قابل فروش, حد هشدار, وضعیت
|
||||
- هر ۴ صفحه: دکمه «خروجی Excel» + `ExportToExcel()` + `EscapeCsv()` + UTF-8 BOM + `jsSaveAsFile`
|
||||
- فایلهای code-behind جدید: `ManualPayments.razor.cs`, `DiscountOrdersMainPage.razor.cs`
|
||||
|
||||
---
|
||||
|
||||
## فاز ۶ — تاریخ شمسی + تاریخچهها + Export Excel (v6.0)
|
||||
|
||||
### تاریخ شمسی یکپارچه (3.6) — ✅ کامل
|
||||
- **۱۹+ فایل** آپدیت شدند: تمام تاریخها از فرمت میلادی (`yyyy/MM/dd`) به شمسی تبدیل شدند
|
||||
- از extension methodهای `DateTimeConverterCL` استفاده شد:
|
||||
- `.MiladiToJalali()` — فرمت تاریخ فقط (مثال: ۱۴۰۴/۰۴/۱۲)
|
||||
- `.MiladiToJalaliWithTime()` — فرمت تاریخ و ساعت
|
||||
- **صفحات اصلاحشده شامل**: Club (Members, Statistics, Features, MemberDetailsDialog)، Commission (Dashboard, WeeklyReports, WithdrawalRequests, Payouts, WithdrawalReports)، Network (UserNetworkInfo, BalancesReport, Statistics, NetworkTreeViewer)، UserOrder, Products, Package, Inventory, DiscountShop, Blog, Content, و غیره
|
||||
- `PropertyColumn Format="yyyy/MM/dd"` → `TemplateColumn` با `.MiladiToJalali()`
|
||||
- `MudDatePicker DateFormat` تغییر نکرد (قابلیت تقویم شمسی نیاز به کتابخانه جداگانه دارد)
|
||||
|
||||
### تاریخچه عضویت باشگاه (5.7) — ✅
|
||||
- **`MemberDetailsDialog.razor`**: تب تاریخچه با فیلدهای صحیح proto:
|
||||
- ستونها: `Action`, `Created`, `PackageName`, `Reason`, `PerformedBy`
|
||||
- Request: `PageIndex = 0` (نه PageNumber)
|
||||
|
||||
### تاریخچه تغییرات شبکه (5.8) — ✅
|
||||
- **`UserNetworkInfo.razor`**: تب تاریخچه با فیلدهای تخصصی:
|
||||
- ستونها: `Action`, `Created`, `OldParentId → NewParentId`, `OldNetworkLeg → NewNetworkLeg` (چپ/راست)
|
||||
- ستون `Reason` و `PerformedBy` اضافه شد
|
||||
- Request: `PageIndex = 0`
|
||||
|
||||
### لاگ اجرای Workerها (5.6) — ✅
|
||||
- در تب WorkerControl از SystemHub قبلاً پیادهسازی شده بود
|
||||
|
||||
### Export Excel / CSV (6.3) — ✅ ۶ صفحه
|
||||
قابلیت خروجی CSV (سازگار با Excel) به ۶ صفحه اصلی اضافه شد:
|
||||
|
||||
| صفحه | فایل(ها) | ستونهای خروجی |
|
||||
|---|---|---|
|
||||
| **Products** (قبلاً موجود) | ProductsMainPage.razor.cs | — |
|
||||
| **Club Members** | ClubMembers.razor + .razor.cs | شناسه,کاربر,نام,پکیج,کد فعالسازی,تاریخ فعالسازی,تاریخ انقضا,فعال,منقضی |
|
||||
| **Withdrawal Requests** | WithdrawalRequests.razor + .razor.cs | شناسه,کاربر,نام,مبلغ,وضعیت,روش,شبا,تاریخ درخواست,تاریخ پردازش,مرجع بانکی |
|
||||
| **Weekly Reports** | WeeklyReports.razor + .razor.cs | هفته,مبلغ استخر,موجودیها,ارزش,وضعیت,تاریخ محاسبه |
|
||||
| **User Orders** | UserOrderMainPage.razor + .razor.cs | شناسه,کاربر,نام,مبلغ,وضعیت پرداخت,وضعیت ارسال,روش پرداخت,تاریخ |
|
||||
| **Stock Movements** | MovementsPage.razor + .razor.cs | شناسه,محصول,نوع,تعداد,قبل,بعد,مرجع,یادداشت,تاریخ |
|
||||
|
||||
**الگوی پیادهسازی:**
|
||||
- دکمه `<MudButton>` با آیکون `FileDownload` در ToolBarContent
|
||||
- `StringBuilder` → CSV با هدرهای فارسی
|
||||
- UTF-8 BOM (`Encoding.UTF8.GetPreamble()`) برای نمایش صحیح فارسی در Excel
|
||||
- `jsSaveAsFile(filename, base64)` از `wwwroot/js/main.js`
|
||||
- متد `EscapeCsv()` برای مقادیر حاوی کاما/کوتیشن
|
||||
- تاریخها در خروجی نیز شمسی هستند
|
||||
|
||||
### فایلهای جدید (این نسخه)
|
||||
|
||||
| فایل | عملکرد |
|
||||
|---|---|
|
||||
| `Pages/Commission/WeeklyReports.razor.cs` | Code-behind برای export (بهدلیل محدودیت Razor parser با escaped quotes) |
|
||||
|
||||
---
|
||||
|
||||
## نسخه قبلی — Audit v5.0 (خرداد ۱۴۰۴)
|
||||
|
||||
### تم رنگی یکپارچه
|
||||
- **`CustomMudTheme.cs`**: تم Primary از `#0380C0` به `#6366f1` (Indigo) تغییر کرد — هماهنگ با FrontOffice
|
||||
- **`PaletteDark`** اضافه شد: Surface `#1a1a2e`, Background `#16213e`, AppbarBackground `#0f3460`
|
||||
- **`app.css`**: رنگ loading از `#1b6ec2` به `var(--mud-palette-primary)` تغییر کرد
|
||||
- **`NavMenu.razor.css`**: رنگ active از `#0380C0` هاردکد به CSS variable تغییر کرد
|
||||
- **`MainLayout.razor`**: حذف inline style `border: 1px solid #5e4df9`
|
||||
|
||||
### Dark Mode Toggle
|
||||
- آیکون ماه/آفتاب در AppBar اضافه شد
|
||||
- `_isDarkMode` state با `MudThemeProvider` اتصال دارد
|
||||
- تعویض بین `PaletteLight` و `PaletteDark` در CustomMudTheme
|
||||
|
||||
### عنوان اپلیکیشن
|
||||
- "پنل مدیریت **فرصت**" → "پنل مدیریت **کارا بازار سلامت**"
|
||||
|
||||
---
|
||||
|
||||
## فاز ۲ — بازطراحی لایوت صفحات
|
||||
|
||||
### BasePageComponent بازنویسی کامل
|
||||
- **قبل**: فیلتر ثابت سمت راست (20%) + جدول (80%)، ارتفاع 85vh هاردکد
|
||||
- **بعد**: فیلتر collapsible بالای جدول، فیلدها در `MudGrid` با `MudItem`، responsive
|
||||
- **۱۳ صفحه** آپدیت شدند: User, Products, Package, Category, UserOrder, UserAddress, UserRole, Role, BlogPost, BlogCategory, Tag, Transactions, Content
|
||||
|
||||
### DataGrid Height یکسانسازی
|
||||
- همه DataGridها: `Height="calc(100vh - 240px)"`
|
||||
- **صفحات اصلاحشده**: تمام ~33 صفحه دارای DataGrid
|
||||
|
||||
### Pager یکسانسازی
|
||||
- **PageSize**: همه `20, 50, 100`
|
||||
- **متن فارسی**: `RowsPerPageString="تعداد در صفحه"` و `InfoFormat="سطر {first_item} تا {last_item} از {all_items}"`
|
||||
|
||||
### MaxWidth یکسانسازی
|
||||
- `MainLayout.razor` MudContainer wrapper اضافه شد → MaxWidth یکسان برای همه صفحات
|
||||
- **۳۱ صفحه**: حذف `MudContainer` تکراری (double nesting)
|
||||
|
||||
### Breadcrumb
|
||||
- **`Shared/AppBreadcrumb.razor`** ساخته شد
|
||||
- جداکننده چپبهراست (`ChevronLeft`)
|
||||
- نقشه فارسی URL segments (60+ مسیر)
|
||||
- آخرین آیتم disabled (صفحه فعلی)
|
||||
|
||||
### Login + VerifyCode بازطراحی
|
||||
- عرض ثابت 35% → responsive `MaxWidth.Small`
|
||||
- لوگو + برندینگ "کارا بازار سلامت" اضافه شد
|
||||
- طراحی gradient background
|
||||
|
||||
---
|
||||
|
||||
## فاز ۲.۵ — پاکسازی ساختاری
|
||||
|
||||
### NavMenu بازسازی کامل
|
||||
- **لینک مرده `categories-dragdrop`**: حذف شد
|
||||
- **Transactions**: حذف از NavMenu (صفحه stub خالی)
|
||||
- **AlertsMonitoring**: حذف از NavMenu (100% mock)
|
||||
- **WorkerControl**: به SystemHub منتقل شد (قبلاً لینک نداشت)
|
||||
- **«پیامهای عمومی»**: از بخش فروشگاه تخفیفی به «مدیریت محتوا» منتقل شد
|
||||
- **ساختار گروهبندی** اصلاح شد:
|
||||
- کمیسیون و شبکه: NavGroup واضح
|
||||
- باشگاه مشتریان: اعضا و آمار + فیچرها
|
||||
- فروشگاه: پکیجها، محصولات (group)، سفارشها، فروشگاه تخفیفی (group)
|
||||
- مدیریت: کاربران، نقشها، پرداخت دستی، **کیفپول (جدید)**، **قراردادها (جدید)**
|
||||
- محتوا: بلاگ، صفحات سایت، پیامهای عمومی
|
||||
- سیستم: مدیریت سیستم، نسخه اپها
|
||||
|
||||
---
|
||||
|
||||
## فاز ۳ — UX بهبودها
|
||||
|
||||
### NoRecordsContent (Empty State)
|
||||
- **۳۳ DataGrid** آپدیت شدند
|
||||
- پیام: `<MudAlert Severity="Info">موردی یافت نشد.</MudAlert>`
|
||||
|
||||
### Delete Confirmation با نام آیتم
|
||||
- **۱۱ صفحه** اصلاح شدند:
|
||||
- Role → `«{model.Title}»`
|
||||
- User → `«{model.FirstName} {model.LastName}»`
|
||||
- Package → `«{model.Title}»`
|
||||
- UserAddress → `«{model.Title}»`
|
||||
- UserRole → `شناسه «{model.Id}»`
|
||||
- UserOrder → `شماره «{model.Id}»`
|
||||
- Category → `«{model.Title}»`
|
||||
- Products → `«{model.Title}»`
|
||||
- DiscountCategories → `«{category?.Title}»` (lookup اضافه شد)
|
||||
- DiscountProducts → `«{product?.Title}»` (lookup اضافه شد)
|
||||
- PublicMessages → `«{message?.Title}»` (lookup اضافه شد)
|
||||
|
||||
### User Menu در AppBar
|
||||
- `MudMenu` جایگزین متن ساده شماره موبایل شد
|
||||
- آیتمها: نام کاربر (غیرفعال) + خروج
|
||||
- آیکون `AccountCircle`
|
||||
- خروج (`signout`) از NavMenu به AppBar منتقل شد
|
||||
|
||||
---
|
||||
|
||||
## فاز ۳.۵ — ادغام صفحات
|
||||
|
||||
### Dashboard Merge ← `Pages/Index.razor`
|
||||
- **`Index.razor` + `SystemOverview.razor`** ادغام شدند
|
||||
- Routes: `/` + `/dashboard/overview`
|
||||
- بخشها:
|
||||
- 6 کارت آمار CMS (کاربران، محصولات، پکیجها، دستهبندیها، سفارشها، مبلغ پرداختشده)
|
||||
- آمار کمیسیون (هفته جاری، استخر، موجودیها، وضعیت محاسبه)
|
||||
- آمار باشگاه (کل/فعال/غیرفعال)
|
||||
- جدول آخرین ۵ سفارش پرداختشده
|
||||
- ۶ دکمه Quick Action
|
||||
- بارگذاری موازی با `Task.WhenAll` + `.ResponseAsync`
|
||||
- `SystemOverview.razor` **حذف شد**
|
||||
|
||||
### DiscountShopHub ← `Pages/DiscountShop/DiscountShopHub.razor`
|
||||
- Routes: `/discount-shop`, `/discount-orders`, `/discount-sales-reports`
|
||||
- ۲ تب: DiscountOrdersMainPage + SalesReports
|
||||
- انتخاب تب از URL
|
||||
|
||||
### ClubHub ← `Pages/Club/ClubHub.razor`
|
||||
- Routes: `/club`, `/club/members`, `/club/statistics`
|
||||
- ۲ تب: ClubMembers + Statistics
|
||||
|
||||
### BlogHub ← `Pages/Blog/BlogHub.razor`
|
||||
- Routes: `/blog`, `/blog/posts`, `/blog/categories`, `/tags`
|
||||
- ۳ تب: BlogPostManagementPage + BlogCategoryManagementPage + TagManagementPage
|
||||
|
||||
### SystemHub ← `Pages/SystemManagement/SystemHub.razor`
|
||||
- Routes: `/system`, `/system/configuration`, `/system/worker-control`, `/system/health`
|
||||
- ۳ تب: Configuration + WorkerControl + HealthDashboard
|
||||
|
||||
### ۱۰ صفحه — `@page` directive حذف شد
|
||||
صفحاتی که به hub منتقل شدند دیگر route مستقل ندارند:
|
||||
DiscountOrdersMainPage, SalesReports, ClubMembers, Statistics, BlogPostManagementPage, BlogCategoryManagementPage, TagManagementPage, Configuration, WorkerControl, HealthDashboard
|
||||
|
||||
---
|
||||
|
||||
## فاز ۴ — فیکسهای بیزینسی
|
||||
|
||||
### Dashboard آمار اصلاح شد
|
||||
- **قبل**: آمار از ۱۰ رکورد اول محاسبه میشد (نادرست)
|
||||
- **بعد**: از `metadata.TotalCount` و APIهای جداگانه استفاده میشود
|
||||
|
||||
### ManualPayments — تأیید/رد
|
||||
- دکمههای Approve/Reject وقتی `Status==0` (در انتظار) نمایش داده میشوند
|
||||
- Confirmation dialog قبل از اجرا
|
||||
- از `ManualPaymentId` (نه `Id`) استفاده میشود
|
||||
|
||||
### UserOrder — فیلتر Cancelled
|
||||
- `<MudSelectItem Value="@(5)">لغو شده</MudSelectItem>` اضافه شد
|
||||
|
||||
### WithdrawalRequests — بهبود کامل
|
||||
- **Pager**: 10/25/50/100 → 20/50/100
|
||||
- **`WithdrawalDetailsDialog.razor`** (جدید): نمایش کامل اطلاعات کاربر، درخواست، و بانکی
|
||||
- **`RejectReasonDialog.razor`** (جدید): ورود دلیل رد با اعتبارسنجی
|
||||
- ViewDetails: از Snackbar TODO به دیالوگ واقعی تغییر کرد
|
||||
- RejectRequest: از دلیل هاردکد به دیالوگ custom تغییر کرد
|
||||
|
||||
---
|
||||
|
||||
## فاز ۵ — صفحات مفقود (CMS-Derived)
|
||||
|
||||
### WalletManagementPage ← `Pages/Wallet/WalletManagementPage.razor`
|
||||
- Route: `/wallets`
|
||||
- **تب ۱ — کیفپولها**: DataGrid با ServerData، فیلتر UserAutoComplete
|
||||
- ستونها: شناسه، کاربر، موجودی اصلی، موجودی شبکه، مشاهده تغییرات
|
||||
- نوع: `GetAllUserWalletByFilterResponseModel`
|
||||
- **تب ۲ — تاریخچه تغییرات**: DataGrid با ServerData، فیلتر کاربر + نوع (افزایش/کاهش)
|
||||
- ستونها: شناسه، کیفپول، نوع (چیپ رنگی)، تغییر موجودی، موجودی فعلی، تغییر شبکه، موجودی شبکه فعلی، شناسه مرجع، تاریخ
|
||||
- نوع: `GetAllUserWalletChangeLogByFilterResponseModel`
|
||||
- فیلدهای proto با typo: `ChangeNerworkValue`, `RefrenceId`
|
||||
|
||||
### UserContractPage ← `Pages/Contract/UserContractPage.razor`
|
||||
- Route: `/contracts`
|
||||
- DataGrid با ServerData، فیلتر UserAutoComplete
|
||||
- ستونها: شناسه، کاربر، قرارداد، کد امضا، لینک PDF، جزئیات
|
||||
- نوع: `GetAllUserContractByFilterResponseModel`
|
||||
|
||||
### ContractDetailsDialog ← `Pages/Contract/ContractDetailsDialog.razor`
|
||||
- جدول ساده: شناسه، کاربر، قالب قرارداد، کد امضا، لینک دانلود PDF
|
||||
|
||||
### ClubFeaturesPage ← `Pages/Club/ClubFeaturesPage.razor`
|
||||
- Route: `/club/features`
|
||||
- **تب ۱ — فیچرهای تعریفشده**: کارتهای MudPaper با عنوان، توضیحات، وضعیت (فعال/غیرفعال)، ترتیب
|
||||
- از `ConfigClient.GetClubFeaturesAsync`
|
||||
- **تب ۲ — فیچرهای کاربران**: DataGrid با فیلتر کاربر، سوئیچ فعال/غیرفعال
|
||||
- از `ClubClient.GetUserClubFeaturesAsync` + `ToggleUserClubFeatureAsync`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ نکات فنی مهم
|
||||
|
||||
### الگوهای Proto
|
||||
- **فیلتر**: `new GetAllXxxByFilterFilter()` (سطح بالا، بدون `.Types.`)
|
||||
- **پاسخ لیست**: `GetAllXxxByFilterResponseModel` (نه `GetXxxResponse`)
|
||||
- **Typoها در proto**: `ChangeNerworkValue` و `RefrenceId` — باید دقیقاً همینها استفاده شوند
|
||||
|
||||
### _Imports.razor — سرویسهای Global
|
||||
سرویسهای زیر از `_Imports.razor` تزریق میشوند و نباید در code-behind با `[Inject]` دوباره تعریف شوند:
|
||||
- `NavigationManager Navigation`
|
||||
- `ILocalStorageService LocalStorageService`
|
||||
- `ISnackbar Snackbar`
|
||||
- `IJSRuntime jsRuntime`
|
||||
- `IDialogService DialogService`
|
||||
- `AuthenticationStateProvider`
|
||||
|
||||
### MudBlazor محدودیتها
|
||||
- `Variant.Flat` وجود ندارد → از `Variant.Text` استفاده شود
|
||||
- `GutterSize` روی `MudGrid` deprecated است
|
||||
|
||||
---
|
||||
|
||||
## فاز ۴ (ادامه) — تکمیل فیکسهای بیزینسی
|
||||
|
||||
### UserSettings — حذف تبهای جعلی (2.5.5)
|
||||
- **`UserSettings.razor`**: تب Notifications (۷ تاگل جعلی بدون بکاند) → placeholder «بهزودی فعال میشود» با آیکون NotificationsActive
|
||||
- **`UserSettings.razor`**: تب Security (فرم تغییر رمز + 2FA بدون بکاند) → placeholder «بهزودی فعال میشود» با آیکون Lock
|
||||
- **حذف کد مرده**: ~۱۵ متغیر notification/security + متدهای `SaveNotificationSettings()`, `SaveSecuritySettings()`, `ChangePassword()`
|
||||
|
||||
### HealthDashboard — حذف متریکهای Mock (2.5.6)
|
||||
- **`HealthDashboard.razor`**: بخش System Resources (۴ کارت CPU=45.2%, Memory=62.8%, Disk=73.5%, Network) → یک کارت با پیام «مانیتورینگ منابع سیستم در نسخههای آینده با اتصال به Prometheus/Grafana فعال خواهد شد»
|
||||
- **حذف کد مرده**: ۹ متغیر mock (`_cpuUsage`, `_memoryUsage`, etc.) + متد `GetResourceColor()`
|
||||
|
||||
### حذف cursor:pointer زائد (4.10)
|
||||
- `Style="cursor:pointer;"` از **۱۷ فایل** حذف شد (MudButton/MudIconButton خودشان cursor:pointer دارند)
|
||||
- شامل: ProductsMainPage, GalleryDialog, Image.razor (base component), RoleMainPage, UserOrderMainPage, PackageMainPage, etc.
|
||||
|
||||
### حذف Console.WriteLine (4.11)
|
||||
- از **۱۱ فایل** حذف شد: DiscountProductService, DiscountCategoryService, NetworkTreeViewer, UserSettings, CategoryMainPage, UserOrderMainPage, Commission/Dashboard, DiscountProductsMainPage, DiscountCategoryMultiSelectCombo, UserRole/CreateDialog
|
||||
- **`Commission/Dashboard.razor.cs`**: `using System.Text.Json` هم حذف شد (دیگر استفاده نمیشد)
|
||||
|
||||
### DiscountProducts — فیلتر دستهبندی (4.7)
|
||||
- **`DiscountProductsMainPage.razor`**: `@inject IDiscountCategoryService` اضافه شد
|
||||
- دستهبندیهای فعال از سرویس بارگذاری و در MudSelect نمایش داده میشوند
|
||||
- جایگزینی `@* TODO: Load categories *@` با `@foreach` واقعی
|
||||
|
||||
### DiscountProducts — Items → ServerData (4.8)
|
||||
- **`DiscountProductsMainPage.razor`**: تبدیل از `Items="@_products"` به `ServerData="LoadServerData"`
|
||||
- متد `LoadServerData(GridState<T>)` اضافه شد با `PageNumber` و `PageSize` از state
|
||||
- `Height="calc(100vh - 300px)"` + `FixedHeader="true"` اضافه شد
|
||||
- Snackbar اطلاعرسانی اضافی در هر بارگذاری حذف شد (redundant)
|
||||
|
||||
### UserOrder — فعالسازی آمار (4.6)
|
||||
- **`UserOrderMainPage.razor`**: بخش آمار (MudCard + MudChart) از حالت comment خارج شد
|
||||
- نمایش تعداد سفارشها + مجموع مبلغ + نمودار Bar وضعیت ارسال
|
||||
|
||||
### UserSettings — persist با localStorage (4.9)
|
||||
- تب General از قبل با localStorage کار میکرد ✅ (فقط تبهای جعلی حذف شدند)
|
||||
|
||||
---
|
||||
|
||||
## فاز ۴.۵ — Cross-Navigation
|
||||
|
||||
### لینک نام کاربر → پروفایل (4.5.1, 4.5.2)
|
||||
- **`UserOrderMainPage.razor`**: ستون `UserFullName` از PropertyColumn به TemplateColumn تبدیل شد — MudLink به `/network/user-info/{UserId}`
|
||||
- **`UserPayouts.razor`**: نام/شناسه کاربر به MudLink تبدیل شد → `/network/user-info/{UserId}`
|
||||
- **`WithdrawalRequests.razor`**: نام کاربر به MudLink تبدیل شد → `/network/user-info/{UserId}`
|
||||
|
||||
### لینک محصول ↔ انبار (4.5.3, 4.5.4) — Skip
|
||||
- صفحه جزئیات محصول وجود ندارد (فقط لیست + دیالوگ ویرایش)
|
||||
- لینک cross-navigation بدون صفحه مقصد معنادار نیست
|
||||
|
||||
### Extract shared status helpers (3.5.6) — Skip
|
||||
- بررسی نشان داد هر context مقادیر متفاوتی دارد:
|
||||
- UserPayouts: status 1 = Success/پرداخت شده
|
||||
- WithdrawalRequests: status 1 = Info/واریز شده
|
||||
- PayoutDetailsDialog: فقط ۳ وضعیت (نه ۶)
|
||||
- استخراج به helper مشترک باعث از دست رفتن معنای domain-specific میشود
|
||||
|
||||
---
|
||||
|
||||
## 📈 آمار تغییرات
|
||||
|
||||
| معیار | تعداد |
|
||||
|---|---|
|
||||
| فایلهای جدید | 11 |
|
||||
| فایلهای حذفشده | 1 (SystemOverview.razor) |
|
||||
| فایلهای ویرایششده | ~65+ |
|
||||
| DataGridهای آپدیتشده | 33 |
|
||||
| Delete confirmation اصلاحشده | 11 |
|
||||
| صفحات ادغامشده | 14 → 5 |
|
||||
| خطاهای build حلشده | 9+ |
|
||||
| Console.WriteLine حذفشده | 22+ (از ۱۱ فایل) |
|
||||
| cursor:pointer حذفشده | 17 فایل |
|
||||
| Cross-link اضافهشده | 3 صفحه (UserOrder, UserPayouts, WithdrawalRequests) |
|
||||
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>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<!-- Suppress pre-existing warnings that do not affect runtime correctness -->
|
||||
<NoWarn>MUD0002;CA2022;BL0005</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -136,9 +138,12 @@
|
||||
<!-- This enables direct communication with CMS (no BFF needed) -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<!-- CMS Protobuf NuGet package (used for both local dev and Docker builds) -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.180" />
|
||||
<!-- CMS Protobuf: NuGet in CI/Docker; ProjectReference for local monorepo dev -->
|
||||
<ItemGroup Condition="Exists('../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj')">
|
||||
<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>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
|
||||
@@ -8,21 +8,46 @@ namespace BackOffice.Common.BaseComponents;
|
||||
public partial class DateRangePicker
|
||||
{
|
||||
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 DateTime? DefaultStart { get; set; }
|
||||
[Parameter] public DateTime? DefaultEnd { 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()
|
||||
{
|
||||
base.OnInitialized();
|
||||
if (DefaultStart.HasValue)
|
||||
_dateRange.Start = DefaultStart.Value.Date;
|
||||
|
||||
if (DefaultEnd.HasValue)
|
||||
_dateRange.End = DefaultEnd.Value.Date;
|
||||
var start = From ?? DefaultStart;
|
||||
var end = To ?? DefaultEnd;
|
||||
ApplyExternalRange(start, end);
|
||||
_lastSyncedFrom = From;
|
||||
_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()
|
||||
{
|
||||
var culture = new CultureInfo("fa-IR");
|
||||
@@ -53,19 +78,35 @@ public partial class DateRangePicker
|
||||
culture.NumberFormat.NumberNegativePattern = 0;
|
||||
return culture;
|
||||
}
|
||||
|
||||
private async Task OnClickOK()
|
||||
{
|
||||
if (_picker.DateRange is not null)
|
||||
{
|
||||
await OnChanged.InvokeAsync(_picker.DateRange);
|
||||
}
|
||||
|
||||
var range = _picker.DateRange ?? _dateRange;
|
||||
_dateRange = range ?? new DateRange();
|
||||
await NotifyRangeChangedAsync(_dateRange);
|
||||
await _picker.CloseAsync();
|
||||
}
|
||||
|
||||
private async Task OnClickClear()
|
||||
{
|
||||
_dateRange = new DateRange();
|
||||
await OnChanged.InvokeAsync(_dateRange);
|
||||
await NotifyRangeChangedAsync(_dateRange);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,12 @@ using CMSMicroservice.Protobuf.Protos.SitePage;
|
||||
using CMSMicroservice.Protobuf.Protos.SitePageSettings;
|
||||
using CMSMicroservice.Protobuf.Protos.UserContract;
|
||||
using CMSMicroservice.Protobuf.Protos.UserWallet;
|
||||
using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog;
|
||||
using CMSMicroservice.Protobuf.Protos.UserWalletHistory;
|
||||
using CMSMicroservice.Protobuf.Protos.PaymentTransaction;
|
||||
using CMSMicroservice.Protobuf.Protos.Transactions;
|
||||
using CMSMicroservice.Protobuf.Protos.UserPackagePurchase;
|
||||
using CMSMicroservice.Protobuf.Protos.ClubMembershipCycle;
|
||||
using CMSMicroservice.Protobuf.Protos.ClubMembershipCycleHistory;
|
||||
using BackOffice.Common.Utilities;
|
||||
using BackOffice.Services.DiscountProduct;
|
||||
using BackOffice.Services.DiscountCategory;
|
||||
@@ -167,7 +172,14 @@ public static class ConfigureServices
|
||||
|
||||
// User Wallet Services
|
||||
services.AddTransient(sp => new UserWalletContract.UserWalletContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new UserWalletChangeLogContract.UserWalletChangeLogContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new UserWalletHistoryContract.UserWalletHistoryContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
|
||||
// Admin Report Services
|
||||
services.AddTransient(sp => new PaymentTransactionContract.PaymentTransactionContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new TransactionsContract.TransactionsContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new UserPackagePurchaseContract.UserPackagePurchaseContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new ClubMembershipCycleContract.ClubMembershipCycleContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
services.AddTransient(sp => new ClubMembershipCycleHistoryContract.ClubMembershipCycleHistoryContractClient(sp.GetRequiredService<CallInvoker>()));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BackOffice.Common.Utilities;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using BackOffice.Common.Utilities;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace BackOffice.Common.Utilities;
|
||||
@@ -19,9 +20,41 @@ public class AppTokenProvider : ITokenProvider
|
||||
{
|
||||
var authorizationToken = await _localStorage.GetItemAsync<string>(GlobalConstants.JwtTokenKey);
|
||||
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;
|
||||
}
|
||||
|
||||
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 --from=build /app/publish/wwwroot .
|
||||
|
||||
# Configure nginx for SPA routing
|
||||
RUN echo 'server { \
|
||||
listen 80; \
|
||||
server_name _; \
|
||||
location / { \
|
||||
root /usr/share/nginx/html; \
|
||||
try_files $uri $uri/ /index.html; \
|
||||
} \
|
||||
}' > /etc/nginx/conf.d/default.conf
|
||||
# Copy entrypoint script that sets blazor-environment header
|
||||
COPY ["BackOffice/docker-entrypoint.sh", "/docker-entrypoint.sh"]
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
# Default Blazor environment (override via K8s env var BLAZOR_ENVIRONMENT)
|
||||
ENV BLAZOR_ENVIRONMENT=Staging
|
||||
# Default gateway URL for gRPC calls (injected into appsettings at startup)
|
||||
ENV GW_URL=https://cms.kbs2.ir
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["/docker-entrypoint.sh"]
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<!-- Local packages (dev builds) -->
|
||||
<!-- <add key="Local" value="/home/masoud/Apps/project/FourSat/nupkg" /> -->
|
||||
<!-- Nexus (hosts FourSat packages + proxies nuget.org) -->
|
||||
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json" allowInsecureConnections="true" />
|
||||
</packageSources>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
@using CMSMicroservice.Protobuf.Protos.Package
|
||||
|
||||
<MudSelect T="long?"
|
||||
Label="@Label"
|
||||
Value="@SelectedPackageId"
|
||||
ValueChanged="OnValueChanged"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Clearable="true"
|
||||
Style="min-width: 160px;">
|
||||
<MudSelectItem T="long?" Value="@((long?)null)">همه پکیجها</MudSelectItem>
|
||||
@foreach (var pkg in _packages)
|
||||
{
|
||||
<MudSelectItem T="long?" Value="@((long?)pkg.Id)">@pkg.Title</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
@@ -0,0 +1,46 @@
|
||||
using CMSMicroservice.Protobuf.Protos.Package;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace BackOffice.Pages.AutoComplete;
|
||||
|
||||
public partial class PackageSelect
|
||||
{
|
||||
[Inject] public PackageContract.PackageContractClient PackageClient { get; set; }
|
||||
|
||||
[Parameter] public string Label { get; set; } = "پکیج";
|
||||
[Parameter] public long? SelectedPackageId { get; set; }
|
||||
[Parameter] public EventCallback<long?> SelectedPackageIdChanged { get; set; }
|
||||
|
||||
private List<GetAllPackageByFilterResponseModel> _packages = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task LoadPackages()
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetAllPackageByFilterRequest
|
||||
{
|
||||
PaginationState = new() { PageNumber = 1, PageSize = 100 },
|
||||
Filter = new()
|
||||
};
|
||||
|
||||
var result = await PackageClient.GetAllPackageByFilterAsync(request);
|
||||
_packages = result.Models?.OrderBy(p => p.SortOrder).ToList()
|
||||
?? new List<GetAllPackageByFilterResponseModel>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_packages = new List<GetAllPackageByFilterResponseModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnValueChanged(long? value)
|
||||
{
|
||||
SelectedPackageId = value;
|
||||
await SelectedPackageIdChanged.InvokeAsync(value);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,11 @@
|
||||
Required="true"
|
||||
Variant="Variant.Outlined" />
|
||||
|
||||
<MudTextField @bind-Value="Model.Slug"
|
||||
Label="نشانی URL (slug)"
|
||||
Variant="Variant.Outlined"
|
||||
HelperText="اختیاری — خالی بگذارید تا خودکار از عنوان ساخته شود. فقط حروف کوچک انگلیسی، عدد و خط تیره." />
|
||||
|
||||
<MudTextField @bind-Value="Model.Summary"
|
||||
Label="خلاصه"
|
||||
Variant="Variant.Outlined"
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
<div>
|
||||
<MudText Typo="Typo.h5" Class="mb-3">فیچرهای باشگاه</MudText>
|
||||
|
||||
@* ── G9: راهنمای ماتریس فیچر (Q28) ── *@
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||
فیچرهای تیکخورده برای خریداران این پکیج فعال میشود. تغییر فیچرها روی کاربران فعلی بلافاصله اعمال میشود.
|
||||
</MudAlert>
|
||||
|
||||
<MudTabs Elevation="0" Rounded="true" Border="true" ApplyEffectsToContainer="true"
|
||||
PanelClass="pt-4" @bind-ActivePanelIndex="_activeTab">
|
||||
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
@page "/club"
|
||||
@page "/club/members"
|
||||
@page "/club/purchases"
|
||||
@page "/club/customer-packages"
|
||||
@page "/club/cycles"
|
||||
@page "/club/history"
|
||||
@page "/club/statistics"
|
||||
@attribute [Authorize]
|
||||
@using BackOffice.Pages.Club.Reports
|
||||
|
||||
<div>
|
||||
<MudText Typo="Typo.h5" Class="mb-3">باشگاه مشتریان</MudText>
|
||||
|
||||
<MudTabs Elevation="0" Rounded="true" Border="true" ApplyEffectsToContainer="true"
|
||||
PanelClass="pt-4" @bind-ActivePanelIndex="_activeTab">
|
||||
<MudTabPanel Text="اعضای باشگاه" Icon="@Icons.Material.Filled.Groups">
|
||||
<MudTabPanel Text="اعضا" Icon="@Icons.Material.Filled.Groups">
|
||||
<ClubMembers />
|
||||
</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 />
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
@@ -25,7 +42,11 @@
|
||||
var uri = Navigation.ToBaseRelativePath(Navigation.Uri).ToLowerInvariant().TrimEnd('/');
|
||||
_activeTab = uri switch
|
||||
{
|
||||
"club/statistics" => 1,
|
||||
"club/purchases" => 1,
|
||||
"club/customer-packages" => 2,
|
||||
"club/cycles" => 3,
|
||||
"club/history" => 4,
|
||||
"club/statistics" => 5,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,11 +3,17 @@
|
||||
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
@using BackOffice.Pages.Club.Components
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using CMSMicroservice.Protobuf.Protos.ClubMembership
|
||||
|
||||
<div>
|
||||
<MudText Typo="Typo.h4" Class="mb-4">مدیریت اعضای باشگاه</MudText>
|
||||
|
||||
@* ── G13: راهنمای اعضای باشگاه (Q28) ── *@
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||
فیلتر بر اساس آخرین پکیج خریداریشده اعمال میشود. هر کاربر میتواند چندین دوره پکیج داشته باشد.
|
||||
</MudAlert>
|
||||
|
||||
<MudDataGrid T="ClubMembershipModel"
|
||||
ServerData="@(new Func<GridState<ClubMembershipModel>, Task<GridData<ClubMembershipModel>>>(ServerReload))"
|
||||
Hover="true"
|
||||
@@ -16,7 +22,8 @@
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">لیست اعضا</MudText>
|
||||
<MudSpacer />
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<UserAutoComplete Label="جستجوی کاربر" @bind-SelectedUserId="_filterUserId" />
|
||||
<MudSwitch T="bool" @bind-Value="_filterIsActive"
|
||||
Color="Color.Success"
|
||||
Label="فقط فعالها" />
|
||||
|
||||
@@ -16,6 +16,7 @@ public partial class ClubMembers
|
||||
|
||||
private MudDataGrid<ClubMembershipModel> _gridData;
|
||||
private bool _filterIsActive = true;
|
||||
private long? _filterUserId;
|
||||
|
||||
private async Task<GridData<ClubMembershipModel>> ServerReload(GridState<ClubMembershipModel> state)
|
||||
{
|
||||
@@ -30,6 +31,9 @@ public partial class ClubMembers
|
||||
// Always use the filter value since it's now a bool (not nullable)
|
||||
request.IsActive = _filterIsActive;
|
||||
|
||||
if (_filterUserId.HasValue && _filterUserId.Value > 0)
|
||||
request.UserId = _filterUserId.Value;
|
||||
|
||||
var result = await ClubContract.GetAllClubMembershipsAsync(request);
|
||||
|
||||
if (result?.Models != null && result.Models.Any())
|
||||
|
||||
@@ -1,32 +1,39 @@
|
||||
@using CMSMicroservice.Protobuf.Protos.ClubMembership
|
||||
@using CMSMicroservice.Protobuf.Protos.Package
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form" @bind-IsValid="_formValid">
|
||||
<MudStack Spacing="3">
|
||||
<MudNumericField @bind-Value="_model.UserId"
|
||||
Label="شناسه کاربر"
|
||||
Required="true"
|
||||
RequiredError="شناسه کاربر الزامی است"
|
||||
Variant="Variant.Outlined" />
|
||||
<UserAutoComplete Label="انتخاب کاربر" @bind-SelectedUserId="_model.UserId" />
|
||||
|
||||
<MudNumericField @bind-Value="_model.PackageId"
|
||||
Label="شناسه پکیج"
|
||||
@if (_isLoadingPackages)
|
||||
{
|
||||
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="56px" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSelect T="long" @bind-Value="_model.PackageId"
|
||||
Label="انتخاب پکیج"
|
||||
Required="true"
|
||||
RequiredError="شناسه پکیج الزامی است"
|
||||
Variant="Variant.Outlined" />
|
||||
|
||||
<MudNumericField @bind-Value="_model.DurationMonths"
|
||||
Label="مدت زمان (ماه)"
|
||||
Required="true"
|
||||
RequiredError="مدت زمان الزامی است"
|
||||
Min="1"
|
||||
Max="12"
|
||||
Variant="Variant.Outlined" />
|
||||
RequiredError="انتخاب پکیج الزامی است"
|
||||
Variant="Variant.Outlined"
|
||||
AnchorOrigin="Origin.BottomCenter">
|
||||
@foreach (var pkg in _packages)
|
||||
{
|
||||
<MudSelectItem T="long" Value="pkg.Id">
|
||||
@pkg.Title — @($"{pkg.Price:N0}") ریال
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
|
||||
<MudAlert Severity="Severity.Info" Dense="true">
|
||||
<MudText Typo="Typo.body2">
|
||||
با تایید، عضویت باشگاه برای کاربر فعال میشود.
|
||||
<br/>
|
||||
<small>عضویت باشگاه بدون محدودیت زمانی است.</small>
|
||||
</MudText>
|
||||
</MudAlert>
|
||||
</MudStack>
|
||||
@@ -37,7 +44,7 @@
|
||||
<MudButton Color="Color.Success"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="@(!_formValid || _isSubmitting)">
|
||||
Disabled="@(!_formValid || _isSubmitting || _isLoadingPackages)">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" />
|
||||
@@ -54,12 +61,49 @@
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; }
|
||||
[Inject] public ClubMembershipContract.ClubMembershipContractClient ClubContract { get; set; }
|
||||
[Inject] public PackageContract.PackageContractClient PackageClient { get; set; }
|
||||
|
||||
private MudForm _form;
|
||||
private bool _formValid;
|
||||
private bool _isSubmitting;
|
||||
private bool _isLoadingPackages = true;
|
||||
private List<GetAllPackageByFilterResponseModel> _packages = new();
|
||||
private ActivateModel _model = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task LoadPackages()
|
||||
{
|
||||
_isLoadingPackages = true;
|
||||
try
|
||||
{
|
||||
var request = new GetAllPackageByFilterRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
|
||||
{
|
||||
PageNumber = 1,
|
||||
PageSize = 50
|
||||
}
|
||||
};
|
||||
var result = await PackageClient.GetAllPackageByFilterAsync(request);
|
||||
if (result?.Models != null)
|
||||
{
|
||||
_packages = result.Models.ToList();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری پکیجها: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoadingPackages = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task Submit()
|
||||
@@ -67,19 +111,31 @@
|
||||
await _form.Validate();
|
||||
if (!_formValid) return;
|
||||
|
||||
if (!_model.UserId.HasValue || _model.UserId.Value <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفاً کاربر را انتخاب کنید.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_model.PackageId <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_isSubmitting = true;
|
||||
try
|
||||
{
|
||||
var request = new ActivateClubMembershipRequest
|
||||
{
|
||||
UserId = _model.UserId,
|
||||
UserId = _model.UserId.Value,
|
||||
PackageId = _model.PackageId,
|
||||
DurationMonths = _model.DurationMonths
|
||||
ForceActivation = true
|
||||
};
|
||||
|
||||
var response = await ClubContract.ActivateClubMembershipAsync(request);
|
||||
|
||||
Snackbar.Add($"عضویت با موفقیت فعال شد.", Severity.Success);
|
||||
Snackbar.Add("عضویت با موفقیت فعال شد.", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -94,8 +150,7 @@
|
||||
|
||||
private class ActivateModel
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public long? UserId { get; set; }
|
||||
public long PackageId { get; set; }
|
||||
public int DurationMonths { get; set; } = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,23 +33,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>پکیج:</strong></td>
|
||||
<td>@Member.PackageName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>کد فعالسازی:</strong></td>
|
||||
<td dir="ltr">@Member.ActivationCode</td>
|
||||
<td>@(string.IsNullOrEmpty(Member.PackageName) ? "—" : Member.PackageName)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تاریخ فعالسازی:</strong></td>
|
||||
<td>@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>
|
||||
<td>@(Member.ActivatedAt != null ? Member.ActivatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() : "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>وضعیت:</strong></td>
|
||||
@@ -61,60 +49,17 @@
|
||||
</MudChip>
|
||||
</td>
|
||||
</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>
|
||||
<td><strong>تاریخ ایجاد:</strong></td>
|
||||
<td>@Member.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</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>
|
||||
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text">
|
||||
تاریخچه تغییرات عضویت در تب «گزارش تاریخچه» باشگاه قابل مشاهده است.
|
||||
</MudAlert>
|
||||
</MudStack>
|
||||
}
|
||||
</DialogContent>
|
||||
@@ -126,39 +71,5 @@
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { 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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using CMSMicroservice.Protobuf.Protos.ClubMembershipCycle
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
|
||||
@inject ClubMembershipCycleContract.ClubMembershipCycleContractClient CycleClient
|
||||
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||
گزارش دورههای عضویت باشگاه — هر خرید پکیج یک دور جدید ایجاد میکند.
|
||||
</MudAlert>
|
||||
|
||||
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||
<Filters>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudSelect T="bool?" Clearable="true" Label="دوره جاری" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_isCurrent">
|
||||
<MudSelectItem T="bool?" Value="@((bool?)true)">فقط دوره جاری</MudSelectItem>
|
||||
<MudSelectItem T="bool?" Value="@((bool?)false)">دورههای قبلی</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudSelect T="int?" Clearable="true" Label="وضعیت جادویی" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_magicState">
|
||||
<MudSelectItem T="int?" Value="0">عادی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="1">در حالت جادویی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="2">جادویی تکمیلشده</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه خرید پکیج" />
|
||||
</MudItem>
|
||||
</Filters>
|
||||
<Content>
|
||||
<MudGrid Class="mb-3" Spacing="2">
|
||||
<MudItem xs="4">
|
||||
<MudPaper Class="pa-3" Outlined="true">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">کل دورهها</MudText>
|
||||
<MudText Typo="Typo.h6">@_totalCount.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudPaper Class="pa-3" Outlined="true">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">دوره جاری</MudText>
|
||||
<MudText Typo="Typo.h6">@_currentCount.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudPaper Class="pa-3" Outlined="true">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">جادویی فعال</MudText>
|
||||
<MudText Typo="Typo.h6">@_magicActive.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudDataGrid @ref="_grid" T="ClubMembershipCycleModel" ServerData="LoadData" ReadOnly="true"
|
||||
Hover="true" Dense="true" Height="calc(100vh - 420px)">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
<PropertyColumn Property="x => x.UserName" Title="کاربر" />
|
||||
<PropertyColumn Property="x => x.CycleNumber" Title="شماره دور" />
|
||||
<PropertyColumn Property="x => x.PackageName" Title="پکیج" />
|
||||
<TemplateColumn Title="مبلغ (ریال)">
|
||||
<CellTemplate>@context.Item.PackageAmount.ToString("N0")</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="روش خرید">
|
||||
<CellTemplate>@ReportLabels.GetPurchaseMethodLabel(context.Item.PurchaseMethod)</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="تاریخ خرید">
|
||||
<CellTemplate>@context.Item.PackagePurchasedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="وضعیت جادویی">
|
||||
<CellTemplate>
|
||||
@ReportLabels.GetMagicStateLabel(null,
|
||||
context.Item.MagicStartedAt?.ToDateTime(),
|
||||
context.Item.MagicCompletedAt?.ToDateTime())
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="دوره جاری">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Size="Size.Small" Color="@(context.Item.IsCurrentCycle ? Color.Success : Color.Default)">
|
||||
@(context.Item.IsCurrentCycle ? "بله" : "خیر")
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<NoRecordsContent>
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||
</NoRecordsContent>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="ClubMembershipCycleModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد در صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</Content>
|
||||
</BasePageComponent>
|
||||
|
||||
@code {
|
||||
private BasePageComponent? _basePage;
|
||||
private MudDataGrid<ClubMembershipCycleModel>? _grid;
|
||||
private long? _userId;
|
||||
private bool? _isCurrent;
|
||||
private int? _magicState;
|
||||
private DateTime? _fromDate, _toDate;
|
||||
private long _totalCount, _currentCount, _magicActive;
|
||||
|
||||
private GetAllClubMembershipCycleByFilterFilter BuildFilter() => new()
|
||||
{
|
||||
UserId = _userId > 0 ? _userId : null,
|
||||
IsCurrentCycle = _isCurrent,
|
||||
MagicState = _magicState,
|
||||
PackagePurchasedFrom = _fromDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value, DateTimeKind.Utc)) : null,
|
||||
PackagePurchasedTo = _toDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null
|
||||
};
|
||||
|
||||
private async Task<GridData<ClubMembershipCycleModel>> LoadData(GridState<ClubMembershipCycleModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filter = BuildFilter();
|
||||
var summary = await CycleClient.GetClubMembershipCycleSummaryAsync(new GetClubMembershipCycleSummaryRequest { Filter = filter });
|
||||
_totalCount = summary.TotalCount;
|
||||
_currentCount = summary.CurrentCycleCount;
|
||||
_magicActive = summary.MagicActiveCount;
|
||||
|
||||
var result = await CycleClient.GetAllClubMembershipCycleByFilterAsync(new GetAllClubMembershipCycleByFilterRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||
Filter = filter
|
||||
});
|
||||
return new GridData<ClubMembershipCycleModel>
|
||||
{
|
||||
Items = result?.Models?.ToList() ?? new(),
|
||||
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
return new GridData<ClubMembershipCycleModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnFilterSubmit() { if (_grid != null) await _grid.ReloadServerData(); }
|
||||
private async Task OnFilterCleared()
|
||||
{
|
||||
_userId = null; _isCurrent = null; _magicState = null; _fromDate = _toDate = null;
|
||||
if (_grid != null) await _grid.ReloadServerData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using CMSMicroservice.Protobuf.Protos.ClubMembership
|
||||
@using CMSMicroservice.Protobuf.Protos.ClubMembershipCycleHistory
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
|
||||
@inject ClubMembershipContract.ClubMembershipContractClient ClubClient
|
||||
@inject ClubMembershipCycleHistoryContract.ClubMembershipCycleHistoryContractClient CycleHistoryClient
|
||||
|
||||
<MudTabs Elevation="0" Rounded="true" Border="true" ApplyEffectsToContainer="true" PanelClass="pt-4">
|
||||
<MudTabPanel Text="تغییرات عضویت" Icon="@Icons.Material.Filled.History">
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||
تاریخچه فعال/غیرفعال شدن عضویت باشگاه.
|
||||
</MudAlert>
|
||||
<MudStack Row="true" Spacing="2" Class="mb-3">
|
||||
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_memUserId" />
|
||||
<MudSelect T="int?" Clearable="true" Label="عملیات" Variant="Variant.Outlined" Margin="Margin.Dense"
|
||||
@bind-Value="_memAction" Style="max-width:200px;">
|
||||
<MudSelectItem T="int?" Value="0">فعالسازی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="1">غیرفعالسازی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="2">ویرایش</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="3">اصلاح دستی</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ReloadMembership">جستجو</MudButton>
|
||||
</MudStack>
|
||||
<MudDataGrid @ref="_memGrid" T="ClubMembershipHistoryModel" ServerData="LoadMembershipHistory" ReadOnly="true"
|
||||
Hover="true" Dense="true" Height="calc(100vh - 400px)">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
<PropertyColumn Property="x => x.UserName" Title="کاربر" />
|
||||
<TemplateColumn Title="وضعیت">
|
||||
<CellTemplate>@ReportLabels.FormatActiveChange(context.Item.OldIsActive, context.Item.NewIsActive)</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="مشارکت اولیه">
|
||||
<CellTemplate>
|
||||
@(context.Item.OldInitialContribution != null ? context.Item.OldInitialContribution.Value.ToString("N0") : "—")
|
||||
→ @(context.Item.NewInitialContribution != null ? context.Item.NewInitialContribution.Value.ToString("N0") : "—")
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="عملیات">
|
||||
<CellTemplate>@ReportLabels.GetClubMembershipActionLabel(context.Item.Action)</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="x => x.PerformedBy" Title="انجامدهنده" />
|
||||
<PropertyColumn Property="x => x.Reason" Title="دلیل" />
|
||||
<TemplateColumn Title="تاریخ">
|
||||
<CellTemplate>@context.Item.CreatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<NoRecordsContent>
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||
</NoRecordsContent>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="ClubMembershipHistoryModel" PageSizeOptions="@(new int[] { 20, 50, 100 })" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="تغییرات دوره" Icon="@Icons.Material.Filled.Timeline">
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||
ردیابی تغییرات دوره از زمان deploy فعال شد. رکوردهای قدیمیتر ممکن است خالی باشند.
|
||||
</MudAlert>
|
||||
<MudStack Row="true" Spacing="2" Class="mb-3">
|
||||
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_cycleUserId" />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ReloadCycle">جستجو</MudButton>
|
||||
</MudStack>
|
||||
<MudDataGrid @ref="_cycleGrid" T="ClubMembershipCycleHistoryModel" ServerData="LoadCycleHistory" ReadOnly="true"
|
||||
Hover="true" Dense="true" Height="calc(100vh - 400px)">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
<PropertyColumn Property="x => x.UserName" Title="کاربر" />
|
||||
<PropertyColumn Property="x => x.CycleNumber" Title="دور" />
|
||||
<TemplateColumn Title="دوره جاری">
|
||||
<CellTemplate>@ReportLabels.FormatBoolChange(context.Item.OldIsCurrentCycle, context.Item.NewIsCurrentCycle)</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="عملیات">
|
||||
<CellTemplate>@ReportLabels.GetClubCycleActionLabel(context.Item.Action)</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="x => x.PerformedBy" Title="انجامدهنده" />
|
||||
<PropertyColumn Property="x => x.Reason" Title="دلیل" />
|
||||
<TemplateColumn Title="تاریخ">
|
||||
<CellTemplate>@context.Item.CreatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<NoRecordsContent>
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||
</NoRecordsContent>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="ClubMembershipCycleHistoryModel" PageSizeOptions="@(new int[] { 20, 50, 100 })" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
|
||||
@code {
|
||||
private MudDataGrid<ClubMembershipHistoryModel>? _memGrid;
|
||||
private MudDataGrid<ClubMembershipCycleHistoryModel>? _cycleGrid;
|
||||
private long? _memUserId, _cycleUserId;
|
||||
private int? _memAction;
|
||||
|
||||
private async Task<GridData<ClubMembershipHistoryModel>> LoadMembershipHistory(GridState<ClubMembershipHistoryModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await ClubClient.GetClubMembershipHistoryAsync(new GetClubMembershipHistoryRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||
Filter = new GetClubMembershipHistoryFilter
|
||||
{
|
||||
UserId = _memUserId > 0 ? _memUserId : null,
|
||||
Action = _memAction
|
||||
}
|
||||
});
|
||||
return new GridData<ClubMembershipHistoryModel>
|
||||
{
|
||||
Items = result?.Models?.ToList() ?? new(),
|
||||
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
return new GridData<ClubMembershipHistoryModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<GridData<ClubMembershipCycleHistoryModel>> LoadCycleHistory(GridState<ClubMembershipCycleHistoryModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await CycleHistoryClient.GetAllClubMembershipCycleHistoryByFilterAsync(new GetAllClubMembershipCycleHistoryByFilterRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||
Filter = new GetAllClubMembershipCycleHistoryByFilterFilter
|
||||
{
|
||||
UserId = _cycleUserId > 0 ? _cycleUserId : null
|
||||
}
|
||||
});
|
||||
return new GridData<ClubMembershipCycleHistoryModel>
|
||||
{
|
||||
Items = result?.Models?.ToList() ?? new(),
|
||||
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
return new GridData<ClubMembershipCycleHistoryModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReloadMembership() { if (_memGrid != null) await _memGrid.ReloadServerData(); }
|
||||
private async Task ReloadCycle() { if (_cycleGrid != null) await _cycleGrid.ReloadServerData(); }
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using CMSMicroservice.Protobuf.Protos.UserPackagePurchase
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
|
||||
@inject UserPackagePurchaseContract.UserPackagePurchaseContractClient PurchaseClient
|
||||
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||
گزارش read-only خریدهای پکیج — فقط مشاهده و فیلتر.
|
||||
</MudAlert>
|
||||
|
||||
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||
<Filters>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudSelect T="int?" Clearable="true" Label="روش خرید" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_purchaseMethod">
|
||||
<MudSelectItem T="int?" Value="1">دایا</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="3">دستی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="2">درگاه</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه خرید" />
|
||||
</MudItem>
|
||||
</Filters>
|
||||
<Content>
|
||||
<MudGrid Class="mb-3" Spacing="2">
|
||||
<MudItem xs="6" sm="4">
|
||||
<MudPaper Class="pa-3" Outlined="true">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">تعداد</MudText>
|
||||
<MudText Typo="Typo.h6">@_totalCount.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="4">
|
||||
<MudPaper Class="pa-3" Outlined="true">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">جمع مبلغ (ریال)</MudText>
|
||||
<MudText Typo="Typo.h6">@_totalAmount.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudDataGrid @ref="_grid" T="UserPackagePurchaseModel" ServerData="LoadData" ReadOnly="true"
|
||||
Hover="true" Dense="true" Height="calc(100vh - 420px)">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
<TemplateColumn Title="کاربر">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.body2">@context.Item.UserName</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">@context.Item.UserMobile</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="x => x.PackageName" Title="پکیج" />
|
||||
<TemplateColumn Title="روش خرید">
|
||||
<CellTemplate>@ReportLabels.GetPurchaseMethodLabel(context.Item.PurchaseMethod)</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="مبلغ (ریال)">
|
||||
<CellTemplate>@context.Item.Amount.ToString("N0")</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="تاریخ خرید">
|
||||
<CellTemplate>@context.Item.PurchasedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="x => x.OrderId" Title="OrderId" />
|
||||
<PropertyColumn Property="x => x.TransactionId" Title="TransactionId" />
|
||||
</Columns>
|
||||
<NoRecordsContent>
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||
</NoRecordsContent>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="UserPackagePurchaseModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد در صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</Content>
|
||||
</BasePageComponent>
|
||||
|
||||
@code {
|
||||
private BasePageComponent? _basePage;
|
||||
private MudDataGrid<UserPackagePurchaseModel>? _grid;
|
||||
private long? _userId;
|
||||
private int? _purchaseMethod;
|
||||
private DateTime? _fromDate, _toDate;
|
||||
private long _totalCount, _totalAmount;
|
||||
|
||||
private GetAllUserPackagePurchaseByFilterFilter BuildFilter() => new()
|
||||
{
|
||||
UserId = _userId > 0 ? _userId : null,
|
||||
PurchaseMethod = _purchaseMethod,
|
||||
PurchasedFrom = _fromDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value, DateTimeKind.Utc)) : null,
|
||||
PurchasedTo = _toDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null
|
||||
};
|
||||
|
||||
private async Task<GridData<UserPackagePurchaseModel>> LoadData(GridState<UserPackagePurchaseModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filter = BuildFilter();
|
||||
var summary = await PurchaseClient.GetUserPackagePurchaseSummaryAsync(new GetUserPackagePurchaseSummaryRequest { Filter = filter });
|
||||
_totalCount = summary.TotalCount;
|
||||
_totalAmount = summary.TotalAmount;
|
||||
|
||||
var result = await PurchaseClient.GetAllUserPackagePurchaseByFilterAsync(new GetAllUserPackagePurchaseByFilterRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||
Filter = filter
|
||||
});
|
||||
return new GridData<UserPackagePurchaseModel>
|
||||
{
|
||||
Items = result?.Models?.ToList() ?? new(),
|
||||
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
return new GridData<UserPackagePurchaseModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnFilterSubmit() { if (_grid != null) await _grid.ReloadServerData(); }
|
||||
private async Task OnFilterCleared()
|
||||
{
|
||||
_userId = null; _purchaseMethod = null; _fromDate = _toDate = null;
|
||||
if (_grid != null) await _grid.ReloadServerData();
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
@using CMSMicroservice.Protobuf.Protos.UserPackagePurchase
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
@using DateTimeConverterCL
|
||||
|
||||
@inject UserPackagePurchaseContract.UserPackagePurchaseContractClient PurchaseClient
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
جزئیات خرید پکیج — @UserName
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
@if (_loading)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="my-4" />
|
||||
}
|
||||
else if (_items.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Dense="true">خریدی برای این کاربر یافت نشد.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="_items" Dense="true" Hover="true" Breakpoint="Breakpoint.Sm">
|
||||
<HeaderContent>
|
||||
<MudTh>شناسه</MudTh>
|
||||
<MudTh>پکیج</MudTh>
|
||||
<MudTh>روش</MudTh>
|
||||
<MudTh>مبلغ (ریال)</MudTh>
|
||||
<MudTh>تاریخ خرید</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="شناسه">@context.Id</MudTd>
|
||||
<MudTd DataLabel="پکیج">@context.PackageName</MudTd>
|
||||
<MudTd DataLabel="روش">
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined"
|
||||
Color="@GetMethodColor(context.PurchaseMethod)">
|
||||
@ReportLabels.GetPurchaseMethodLabel(context.PurchaseMethod)
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="مبلغ">@context.Amount.ToString("N0")</MudTd>
|
||||
<MudTd DataLabel="تاریخ">
|
||||
@(context.PurchasedAt != null
|
||||
? context.PurchasedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()
|
||||
: "-")
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Close" Variant="Variant.Text">بستن</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
[Parameter] public long UserId { get; set; }
|
||||
[Parameter] public string UserName { get; set; } = string.Empty;
|
||||
|
||||
private bool _loading = true;
|
||||
private List<UserPackagePurchaseModel> _items = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await PurchaseClient.GetAllUserPackagePurchaseByFilterAsync(
|
||||
new GetAllUserPackagePurchaseByFilterRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
|
||||
{
|
||||
PageNumber = 1,
|
||||
PageSize = 500
|
||||
},
|
||||
Filter = new GetAllUserPackagePurchaseByFilterFilter
|
||||
{
|
||||
UserId = UserId
|
||||
}
|
||||
});
|
||||
|
||||
_items = result?.Models?
|
||||
.OrderBy(x => x.PurchasedAt?.ToDateTime() ?? DateTime.MinValue)
|
||||
.ToList() ?? new();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری جزئیات: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetMethodColor(int method) => method switch
|
||||
{
|
||||
1 => Color.Info, // دایا
|
||||
2 => Color.Primary, // درگاه
|
||||
3 => Color.Secondary, // دستی
|
||||
_ => Color.Default
|
||||
};
|
||||
|
||||
private void Close() => MudDialog.Close();
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
@using System.Text
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using BackOffice.Common.Utilities
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using BackOffice.Pages.Club.Reports.Components
|
||||
@using CMSMicroservice.Protobuf.Protos.UserPackagePurchase
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
@using DateTimeConverterCL
|
||||
@using Microsoft.JSInterop
|
||||
|
||||
@inject UserPackagePurchaseContract.UserPackagePurchaseContractClient PurchaseClient
|
||||
@inject IDialogService DialogService
|
||||
@inject IJSRuntime JsRuntime
|
||||
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||
گزارش تجمیعی مشتریان — اولین/آخرین پکیج، تفکیک دایا / دستی / درگاه. خروجی Excel مطابق فیلتر فعال است.
|
||||
</MudAlert>
|
||||
|
||||
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||
<Filters>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudSelect T="int?" Clearable="true" Label="روش خرید" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_purchaseMethod">
|
||||
<MudSelectItem T="int?" Value="1">دایا</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="3">دستی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="2">درگاه</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه خرید" />
|
||||
</MudItem>
|
||||
</Filters>
|
||||
<Content>
|
||||
<MudStack Row="true" Justify="Justify.FlexEnd" Class="mb-3">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Success"
|
||||
StartIcon="@Icons.Material.Filled.FileDownload"
|
||||
OnClick="ExportToExcel" Disabled="_exporting">
|
||||
خروجی Excel
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<MudDataGrid @ref="_grid" T="CustomerPackagePurchaseRollupModel" ServerData="LoadData" ReadOnly="true"
|
||||
Hover="true" Dense="true" Height="calc(100vh - 420px)">
|
||||
<Columns>
|
||||
<TemplateColumn Title="مشتری">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.body2">@context.Item.UserName</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">@context.Item.UserMobile</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="اولین پکیج">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.body2">@context.Item.FirstPackageName</MudText>
|
||||
<MudText Typo="Typo.caption">@context.Item.FirstAmount.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
@(context.Item.FirstPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="آخرین پکیج">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.body2">@context.Item.LastPackageName</MudText>
|
||||
<MudText Typo="Typo.caption">@context.Item.LastAmount.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
@(context.Item.LastPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")
|
||||
</MudText>
|
||||
<MudChip T="string" Size="Size.Small" Class="mt-1" Variant="Variant.Filled"
|
||||
Color="@GetMethodColor(context.Item.LastPurchaseMethod)">
|
||||
@ReportLabels.GetPurchaseMethodLabel(context.Item.LastPurchaseMethod)
|
||||
</MudChip>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="تعداد / جمع">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText>@context.Item.PurchaseCount خرید</MudText>
|
||||
<MudText Typo="Typo.caption">@context.Item.TotalAmount.ToString("N0") ریال</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="دایا">
|
||||
<CellTemplate>
|
||||
<MudText Typo="Typo.caption">@context.Item.DayaCount عدد</MudText>
|
||||
<MudText Typo="Typo.caption">@context.Item.DayaAmount.ToString("N0")</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="دستی">
|
||||
<CellTemplate>
|
||||
<MudText Typo="Typo.caption">@context.Item.ManualCount عدد</MudText>
|
||||
<MudText Typo="Typo.caption">@context.Item.ManualAmount.ToString("N0")</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="درگاه">
|
||||
<CellTemplate>
|
||||
<MudText Typo="Typo.caption">@context.Item.GatewayCount عدد</MudText>
|
||||
<MudText Typo="Typo.caption">@context.Item.GatewayAmount.ToString("N0")</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="عملیات">
|
||||
<CellTemplate>
|
||||
<MudButton Size="Size.Small" Variant="Variant.Outlined" Color="Color.Primary"
|
||||
OnClick="() => OpenDetails(context.Item)">
|
||||
جزئیات
|
||||
</MudButton>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<NoRecordsContent>
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||
</NoRecordsContent>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="CustomerPackagePurchaseRollupModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد در صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</Content>
|
||||
</BasePageComponent>
|
||||
|
||||
@code {
|
||||
private BasePageComponent? _basePage;
|
||||
private MudDataGrid<CustomerPackagePurchaseRollupModel>? _grid;
|
||||
private long? _userId;
|
||||
private int? _purchaseMethod;
|
||||
private DateTime? _fromDate, _toDate;
|
||||
private bool _exporting;
|
||||
|
||||
private GetCustomerPackagePurchaseRollupFilter BuildFilter()
|
||||
{
|
||||
var filter = new GetCustomerPackagePurchaseRollupFilter();
|
||||
if (_userId is > 0)
|
||||
filter.UserId = _userId;
|
||||
if (_purchaseMethod.HasValue)
|
||||
filter.PurchaseMethod = _purchaseMethod.Value;
|
||||
if (_fromDate.HasValue)
|
||||
filter.PurchasedFrom = Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value.Date, DateTimeKind.Utc));
|
||||
if (_toDate.HasValue)
|
||||
filter.PurchasedTo = Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc));
|
||||
return filter;
|
||||
}
|
||||
|
||||
private async Task<GridData<CustomerPackagePurchaseRollupModel>> LoadData(GridState<CustomerPackagePurchaseRollupModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await PurchaseClient.GetCustomerPackagePurchaseRollupAsync(
|
||||
new GetCustomerPackagePurchaseRollupRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
|
||||
{
|
||||
PageNumber = state.Page + 1,
|
||||
PageSize = state.PageSize
|
||||
},
|
||||
Filter = BuildFilter()
|
||||
});
|
||||
|
||||
return new GridData<CustomerPackagePurchaseRollupModel>
|
||||
{
|
||||
Items = result?.Models?.ToList() ?? new(),
|
||||
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
return new GridData<CustomerPackagePurchaseRollupModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnFilterSubmit()
|
||||
{
|
||||
if (_grid != null) await _grid.ReloadServerData();
|
||||
}
|
||||
|
||||
private async Task OnFilterCleared()
|
||||
{
|
||||
_userId = null;
|
||||
_purchaseMethod = null;
|
||||
_fromDate = _toDate = null;
|
||||
if (_grid != null) await _grid.ReloadServerData();
|
||||
}
|
||||
|
||||
private async Task OpenDetails(CustomerPackagePurchaseRollupModel row)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(CustomerPackagePurchaseDetailsDialog.UserId), row.UserId },
|
||||
{ nameof(CustomerPackagePurchaseDetailsDialog.UserName), row.UserName }
|
||||
};
|
||||
var options = new DialogOptions { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true };
|
||||
await DialogService.ShowAsync<CustomerPackagePurchaseDetailsDialog>("جزئیات خرید پکیج", parameters, options);
|
||||
}
|
||||
|
||||
private async Task ExportToExcel()
|
||||
{
|
||||
_exporting = true;
|
||||
try
|
||||
{
|
||||
// PageSize=0 → همهٔ ردیفهای مطابق فیلتر، مستقل از صفحهبندی گرید
|
||||
var result = await PurchaseClient.GetCustomerPackagePurchaseRollupAsync(
|
||||
new GetCustomerPackagePurchaseRollupRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
|
||||
{
|
||||
PageNumber = 1,
|
||||
PageSize = 0
|
||||
},
|
||||
Filter = BuildFilter()
|
||||
});
|
||||
|
||||
var all = result?.Models?.ToList() ?? new();
|
||||
if (all.Count == 0)
|
||||
{
|
||||
Snackbar.Add("دادهای برای خروجی با فیلتر فعلی وجود ندارد.", Severity.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
var headers = new[]
|
||||
{
|
||||
"شناسه کاربر", "نام مشتری", "موبایل",
|
||||
"اولین پکیج", "مبلغ اولین", "تاریخ اولین",
|
||||
"آخرین پکیج", "مبلغ آخرین", "تاریخ آخرین", "روش آخرین",
|
||||
"تعداد خرید", "جمع مبلغ",
|
||||
"تعداد دایا", "مبلغ دایا",
|
||||
"تعداد دستی", "مبلغ دستی",
|
||||
"تعداد درگاه", "مبلغ درگاه"
|
||||
};
|
||||
|
||||
var rows = all.Select(o => new object?[]
|
||||
{
|
||||
o.UserId,
|
||||
o.UserName,
|
||||
o.UserMobile,
|
||||
o.FirstPackageName,
|
||||
o.FirstAmount,
|
||||
o.FirstPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-",
|
||||
o.LastPackageName,
|
||||
o.LastAmount,
|
||||
o.LastPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-",
|
||||
ReportLabels.GetPurchaseMethodLabel(o.LastPurchaseMethod),
|
||||
o.PurchaseCount,
|
||||
o.TotalAmount,
|
||||
o.DayaCount,
|
||||
o.DayaAmount,
|
||||
o.ManualCount,
|
||||
o.ManualAmount,
|
||||
o.GatewayCount,
|
||||
o.GatewayAmount
|
||||
});
|
||||
|
||||
var filename = $"customer-packages-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||
CsvExportHelper.ToExcelXmlBase64("مشتریان پکیج", headers, rows));
|
||||
Snackbar.Add($"خروجی Excel آماده شد ({all.Count} مشتری).", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_exporting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetMethodColor(int method) => method switch
|
||||
{
|
||||
1 => Color.Info,
|
||||
2 => Color.Primary,
|
||||
3 => Color.Secondary,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
@@ -41,11 +41,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>ارزش هر بالانس:</strong></td>
|
||||
<td>@Payout.ValuePerBalance.ToString("N0") ریال</td>
|
||||
<td>@Payout.ValuePerBalance.ToString("N0") تومان</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>مبلغ کل:</strong></td>
|
||||
<td><strong>@Payout.TotalAmount.ToString("N0") ریال</strong></td>
|
||||
<td><strong>@Payout.TotalAmount.ToString("N0") تومان</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>وضعیت:</strong></td>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<DialogContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudAlert Severity="Severity.Warning" Dense="true">
|
||||
درخواست برداشت @Amount.ToString("N0") ریال برای @UserName رد خواهد شد.
|
||||
درخواست برداشت @Amount.ToString("N0") تومان برای @UserName رد خواهد شد.
|
||||
</MudAlert>
|
||||
<MudTextField @bind-Value="_reason"
|
||||
Label="دلیل رد درخواست"
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>مبلغ:</strong></td>
|
||||
<td><MudText Color="Color.Primary"><strong>@Request.Amount.ToString("N0") ریال</strong></MudText></td>
|
||||
<td><MudText Color="Color.Primary"><strong>@Request.Amount.ToString("N0") تومان</strong></MudText></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>روش برداشت:</strong></td>
|
||||
|
||||
@@ -5,116 +5,59 @@
|
||||
@using MudBlazor
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
|
||||
<div>
|
||||
<MudText Typo="Typo.h4" Class="mb-4">داشبورد کمیسیون</MudText>
|
||||
<MudStack Spacing="4">
|
||||
|
||||
@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>
|
||||
@* ── عنوان صفحه ── *@
|
||||
<div>
|
||||
<MudText Typo="Typo.h5" Class="mb-1">داشبورد کمیسیون</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||
@if (_poolData?.CalculatedAt != null)
|
||||
{
|
||||
var calculatedDate = _poolData.CalculatedAt.ToDateTime().ToLocalTime();
|
||||
var persianDate = PersianDateTime.ConvertToPersianDateTime(calculatedDate);
|
||||
@($"در تاریخ {persianDate}")
|
||||
}
|
||||
else
|
||||
{
|
||||
@("در انتظار محاسبه")
|
||||
}
|
||||
مبلغ پورسانت هر هفته بین اعضاء تقسیم میشود. هر پکیج استخر مستقل دارد.
|
||||
</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Week Selector -->
|
||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudText Typo="Typo.h6">انتخاب هفته:</MudText>
|
||||
</div>
|
||||
|
||||
@* ── فیلتر (اول از همه) ── *@
|
||||
<MudPaper Elevation="1" Class="pa-4">
|
||||
<MudGrid AlignItems="AlignItems.End" Spacing="3">
|
||||
<MudItem xs="12" sm="5">
|
||||
@if (_allWeeks.Any())
|
||||
{
|
||||
<MudSelect T="long"
|
||||
Value="_currentWeekDefinitionId"
|
||||
Label="شماره هفته"
|
||||
Label="هفته"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
AnchorOrigin="Origin.BottomCenter"
|
||||
Style="min-width: 400px;"
|
||||
AnchorOrigin="Origin.BottomLeft"
|
||||
TransformOrigin="Origin.TopLeft"
|
||||
AdornmentIcon="@Icons.Material.Filled.CalendarToday"
|
||||
Adornment="Adornment.Start"
|
||||
ValueChanged="OnWeekChanged">
|
||||
|
||||
@if (_availableWeeks?.CurrentWeek != null)
|
||||
{
|
||||
<MudSelectItem T="long" Value="@_availableWeeks.CurrentWeek.WeekDefinitionId">
|
||||
<MudText>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Today" Size="Size.Small" Class="ml-2" />
|
||||
@_availableWeeks.CurrentWeek.DisplayText
|
||||
</MudText>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Today" Size="Size.Small" Color="Color.Primary" />
|
||||
<span>@_availableWeeks.CurrentWeek.DisplayText</span>
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Primary" Variant="Variant.Text">جاری</MudChip>
|
||||
</MudStack>
|
||||
</MudSelectItem>
|
||||
<MudDivider />
|
||||
}
|
||||
|
||||
@if (_availableWeeks?.CalculatedWeeks?.Any() == true)
|
||||
{
|
||||
<MudListSubheader>هفتههای محاسبه شده</MudListSubheader>
|
||||
<MudListSubheader>تسویهشده</MudListSubheader>
|
||||
@foreach (var week in _availableWeeks.CalculatedWeeks)
|
||||
{
|
||||
<MudSelectItem T="long" Value="@week.WeekDefinitionId">
|
||||
<MudText>
|
||||
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small" Class="ml-2" />
|
||||
@week.DisplayText
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Size="Size.Small" Color="Color.Success" />
|
||||
<span>@week.DisplayText</span>
|
||||
@if (week.TotalPoolAmount > 0)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Success" Class="mr-2">
|
||||
@week.TotalPoolAmount.ToString("N0") ریال
|
||||
</MudChip>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">(@week.TotalPoolAmount.ToString("N0") T)</MudText>
|
||||
}
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudSelectItem>
|
||||
}
|
||||
<MudDivider />
|
||||
@@ -122,14 +65,14 @@
|
||||
|
||||
@if (_availableWeeks?.PendingWeeks?.Any() == true)
|
||||
{
|
||||
<MudListSubheader>هفتههای محاسبه نشده</MudListSubheader>
|
||||
<MudListSubheader>در انتظار تسویه</MudListSubheader>
|
||||
@foreach (var week in _availableWeeks.PendingWeeks)
|
||||
{
|
||||
<MudSelectItem T="long" Value="@week.WeekDefinitionId">
|
||||
<MudText>
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccessTime" Color="Color.Warning" Size="Size.Small" Class="ml-2" />
|
||||
@week.DisplayText
|
||||
</MudText>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccessTime" Size="Size.Small" Color="Color.Warning" />
|
||||
<span>@week.DisplayText</span>
|
||||
</MudStack>
|
||||
</MudSelectItem>
|
||||
}
|
||||
<MudDivider />
|
||||
@@ -141,10 +84,10 @@
|
||||
@foreach (var week in _availableWeeks.FutureWeeks)
|
||||
{
|
||||
<MudSelectItem T="long" Value="@week.WeekDefinitionId">
|
||||
<MudText>
|
||||
<MudIcon Icon="@Icons.Material.Filled.CalendarMonth" Color="Color.Info" Size="Size.Small" Class="ml-2" />
|
||||
@week.DisplayText
|
||||
</MudText>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.CalendarMonth" Size="Size.Small" Color="Color.Tertiary" />
|
||||
<span>@week.DisplayText</span>
|
||||
</MudStack>
|
||||
</MudSelectItem>
|
||||
}
|
||||
}
|
||||
@@ -154,97 +97,222 @@
|
||||
{
|
||||
<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"
|
||||
Color="Color.Primary"
|
||||
FullWidth="true"
|
||||
OnClick="LoadPoolData"
|
||||
StartIcon="@Icons.Material.Filled.Refresh">
|
||||
بارگذاری
|
||||
StartIcon="@Icons.Material.Filled.Search"
|
||||
Disabled="_isLoading">
|
||||
نمایش
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
<!-- Pool Details -->
|
||||
@if (_poolData != null)
|
||||
@* ── لودینگ ── *@
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">جزئیات استخر</MudText>
|
||||
<MudSimpleTable Hover="true" Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>شناسه استخر:</strong></td>
|
||||
<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()
|
||||
<MudPaper Elevation="0" Class="pa-6 d-flex justify-center align-center">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">در حال بارگذاری اطلاعات استخر…</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Color="Color.Secondary">-</MudText>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>تاریخ ایجاد:</strong></td>
|
||||
<td>@_poolData.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudPaper>
|
||||
@* ── نوار وضعیت هفته / پکیج ── *@
|
||||
<MudPaper Elevation="0" Class="pa-3 mud-background-gray rounded">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Wrap="Wrap.Wrap">
|
||||
<MudIcon Icon="@Icons.Material.Filled.DateRange" Color="Color.Default" Size="Size.Small" />
|
||||
<MudText Typo="Typo.body1">@_currentWeekDisplayText</MudText>
|
||||
|
||||
@if (!string.IsNullOrEmpty(_poolData?.PackageTitle))
|
||||
{
|
||||
<MudDivider Vertical="true" FlexItem="true" />
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Primary" Icon="@Icons.Material.Filled.WorkspacePremium">
|
||||
@_poolData.PackageTitle
|
||||
</MudChip>
|
||||
}
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<MudPaper Class="pa-4 mt-4" Elevation="2">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">عملیات سریع</MudText>
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudSpacer />
|
||||
|
||||
@if (_poolData != null && _poolData.Id > 0)
|
||||
{
|
||||
@if (_poolData.IsCalculated)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Success" Size="Size.Small"
|
||||
Icon="@Icons.Material.Filled.CheckCircle" Variant="Variant.Filled">
|
||||
تسویه انجام شده
|
||||
</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudChip T="string" Color="Color.Warning" Size="Size.Small"
|
||||
Icon="@Icons.Material.Filled.Pending" Variant="Variant.Filled">
|
||||
در انتظار تسویه
|
||||
</MudChip>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
@* ── ۳ کارت اصلی ── *@
|
||||
<MudGrid Spacing="3">
|
||||
@* کارت ۱: کل مبلغ استخر *@
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudPaper Elevation="2" Class="pa-4" Style="border-top: 4px solid var(--mud-palette-primary);">
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Color="Color.Primary" Size="Size.Small" />
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">کل مبلغ استخر پورسانت</MudText>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.h5" Style="font-weight:700; direction:ltr; text-align:right;">
|
||||
@((_poolData?.TotalPoolAmount ?? 0).ToString("N0"))
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">تومان — جمع کارمزد فعالسازیهای این هفته</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
@* کارت ۲: تعداد واحدهای پورسانت *@
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudPaper Elevation="2" Class="pa-4" Style="border-top: 4px solid var(--mud-palette-success);">
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.People" Color="Color.Success" Size="Size.Small" />
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">واحدهای پورسانت (بالانس)</MudText>
|
||||
<MudTooltip Text="تعداد واحدهایی که پورسانت به آنها تعلق میگیرد. هر عضو بر اساس شبکهاش چند واحد دریافت میکند.">
|
||||
<MudIcon Icon="@Icons.Material.Filled.HelpOutline" Size="Size.Small" Color="Color.Tertiary" />
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.h5" Style="font-weight:700; direction:ltr; text-align:right;">
|
||||
@((_poolData?.TotalBalances ?? 0).ToString("N0"))
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">واحد — مجموع همه اعضاء فعال</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
@* کارت ۳: سهم هر واحد *@
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudPaper Elevation="2" Class="pa-4" Style="border-top: 4px solid var(--mud-palette-warning);">
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.LocalAtm" Color="Color.Warning" Size="Size.Small" />
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">سهم پورسانت هر واحد</MudText>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.h5" Style="font-weight:700; direction:ltr; text-align:right;">
|
||||
@((_poolData?.ValuePerBalance ?? 0).ToString("N0"))
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
تومان — @((_poolData?.IsCalculated == true) ? "قطعی شده" : "قطعی نشده")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@* ── اعتبارسنجی مبلغ استخر ── *@
|
||||
@if (_poolData != null && _poolData.Id > 0)
|
||||
{
|
||||
@if (!_poolData.ValidationIsValid)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Variant="Variant.Filled" Icon="@Icons.Material.Filled.ReportProblem" NoIcon="false">
|
||||
<MudText Typo="Typo.subtitle2" Class="mb-2">مبلغ استخر با تعداد فعالسازیهای دیتابیس مطابقت ندارد</MudText>
|
||||
<MudGrid Spacing="2">
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudText Typo="Typo.caption">مبلغ ثبتشده</MudText>
|
||||
<MudText Typo="Typo.body2"><strong>@_poolData.TotalPoolAmount.ToString("N0") تومان</strong></MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudText Typo="Typo.caption">مبلغ انتظاری (@_poolData.ValidationActivationCount فعالسازی)</MudText>
|
||||
<MudText Typo="Typo.body2"><strong>@_poolData.ValidationExpectedAmount.ToString("N0") تومان</strong></MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudText Typo="Typo.caption">اختلاف</MudText>
|
||||
<MudText Typo="Typo.body2" Style="color:var(--mud-palette-warning-lighten);">
|
||||
<strong>@_poolData.ValidationDiscrepancy.ToString("N0") تومان</strong>
|
||||
</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudAlert>
|
||||
}
|
||||
else if (_poolData.ValidationActivationCount > 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Success" Variant="Variant.Text" Dense="true" Icon="@Icons.Material.Filled.VerifiedUser">
|
||||
مبلغ استخر صحیح است — با @_poolData.ValidationActivationCount فعالسازی در این هفته مطابقت دارد.
|
||||
</MudAlert>
|
||||
}
|
||||
}
|
||||
|
||||
@* ── جزئیات تکمیلی (بدون تکرار اطلاعات کارتها) ── *@
|
||||
@if (_poolData != null && _poolData.Id > 0)
|
||||
{
|
||||
<MudExpansionPanels Elevation="1">
|
||||
<MudExpansionPanel Text="جزئیات بیشتر" Icon="@Icons.Material.Filled.ExpandMore">
|
||||
<MudSimpleTable Dense="true" Hover="true" Style="max-width:540px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:200px;"><MudText Typo="Typo.body2" Color="Color.Secondary">شناسه استخر</MudText></td>
|
||||
<td><MudText Typo="Typo.body2">#@_poolData.Id</MudText></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><MudText Typo="Typo.body2" Color="Color.Secondary">تاریخ ایجاد استخر</MudText></td>
|
||||
<td><MudText Typo="Typo.body2">@_poolData.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</MudText></td>
|
||||
</tr>
|
||||
@if (_poolData.CalculatedAt != null)
|
||||
{
|
||||
<tr>
|
||||
<td><MudText Typo="Typo.body2" Color="Color.Secondary">تاریخ تسویه</MudText></td>
|
||||
<td><MudText Typo="Typo.body2">@_poolData.CalculatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</MudText></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
}
|
||||
|
||||
@* ── دکمههای عملیاتی ── *@
|
||||
<MudPaper Elevation="0" Class="pa-3 mud-background-gray rounded">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Wrap="Wrap.Wrap">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Payments"
|
||||
Href="/commission/payouts">
|
||||
مشاهده پرداختها
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Secondary"
|
||||
StartIcon="@Icons.Material.Filled.AccountBalance"
|
||||
Href="/commission/withdrawals">
|
||||
درخواستهای برداشت
|
||||
Href="/commission/payouts"
|
||||
Size="Size.Medium">
|
||||
پرداختهای این هفته
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Info"
|
||||
StartIcon="@Icons.Material.Filled.Calculate"
|
||||
Disabled="@(_poolData?.IsCalculated == true)"
|
||||
OnClick="TriggerCalculation">
|
||||
اجرای محاسبه دستی
|
||||
Color="Color.Default"
|
||||
StartIcon="@Icons.Material.Filled.AccountBalance"
|
||||
Href="/commission/withdrawals"
|
||||
Size="Size.Medium">
|
||||
درخواستهای برداشت
|
||||
</MudButton>
|
||||
<MudSpacer />
|
||||
@if (_poolData?.IsCalculated != true)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Warning"
|
||||
StartIcon="@Icons.Material.Filled.Calculate"
|
||||
OnClick="TriggerCalculation"
|
||||
Size="Size.Medium">
|
||||
اجرای تسویه دستی
|
||||
</MudButton>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</div>
|
||||
|
||||
</MudStack>
|
||||
|
||||
@@ -22,6 +22,9 @@ public partial class Dashboard
|
||||
private string _currentWeekDisplayText = string.Empty; // نمایش فارسی
|
||||
private GetWeeklyCommissionPoolResponse? _poolData;
|
||||
|
||||
// Package filter
|
||||
private long? _selectedPackageId;
|
||||
|
||||
// Available weeks data
|
||||
private CMSMicroservice.Protobuf.Protos.Commission.GetAvailableWeeksResponse? _availableWeeks;
|
||||
private List<CMSMicroservice.Protobuf.Protos.Commission.WeekInfo> _allWeeks = new();
|
||||
@@ -91,6 +94,12 @@ public partial class Dashboard
|
||||
await LoadPoolData();
|
||||
}
|
||||
|
||||
private async Task OnPackageChanged(long? packageId)
|
||||
{
|
||||
_selectedPackageId = packageId;
|
||||
await LoadPoolData();
|
||||
}
|
||||
|
||||
private async Task LoadPoolData()
|
||||
{
|
||||
if (_currentWeekDefinitionId <= 0)
|
||||
@@ -106,7 +115,8 @@ public partial class Dashboard
|
||||
|
||||
var request = new GetWeeklyCommissionPoolRequest
|
||||
{
|
||||
WeekDefinitionId = _currentWeekDefinitionId
|
||||
WeekDefinitionId = _currentWeekDefinitionId,
|
||||
PackageId = _selectedPackageId
|
||||
};
|
||||
|
||||
_poolData = await CommissionContract.GetWeeklyCommissionPoolAsync(request);
|
||||
@@ -126,24 +136,41 @@ public partial class Dashboard
|
||||
|
||||
private async Task TriggerCalculation()
|
||||
{
|
||||
var packageInfo = _selectedPackageId.HasValue
|
||||
? $" (فقط پکیج #{_selectedPackageId})"
|
||||
: " (همه پکیجها)";
|
||||
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"تایید",
|
||||
$"آیا از اجرای محاسبه دستی برای هفته {_currentWeekDisplayName} مطمئن هستید؟",
|
||||
$"آیا از اجرای محاسبه دستی برای هفته {_currentWeekDisplayName}{packageInfo} مطمئن هستید؟",
|
||||
yesText: "بله", cancelText: "خیر");
|
||||
|
||||
if (confirmed == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await CommissionContract.TriggerWeeklyCalculationAsync(new TriggerWeeklyCalculationRequest()
|
||||
var req = new TriggerWeeklyCalculationRequest()
|
||||
{
|
||||
WeekDefinitionId = _currentWeekDefinitionId,
|
||||
ForceRecalculate = true,
|
||||
SkipBalances = false,
|
||||
SkipPayouts = 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
|
||||
await Task.Delay(2000);
|
||||
|
||||
@@ -19,4 +19,5 @@ public class WithdrawalRequestModel
|
||||
public string? BankTrackingCode { get; set; }
|
||||
public string? PaymentFailureReason { get; set; }
|
||||
public string? IbanNumber { get; set; }
|
||||
public string? PackageName { get; set; }
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
<div>
|
||||
<MudText Typo="Typo.h4" Class="mb-4"> پرداخت های کاربران</MudText>
|
||||
|
||||
@* ── G11b: راهنمای پرداختها (Q28) ── *@
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||
پاداشها بر اساس هر پکیج جداگانه محاسبه میشود. ستون «پکیج» نشاندهنده پکیج منبع پاداش است.
|
||||
</MudAlert>
|
||||
|
||||
<MudDataGrid T="UserCommissionPayoutModel"
|
||||
ServerData="@(new Func<GridState<UserCommissionPayoutModel>, Task<GridData<UserCommissionPayoutModel>>>(ServerReload))"
|
||||
Hover="true"
|
||||
@@ -36,12 +41,29 @@
|
||||
<MudSelectItem T="int?" Value="@((int?)4)">شکست خورده</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@((int?)5)">لغو شده</MudSelectItem>
|
||||
</MudSelect>
|
||||
<PackageSelect Label="پکیج"
|
||||
@bind-SelectedPackageId="_filterPackageId" />
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="ApplyFilter"
|
||||
StartIcon="@Icons.Material.Filled.Search">
|
||||
جستجو
|
||||
</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>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
@@ -64,6 +86,14 @@
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.PackageTitle" Title="پکیج">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Tertiary" Size="Size.Small">
|
||||
@(string.IsNullOrEmpty(context.Item.PackageTitle) ? "-" : context.Item.PackageTitle)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Property="x => x.BalancesEarned" Title="بالانسها">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Info" Size="Size.Small">
|
||||
|
||||
@@ -17,6 +17,8 @@ public partial class UserPayouts
|
||||
private long? _filterUserId;
|
||||
private long? _filterWeekDefinitionId;
|
||||
private int? _filterStatus;
|
||||
private long? _filterPackageId;
|
||||
private bool _isCreditingWallets;
|
||||
|
||||
private async Task<GridData<UserCommissionPayoutModel>> ServerReload(GridState<UserCommissionPayoutModel> state)
|
||||
{
|
||||
@@ -44,6 +46,11 @@ public partial class UserPayouts
|
||||
request.Status = _filterStatus.Value;
|
||||
}
|
||||
|
||||
if (_filterPackageId.HasValue && _filterPackageId.Value > 0)
|
||||
{
|
||||
request.PackageId = _filterPackageId.Value;
|
||||
}
|
||||
|
||||
var result = await CommissionContract.GetUserCommissionPayoutsAsync(request);
|
||||
|
||||
if (result?.Models != null && result.Models.Any())
|
||||
@@ -119,7 +126,7 @@ public partial class UserPayouts
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"تایید برداشت",
|
||||
$"آیا از تایید برداشت برای کاربر {payout.UserName} (مبلغ: {payout.TotalAmount:N0} ریال) مطمئن هستید؟",
|
||||
$"آیا از تایید برداشت برای کاربر {payout.UserName} (مبلغ: {payout.TotalAmount:N0} تومان) مطمئن هستید؟",
|
||||
yesText: "تایید", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
@@ -141,6 +148,50 @@ public partial class UserPayouts
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BulkCreditWallets()
|
||||
{
|
||||
if (!_filterWeekDefinitionId.HasValue)
|
||||
{
|
||||
Snackbar.Add("لطفاً ابتدا یک هفته انتخاب کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"واریز دستهجمعی به کیف پول",
|
||||
$"آیا از واریز کمیسیون همه پرداختهای «در انتظار» هفته انتخابشده به کیف پول کاربران مطمئن هستید؟ این عملیات برگشتپذیر نیست.",
|
||||
yesText: "بله، واریز کن", cancelText: "لغو");
|
||||
|
||||
if (confirmed != true)
|
||||
return;
|
||||
|
||||
_isCreditingWallets = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var result = await CommissionContract.BulkCreditPayoutsAsync(new BulkCreditPayoutsRequest
|
||||
{
|
||||
WeekDefinitionId = _filterWeekDefinitionId.Value
|
||||
});
|
||||
|
||||
if (result.CreditedCount == 0)
|
||||
Snackbar.Add("هیچ پرداخت در انتظاری برای این هفته یافت نشد", Severity.Info);
|
||||
else
|
||||
Snackbar.Add(result.Message, Severity.Success);
|
||||
|
||||
await ApplyFilter();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در واریز: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isCreditingWallets = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RejectWithdrawal(UserCommissionPayoutModel payout)
|
||||
{
|
||||
var reason = await DialogService.ShowMessageBox(
|
||||
|
||||
@@ -67,13 +67,13 @@
|
||||
<PropertyColumn Property="x => x.WeekDisplayName" Title="هفته" />
|
||||
<PropertyColumn Property="x => x.TotalPoolAmount" Title="مبلغ کل استخر" Format="N0">
|
||||
<CellTemplate>
|
||||
<MudText>@context.Item.TotalPoolAmount.ToString("N0") ریال</MudText>
|
||||
<MudText>@context.Item.TotalPoolAmount.ToString("N0") تومان</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.TotalBalances" Title="مجموع موجودیها" />
|
||||
<PropertyColumn Property="x => x.ValuePerBalance" Title="ارزش هر موجودی" Format="N0">
|
||||
<CellTemplate>
|
||||
<MudText>@context.Item.ValuePerBalance.ToString("N0") ریال</MudText>
|
||||
<MudText>@context.Item.ValuePerBalance.ToString("N0") تومان</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.IsCalculated" Title="وضعیت">
|
||||
@@ -128,7 +128,7 @@
|
||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">مجموع استخرها</MudText>
|
||||
<MudText Typo="Typo.h4">@_totalPoolSum.ToString("N0")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">ریال</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">تومان</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
@@ -149,7 +149,7 @@
|
||||
<MudPaper Elevation="0" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.h6" Color="Color.Info">میانگین ارزش</MudText>
|
||||
<MudText Typo="Typo.h4">@_averageValue.ToString("N0")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">ریال</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">تومان</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
Margin="Margin.Dense">
|
||||
<MudSelectItem T="int?" Value="@(null as int?)">همه وضعیتها</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="3">برداشت انجام شده</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="4">خطا در پرداخت بانکی</MudSelectItem>
|
||||
@@ -107,19 +107,19 @@
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Primary">مجموع مبلغ برداشتها</MudText>
|
||||
<MudText Typo="Typo.h5">@_summary.TotalAmount.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.h5">@_summary.TotalAmount.ToString("N0") تومان</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Success">مبلغ پرداخت شده</MudText>
|
||||
<MudText Typo="Typo.h5">@_summary.TotalPaid.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.h5">@_summary.TotalPaid.ToString("N0") تومان</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Warning">مبلغ در انتظار</MudText>
|
||||
<MudText Typo="Typo.h5">@_summary.TotalPending.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.h5">@_summary.TotalPending.ToString("N0") تومان</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
@@ -189,17 +189,17 @@
|
||||
<PropertyColumn Property="x => x.PendingCount" Title="در انتظار" />
|
||||
<PropertyColumn Property="x => x.TotalAmount" Title="مبلغ کل" Format="N0">
|
||||
<CellTemplate>
|
||||
@context.Item.TotalAmount.ToString("N0") ریال
|
||||
@context.Item.TotalAmount.ToString("N0") تومان
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.PaidAmount" Title="پرداخت شده" Format="N0">
|
||||
<CellTemplate>
|
||||
@context.Item.PaidAmount.ToString("N0") ریال
|
||||
@context.Item.PaidAmount.ToString("N0") تومان
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.PendingAmount" Title="در انتظار پرداخت" Format="N0">
|
||||
<CellTemplate>
|
||||
@context.Item.PendingAmount.ToString("N0") ریال
|
||||
@context.Item.PendingAmount.ToString("N0") تومان
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
</Columns>
|
||||
@@ -431,9 +431,9 @@
|
||||
|
||||
sb.AppendLine("خلاصه:");
|
||||
sb.AppendLine($"تعداد کل درخواستها: {_summary.TotalRequests}");
|
||||
sb.AppendLine($"مبلغ کل برداشتها: {_summary.TotalAmount:N0} ریال");
|
||||
sb.AppendLine($"مبلغ پرداخت شده: {_summary.TotalPaid:N0} ریال");
|
||||
sb.AppendLine($"مبلغ در انتظار: {_summary.TotalPending:N0} ریال");
|
||||
sb.AppendLine($"مبلغ کل برداشتها: {_summary.TotalAmount:N0} تومان");
|
||||
sb.AppendLine($"مبلغ پرداخت شده: {_summary.TotalPaid:N0} تومان");
|
||||
sb.AppendLine($"مبلغ در انتظار: {_summary.TotalPending:N0} تومان");
|
||||
sb.AppendLine($"تعداد کاربران یکتا: {_summary.UniqueUsers}");
|
||||
sb.AppendLine($"نرخ موفقیت: {_summary.SuccessRate:N2}%");
|
||||
sb.AppendLine();
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
|
||||
<CellTemplate>
|
||||
<MudText Typo="Typo.body2" Color="Color.Primary">
|
||||
<strong>@context.Item.Amount.ToString("N0") ریال</strong>
|
||||
<strong>@context.Item.Amount.ToString("N0") تومان</strong>
|
||||
</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
@@ -87,7 +87,8 @@ public partial class WithdrawalRequests
|
||||
BankReferenceId = grpcModel.BankReferenceId,
|
||||
BankTrackingCode = grpcModel.BankTrackingCode,
|
||||
PaymentFailureReason = grpcModel.PaymentFailureReason,
|
||||
IbanNumber = grpcModel.IbanNumber
|
||||
IbanNumber = grpcModel.IbanNumber,
|
||||
PackageName = grpcModel.PackageName
|
||||
};
|
||||
}
|
||||
|
||||
@@ -163,7 +164,7 @@ public partial class WithdrawalRequests
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"تایید درخواست",
|
||||
$"آیا از تایید درخواست برداشت {request.Amount:N0} ریال برای {request.UserName} مطمئن هستید؟",
|
||||
$"آیا از تایید درخواست برداشت {request.Amount:N0} تومان برای {request.UserName} مطمئن هستید؟",
|
||||
yesText: "تایید", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
@@ -222,7 +223,7 @@ public partial class WithdrawalRequests
|
||||
{
|
||||
var confirmed = await DialogService.ShowMessageBox(
|
||||
"پردازش پرداخت",
|
||||
$"آیا پرداخت {request.Amount:N0} ریال به {request.UserName} انجام شده است؟",
|
||||
$"آیا پرداخت {request.Amount:N0} تومان به {request.UserName} انجام شده است؟",
|
||||
yesText: "بله، پردازش شد", cancelText: "لغو");
|
||||
|
||||
if (confirmed == true)
|
||||
@@ -269,7 +270,7 @@ public partial class WithdrawalRequests
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("شناسه,کاربر,نام کاربر,مبلغ,وضعیت,روش,شماره شبا,تاریخ درخواست,تاریخ پردازش,شماره مرجع بانکی");
|
||||
sb.AppendLine("شناسه,کاربر,نام کاربر,پکیج,مبلغ,وضعیت,روش,شماره شبا,تاریخ درخواست,تاریخ پردازش,شماره مرجع بانکی");
|
||||
|
||||
foreach (var r in result.Models)
|
||||
{
|
||||
@@ -278,6 +279,7 @@ public partial class WithdrawalRequests
|
||||
mapped.Id,
|
||||
mapped.UserId,
|
||||
EscapeCsv(mapped.UserName),
|
||||
EscapeCsv(mapped.PackageName ?? "-"),
|
||||
mapped.Amount,
|
||||
EscapeCsv(GetStatusText(mapped.Status)),
|
||||
EscapeCsv(GetMethodText(mapped.Method)),
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">مجموع فروش (۷ روز)</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Primary">
|
||||
@_stats.TotalSalesLast7Days.ToString("N0") ریال
|
||||
@_stats.TotalSalesLast7Days.ToString("N0") تومان
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
امروز: @_stats.TodaySales.ToString("N0") ریال
|
||||
امروز: @_stats.TodaySales.ToString("N0") تومان
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
@@ -43,7 +43,7 @@
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">میانگین مبلغ هر سفارش</MudText>
|
||||
<MudText Typo="Typo.h5">
|
||||
@_stats.AverageOrderAmount.ToString("N0") ریال
|
||||
@_stats.AverageOrderAmount.ToString("N0") تومان
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Processing">در حال آمادهسازی</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Delivered">تحویل داده شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.ReadyForOfficePickup">آماده تحویل در دفتر</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
||||
</MudSelect>
|
||||
@@ -55,6 +56,15 @@
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
@if (Model.Status == OrderStatus.ReadyForOfficePickup)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudAlert Severity="Severity.Info">
|
||||
<MudText>سفارش برای تحویل حضوری در دفتر آماده است.</MudText>
|
||||
</MudAlert>
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
@if (Model.Status == OrderStatus.Shipped)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
@@ -126,8 +136,9 @@
|
||||
OrderStatus.Processing => Color.Primary,
|
||||
OrderStatus.Shipped => Color.Secondary,
|
||||
OrderStatus.Delivered => Color.Success,
|
||||
OrderStatus.Cancelled => Color.Error,
|
||||
OrderStatus.Returned => Color.Dark,
|
||||
OrderStatus.ReadyForOfficePickup => Color.Primary,
|
||||
OrderStatus.Cancelled => Color.Dark,
|
||||
OrderStatus.Returned => Color.Error,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
@@ -141,6 +152,7 @@
|
||||
OrderStatus.Processing => "در حال آمادهسازی",
|
||||
OrderStatus.Shipped => "ارسال شده",
|
||||
OrderStatus.Delivered => "تحویل داده شده",
|
||||
OrderStatus.ReadyForOfficePickup => "آماده تحویل در دفتر",
|
||||
OrderStatus.Cancelled => "لغو شده",
|
||||
OrderStatus.Returned => "مرجوع شده",
|
||||
_ => "نامشخص"
|
||||
|
||||
@@ -1,36 +1,72 @@
|
||||
@using BackOffice.Services.DiscountOrder
|
||||
@using BackOffice.Common.BaseComponents
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ShoppingCart" Class="ml-2" />
|
||||
جزئیات سفارش #@Order.OrderNumber
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<!-- اطلاعات کاربر -->
|
||||
<MudItem xs="12">
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudText Typo="Typo.h6" GutterBottom="true">اطلاعات سفارش</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="6">
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">شماره سفارش:</MudText>
|
||||
<MudText Typo="Typo.body1"><strong>@Order.OrderNumber</strong></MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">شناسه کاربر:</MudText>
|
||||
<MudText Typo="Typo.body1">@Order.UserId</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h6">جزئیات سفارش شماره @(string.IsNullOrWhiteSpace(Order.OrderNumber) ? Order.Id.ToString() : Order.OrderNumber)</MudText>
|
||||
<MudText Typo="Typo.body2">
|
||||
کاربر:
|
||||
@if (!string.IsNullOrWhiteSpace(Order.UserFullName))
|
||||
{
|
||||
@Order.UserFullName
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>#@Order.UserId</text>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(DisplayMobile))
|
||||
{
|
||||
<text> (@DisplayMobile)</text>
|
||||
}
|
||||
</MudText>
|
||||
|
||||
<!-- Timeline وضعیت -->
|
||||
<MudItem xs="12">
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudText Typo="Typo.subtitle2" GutterBottom="true">Timeline وضعیت</MudText>
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center" Justify="Justify.Center">
|
||||
<MudPaper Class="pa-3">
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.subtitle2">خلاصه سفارش</MudText>
|
||||
<MudDivider />
|
||||
<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)
|
||||
{
|
||||
<MudStack Spacing="0" AlignItems="AlignItems.Center">
|
||||
@@ -41,144 +77,10 @@
|
||||
</MudStack>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- وضعیت سفارش -->
|
||||
<MudItem xs="12" sm="6">
|
||||
<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">
|
||||
<MudDivider Class="my-2" />
|
||||
<MudText Typo="Typo.body2">
|
||||
وضعیت ارسال:
|
||||
<MudSelect T="OrderStatus"
|
||||
Label="وضعیت ارسال"
|
||||
Dense="true"
|
||||
@@ -189,18 +91,16 @@
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Processing">در حال آمادهسازی</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Delivered">تحویل داده شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.ReadyForOfficePickup">آماده تحویل در دفتر</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus" Value="@OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6">
|
||||
</MudText>
|
||||
<MudTextField T="string"
|
||||
Label="کد رهگیری"
|
||||
Variant="Variant.Outlined"
|
||||
Dense="true"
|
||||
@bind-Value="_trackingCode" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string"
|
||||
Label="یادداشت ادمین"
|
||||
Variant="Variant.Outlined"
|
||||
@@ -208,51 +108,124 @@
|
||||
Lines="3"
|
||||
HelperText="دلیل تغییر وضعیت یا توضیحات اضافی"
|
||||
@bind-Value="_adminNotes" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@if (_newStatus == OrderStatus.Cancelled || _newStatus == OrderStatus.Returned)
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Class="mt-2">
|
||||
<MudAlert Severity="Severity.Warning" Dense="true">
|
||||
توجه: در صورت لغو یا مرجوعی سفارش، موجودی محصولات به انبار بازگردانده میشود.
|
||||
</MudAlert>
|
||||
}
|
||||
@if (_newStatus == OrderStatus.ReadyForOfficePickup)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Dense="true">
|
||||
سفارش برای تحویل حضوری در دفتر آماده است.
|
||||
</MudAlert>
|
||||
}
|
||||
@if (_newStatus == OrderStatus.Shipped)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Class="mt-2">
|
||||
پس از تغییر وضعیت به "ارسال شده"، اطلاعرسانی به کاربر ارسال خواهد شد.
|
||||
<MudAlert Severity="Severity.Info" Dense="true">
|
||||
پس از تغییر وضعیت به «ارسال شده»، اطلاعرسانی به کاربر ارسال خواهد شد.
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- یادداشتها -->
|
||||
@if (!string.IsNullOrEmpty(Order.Notes) || !string.IsNullOrEmpty(Order.AdminNotes))
|
||||
<MudPaper Class="pa-3">
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.subtitle2">آدرس تحویل</MudText>
|
||||
<MudDivider />
|
||||
@if (Order.Address != null && !string.IsNullOrWhiteSpace(Order.Address.Address))
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
@if (!string.IsNullOrWhiteSpace(Order.Address.Title))
|
||||
{
|
||||
<MudText Typo="Typo.body2"><strong>@Order.Address.Title</strong></MudText>
|
||||
}
|
||||
<MudText Typo="Typo.body2">@Order.Address.Address</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(Order.Address.PostalCode))
|
||||
{
|
||||
<MudText Typo="Typo.body2">کد پستی: @Order.Address.PostalCode</MudText>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(DisplayMobile))
|
||||
{
|
||||
<MudText Typo="Typo.body2">تلفن سفارشدهنده: @DisplayMobile</MudText>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.caption">آدرسی ثبت نشده است.</MudText>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="pa-3">
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.subtitle2">اقلام فاکتور</MudText>
|
||||
<MudDivider />
|
||||
@if (Order.Items.Count > 0)
|
||||
{
|
||||
<MudTable Items="Order.Items" Dense="true">
|
||||
<HeaderContent>
|
||||
<MudTh>تصویر</MudTh>
|
||||
<MudTh>محصول</MudTh>
|
||||
<MudTh>قیمت واحد</MudTh>
|
||||
<MudTh>تعداد</MudTh>
|
||||
<MudTh>تخفیف</MudTh>
|
||||
<MudTh>قیمت نهایی</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>
|
||||
@if (!string.IsNullOrWhiteSpace(context.ThumbnailPath))
|
||||
{
|
||||
<Image Src="@context.ThumbnailPath" Height="50" Width="50" />
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@context.ProductTitle</MudTd>
|
||||
<MudTd>@context.UnitPrice.ToString("N0")</MudTd>
|
||||
<MudTd>@context.Count</MudTd>
|
||||
<MudTd>
|
||||
@if (context.MaxDiscountPercent > 0)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Success">@context.MaxDiscountPercent%</MudChip>
|
||||
}
|
||||
else if (context.DiscountAmount > 0)
|
||||
{
|
||||
@context.DiscountAmount.ToString("N0")
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>-</text>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@context.FinalPrice.ToString("N0")</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.caption">آیتمی برای این سفارش ثبت نشده است.</MudText>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Order.Notes))
|
||||
{
|
||||
<MudText Typo="Typo.h6" GutterBottom="true">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Comment" Size="Size.Small" />
|
||||
یادداشت کاربر
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mb-4">@Order.Notes</MudText>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Order.AdminNotes))
|
||||
{
|
||||
<MudText Typo="Typo.h6" GutterBottom="true">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Note" Size="Size.Small" />
|
||||
یادداشت ادمین
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1">@Order.AdminNotes</MudText>
|
||||
}
|
||||
<MudPaper Class="pa-3">
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.subtitle2">یادداشت کاربر</MudText>
|
||||
<MudDivider />
|
||||
<MudText Typo="Typo.body2">@Order.Notes</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using BackOffice.Common.Utilities;
|
||||
using BackOffice.Services.DiscountOrder;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.DiscountShop.Components;
|
||||
@@ -8,6 +10,7 @@ public partial class OrderDetailsDialog
|
||||
{
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
[Inject] public IDiscountOrderService OrderService { get; set; } = default!;
|
||||
[Inject] private IJSRuntime JsRuntime { get; set; } = default!;
|
||||
|
||||
[Parameter] public DiscountOrderDetailsDto Order { get; set; } = null!;
|
||||
|
||||
@@ -17,6 +20,9 @@ public partial class OrderDetailsDialog
|
||||
private bool _isSaving;
|
||||
private List<TimelineStep> _timelineSteps = new();
|
||||
|
||||
private string? DisplayMobile =>
|
||||
!string.IsNullOrWhiteSpace(Order.UserMobile) ? Order.UserMobile : Order.Address?.Phone;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_newStatus = Order.Status;
|
||||
@@ -127,34 +133,69 @@ public partial class OrderDetailsDialog
|
||||
|
||||
private void Close() => MudDialog.Close();
|
||||
|
||||
private Color GetStatusColor(OrderStatus status)
|
||||
private Color GetPaymentStatusColor(int status) => status switch
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
OrderStatus.Pending => Color.Warning,
|
||||
OrderStatus.Paid => Color.Info,
|
||||
OrderStatus.Processing => Color.Primary,
|
||||
OrderStatus.Shipped => Color.Secondary,
|
||||
OrderStatus.Delivered => Color.Success,
|
||||
OrderStatus.Cancelled => Color.Error,
|
||||
OrderStatus.Returned => Color.Dark,
|
||||
0 => Color.Warning,
|
||||
1 => Color.Success,
|
||||
2 => Color.Error,
|
||||
3 => Color.Info,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
|
||||
private string GetStatusText(OrderStatus status)
|
||||
private string GetPaymentStatusText(int status) => status switch
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
OrderStatus.Pending => "در انتظار پرداخت",
|
||||
OrderStatus.Paid => "پرداخت شده",
|
||||
OrderStatus.Processing => "در حال آمادهسازی",
|
||||
OrderStatus.Shipped => "ارسال شده",
|
||||
OrderStatus.Delivered => "تحویل داده شده",
|
||||
OrderStatus.Cancelled => "لغو شده",
|
||||
OrderStatus.Returned => "مرجوع شده",
|
||||
0 => "در انتظار پرداخت",
|
||||
1 => "پرداخت شده",
|
||||
2 => "ناموفق",
|
||||
3 => "بازپرداخت شده",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
|
||||
private string GetPaymentStatusIcon(int status) => status switch
|
||||
{
|
||||
1 => Icons.Material.Filled.CheckCircle,
|
||||
2 => Icons.Material.Filled.Cancel,
|
||||
3 => Icons.Material.Filled.Replay,
|
||||
_ => Icons.Material.Filled.Schedule
|
||||
};
|
||||
|
||||
private async Task PrintInvoiceAsync()
|
||||
{
|
||||
var lines = Order.Items.Select(i => new OrderInvoicePrint.InvoiceLine
|
||||
{
|
||||
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
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
Format="N0"
|
||||
Immediate="true"
|
||||
Disabled="_loading"
|
||||
Label="قیمت (ریال) *"
|
||||
Label="قیمت (تومان) *"
|
||||
Required="true"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" />
|
||||
|
||||
@@ -20,14 +20,14 @@
|
||||
<MudList T="string" Dense="true">
|
||||
<MudListItem T="string"
|
||||
OnClick="@(() => OnTreeNodeSelected(null))"
|
||||
Selected="@(_selectedParentId == null)">
|
||||
Class="@(_selectedParentId == null ? "mud-selected-item" : "")">
|
||||
<MudText Typo="Typo.body2">همه دستهبندیها</MudText>
|
||||
</MudListItem>
|
||||
@foreach (var node in _treeNodes)
|
||||
{
|
||||
<MudListItem T="string"
|
||||
OnClick="@(() => OnTreeNodeSelected(node.Id))"
|
||||
Selected="@(_selectedParentId == node.Id)">
|
||||
Class="@(_selectedParentId == node.Id ? "mud-selected-item" : "")">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<div style="width:@(node.Depth * 16)px;"></div>
|
||||
<MudText Typo="Typo.body2">@node.Title</MudText>
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
@bind-Value="@_trackingCodeFilter" Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
||||
</MudItem>
|
||||
<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"/>
|
||||
</MudItem>
|
||||
<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"/>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
@@ -49,6 +49,8 @@
|
||||
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Delivered">تحویل داده شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.ReadyForOfficePickup">آماده تحویل در دفتر</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus?" Value="@OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</Filters>
|
||||
@@ -97,20 +99,20 @@
|
||||
|
||||
<TemplateColumn Title="مبلغ کل">
|
||||
<CellTemplate>
|
||||
<MudText>@context.Item.TotalPrice.ToString("N0") ریال</MudText>
|
||||
<MudText>@context.Item.TotalPrice.ToString("N0") تومان</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
|
||||
<TemplateColumn Title="تخفیف کیفپول">
|
||||
<TemplateColumn Title="کیف پول اعتباری">
|
||||
<CellTemplate>
|
||||
<MudText Color="Color.Success">@context.Item.DiscountBalanceUsed.ToString("N0") ریال</MudText>
|
||||
<MudText Color="Color.Success">@context.Item.DiscountBalanceUsed.ToString("N0") تومان</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
|
||||
<TemplateColumn Title="پرداخت درگاه">
|
||||
<CellTemplate>
|
||||
<MudText Typo="Typo.body2">
|
||||
<strong>@context.Item.GatewayAmount.ToString("N0") ریال</strong>
|
||||
<strong>@context.Item.GatewayAmount.ToString("N0") تومان</strong>
|
||||
</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text;
|
||||
using BackOffice.Common.BaseComponents;
|
||||
using BackOffice.Common.Utilities;
|
||||
using BackOffice.Services.DiscountOrder;
|
||||
using BackOffice.Pages.DiscountShop.Components;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -74,6 +75,15 @@ public partial class DiscountOrdersMainPage
|
||||
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 options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large, FullWidth = true };
|
||||
var dialog = await DialogService.ShowAsync<OrderDetailsDialog>("جزئیات سفارش", parameters, options);
|
||||
@@ -178,8 +188,9 @@ public partial class DiscountOrdersMainPage
|
||||
OrderStatus.Processing => Color.Primary,
|
||||
OrderStatus.Shipped => Color.Secondary,
|
||||
OrderStatus.Delivered => Color.Success,
|
||||
OrderStatus.Cancelled => Color.Error,
|
||||
OrderStatus.Returned => Color.Dark,
|
||||
OrderStatus.Cancelled => Color.Dark,
|
||||
OrderStatus.ReadyForOfficePickup => Color.Primary,
|
||||
OrderStatus.Returned => Color.Error,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
@@ -194,6 +205,7 @@ public partial class DiscountOrdersMainPage
|
||||
OrderStatus.Shipped => "ارسال شده",
|
||||
OrderStatus.Delivered => "تحویل داده شده",
|
||||
OrderStatus.Cancelled => "لغو شده",
|
||||
OrderStatus.ReadyForOfficePickup => "آماده تحویل در دفتر",
|
||||
OrderStatus.Returned => "مرجوع شده",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
@@ -233,44 +245,36 @@ public partial class DiscountOrdersMainPage
|
||||
|
||||
try
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("شناسه,نام کاربر,موبایل,مبلغ کل,تخفیف کیفپول,پرداخت درگاه,تعداد آیتم,وضعیت پرداخت,وضعیت ارسال,تاریخ پرداخت,تاریخ ثبت,آدرس");
|
||||
|
||||
foreach (var o in _orders)
|
||||
var headers = new[]
|
||||
{
|
||||
"شناسه", "نام مشتری", "موبایل", "مبلغ کل", "کیف پول اعتباری", "پرداخت درگاه",
|
||||
"تعداد آیتم", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت", "تاریخ ثبت", "آدرس"
|
||||
};
|
||||
|
||||
var rows = _orders.Select(o => new object?[]
|
||||
{
|
||||
sb.AppendLine(string.Join(",",
|
||||
o.Id,
|
||||
EscapeCsv(o.UserFullName),
|
||||
EscapeCsv(o.UserMobile),
|
||||
o.TotalPrice.ToString("N0"),
|
||||
o.DiscountBalanceUsed.ToString("N0"),
|
||||
o.GatewayAmount.ToString("N0"),
|
||||
CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
|
||||
o.UserMobile ?? "",
|
||||
o.TotalPrice,
|
||||
o.DiscountBalanceUsed,
|
||||
o.GatewayAmount,
|
||||
o.ItemsCount,
|
||||
EscapeCsv(GetPaymentStatusText(o.PaymentStatusValue)),
|
||||
EscapeCsv(GetStatusText(o.Status)),
|
||||
GetPaymentStatusText(o.PaymentStatusValue),
|
||||
GetStatusText(o.Status),
|
||||
o.PaymentDate?.MiladiToJalaliWithTime() ?? "-",
|
||||
o.Created?.MiladiToJalaliWithTime() ?? "-",
|
||||
EscapeCsv(o.ShippingAddress ?? "-")));
|
||||
}
|
||||
o.ShippingAddress ?? "-"
|
||||
});
|
||||
|
||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
||||
var base64 = Convert.ToBase64String(bytes);
|
||||
var filename = $"discount-orders-{DateTime.Now:yyyyMMddHHmmss}.csv";
|
||||
|
||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
|
||||
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
|
||||
var filename = $"discount-orders-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||
CsvExportHelper.ToExcelXmlBase64("سفارشهای اعتباری", headers, rows));
|
||||
Snackbar.Add("خروجی Excel آماده شد.", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private static string EscapeCsv(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return "";
|
||||
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
|
||||
return $"\"{value.Replace("\"", "\"\"")}\"";
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
<TemplateColumn Title="قیمت" CellStyle="text-wrap: nowrap;">
|
||||
<CellTemplate>
|
||||
<MudText>@context.Item.Price.ToString("N0") ریال</MudText>
|
||||
<MudText>@context.Item.Price.ToString("N0") تومان</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Processing">در حال پردازش</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Shipped">ارسال شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Delivered">تحویل شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.ReadyForOfficePickup">آماده تحویل در دفتر</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Cancelled">لغو شده</MudSelectItem>
|
||||
<MudSelectItem T="OrderStatus?" Value="OrderStatus.Returned">مرجوع شده</MudSelectItem>
|
||||
</MudSelect>
|
||||
@@ -113,19 +114,19 @@
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Success">مجموع فروش (مبلغ نهایی)</MudText>
|
||||
<MudText Typo="Typo.h5">@_totalSales.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.h5">@_totalSales.ToString("N0") تومان</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Warning">مجموع تخفیف</MudText>
|
||||
<MudText Typo="Typo.h5">@_totalDiscount.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.h5">@_totalDiscount.ToString("N0") تومان</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Info">میانگین مبلغ سفارش</MudText>
|
||||
<MudText Typo="Typo.h5">@_averageOrder.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.h5">@_averageOrder.ToString("N0") تومان</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
@@ -242,12 +243,12 @@
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="مبلغ نهایی">
|
||||
<CellTemplate>
|
||||
@context.Item.GatewayAmount.ToString("N0") ریال
|
||||
@context.Item.GatewayAmount.ToString("N0") تومان
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="تخفیف">
|
||||
<CellTemplate>
|
||||
@context.Item.DiscountBalanceUsed.ToString("N0") ریال
|
||||
@context.Item.DiscountBalanceUsed.ToString("N0") تومان
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="وضعیت">
|
||||
@@ -498,7 +499,8 @@
|
||||
OrderStatus.Processing => Color.Info,
|
||||
OrderStatus.Shipped => Color.Info,
|
||||
OrderStatus.Delivered => Color.Success,
|
||||
OrderStatus.Cancelled => Color.Error,
|
||||
OrderStatus.ReadyForOfficePickup => Color.Primary,
|
||||
OrderStatus.Cancelled => Color.Dark,
|
||||
OrderStatus.Returned => Color.Error,
|
||||
_ => Color.Default
|
||||
};
|
||||
@@ -513,6 +515,7 @@
|
||||
OrderStatus.Processing => "در حال پردازش",
|
||||
OrderStatus.Shipped => "ارسال شده",
|
||||
OrderStatus.Delivered => "تحویل شده",
|
||||
OrderStatus.ReadyForOfficePickup => "آماده تحویل در دفتر",
|
||||
OrderStatus.Cancelled => "لغو شده",
|
||||
OrderStatus.Returned => "مرجوع شده",
|
||||
_ => "نامشخص"
|
||||
@@ -527,28 +530,31 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("شناسه,نام کاربر,موبایل,تاریخ ثبت,تعداد آیتم,مبلغ نهایی,تخفیف,وضعیت");
|
||||
|
||||
foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created))
|
||||
var headers = new[]
|
||||
{
|
||||
"شناسه", "نام مشتری", "موبایل", "تاریخ ثبت", "تعداد آیتم", "مبلغ نهایی", "تخفیف", "وضعیت"
|
||||
};
|
||||
|
||||
var rows = _orders
|
||||
.OrderBy(o => o.Created ?? o.PaymentDate ?? DateTime.MaxValue)
|
||||
.Select(o => new object?[]
|
||||
{
|
||||
sb.AppendLine(string.Join(",",
|
||||
o.Id,
|
||||
EscapeCsv(o.UserFullName),
|
||||
EscapeCsv(o.UserMobile),
|
||||
o.Created?.MiladiToJalaliWithTime() ?? "-",
|
||||
CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
|
||||
o.UserMobile ?? "",
|
||||
o.Created?.MiladiToJalaliWithTime()
|
||||
?? o.PaymentDate?.MiladiToJalaliWithTime()
|
||||
?? "-",
|
||||
o.ItemsCount,
|
||||
o.GatewayAmount,
|
||||
o.DiscountBalanceUsed,
|
||||
GetStatusText(o.Status)));
|
||||
}
|
||||
GetStatusText(o.Status)
|
||||
});
|
||||
|
||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
||||
var base64 = Convert.ToBase64String(bytes);
|
||||
var filename = $"discount-sales-{DateTime.Now:yyyyMMddHHmmss}.csv";
|
||||
|
||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
|
||||
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
|
||||
var filename = $"discount-sales-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||
CsvExportHelper.ToExcelXmlBase64("گزارش فروش اعتباری", headers, rows));
|
||||
Snackbar.Add("خروجی Excel آماده شد.", Severity.Success);
|
||||
}
|
||||
|
||||
private async Task ExportToPdf()
|
||||
@@ -566,18 +572,18 @@
|
||||
|
||||
sb.AppendLine("خلاصه:");
|
||||
sb.AppendLine($"تعداد سفارشها: {_totalOrders}");
|
||||
sb.AppendLine($"مجموع فروش (مبلغ نهایی): {_totalSales:N0} ریال");
|
||||
sb.AppendLine($"مجموع تخفیف: {_totalDiscount:N0} ریال");
|
||||
sb.AppendLine($"میانگین مبلغ سفارش: {_averageOrder:N0} ریال");
|
||||
sb.AppendLine($"مجموع فروش (مبلغ نهایی): {_totalSales:N0} تومان");
|
||||
sb.AppendLine($"مجموع تخفیف: {_totalDiscount:N0} تومان");
|
||||
sb.AppendLine($"میانگین مبلغ سفارش: {_averageOrder:N0} تومان");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("جزئیات سفارشها:");
|
||||
sb.AppendLine("شناسه | نام کاربر | تاریخ | تعداد آیتم | مبلغ نهایی | تخفیف | وضعیت");
|
||||
sb.AppendLine("شناسه | نام مشتری | تاریخ | تعداد آیتم | مبلغ نهایی | تخفیف | وضعیت");
|
||||
|
||||
foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created))
|
||||
{
|
||||
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();
|
||||
@@ -587,14 +593,4 @@
|
||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
|
||||
Snackbar.Add("خروجی PDF (نسخه متنی) آماده شد.", Severity.Success);
|
||||
}
|
||||
|
||||
private static string EscapeCsv(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return "";
|
||||
|
||||
var needsQuotes = value.Contains(',') || value.Contains('"') || value.Contains('\n');
|
||||
var escaped = value.Replace("\"", "\"\"");
|
||||
return needsQuotes ? $"\"{escaped}\"" : escaped;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">مبلغ کل استخر</MudText>
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary">
|
||||
@(_currentPool?.TotalPoolAmount.ToString("N0") ?? "0") ریال
|
||||
@(_currentPool?.TotalPoolAmount.ToString("N0") ?? "0") تومان
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
|
||||
@@ -33,10 +33,8 @@
|
||||
Min="0" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudNumericField @bind-Value="_maxBalance"
|
||||
Label="حداکثر موجودی"
|
||||
Variant="Variant.Outlined"
|
||||
Min="0" />
|
||||
<PackageSelect Label="پکیج"
|
||||
@bind-SelectedPackageId="_filterPackageId" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2" Class="d-flex align-center">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
@@ -72,6 +70,14 @@
|
||||
<PropertyColumn Property="x => x.UserFullName" Title="نام کاربر" />
|
||||
<PropertyColumn Property="x => x.WeekDisplayName" Title="هفته" />
|
||||
|
||||
<PropertyColumn Property="x => x.PackageTitle" Title="پکیج">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Color="Color.Tertiary" Size="Size.Small">
|
||||
@(string.IsNullOrEmpty(context.Item.PackageTitle) ? "-" : context.Item.PackageTitle)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<TemplateColumn Title="چپ (جدید + قبلی = مجموع)">
|
||||
<CellTemplate>
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
@@ -129,7 +135,7 @@
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.PoolContribution" Title="سهم از استخر">
|
||||
<CellTemplate>
|
||||
@context.Item.PoolContribution.ToString("N0") ریال
|
||||
@context.Item.PoolContribution.ToString("N0") تومان
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.IsExpired" Title="وضعیت">
|
||||
@@ -193,6 +199,7 @@
|
||||
private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
||||
private long? _filterUserId = null;
|
||||
private long? _filterWeekDefinitionId = null;
|
||||
private long? _filterPackageId = null;
|
||||
private int? _minBalance = null;
|
||||
private int? _maxBalance = null;
|
||||
private bool _onlyActive = false;
|
||||
@@ -222,6 +229,11 @@ private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
||||
request.WeekDefinitionId = _filterWeekDefinitionId.Value;
|
||||
}
|
||||
|
||||
if (_filterPackageId.HasValue && _filterPackageId.Value > 0)
|
||||
{
|
||||
request.PackageId = _filterPackageId.Value;
|
||||
}
|
||||
|
||||
var response = await CommissionClient.GetUserWeeklyBalancesAsync(request);
|
||||
|
||||
var items = response.Models.Select(b => new UserWeeklyBalanceModel
|
||||
@@ -238,7 +250,8 @@ private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
||||
MatchedBalance = b.TotalBalances,
|
||||
PoolContribution = b.WeeklyPoolContribution,
|
||||
CalculatedAt = b.CalculatedAt?.ToDateTime(),
|
||||
IsExpired = b.IsExpired
|
||||
IsExpired = b.IsExpired,
|
||||
PackageTitle = b.PackageTitle
|
||||
}).ToList();
|
||||
|
||||
// Apply balance range filter if specified
|
||||
@@ -308,5 +321,6 @@ private MudDataGrid <UserWeeklyBalanceModel> _grid { get; set; }
|
||||
public long PoolContribution { get; set; }
|
||||
public DateTime? CalculatedAt { get; set; }
|
||||
public bool IsExpired { get; set; }
|
||||
public string PackageTitle { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
@using CMSMicroservice.Protobuf.Protos.NetworkMembership
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
<MudIcon Icon="@Icons.Material.Filled.SwapHoriz" Class="ml-1" />
|
||||
تغییر والد در شبکه
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudAlert Severity="Severity.Warning" Dense="true" Class="mb-4">
|
||||
⚠️ این عملیات حساس است و ساختار درخت شبکه را تغییر میدهد.
|
||||
تاریخچه تغییر ثبت خواهد شد.
|
||||
</MudAlert>
|
||||
|
||||
<MudForm @ref="_form">
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField T="string"
|
||||
Label="کاربر فعلی"
|
||||
Value="@UserDisplayName"
|
||||
ReadOnly="true"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Person" />
|
||||
|
||||
<MudNumericField T="long"
|
||||
@bind-Value="_newParentId"
|
||||
Label="شناسه والد جدید"
|
||||
Required="true"
|
||||
RequiredError="شناسه والد جدید الزامی است"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.PersonSearch"
|
||||
Min="1" />
|
||||
|
||||
<MudSelect T="int"
|
||||
@bind-Value="_newLeg"
|
||||
Label="ساق جدید"
|
||||
Required="true"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.AccountTree">
|
||||
<MudSelectItem T="int" Value="0">چپ (تیم اول)</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="1">راست (تیم دوم)</MudSelectItem>
|
||||
</MudSelect>
|
||||
|
||||
<MudTextField T="string"
|
||||
@bind-Value="_reason"
|
||||
Label="دلیل تغییر"
|
||||
Required="true"
|
||||
RequiredError="دلیل تغییر الزامی است"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="3"
|
||||
Placeholder="دلیل جابجایی کاربر در شبکه را وارد کنید..."
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Description" />
|
||||
</MudStack>
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel" Color="Color.Default">انصراف</MudButton>
|
||||
<MudButton OnClick="Submit"
|
||||
Color="Color.Error"
|
||||
Variant="Variant.Filled"
|
||||
Disabled="_isSubmitting"
|
||||
StartIcon="@Icons.Material.Filled.SwapHoriz">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="ml-2" />
|
||||
<span>در حال انجام...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>تغییر والد</span>
|
||||
}
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,66 @@
|
||||
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.Network.Components;
|
||||
|
||||
public partial class ChangeParentDialog
|
||||
{
|
||||
[Parameter] public long UserId { get; set; }
|
||||
[Parameter] public string UserDisplayName { get; set; } = "";
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Inject] public NetworkMembershipContract.NetworkMembershipContractClient NetworkContract { get; set; } = default!;
|
||||
|
||||
private MudForm _form = default!;
|
||||
private long _newParentId;
|
||||
private int _newLeg;
|
||||
private string _reason = "";
|
||||
private bool _isSubmitting;
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
await _form.Validate();
|
||||
if (!_form.IsValid)
|
||||
return;
|
||||
|
||||
if (_newParentId == UserId)
|
||||
{
|
||||
Snackbar.Add("والد جدید نمیتواند خود کاربر باشد.", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_isSubmitting = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var request = new ChangeNetworkParentRequest
|
||||
{
|
||||
UserId = UserId,
|
||||
NewParentId = _newParentId,
|
||||
NewLeg = _newLeg,
|
||||
Reason = _reason
|
||||
};
|
||||
|
||||
await NetworkContract.ChangeNetworkParentAsync(request);
|
||||
Snackbar.Add("والد کاربر با موفقیت تغییر کرد.", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
catch (Grpc.Core.RpcException ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Status.Detail}", Severity.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در تغییر والد: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSubmitting = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
}
|
||||
@@ -169,6 +169,42 @@
|
||||
</CellTemplate>
|
||||
</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">
|
||||
<CellTemplate>
|
||||
<MudButtonGroup Size="Size.Small" Variant="Variant.Text">
|
||||
@@ -337,7 +373,9 @@
|
||||
isClubActive = n.IsClubActive,
|
||||
isActivatedInTargetWeek = n.IsActivatedInTargetWeek,
|
||||
activationWeekDefinitionId = n.ActivationWeekDefinitionId,
|
||||
clubActivatedAt = n.ClubActivatedAt?.ToDateTime().ToLocalTime().MiladiToJalali() ?? ""
|
||||
clubActivatedAt = n.ClubActivatedAt?.ToDateTime().ToLocalTime().MiladiToJalali() ?? "",
|
||||
isNewActivation = n.IsNewActivation,
|
||||
packageName = n.PackageName ?? ""
|
||||
}).ToArray();
|
||||
|
||||
var options = new { filterWeek = _activationWeekFilter };
|
||||
|
||||
@@ -254,7 +254,7 @@
|
||||
<td><strong>کل کمیسیون کسب شده:</strong></td>
|
||||
<td>
|
||||
<MudText Typo="Typo.body1" Color="Color.Primary">
|
||||
<strong>@_userInfo.TotalEarnedCommission.ToString("N0") ریال</strong>
|
||||
<strong>@_userInfo.TotalEarnedCommission.ToString("N0") تومان</strong>
|
||||
</MudText>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -262,7 +262,7 @@
|
||||
<td><strong>کمیسیون پرداخت شده:</strong></td>
|
||||
<td>
|
||||
<MudText Typo="Typo.body1" Color="Color.Success">
|
||||
@_userInfo.TotalPaidCommission.ToString("N0") ریال
|
||||
@_userInfo.TotalPaidCommission.ToString("N0") تومان
|
||||
</MudText>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -270,7 +270,7 @@
|
||||
<td><strong>کمیسیون در انتظار:</strong></td>
|
||||
<td>
|
||||
<MudText Typo="Typo.body1" Color="Color.Warning">
|
||||
@_userInfo.PendingCommission.ToString("N0") ریال
|
||||
@_userInfo.PendingCommission.ToString("N0") تومان
|
||||
</MudText>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -300,9 +300,9 @@
|
||||
<MudSimpleTable Dense="true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>پکیج طلایی:</strong></td>
|
||||
<td><strong>خرید پکیج:</strong></td>
|
||||
<td>
|
||||
@if (_userInfo.HasPurchasedGoldenPackage)
|
||||
@if (_userInfo.HasPurchasedPackage)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Success" Size="Size.Small" Icon="@Icons.Material.Filled.CheckCircle">
|
||||
خریداری شده
|
||||
@@ -434,6 +434,12 @@
|
||||
OnClick="@(() => NavigationManager.NavigateTo($"/commission/payouts?userId={UserId}"))">
|
||||
پرداختهای کاربر
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Warning"
|
||||
StartIcon="@Icons.Material.Filled.SwapHoriz"
|
||||
OnClick="OpenChangeParentDialog">
|
||||
تغییر والد
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
StartIcon="@Icons.Material.Filled.Refresh"
|
||||
@@ -537,4 +543,28 @@
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private async Task OpenChangeParentDialog()
|
||||
{
|
||||
var displayName = $"{_userInfo.UserName} (ID: {UserId})";
|
||||
var parameters = new DialogParameters<Network.Components.ChangeParentDialog>
|
||||
{
|
||||
{ x => x.UserId, UserId },
|
||||
{ x => x.UserDisplayName, displayName }
|
||||
};
|
||||
var options = new DialogOptions
|
||||
{
|
||||
MaxWidth = MaxWidth.Small,
|
||||
FullWidth = true,
|
||||
CloseOnEscapeKey = true
|
||||
};
|
||||
var dialog = await DialogService.ShowAsync<Network.Components.ChangeParentDialog>("تغییر والد شبکه", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result is { Canceled: false })
|
||||
{
|
||||
await LoadUserInfo();
|
||||
await LoadNetworkHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@using CMSMicroservice.Protobuf.Protos.Package
|
||||
@using CMSMicroservice.Protobuf.Protos.Configuration
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Tizzani.MudBlazor.HtmlEditor
|
||||
@@ -41,12 +42,77 @@
|
||||
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت" Variant="Variant.Outlined" />
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت (ریال)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.SortOrder" Disabled="_isLoading" Label="ترتیب نمایش" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.ActivationFee" Format="N0" Immediate="true" Disabled="_isLoading" Label="هزینه فعالسازی (ریال)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="double" HideSpinButtons="true" @bind-Value="Model.DiscountMultiplier" Disabled="_isLoading" Label="ضریب تخفیف" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="double" HideSpinButtons="true" @bind-Value="Model.MagicWalletMultiplier" Disabled="_isLoading" Label="ضریب کیف پول جادویی" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.MagicWalletMaxDeposit" Format="N0" Immediate="true" Disabled="_isLoading" Label="سقف واریز جادویی" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.MagicWalletMaxCredit" Format="N0" Immediate="true" Disabled="_isLoading" Label="سقف اعتبار جادویی" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.MaxBalancesPerLeg" Disabled="_isLoading" Label="حداکثر تعادل هر پا" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.MaxNetworkLevel" Disabled="_isLoading" Label="حداکثر سطح شبکه" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" @bind-Value="Model.IsActive" Disabled="_isLoading" Label="فعال" Color="Color.Success" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" @bind-Value="Model.IsBasePackage" Disabled="_isLoading" Label="پکیج پایه" Color="Color.Primary" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" @bind-Value="Model.SupportsDirectPurchase" Disabled="_isLoading" Label="پرداخت مستقیم" Color="Color.Info" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" @bind-Value="Model.SupportsDayaPurchase" Disabled="_isLoading" Label="اعتبار دایا" Color="Color.Warning" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
@if (_features.Any())
|
||||
{
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-4 mb-2"><strong>ویژگیهای پکیج</strong></MudText>
|
||||
<MudStack Row="true" Wrap="Wrap.Wrap">
|
||||
@foreach (var feature in _features)
|
||||
{
|
||||
<MudCheckBox T="bool"
|
||||
Value="@(_selectedFeatureIds.Contains(feature.Id))"
|
||||
ValueChanged="@((bool v) => ToggleFeature(feature.Id, v))"
|
||||
Disabled="_isLoading"
|
||||
Label="@feature.Title"
|
||||
Color="Color.Primary" />
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudHtmlEditor @bind-Html="Model.Description" MinHeight="300px">
|
||||
<MudHtmlToolbarOptions InsertImage="false" /> <!-- This will exclude the "insert image" toolbar option -->
|
||||
<MudHtmlToolbarOptions InsertImage="false" />
|
||||
</MudHtmlEditor>
|
||||
</MudItem>
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,5 @@
|
||||
@using CMSMicroservice.Protobuf.Protos.Package
|
||||
@using CMSMicroservice.Protobuf.Protos.Configuration
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Tizzani.MudBlazor.HtmlEditor
|
||||
@@ -42,12 +43,77 @@
|
||||
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Disabled="_isLoading" Label="قیمت" Variant="Variant.Outlined" />
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Disabled="_isLoading" Label="قیمت (ریال)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.SortOrder" Disabled="_isLoading" Label="ترتیب نمایش" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.ActivationFee" Format="N0" Immediate="true" Disabled="_isLoading" Label="هزینه فعالسازی (ریال)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="double" HideSpinButtons="true" @bind-Value="Model.DiscountMultiplier" Disabled="_isLoading" Label="ضریب تخفیف" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="double" HideSpinButtons="true" @bind-Value="Model.MagicWalletMultiplier" Disabled="_isLoading" Label="ضریب کیف پول جادویی" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.MagicWalletMaxDeposit" Format="N0" Immediate="true" Disabled="_isLoading" Label="سقف واریز جادویی" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.MagicWalletMaxCredit" Format="N0" Immediate="true" Disabled="_isLoading" Label="سقف اعتبار جادویی" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.MaxBalancesPerLeg" Disabled="_isLoading" Label="حداکثر تعادل هر پا" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="int" HideSpinButtons="true" @bind-Value="Model.MaxNetworkLevel" Disabled="_isLoading" Label="حداکثر سطح شبکه" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" @bind-Value="Model.IsActive" Disabled="_isLoading" Label="فعال" Color="Color.Success" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" @bind-Value="Model.IsBasePackage" Disabled="_isLoading" Label="پکیج پایه" Color="Color.Primary" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" @bind-Value="Model.SupportsDirectPurchase" Disabled="_isLoading" Label="پرداخت مستقیم" Color="Color.Info" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" @bind-Value="Model.SupportsDayaPurchase" Disabled="_isLoading" Label="اعتبار دایا" Color="Color.Warning" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
@if (_features.Any())
|
||||
{
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-4 mb-2"><strong>ویژگیهای پکیج</strong></MudText>
|
||||
<MudStack Row="true" Wrap="Wrap.Wrap">
|
||||
@foreach (var feature in _features)
|
||||
{
|
||||
<MudCheckBox T="bool"
|
||||
Value="@(_selectedFeatureIds.Contains(feature.Id))"
|
||||
ValueChanged="@((bool v) => ToggleFeature(feature.Id, v))"
|
||||
Disabled="_isLoading"
|
||||
Label="@feature.Title"
|
||||
Color="Color.Primary" />
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudHtmlEditor @bind-Html="Model.Description" MinHeight="300px">
|
||||
<MudHtmlToolbarOptions InsertImage="false" /> <!-- This will exclude the "insert image" toolbar option -->
|
||||
<MudHtmlToolbarOptions InsertImage="false" />
|
||||
</MudHtmlEditor>
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -12,13 +12,21 @@
|
||||
</MudItem>
|
||||
</Filters>
|
||||
<Content>
|
||||
@* ── G8: راهنمای مدیریت پکیج (Q28) ── *@
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Text" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Warning">
|
||||
تغییر قیمت روی کاربران فعلی اثر ندارد — فقط خریدهای جدید. هر پکیج Pool پورسانت مستقل دارد.
|
||||
</MudAlert>
|
||||
<MudDataGrid T="DataModel" ServerData="@(new Func<GridState<DataModel>, Task<GridData<DataModel>>>(ServerReload))"
|
||||
Hover="true" @ref="_gridData" Height="calc(100vh - 240px)">
|
||||
<ColGroup>
|
||||
<col />
|
||||
<col />
|
||||
<col />
|
||||
<col style="width: 58px;" />
|
||||
<col />
|
||||
<col />
|
||||
<col />
|
||||
<col />
|
||||
<col style="width: 80px;" />
|
||||
</ColGroup>
|
||||
<ToolBarContent>
|
||||
<MudText>مدیریت پکیج</MudText>
|
||||
@@ -51,31 +59,42 @@
|
||||
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.Description" Title="توضیحات" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||
<PropertyColumn Property="x => x.Price" Title="قیمت" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||
<CellTemplate>
|
||||
@if (string.IsNullOrWhiteSpace(context.Item.Description))
|
||||
<MudText Typo="Typo.inherit">@context.Item.Price.ToString("N0")</MudText>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.SortOrder" Title="ترتیب" />
|
||||
<PropertyColumn Property="x => x.IsActive" Title="وضعیت" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||
<CellTemplate>
|
||||
@if (context.Item.IsActive)
|
||||
{
|
||||
<MudText Typo="Typo.inherit">-</MudText>
|
||||
<MudChip T="string" Color="Color.Success" Variant="Variant.Filled" Size="Size.Small">فعال</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<MudTooltip Text="@(context.Item.Description.HtmlToText())" Arrow="true" Style="@($"{(context.Item.Description.Length < 70 ? string.Empty : "width:600px;")}")">
|
||||
<MudText Typo="Typo.inherit" Style="text-wrap: nowrap;">
|
||||
@(context.Item.Description.HtmlToText().Truncate(20, true))
|
||||
</MudText>
|
||||
</MudTooltip>
|
||||
<MudChip T="string" Color="Color.Default" Variant="Variant.Filled" Size="Size.Small">غیرفعال</MudChip>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<TemplateColumn StickyLeft="true" Title="عملیات" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||
<PropertyColumn Property="x => x.IsBasePackage" Title="نوع" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||
<CellTemplate>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
@if (context.Item.IsBasePackage)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Primary" Variant="Variant.Filled" Size="Size.Small">پایه</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined" Size="Size.Small">عادی</MudChip>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<TemplateColumn Title="">
|
||||
<CellTemplate>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudTooltip Text="ویرایش">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.EditNote" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => Update(context.Item.Adapt<DataModel>()))" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => Update(context.Item))" />
|
||||
</MudTooltip>
|
||||
|
||||
<MudTooltip Text="آرشیو">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OnDelete(context.Item))" />
|
||||
</MudTooltip>
|
||||
|
||||
@@ -33,7 +33,7 @@ public partial class PackageMainPage
|
||||
{
|
||||
var parameters = new DialogParameters<UpdateDialog> { { x => x.Model, model.Adapt<UpdatePackageRequest>() } };
|
||||
|
||||
var dialog = await DialogService.ShowAsync<UpdateDialog>($"ویرایش پکیج", parameters, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
|
||||
var dialog = await DialogService.ShowAsync<UpdateDialog>($"ویرایش پکیج", parameters, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Medium });
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (!result.Canceled)
|
||||
@@ -70,7 +70,7 @@ public partial class PackageMainPage
|
||||
|
||||
public async Task CreateNew()
|
||||
{
|
||||
var dialog = await DialogService.ShowAsync<CreateDialog>($"افزودن پکیج", new DialogParameters<CreateDialog>() { { x => x.Model, new CreateNewPackageRequest() } }, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
|
||||
var dialog = await DialogService.ShowAsync<CreateDialog>($"افزودن پکیج", new DialogParameters<CreateDialog>() { { x => x.Model, new CreateNewPackageRequest() { IsActive = true, SupportsDirectPurchase = true, DiscountMultiplier = 2.0, MagicWalletMultiplier = 2.5, MaxBalancesPerLeg = 300, MaxNetworkLevel = 15, MagicWalletMaxDeposit = 1_000_000_000, MagicWalletMaxCredit = 2_500_000_000 } } }, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Medium });
|
||||
var result = await dialog.Result;
|
||||
if (!result.Canceled)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using CMSMicroservice.Protobuf.Protos.UserWallet
|
||||
|
||||
@inject UserWalletContract.UserWalletContractClient WalletClient
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudAlert Severity="Severity.Warning" Dense="true" Variant="Variant.Outlined">
|
||||
مبلغ مستقیماً به کیف پول اصلی (Balance) کاربر اضافه میشود و در دفتر مالی با نوع «شارژ دستی کیف اصلی» ثبت میگردد.
|
||||
</MudAlert>
|
||||
|
||||
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
|
||||
|
||||
<MudNumericField T="long" @bind-Value="_amount"
|
||||
Label="مبلغ (تومان)"
|
||||
Variant="Variant.Outlined"
|
||||
Min="10000"
|
||||
HelperText="حداقل ۱۰,۰۰۰ تومان" />
|
||||
|
||||
<MudTextField T="string" @bind-Value="_note"
|
||||
Label="توضیحات ادمین (اختیاری)"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="2" />
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel" Disabled="_isSubmitting">انصراف</MudButton>
|
||||
<MudButton Color="Color.Primary" Variant="Variant.Filled"
|
||||
OnClick="Submit" Disabled="_isSubmitting || !_userId.HasValue || _amount < 10000">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||||
}
|
||||
ثبت شارژ
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
private long? _userId;
|
||||
private long _amount = 10000;
|
||||
private string? _note;
|
||||
private bool _isSubmitting;
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
if (!_userId.HasValue || _userId.Value <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفاً کاربر را انتخاب کنید", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_amount < 10000)
|
||||
{
|
||||
Snackbar.Add("حداقل مبلغ شارژ ۱۰,۰۰۰ تومان است", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
bool? confirm = await DialogService.ShowMessageBox(
|
||||
"تأیید شارژ دستی",
|
||||
$"آیا از شارژ {_amount:N0} تومان به کیف پول اصلی کاربر #{_userId.Value} مطمئن هستید؟",
|
||||
yesText: "بله، شارژ شود", cancelText: "لغو");
|
||||
|
||||
if (confirm != true)
|
||||
return;
|
||||
|
||||
_isSubmitting = true;
|
||||
try
|
||||
{
|
||||
var request = new AdminManualCreditChargeRequest
|
||||
{
|
||||
UserId = _userId.Value,
|
||||
Amount = _amount
|
||||
};
|
||||
if (!string.IsNullOrWhiteSpace(_note))
|
||||
request.Note = _note.Trim();
|
||||
|
||||
var response = await WalletClient.AdminManualCreditChargeAsync(request);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
Snackbar.Add(
|
||||
$"شارژ موفق — تراکنش #{response.TransactionId}، موجودی جدید: {response.NewBalance:N0}",
|
||||
Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add(response.Message ?? "شارژ ناموفق بود", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@using CMSMicroservice.Protobuf.Protos.ManualPayment
|
||||
@using CMSMicroservice.Protobuf.Protos.Package
|
||||
@using BackOffice.Pages.Payment.Components
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@@ -13,21 +14,36 @@
|
||||
<UserAutoComplete Label="انتخاب کاربر"
|
||||
@bind-SelectedUserId="_selectedUserId" />
|
||||
|
||||
<MudNumericField T="long"
|
||||
Label="مبلغ"
|
||||
<MudSelect T="long?"
|
||||
Label="انتخاب پکیج"
|
||||
Value="@_selectedPackageId"
|
||||
ValueChanged="OnPackageChanged"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentText="تومان"
|
||||
Disabled="true"
|
||||
@bind-Value="_createModel.Amount" />
|
||||
Margin="Margin.Dense"
|
||||
Required="true"
|
||||
RequiredError="انتخاب پکیج الزامی است">
|
||||
@foreach (var pkg in _packages)
|
||||
{
|
||||
<MudSelectItem T="long?" Value="@((long?)pkg.Id)">
|
||||
@pkg.Title — @pkg.Price.ToString("N0") تومان
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
@if (_selectedPackagePrice.HasValue)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text">
|
||||
مبلغ پکیج: <strong>@_selectedPackagePrice.Value.ToString("N0") تومان</strong> — این مبلغ به کیفپول کاربر اضافه میشود.
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
<MudSelect T="int"
|
||||
Label="نوع پرداخت"
|
||||
Variant="Variant.Outlined"
|
||||
@bind-Value="_selectedType">
|
||||
<MudSelectItem T="int" Value="1">واریز نقدی</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="2">شارژ کیف پول تخفیف</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="3">شارژ کیف پول شبکه</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="2">شارژ کیف پول اعتباری</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="3">شارژ پاداش های دریافتی</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="4">تسویه حساب</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="5">اصلاح خطا</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="6">بازپرداخت</MudSelectItem>
|
||||
@@ -83,7 +99,7 @@
|
||||
<MudText Typo="Typo.h6">جزئیات فعالسازی دستی</MudText>
|
||||
<MudText Typo="Typo.body2">شناسه: @Model.Id</MudText>
|
||||
<MudText Typo="Typo.body2">کاربر: @Model.UserFullName (@Model.UserId)</MudText>
|
||||
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") تومان</MudText>
|
||||
<MudText Typo="Typo.body2">نوع: @Model.TypeDisplay</MudText>
|
||||
<MudText Typo="Typo.body2">وضعیت: @Model.StatusDisplay</MudText>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using CMSMicroservice.Protobuf.Protos.ManualPayment;
|
||||
using CMSMicroservice.Protobuf.Protos.Package;
|
||||
using Google.Protobuf;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
@@ -24,19 +25,23 @@ public partial class ManualPaymentDialog
|
||||
{
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Inject] public ManualPaymentContract.ManualPaymentContractClient ManualPaymentClient { get; set; } = default!;
|
||||
[Inject] public PackageContract.PackageContractClient PackageClient { get; set; } = default!;
|
||||
// Snackbar is injected via _Imports.razor
|
||||
|
||||
[Parameter] public ManualPaymentDialogMode Mode { get; set; }
|
||||
[Parameter] public ManualPaymentModel? Model { get; set; }
|
||||
|
||||
private ManualPaymentModel _createModel = new(){
|
||||
Amount = 56000000,
|
||||
};
|
||||
private ManualPaymentModel _createModel = new(){ Amount = 0 };
|
||||
private string? _adminNote;
|
||||
|
||||
// User selection
|
||||
private long? _selectedUserId;
|
||||
|
||||
// Package selection
|
||||
private long? _selectedPackageId;
|
||||
private long? _selectedPackagePrice;
|
||||
private List<GetAllPackageByFilterResponseModel> _packages = new();
|
||||
|
||||
// Type and Description
|
||||
private int _selectedType = 1; // Default: CashDeposit
|
||||
private string _description = "عضویت دستی باشگاه مشتریان";
|
||||
@@ -48,6 +53,46 @@ public partial class ManualPaymentDialog
|
||||
|
||||
private bool _isSubmitting;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task LoadPackages()
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetAllPackageByFilterRequest
|
||||
{
|
||||
PaginationState = new() { PageNumber = 1, PageSize = 100 },
|
||||
Filter = new()
|
||||
};
|
||||
|
||||
var result = await PackageClient.GetAllPackageByFilterAsync(request);
|
||||
_packages = result.Models?.OrderBy(p => p.SortOrder).ToList()
|
||||
?? new List<GetAllPackageByFilterResponseModel>();
|
||||
|
||||
// اگر پکیج پایه وجود دارد، پیشفرض انتخاب کن
|
||||
var basePackage = _packages.FirstOrDefault(p => p.IsBasePackage);
|
||||
if (basePackage != null)
|
||||
{
|
||||
_selectedPackageId = basePackage.Id;
|
||||
_selectedPackagePrice = basePackage.Price;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_packages = new List<GetAllPackageByFilterResponseModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPackageChanged(long? packageId)
|
||||
{
|
||||
_selectedPackageId = packageId;
|
||||
var pkg = _packages.FirstOrDefault(p => p.Id == packageId);
|
||||
_selectedPackagePrice = pkg?.Price;
|
||||
}
|
||||
|
||||
private async Task OnImageFileSelected(IBrowserFile? file)
|
||||
{
|
||||
if (file == null)
|
||||
@@ -81,6 +126,12 @@ public partial class ManualPaymentDialog
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_selectedPackageId.HasValue || _selectedPackageId.Value <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_selectedType <= 0)
|
||||
{
|
||||
Snackbar.Add("لطفاً نوع پرداخت را انتخاب کنید.", Severity.Warning);
|
||||
@@ -101,7 +152,8 @@ public partial class ManualPaymentDialog
|
||||
var request = new CreateManualPaymentRequest
|
||||
{
|
||||
UserId = _selectedUserId.Value,
|
||||
Amount = _createModel.Amount,
|
||||
Amount = 0, // deprecated — مبلغ از پکیج خوانده میشود
|
||||
PackageId = _selectedPackageId.Value,
|
||||
Type = (ManualPaymentType)_selectedType,
|
||||
Description = _description
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<MudText Typo="Typo.h6">جزئیات تراکنش</MudText>
|
||||
<MudText Typo="Typo.body2">شناسه: @Model.Id</MudText>
|
||||
<MudText Typo="Typo.body2">RefId: @Model.RefId</MudText>
|
||||
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.body2">مبلغ: @Model.Amount.ToString("N0") تومان</MudText>
|
||||
<MudText Typo="Typo.body2">وضعیت: @StatusText</MudText>
|
||||
<MudText Typo="Typo.body2">تاریخ پرداخت: @Model.PaymentDate.ToLocalTime().MiladiToJalaliWithTime()</MudText>
|
||||
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
@page "/payment/ledger-transactions"
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using CMSMicroservice.Protobuf.Protos
|
||||
@using CMSMicroservice.Protobuf.Protos.Transactions
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
|
||||
@inject TransactionsContract.TransactionsContractClient TransactionsClient
|
||||
|
||||
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||
<Filters>
|
||||
<MudItem xs="12">
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true">
|
||||
گزارش read-only دفتر تراکنشهای مالی (جدول Transactions). برای جزئیات درگاه پرداخت به «گزارش تراکنش درگاه» مراجعه کنید.
|
||||
</MudAlert>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudTextField T="long?" Label="شناسه" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_id" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudTextField T="string" Label="RefId" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_refId" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudTextField T="string" Label="توضیحات" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_description" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudSelect T="PaymentStatus?" Clearable="true" Label="وضعیت پرداخت" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_paymentStatus">
|
||||
<MudSelectItem T="PaymentStatus?" Value="PaymentStatus.Success">موفق</MudSelectItem>
|
||||
<MudSelectItem T="PaymentStatus?" Value="PaymentStatus.Pending">در انتظار</MudSelectItem>
|
||||
<MudSelectItem T="PaymentStatus?" Value="PaymentStatus.Reject">رد شده</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudSelect T="TransactionType?" Clearable="true" Label="نوع تراکنش" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_type">
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.Buy">خرید</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.DepositIpg">واریز درگاه</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.DepositExternal1">واریز خارجی</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.Withdraw">برداشت</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.NetworkCommission">کمیسیون شبکه</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.ClubActivation">فعالسازی باشگاه</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.DiscountWalletCharge">شارژ کیف تخفیف</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.DiscountShopPurchase">خرید فروشگاه تخفیف</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.MagicWalletDeposit">واریز کیف جادویی</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.MagicWalletBonus">بونوس کیف جادویی</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.CreditWalletCharge">شارژ کیف اصلی</MudSelectItem>
|
||||
<MudSelectItem T="TransactionType?" Value="TransactionType.ManualCreditWalletCharge">شارژ دستی کیف اصلی</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<DateRangePicker @bind-From="_createdFrom" @bind-To="_createdTo" Label="بازه ثبت" />
|
||||
</MudItem>
|
||||
</Filters>
|
||||
<Content>
|
||||
<MudGrid Class="mb-3" Spacing="2">
|
||||
<MudItem xs="6" sm="3">
|
||||
<MudPaper Class="pa-3" Outlined="true">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">تعداد</MudText>
|
||||
<MudText Typo="Typo.h6">@_summaryTotalCount.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="3">
|
||||
<MudPaper Class="pa-3" Outlined="true">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">جمع مبلغ (تومان)</MudText>
|
||||
<MudText Typo="Typo.h6">@_summaryTotalAmount.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="2">
|
||||
<MudPaper Class="pa-3" Outlined="true">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">موفق</MudText>
|
||||
<MudText Typo="Typo.h6" Color="Color.Success">@_summarySuccess.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="2">
|
||||
<MudPaper Class="pa-3" Outlined="true">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">در انتظار</MudText>
|
||||
<MudText Typo="Typo.h6" Color="Color.Warning">@_summaryPending.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="2">
|
||||
<MudPaper Class="pa-3" Outlined="true">
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">رد شده</MudText>
|
||||
<MudText Typo="Typo.h6" Color="Color.Error">@_summaryReject.ToString("N0")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudDataGrid @ref="_dataGrid" T="GetAllTransactionsByFilterResponseModel"
|
||||
ServerData="LoadData" ReadOnly="true"
|
||||
Hover="true" Dense="true" Height="calc(100vh - 400px)">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.subtitle1">دفتر تراکنشهای مالی</MudText>
|
||||
<MudSpacer />
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Info" Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.FileDownload" OnClick="ExportCsv">CSV</MudButton>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
<TemplateColumn Title="نوع">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Text">
|
||||
@ReportLabels.GetTransactionTypeLabel(context.Item.HasType ? context.Item.Type : null)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="مبلغ (تومان)">
|
||||
<CellTemplate>@context.Item.Amount.ToString("N0")</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="x => x.Description" Title="توضیحات" />
|
||||
<TemplateColumn Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string" Size="Size.Small" Color="@ReportLabels.GetTransactionPaymentStatusColor(context.Item.HasPaymentStatus ? context.Item.PaymentStatus : null)">
|
||||
@ReportLabels.GetTransactionPaymentStatusLabel(context.Item.HasPaymentStatus ? context.Item.PaymentStatus : null)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="x => x.RefId" Title="RefId" />
|
||||
<TemplateColumn Title="تاریخ پرداخت">
|
||||
<CellTemplate>
|
||||
@(context.Item.PaymentDate != null
|
||||
? context.Item.PaymentDate.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()
|
||||
: "—")
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="تاریخ ثبت">
|
||||
<CellTemplate>@context.Item.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<NoRecordsContent>
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">
|
||||
با فیلتر فعلی موردی یافت نشد — بازه تاریخ یا فیلترها را تغییر دهید.
|
||||
</MudAlert>
|
||||
</NoRecordsContent>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="GetAllTransactionsByFilterResponseModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد در صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</Content>
|
||||
</BasePageComponent>
|
||||
|
||||
@code {
|
||||
private BasePageComponent? _basePage;
|
||||
private MudDataGrid<GetAllTransactionsByFilterResponseModel>? _dataGrid;
|
||||
private long? _id;
|
||||
private string? _refId, _description;
|
||||
private PaymentStatus? _paymentStatus;
|
||||
private TransactionType? _type;
|
||||
private DateTime? _createdFrom, _createdTo;
|
||||
private long _summaryTotalCount, _summaryTotalAmount, _summarySuccess, _summaryPending, _summaryReject;
|
||||
|
||||
private GetAllTransactionsByFilterFilter BuildFilter()
|
||||
{
|
||||
var filter = new GetAllTransactionsByFilterFilter
|
||||
{
|
||||
Id = _id > 0 ? _id : null,
|
||||
RefId = string.IsNullOrWhiteSpace(_refId) ? null : _refId,
|
||||
Description = string.IsNullOrWhiteSpace(_description) ? null : _description,
|
||||
CreatedFrom = _createdFrom.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_createdFrom.Value, DateTimeKind.Utc)) : null,
|
||||
CreatedTo = _createdTo.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_createdTo.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null
|
||||
};
|
||||
|
||||
if (_paymentStatus.HasValue)
|
||||
filter.PaymentStatus = _paymentStatus.Value;
|
||||
else
|
||||
filter.ClearPaymentStatusItem();
|
||||
|
||||
if (_type.HasValue)
|
||||
filter.Type = _type.Value;
|
||||
else
|
||||
filter.ClearTypeItem();
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private async Task LoadSummary()
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = await TransactionsClient.GetTransactionSummaryAsync(new GetTransactionSummaryRequest { Filter = BuildFilter() });
|
||||
_summaryTotalCount = s.TotalCount;
|
||||
_summaryTotalAmount = s.TotalAmount;
|
||||
_summarySuccess = s.SuccessCount;
|
||||
_summaryPending = s.PendingCount;
|
||||
_summaryReject = s.RejectCount;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task<GridData<GetAllTransactionsByFilterResponseModel>> LoadData(GridState<GetAllTransactionsByFilterResponseModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadSummary();
|
||||
var result = await TransactionsClient.GetAllTransactionsByFilterAsync(new GetAllTransactionsByFilterRequest
|
||||
{
|
||||
PaginationState = new PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||
SortBy = "-Created",
|
||||
Filter = BuildFilter()
|
||||
});
|
||||
return new GridData<GetAllTransactionsByFilterResponseModel>
|
||||
{
|
||||
Items = result?.Models?.ToList() ?? new(),
|
||||
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
return new GridData<GetAllTransactionsByFilterResponseModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnFilterSubmit() { if (_dataGrid != null) await _dataGrid.ReloadServerData(); }
|
||||
private async Task OnFilterCleared()
|
||||
{
|
||||
_id = null; _refId = _description = null;
|
||||
_paymentStatus = null; _type = null;
|
||||
_createdFrom = _createdTo = null;
|
||||
if (_dataGrid != null) await _dataGrid.ReloadServerData();
|
||||
}
|
||||
|
||||
private async Task ExportCsv()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await TransactionsClient.GetAllTransactionsByFilterAsync(new GetAllTransactionsByFilterRequest
|
||||
{
|
||||
PaginationState = new PaginationState { PageNumber = 1, PageSize = 5000 },
|
||||
SortBy = "-Created",
|
||||
Filter = BuildFilter()
|
||||
});
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine("Id,Type,Amount,Description,Status,RefId,PaymentDate,Created");
|
||||
foreach (var r in result.Models)
|
||||
{
|
||||
var type = r.HasType ? ReportLabels.GetTransactionTypeLabel(r.Type) : "";
|
||||
var status = r.HasPaymentStatus ? ReportLabels.GetTransactionPaymentStatusLabel(r.PaymentStatus) : "";
|
||||
var paymentDate = r.PaymentDate != null ? r.PaymentDate.ToDateTime().ToString("yyyy-MM-dd HH:mm") : "";
|
||||
sb.AppendLine($"{r.Id},{type},{r.Amount},\"{r.Description}\",{status},{r.RefId},{paymentDate},{r.Created.ToDateTime():yyyy-MM-dd HH:mm}");
|
||||
}
|
||||
await jsRuntime.InvokeVoidAsync("jsSaveAsFile", "ledger-transactions.csv", sb.ToString());
|
||||
}
|
||||
catch (Exception ex) { Snackbar.Add($"خطا در export: {ex.Message}", Severity.Error); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
@page "/payment/manual-credit-charges"
|
||||
@attribute [Authorize(Roles = "Administrator")]
|
||||
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
@using BackOffice.Pages.Payment.Components
|
||||
@using CMSMicroservice.Protobuf.Protos
|
||||
@using CMSMicroservice.Protobuf.Protos.UserWallet
|
||||
@using DateTimeConverterCL
|
||||
@using Google.Protobuf.WellKnownTypes
|
||||
|
||||
@inject UserWalletContract.UserWalletContractClient WalletClient
|
||||
|
||||
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||
<Filters>
|
||||
<MudItem xs="12">
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true">
|
||||
شارژ دستی کیف پول اصلی (Balance) — بدون درگاه. جدا از «پرداختهای دستی» عضویت باشگاه.
|
||||
</MudAlert>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<UserAutoComplete Label="جستجوی کاربر" @bind-SelectedUserId="_userIdFilter" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudTextField T="long?" Label="شناسه تراکنش" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_transactionIdFilter" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudTextField T="string" Label="RefId" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_refIdFilter" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<DateRangePicker @bind-From="_createdFrom" @bind-To="_createdTo" Label="بازه ثبت" />
|
||||
</MudItem>
|
||||
</Filters>
|
||||
<Content>
|
||||
<MudDataGrid @ref="_dataGrid" T="ManualCreditChargeModel"
|
||||
ServerData="LoadData"
|
||||
Hover="true" Dense="true"
|
||||
Height="calc(100vh - 240px)">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.subtitle1">شارژ دستی کیف پول اصلی</MudText>
|
||||
<MudSpacer />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.Add" OnClick="OpenCreateDialog">
|
||||
شارژ دستی جدید
|
||||
</MudButton>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.TransactionId" Title="شناسه تراکنش" />
|
||||
<PropertyColumn Property="x => x.UserId" Title="شناسه کاربر" />
|
||||
<PropertyColumn Property="x => x.UserName" Title="نام کاربر" />
|
||||
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
|
||||
<CellTemplate>
|
||||
@context.Item.Amount.ToString("N0")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.NewBalance" Title="موجودی بعد از شارژ">
|
||||
<CellTemplate>
|
||||
@context.Item.NewBalance.ToString("N0")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.RefId" Title="RefId" />
|
||||
<PropertyColumn Property="x => x.Description" Title="توضیحات" />
|
||||
<TemplateColumn Title="تاریخ">
|
||||
<CellTemplate>
|
||||
@(context.Item.Created?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<NoRecordsContent>
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||
</NoRecordsContent>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="ManualCreditChargeModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد در صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</Content>
|
||||
</BasePageComponent>
|
||||
|
||||
@code {
|
||||
private BasePageComponent? _basePage;
|
||||
private MudDataGrid<ManualCreditChargeModel>? _dataGrid;
|
||||
private long? _userIdFilter;
|
||||
private long? _transactionIdFilter;
|
||||
private string? _refIdFilter;
|
||||
private DateTime? _createdFrom;
|
||||
private DateTime? _createdTo;
|
||||
|
||||
private async Task<GridData<ManualCreditChargeModel>> LoadData(GridState<ManualCreditChargeModel> state)
|
||||
{
|
||||
var request = new GetManualCreditChargesRequest
|
||||
{
|
||||
PaginationState = new PaginationState
|
||||
{
|
||||
PageNumber = state.Page + 1,
|
||||
PageSize = state.PageSize
|
||||
},
|
||||
Filter = new GetManualCreditChargesFilter()
|
||||
};
|
||||
|
||||
if (_userIdFilter.HasValue)
|
||||
request.Filter.UserId = _userIdFilter.Value;
|
||||
if (_transactionIdFilter.HasValue)
|
||||
request.Filter.TransactionId = _transactionIdFilter.Value;
|
||||
if (!string.IsNullOrWhiteSpace(_refIdFilter))
|
||||
request.Filter.RefId = _refIdFilter.Trim();
|
||||
if (_createdFrom.HasValue)
|
||||
request.Filter.CreatedFrom = Timestamp.FromDateTime(DateTime.SpecifyKind(_createdFrom.Value, DateTimeKind.Utc));
|
||||
if (_createdTo.HasValue)
|
||||
request.Filter.CreatedTo = Timestamp.FromDateTime(DateTime.SpecifyKind(_createdTo.Value, DateTimeKind.Utc));
|
||||
|
||||
var response = await WalletClient.GetManualCreditChargesAsync(request);
|
||||
|
||||
return new GridData<ManualCreditChargeModel>
|
||||
{
|
||||
Items = response.Models.ToList(),
|
||||
TotalItems = (int)(response.MetaData?.TotalCount ?? 0)
|
||||
};
|
||||
}
|
||||
|
||||
private async Task OnFilterSubmit()
|
||||
{
|
||||
if (_dataGrid != null)
|
||||
await _dataGrid.ReloadServerData();
|
||||
}
|
||||
|
||||
private async Task OnFilterCleared()
|
||||
{
|
||||
_userIdFilter = null;
|
||||
_transactionIdFilter = null;
|
||||
_refIdFilter = null;
|
||||
_createdFrom = null;
|
||||
_createdTo = null;
|
||||
if (_dataGrid != null)
|
||||
await _dataGrid.ReloadServerData();
|
||||
}
|
||||
|
||||
private async Task OpenCreateDialog()
|
||||
{
|
||||
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true };
|
||||
var dialog = DialogService.Show<ManualCreditChargeDialog>("شارژ دستی کیف پول اصلی", options);
|
||||
var result = await dialog.Result;
|
||||
if (result is { Canceled: false } && _dataGrid != null)
|
||||
await _dataGrid.ReloadServerData();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,12 @@
|
||||
|
||||
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
|
||||
<Filters>
|
||||
@* ── G10: هشدار پرداخت دستی (Q28) ── *@
|
||||
<MudItem xs="12">
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Text" Dense="true" Icon="@Icons.Material.Filled.Warning">
|
||||
مبلغ پرداخت باید مطابق قیمت پکیج انتخابی باشد. پکیجهای مختلف قیمت متفاوتی دارند.
|
||||
</MudAlert>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<UserAutoComplete Label="جستجوی کاربر" @bind-SelectedUserId="_userIdFilter" />
|
||||
</MudItem>
|
||||
@@ -26,8 +32,8 @@
|
||||
<MudSelect T="int?" Clearable="true" Label="نوع" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_typeFilter">
|
||||
<MudSelectItem T="int?" Value="@(1)">واریز نقدی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(2)">شارژ کیفپول تخفیف</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(3)">شارژ کیفپول شبکه</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(2)">شارژ کیف پول اعتباری</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(3)">شارژ پاداش های دریافتی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(4)">تسویه حساب</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(5)">اصلاح خطا</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(6)">بازگشت وجه</MudSelectItem>
|
||||
@@ -219,8 +225,8 @@
|
||||
return type switch
|
||||
{
|
||||
1 => "واریز نقدی",
|
||||
2 => "شارژ کیفپول تخفیف",
|
||||
3 => "شارژ کیفپول شبکه",
|
||||
2 => "شارژ کیف پول اعتباری",
|
||||
3 => "شارژ پاداش های دریافتی",
|
||||
4 => "تسویه حساب",
|
||||
5 => "اصلاح خطا",
|
||||
6 => "بازگشت وجه",
|
||||
@@ -257,7 +263,7 @@
|
||||
{
|
||||
bool? confirm = await DialogService.ShowMessageBox(
|
||||
"تایید پرداخت",
|
||||
$"آیا از تایید پرداخت دستی #{model.Id} به مبلغ {model.Amount:N0} ریال مطمئن هستید؟",
|
||||
$"آیا از تایید پرداخت دستی #{model.Id} به مبلغ {model.Amount:N0} تومان مطمئن هستید؟",
|
||||
yesText: "تایید", cancelText: "لغو");
|
||||
if (confirm == true)
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ public partial class ManualPayments
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("شناسه,شناسه کاربر,نام کاربر,مبلغ,نوع,وضعیت,تاریخ ایجاد");
|
||||
sb.AppendLine("شناسه,شناسه کاربر,نام کاربر,پکیج,مبلغ,نوع,وضعیت,تاریخ ایجاد");
|
||||
|
||||
foreach (var m in response.Models)
|
||||
{
|
||||
@@ -45,6 +45,7 @@ public partial class ManualPayments
|
||||
m.Id,
|
||||
m.UserId,
|
||||
EscapeCsv(m.UserFullName),
|
||||
EscapeCsv(m.PackageName ?? "-"),
|
||||
m.Amount.ToString("N0"),
|
||||
EscapeCsv(GetTypeText((int)m.Type)),
|
||||
EscapeCsv(GetStatusText((int)m.Status)),
|
||||
|
||||
@@ -1,78 +1,115 @@
|
||||
@page "/payment/transactions"
|
||||
@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">
|
||||
<Filters>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<BackOffice.Common.BaseComponents.DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه تاریخی" />
|
||||
<MudItem xs="12">
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true">
|
||||
گزارش read-only تراکنشهای درگاه پرداخت. برای ثبت پرداخت دستی به «پرداختهای دستی» مراجعه کنید.
|
||||
</MudAlert>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudSelect T="int?" Clearable="true" Label="وضعیت پرداخت" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_statusFilter">
|
||||
<MudSelectItem T="int?" Value="@(0)">در انتظار</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(1)">موفق</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(2)">ناموفق</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(3)">بازگشت وجه</MudSelectItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudTextField T="string" Label="موبایل" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_mobile" />
|
||||
</MudItem>
|
||||
<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>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudSelect T="int?" Clearable="true" Label="نوع تراکنش" Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" @bind-Value="_typeFilter">
|
||||
<MudSelectItem T="int?" Value="@(0)">نامشخص</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(1)">خرید پکیج</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(2)">پرداخت دستی</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(3)">شارژ کیفپول</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(4)">بازگشت وجه</MudSelectItem>
|
||||
</MudSelect>
|
||||
<DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه تاریخ" />
|
||||
</MudItem>
|
||||
</Filters>
|
||||
<Content>
|
||||
<MudDataGrid @ref="_dataGrid" T="TransactionModel"
|
||||
ServerData="LoadData"
|
||||
Hover="true" Dense="true"
|
||||
Height="calc(100vh - 240px)">
|
||||
<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="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>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
<PropertyColumn Property="x => x.RefId" Title="شماره مرجع" />
|
||||
<PropertyColumn Property="x => x.Amount" Title="مبلغ">
|
||||
<CellTemplate>
|
||||
@context.Item.Amount.ToString("N0")
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.UserId" Title="کاربر" />
|
||||
<PropertyColumn Property="x => x.Mobile" Title="موبایل" />
|
||||
<PropertyColumn Property="x => x.GatewayProvider" Title="درگاه" />
|
||||
<TemplateColumn Title="مبلغ (تومان)">
|
||||
<CellTemplate>@context.Item.Amount.ToString("N0")</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="x => x.Authority" Title="Authority" />
|
||||
<PropertyColumn Property="x => x.RefId" Title="RefId" />
|
||||
<PropertyColumn Property="x => x.CardPan" Title="کارت" />
|
||||
<TemplateColumn Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Size="Size.Small"
|
||||
Color="@GetStatusColor(context.Item.PaymentStatus)">
|
||||
@GetStatusText(context.Item.PaymentStatus)
|
||||
<MudChip T="string" Size="Size.Small" Color="@(context.Item.PaymentStatus ? Color.Success : Color.Warning)">
|
||||
@ReportLabels.GetPaymentStatusLabel(context.Item.PaymentStatus)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="تاریخ پرداخت">
|
||||
<CellTemplate>
|
||||
@context.Item.PaymentDate.ToLocalTime().MiladiToJalaliWithTime()
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="نوع">
|
||||
<CellTemplate>
|
||||
@GetTypeText(context.Item.Type)
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="عملیات">
|
||||
<CellTemplate>
|
||||
<MudTooltip Text="جزئیات">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenDetails(context.Item))" />
|
||||
</MudTooltip>
|
||||
</CellTemplate>
|
||||
<PropertyColumn Property="x => x.VerificationStatusCode" Title="کد Verify" />
|
||||
<PropertyColumn Property="x => x.OrderId" Title="OrderId" />
|
||||
<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>
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">
|
||||
با فیلتر فعلی موردی یافت نشد — بازه تاریخ یا کاربر را تغییر دهید.
|
||||
</MudAlert>
|
||||
</NoRecordsContent>
|
||||
<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}"
|
||||
RowsPerPageString="تعداد در صفحه" />
|
||||
</PagerContent>
|
||||
@@ -80,3 +117,87 @@
|
||||
</Content>
|
||||
</BasePageComponent>
|
||||
|
||||
@code {
|
||||
private BasePageComponent? _basePage;
|
||||
private MudDataGrid<PaymentTransactionModel>? _dataGrid;
|
||||
private long? _userId;
|
||||
private string? _mobile, _authority, _refId, _orderId, _gateway;
|
||||
private bool? _paymentStatus;
|
||||
private DateTime? _fromDate, _toDate;
|
||||
private long _summaryTotalCount, _summaryTotalAmount, _summarySuccessful, _summaryPending;
|
||||
|
||||
private GetAllPaymentTransactionByFilterFilter BuildFilter() => new()
|
||||
{
|
||||
UserId = _userId > 0 ? _userId : null,
|
||||
Mobile = string.IsNullOrWhiteSpace(_mobile) ? null : _mobile,
|
||||
Authority = string.IsNullOrWhiteSpace(_authority) ? null : _authority,
|
||||
RefId = string.IsNullOrWhiteSpace(_refId) ? null : _refId,
|
||||
OrderId = string.IsNullOrWhiteSpace(_orderId) ? null : _orderId,
|
||||
GatewayProvider = string.IsNullOrWhiteSpace(_gateway) ? null : _gateway,
|
||||
PaymentStatus = _paymentStatus,
|
||||
CreatedFrom = _fromDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value, DateTimeKind.Utc)) : null,
|
||||
CreatedTo = _toDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null
|
||||
};
|
||||
|
||||
private async Task LoadSummary()
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = await PaymentTxClient.GetPaymentTransactionSummaryAsync(new GetPaymentTransactionSummaryRequest { Filter = BuildFilter() });
|
||||
_summaryTotalCount = s.TotalCount;
|
||||
_summaryTotalAmount = s.TotalAmount;
|
||||
_summarySuccessful = s.SuccessfulCount;
|
||||
_summaryPending = s.PendingCount;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task<GridData<PaymentTransactionModel>> LoadData(GridState<PaymentTransactionModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadSummary();
|
||||
var result = await PaymentTxClient.GetAllPaymentTransactionByFilterAsync(new GetAllPaymentTransactionByFilterRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||
Filter = BuildFilter()
|
||||
});
|
||||
return new GridData<PaymentTransactionModel>
|
||||
{
|
||||
Items = result?.Models?.ToList() ?? new(),
|
||||
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
return new GridData<PaymentTransactionModel>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnFilterSubmit() { if (_dataGrid != null) await _dataGrid.ReloadServerData(); }
|
||||
private async Task OnFilterCleared()
|
||||
{
|
||||
_userId = null; _mobile = _authority = _refId = _orderId = _gateway = null;
|
||||
_paymentStatus = null; _fromDate = _toDate = null;
|
||||
if (_dataGrid != null) await _dataGrid.ReloadServerData();
|
||||
}
|
||||
|
||||
private async Task ExportCsv()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await PaymentTxClient.GetAllPaymentTransactionByFilterAsync(new GetAllPaymentTransactionByFilterRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = 1, PageSize = 5000 },
|
||||
Filter = BuildFilter()
|
||||
});
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine("Id,UserId,Mobile,Gateway,Amount,Authority,RefId,Status,OrderId,Created");
|
||||
foreach (var r in result.Models)
|
||||
sb.AppendLine($"{r.Id},{r.UserId},{r.Mobile},{r.GatewayProvider},{r.Amount},{r.Authority},{r.RefId},{r.PaymentStatus},{r.OrderId},{r.CreatedAt.ToDateTime():yyyy-MM-dd HH:mm}");
|
||||
await jsRuntime.InvokeVoidAsync("jsSaveAsFile", "payment-transactions.csv", sb.ToString());
|
||||
}
|
||||
catch (Exception ex) { Snackbar.Add($"خطا در export: {ex.Message}", Severity.Error); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
using BackOffice.Common.BaseComponents;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.Payment;
|
||||
|
||||
public partial class Transactions
|
||||
{
|
||||
private BasePageComponent? _basePage;
|
||||
private MudDataGrid<TransactionModel>? _dataGrid;
|
||||
|
||||
private DateTime? _fromDate;
|
||||
private DateTime? _toDate;
|
||||
private int? _statusFilter;
|
||||
private int? _typeFilter;
|
||||
|
||||
private async Task<GridData<TransactionModel>> LoadData(GridState<TransactionModel> state)
|
||||
{
|
||||
// TODO: Connect to BackOffice.BFF Transactions when API is ready
|
||||
await Task.CompletedTask;
|
||||
|
||||
return new GridData<TransactionModel>
|
||||
{
|
||||
Items = Array.Empty<TransactionModel>(),
|
||||
TotalItems = 0
|
||||
};
|
||||
}
|
||||
|
||||
private async Task OnFilterSubmit()
|
||||
{
|
||||
if (_dataGrid != null)
|
||||
await _dataGrid.ReloadServerData();
|
||||
}
|
||||
|
||||
private async Task OnFilterCleared()
|
||||
{
|
||||
_fromDate = null;
|
||||
_toDate = null;
|
||||
_statusFilter = null;
|
||||
_typeFilter = null;
|
||||
if (_dataGrid != null)
|
||||
await _dataGrid.ReloadServerData();
|
||||
}
|
||||
|
||||
private void OpenDetails(TransactionModel model)
|
||||
{
|
||||
// TODO: Open TransactionDetailsDialog with model
|
||||
}
|
||||
|
||||
private static string GetStatusText(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => "در انتظار",
|
||||
1 => "موفق",
|
||||
2 => "ناموفق",
|
||||
3 => "بازگشت وجه",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private static Color GetStatusColor(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => Color.Warning,
|
||||
1 => Color.Success,
|
||||
2 => Color.Error,
|
||||
3 => Color.Info,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetTypeText(int type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
1 => "خرید پکیج",
|
||||
2 => "پرداخت دستی",
|
||||
3 => "شارژ کیفپول",
|
||||
4 => "بازگشت وجه",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
|
||||
private class TransactionModel
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string? RefId { get; set; }
|
||||
public long Amount { get; set; }
|
||||
public int PaymentStatus { get; set; }
|
||||
public DateTime PaymentDate { get; set; }
|
||||
public int Type { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
|
||||
<MudNumericField T="long?"
|
||||
HideSpinButtons="true"
|
||||
Label="قیمت جدید (ریال)"
|
||||
Label="قیمت جدید (تومان)"
|
||||
@bind-Value="_newPrice"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" />
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت (ریال)" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت (تومان)" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت (ریال)" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudNumericField T="long" HideSpinButtons="true" @bind-Value="Model.Price" Format="N0" Immediate="true" Disabled="_isLoading" Label="قیمت (تومان)" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
|
||||
<TemplateColumn Title="قیمت" CellStyle="text-wrap: nowrap;">
|
||||
<CellTemplate>
|
||||
@context.Item.Price.ToString("N0") ریال
|
||||
@context.Item.Price.ToString("N0") تومان
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
|
||||
|
||||
@@ -174,8 +174,7 @@
|
||||
<MudItem xs="12">
|
||||
<MudSwitch T="bool" @bind-Value="_networkConfig.SpilloverEnabled"
|
||||
Color="Color.Primary"
|
||||
Label="Spillover فعال باشد"
|
||||
HelperText="اعضای اضافی به پایین درخت منتقل شوند" />
|
||||
Label="Spillover فعال باشد — اعضای اضافی به پایین درخت منتقل شوند" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@@ -340,8 +339,7 @@
|
||||
<MudItem xs="12">
|
||||
<MudSwitch @bind-Value="_systemConfig.MaintenanceMode"
|
||||
Color="Color.Warning"
|
||||
Label="حالت تعمیر و نگهداری"
|
||||
HelperText="غیرفعال کردن موقت سیستم برای بهروزرسانی" />
|
||||
Label="حالت تعمیر و نگهداری — غیرفعال کردن موقت سیستم برای بهروزرسانی" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
مبلغ تخفیف بهصورت ریالی وارد شود.
|
||||
مبلغ تخفیف بهصورت تومانی وارد شود.
|
||||
</MudText>
|
||||
|
||||
<MudNumericField T="long?"
|
||||
@bind-Value="_discountAmount"
|
||||
Label="مبلغ تخفیف (ریال)"
|
||||
Label="مبلغ تخفیف (تومان)"
|
||||
Variant="Variant.Outlined"
|
||||
HideSpinButtons="true" />
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public partial class ApplyDiscountDialog
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
Snackbar.Add($"تخفیف با موفقیت اعمال شد. مبلغ نهایی: {response.FinalAmount.ToString("N0")} ریال", Severity.Success);
|
||||
Snackbar.Add($"تخفیف با موفقیت اعمال شد. مبلغ نهایی: {response.FinalAmount.ToString("N0")} تومان", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
else
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
<MudSelectItem T="int" Value="2">تحویل پست</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="3">تحویل به مشتری</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="4">مرجوع شده</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="5">لغو شده</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="6">آماده تحویل در دفتر</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
|
||||
@@ -56,6 +58,24 @@
|
||||
</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)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
|
||||
@@ -37,6 +37,8 @@ public partial class ChangeOrderStatusDialog
|
||||
2 => "تحویل پست",
|
||||
3 => "تحویل به مشتری",
|
||||
4 => "مرجوع شده",
|
||||
5 => "لغو شده",
|
||||
6 => "آماده تحویل در دفتر",
|
||||
_ => "بدون ارسال / نامشخص"
|
||||
};
|
||||
}
|
||||
@@ -49,6 +51,8 @@ public partial class ChangeOrderStatusDialog
|
||||
2 => Color.Info,
|
||||
3 => Color.Success,
|
||||
4 => Color.Error,
|
||||
5 => Color.Dark,
|
||||
6 => Color.Primary,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,6 +95,8 @@
|
||||
<MudSelectItem T="int" Value="2">تحویل پست</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="3">تحویل به مشتری</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="4">مرجوع شده</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="5">لغو شده</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="6">آماده تحویل در دفتر</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudText>
|
||||
<MudTextField T="string"
|
||||
@@ -123,6 +125,14 @@
|
||||
{
|
||||
<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>
|
||||
</MudPaper>
|
||||
|
||||
@@ -162,6 +172,13 @@
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<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>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using BackOffice.Common.Utilities;
|
||||
using CMSMicroservice.Protobuf.Protos;
|
||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.UserOrder.Components;
|
||||
@@ -9,6 +11,7 @@ public partial class UserOrderDetailsDialog
|
||||
{
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Inject] public UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
|
||||
[Inject] private IJSRuntime JsRuntime { get; set; } = default!;
|
||||
|
||||
[Parameter] public long OrderId { get; set; }
|
||||
|
||||
@@ -56,6 +59,8 @@ public partial class UserOrderDetailsDialog
|
||||
2 => Color.Info, // InTransit
|
||||
3 => Color.Success, // Delivered
|
||||
4 => Color.Error, // Returned
|
||||
5 => Color.Dark, // Cancelled
|
||||
6 => Color.Primary, // ReadyForOfficePickup
|
||||
_ => Color.Default // None / Unknown
|
||||
};
|
||||
}
|
||||
@@ -68,6 +73,8 @@ public partial class UserOrderDetailsDialog
|
||||
2 => "تحویل پست",
|
||||
3 => "تحویل به مشتری",
|
||||
4 => "مرجوع شده",
|
||||
5 => "لغو شده",
|
||||
6 => "آماده تحویل در دفتر",
|
||||
_ => "بدون ارسال / نامشخص"
|
||||
};
|
||||
}
|
||||
@@ -178,5 +185,61 @@ public partial class UserOrderDetailsDialog
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
<MudSelectItem T="int?" Value="@(3)">تحویل به مشتری</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(4)">مرجوع شده</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(5)">لغو شده</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(6)">آماده تحویل در دفتر</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
@@ -113,13 +114,13 @@
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Success">مجموع فروش</MudText>
|
||||
<MudText Typo="Typo.h5">@_totalSales.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.h5">@_totalSales.ToString("N0") تومان</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudPaper Elevation="1" Class="pa-4 text-center">
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Info">میانگین مبلغ سفارش</MudText>
|
||||
<MudText Typo="Typo.h5">@_averageOrder.ToString("N0") ریال</MudText>
|
||||
<MudText Typo="Typo.h5">@_averageOrder.ToString("N0") تومان</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
@@ -237,7 +238,7 @@
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="مبلغ">
|
||||
<CellTemplate>
|
||||
@context.Item.Amount.ToString("N0") ریال
|
||||
@context.Item.Amount.ToString("N0") تومان
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="وضعیت پرداخت">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text;
|
||||
using BackOffice.Common.Utilities;
|
||||
using CMSMicroservice.Protobuf.Protos;
|
||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -215,7 +216,8 @@ public partial class OrderSalesReports
|
||||
2 => Color.Info,
|
||||
3 => Color.Success,
|
||||
4 => Color.Error,
|
||||
5 => Color.Error,
|
||||
5 => Color.Dark,
|
||||
6 => Color.Primary,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
@@ -229,6 +231,7 @@ public partial class OrderSalesReports
|
||||
3 => "تحویل به مشتری",
|
||||
4 => "مرجوع شده",
|
||||
5 => "لغو شده",
|
||||
6 => "آماده تحویل در دفتر",
|
||||
_ => "بدون ارسال / نامشخص"
|
||||
};
|
||||
}
|
||||
@@ -241,27 +244,26 @@ public partial class OrderSalesReports
|
||||
return;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("شناسه,کاربر,نام کاربر,مبلغ,وضعیت پرداخت,وضعیت ارسال,تاریخ پرداخت");
|
||||
|
||||
foreach (var o in _orders)
|
||||
var headers = new[]
|
||||
{
|
||||
"شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت"
|
||||
};
|
||||
|
||||
var rows = _orders.Select(o => new object?[]
|
||||
{
|
||||
sb.AppendLine(string.Join(",",
|
||||
o.Id,
|
||||
o.UserId,
|
||||
EscapeCsv(o.UserFullName),
|
||||
CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
|
||||
o.Amount,
|
||||
EscapeCsv(o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده"),
|
||||
EscapeCsv(GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())),
|
||||
o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-"));
|
||||
}
|
||||
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 base64 = Convert.ToBase64String(bytes);
|
||||
var filename = $"store-sales-{DateTime.Now:yyyyMMddHHmmss}.csv";
|
||||
|
||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
|
||||
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
|
||||
var filename = $"store-sales-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||
CsvExportHelper.ToExcelXmlBase64("گزارش فروش", headers, rows));
|
||||
Snackbar.Add("خروجی Excel آماده شد.", Severity.Success);
|
||||
}
|
||||
|
||||
protected async Task ExportToPdf()
|
||||
@@ -279,8 +281,8 @@ public partial class OrderSalesReports
|
||||
|
||||
sb.AppendLine("خلاصه:");
|
||||
sb.AppendLine($"تعداد سفارشها: {_totalOrders}");
|
||||
sb.AppendLine($"مجموع فروش: {_totalSales:N0} ریال");
|
||||
sb.AppendLine($"میانگین مبلغ سفارش: {_averageOrder:N0} ریال");
|
||||
sb.AppendLine($"مجموع فروش: {_totalSales:N0} تومان");
|
||||
sb.AppendLine($"میانگین مبلغ سفارش: {_averageOrder:N0} تومان");
|
||||
sb.AppendLine($"پرداخت شده: {_paidCount}");
|
||||
sb.AppendLine();
|
||||
|
||||
@@ -290,7 +292,7 @@ public partial class OrderSalesReports
|
||||
foreach (var o in _orders)
|
||||
{
|
||||
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();
|
||||
@@ -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"/>
|
||||
</MudItem>
|
||||
<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"/>
|
||||
</MudItem>
|
||||
<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"/>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
@@ -56,6 +56,7 @@
|
||||
<MudSelectItem T="int?" Value="@(3)">تحویل به مشتری</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(4)">مرجوع شده</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(5)">لغو شده</MudSelectItem>
|
||||
<MudSelectItem T="int?" Value="@(6)">آماده تحویل در دفتر</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</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.UserOrder;
|
||||
using BackOffice.Common.BaseComponents;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
@@ -213,6 +214,8 @@ public partial class UserOrderMainPage
|
||||
2 => Color.Info, // InTransit
|
||||
3 => Color.Success, // Delivered
|
||||
4 => Color.Error, // Returned
|
||||
5 => Color.Dark, // Cancelled
|
||||
6 => Color.Primary, // ReadyForOfficePickup
|
||||
_ => Color.Default // None / Unknown
|
||||
};
|
||||
}
|
||||
@@ -225,6 +228,8 @@ public partial class UserOrderMainPage
|
||||
2 => "تحویل پست",
|
||||
3 => "تحویل به مشتری",
|
||||
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)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
@@ -315,40 +329,31 @@ public partial class UserOrderMainPage
|
||||
return;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("شناسه,کاربر,نام کاربر,مبلغ,وضعیت پرداخت,وضعیت ارسال,روش پرداخت,تاریخ پرداخت");
|
||||
|
||||
foreach (var o in result.Models)
|
||||
var headers = new[]
|
||||
{
|
||||
"شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "روش پرداخت", "تاریخ پرداخت"
|
||||
};
|
||||
|
||||
var rows = result.Models.Select(o => new object?[]
|
||||
{
|
||||
sb.AppendLine(string.Join(",",
|
||||
o.Id,
|
||||
o.UserId,
|
||||
EscapeCsv(o.UserFullName),
|
||||
CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
|
||||
o.Amount,
|
||||
EscapeCsv(o.PaymentStatus.ToString()),
|
||||
EscapeCsv(GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())),
|
||||
EscapeCsv(GetPaymentMethodText(o.PaymentMethod.GetHashCode())),
|
||||
o.PaymentDate?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-"));
|
||||
}
|
||||
GetPaymentStatusText(o.PaymentStatus.GetHashCode()),
|
||||
GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
|
||||
GetPaymentMethodText(o.PaymentMethod.GetHashCode()),
|
||||
o.PaymentDate?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-"
|
||||
});
|
||||
|
||||
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
||||
var base64 = Convert.ToBase64String(bytes);
|
||||
var filename = $"user-orders-{DateTime.Now:yyyyMMddHHmmss}.csv";
|
||||
|
||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
|
||||
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
|
||||
var filename = $"user-orders-{DateTime.Now:yyyyMMddHHmmss}.xls";
|
||||
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
|
||||
CsvExportHelper.ToExcelXmlBase64("سفارشها", headers, rows));
|
||||
Snackbar.Add("خروجی Excel آماده شد.", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
@attribute [Authorize(Roles = "Administrator")]
|
||||
|
||||
@using CMSMicroservice.Protobuf.Protos.UserWallet
|
||||
@using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog
|
||||
@using CMSMicroservice.Protobuf.Protos.UserWalletHistory
|
||||
@using BackOffice.Pages.AutoComplete
|
||||
|
||||
@inject UserWalletContract.UserWalletContractClient WalletClient
|
||||
@inject UserWalletChangeLogContract.UserWalletChangeLogContractClient ChangeLogClient
|
||||
@inject UserWalletHistoryContract.UserWalletHistoryContractClient ChangeLogClient
|
||||
|
||||
<div>
|
||||
<MudText Typo="Typo.h5" Class="mb-3">مدیریت کیفپول کاربران</MudText>
|
||||
@@ -30,15 +30,27 @@
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
<PropertyColumn Property="x => x.UserId" Title="کاربر" />
|
||||
<PropertyColumn Property="x => x.UserId" Title="کاربر">
|
||||
<CellTemplate>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.body2"><strong>@context.Item.UserName</strong></MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">ID: @context.Item.UserId</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<TemplateColumn Title="موجودی اصلی">
|
||||
<CellTemplate>
|
||||
<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>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="موجودی شبکه">
|
||||
<TemplateColumn Title="پاداش های دریافتی">
|
||||
<CellTemplate>
|
||||
<MudText Color="Color.Success">@context.Item.NetworkBalance.ToString("N0") ریال</MudText>
|
||||
<MudText Color="Color.Success">@context.Item.NetworkBalance.ToString("N0") تومان</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="موجودی اعتباری">
|
||||
<CellTemplate>
|
||||
<MudText Color="Color.Tertiary">@context.Item.DiscountBalance.ToString("N0") تومان</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="عملیات" Sortable="false">
|
||||
@@ -78,7 +90,7 @@
|
||||
OnClick="ApplyLogFilter">جستجو</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<MudDataGrid T="GetAllUserWalletChangeLogByFilterResponseModel" @ref="_logGrid"
|
||||
<MudDataGrid T="GetAllUserWalletHistoryByFilterResponseModel" @ref="_logGrid"
|
||||
ServerData="LoadChangeLogs"
|
||||
Hover="true" Dense="true"
|
||||
Height="calc(100vh - 340px)">
|
||||
@@ -103,16 +115,26 @@
|
||||
<MudText Color="Color.Primary">@context.Item.CurrentBalance.ToString("N0")</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="تغییر شبکه">
|
||||
<TemplateColumn Title="تغییر پاداش های دریافتی">
|
||||
<CellTemplate>
|
||||
<MudText>@context.Item.ChangeNerworkValue.ToString("N0")</MudText>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="موجودی شبکه فعلی">
|
||||
<TemplateColumn Title="پاداش های دریافتی فعلی">
|
||||
<CellTemplate>
|
||||
<MudText Color="Color.Success">@context.Item.CurrentNetworkBalance.ToString("N0")</MudText>
|
||||
</CellTemplate>
|
||||
</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="شناسه مرجع">
|
||||
<CellTemplate>
|
||||
@(context.Item.RefrenceId?.ToString() ?? "—")
|
||||
@@ -128,7 +150,7 @@
|
||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
|
||||
</NoRecordsContent>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="GetAllUserWalletChangeLogByFilterResponseModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
<MudDataGridPager T="GetAllUserWalletHistoryByFilterResponseModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
|
||||
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
|
||||
RowsPerPageString="تعداد در صفحه" />
|
||||
</PagerContent>
|
||||
@@ -140,7 +162,7 @@
|
||||
@code {
|
||||
private int _activeTab;
|
||||
private MudDataGrid<GetAllUserWalletByFilterResponseModel>? _walletGrid;
|
||||
private MudDataGrid<GetAllUserWalletChangeLogByFilterResponseModel>? _logGrid;
|
||||
private MudDataGrid<GetAllUserWalletHistoryByFilterResponseModel>? _logGrid;
|
||||
|
||||
// Wallet filters
|
||||
private long? _filterUserId;
|
||||
@@ -176,14 +198,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<GridData<GetAllUserWalletChangeLogByFilterResponseModel>> LoadChangeLogs(GridState<GetAllUserWalletChangeLogByFilterResponseModel> state)
|
||||
private async Task<GridData<GetAllUserWalletHistoryByFilterResponseModel>> LoadChangeLogs(GridState<GetAllUserWalletHistoryByFilterResponseModel> state)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetAllUserWalletChangeLogByFilterRequest
|
||||
var request = new GetAllUserWalletHistoryByFilterRequest
|
||||
{
|
||||
PaginationState = new() { PageNumber = state.Page + 1, PageSize = state.PageSize },
|
||||
Filter = new GetAllUserWalletChangeLogByFilterFilter()
|
||||
Filter = new GetAllUserWalletHistoryByFilterFilter()
|
||||
};
|
||||
|
||||
if (_logFilterUserId.HasValue && _logFilterUserId.Value > 0)
|
||||
@@ -192,8 +214,8 @@
|
||||
if (_logFilterIsIncrease.HasValue)
|
||||
request.Filter.IsIncrease = _logFilterIsIncrease.Value;
|
||||
|
||||
var result = await ChangeLogClient.GetAllUserWalletChangeLogByFilterAsync(request);
|
||||
return new GridData<GetAllUserWalletChangeLogByFilterResponseModel>
|
||||
var result = await ChangeLogClient.GetAllUserWalletHistoryByFilterAsync(request);
|
||||
return new GridData<GetAllUserWalletHistoryByFilterResponseModel>
|
||||
{
|
||||
Items = result?.Models?.ToList() ?? new(),
|
||||
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
|
||||
@@ -202,7 +224,7 @@
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||
return new GridData<GetAllUserWalletChangeLogByFilterResponseModel>();
|
||||
return new GridData<GetAllUserWalletHistoryByFilterResponseModel>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ public class DiscountOrderService : IDiscountOrderService
|
||||
DiscountBalanceUsed = response.DiscountBalanceUsed,
|
||||
GatewayAmount = response.GatewayAmount,
|
||||
PaymentCompleted = response.PaymentCompleted,
|
||||
PaymentStatusValue = (int)response.PaymentStatus,
|
||||
TransactionId = response.TransactionId,
|
||||
Status = MapFromProtoStatus(response.DeliveryStatus, response.PaymentCompleted),
|
||||
TrackingCode = response.TrackingCode,
|
||||
@@ -200,12 +201,13 @@ public class DiscountOrderService : IDiscountOrderService
|
||||
return status switch
|
||||
{
|
||||
OrderStatus.Pending => ProtoDeliveryStatus.DeliveryPending,
|
||||
OrderStatus.Paid => ProtoDeliveryStatus.DeliveryPending, // Paid but not processed yet
|
||||
OrderStatus.Paid => ProtoDeliveryStatus.DeliveryPending,
|
||||
OrderStatus.Processing => ProtoDeliveryStatus.DeliveryProcessing,
|
||||
OrderStatus.Shipped => ProtoDeliveryStatus.DeliveryShipped,
|
||||
OrderStatus.Delivered => ProtoDeliveryStatus.DeliveryDelivered,
|
||||
OrderStatus.Cancelled => ProtoDeliveryStatus.DeliveryCancelled,
|
||||
OrderStatus.Returned => ProtoDeliveryStatus.DeliveryCancelled, // Treat as cancelled
|
||||
OrderStatus.ReadyForOfficePickup => ProtoDeliveryStatus.DeliveryReadyForOffice,
|
||||
OrderStatus.Returned => ProtoDeliveryStatus.DeliveryReturned,
|
||||
_ => ProtoDeliveryStatus.DeliveryPending
|
||||
};
|
||||
}
|
||||
@@ -221,6 +223,8 @@ public class DiscountOrderService : IDiscountOrderService
|
||||
ProtoDeliveryStatus.DeliveryShipped => OrderStatus.Shipped,
|
||||
ProtoDeliveryStatus.DeliveryDelivered => OrderStatus.Delivered,
|
||||
ProtoDeliveryStatus.DeliveryCancelled => OrderStatus.Cancelled,
|
||||
ProtoDeliveryStatus.DeliveryReadyForOffice => OrderStatus.ReadyForOfficePickup,
|
||||
ProtoDeliveryStatus.DeliveryReturned => OrderStatus.Returned,
|
||||
_ => OrderStatus.Pending
|
||||
};
|
||||
}
|
||||
@@ -235,6 +239,8 @@ public class DiscountOrderService : IDiscountOrderService
|
||||
ProtoDeliveryStatus.DeliveryShipped => OrderStatus.Shipped,
|
||||
ProtoDeliveryStatus.DeliveryDelivered => OrderStatus.Delivered,
|
||||
ProtoDeliveryStatus.DeliveryCancelled => OrderStatus.Cancelled,
|
||||
ProtoDeliveryStatus.DeliveryReadyForOffice => OrderStatus.ReadyForOfficePickup,
|
||||
ProtoDeliveryStatus.DeliveryReturned => OrderStatus.Returned,
|
||||
_ => OrderStatus.Pending
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ public enum OrderStatus
|
||||
Shipped = 3, // DELIVERY_SHIPPED
|
||||
Delivered = 4, // DELIVERY_DELIVERED
|
||||
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
|
||||
@@ -62,11 +63,17 @@ public class DiscountOrderDetailsDto
|
||||
public long Id { get; set; }
|
||||
public long UserId { get; set; }
|
||||
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 long TotalPrice { get; set; }
|
||||
public long DiscountBalanceUsed { get; set; }
|
||||
public long GatewayAmount { get; set; }
|
||||
public long VatAmount { 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 OrderStatus Status { get; set; }
|
||||
public string? TrackingCode { get; set; }
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
// Payment
|
||||
"payment" => "پرداخت",
|
||||
"transactions" => "تراکنشها",
|
||||
"ledger-transactions" => "دفتر تراکنشهای مالی",
|
||||
"manual-payments" => "پرداخت دستی",
|
||||
|
||||
// Network
|
||||
|
||||
@@ -75,7 +75,12 @@
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/club/members"
|
||||
Icon="@Icons.Material.Filled.Groups">
|
||||
اعضا و آمار
|
||||
اعضا و گزارشها
|
||||
</MudNavLink>
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/club/customer-packages"
|
||||
Icon="@Icons.Material.Filled.PeopleAlt">
|
||||
گزارش مشتریان پکیج
|
||||
</MudNavLink>
|
||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||
Href="/club/features"
|
||||
@@ -200,6 +205,24 @@
|
||||
</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"
|
||||
Href="/wallets"
|
||||
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;'
|
||||
@@ -94,8 +94,9 @@
|
||||
.admin-node-card .node-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 2px;
|
||||
gap: 4px;
|
||||
margin-top: 3px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.admin-node-card .level-badge {
|
||||
@@ -124,8 +125,8 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 6px;
|
||||
padding-top: 6px;
|
||||
margin-top: 5px;
|
||||
padding-top: 5px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
font-size: 10px;
|
||||
}
|
||||
@@ -204,6 +205,48 @@
|
||||
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 {
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.2); opacity: 0.8; }
|
||||
|
||||
@@ -39,8 +39,8 @@ window.AdminOrgChart = {
|
||||
this.chart = new d3.OrgChart()
|
||||
.container('#' + containerId)
|
||||
.data(data)
|
||||
.nodeWidth((d) => 160)
|
||||
.nodeHeight((d) => 80)
|
||||
.nodeWidth((d) => 178)
|
||||
.nodeHeight((d) => 92)
|
||||
.childrenMargin((d) => 60)
|
||||
.compactMarginBetween((d) => 20)
|
||||
.compactMarginPair((d) => 20)
|
||||
@@ -83,12 +83,19 @@ window.AdminOrgChart = {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Highlight badge for target week matches
|
||||
let highlightBadge = '';
|
||||
if (weekFilterActive && isTargetWeek) {
|
||||
highlightBadge = '<div class="target-week-highlight">✨</div>';
|
||||
// نوع فعالسازی - inline pill در meta row
|
||||
let activationTypePill = '';
|
||||
if (data.isNewActivation === true) {
|
||||
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
|
||||
const firstChar = data.userName ? data.userName.charAt(0).toUpperCase() : '?';
|
||||
|
||||
@@ -114,6 +121,14 @@ window.AdminOrgChart = {
|
||||
if (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) {
|
||||
tooltipLines.push(isTargetWeek ? '🎯 فعال در هفته انتخابی' : '⚪ خارج از هفته انتخابی');
|
||||
}
|
||||
@@ -125,14 +140,15 @@ window.AdminOrgChart = {
|
||||
data-user-id="${data.userId}"
|
||||
title="${tooltipText}">
|
||||
${weekIndicator}
|
||||
${highlightBadge}
|
||||
<div class="node-header">
|
||||
<div class="node-avatar">${firstChar}</div>
|
||||
<div class="node-main-info">
|
||||
<div class="node-name">${data.userName || 'کاربر ' + data.userId}</div>
|
||||
${packageBadge}
|
||||
<div class="node-meta">
|
||||
${levelBadge}
|
||||
${legText ? `<span class="${legClass}">${legText}</span>` : ''}
|
||||
${activationTypePill}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
|
||||
const cacheNamePrefix = 'offline-cache-';
|
||||
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
|
||||
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) {
|
||||
console.info('Service worker: Install');
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.306",
|
||||
"rollForward": "latestPatch"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user