Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/docs/WORKFLOW_CURRENCY_SELECTOR_IMPROVEMENT.md
2026-04-14 19:34:55 +03:30

366 lines
11 KiB
Markdown
Executable file
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# بهبود Currency Selector در فیلترهای نودهای Workflow
## 🎯 هدف
تبدیل فیلد `currency_id` از TextField معمولی به یک **Dropdown هوشمند** که ارزهای کسب‌وکار را به صورت dynamic از API می‌خواند.
---
## ✅ تغییرات انجام شده
### 1. **Backend - Schema آماده بود** ✅
در `document_triggers.py` (InvoiceCreatedTrigger):
```python
"currency_id": {
"type": "integer",
"description": "فیلتر بر اساس ارز",
"ui_type": "currency_selector", # ✅
"ui_config": {
"business_scoped": True,
"show_all_option": True
},
"required": False
}
```
---
### 2. **Frontend - Widget بهبود یافت** ✅
#### A. اضافه کردن State برای ارزها:
در `workflow_node_config_dialog.dart`:
```dart
class _WorkflowNodeConfigDialogState extends State<WorkflowNodeConfigDialog> {
// ...
List<Map<String, dynamic>> _currencies = []; // ✅ لیست ارزها
bool _loadingCurrencies = false; // ✅ وضعیت لود
```
#### B. متد بارگذاری ارزها:
```dart
Future<void> _loadCurrenciesIfNeeded() async {
if (widget.businessId == null) return;
// بررسی نیاز به لود ارزها
bool needsCurrencies = false;
if (_configSchema != null) {
for (final entry in _configSchema!.entries) {
final schema = entry.value;
if (schema is Map<String, dynamic>) {
final uiType = schema['ui_type'] as String?;
if (uiType == 'currency_selector') {
needsCurrencies = true;
break;
}
}
}
}
if (needsCurrencies) {
setState(() => _loadingCurrencies = true);
try {
final response = await _workflowService.getBusinessCurrencies(
businessId: widget.businessId!,
);
setState(() {
_currencies = response;
_loadingCurrencies = false;
});
} catch (e) {
// در صورت خطا، از لیست پیش‌فرض استفاده می‌شود
setState(() {
_currencies = [
{'id': 1, 'code': 'IRR', 'name': 'ریال', 'symbol': '﷼'},
// ...
];
_loadingCurrencies = false;
});
}
}
}
```
#### C. Widget بهبود یافته `_buildCurrencySelector`:
```dart
Widget _buildCurrencySelector(...) {
// چک reference
if (currentValue?.toString().startsWith('\$') ?? false) {
return _buildReferenceTextField(...);
}
return Column(
children: [
// Loading state
if (_loadingCurrencies)
Container(
child: Row(
children: [
CircularProgressIndicator(),
Text('در حال بارگذاری ارزها...'),
],
),
)
// Empty state
else if (_currencies.isEmpty)
Container(
child: Text('ارزی یافت نشد'),
)
// Dropdown با ارزها
else
DropdownButtonFormField<int>(
value: currentValue is int ? currentValue : null,
decoration: InputDecoration(
labelText: _formatKey(key),
prefixIcon: Icon(Icons.monetization_on),
),
items: _currencies.map((currency) {
final id = currency['id'] as int;
final symbol = currency['symbol'] as String? ?? '';
final name = currency['title'] ?? currency['name'] ?? '';
final code = currency['code'] ?? '';
final isDefault = currency['is_default'] == true;
return DropdownMenuItem<int>(
value: id,
child: Row(
children: [
Text(symbol, style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(width: 8),
Expanded(child: Text('$name ($code)')),
if (isDefault)
Container(
child: Text('پیش‌فرض'),
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer,
),
),
],
),
);
}).toList(),
onChanged: (newValue) {
// ...
},
),
// دکمه Reference
if (!_loadingCurrencies)
OutlinedButton.icon(
icon: Icon(Icons.link),
label: Text('استفاده از نود قبلی'),
onPressed: () => _showReferenceSelector(key),
),
],
);
}
```
---
### 3. **Service - متد جدید** ✅
در `workflow_service.dart`:
```dart
Future<List<Map<String, dynamic>>> getBusinessCurrencies({
required int businessId,
}) async {
final res = await _apiClient.get<Map<String, dynamic>>(
'/api/v1/currencies/business/$businessId',
);
final data = res.data?['data'];
if (data is List) {
return data
.map<Map<String, dynamic>>((c) => Map<String, dynamic>.from(c as Map))
.toList();
}
return const <Map<String, dynamic>>[];
}
```
---
## 🎨 نمایش در UI
### حالت Loading:
```
┌─────────────────────────────────────────┐
│ ⏳ در حال بارگذاری ارزها... │
└─────────────────────────────────────────┘
```
### حالت Empty:
```
┌─────────────────────────────────────────┐
│ ⚠️ ارزی یافت نشد. لطفاً شناسه ارز را │
│ وارد کنید. │
└─────────────────────────────────────────┘
```
### حالت عادی (Dropdown):
```
┌─────────────────────────────────────────┐
│ 💰 ارز ▼ │
├─────────────────────────────────────────┤
│ ﷼ ریال ایران (IRR) [پیش‌فرض] ✓ │
│ $ دلار آمریکا (USD) │
│ € یورو (EUR) │
│ د.إ درهم امارات (AED) │
└─────────────────────────────────────────┘
[🔗 استفاده از نود قبلی]
```
### حالت Reference:
```
┌─────────────────────────────────────────┐
│ ارز │
├─────────────────────────────────────────┤
│ 🔗 $trigger-1.currency_id [🔗] [⭐] │
└─────────────────────────────────────────┘
ℹ️ این مقدار از یک نود قبلی استفاده می‌کند
[🔗 استفاده از نود قبلی]
```
---
## 🔄 روند کار
### 1. هنگام باز شدن دیالوگ:
```
initState()
↓
تشخیص schema های موجود
↓
آیا ui_type = "currency_selector" وجود دارد؟
↓ (بله)
_loadCurrenciesIfNeeded()
↓
API Call: GET /api/v1/currencies/business/{businessId}
↓
setState: _currencies = response
↓
Rebuild Widget با لیست ارزها
```
### 2. هنگام انتخاب:
```
کاربر روی Dropdown کلیک می‌کند
↓
لیست ارزها نمایش داده می‌شود
↓
کاربر یک ارز را انتخاب می‌کند
↓
onChanged() فراخوانی می‌شود
↓
_config[key] = selectedCurrencyId
↓
setState() برای به‌روزرسانی UI
```
---
## 🎁 ویژگی‌های اضافه شده
### 1. **نمایش ارز پیش‌فرض**:
- ارزی که `is_default = true` است، با badge "پیش‌فرض" نمایش داده می‌شود
### 2. **نمایش نماد ارز**:
- هر ارز با نماد خودش نمایش داده می‌شود (﷼, $, €, د.إ)
### 3. **Fallback**:
- در صورت خطا در API، از لیست hardcoded استفاده می‌شود
### 4. **Reference Support**:
- پشتیبانی کامل از مقادیر reference (`$node_id.currency_id`)
### 5. **Loading State**:
- نمایش وضعیت بارگذاری با CircularProgressIndicator
### 6. **Empty State**:
- پیام مناسب برای حالتی که ارزی موجود نیست
---
## 📊 مقایسه قبل و بعد
| ویژگی | قبل ❌ | بعد ✅ |
|-------|--------|-------|
| نوع ورودی | TextField (number) | Dropdown با لیست |
| منبع داده | - | API (Dynamic) |
| نمایش نام ارز | خیر (فقط ID) | بله (نام + نماد + کد) |
| نمایش ارز پیش‌فرض | خیر | بله (با badge) |
| Loading State | خیر | بله |
| Empty State | خیر | بله |
| Reference Support | خیر | بله |
| UX | ضعیف (تایپ ID) | عالی (انتخاب بصری) |
---
## 🧪 نحوه تست
### 1. تست عادی:
```
1. وارد صفحه ویرایش workflow شوید
2. یک نود trigger از نوع "InvoiceCreatedTrigger" اضافه کنید
3. روی ویرایش نود کلیک کنید
4. به فیلتر currency_id بروید
5. باید Dropdown با لیست ارزهای کسب‌وکار نمایش داده شود
6. ارزی را انتخاب کنید و ذخیره کنید
```
### 2. تست Reference:
```
1. دو نود اضافه کنید (یک trigger و یک action)
2. در action، روی دکمه "استفاده از نود قبلی" کلیک کنید
3. نود trigger را انتخاب کنید
4. فیلد currency_id را انتخاب کنید
5. باید $trigger-1.currency_id در TextField نمایش داده شود
```
### 3. تست خطا:
```
1. اتصال به API را قطع کنید
2. دیالوگ تنظیمات نود را باز کنید
3. باید لیست fallback با 4 ارز رایج نمایش داده شود
```
---
## 📁 فایل‌های تغییر یافته
### Backend:
- ✅ `hesabixAPI/app/services/workflow/triggers/document_triggers.py` (قبلاً آماده بود)
### Frontend:
- ✅ `hesabixUI/hesabix_ui/lib/widgets/workflow/workflow_node_config_dialog.dart`
- ✅ `hesabixUI/hesabix_ui/lib/services/workflow_service.dart`
---
## 🚀 بهبودهای آینده (اختیاری)
1. **Cache کردن ارزها**: برای جلوگیری از API call های مکرر
2. **Multi-Currency Support**: انتخاب چند ارز همزمان
3. **Currency Conversion**: نمایش نرخ تبدیل
4. **Search در Dropdown**: برای لیست‌های بلند
5. **Custom Currency**: امکان افزودن ارز دلخواه
---
**تاریخ**: دسامبر 2025
**وضعیت**: ✅ کامل و آماده استفاده