13 KiB
سناریو: زمانبندی خودکار ثبت نرخ تسعیر کسبوکار + آفست (درصد / مبلغ)
تاریخ: ۲۰۲۶-۰۷-۱۸
وضعیت: پیادهسازیشده (MVP)
وابستگیها: فاز ۰ (اسنپشات مرکزی) + فاز ۱ (نوار ابزار / apply دستی)
جایگاه در نقشه راه: جایگزین عملی «فاز ۱ب» قدیمی؛ دیگر هر کسبوکار به API خارجی وصل نمیشود.
۱. مسئله کاربر
کاربر میخواهد در تنظیمات کسبوکار بتواند:
- ثبت نرخ در «تسعیر ارز» (
business_currency_rates) را خودکار و زمانبندیشده کند. - برای هر ارز فرعی، نرخ نهایی را نسبت به نرخ مرجع بالاتر یا پایینتر تنظیم کند:
- با درصد (مثلاً +۲٪ یا −۱٫۵٪)، یا
- با مبلغ ثابت (مثلاً +۵۰٬۰۰۰ ریال روی هر واحد دلار).
هدف: بدون دخالت روزانه، نرخهای کسبوکار بهروز بماند و حاشیهٔ تجاری/احتیاطی هم اعمال شود.
۲. اصل معماری (خیلی مهم)
| لایه | نقش |
|---|---|
| ادمین کل (فاز ۰) | واکشی متمرکز از BRS/مثقال → fx_global_rates |
| کسبوکار (این سناریو) | فقط از اسنپشات مرکزی میخواند و با آفست خودش در business_currency_rates مینویسد |
[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 برای ۵۰ هزار واحد پایه |
| فعال | ✓ |
فرمول:
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 موجود با:
note = "خودکار | اسنپشات مرکزی | آفست +2% | ref=1932000 @ 2026-07-18T..."
created_by_user_id = کاربر سیستم / مالک کسبوکار (تصمیم باز D1)
۵. Worker / اجرا
۵.۱ حلقه پسزمینه
شبیه fx_global_rates_fetch_loop:
- هر ۱–۵ دقیقه کسبوکارهایی که
enabledوnext_run_at <= nowرا پیدا کند. - برای هر کدام:
- اسنپشات مرتبط با ارزهای فرعی را بخواند.
- اگر اسنپشات خیلی قدیمی (مثلاً > ۲۴س) و سیاست
block_if_stale→ skip با status=error. - آفست را اعمال کند.
- در صورت
skip_if_unchangedبا آخرین نرخ مقایسه کند. - ردیف(های) جدید در
business_currency_ratesبنویسد. last_run_*وnext_run_atرا بهروز کند.
۵.۲ محدودیتها
- فقط کسبوکارهای MC
- حداکثر N کسبوکار در هر تیک (مثلاً ۵۰) تا فشار DB کنترل شود
- اجرا در
asyncio.to_thread(مثل فاز ۰)
۵.۳ همپوشانی با فاز ۰
ترتیب زمانی ایدهآل:
دقیقه ۰: واکشی مرکزی 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-syncGET|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 را شروع کرد.