Compare commits
2 Commits
b2d676b555
...
f64b6be7da
| Author | SHA1 | Date | |
|---|---|---|---|
| f64b6be7da | |||
| 9185aa227d |
@@ -1,6 +1,6 @@
|
|||||||
# FrontOffice to CMS API Compatibility Analysis
|
# FrontOffice to CMS API Compatibility Analysis
|
||||||
|
|
||||||
**تاریخ:** 5 فوریه 2026
|
**تاریخ:** 6 فوریه 2026
|
||||||
**وضعیت:** در حال بررسی
|
**وضعیت:** در حال بررسی
|
||||||
|
|
||||||
## خلاصه اجرایی
|
## خلاصه اجرایی
|
||||||
@@ -40,8 +40,16 @@
|
|||||||
|------------|-------------------|---------------|---------|
|
|------------|-------------------|---------------|---------|
|
||||||
| `GetCustomerProducts()` | ProductService | ✅ موجود | **پیاده شد در Task قبل** |
|
| `GetCustomerProducts()` | ProductService | ✅ موجود | **پیاده شد در Task قبل** |
|
||||||
| `GetCustomerProductsByFilter()` | ProductService | ✅ موجود | **پیاده شد در Task قبل** |
|
| `GetCustomerProductsByFilter()` | ProductService | ✅ موجود | **پیاده شد در Task قبل** |
|
||||||
|
| `GetAllProductsByFilter()` | Products.razor | ✅ پیاده شد | **Public API - Feb 6, 2026** |
|
||||||
|
|
||||||
**نتیجه:** ✅ تمام Products APIs موجود است
|
**GetAllProductsByFilter Details:**
|
||||||
|
- از `GetCustomerProductsByFilterQuery` استفاده میکند
|
||||||
|
- پشتیبانی از فیلترها: Title, Price, Discount, CategoryId, SaleCount, و...
|
||||||
|
- Sorting: پشتیبانی کامل (مثلاً "price desc")
|
||||||
|
- Pagination: با MetaData کامل
|
||||||
|
- CategoryIds: لیست شناسه دستهبندیهای محصول
|
||||||
|
|
||||||
|
**نتیجه:** ✅ تمام Products APIs موجود و پیاده شده
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -51,12 +59,18 @@
|
|||||||
|
|
||||||
| API Method | استفاده در Service | Status در CMS | یادداشت |
|
| API Method | استفاده در Service | Status در CMS | یادداشت |
|
||||||
|------------|-------------------|---------------|---------|
|
|------------|-------------------|---------------|---------|
|
||||||
| `GetAllCategories()` | CategoryService | ✅ موجود | Admin API: `GetAllCategoryByFilter()` |
|
| `GetAllCategoriesForCustomer()` | CategoryService | ✅ پیاده شد | **Customer API - Feb 6, 2026** |
|
||||||
| `GetCategoryById()` | CategoryService | ✅ موجود | Admin API: `GetCategory()` |
|
| `GetCategoryById()` | CategoryService | ✅ موجود | Admin API: `GetCategory()` |
|
||||||
|
|
||||||
**یادداشت:** CategoryService در FrontOffice از Admin APIs استفاده میکند (بدون احراز هویت). این مشکلی ندارد چون Categories عمومی هستند.
|
**GetAllCategoriesForCustomer Details:**
|
||||||
|
- از `GetAllCategoryByFilterQuery` استفاده میکند
|
||||||
|
- فقط دستهبندیهای فعال (IsActive = true)
|
||||||
|
- مرتبسازی بر اساس SortOrder
|
||||||
|
- پشتیبانی Pagination (default: PageSize=100)
|
||||||
|
- شامل: Id, Name, Title, Description, ImagePath, ParentId, IsActive, SortOrder
|
||||||
|
- ISender به CategoryService اضافه شد
|
||||||
|
|
||||||
**نتیجه:** ✅ Category APIs موجود است
|
**نتیجه:** ✅ تمام Category APIs پیاده شده
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -66,12 +80,127 @@
|
|||||||
|
|
||||||
| API Method | استفاده در Service | Status در CMS | یادداشت |
|
| API Method | استفاده در Service | Status در CMS | یادداشت |
|
||||||
|------------|-------------------|---------------|---------|
|
|------------|-------------------|---------------|---------|
|
||||||
| `GetAllUserOrderByFilter()` | OrderService | ✅ موجود | Proto: `GetCustomerOrders()` **پیاده شد** |
|
| `GetAllUserOrderByFilter()` | OrderService, Orders.razor | ✅ پیاده شد | **Feb 6, 2026** - Admin API |
|
||||||
| `GetUserOrder()` | OrderService | ✅ موجود | Proto: `GetCustomerOrder()` **پیاده شد** |
|
| `GetUserOrder()` | OrderService, OrderDetail.razor | ✅ پیاده شد | **Feb 6, 2026** - جزئیات کامل سفارش |
|
||||||
| `GetUserOrderHistory()` | OrderService | ⚠️ نیاز به بررسی | Proto: `GetCustomerOrderHistory()` **پیاده شد** |
|
| `GetCustomerOrders()` | OrderService | ✅ موجود | Customer API با فیلتر UserId |
|
||||||
| `PlaceOrder()` (Checkout) | Checkout.razor | ❓ نیاز به بررسی | باید از Transaction یا UserOrder باشد |
|
| `GetCustomerOrder()` | OrderService | ✅ موجود | Customer API با فیلتر UserId |
|
||||||
|
| `GetUserOrderHistory()` | OrderService | ✅ موجود | Proto: `GetCustomerOrderHistory()` |
|
||||||
|
| `GetVATRate()` | VATService, OrderService | ✅ پیاده شد | **Feb 6, 2026** |
|
||||||
|
| `SubmitShopBuyOrder()` | CheckoutSummary.razor | ✅ پیاده شد | **Feb 6, 2026** - تکمیل فرآیند خرید |
|
||||||
|
|
||||||
**نتیجه:** ⚠️ نیاز به بررسی PlaceOrder workflow
|
**GetVATRate Details:**
|
||||||
|
- نرخ مالیات بر ارزش افزوده ایران: 9%
|
||||||
|
- `VatRate = 0.09` (decimal)
|
||||||
|
- `VatPercentage = 9` (int)
|
||||||
|
- `IsEnabled = true`
|
||||||
|
- استفاده در VATService برای محاسبه مالیات محصولات
|
||||||
|
|
||||||
|
**SubmitShopBuyOrder Details (Feb 6, 2026 - Updated with Wallet Payment):**
|
||||||
|
تبدیل سبد خرید به سفارش نهایی با پرداخت از کیف پول:
|
||||||
|
|
||||||
|
1. **احراز هویت**: استخراج UserId از JWT Token (ICurrentUserService)
|
||||||
|
2. **اعتبارسنجی سبد خرید**:
|
||||||
|
- بازیابی محصولات سبد خرید با Include(Product)
|
||||||
|
- چک کردن خالی نبودن سبد
|
||||||
|
3. **اعتبارسنجی آدرس**:
|
||||||
|
- دریافت آدرس پیشفرض کاربر
|
||||||
|
- اجباری بودن وجود آدرس
|
||||||
|
4. **محاسبات مالی**:
|
||||||
|
- مبلغ پایه: جمع (قیمت × تعداد) تمام آیتمها
|
||||||
|
- مالیات: 9% از مبلغ پایه
|
||||||
|
- مبلغ کل: مبلغ پایه + مالیات
|
||||||
|
- اعتبارسنجی مبلغ: |serverTotal - clientTotal| < 100
|
||||||
|
5. **اعتبارسنجی کیف پول (New - Feb 6)**:
|
||||||
|
- بازیابی کیف پول کاربر (UserWallet)
|
||||||
|
- چک موجودی: Balance >= TotalAmount
|
||||||
|
- خطا در صورت کمبود موجودی با نمایش موجودی فعلی و مبلغ مورد نیاز
|
||||||
|
6. **ایجاد تراکنش (New - Feb 6)**:
|
||||||
|
- Type: TransactionType.Buy (0)
|
||||||
|
- Amount: TotalAmount
|
||||||
|
- PaymentStatus: Success
|
||||||
|
- PaymentDate: DateTime.UtcNow
|
||||||
|
- RefId: SHOP_{timestamp}
|
||||||
|
- Description: "خرید محصولات - سفارش #{OrderId}"
|
||||||
|
7. **کسر از کیف پول (New - Feb 6)**:
|
||||||
|
- Balance -= TotalAmount
|
||||||
|
- ثبت موجودی جدید در UserWallet
|
||||||
|
8. **لاگ تغییرات کیف پول (New - Feb 6)**:
|
||||||
|
- CurrentBalance: موجودی جدید
|
||||||
|
- ChangeValue: -TotalAmount (منفی برای برداشت)
|
||||||
|
- CurrentNetworkBalance: بدون تغییر
|
||||||
|
- CurrentDiscountBalance: بدون تغییر
|
||||||
|
- IsIncrease: false (برداشت)
|
||||||
|
- RefrenceId: TransactionId
|
||||||
|
9. **ایجاد سفارش (UserOrder) - Updated**:
|
||||||
|
- TransactionId: لینک به تراکنش (New)
|
||||||
|
- PaymentStatus: Success (Changed from Pending)
|
||||||
|
- PaymentDate: DateTime.UtcNow (New)
|
||||||
|
- PaymentMethod: Wallet (New)
|
||||||
|
- DeliveryStatus: Pending
|
||||||
|
- HasVAT: true
|
||||||
|
10. **ثبت مالیات (OrderVAT)**:
|
||||||
|
- VATRate: 0.09m (decimal)
|
||||||
|
- BaseAmount: مبلغ قبل از مالیات
|
||||||
|
- VATAmount: مبلغ مالیات
|
||||||
|
- TotalAmount: مبلغ کل
|
||||||
|
11. **جزئیات فاکتور (FactorDetails)**:
|
||||||
|
- یک رکورد برای هر آیتم سبد خرید
|
||||||
|
- ذخیره ProductId, Count, UnitPrice, UnitDiscountPrice
|
||||||
|
12. **پاکسازی سبد خرید**:
|
||||||
|
- Soft delete تمام آیتمهای سبد (IsDeleted = true)
|
||||||
|
|
||||||
|
**Transaction Flow:**
|
||||||
|
```
|
||||||
|
User → Cart → SubmitShopBuyOrder →
|
||||||
|
1. Validate Cart
|
||||||
|
2. Validate Address
|
||||||
|
3. Calculate Amount (Base + 9% VAT)
|
||||||
|
4. Validate Wallet Balance
|
||||||
|
5. Create Transaction (Type=Buy, Status=Success)
|
||||||
|
6. Deduct from Wallet.Balance
|
||||||
|
7. Create UserWalletChangeLog (audit trail)
|
||||||
|
8. Create Order (linked to Transaction, PaymentStatus=Success, PaymentMethod=Wallet)
|
||||||
|
9. Create OrderVAT
|
||||||
|
10. Create FactorDetails
|
||||||
|
11. Clear Cart
|
||||||
|
→ Return OrderId
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wallet Types:**
|
||||||
|
- **Balance** (موجودی عادی): Used for purchases - deducted in this flow
|
||||||
|
- **NetworkBalance** (موجودی شبکه): Commission wallet - not touched
|
||||||
|
- **DiscountBalance** (موجودی تخفیف): Discount-only wallet - not touched
|
||||||
|
|
||||||
|
**Error Handling:**
|
||||||
|
- "کیف پول یافت نشد": User has no wallet record
|
||||||
|
- "موجودی کیف پول کافی نیست. موجودی: X تومان، مورد نیاز: Y تومان": Insufficient funds
|
||||||
|
|
||||||
|
**خروجی**: شناسه سفارش (OrderId) برای redirect به صفحه جزئیات
|
||||||
|
|
||||||
|
**GetUserOrder Details (Feb 6, 2026):**
|
||||||
|
نمایش جزئیات کامل یک سفارش:
|
||||||
|
- اطلاعات سفارش: Id, Amount, PaymentStatus, PaymentDate, DeliveryStatus
|
||||||
|
- اطلاعات کاربر: UserFullName, UserNationalCode
|
||||||
|
- آدرس: UserAddressText
|
||||||
|
- مالیات (OrderVAT): VATRate, BaseAmount, VATAmount, TotalAmount, IsPaid
|
||||||
|
- ردیابی: TrackingCode, DeliveryDescription
|
||||||
|
- محصولات (FactorDetails): ProductId, ProductTitle, ProductThumbnailPath, UnitPrice, Count, UnitDiscountPrice
|
||||||
|
|
||||||
|
**اصلاحات صفحه OrderDetail.razor:**
|
||||||
|
- ✅ رفع NullReferenceException برای PaymentDate
|
||||||
|
- ✅ نمایش "تاریخ ثبت" برای سفارشات Pending (بدون PaymentDate)
|
||||||
|
- ✅ رفع نمایش اشتباه ProductThumbnailPath به جای ProductTitle
|
||||||
|
- ✅ رفع خطاهای nullable value access (.Value → ?? 0)
|
||||||
|
- ✅ محاسبه صحیح subtotal با nullable handling
|
||||||
|
|
||||||
|
**GetAllUserOrderByFilter Details (Feb 6, 2026):**
|
||||||
|
لیست تمام سفارشات با فیلترهای پیشرفته:
|
||||||
|
- فیلترها: UserId (optional - 0 = همه کاربران), PaymentStatus, DeliveryStatus, PaymentDate
|
||||||
|
- Pagination: MetaData کامل
|
||||||
|
- Sorting: بر اساس فیلدهای مختلف
|
||||||
|
- جزئیات هر سفارش: اطلاعات کاربر، آدرس، مالیات، محصولات، وضعیت ارسال
|
||||||
|
|
||||||
|
**نتیجه:** ✅ تمام UserOrder APIs پیاده شده - فرآیند خرید کامل است
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -81,13 +210,32 @@
|
|||||||
|
|
||||||
| API Method | استفاده در Service | Status در CMS | یادداشت |
|
| API Method | استفاده در Service | Status در CMS | یادداشت |
|
||||||
|------------|-------------------|---------------|---------|
|
|------------|-------------------|---------------|---------|
|
||||||
| `GetCustomerWallet()` | WalletService | ✅ موجود | **پیاده شد در Task قبل** |
|
| `GetCustomerWallet()` | WalletService | ✅ موجود | 3 نوع کیف پول: Balance, NetworkBalance, DiscountBalance |
|
||||||
| `GetCustomerWalletChangeLog()` | WalletService | ✅ موجود | **پیاده شد در Task قبل** |
|
| `GetCustomerWalletChangeLog()` | WalletService | ✅ موجود | 6 فیلد موجودی: Current+Change برای هر 3 کیف پول |
|
||||||
| `CustomerWithdrawBalance()` | WalletService | ✅ موجود | **پیاده شد در Task قبل** |
|
| `CustomerWithdrawBalance()` | WalletService | ✅ موجود | Proto موجود است |
|
||||||
| `GetCustomerWithdrawals()` | WithdrawalRequests.razor | ✅ موجود | **پیاده شد در Task قبل** |
|
| `GetCustomerWithdrawals()` | WithdrawalRequests.razor | ✅ موجود | لیست درخواستهای برداشت |
|
||||||
| `GetCustomerWithdrawalSettings()` | WalletService | ✅ موجود | **پیاده شد در Task قبل** |
|
| `GetCustomerWithdrawalSettings()` | WalletService | ✅ موجود | حداقل مبلغ برداشت |
|
||||||
|
|
||||||
**نتیجه:** ✅ تمام UserWallet APIs موجود است
|
**سه نوع کیف پول:**
|
||||||
|
1. **عادی (Regular)**: Balance & ChangeValue - برای خرید و شارژ عادی
|
||||||
|
2. **شبکه (Network)**: NetworkBalance & ChangeNerworkValue - پاداش تیمی و کمیسیون
|
||||||
|
3. **تخفیفی (Discount)**: DiscountBalance & ChangeDiscountValue - برای خرید تخفیفی
|
||||||
|
|
||||||
|
**ساختار تراکنش (CustomerWalletChangeLogModel):**
|
||||||
|
- `CurrentBalance` + `ChangeValue` - موجودی و تغییر کیف پول عادی
|
||||||
|
- `CurrentNetworkBalance` + `ChangeNerworkValue` - موجودی و تغییر کیف پول شبکه
|
||||||
|
- `CurrentDiscountBalance` + `ChangeDiscountValue` - موجودی و تغییر کیف پول تخفیفی
|
||||||
|
- `IsIncrease` - آیا افزایش است یا کاهش
|
||||||
|
- `RefrenceId` - شناسه ارجاع (سفارش، پرداخت، و...)
|
||||||
|
- `CreatedAt` - تاریخ تراکنش (UTC Timestamp)
|
||||||
|
|
||||||
|
**UI تراکنشها:**
|
||||||
|
- Desktop: جدول با ستونهای جداگانه برای هر 3 کیف پول (تغییرات/مانده)
|
||||||
|
- Mobile: کارتها با 3 باکس افقی (عادی آبی، شبکه سبز، تخفیفی زرد)
|
||||||
|
- تاریخ: تبدیل UTC به Local Time و نمایش جلالی
|
||||||
|
- توضیحات: نمایش اینکه کدام کیف پولها تغییر کردهاند
|
||||||
|
|
||||||
|
**نتیجه:** ✅ تمام UserWallet APIs موجود و پیاده شده با UI کامل (Feb 5, 2026)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -117,7 +265,24 @@
|
|||||||
| `UpdateCustomerCartItem()` | CartService | ✅ پیاده شد | **Command Handler تکمیل شد - Feb 5** |
|
| `UpdateCustomerCartItem()` | CartService | ✅ پیاده شد | **Command Handler تکمیل شد - Feb 5** |
|
||||||
| `RemoveFromCustomerCart()` | CartService | ✅ پیاده شد | **Command Handler تکمیل شد - Feb 5** |
|
| `RemoveFromCustomerCart()` | CartService | ✅ پیاده شد | **Command Handler تکمیل شد - Feb 5** |
|
||||||
|
|
||||||
**نتیجه:** ✅ تمام UserCart Customer APIs پیاده شده
|
**اصلاحات Feb 6, 2026:**
|
||||||
|
- ✅ **رفع باگ Cart APIs در CheckoutSummary**: تمام صفحات از Admin APIs استفاده میکردند
|
||||||
|
- ✅ تغییر `AddNewUserCartAsync` → `AddNewUserCartForCustomerAsync`
|
||||||
|
- ✅ تغییر `UpdateUserCartAsync` → `UpdateUserCartForCustomerAsync`
|
||||||
|
- ✅ تغییر request model: `AddNewUserCartRequest` → `AddNewUserCartForCustomerRequest`
|
||||||
|
- ✅ تغییر request model: `UpdateUserCartRequest` → `UpdateUserCartForCustomerRequest`
|
||||||
|
- ✅ اضافه `RemoveUserCartForCustomerAsync` برای حذف صحیح آیتم
|
||||||
|
- ✅ اصلاح field name: `UserCartId` → `CartItemId` (Proto: cart_item_id)
|
||||||
|
- ✅ رفع منطق حذف: از Update با Count=0 به RemoveUserCartForCustomer تغییر یافت
|
||||||
|
|
||||||
|
**Field Naming Convention:**
|
||||||
|
- Proto: `cart_item_id` (snake_case)
|
||||||
|
- C# Generated: `CartItemId` (PascalCase)
|
||||||
|
- ❌ نباید: `UserCartId` (نام قدیمی Admin API)
|
||||||
|
|
||||||
|
**تاثیر:** حالا عملیات سبد خرید (افزودن/ویرایش/حذف) صحیح کار میکند و فقط سبد کاربر جاری را تغییر میدهد
|
||||||
|
|
||||||
|
**نتیجه:** ✅ تمام UserCart Customer APIs پیاده شده و باگهای Security و Field Naming رفع شد
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -135,7 +300,19 @@
|
|||||||
|
|
||||||
**یادداشت:** CityName و ProvinceName در response خالی است - FrontOffice باید از City API جداگانه استفاده کند.
|
**یادداشت:** CityName و ProvinceName در response خالی است - FrontOffice باید از City API جداگانه استفاده کند.
|
||||||
|
|
||||||
**نتیجه:** ✅ تمام UserAddress Customer APIs پیاده شده
|
**اصلاحات Feb 6, 2026:**
|
||||||
|
- ✅ **رفع باگ صفحه Addresses**: تمام صفحات FrontOffice از Admin APIs استفاده میکردند
|
||||||
|
- ✅ تغییر `GetAllUserAddressByFilter` → `GetCustomerAddresses` در Addresses.razor
|
||||||
|
- ✅ تغییر `CreateNewUserAddress` → `CreateCustomerAddress` در AddAddressDialog
|
||||||
|
- ✅ تغییر `UpdateUserAddress` → `UpdateCustomerAddress` در EditAddressDialog
|
||||||
|
- ✅ تغییر `DeleteUserAddress` → `DeleteCustomerAddress` در Addresses.razor
|
||||||
|
- ✅ تغییر `SetAddressAsDefault` → `SetCustomerDefaultAddress` در Addresses.razor
|
||||||
|
- ✅ اصلاح Model type: `GetAllUserAddressByFilterResponseModel` → `CustomerAddressModel`
|
||||||
|
- ✅ اصلاح field name: `response.Addresses` → `response.Models`
|
||||||
|
|
||||||
|
**تاثیر:** حالا کاربران فقط آدرسهای خودشان را میبینند (قبلاً همه آدرسها نمایش داده میشد)
|
||||||
|
|
||||||
|
**نتیجه:** ✅ تمام UserAddress Customer APIs پیاده شده و باگ Security رفع شد
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -173,11 +350,23 @@
|
|||||||
|
|
||||||
| API Method | استفاده در Service | Status در CMS | یادداشت |
|
| API Method | استفاده در Service | Status در CMS | یادداشت |
|
||||||
|------------|-------------------|---------------|---------|
|
|------------|-------------------|---------------|---------|
|
||||||
| `GetMyNetworkTree()` | NetworkMembershipService | ✅ موجود | **پیاده شد در Task قبل** |
|
| `GetMyNetworkTree()` | NetworkMembershipService | ✅ موجود | Customer Query جداگانه با ICurrentUserService |
|
||||||
| `GetSubordinateTree()` | NetworkMembershipService | ✅ موجود | **پیاده شد در Task قبل** |
|
| `GetSubordinateTree()` | NetworkMembershipService | ✅ موجود | Recursive tree traversal |
|
||||||
| `GetMyNetworkStatistics()` | NetworkStatisticsPage.razor | ✅ موجود | **پیاده شد در Task قبل** |
|
| `GetMyNetworkStatistics()` | NetworkStatisticsPage.razor | ✅ موجود | با شمارش recursive تمام descendants |
|
||||||
|
|
||||||
**نتیجه:** ✅ تمام NetworkMembership APIs موجود است
|
**اصلاحات انجام شده (Feb 5, 2026):**
|
||||||
|
1. ✅ **GetMyNetworkTree Customer Query**:
|
||||||
|
- ایجاد Query و Handler جداگانه برای Customer
|
||||||
|
- استفاده از ICurrentUserService به جای UserId در request
|
||||||
|
- رفع خطای Validation (UserId=0 قبلاً غیرمجاز بود)
|
||||||
|
|
||||||
|
2. ✅ **GetNetworkStatistics Bug Fix**:
|
||||||
|
- قبلاً: فقط direct children (depth=1) شمارش میشد
|
||||||
|
- بعد: recursive counting تمام descendants در leftLeg و rightLeg
|
||||||
|
- متدهای کمکی: `GetAllDescendants()` و `CalculateDepths()`
|
||||||
|
- فرمول: `leftLegCount = GetAllDescendants(leftChild).Count + 1`
|
||||||
|
|
||||||
|
**نتیجه:** ✅ تمام NetworkMembership APIs موجود و اصلاح شده
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -242,25 +431,31 @@
|
|||||||
5. ✅ Package APIs - تمام Customer endpoints پیاده شده
|
5. ✅ Package APIs - تمام Customer endpoints پیاده شده
|
||||||
6. ✅ NetworkMembership APIs - پیاده شده
|
6. ✅ NetworkMembership APIs - پیاده شده
|
||||||
7. ✅ Commission APIs - پیاده شده
|
7. ✅ Commission APIs - پیاده شده
|
||||||
8. ✅ Category APIs - از Admin API استفاده میکند (OK)
|
8. ✅ Category APIs - GetAllCategoriesForCustomer پیاده شد (Feb 6, 2026)
|
||||||
9. ✅ City APIs - Public API موجود
|
9. ✅ City APIs - Public API موجود
|
||||||
10. ✅ ClubMembership APIs - Proto موجود
|
10. ✅ ClubMembership APIs - Proto موجود
|
||||||
11. ✅ Configuration APIs - Proto موجود
|
11. ✅ Configuration APIs - Proto موجود
|
||||||
12. ✅ AppVersion APIs - Proto موجود
|
12. ✅ AppVersion APIs - Proto موجود
|
||||||
13. ✅ **UserCarts APIs - تمام Customer endpoints پیاده شد (Feb 5, 2026)** 🆕
|
13. ✅ **UserCarts APIs - تمام Customer endpoints پیاده شد (Feb 5, 2026) + اصلاحات Feb 6** 🆕
|
||||||
14. ✅ **UserAddress APIs - تمام Customer endpoints پیاده شد (Feb 5, 2026)** 🆕
|
14. ✅ **UserAddress APIs - تمام Customer endpoints پیاده شد (Feb 5, 2026) + باگ Security رفع شد Feb 6** 🆕
|
||||||
|
15. ✅ **UserOrder APIs - Checkout workflow کامل شد (Feb 6, 2026)** 🆕
|
||||||
|
16. ✅ **Products APIs - GetAllProductsByFilter پیاده شد (Feb 6, 2026)** 🆕
|
||||||
|
|
||||||
### ⚠️ نیاز به توجه
|
### ⚠️ نیاز به توجه
|
||||||
|
|
||||||
~~1. **UserCarts APIs** - نیاز به Customer-specific endpoints~~
|
~~1. **UserCarts APIs** - نیاز به Customer-specific endpoints~~
|
||||||
**✅ تکمیل شد - Feb 5, 2026**
|
**✅ تکمیل شد - Feb 5, 2026 + اصلاحات Feb 6, 2026**
|
||||||
|
|
||||||
~~2. **UserAddress APIs** - نیاز به Customer-specific endpoints~~
|
~~2. **UserAddress APIs** - نیاز به Customer-specific endpoints~~
|
||||||
**✅ تکمیل شد - Feb 5, 2026**
|
**✅ تکمیل شد - Feb 5, 2026 + باگ Security رفع شد Feb 6, 2026**
|
||||||
|
|
||||||
3. **UserOrder/Checkout APIs** - نیاز به بررسی:
|
~~3. **UserOrder/Checkout APIs** - نیاز به بررسی~~
|
||||||
- `PlaceOrder()` یا `CreateOrder()` - ❓ نیاز به بررسی workflow
|
**✅ تکمیل شد - Feb 6, 2026:**
|
||||||
- `CancelOrder()` - ❓ نیاز به بررسی
|
- ✅ SubmitShopBuyOrder - تبدیل سبد خرید به سفارش
|
||||||
|
- ✅ GetUserOrder - نمایش جزئیات سفارش
|
||||||
|
- ✅ GetAllUserOrderByFilter - لیست سفارشات
|
||||||
|
- ✅ GetVATRate - دریافت نرخ مالیات 9%
|
||||||
|
- ✅ OrderDetail.razor - رفع باگهای NullReference
|
||||||
|
|
||||||
4. **UpdateCustomerProfile, ChangeCustomerPassword, UpdateCustomerSettings** - Proto موجود اما Query/Handler نیاز است
|
4. **UpdateCustomerProfile, ChangeCustomerPassword, UpdateCustomerSettings** - Proto موجود اما Query/Handler نیاز است
|
||||||
|
|
||||||
@@ -277,6 +472,11 @@
|
|||||||
- ✅ RemoveFromCustomerCartCommand و Handler
|
- ✅ RemoveFromCustomerCartCommand و Handler
|
||||||
- ✅ UserCartsService با ISender
|
- ✅ UserCartsService با ISender
|
||||||
|
|
||||||
|
**✅ اصلاحات Security - Feb 6, 2026:**
|
||||||
|
- ✅ CartService.cs: تمام عملیات به Customer APIs تغییر یافت
|
||||||
|
- ✅ رفع باگ Field Naming: UserCartId → CartItemId
|
||||||
|
- ✅ رفع منطق حذف: از Update به RemoveUserCartForCustomer
|
||||||
|
|
||||||
~~### Priority 2: UserAddress Customer Endpoints~~
|
~~### Priority 2: UserAddress Customer Endpoints~~
|
||||||
~~این APIs برای Checkout و مدیریت آدرسها ضروری هستند.~~
|
~~این APIs برای Checkout و مدیریت آدرسها ضروری هستند.~~
|
||||||
**✅ تکمیل شد - Feb 5, 2026:**
|
**✅ تکمیل شد - Feb 5, 2026:**
|
||||||
@@ -288,7 +488,15 @@
|
|||||||
- ✅ UserAddressService با ISender
|
- ✅ UserAddressService با ISender
|
||||||
- ⚠️ **یادداشت:** CityName/ProvinceName در response خالی است - FrontOffice باید از City API استفاده کند
|
- ⚠️ **یادداشت:** CityName/ProvinceName در response خالی است - FrontOffice باید از City API استفاده کند
|
||||||
|
|
||||||
### Priority 3: Checkout/Order Creation
|
**✅ اصلاحات Security - Feb 6, 2026:**
|
||||||
|
- ✅ Addresses.razor: GetCustomerAddresses (قبلاً تمام آدرسها نمایش مییافت)
|
||||||
|
- ✅ Index.razor (Profile): GetCustomerAddresses
|
||||||
|
- ✅ CheckoutSummary.razor: GetCustomerAddresses
|
||||||
|
- ✅ Checkout.razor: GetCustomerAddresses
|
||||||
|
- ✅ AddAddressDialog.razor: CreateCustomerAddress
|
||||||
|
- ✅ EditAddressDialog.razor: UpdateCustomerAddress
|
||||||
|
|
||||||
|
~~### Priority 3: Checkout/Order Creation~~
|
||||||
باید workflow ثبت سفارش بررسی شود.
|
باید workflow ثبت سفارش بررسی شود.
|
||||||
|
|
||||||
### Priority 4: Customer Profile Updates
|
### Priority 4: Customer Profile Updates
|
||||||
@@ -298,14 +506,101 @@
|
|||||||
|
|
||||||
## وضعیت پروژه
|
## وضعیت پروژه
|
||||||
|
|
||||||
**تکمیل شده:** ~95%
|
**تکمیل شده:** ~97%
|
||||||
**آخرین بهروزرسانی:** 5 فوریه 2026
|
**آخرین بهروزرسانی:** 6 فوریه 2026
|
||||||
|
|
||||||
**تغییرات امروز:**
|
**تغییرات Feb 6, 2026:**
|
||||||
|
|
||||||
|
**Phase 1: رفع باگهای Critical Security در FrontOffice**
|
||||||
|
- ✅ **UserAddress Security Bug Fix**: تغییر از Admin APIs به Customer APIs در تمام صفحات
|
||||||
|
- Addresses.razor, Index.razor (Profile), CheckoutSummary.razor, Checkout.razor
|
||||||
|
- AddAddressDialog, EditAddressDialog
|
||||||
|
- قبلاً همه آدرسهای تمام کاربران نمایش داده میشد ⚠️
|
||||||
|
- حالا فقط آدرسهای کاربر لاگین شده (با ICurrentUserService)
|
||||||
|
|
||||||
|
- ✅ **UserCart Security Bug Fix**: تغییر از Admin APIs به Customer APIs در CartService
|
||||||
|
- تمام عملیات: Add, Update, Remove, Clear
|
||||||
|
- رفع باگ Field Naming: UserCartId → CartItemId (Proto: cart_item_id)
|
||||||
|
- رفع منطق حذف: از UpdateUserCart با Count=0 به RemoveUserCartForCustomer
|
||||||
|
- قبلاً تمام سبدهای خرید تمام کاربران قابل دسترسی بود ⚠️
|
||||||
|
|
||||||
|
**Phase 2: پیادهسازی APIs گمشده**
|
||||||
|
- ✅ **GetVATRate**: پیادهسازی در UserOrderService
|
||||||
|
- نرخ مالیات بر ارزش افزوده ایران: 9%
|
||||||
|
- استفاده در VATService و Products page
|
||||||
|
|
||||||
|
- ✅ **GetAllProductsByFilter**: پیادهسازی در ProductsService
|
||||||
|
- استفاده از GetCustomerProductsByFilterQuery
|
||||||
|
- پشتیبانی کامل از filtering, sorting, pagination
|
||||||
|
- CategoryIds mapping به درستی
|
||||||
|
|
||||||
|
- ✅ **GetAllCategoriesForCustomer**: پیادهسازی در CategoryService
|
||||||
|
- استفاده از GetAllCategoryByFilterQuery
|
||||||
|
- فقط دستهبندیهای فعال (IsActive = true)
|
||||||
|
- ISender به CategoryService اضافه شد
|
||||||
|
- مرتبسازی بر اساس SortOrder
|
||||||
|
|
||||||
|
**Phase 3: تکمیل Checkout Workflow**
|
||||||
|
- ✅ **SubmitShopBuyOrder**: تبدیل سبد خرید به سفارش نهایی با **پرداخت از کیف پول** (Updated Feb 6)
|
||||||
|
- احراز هویت با ICurrentUserService (UserId از JWT)
|
||||||
|
- اعتبارسنجی سبد خرید (خالی نباشد) و آدرس پیشفرض
|
||||||
|
- محاسبات مالی: مبلغ پایه + مالیات 9% = مبلغ کل
|
||||||
|
- **اعتبارسنجی موجودی کیف پول**: Balance >= TotalAmount 🆕
|
||||||
|
- **ایجاد تراکنش**: Type=Buy, PaymentStatus=Success, RefId=SHOP_{timestamp} 🆕
|
||||||
|
- **کسر از کیف پول**: Balance -= TotalAmount 🆕
|
||||||
|
- **ثبت لاگ تغییرات**: UserWalletChangeLog با تمام جزئیات (audit trail) 🆕
|
||||||
|
- ایجاد سفارش (UserOrder): **PaymentStatus=Success, PaymentMethod=Wallet, TransactionId** (Updated from Pending)
|
||||||
|
- ثبت مالیات (OrderVAT): VATRate, BaseAmount, VATAmount, TotalAmount
|
||||||
|
- ایجاد جزئیات فاکتور (FactorDetails) برای هر محصول
|
||||||
|
- پاکسازی سبد خرید (soft delete)
|
||||||
|
- بازگشت OrderId برای redirect
|
||||||
|
- **خطاها**: "کیف پول یافت نشد", "موجودی کیف پول کافی نیست"
|
||||||
|
|
||||||
|
- ✅ **GetUserOrder**: نمایش جزئیات کامل سفارش
|
||||||
|
- استفاده از GetCustomerOrderQuery
|
||||||
|
- اطلاعات سفارش + کاربر + آدرس + مالیات + محصولات + ردیابی
|
||||||
|
- پشتیبانی از nullable fields (PaymentDate, PaymentMethod)
|
||||||
|
|
||||||
|
- ✅ **GetAllUserOrderByFilter**: لیست سفارشات با فیلتر
|
||||||
|
- Admin API - میتواند همه سفارشات را ببیند
|
||||||
|
- فیلترها: UserId, PaymentStatus, DeliveryStatus, PaymentDate
|
||||||
|
- Pagination + Sorting کامل
|
||||||
|
|
||||||
|
- ✅ **OrderDetail.razor - رفع باگهای UI**:
|
||||||
|
- رفع NullReferenceException برای PaymentDate (null برای سفارشات Pending)
|
||||||
|
- نمایش "تاریخ ثبت" به جای "تاریخ پرداخت" برای سفارشات بدون پرداخت
|
||||||
|
- رفع نمایش ProductThumbnailPath به جای ProductTitle
|
||||||
|
- رفع خطاهای nullable value access: .Value → ?? 0
|
||||||
|
- محاسبه صحیح subtotal با null coalescing
|
||||||
|
|
||||||
|
**خلاصه تغییرات:**
|
||||||
|
- 🔒 **Security**: رفع باگهای critical در UserAddress و UserCart (همه کاربران قابل مشاهده بودند)
|
||||||
|
- 📦 **Products**: GetAllProductsByFilter + GetAllCategoriesForCustomer پیاده شد
|
||||||
|
- 💰 **VAT**: GetVATRate با نرخ 9% ایران
|
||||||
|
- 🛒 **Checkout**: workflow کامل - سبد خرید → سفارش → نمایش جزئیات
|
||||||
|
- 🐛 **Bug Fixes**: OrderDetail null handling + Field naming (UserCartId → CartItemId)
|
||||||
|
|
||||||
|
**تغییرات قبلی (Feb 5, 2026):**
|
||||||
|
|
||||||
|
**Phase 1: UserCart & UserAddress Customer Endpoints**
|
||||||
- ✅ پیادهسازی کامل UserCart Customer endpoints (4 Handler + Service)
|
- ✅ پیادهسازی کامل UserCart Customer endpoints (4 Handler + Service)
|
||||||
- ✅ پیادهسازی کامل UserAddress Customer endpoints (5 Handler + Service)
|
- ✅ پیادهسازی کامل UserAddress Customer endpoints (5 Handler + Service)
|
||||||
- ✅ اضافه کردن Proto definitions برای Customer Address
|
- ✅ اضافه کردن Proto definitions برای Customer Address
|
||||||
- ✅ Build موفقیتآمیز: 0 Errors
|
|
||||||
|
**Phase 2: NetworkMembership Bug Fixes**
|
||||||
|
- ✅ GetMyNetworkTree Customer Query (رفع خطای Validation)
|
||||||
|
- ✅ GetNetworkStatistics Recursive Counting (رفع باگ شمارش نادرست)
|
||||||
|
|
||||||
|
**Phase 3: UserWallet UI Enhancement**
|
||||||
|
- ✅ رفع باگ نمایش 0 در مبالغ تراکنشها
|
||||||
|
- ✅ اضافه کردن CurrentDiscountBalance و ChangeDiscountValue به Proto (v0.0.177)
|
||||||
|
- ✅ جداسازی تراکنشها به 3 نوع کیف پول (عادی، شبکه، تخفیفی)
|
||||||
|
- ✅ اصلاح نامگذاری: "اعتباری" → "عادی"
|
||||||
|
- ✅ رفع باگ تاریخ: اضافه کردن ToLocalTime() برای تبدیل UTC
|
||||||
|
- ✅ UI Desktop: جدول با ستونهای جداگانه برای هر 3 کیف پول
|
||||||
|
- ✅ UI Mobile: کارتها با 3 باکس افقی (عادی آبی، شبکه سبز، تخفیفی زرد)
|
||||||
|
- ✅ نمایش همزمان تغییرات و موجودی مانده برای هر کیف پول
|
||||||
|
- ✅ تغییر FrontOffice.Main.csproj: PackageReference → ProjectReference
|
||||||
|
|
||||||
**باقی مانده:**
|
**باقی مانده:**
|
||||||
- ⚠️ Checkout workflow و Order creation (نیاز به بررسی)
|
- ⚠️ Checkout workflow و Order creation (نیاز به بررسی)
|
||||||
@@ -313,6 +608,12 @@
|
|||||||
- 📝 CityName/ProvinceName در GetCustomerAddresses خالی است (نیاز به City API lookup در FrontOffice)
|
- 📝 CityName/ProvinceName در GetCustomerAddresses خالی است (نیاز به City API lookup در FrontOffice)
|
||||||
|
|
||||||
**Build Status:**
|
**Build Status:**
|
||||||
- ✅ 0 Errors
|
- ✅ CMS: 0 Errors, ~60 Warnings (unused proto imports)
|
||||||
- ⚠️ ~315 Warnings (nullable reference types - غیر بحرانی)
|
- ✅ FrontOffice: 0 Errors, ~120 Warnings (nullable references)
|
||||||
|
|
||||||
|
**صفحات تست شده (Feb 6):**
|
||||||
|
- ✅ /profile/addresses - کار میکند (فقط آدرسهای خود کاربر)
|
||||||
|
- ✅ /products - کار میکند (لیست محصولات با filtering و sorting)
|
||||||
|
- ✅ /categories - کار میکند (لیست دستهبندیهای فعال)
|
||||||
|
- ✅ /profile/wallet - کار میکند (3 کیف پول با تراکنشهای کامل)
|
||||||
|
|
||||||
|
|||||||
+25
-1
@@ -3,14 +3,18 @@ namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership
|
|||||||
public class GetClubMembershipQueryHandler : IRequestHandler<GetClubMembershipQuery, ClubMembershipDto?>
|
public class GetClubMembershipQueryHandler : IRequestHandler<GetClubMembershipQuery, ClubMembershipDto?>
|
||||||
{
|
{
|
||||||
private readonly IApplicationDbContext _context;
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly ILogger<GetClubMembershipQueryHandler> _logger;
|
||||||
|
|
||||||
public GetClubMembershipQueryHandler(IApplicationDbContext context)
|
public GetClubMembershipQueryHandler(IApplicationDbContext context, ILogger<GetClubMembershipQueryHandler> logger)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ClubMembershipDto?> Handle(GetClubMembershipQuery request, CancellationToken cancellationToken)
|
public async Task<ClubMembershipDto?> Handle(GetClubMembershipQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("GetClubMembership called for UserId: {UserId}", request.UserId);
|
||||||
|
|
||||||
var membership = await _context.ClubMemberships
|
var membership = await _context.ClubMemberships
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => x.UserId == request.UserId)
|
.Where(x => x.UserId == request.UserId)
|
||||||
@@ -27,6 +31,26 @@ public class GetClubMembershipQueryHandler : IRequestHandler<GetClubMembershipQu
|
|||||||
})
|
})
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
// اگر کاربر عضویت نداره، یک DTO با وضعیت غیرفعال برگردون
|
||||||
|
if (membership == null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("No membership found for UserId: {UserId}, returning inactive status", request.UserId);
|
||||||
|
return new ClubMembershipDto
|
||||||
|
{
|
||||||
|
Id = 0,
|
||||||
|
UserId = request.UserId,
|
||||||
|
IsActive = false,
|
||||||
|
ActivatedAt = null,
|
||||||
|
InitialContribution = 0,
|
||||||
|
TotalEarned = 0,
|
||||||
|
Created = DateTimeOffset.UtcNow,
|
||||||
|
LastModified = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Membership found for UserId: {UserId}, IsActive: {IsActive}, Id: {Id}",
|
||||||
|
request.UserId, membership.IsActive, membership.Id);
|
||||||
|
|
||||||
return membership;
|
return membership;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query برای دریافت پرداختهای کمیسیون کاربر جاری (از JWT)
|
||||||
|
/// </summary>
|
||||||
|
public record GetMyCommissionPayoutsQuery : IRequest<GetMyCommissionPayoutsResponseDto>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// فیلتر وضعیت
|
||||||
|
/// </summary>
|
||||||
|
public CommissionPayoutStatus? Status { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شماره هفته (اختیاری)
|
||||||
|
/// </summary>
|
||||||
|
public long? WeekDefinitionId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pagination
|
||||||
|
/// </summary>
|
||||||
|
public PaginationState? PaginationState { get; init; }
|
||||||
|
}
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Application.Common.Extensions;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts;
|
||||||
|
|
||||||
|
public class GetMyCommissionPayoutsQueryHandler : IRequestHandler<GetMyCommissionPayoutsQuery, GetMyCommissionPayoutsResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly ICurrentUserService _currentUser;
|
||||||
|
|
||||||
|
public GetMyCommissionPayoutsQueryHandler(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
ICurrentUserService currentUser)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_currentUser = currentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetMyCommissionPayoutsResponseDto> Handle(GetMyCommissionPayoutsQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// دریافت UserId از JWT (فقط برای Customer API)
|
||||||
|
if (!long.TryParse(_currentUser.UserId, out var userId))
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("User not authenticated");
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = _context.UserCommissionPayouts
|
||||||
|
.Include(x => x.WeekDefinition)
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.AsNoTracking()
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
// فیلترها
|
||||||
|
if (request.Status.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.Status == request.Status.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.WeekDefinitionId.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// مرتبسازی: جدیدترین اول
|
||||||
|
query = query.OrderByDescending(x => x.Created);
|
||||||
|
|
||||||
|
var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
|
||||||
|
|
||||||
|
var models = await query
|
||||||
|
.PaginatedListAsync(paginationState: request.PaginationState)
|
||||||
|
.Select(x => new GetMyCommissionPayoutsResponseModel
|
||||||
|
{
|
||||||
|
Id = x.Id,
|
||||||
|
WeekDefinitionId = x.WeekDefinitionId,
|
||||||
|
WeekDisplayName = x.WeekDefinition != null ? x.WeekDefinition.DisplayName : "",
|
||||||
|
BalancesEarned = x.BalancesEarned,
|
||||||
|
TotalAmount = x.TotalAmount,
|
||||||
|
AmountFormatted = x.TotalAmount.ToString("N0") + " تومان",
|
||||||
|
Status = x.Status,
|
||||||
|
CalculatedDate = x.PaidAt ?? (DateTime?)x.Created,
|
||||||
|
DatePersian = ""
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new GetMyCommissionPayoutsResponseDto
|
||||||
|
{
|
||||||
|
MetaData = meta,
|
||||||
|
Models = models
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts;
|
||||||
|
|
||||||
|
public class GetMyCommissionPayoutsQueryValidator : AbstractValidator<GetMyCommissionPayoutsQuery>
|
||||||
|
{
|
||||||
|
public GetMyCommissionPayoutsQueryValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.PaginationState)
|
||||||
|
.NotNull()
|
||||||
|
.WithMessage("Pagination state is required");
|
||||||
|
|
||||||
|
When(x => x.PaginationState != null, () =>
|
||||||
|
{
|
||||||
|
RuleFor(x => x.PaginationState!.PageNumber)
|
||||||
|
.GreaterThan(0)
|
||||||
|
.WithMessage("Page number must be greater than 0");
|
||||||
|
|
||||||
|
RuleFor(x => x.PaginationState!.PageSize)
|
||||||
|
.GreaterThan(0)
|
||||||
|
.LessThanOrEqualTo(100)
|
||||||
|
.WithMessage("Page size must be between 1 and 100");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts;
|
||||||
|
|
||||||
|
public class GetMyCommissionPayoutsResponseDto
|
||||||
|
{
|
||||||
|
public MetaData? MetaData { get; set; }
|
||||||
|
public List<GetMyCommissionPayoutsResponseModel> Models { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetMyCommissionPayoutsResponseModel
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public long WeekDefinitionId { get; set; }
|
||||||
|
public string WeekDisplayName { get; set; } = string.Empty;
|
||||||
|
public int BalancesEarned { get; set; }
|
||||||
|
public long TotalAmount { get; set; }
|
||||||
|
public string AmountFormatted { get; set; } = string.Empty;
|
||||||
|
public CommissionPayoutStatus Status { get; set; }
|
||||||
|
public DateTime? CalculatedDate { get; set; }
|
||||||
|
public string DatePersian { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyWeeklyBalances;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query برای دریافت تعادلهای هفتگی کاربر جاری (از JWT)
|
||||||
|
/// </summary>
|
||||||
|
public record GetMyWeeklyBalancesQuery : IRequest<GetUserWeeklyBalancesResponseDto>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه تعریف هفته (اختیاری)
|
||||||
|
/// </summary>
|
||||||
|
public long? WeekDefinitionId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فقط موارد Expired نشده؟
|
||||||
|
/// </summary>
|
||||||
|
public bool OnlyActive { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pagination
|
||||||
|
/// </summary>
|
||||||
|
public PaginationState? PaginationState { get; init; }
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyWeeklyBalances;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handler برای دریافت تعادلهای هفتگی کاربر جاری
|
||||||
|
/// </summary>
|
||||||
|
public class GetMyWeeklyBalancesQueryHandler : IRequestHandler<GetMyWeeklyBalancesQuery, GetUserWeeklyBalancesResponseDto>
|
||||||
|
{
|
||||||
|
private readonly ICurrentUserService _currentUserService;
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
private readonly ILogger<GetMyWeeklyBalancesQueryHandler> _logger;
|
||||||
|
|
||||||
|
public GetMyWeeklyBalancesQueryHandler(
|
||||||
|
ICurrentUserService currentUserService,
|
||||||
|
IMediator mediator,
|
||||||
|
ILogger<GetMyWeeklyBalancesQueryHandler> logger)
|
||||||
|
{
|
||||||
|
_currentUserService = currentUserService;
|
||||||
|
_mediator = mediator;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetUserWeeklyBalancesResponseDto> Handle(GetMyWeeklyBalancesQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// دریافت UserId از JWT
|
||||||
|
if (!long.TryParse(_currentUserService.UserId, out var userId) || userId <= 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetMyWeeklyBalances called without valid user authentication");
|
||||||
|
throw new UnauthorizedAccessException("کاربر احراز هویت نشده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("GetMyWeeklyBalances for UserId: {UserId}, WeekDefinitionId: {WeekDefinitionId}",
|
||||||
|
userId, request.WeekDefinitionId);
|
||||||
|
|
||||||
|
// فراخوانی GetUserWeeklyBalancesQuery با UserId از JWT
|
||||||
|
var query = new GetUserWeeklyBalancesQuery
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
WeekDefinitionId = request.WeekDefinitionId,
|
||||||
|
OnlyActive = request.OnlyActive,
|
||||||
|
PaginationState = request.PaginationState
|
||||||
|
};
|
||||||
|
|
||||||
|
return await _mediator.Send(query, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -22,6 +22,16 @@ public class GetCustomerWalletChangeLogResponseDto
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public long ChangeNerworkValue { get; set; }
|
public long ChangeNerworkValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// موجودی جاری تخفیف
|
||||||
|
/// </summary>
|
||||||
|
public long CurrentDiscountBalance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مقدار تغییر تخفیف
|
||||||
|
/// </summary>
|
||||||
|
public long ChangeDiscountValue { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// افزایشی است؟
|
/// افزایشی است؟
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -160,9 +160,11 @@ message CustomerWalletChangeLogModel
|
|||||||
int64 change_value = 2;
|
int64 change_value = 2;
|
||||||
int64 current_network_balance = 3;
|
int64 current_network_balance = 3;
|
||||||
int64 change_nerwork_value = 4;
|
int64 change_nerwork_value = 4;
|
||||||
bool is_increase = 5;
|
int64 current_discount_balance = 5;
|
||||||
google.protobuf.Int64Value refrence_id = 6;
|
int64 change_discount_value = 6;
|
||||||
google.protobuf.Timestamp created_at = 7;
|
bool is_increase = 7;
|
||||||
|
google.protobuf.Int64Value refrence_id = 8;
|
||||||
|
google.protobuf.Timestamp created_at = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CustomerWithdrawBalanceRequest
|
message CustomerWithdrawBalanceRequest
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships;
|
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships;
|
||||||
|
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership;
|
||||||
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics;
|
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics;
|
||||||
using CMSMicroservice.Protobuf.Protos.ClubMembership;
|
using CMSMicroservice.Protobuf.Protos.ClubMembership;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
@@ -11,6 +12,37 @@ public class ClubMembershipProfile : IRegister
|
|||||||
{
|
{
|
||||||
public void Register(TypeAdapterConfig config)
|
public void Register(TypeAdapterConfig config)
|
||||||
{
|
{
|
||||||
|
// =====================
|
||||||
|
// GetClubMembership
|
||||||
|
// =====================
|
||||||
|
|
||||||
|
// GetClubMembershipRequest -> GetClubMembershipQuery
|
||||||
|
config.NewConfig<GetClubMembershipRequest, GetClubMembershipQuery>()
|
||||||
|
.MapWith(src => new GetClubMembershipQuery
|
||||||
|
{
|
||||||
|
UserId = src.UserId
|
||||||
|
});
|
||||||
|
|
||||||
|
// ClubMembershipDto -> GetClubMembershipResponse
|
||||||
|
config.NewConfig<ClubMembershipDto, GetClubMembershipResponse>()
|
||||||
|
.MapWith(src => new GetClubMembershipResponse
|
||||||
|
{
|
||||||
|
Id = src.Id,
|
||||||
|
UserId = src.UserId,
|
||||||
|
PackageId = 0, // Not available
|
||||||
|
PackageName = string.Empty, // Not available
|
||||||
|
ActivationCode = string.Empty, // Not available
|
||||||
|
ActivatedAt = src.ActivatedAt.HasValue
|
||||||
|
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.ActivatedAt.Value, DateTimeKind.Utc))
|
||||||
|
: null,
|
||||||
|
ExpiresAt = null, // Not available
|
||||||
|
IsActive = src.IsActive,
|
||||||
|
Created = Timestamp.FromDateTimeOffset(src.Created),
|
||||||
|
Features = { }, // Empty for now
|
||||||
|
Status = src.IsActive ? "Active" : "Inactive",
|
||||||
|
DaysRemaining = 0 // Calculate if ExpiresAt is available
|
||||||
|
});
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// GetAllClubMemberships
|
// GetAllClubMemberships
|
||||||
// =====================
|
// =====================
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
using System.Linq;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
|
||||||
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetMyWeeklyBalances;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts;
|
||||||
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions;
|
||||||
using CMSMicroservice.Application.Common.Models;
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
using CMSMicroservice.Protobuf.Protos.Commission;
|
using CMSMicroservice.Protobuf.Protos.Commission;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
@@ -124,5 +128,77 @@ public class CommissionProfile : IRegister
|
|||||||
: null)
|
: null)
|
||||||
.Map(dest => dest.IsExpired, src => src.IsExpired)
|
.Map(dest => dest.IsExpired, src => src.IsExpired)
|
||||||
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created));
|
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created));
|
||||||
|
|
||||||
|
// GetMyCommissionPayouts Request Mapping
|
||||||
|
config.NewConfig<GetMyCommissionPayoutsRequest, GetMyCommissionPayoutsQuery>()
|
||||||
|
.Map(dest => dest.Status, src => src.Status != null && src.Status.Value >= 0
|
||||||
|
? (CommissionPayoutStatus?)src.Status.Value
|
||||||
|
: null)
|
||||||
|
.Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId != null ? src.WeekDefinitionId.Value : (long?)null)
|
||||||
|
.Map(dest => dest.PaginationState, src => new PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = src.PageNumber > 0 ? src.PageNumber : 1,
|
||||||
|
PageSize = src.PageSize > 0 ? src.PageSize : 10
|
||||||
|
});
|
||||||
|
|
||||||
|
// GetMyCommissionPayouts Response Mapping
|
||||||
|
config.NewConfig<GetMyCommissionPayoutsResponseDto, GetMyCommissionPayoutsResponse>()
|
||||||
|
.Map(dest => dest.MetaData, src => src.MetaData != null
|
||||||
|
? new CustomerMetaData { TotalCount = src.MetaData.TotalCount }
|
||||||
|
: new CustomerMetaData())
|
||||||
|
.Map(dest => dest.Payouts, src => src.Models);
|
||||||
|
|
||||||
|
// CustomerCommissionPayoutModel Mapping
|
||||||
|
config.NewConfig<GetMyCommissionPayoutsResponseModel, CustomerCommissionPayoutModel>()
|
||||||
|
.Map(dest => dest.Id, src => src.Id)
|
||||||
|
.Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId)
|
||||||
|
.Map(dest => dest.WeekDisplayName, src => src.WeekDisplayName)
|
||||||
|
.Map(dest => dest.BalancesEarned, src => src.BalancesEarned)
|
||||||
|
.Map(dest => dest.TotalAmount, src => src.TotalAmount)
|
||||||
|
.Map(dest => dest.AmountFormatted, src => src.AmountFormatted)
|
||||||
|
.Map(dest => dest.Status, src => (int)src.Status)
|
||||||
|
.Map(dest => dest.CalculatedDate, src => src.CalculatedDate.HasValue
|
||||||
|
? Timestamp.FromDateTime(src.CalculatedDate.Value.ToUniversalTime())
|
||||||
|
: null)
|
||||||
|
.Map(dest => dest.DatePersian, src => src.DatePersian);
|
||||||
|
|
||||||
|
// GetMyWeeklyBalances Request Mapping
|
||||||
|
config.NewConfig<GetMyWeeklyBalancesRequest, GetMyWeeklyBalancesQuery>()
|
||||||
|
.Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId != null ? src.WeekDefinitionId.Value : (long?)null)
|
||||||
|
.Map(dest => dest.OnlyActive, src => src.OnlyActive)
|
||||||
|
.Map(dest => dest.PaginationState, src => new PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = src.PageNumber > 0 ? src.PageNumber : 1,
|
||||||
|
PageSize = src.PageSize > 0 ? src.PageSize : 10
|
||||||
|
});
|
||||||
|
|
||||||
|
// GetMyWeeklyBalances Response Mapping
|
||||||
|
config.NewConfig<GetUserWeeklyBalancesResponseDto, GetMyWeeklyBalancesResponse>()
|
||||||
|
.Map(dest => dest.MetaData, src => src.MetaData != null
|
||||||
|
? new CustomerMetaData { TotalCount = src.MetaData.TotalCount }
|
||||||
|
: new CustomerMetaData())
|
||||||
|
.Map(dest => dest.Balances, src => src.Models)
|
||||||
|
.Map(dest => dest.TotalLeftBalances, src => src.Models.Sum(m => m.LeftLegTotal))
|
||||||
|
.Map(dest => dest.TotalRightBalances, src => src.Models.Sum(m => m.RightLegTotal))
|
||||||
|
.Map(dest => dest.WeakerLeg, src =>
|
||||||
|
src.Models.Sum(m => m.LeftLegTotal) < src.Models.Sum(m => m.RightLegTotal)
|
||||||
|
? "Left"
|
||||||
|
: src.Models.Sum(m => m.LeftLegTotal) > src.Models.Sum(m => m.RightLegTotal)
|
||||||
|
? "Right"
|
||||||
|
: "Equal");
|
||||||
|
|
||||||
|
// CustomerWeeklyBalanceModel Mapping
|
||||||
|
config.NewConfig<GetUserWeeklyBalancesResponseModel, CustomerWeeklyBalanceModel>()
|
||||||
|
.Map(dest => dest.Id, src => src.Id)
|
||||||
|
.Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId)
|
||||||
|
.Map(dest => dest.WeekDisplayName, src => src.WeekDisplayName)
|
||||||
|
.Map(dest => dest.LeftLegBalances, src => src.LeftLegTotal)
|
||||||
|
.Map(dest => dest.RightLegBalances, src => src.RightLegTotal)
|
||||||
|
.Map(dest => dest.TotalBalances, src => src.TotalBalances)
|
||||||
|
.Map(dest => dest.WeeklyPoolContribution, src => src.WeeklyPoolContribution)
|
||||||
|
.Map(dest => dest.IsExpired, src => src.IsExpired)
|
||||||
|
.Map(dest => dest.CalculatedAt, src => src.CalculatedAt.HasValue
|
||||||
|
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.CalculatedAt.Value, DateTimeKind.Utc))
|
||||||
|
: null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,19 @@ using CMSMicroservice.Application.CategoryCQ.Commands.UpdateCategory;
|
|||||||
using CMSMicroservice.Application.CategoryCQ.Commands.DeleteCategory;
|
using CMSMicroservice.Application.CategoryCQ.Commands.DeleteCategory;
|
||||||
using CMSMicroservice.Application.CategoryCQ.Queries.GetCategory;
|
using CMSMicroservice.Application.CategoryCQ.Queries.GetCategory;
|
||||||
using CMSMicroservice.Application.CategoryCQ.Queries.GetAllCategoryByFilter;
|
using CMSMicroservice.Application.CategoryCQ.Queries.GetAllCategoryByFilter;
|
||||||
|
using MediatR;
|
||||||
|
using AppModels = CMSMicroservice.Application.Common.Models;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
public class CategoryService : CategoryContract.CategoryContractBase
|
public class CategoryService : CategoryContract.CategoryContractBase
|
||||||
{
|
{
|
||||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||||
|
private readonly ISender _sender;
|
||||||
|
|
||||||
public CategoryService(IDispatchRequestToCQRS dispatchRequestToCQRS)
|
public CategoryService(IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender)
|
||||||
{
|
{
|
||||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||||
|
_sender = sender;
|
||||||
}
|
}
|
||||||
public override async Task<CreateNewCategoryResponse> CreateNewCategory(CreateNewCategoryRequest request, ServerCallContext context)
|
public override async Task<CreateNewCategoryResponse> CreateNewCategory(CreateNewCategoryRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
@@ -45,20 +50,52 @@ public class CategoryService : CategoryContract.CategoryContractBase
|
|||||||
|
|
||||||
public override async Task<GetAllCategoriesForCustomerResponse> GetAllCategoriesForCustomer(GetAllCategoriesForCustomerRequest request, ServerCallContext context)
|
public override async Task<GetAllCategoriesForCustomerResponse> GetAllCategoriesForCustomer(GetAllCategoriesForCustomerRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
// TODO: Implement using existing CMS Category Application layer
|
// Use GetAllCategoryByFilter to get all active categories
|
||||||
// For now, return empty response
|
var query = new GetAllCategoryByFilterQuery
|
||||||
return new GetAllCategoriesForCustomerResponse
|
{
|
||||||
|
Filter = new CMSMicroservice.Application.CategoryCQ.Queries.GetAllCategoryByFilter.GetAllCategoryByFilterFilter
|
||||||
|
{
|
||||||
|
IsActive = true // Only active categories for customers
|
||||||
|
},
|
||||||
|
PaginationState = new AppModels.PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = request.PageNumber > 0 ? request.PageNumber : 1,
|
||||||
|
PageSize = request.PageSize > 0 ? request.PageSize : 100 // Default to large page size to get all
|
||||||
|
},
|
||||||
|
SortBy = "SortOrder" // Sort by display order
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _sender.Send(query, context.CancellationToken);
|
||||||
|
|
||||||
|
var response = new GetAllCategoriesForCustomerResponse
|
||||||
{
|
{
|
||||||
MetaData = new CMSMicroservice.Protobuf.Protos.MetaData
|
MetaData = new CMSMicroservice.Protobuf.Protos.MetaData
|
||||||
{
|
{
|
||||||
CurrentPage = request.PageNumber,
|
CurrentPage = result.MetaData.CurrentPage,
|
||||||
PageSize = request.PageSize,
|
PageSize = result.MetaData.PageSize,
|
||||||
TotalCount = 0,
|
TotalCount = result.MetaData.TotalCount,
|
||||||
TotalPage = 0,
|
TotalPage = result.MetaData.TotalPage,
|
||||||
HasNext = false,
|
HasNext = result.MetaData.HasNext,
|
||||||
HasPrevious = false
|
HasPrevious = result.MetaData.HasPrevious
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
foreach (var cat in result.Models)
|
||||||
|
{
|
||||||
|
response.Models.Add(new GetAllCategoryFilterResponseModel
|
||||||
|
{
|
||||||
|
Id = cat.Id,
|
||||||
|
Name = cat.Name,
|
||||||
|
Title = cat.Title,
|
||||||
|
Description = cat.Description ?? string.Empty,
|
||||||
|
ImagePath = cat.ImagePath ?? string.Empty,
|
||||||
|
ParentId = cat.ParentId ?? 0,
|
||||||
|
IsActive = cat.IsActive,
|
||||||
|
SortOrder = cat.SortOrder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetCategoryByIdForCustomerResponse> GetCategoryByIdForCustomer(GetCategoryByIdForCustomerRequest request, ServerCallContext context)
|
public override async Task<GetCategoryByIdForCustomerResponse> GetCategoryByIdForCustomer(GetCategoryByIdForCustomerRequest request, ServerCallContext context)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
using CMSMicroservice.Protobuf.Protos.ClubMembership;
|
using CMSMicroservice.Protobuf.Protos.ClubMembership;
|
||||||
using CMSMicroservice.WebApi.Common.Services;
|
using CMSMicroservice.WebApi.Common.Services;
|
||||||
using CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership;
|
using CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership;
|
||||||
@@ -10,16 +11,24 @@ using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembershipHist
|
|||||||
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics;
|
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics;
|
||||||
using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures;
|
using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures;
|
||||||
using CMSMicroservice.Application.ClubFeatureCQ.Commands.ToggleUserClubFeature;
|
using CMSMicroservice.Application.ClubFeatureCQ.Commands.ToggleUserClubFeature;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
|
|
||||||
public class ClubMembershipService : ClubMembershipContract.ClubMembershipContractBase
|
public class ClubMembershipService : ClubMembershipContract.ClubMembershipContractBase
|
||||||
{
|
{
|
||||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||||
|
private readonly ICurrentUserService _currentUserService;
|
||||||
|
private readonly ILogger<ClubMembershipService> _logger;
|
||||||
|
|
||||||
public ClubMembershipService(IDispatchRequestToCQRS dispatchRequestToCQRS)
|
public ClubMembershipService(
|
||||||
|
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||||
|
ICurrentUserService currentUserService,
|
||||||
|
ILogger<ClubMembershipService> logger)
|
||||||
{
|
{
|
||||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||||
|
_currentUserService = currentUserService;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<Empty> ActivateClubMembership(ActivateClubMembershipRequest request, ServerCallContext context)
|
public override async Task<Empty> ActivateClubMembership(ActivateClubMembershipRequest request, ServerCallContext context)
|
||||||
@@ -39,6 +48,25 @@ public class ClubMembershipService : ClubMembershipContract.ClubMembershipContra
|
|||||||
|
|
||||||
public override async Task<GetClubMembershipResponse> GetClubMembership(GetClubMembershipRequest request, ServerCallContext context)
|
public override async Task<GetClubMembershipResponse> GetClubMembership(GetClubMembershipRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
|
// اگر UserId در request نیست یا صفر است، از JWT بخوان
|
||||||
|
if (request.UserId <= 0)
|
||||||
|
{
|
||||||
|
if (long.TryParse(_currentUserService.UserId, out var tokenUserId) && tokenUserId > 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("GetClubMembership: Reading UserId from JWT token: {UserId}", tokenUserId);
|
||||||
|
request = new GetClubMembershipRequest { UserId = tokenUserId };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetClubMembership: No valid UserId in request or JWT token");
|
||||||
|
throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("GetClubMembership: Using UserId from request: {UserId}", request.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
return await _dispatchRequestToCQRS.Handle<GetClubMembershipRequest, GetClubMembershipQuery, GetClubMembershipResponse>(request, context);
|
return await _dispatchRequestToCQRS.Handle<GetClubMembershipRequest, GetClubMembershipQuery, GetClubMembershipResponse>(request, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool;
|
|||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
|
||||||
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetMyWeeklyBalances;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
|
||||||
@@ -19,6 +20,7 @@ using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs;
|
|||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalReports;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalReports;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions;
|
||||||
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
|
|
||||||
@@ -68,6 +70,11 @@ public class CommissionService : CommissionContract.CommissionContractBase
|
|||||||
return await _dispatchRequestToCQRS.Handle<GetUserCommissionPayoutsRequest, GetUserCommissionPayoutsQuery, GetUserCommissionPayoutsResponse>(request, context);
|
return await _dispatchRequestToCQRS.Handle<GetUserCommissionPayoutsRequest, GetUserCommissionPayoutsQuery, GetUserCommissionPayoutsResponse>(request, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<GetMyCommissionPayoutsResponse> GetMyCommissionPayouts(GetMyCommissionPayoutsRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return await _dispatchRequestToCQRS.Handle<GetMyCommissionPayoutsRequest, GetMyCommissionPayoutsQuery, GetMyCommissionPayoutsResponse>(request, context);
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task<GetCommissionPayoutHistoryResponse> GetCommissionPayoutHistory(GetCommissionPayoutHistoryRequest request, ServerCallContext context)
|
public override async Task<GetCommissionPayoutHistoryResponse> GetCommissionPayoutHistory(GetCommissionPayoutHistoryRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
return await _dispatchRequestToCQRS.Handle<GetCommissionPayoutHistoryRequest, GetCommissionPayoutHistoryQuery, GetCommissionPayoutHistoryResponse>(request, context);
|
return await _dispatchRequestToCQRS.Handle<GetCommissionPayoutHistoryRequest, GetCommissionPayoutHistoryQuery, GetCommissionPayoutHistoryResponse>(request, context);
|
||||||
@@ -128,4 +135,9 @@ public class CommissionService : CommissionContract.CommissionContractBase
|
|||||||
{
|
{
|
||||||
return await _dispatchRequestToCQRS.Handle<GetWithdrawalReportsRequest, GetWithdrawalReportsQuery, GetWithdrawalReportsResponse>(request, context);
|
return await _dispatchRequestToCQRS.Handle<GetWithdrawalReportsRequest, GetWithdrawalReportsQuery, GetWithdrawalReportsResponse>(request, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<GetMyWeeklyBalancesResponse> GetMyWeeklyBalances(GetMyWeeklyBalancesRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return await _dispatchRequestToCQRS.Handle<GetMyWeeklyBalancesRequest, GetMyWeeklyBalancesQuery, GetMyWeeklyBalancesResponse>(request, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures;
|
||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
using CMSMicroservice.Domain.Common;
|
using CMSMicroservice.Domain.Common;
|
||||||
using CMSMicroservice.Protobuf.Protos.Configuration;
|
using CMSMicroservice.Protobuf.Protos.Configuration;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
|
using MediatR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
@@ -12,10 +15,17 @@ namespace CMSMicroservice.WebApi.Services;
|
|||||||
public class ConfigurationService : ConfigurationContract.ConfigurationContractBase
|
public class ConfigurationService : ConfigurationContract.ConfigurationContractBase
|
||||||
{
|
{
|
||||||
private readonly ILogger<ConfigurationService> _logger;
|
private readonly ILogger<ConfigurationService> _logger;
|
||||||
|
private readonly ICurrentUserService _currentUserService;
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
public ConfigurationService(ILogger<ConfigurationService> logger)
|
public ConfigurationService(
|
||||||
|
ILogger<ConfigurationService> logger,
|
||||||
|
ICurrentUserService currentUserService,
|
||||||
|
IMediator mediator)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_currentUserService = currentUserService;
|
||||||
|
_mediator = mediator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -41,6 +51,61 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
|||||||
return Task.FromResult(response);
|
return Task.FromResult(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت تنظیمات باشگاه مشتریان
|
||||||
|
/// </summary>
|
||||||
|
public override Task<GetClubConfigurationResponse> GetClubConfiguration(Empty request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var response = new GetClubConfigurationResponse
|
||||||
|
{
|
||||||
|
ActivationFee = SystemConstants.ClubActivationFee,
|
||||||
|
MembershipGiftValue = SystemConstants.ClubMembershipGiftValue
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogDebug("Club configuration requested: ActivationFee={ActivationFee}, GiftValue={GiftValue}",
|
||||||
|
response.ActivationFee, response.MembershipGiftValue);
|
||||||
|
|
||||||
|
return Task.FromResult(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت ویژگیهای باشگاه مشتریان برای کاربر جاری
|
||||||
|
/// </summary>
|
||||||
|
public override async Task<GetClubFeaturesResponse> GetClubFeatures(Empty request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
// دریافت UserId از JWT
|
||||||
|
if (!long.TryParse(_currentUserService.UserId, out var userId) || userId <= 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetClubFeatures called without valid user authentication");
|
||||||
|
return new GetClubFeaturesResponse(); // لیست خالی برای کاربران غیر احراز هویت شده
|
||||||
|
}
|
||||||
|
|
||||||
|
// فراخوانی GetUserClubFeatures از طریق MediatR (CQRS داخلی)
|
||||||
|
var query = new GetUserClubFeaturesQuery { UserId = userId };
|
||||||
|
var userFeatures = await _mediator.Send(query, context.CancellationToken);
|
||||||
|
|
||||||
|
// تبدیل به فرمت GetClubFeaturesResponse
|
||||||
|
var response = new GetClubFeaturesResponse();
|
||||||
|
foreach (var feature in userFeatures)
|
||||||
|
{
|
||||||
|
response.Features.Add(new ClubFeatureModel
|
||||||
|
{
|
||||||
|
Id = feature.ClubFeatureId,
|
||||||
|
Title = feature.FeatureTitle ?? string.Empty,
|
||||||
|
Description = feature.FeatureDescription ?? string.Empty,
|
||||||
|
IsEnabled = feature.IsActive,
|
||||||
|
DisplayOrder = feature.SortOrder,
|
||||||
|
GrantedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(feature.GrantedAt, DateTimeKind.Utc)),
|
||||||
|
CreatedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(feature.CreatedAt, DateTimeKind.Utc)),
|
||||||
|
Notes = feature.Notes ?? string.Empty
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Club features requested for user {UserId}: {Count} features returned", userId, response.Features.Count);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// دریافت تمام تنظیمات
|
/// دریافت تمام تنظیمات
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProductsByFilter
|
|||||||
using Mapster;
|
using Mapster;
|
||||||
using AppModels = CMSMicroservice.Application.Common.Models;
|
using AppModels = CMSMicroservice.Application.Common.Models;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
|
|
||||||
@@ -39,7 +40,64 @@ public class ProductsService : ProductsContract.ProductsContractBase
|
|||||||
|
|
||||||
public override async Task<GetAllProductsByFilterResponse> GetAllProductsByFilter(GetAllProductsByFilterRequest request, ServerCallContext context)
|
public override async Task<GetAllProductsByFilterResponse> GetAllProductsByFilter(GetAllProductsByFilterRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
// Map to GetCustomerProductsByFilter query (public products API)
|
||||||
|
var query = new GetCustomerProductsByFilterQuery
|
||||||
|
{
|
||||||
|
Id = request.Filter?.Id,
|
||||||
|
Title = request.Filter?.Title ?? string.Empty,
|
||||||
|
Description = request.Filter?.Description ?? string.Empty,
|
||||||
|
ShortInfomation = request.Filter?.ShortInfomation ?? string.Empty,
|
||||||
|
FullInformation = request.Filter?.FullInformation ?? string.Empty,
|
||||||
|
Price = request.Filter?.Price,
|
||||||
|
Discount = request.Filter?.Discount,
|
||||||
|
Rate = request.Filter?.Rate,
|
||||||
|
SaleCount = request.Filter?.SaleCount,
|
||||||
|
ViewCount = request.Filter?.ViewCount,
|
||||||
|
RemainingCount = request.Filter?.RemainingCount,
|
||||||
|
CategoryIds = request.Filter?.CategoryId.HasValue == true
|
||||||
|
? new List<long> { request.Filter.CategoryId.Value }
|
||||||
|
: new List<long>(),
|
||||||
|
SortBy = request.SortBy ?? string.Empty,
|
||||||
|
PaginationState = request.PaginationState != null
|
||||||
|
? new AppModels.PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = request.PaginationState.PageNumber,
|
||||||
|
PageSize = request.PaginationState.PageSize
|
||||||
|
}
|
||||||
|
: new AppModels.PaginationState { PageNumber = 1, PageSize = 20 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _sender.Send(query, context.CancellationToken);
|
||||||
|
|
||||||
|
return new GetAllProductsByFilterResponse
|
||||||
|
{
|
||||||
|
MetaData = new CMSMicroservice.Protobuf.Protos.MetaData
|
||||||
|
{
|
||||||
|
CurrentPage = result.MetaData.CurrentPage,
|
||||||
|
TotalPage = result.MetaData.TotalPage,
|
||||||
|
PageSize = result.MetaData.PageSize,
|
||||||
|
TotalCount = result.MetaData.TotalCount,
|
||||||
|
HasPrevious = result.MetaData.HasPrevious,
|
||||||
|
HasNext = result.MetaData.HasNext
|
||||||
|
},
|
||||||
|
Models = { result.Models.Select(m => new GetAllProductsByFilterResponseModel
|
||||||
|
{
|
||||||
|
Id = m.Id,
|
||||||
|
Title = m.Title,
|
||||||
|
Description = m.Description,
|
||||||
|
ShortInfomation = m.ShortInfomation,
|
||||||
|
FullInformation = m.FullInformation,
|
||||||
|
Price = m.Price,
|
||||||
|
Discount = m.Discount,
|
||||||
|
Rate = m.Rate,
|
||||||
|
ImagePath = m.ImagePath,
|
||||||
|
ThumbnailPath = m.ThumbnailPath,
|
||||||
|
SaleCount = m.SaleCount,
|
||||||
|
ViewCount = m.ViewCount,
|
||||||
|
RemainingCount = m.RemainingCount,
|
||||||
|
CategoryIds = { m.Categories?.Select(c => c.CategoryId) ?? Enumerable.Empty<long>() }
|
||||||
|
}) }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<BulkUpdateProductPricesResponse> BulkUpdateProductPrices(BulkUpdateProductPricesRequest request, ServerCallContext context)
|
public override async Task<BulkUpdateProductPricesResponse> BulkUpdateProductPrices(BulkUpdateProductPricesRequest request, ServerCallContext context)
|
||||||
|
|||||||
@@ -2,23 +2,33 @@ using CMSMicroservice.Protobuf.Protos.UserOrder;
|
|||||||
using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrders;
|
using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrders;
|
||||||
using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrder;
|
using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrder;
|
||||||
using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrderHistory;
|
using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrderHistory;
|
||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities;
|
||||||
|
using CMSMicroservice.Domain.Entities.Order;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
using AppModels = CMSMicroservice.Application.Common.Models;
|
using AppModels = CMSMicroservice.Application.Common.Models;
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using CMSMicroservice.Protobuf.Protos;
|
using CMSMicroservice.Protobuf.Protos;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
|
|
||||||
public class UserOrderService : UserOrderContract.UserOrderContractBase
|
public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||||
{
|
{
|
||||||
private readonly ISender _sender;
|
private readonly ISender _sender;
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly ICurrentUserService _currentUserService;
|
||||||
|
|
||||||
public UserOrderService(ISender sender)
|
public UserOrderService(ISender sender, IApplicationDbContext context, ICurrentUserService currentUserService)
|
||||||
{
|
{
|
||||||
_sender = sender;
|
_sender = sender;
|
||||||
|
_context = context;
|
||||||
|
_currentUserService = currentUserService;
|
||||||
}
|
}
|
||||||
public override async Task<CreateNewUserOrderResponse> CreateNewUserOrder(CreateNewUserOrderRequest request, ServerCallContext context)
|
public override async Task<CreateNewUserOrderResponse> CreateNewUserOrder(CreateNewUserOrderRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
@@ -37,17 +47,299 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
|||||||
|
|
||||||
public override async Task<GetUserOrderResponse> GetUserOrder(GetUserOrderRequest request, ServerCallContext context)
|
public override async Task<GetUserOrderResponse> GetUserOrder(GetUserOrderRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
var query = new GetCustomerOrderQuery
|
||||||
|
{
|
||||||
|
OrderId = request.Id,
|
||||||
|
UserId = 0 // از JWT دریافت میشود
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _sender.Send(query, context.CancellationToken);
|
||||||
|
|
||||||
|
var response = new GetUserOrderResponse
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Amount = result.Amount,
|
||||||
|
PackageId = result.PackageId ?? 0,
|
||||||
|
TransactionId = result.TransactionId,
|
||||||
|
UserId = result.UserId,
|
||||||
|
UserAddressId = result.UserAddressId,
|
||||||
|
UserAddressText = result.UserAddressText,
|
||||||
|
TrackingCode = result.TrackingCode,
|
||||||
|
DeliveryDescription = result.DeliveryDescription,
|
||||||
|
UserFullName = result.UserFullName,
|
||||||
|
UserNationalCode = result.UserNationalCode
|
||||||
|
};
|
||||||
|
|
||||||
|
// VAT Info
|
||||||
|
if (result.VatAmount > 0)
|
||||||
|
{
|
||||||
|
response.VatInfo = new OrderVATInfo
|
||||||
|
{
|
||||||
|
VatRate = result.VatPercentage / 100,
|
||||||
|
BaseAmount = result.Amount - result.VatAmount,
|
||||||
|
VatAmount = result.VatAmount,
|
||||||
|
TotalAmount = result.Amount,
|
||||||
|
IsPaid = result.PaymentStatus == Domain.Enums.PaymentStatus.Success
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
response.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)result.PaymentStatus;
|
||||||
|
if (result.PaymentDate.HasValue)
|
||||||
|
response.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(result.PaymentDate.Value, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
if (result.PaymentMethod.HasValue)
|
||||||
|
response.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)result.PaymentMethod.Value;
|
||||||
|
|
||||||
|
response.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)result.DeliveryStatus;
|
||||||
|
|
||||||
|
foreach (var fd in result.FactorDetails)
|
||||||
|
{
|
||||||
|
response.FactorDetails.Add(new GetUserOrderResponseFactorDetail
|
||||||
|
{
|
||||||
|
ProductId = fd.ProductId,
|
||||||
|
ProductTitle = fd.ProductTitle,
|
||||||
|
ProductThumbnailPath = fd.ProductThumbnailPath,
|
||||||
|
UnitPrice = fd.UnitPrice,
|
||||||
|
Count = fd.Count,
|
||||||
|
UnitDiscountPrice = fd.UnitDiscountPrice
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetAllUserOrderByFilterResponse> GetAllUserOrderByFilter(GetAllUserOrderByFilterRequest request, ServerCallContext context)
|
public override async Task<GetAllUserOrderByFilterResponse> GetAllUserOrderByFilter(GetAllUserOrderByFilterRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
// Admin API - can view all orders or filter by specific user
|
||||||
|
var query = new GetCustomerOrdersQuery
|
||||||
|
{
|
||||||
|
UserId = request.Filter?.UserId ?? 0, // 0 means all users (admin view)
|
||||||
|
PaginationState = request.PaginationState?.Adapt<AppModels.PaginationState>(),
|
||||||
|
PaymentStatusFilter = request.Filter?.PaymentStatus != null
|
||||||
|
? (int?)request.Filter.PaymentStatus
|
||||||
|
: null,
|
||||||
|
DeliveryStatusFilter = request.Filter?.DeliveryStatus != null
|
||||||
|
? (int?)request.Filter.DeliveryStatus
|
||||||
|
: null,
|
||||||
|
FromDate = request.Filter?.PaymentDate?.ToDateTime(),
|
||||||
|
ToDate = null
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _sender.Send(query, context.CancellationToken);
|
||||||
|
|
||||||
|
var response = new GetAllUserOrderByFilterResponse
|
||||||
|
{
|
||||||
|
MetaData = result.MetaData.Adapt<MetaData>()
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var model in result.Models)
|
||||||
|
{
|
||||||
|
var orderModel = new GetAllUserOrderByFilterResponseModel
|
||||||
|
{
|
||||||
|
Id = model.Id,
|
||||||
|
Amount = model.Amount,
|
||||||
|
PackageId = model.PackageId ?? 0,
|
||||||
|
TransactionId = model.TransactionId,
|
||||||
|
UserId = model.UserId,
|
||||||
|
UserAddressId = model.UserAddressId,
|
||||||
|
UserAddressText = model.UserAddressText,
|
||||||
|
TrackingCode = model.TrackingCode,
|
||||||
|
DeliveryDescription = model.DeliveryDescription,
|
||||||
|
UserFullName = model.UserFullName,
|
||||||
|
UserNationalCode = model.UserNationalCode,
|
||||||
|
VatAmount = model.VatAmount,
|
||||||
|
VatPercentage = model.VatPercentage
|
||||||
|
};
|
||||||
|
|
||||||
|
orderModel.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)model.PaymentStatus;
|
||||||
|
if (model.PaymentDate.HasValue)
|
||||||
|
orderModel.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(model.PaymentDate.Value, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
if (model.PaymentMethod.HasValue)
|
||||||
|
orderModel.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)model.PaymentMethod.Value;
|
||||||
|
|
||||||
|
orderModel.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)model.DeliveryStatus;
|
||||||
|
|
||||||
|
foreach (var fd in model.FactorDetails)
|
||||||
|
{
|
||||||
|
orderModel.FactorDetails.Add(new GetAllUserOrderByFilterResponseModelFactorDetail
|
||||||
|
{
|
||||||
|
ProductId = fd.ProductId,
|
||||||
|
ProductTitle = fd.ProductTitle,
|
||||||
|
ProductThumbnailPath = fd.ProductThumbnailPath,
|
||||||
|
UnitPrice = fd.UnitPrice,
|
||||||
|
Count = fd.Count,
|
||||||
|
UnitDiscountPrice = fd.UnitDiscountPrice
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Models.Add(orderModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<SubmitShopBuyOrderResponse> SubmitShopBuyOrder(SubmitShopBuyOrderRequest request, ServerCallContext context)
|
public override async Task<SubmitShopBuyOrderResponse> SubmitShopBuyOrder(SubmitShopBuyOrderRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
// Get UserId from JWT or request
|
||||||
|
var userId = !string.IsNullOrEmpty(_currentUserService.UserId)
|
||||||
|
? long.Parse(_currentUserService.UserId)
|
||||||
|
: request.UserId;
|
||||||
|
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.Unauthenticated, "User not authenticated"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's cart items with product details
|
||||||
|
var cartItems = await _context.UserCarts
|
||||||
|
.Include(c => c.Product)
|
||||||
|
.Where(c => c.UserId == userId && !c.IsDeleted)
|
||||||
|
.ToListAsync(context.CancellationToken);
|
||||||
|
|
||||||
|
if (!cartItems.Any())
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.FailedPrecondition, "سبد خرید خالی است"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's default address
|
||||||
|
var defaultAddress = await _context.UserAddresses
|
||||||
|
.Where(a => a.UserId == userId && a.IsDefault && !a.IsDeleted)
|
||||||
|
.FirstOrDefaultAsync(context.CancellationToken);
|
||||||
|
|
||||||
|
if (defaultAddress == null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.FailedPrecondition, "آدرس پیشفرض یافت نشد"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate amounts
|
||||||
|
const decimal vatRate = 0.09m;
|
||||||
|
long baseAmount = cartItems.Sum(c => c.Product.Price * c.Count);
|
||||||
|
long vatAmount = (long)(baseAmount * vatRate);
|
||||||
|
long totalAmount = baseAmount + vatAmount;
|
||||||
|
|
||||||
|
// Validate total amount
|
||||||
|
if (request.TotalAmount > 0 && Math.Abs(totalAmount - request.TotalAmount) > 100)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument,
|
||||||
|
$"مبلغ نامعتبر است. محاسبه شده: {totalAmount}, دریافتی: {request.TotalAmount}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's wallet
|
||||||
|
var wallet = await _context.UserWallets
|
||||||
|
.Where(w => w.UserId == userId)
|
||||||
|
.FirstOrDefaultAsync(context.CancellationToken);
|
||||||
|
|
||||||
|
if (wallet == null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.FailedPrecondition, "کیف پول یافت نشد"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check wallet balance
|
||||||
|
if (wallet.Balance < totalAmount)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.FailedPrecondition,
|
||||||
|
$"موجودی کیف پول کافی نیست. موجودی: {wallet.Balance:N0} تومان، مورد نیاز: {totalAmount:N0} تومان"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create transaction
|
||||||
|
var transaction = new Transaction
|
||||||
|
{
|
||||||
|
Amount = totalAmount,
|
||||||
|
Description = $"خرید محصولات - سفارش شماره در حال ایجاد",
|
||||||
|
PaymentStatus = CMSMicroservice.Domain.Enums.PaymentStatus.Success,
|
||||||
|
PaymentDate = DateTime.UtcNow,
|
||||||
|
Type = CMSMicroservice.Domain.Enums.TransactionType.Buy,
|
||||||
|
RefId = $"SHOP_{DateTime.UtcNow.Ticks}"
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Transactions.Add(transaction);
|
||||||
|
await _context.SaveChangesAsync(context.CancellationToken);
|
||||||
|
|
||||||
|
// Deduct from wallet
|
||||||
|
var oldBalance = wallet.Balance;
|
||||||
|
wallet.Balance -= totalAmount;
|
||||||
|
|
||||||
|
// Create wallet change log
|
||||||
|
var walletLog = new UserWalletChangeLog
|
||||||
|
{
|
||||||
|
WalletId = wallet.Id,
|
||||||
|
CurrentBalance = wallet.Balance,
|
||||||
|
ChangeValue = -totalAmount,
|
||||||
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||||
|
ChangeNerworkValue = 0,
|
||||||
|
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||||
|
ChangeDiscountValue = 0,
|
||||||
|
IsIncrease = false,
|
||||||
|
RefrenceId = transaction.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.UserWalletChangeLogs.Add(walletLog);
|
||||||
|
|
||||||
|
// Create order
|
||||||
|
var order = new UserOrder
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
UserAddressId = defaultAddress.Id,
|
||||||
|
Amount = totalAmount,
|
||||||
|
TransactionId = transaction.Id,
|
||||||
|
PaymentStatus = CMSMicroservice.Domain.Enums.PaymentStatus.Success,
|
||||||
|
PaymentDate = DateTime.UtcNow,
|
||||||
|
PaymentMethod = CMSMicroservice.Domain.Enums.PaymentMethod.Wallet,
|
||||||
|
DeliveryStatus = CMSMicroservice.Domain.Enums.DeliveryStatus.Pending,
|
||||||
|
HasVAT = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.UserOrders.Add(order);
|
||||||
|
await _context.SaveChangesAsync(context.CancellationToken);
|
||||||
|
|
||||||
|
// Update transaction description with order ID
|
||||||
|
transaction.Description = $"خرید محصولات - سفارش #{order.Id}";
|
||||||
|
await _context.SaveChangesAsync(context.CancellationToken);
|
||||||
|
|
||||||
|
// Create order VAT record
|
||||||
|
var orderVat = new OrderVAT
|
||||||
|
{
|
||||||
|
OrderId = order.Id,
|
||||||
|
VATRate = vatRate,
|
||||||
|
BaseAmount = baseAmount,
|
||||||
|
VATAmount = vatAmount,
|
||||||
|
TotalAmount = totalAmount
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.OrderVATs.Add(orderVat);
|
||||||
|
|
||||||
|
// Create factor details for each cart item
|
||||||
|
foreach (var cartItem in cartItems)
|
||||||
|
{
|
||||||
|
var factorDetail = new FactorDetails
|
||||||
|
{
|
||||||
|
OrderId = order.Id,
|
||||||
|
ProductId = cartItem.ProductId,
|
||||||
|
Count = cartItem.Count,
|
||||||
|
UnitPrice = cartItem.Product.Price,
|
||||||
|
UnitDiscount = 0,
|
||||||
|
UnitDiscountPrice = cartItem.Product.Price,
|
||||||
|
IsChangePrice = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.FactorDetails.Add(factorDetail);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(context.CancellationToken);
|
||||||
|
|
||||||
|
// Clear user's cart
|
||||||
|
foreach (var cartItem in cartItems)
|
||||||
|
{
|
||||||
|
cartItem.IsDeleted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(context.CancellationToken);
|
||||||
|
|
||||||
|
return new SubmitShopBuyOrderResponse
|
||||||
|
{
|
||||||
|
Id = order.Id
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<CancelOrderResponse> CancelOrder(CancelOrderRequest request, ServerCallContext context)
|
public override async Task<CancelOrderResponse> CancelOrder(CancelOrderRequest request, ServerCallContext context)
|
||||||
@@ -131,14 +423,14 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
|||||||
VatPercentage = model.VatPercentage
|
VatPercentage = model.VatPercentage
|
||||||
};
|
};
|
||||||
|
|
||||||
orderModel.PaymentStatus = (PaymentStatus)model.PaymentStatus;
|
orderModel.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)model.PaymentStatus;
|
||||||
if (model.PaymentDate.HasValue)
|
if (model.PaymentDate.HasValue)
|
||||||
orderModel.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(model.PaymentDate.Value, DateTimeKind.Utc));
|
orderModel.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(model.PaymentDate.Value, DateTimeKind.Utc));
|
||||||
|
|
||||||
if (model.PaymentMethod.HasValue)
|
if (model.PaymentMethod.HasValue)
|
||||||
orderModel.PaymentMethod = (PaymentMethod)model.PaymentMethod.Value;
|
orderModel.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)model.PaymentMethod.Value;
|
||||||
|
|
||||||
orderModel.DeliveryStatus = (DeliveryStatus)model.DeliveryStatus;
|
orderModel.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)model.DeliveryStatus;
|
||||||
|
|
||||||
foreach (var fd in model.FactorDetails)
|
foreach (var fd in model.FactorDetails)
|
||||||
{
|
{
|
||||||
@@ -197,14 +489,14 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
response.PaymentStatus = (PaymentStatus)result.PaymentStatus;
|
response.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)result.PaymentStatus;
|
||||||
if (result.PaymentDate.HasValue)
|
if (result.PaymentDate.HasValue)
|
||||||
response.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(result.PaymentDate.Value, DateTimeKind.Utc));
|
response.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(result.PaymentDate.Value, DateTimeKind.Utc));
|
||||||
|
|
||||||
if (result.PaymentMethod.HasValue)
|
if (result.PaymentMethod.HasValue)
|
||||||
response.PaymentMethod = (PaymentMethod)result.PaymentMethod.Value;
|
response.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)result.PaymentMethod.Value;
|
||||||
|
|
||||||
response.DeliveryStatus = (DeliveryStatus)result.DeliveryStatus;
|
response.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)result.DeliveryStatus;
|
||||||
|
|
||||||
foreach (var fd in result.FactorDetails)
|
foreach (var fd in result.FactorDetails)
|
||||||
{
|
{
|
||||||
@@ -384,4 +676,15 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
|||||||
TotalAmount = totalAmount
|
TotalAmount = totalAmount
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task<GetVATRateResponse> GetVATRate(Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
// VAT Rate for Iran: 9% (نرخ مالیات بر ارزش افزوده ایران)
|
||||||
|
return Task.FromResult(new GetVATRateResponse
|
||||||
|
{
|
||||||
|
VatRate = 0.09,
|
||||||
|
VatPercentage = 9,
|
||||||
|
IsEnabled = true
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
|||||||
ChangeValue = log.ChangeValue,
|
ChangeValue = log.ChangeValue,
|
||||||
CurrentNetworkBalance = log.CurrentNetworkBalance,
|
CurrentNetworkBalance = log.CurrentNetworkBalance,
|
||||||
ChangeNerworkValue = log.ChangeNerworkValue,
|
ChangeNerworkValue = log.ChangeNerworkValue,
|
||||||
|
CurrentDiscountBalance = log.CurrentDiscountBalance,
|
||||||
|
ChangeDiscountValue = log.ChangeDiscountValue,
|
||||||
IsIncrease = log.IsIncrease,
|
IsIncrease = log.IsIncrease,
|
||||||
RefrenceId = log.RefrenceId,
|
RefrenceId = log.RefrenceId,
|
||||||
CreatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.SpecifyKind(log.Created, DateTimeKind.Utc))
|
CreatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.SpecifyKind(log.Created, DateTimeKind.Utc))
|
||||||
|
|||||||
Reference in New Issue
Block a user