arc/docs/MULTI_CURRENCY_AUTO_SYNC_SCENARIO.md

301 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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.

# سناریو: زمان‌بندی خودکار ثبت نرخ تسعیر کسب‌وکار + آفست (درصد / مبلغ)
**تاریخ:** ۲۰۲۶-۰۷-۱۸
**وضعیت:** پیاده‌سازی‌شده (MVP)
**وابستگی‌ها:** فاز ۰ (اسنپ‌شات مرکزی) + فاز ۱ (نوار ابزار / apply دستی)
**جایگاه در نقشه راه:** جایگزین عملی «فاز ۱ب» قدیمی؛ دیگر هر کسب‌وکار به API خارجی وصل نمی‌شود.
---
## ۱. مسئله کاربر
کاربر می‌خواهد در **تنظیمات کسب‌وکار** بتواند:
1. ثبت نرخ در «تسعیر ارز» (`business_currency_rates`) را **خودکار و زمان‌بندی‌شده** کند.
2. برای هر ارز فرعی، نرخ نهایی را نسبت به نرخ مرجع **بالاتر یا پایین‌تر** تنظیم کند:
- با **درصد** (مثلاً +۲٪ یا −۱٫۵٪)، یا
- با **مبلغ ثابت** (مثلاً +۵۰٬۰۰۰ ریال روی هر واحد دلار).
هدف: بدون دخالت روزانه، نرخ‌های کسب‌وکار به‌روز بماند و حاشیهٔ تجاری/احتیاطی هم اعمال شود.
---
## ۲. اصل معماری (خیلی مهم)
| لایه | نقش |
|------|-----|
| **ادمین کل** (فاز ۰) | واکشی متمرکز از BRS/مثقال → `fx_global_rates` |
| **کسب‌وکار** (این سناریو) | فقط از اسنپ‌شات مرکزی می‌خواند و با آفست خودش در `business_currency_rates` می‌نویسد |
```text
[Provider API] --۱۵دقیقه--> [fx_global_rates]
│
▼ (زمان‌بندی کسب‌وکار)
[آفست per ارز]
│
▼
[business_currency_rates]
(نرخ تسعیر اسناد)
```
**نباید** برای هر کسب‌وکار درخواست جدا به brsapi زده شود (مسدود شدن کلید).
**فقط وقتی `is_multi_currency = true`** این تنظیمات دیده و اجرا می‌شود.
---
## ۳. تجربه کاربری (تنظیمات)
### ۳.۱ محل ورود
مسیر پیشنهادی (کنار سیاست تسعیر فاکتور):
`تنظیمات کسب‌وکار → ارزها / تسعیر → «به‌روزرسانی خودکار نرخ»`
یا کارت جدا در دسته مالی:
- عنوان: «زمان‌بندی نرخ تسعیر»
- فقط اگر چندارزی فعال باشد؛ وگرنه مخفی یا غیرفعال با توضیح.
### ۳.۲ بلوک اصلی تنظیمات
| فیلد | توضیح |
|------|--------|
| فعال‌سازی | سوئیچ On/Off |
| منبع نرخ | فعلاً فقط «اسنپ‌شات مرکزی سیستم» (ثابت) |
| حالت زمان‌بندی | یکی از گزینه‌های زیر |
| ارزهای مشمول | همه فرعی‌ها / انتخابی |
| جلوگیری از تکرار | اگر نرخ محاسبه‌شده با آخرین نرخ ثبت‌شده تفاوت معناداری نداشت، ننویسد (تلورانس) |
### ۳.۳ حالت‌های زمان‌بندی (پیشنهاد)
**الف) بازه‌ای ساده (MVP)**
- هر N ساعت (۱ / ۲ / ۳ / ۶ / ۱۲ / ۲۴)
- هم‌تراز با سادگی فعلی worker سیستم
**ب) ساعات ثابت روزانه**
- لیست ساعات مثل `09:00, 13:00, 18:00` به timezone کسب‌وکار (`display_timezone`)
- مناسب کسب‌وکارهایی که می‌خواهند قبل از شروع کار / ظهر / عصر نرخ قفل شود
**ج) Cron پیشرفته (فاز بعد از MVP — اختیاری)**
- عبارت cron کامل برای کاربران حرفه‌ای
**توصیه MVP:** الف + ب. ج را عقب بیندازیم.
### ۳.۴ آفست per ارز
جدول برای هر ارز فرعی فعال:
| ستون | مثال |
|------|------|
| ارز | USD |
| نوع آفست | درصد / مبلغ ثابت / بدون آفست |
| جهت | بالاتر (+) / پایین‌تر (−) |
| مقدار | `2` برای ۲٪ یا `50000` برای ۵۰ هزار واحد پایه |
| فعال | ✓ |
**فرمول:**
```text
base_rate = نرخ اسنپ‌شات (تبدیل‌شده به ارز پایه کسب‌وکار)
final_rate =
if none: base_rate
if percent: base_rate * (1 + sign * percent/100)
if amount: base_rate + sign * amount
```
`sign = +1` بالاتر، `−1` پایین‌تر.
نرخ نهایی باید `> 0` باشد؛ در غیر این صورت آن ارز در آن اجرا skip + خطا در لاگ.
**نمونه:**
اسنپ‌شات: ۱ USD = 1,932,000 ریال
آفست: +۲٪ → ثبت: 1,970,640
آفست: −۵۰٬۰۰۰ ریال → ثبت: 1,882,000
### ۳.۵ پیش‌نمایش زنده در UI
قبل از ذخیره / کنار جدول:
- «نرخ مرجع الان: …»
- «نرخ پس از آفست: …»
- «آخرین اجرای موفق: …»
- «اجرای بعدی تقریبی: …»
- دکمه «اجرای آزمایشی اکنون» (فقط با مجوز add)
---
## ۴. مدل داده پیشنهادی
### ۴.۱ تنظیمات سطح کسب‌وکار
جدول `business_fx_auto_sync_settings` (یا JSON روی `businesses`):
| فیلد | نوع | توضیح |
|------|-----|--------|
| business_id | FK | |
| enabled | bool | |
| schedule_mode | enum | `interval` \| `daily_times` |
| interval_hours | int? | برای mode=interval |
| daily_times | json? | `["09:00","18:00"]` |
| timezone | str? | پیش‌فرض از `display_timezone` کسب‌وکار |
| skip_if_unchanged | bool | پیش‌فرض true |
| min_change_percent | numeric? | مثلاً ۰٫۰۱٪ — کمتر از این ننویس |
| last_run_at | datetime? | |
| last_run_status | str? | ok/error/partial |
| last_run_message | text? | |
| next_run_at | datetime? | برای UI و worker |
### ۴.۲ آفست per ارز
جدول `business_fx_auto_sync_currency_rules`:
| فیلد | نوع |
|------|-----|
| business_id | FK |
| currency_id | FK |
| enabled | bool |
| offset_type | `none` \| `percent` \| `amount` |
| offset_direction | `up` \| `down` |
| offset_value | Numeric |
| unique(business_id, currency_id) | |
اگر برای ارزی rule نباشد → `none` (ثبت عین اسنپ‌شات).
### ۴.۳ خروجی هر اجرا
همان `business_currency_rates` موجود با:
```text
note = "خودکار | اسنپ‌شات مرکزی | آفست +2% | ref=1932000 @ 2026-07-18T..."
created_by_user_id = کاربر سیستم / مالک کسب‌وکار (تصمیم باز D1)
```
---
## ۵. Worker / اجرا
### ۵.۱ حلقه پس‌زمینه
شبیه `fx_global_rates_fetch_loop`:
- هر ۱–۵ دقیقه کسب‌وکارهایی که `enabled` و `next_run_at <= now` را پیدا کند.
- برای هر کدام:
1. اسنپ‌شات مرتبط با ارزهای فرعی را بخواند.
2. اگر اسنپ‌شات خیلی قدیمی (مثلاً > ۲۴س) و سیاست `block_if_stale` → skip با status=error.
3. آفست را اعمال کند.
4. در صورت `skip_if_unchanged` با آخرین نرخ مقایسه کند.
5. ردیف(های) جدید در `business_currency_rates` بنویسد.
6. `last_run_*` و `next_run_at` را به‌روز کند.
### ۵.۲ محدودیت‌ها
- فقط کسب‌وکارهای MC
- حداکثر N کسب‌وکار در هر تیک (مثلاً ۵۰) تا فشار DB کنترل شود
- اجرا در `asyncio.to_thread` (مثل فاز ۰)
### ۵.۳ همپوشانی با فاز ۰
ترتیب زمانی ایده‌آل:
```text
دقیقه ۰: واکشی مرکزی BRS
دقیقه ۱+: اعمال خودکار کسب‌وکارها روی همان اسنپ‌شات
```
لازم نیست کسب‌وکار دقیقاً هم‌زمان با fetch مرکزی باشد؛ کافی است اسنپ‌شات تازه باشد.
---
## ۶. API پیشنهادی
| متد | مسیر | توضیح |
|-----|------|--------|
| GET | `/businesses/{id}/fx-auto-sync` | خواندن تنظیمات + rules + وضعیت آخرین اجرا |
| PUT | `/businesses/{id}/fx-auto-sync` | ذخیره تنظیمات و آفست‌ها |
| POST | `/businesses/{id}/fx-auto-sync/run-now` | اجرای دستی فوری (تست) |
| GET | `/businesses/{id}/fx-auto-sync/preview` | پیش‌نمایش نرخ مرجع + نرخ نهایی بدون ذخیره |
مجوز: `currency_revaluation` (view/edit) یا `settings.business` — پیشنهاد:
- مشاهده: `currency_revaluation.view`
- ویرایش/اجرا: `currency_revaluation.add` + `settings.business`
---
## ۷. سناریوهای پذیرش
### S1 — تک‌ارزی
تنظیمات دیده نمی‌شود؛ API → `MULTI_CURRENCY_REQUIRED`.
### S2 — فعال‌سازی بازه‌ای بدون آفست
هر ۶ ساعت برای USD/EUR ردیف جدید با نرخ عین اسنپ‌شات ثبت می‌شود؛ note شامل «خودکار».
### S3 — آفست درصدی
USD +۲٪؛ مقدار ثبت‌شده دقیقاً `ref * 1.02` (با گرد کردن طبق `decimal_places` ارز پایه).
### S4 — آفست مبلغی پایین‌تر
EUR −۱۰٬۰۰۰ ریال؛ نرخ منفی نشود.
### S5 — skip بدون تغییر
اگر نرخ نهایی با آخرین نرخ کمتر از تلورانس فرق داشت، ردیف جدید ساخته نشود؛ `last_run_status=ok` با پیام «بدون تغییر».
### S6 — اسنپ‌شات خالی/کهنه
اجرا fail جزئی یا کامل با پیام واضح در تنظیمات؛ فاکتور از نرخ قبلی استفاده می‌کند.
### S7 — اجرای آزمایشی
دکمه Run now همان منطق را فوری اجرا می‌کند و در تاریخچه نرخ دیده می‌شود.
### S8 — خاموش کردن
با Off شدن، `next_run_at` خالی و worker دیگر این کسب‌وکار را برنمی‌دارد.
---
## ۸. تصمیم‌های باز محصول
| # | موضوع | گزینه‌ها | پیشنهاد |
|---|--------|----------|---------|
| D1 | `created_by_user_id` برای ردیف خودکار | کاربر سیستم / آخرین ویرایش‌کننده تنظیمات / مالک | آخرین کاربری که تنظیمات را ذخیره کرده |
| D2 | اگر اسنپ‌شات برای یک ارز نبود | skip همان ارز / fail کل اجرا | skip همان ارز + status=partial |
| D3 | چند بار در روز با ساعات ثابت و timezone | اجباری به `display_timezone` | بله |
| D4 | آیا آفست روی فاکتور هم جداگانه باشد؟ | خیر — فقط روی نرخ تسعیر کسب‌وکار | فقط تسعیر |
| D5 | سقف آفست | مثلاً درصد ≤ ۵۰٪ | برای جلوگیری از اشتباه فاحش |
| D6 | نام فاز در نقشه راه | «فاز ۱٫۵ / Auto Sync» به‌جای ۱ب قدیمی | بله |
---
## ۹. خارج از محدوده این سناریو
- اتصال مستقیم کسب‌وکار به API مثقال/BRS (کلید per business)
- تغییر خودکار قیمت کالا (فاز ۶ جداست؛ می‌تواند بعداً از همین نرخ تسعیر تغذیه شود)
- آفست متفاوت برای خرید در برابر فروش روی کالا
- تسعیر مجدد اسناد قدیمی با نرخ جدید
---
## ۱۰. تخمین فازبندی پیاده‌سازی
| مرحله | محتوا | وضعیت |
|-------|--------|--------|
| **A — MVP** | سوئیچ + interval ساعات + آفست percent/amount per ارز + worker + run-now + preview | ✅ |
| **B** | ساعات ثابت روزانه + timezone + skip_if_unchanged + هشدار stale | ✅ (همراه MVP) |
| **C** | لاگ اجراهای اخیر در UI + اعلان اختیاری «اجرای ناموفق» | ⏳ بعداً |
### API (خلاصه)
- `GET/PUT /api/v1/businesses/{id}/fx-auto-sync`
- `GET|POST /api/v1/businesses/{id}/fx-auto-sync/preview` (POST = پیش‌نویس بدون ذخیره)
- `POST /api/v1/businesses/{id}/fx-auto-sync/run-now`
### UI
- مسیر: `settings/fx-auto-sync`
- ورود از هاب تنظیمات مالی (فقط `is_multi_currency`)
---
## ۱۱. جمع‌بندی برای تأیید
این قابلیت **تنظیمات سطح کسب‌وکار** است، نه ادمین کل:
- منبع نرخ = اسنپ‌شات مرکزی (فاز ۰)
- زمان‌بندی = بازه یا ساعات روز
- حاشیه = درصد یا مبلغ، بالاتر/پایین‌تر، per ارز
- خروجی = ردیف‌های تاریخچه تسعیر که نوار ابزار و فاکتور همین الان از آن‌ها استفاده می‌کنند
با تأیید تصمیم‌های D1–D5 می‌توان پیاده‌سازی مرحله A را شروع کرد.