forked from hesabix/arc
569 lines
29 KiB
Markdown
569 lines
29 KiB
Markdown
# راهنمای فنی پیادهسازی چندارزی عملیاتی (Hesabix)
|
||
|
||
**وضعیت:** نسخه ۱ پیادهسازیشده (هسته عملیاتی) — برنامه نسخه ۲ در `MULTI_CURRENCY_EXECUTION_PLAN.md` بخشهای ۱۶–۱۸
|
||
**مخاطب:** تیم توسعه Backend / Flutter
|
||
**هدف:** چندارزی عملیاتی با حفظ سادگی کامل برای کسبوکارهای تکارزی.
|
||
|
||
> برای وضعیت فازها و برنامه v2 به سند اجرایی مراجعه کنید؛ این راهنما قراردادهای طراحی را نگه میدارد.
|
||
|
||
---
|
||
|
||
## ۱. خلاصه اجرایی
|
||
|
||
امروز حسابیکس **چندارزی در سطح سند** دارد (ارز اصلی/فرعی، تاریخچه نرخ دستی، تسعیر فاکتور، فیلتر ارز در بسیاری گزارشها). درخواست کاربر عمدتاً به **چندارزی عملیاتی** مربوط است:
|
||
|
||
| قابلیت | وضعیت فعلی | هدف |
|
||
|--------|------------|-----|
|
||
| نرخ روز در نوار ابزار + API خارجی | ندارد | دارد |
|
||
| قیمت دوگانه کالا + sync از نرخ | ناقص | دارد |
|
||
| جمع دوگانه فاکتور (ارزی + پایه + نرخ) | ناقص | دارد |
|
||
| پرداخت فاکتور با ارز دیگر + نرخ per قسط | مسدود | دارد |
|
||
| انتقال بانکی بینارزی با نرخ دستی | ندارد | دارد |
|
||
| مانده شخص به تفکیک ارز | ندارد | دارد |
|
||
| فیلتر/نمایش دوگانه در اسناد و گزارشها | ناقص | یکدست |
|
||
|
||
**اصل طلایی طراحی:** کسبوکاری که فقط یک ارز دارد، باید UI و جریان کاریاش دقیقاً مثل قبل بماند و هیچ اثری از چندارزی نبیند.
|
||
|
||
---
|
||
|
||
## ۲. اصل Progressive Disclosure (مخفیسازی برای تکارزی)
|
||
|
||
### ۲.۱ تعریف رسمی «حالت چندارزی فعال»
|
||
|
||
```text
|
||
is_multi_currency_enabled(business) :=
|
||
تعداد ارزهای فعال در business_currencies >= 2
|
||
OR
|
||
(وجود حداقل یک ارز فرعی غیر از default_currency_id)
|
||
```
|
||
|
||
- **تکارزی (MC = OFF):** فقط `default_currency_id` فعال است؛ هیچ ارز فرعی در `business_currencies` نیست.
|
||
- **چندارزی (MC = ON):** حداقل یک ارز فرعی به کسبوکار اضافه شده.
|
||
|
||
> فعالسازی چندارزی = افزودن ارز فرعی در تنظیمات ارزهای کسبوکار (مسیر موجود). نیازی به سوئیچ جداگانه نیست مگر بعداً برای «خاموش کردن موقت UI» لازم شود.
|
||
|
||
### ۲.۲ منبع حقیقت در کلاینت
|
||
|
||
یک مقدار واحد در `AuthStore` / context کسبوکار:
|
||
|
||
| فیلد پیشنهادی | نوع | توضیح |
|
||
|---------------|-----|--------|
|
||
| `isMultiCurrency` | `bool` | از API پروفایل/کسبوکار محاسبه و ارسال شود |
|
||
| `secondaryCurrencyCount` | `int` | اختیاری برای دیباگ |
|
||
| `baseCurrency` | object | ارز اصلی |
|
||
| `activeCurrencies` | list | فقط وقتی MC=ON لازم است |
|
||
|
||
**Backend:** در پاسخ `GET /businesses/{id}` یا bootstrap session فیلد `is_multi_currency: bool` اضافه شود تا UI مجبور به حدس نباشد.
|
||
|
||
### ۲.۳ قوانین مخفیسازی UI (اجباری)
|
||
|
||
وقتی `isMultiCurrency == false`:
|
||
|
||
| محل | رفتار |
|
||
|-----|--------|
|
||
| نوار ابزار بالایی | بدون ویجت نرخ ارز |
|
||
| منوی تنظیمات | لینک «تسعیر ارز / نرخها / سیاست تسعیر» مخفی یا داخل «ارزها» فقط بهصورت غیرفعال با توضیح «پس از افزودن ارز دوم فعال میشود» |
|
||
| فاکتور | بدون انتخابگر ارز (ارز = پیشفرض)، بدون فیلد نرخ تسعیر، بدون جمع دوگانه |
|
||
| پرداخت فاکتور | بدون انتخاب ارز پرداخت / نرخ تبدیل |
|
||
| انتقال بانکی | بدون انتخابگر ارز (یا فقط نمایش واحد ارز پیشفرض)، بدون نرخ تبدیل |
|
||
| فرم کالا | بدون بخش قیمت ارزی / sync نرخ |
|
||
| اشخاص | بدون تفکیک مانده per ارز |
|
||
| اسناد / گزارشها | بدون فیلتر ارز و بدون گزینه «همه ارزها» |
|
||
| چاپ فاکتور | بدون بلوک نرخ / معادل ریالی |
|
||
|
||
وقتی `isMultiCurrency == true`: همه قابلیتهای فازهای زیر بهتدریج ظاهر میشوند.
|
||
|
||
### ۲.۴ قوانین Backend (دفاع در عمق)
|
||
|
||
حتی اگر کلاینت باگ داشته باشد:
|
||
|
||
- APIهای مخصوص MC (نرخ روز، پرداخت چندارزی، انتقال بینارزی، قیمت sync) اگر کسبوکار تکارزی باشد → `400 MULTI_CURRENCY_REQUIRED` یا silently no-op طبق قرارداد هر endpoint.
|
||
- اسناد همیشه `currency_id = default` برای تکارزی اجباری بماند.
|
||
- پرداخت با ارز متفاوت از فاکتور فقط وقتی MC=ON و ارز پرداخت در `business_currencies` باشد مجاز است.
|
||
|
||
### ۲.۵ ویجتهای مشترک (الگو)
|
||
|
||
```dart
|
||
/// الگوی پیشنهادی — پیادهسازی در فاز ۰
|
||
class MultiCurrencyGate extends StatelessWidget {
|
||
final bool isMultiCurrency;
|
||
final Widget child;
|
||
final Widget? singleCurrencyChild; // معمولاً SizedBox.shrink()
|
||
...
|
||
}
|
||
```
|
||
|
||
همه ورودیهای جدید MC باید پشت این gate باشند تا پراکندگی شرطها کم شود.
|
||
|
||
### ۲.۶ مهاجرت کسبوکارهای موجود
|
||
|
||
- کسبوکارهایی که از قبل ارز فرعی دارند → MC=ON؛ UI جدید را میبینند.
|
||
- بقیه → هیچ تغییری در ظاهر.
|
||
- افزودن اولین ارز فرعی → بلافاصله MC=ON؛ بهتر است یک onboarding کوتاه («نرخ مرجع را ثبت کنید») نمایش داده شود (فاز ۱).
|
||
|
||
---
|
||
|
||
## ۳. وضعیت موجود (Baseline) — چه چیزهایی از قبل هست
|
||
|
||
استفاده مجدد؛ از نو ساخته نشود مگر ناقص باشد.
|
||
|
||
| قطعه | مسیر تقریبی | نقش |
|
||
|------|-------------|-----|
|
||
| مدل ارزها | `adapters/db/models/currency.py` | کاتالوگ + `business_currencies` |
|
||
| نرخ تسعیر دستی | `business_currency_rate.py` + service/API | تاریخچه نرخ نسبت به ارز پایه |
|
||
| تسعیر فاکتور | `invoice_fx_revaluation.py` | snapshot در `extra_info.fx` |
|
||
| صفحه نرخها | `currency_revaluation_page.dart` | CRUD نرخ |
|
||
| سیاست تسعیر | `fx_revaluation_settings_page.dart` | as_of / when_no_rate |
|
||
| انتخاب ارز فاکتور | `invoice_info_form` + `InvoiceFxRateField` | فقط وقتی ارز ≠ پایه |
|
||
| مانده شخص به پایه | `person_service.calculate_person_balance` | تجمیع تسعیرشده |
|
||
| لیستقیمت چندارزی | `price_items.currency_id` | قیمت per ارز در لیستقیمت |
|
||
| انتقال همارز | `transfer_service` | یک ارز؛ حسابها فیلتر میشوند |
|
||
| پرداخت همارز | `invoice_service._validate_invoice_payment_item_currency` | mismatch = خطا |
|
||
|
||
---
|
||
|
||
## ۴. معماری هدف (Target Architecture)
|
||
|
||
### ۴.۱ مفاهیم
|
||
|
||
```text
|
||
Base Currency = ارز اصلی کسبوکار (default_currency_id)
|
||
Document Currency = ارز سند (فاکتور/دریافت/انتقال/...)
|
||
Payment Currency = ارز حساب/وسیله پرداخت (ممکن است ≠ Document Currency)
|
||
Spot / Reference Rate = نرخ مرجع روز (۱ واحد ارز فرعی = rate × پایه)
|
||
Transaction Rate = نرخ قفلشده روی همان تراکنش/قسط (ممکن است ≠ نرخ روز)
|
||
```
|
||
|
||
### ۴.۲ اصل حسابداری
|
||
|
||
- ماندههای حساب بانکی/صندوق/تنخواه **همیشه به ارز همان حساب** نگه داشته میشوند.
|
||
- بدهی شخص بابت فاکتور **به ارز فاکتور** است.
|
||
- وقتی پرداخت با ارز دیگر انجام شود، باید:
|
||
1. مبلغ به ارز فاکتور (کاهش بدهی)،
|
||
2. مبلغ به ارز پرداخت (حرکت حساب نقدی)،
|
||
3. نرخ و مابهالتفاوت تسعیر (در صورت نیاز طبق سیاست)،
|
||
همگی ثبت و قابل گزارش باشند.
|
||
|
||
### ۴.۳ ذخیره نرخ روی تراکنش
|
||
|
||
هر جا نرخ مهم است، **snapshot** شود (مثل فاکتور فعلی با `extra_info.fx`):
|
||
|
||
```json
|
||
{
|
||
"fx": {
|
||
"base_currency_id": 1,
|
||
"from_currency_id": 2,
|
||
"to_currency_id": 1,
|
||
"rate": "1600000",
|
||
"rate_row_id": 123,
|
||
"mode": "manual|auto|selected|api",
|
||
"effective_at": "2026-07-18T10:00:00+00:00",
|
||
"source": "user|business_rate|provider:mesghal"
|
||
}
|
||
}
|
||
```
|
||
|
||
برای پرداختهای چندمرحلهای: هر آیتم پرداخت `fx` خودش را دارد.
|
||
|
||
### ۴.۴ قابلیت مشاهده برای تکارزی
|
||
|
||
هیچ جدول/ستون جدیدی رفتار تکارزی را تغییر ندهد. ستونهای جدید nullable و فقط در مسیر MC پر شوند.
|
||
|
||
---
|
||
|
||
## ۵. فازهای پیادهسازی (ترتیب پیشنهادی)
|
||
|
||
هر فاز باید بهتنهایی قابل انتشار باشد و پشت `isMultiCurrency` gate باشد.
|
||
|
||
---
|
||
|
||
### فاز ۰ — زیرساخت Gate و قرارداد API
|
||
|
||
**هدف:** شرط یکتا برای مخفیسازی؛ بدون قابلیت کاربرپسند جدید.
|
||
|
||
#### Backend
|
||
- [ ] افزودن `is_multi_currency` به پاسخ کسبوکار / session bootstrap
|
||
- [ ] هلپر مشترک: `assert_multi_currency(business)` و `business_is_multi_currency(db, id)`
|
||
- [ ] تست واحد: کسبوکار با ۱ ارز → false؛ با ۲ ارز → true
|
||
|
||
#### Frontend
|
||
- [ ] خواندن `isMultiCurrency` در `AuthStore`
|
||
- [ ] ویجت/اکستنشن `MultiCurrencyGate`
|
||
- [ ] مخفیسازی منوهای موجود تسعیر/نرخ وقتی MC=OFF (در صورت نمایش فعلی)
|
||
- [ ] مخفیسازی انتخابگر ارز در فرمهایی که فقط یک گزینه دارند (اختیاری ولی توصیهشده برای یکدستسازی)
|
||
|
||
#### معیار پذیرش
|
||
- کسبوکار تکارزی هیچ منو/فیلد جدید MC نمیبیند.
|
||
- افزودن ارز دوم → بدون رفرش اجباری شدید، پس از reload session، UI MC ظاهر شود.
|
||
|
||
**وابستگی:** ندارد — اول انجام شود.
|
||
|
||
---
|
||
|
||
### فاز ۱ — نرخ مرجع روز (دستی + نمایش نوار ابزار)
|
||
|
||
**هدف:** آیتم نرخ روزانه در نوار ابزار بالایی؛ ورود دستی؛ بدون الزام API خارجی در همین فاز.
|
||
|
||
#### دامنه
|
||
- ویجت toolbar فقط وقتی `isMultiCurrency`
|
||
- نمایش آخرین نرخ هر ارز فرعی نسبت به پایه (مثلاً USD = 1,600,000)
|
||
- دکمه «بهروزرسانی / ثبت نرخ» → دیالوگ یا لینک به صفحه نرخها
|
||
- ثبت در همان `business_currency_rates` (effective_at = now)
|
||
|
||
#### Backend
|
||
- [x] endpoint خلاصه نرخهای جاری:
|
||
`GET /businesses/{id}/currency-rates/latest`
|
||
- [x] `POST .../currency-rates/bulk` برای ثبت چند ارز یکجا از دیالوگ toolbar
|
||
|
||
#### Frontend
|
||
- [x] ویجت `DailyFxRatesToolbarChip` در shell کسبوکار
|
||
- [x] دیالوگ/شیت ثبت سریع نرخ + «از نرخ روز» (اسنپشات مرکزی فاز ۰)
|
||
- [x] کش کوتاهمدت + refresh دستی
|
||
|
||
#### معیار پذیرش
|
||
- تکارزی: toolbar بدون تغییر. ✅
|
||
- چندارزی: نرخها دیده میشوند؛ ثبت دستی در تاریخچه ظاهر میشود. ✅
|
||
|
||
---
|
||
|
||
### فاز ۱ب — آپدیت خودکار نرخ از اسنپشات مرکزی + آفست (پیادهسازیشده)
|
||
|
||
**هدف:** ثبت زمانبندیشدهٔ نرخ تسعیر کسبوکار از اسنپشات مرکزی، با حاشیهٔ درصد/مبلغ per ارز.
|
||
|
||
> جایگزین طرح قدیمی «کلید API per business». جزئیات: `docs/MULTI_CURRENCY_AUTO_SYNC_SCENARIO.md`
|
||
|
||
#### آنچه پیاده شد
|
||
- جداول `business_fx_auto_sync_settings` + `business_fx_auto_sync_currency_rules`
|
||
- API: `GET/PUT .../fx-auto-sync` ، `GET/POST .../preview` ، `POST .../run-now`
|
||
- Worker: `fx_auto_sync_loop` (هر دقیقه due ها)
|
||
- UI: تنظیمات کسبوکار → «بهروزرسانی خودکار نرخ تسعیر» (فقط MC)
|
||
- حالتها: interval (۱–۲۴س) و ساعات ثابت روزانه + timezone
|
||
- ایمنی: skip_if_unchanged، block_if_stale، سقف آفست درصد ۵۰٪
|
||
|
||
#### معیار پذیرش
|
||
- تکارزی: منوی تنظیمات و UI مخفی/گیتشده. ✅
|
||
- چندارزی + فعال: نرخها طبق زمانبندی در `business_currency_rates` ثبت و در toolbar دیده میشوند. ✅
|
||
- بدون فعالسازی → همه چیز دستی مثل فاز ۱. ✅
|
||
|
||
---
|
||
|
||
### فاز ۲ — نمایش دوگانه جمع فاکتور + نرخ کنار جمع
|
||
|
||
**هدف:** در ثبت/نمایش/چاپ فاکتور ارزی: جمع ارزی + معادل پایه + نرخ استفادهشده.
|
||
|
||
#### دامنه
|
||
- فقط وقتی ارز فاکتور ≠ پایه و MC=ON
|
||
- استفاده از snapshot موجود `extra_info.fx` (تقویت نمایش؛ نه لزوماً مدل جدید)
|
||
- امکان override نرخ دستی **داخل فاکتور** (اگر هنوز فقط از لیست نرخ است: اجازه ورود نرخ آزاد + ذخیره بهعنوان rate جدید یا inline در `fx.rate` با `mode=manual`)
|
||
|
||
#### UI
|
||
- انتهای فاکتور:
|
||
- جمع کل: `1,000 USD`
|
||
- معادل پایه: `1,600,000,000 IRR` (نرخ: `1,600,000`)
|
||
- چاپ و لینک عمومی همان منطق
|
||
|
||
#### Backend
|
||
- [ ] در پاسخ جزئیات فاکتور فیلدهای محاسبهشده:
|
||
`totals.foreign`, `totals.base`, `fx` (اگر نباشد از resolve بساز — بهتر snapshot اجباری بماند)
|
||
- [ ] اطمینان از پر شدن `fx` هنگام create/update (الان هست؛ تست پوشش)
|
||
|
||
#### معیار پذیرش
|
||
- تکارزی / فاکتور پایه: هیچ بلوک معادلی نیست.
|
||
- فاکتور ارزی: جمع دوگانه + نرخ در UI و چاپ.
|
||
|
||
---
|
||
|
||
### فاز ۳ — پرداخت چندارزی فاکتور (هسته عملیاتی)
|
||
|
||
**هدف:** فاکتور دلاری؛ پرداخت ریالی (یا ارز دیگر)؛ چند قسط با نرخ جدا؛ نمایش در بخش پرداختها.
|
||
|
||
#### مثال کاربر
|
||
فاکتور `1000 USD`
|
||
- قسط ۱: `200 USD` معادل با نرخ `140,000` تومان → پرداخت از حساب ریالی
|
||
- قسط ۲: `800 USD` با نرخ `150,000`
|
||
نمایش: `100$ (۱۶۰,۰۰۰,۰۰۰ ریال با نرخ …)` — واحدها طبق ارز پایه کسبوکار.
|
||
|
||
> توجه: نرخهای مثال کاربر ممکن است تومان باشد؛ در سیستم ارز پایه اغلب IRR است. در UI برچسب واحد باید از ارز پایه بیاید و در docs تبدیل تومان↔ریال صریح باشد.
|
||
|
||
#### مدل داده پیشنهادی (آیتم پرداخت)
|
||
|
||
```json
|
||
{
|
||
"transaction_type": "bank",
|
||
"bank_id": 10,
|
||
"amount": 280000000,
|
||
"amount_currency_id": 1,
|
||
"settles_amount": 200,
|
||
"settles_currency_id": 2,
|
||
"fx": {
|
||
"rate": "1400000",
|
||
"mode": "manual",
|
||
"base_currency_id": 1,
|
||
"from_currency_id": 2,
|
||
"to_currency_id": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
معنی:
|
||
- `settles_amount` به ارز فاکتور از بدهی کم میکند.
|
||
- `amount` از حساب پرداخت کم/زیاد میشود.
|
||
- `amount ≈ settles_amount × rate` (با تلورانس گرد کردن طبق `decimal_places`).
|
||
|
||
#### Backend (شکستن قفل فعلی)
|
||
- [ ] حذف/شل کردن فرض «حساب باید = ارز فاکتور» وقتی:
|
||
- MC=ON
|
||
- و فیلدهای settles/fx معتبرند
|
||
- [ ] مسیر همارز فعلی بدون تغییر بماند (سازگاری عقبرو)
|
||
- [ ] تولید خطوط سند حسابداری صحیح (بدهکار/بستانکار شخص به ارز فاکتور یا معادل؛ نقد به ارز حساب؛ تفاوت تسعیر در صورت سیاست)
|
||
- [ ] ذخیره fx روی هر payment line در `extra_info`
|
||
|
||
#### Frontend
|
||
- [ ] در `invoice_transactions_widget`: اگر MC و حساب≠ارز فاکتور → فیلد نرخ + مبلغ تسویه ارزی
|
||
- [ ] نمایش لیست پرداختها با فرمت دوگانه
|
||
- [ ] اگر MC=OFF → UI و validation عین امروز
|
||
|
||
#### معیار پذیرش
|
||
- سناریو مثال ۱۰۰۰ دلاری با دو نرخ مختلف قابل ثبت و گزارش است.
|
||
- کسبوکار تکارزی هیچ فیلد جدیدی نمیبیند و رفتار پرداخت تغییر نکرده.
|
||
|
||
#### ریسک / نیاز تصمیم محصول (قبل از کد)
|
||
1. آیا تفاوت نرخ نسبت به نرخ فاکتور بهعنوان سود/زیان تسعیر ثبت شود یا فقط اطلاعاتی باشد؟
|
||
2. مانده فاکتور همیشه به ارز فاکتور کم شود (`settles_amount`) — توصیه: بله.
|
||
3. واحد نمایش نرخ (ریال در برابر تومان) در تنظیمات کسبوکار.
|
||
|
||
---
|
||
|
||
### فاز ۴ — انتقال بانکی بینارزی با نرخ دستی
|
||
|
||
**هدف:** از حساب دلاری به حساب ریالی (یا بالعکس) با نرخ دستی.
|
||
|
||
#### مدل
|
||
```json
|
||
{
|
||
"source": { "type": "bank", "id": 1 },
|
||
"destination": { "type": "bank", "id": 2 },
|
||
"source_amount": 1000,
|
||
"destination_amount": 1600000000,
|
||
"fx": { "rate": "1600000", "mode": "manual" }
|
||
}
|
||
```
|
||
|
||
- ارز از حساب مبدأ/مقصد خوانده میشود (نه یک `currency_id` سند واحد — یا سند با ارز پایه و جزئیات در extra).
|
||
- تصمیم طراحی سند: ترجیح = `extra_info` با دو مبلغ + fx؛ خطوط DocumentLine به ارز منطقی هر حساب (یا معادل پایه طبق الگوی حسابداری فعلی).
|
||
|
||
#### UI
|
||
- فقط وقتی ارز مبدأ ≠ ارز مقصد و MC=ON: فیلد نرخ + مبلغ مقصد (یا محاسبه خودکار).
|
||
- همارز: فرم فعلی بدون تغییر ظاهری.
|
||
|
||
#### معیار پذیرش
|
||
- انتقال همارز عین قبل.
|
||
- انتقال بینارزی با نرخ دستی موجودی دو حساب را درست جابهجا میکند.
|
||
|
||
---
|
||
|
||
### فاز ۵ — مانده اشخاص به تفکیک ارز
|
||
|
||
**هدف:** خلاصه وضعیت مالی:
|
||
|
||
```text
|
||
بدهکار: 10,000,000 IRR
|
||
بستانکار: 200 USD
|
||
────────────────
|
||
جمع معادل پایه (نرخ مرجع روز): …
|
||
جمع ریالی جدا (اختیاری طبق ارز پایه)
|
||
نمایش ردیفی: 160,000,000 IRR (100 USD)
|
||
```
|
||
|
||
#### Backend
|
||
- [ ] `GET .../persons/{id}/balances-by-currency`
|
||
- [ ] تجمیع خطوط سند **بدون تبدیل** per `document.currency_id`
|
||
- [ ] فیلد اختیاری `base_equivalent` با نرخ latest یا نرخ روز
|
||
|
||
#### Frontend
|
||
- [ ] کارت خلاصه در جزئیات شخص — فقط MC=ON
|
||
- [ ] لیست اشخاص: ستون تراز فعلی برای تکارزی بماند؛ برای MC میتوان tooltip/زیرنویس per ارز گذاشت (فاز بعدی UI)
|
||
|
||
#### معیار پذیرش
|
||
- تکارزی: همان یک عدد تراز.
|
||
- چندارزی: تفکیک واضح؛ حسابها «به هم نمیریزد».
|
||
|
||
---
|
||
|
||
### فاز ۶ — قیمت دوگانه کالا + آپدیت از نرخ مرجع
|
||
|
||
**هدف:** روی کالا قیمت خرید/فروش ریالی و ارزی؛ در صورت انتخاب کاربر، ریالی از نرخ روز آپدیت شود.
|
||
|
||
#### گزینههای طراحی (یکی انتخاب شود قبل از پیادهسازی)
|
||
|
||
**گزینه A — فیلدهای اختصاصی روی Product (سادهتر برای کاربر ایرانی)**
|
||
`purchase_price_base`, `sales_price_base`, `purchase_price_fx`, `sales_price_fx`, `fx_currency_id`, `auto_update_base_from_fx: bool`
|
||
|
||
**گزینه B — تکیه بر PriceList موجود**
|
||
قیمت ارزی و ریالی بهصورت دو ردیف price_item؛ sync جابیتوانی بین دو ردیف.
|
||
|
||
**توصیه:** گزینه A برای UX فرم کالا + همگامسازی اختیاری با لیستقیمت پیشفرض.
|
||
|
||
#### رفتار sync
|
||
- وقتی `auto_update_base_from_fx` و نرخ latest موجود:
|
||
`base = fx_price × rate`
|
||
- دکمه دستی «بروزرسانی از نرخ روز» در فرم کالا / عملیات گروهی
|
||
- فقط MC=ON؛ تکارزی فقط همان `base_*` فعلی
|
||
|
||
#### معیار پذیرش
|
||
- تکارزی: فرم قیمت بدون بخش ارزی.
|
||
- چندارزی: دو قیمت + آپدیت اختیاری از نرخ toolbar/فاز۱.
|
||
|
||
---
|
||
|
||
### فاز ۷ — یکدستسازی اسناد و گزارشها
|
||
|
||
**هدف:** در بالای صفحات اسناد/ریزگردش/گزارشها:
|
||
|
||
- انتخاب ارز: دلار / ریال / … / **همه ارزها**
|
||
- فقط وقتی MC=ON
|
||
- در حالت یک ارز مشخص: فقط اسناد همان ارز
|
||
- در حالت همه: جمع نهایی با تسعیر به پایه **و** نمایش مقدار اصلی کنارش
|
||
مثال: `۱۶۰,۰۰۰,۰۰۰ ریال (100 $)`
|
||
|
||
#### کارها
|
||
- [ ] فیلتر ارز در `documents_page` و لیستهای مشابه
|
||
- [ ] یکدست کردن گزارشهای بدهکار/بستانکار با گزینه «همه»
|
||
- [ ] فرمتر مشترک `formatAmountWithOriginalCurrency(...)`
|
||
- [ ] داشبورد: الگوی `totals_by_currency` موجود گسترش داده شود
|
||
|
||
#### معیار پذیرش
|
||
- تکارزی: بدون dropdown ارز.
|
||
- چندارزی: فیلتر کار میکند؛ در «همه» شفافیت ارز اصلی سند حفظ میشود.
|
||
|
||
---
|
||
|
||
## ۶. ماتریس وابستگی فازها
|
||
|
||
```text
|
||
فاز ۰ (Gate)
|
||
└─► فاز ۱ (نرخ روز دستی + toolbar)
|
||
├─► فاز ۱ب (API خودکار) [موازی نسبی]
|
||
├─► فاز ۲ (جمع دوگانه فاکتور)
|
||
├─► فاز ۳ (پرداخت چندارزی) [نیاز به نرخ + تصمیم حسابداری]
|
||
├─► فاز ۴ (انتقال بینارزی)
|
||
├─► فاز ۵ (مانده شخص per ارز)
|
||
├─► فاز ۶ (قیمت کالا) [وابسته به نرخ روز]
|
||
└─► فاز ۷ (گزارشها/اسناد) [میتواند موازی بعد از ۰/۱]
|
||
```
|
||
|
||
پیشنهاد ترتیب انتشار: **۰ → ۱ → ۲ → ۳ → ۵ → ۴ → ۶ → ۷** و ۱ب هر زمان پس از ۱.
|
||
|
||
---
|
||
|
||
## ۷. قراردادهای کراسکاتینگ
|
||
|
||
### ۷.۱ Localization
|
||
- همه رشتههای جدید در `app_fa.arb` / `app_en.arb`
|
||
- هیچ متن سختکد فارسی در ویجتهای جدید
|
||
|
||
### ۷.۲ Permissions
|
||
- نرخها: همان `currency_revaluation` (view/add/edit/delete)
|
||
- پرداخت چندارزی: مجوزهای موجود فاکتور/دریافتپرداخت
|
||
- تنظیمات provider: `settings.business`
|
||
|
||
### ۷.۳ گرد کردن
|
||
- احترام به `currencies.decimal_places` و `round_monetary_amounts`
|
||
- تلورانس اختلاف `amount` vs `settles × rate` در حد ۱ واحد کوچکترین اعشار
|
||
|
||
### ۷.۴ تست
|
||
هر فاز حداقل:
|
||
- تست API برای کسبوکار تکارزی (باید رد/مخفی شود)
|
||
- تست API برای چندارزی (مسیر خوشحال)
|
||
- یک golden/widget test برای Gate در Flutter در صورت امکان
|
||
|
||
### ۷.۵ لاگ و پشتیبانی
|
||
- در payment/transfer چندارزی: log شامل rate و currency ids
|
||
- در UI خطاهای `PAYMENT_CURRENCY_MISMATCH` پیام فارسی راهنما وقتی کاربر نرخ وارد نکرده
|
||
|
||
---
|
||
|
||
## ۸. تصمیمهای باز محصول (باید قبل از فاز مربوطه بسته شوند)
|
||
|
||
| # | موضوع | فاز | گزینهها |
|
||
|---|--------|-----|----------|
|
||
| D1 | تفاوت نرخ پرداخت vs نرخ فاکتور بهعنوان سود/زیان تسعیر حسابداری؟ | ۳ | ثبت حسابداری / فقط نمایش |
|
||
| D2 | واحد نرخ پیشفرض UI: ریال یا تومان؟ | ۱،۳ | IRR خالص / نمایش تومان با ×۱۰ |
|
||
| D3 | قیمت کالا: فیلد روی Product یا فقط PriceList؟ | ۶ | A / B |
|
||
| D4 | ارز سند انتقال بینارزی؟ | ۴ | ارز پایه / ارز مبدأ / بدون ارز واحد + extra |
|
||
| D5 | Provider اول API: کدام سرویس؟ | ۱ب | مثقال / سایر |
|
||
| D6 | آیا حذف آخرین ارز فرعی (برگشت به تکارزی) اسناد قدیمی ارزی را قفل کند؟ | ۰ | بله توصیه میشود اگر سند ارزی وجود دارد |
|
||
|
||
---
|
||
|
||
## ۹. چکلیست «تکارزی چیزی نمیبیند»
|
||
|
||
قبل از merge هر PR چندارزی:
|
||
|
||
- [ ] `isMultiCurrency == false` در emulator/session تست شده
|
||
- [ ] toolbar بدون chip نرخ
|
||
- [ ] فاکتور بدون fx field و بدون dual total
|
||
- [ ] پرداخت بدون فیلد نرخ/ارز دوم
|
||
- [ ] انتقال بدون نرخ تبدیل
|
||
- [ ] شخص بدون کارت per-currency
|
||
- [ ] گزارش/اسناد بدون فیلتر ارز
|
||
- [ ] منوی تنظیمات بدون آیتمهای فقط-MC (یا با توضیح غیرفعال)
|
||
- [ ] APIهای جدید برای business تکارزی رفتار امن دارند
|
||
|
||
---
|
||
|
||
## ۱۰. نقشه فایلهای کلیدی (مرجع سریع)
|
||
|
||
### Backend
|
||
- `hesabixAPI/adapters/db/models/currency.py`
|
||
- `hesabixAPI/adapters/db/models/business_currency_rate.py`
|
||
- `hesabixAPI/app/services/business_currency_rate_service.py`
|
||
- `hesabixAPI/app/services/invoice_fx_revaluation.py`
|
||
- `hesabixAPI/app/services/invoice_service.py` (پرداخت و validation ارز)
|
||
- `hesabixAPI/app/services/transfer_service.py`
|
||
- `hesabixAPI/app/services/person_service.py`
|
||
- `hesabixAPI/adapters/api/v1/business_currency_rates.py`
|
||
|
||
### Frontend
|
||
- `hesabixUI/hesabix_ui/lib/core/auth_store.dart` (افزودن flag)
|
||
- shell کسبوکار (toolbar)
|
||
- `widgets/invoice/invoice_fx_rate_field.dart`
|
||
- `widgets/invoice/invoice_transactions_widget.dart`
|
||
- `widgets/transfer/transfer_form_dialog.dart`
|
||
- `pages/business/currency_revaluation_page.dart`
|
||
- `pages/business/persons_page.dart` / جزئیات شخص
|
||
- `widgets/product/sections/product_pricing_inventory_section.dart`
|
||
|
||
---
|
||
|
||
## ۱۱. نحوه پیشبرد کار روی این سند
|
||
|
||
1. فاز ۰ را تمام و merge کنید.
|
||
2. برای هر فاز بعدی: یک issue/PR با عنوان `MC-Phase-N: ...` و لینک به بخش همان فاز در این فایل.
|
||
3. تصمیمهای باز (بخش ۸) را قبل از شروع فاز در همین سند بهروز کنید (وضعیت: تصمیم گرفته شد — تاریخ — انتخاب).
|
||
4. پس از اتمام هر فاز، چکباکسهای همان بخش را تیک بزنید و در انتهای بخش یک خط «انجامشده در: commit/PR» اضافه کنید.
|
||
5. تا وقتی فاز ۰ و قانون Gate پایدار نشده، فازهای بعدی شروع نشوند.
|
||
|
||
---
|
||
|
||
## ۱۲. خارج از محدوده این برنامه (فعلاً)
|
||
|
||
- چند واحد پول همزمان بهعنوان «ارز پایه چندتایی» (فقط یک base)
|
||
- ارزهای رمزنگاری / نرخ لحظهای معاملات
|
||
- تسعیر اجباری پایان دوره فراتر از منطق موجود year-end
|
||
- تغییر رفتار مالیاتی سامانه مودیان برای فاکتور غیرریالی (قوانین جدا)
|
||
|
||
---
|
||
|
||
## ۱۳. وضعیت فاز ۰ (بهروز ۲۰۲۶-۰۷-۱۸)
|
||
|
||
سناریوی اجرایی: [MULTI_CURRENCY_PHASE0_SCENARIO.md](./MULTI_CURRENCY_PHASE0_SCENARIO.md)
|
||
|
||
پیادهسازیشده:
|
||
- `is_multi_currency` روی پاسخ کسبوکار + Gate UI (منوی تسعیر و سیاست FX فقط برای MC)
|
||
- ادمین: ارائهدهندگان نرخ (`/admin/fx-providers`) با کلید رمزنگاریشده
|
||
- اسنپشات مرکزی `fx_global_rates` + worker دورهای (بازه پیشفرض ۱۵ دقیقه)
|
||
- BRS API (`brsapi.ir`) بهعنوان provider اول
|
||
- ثبت سریع تسعیر کسبوکار از اسنپشات (`apply-from-global`)
|
||
|
||
کلید API را در ریپو commit نکنید؛ از UI ادمین یا `HESABIX_FX_BRSAPI_API_KEY` تنظیم کنید.
|