arc/docs/MULTI_CURRENCY_AUTO_SYNC_SCENARIO.md

13 KiB
Raw Permalink Blame History

سناریو: زمان‌بندی خودکار ثبت نرخ تسعیر کسب‌وکار + آفست (درصد / مبلغ)

تاریخ: ۲۰۲۶-۰۷-۱۸
وضعیت: پیاده‌سازی‌شده (MVP)
وابستگی‌ها: فاز ۰ (اسنپ‌شات مرکزی) + فاز ۱ (نوار ابزار / apply دستی)
جایگاه در نقشه راه: جایگزین عملی «فاز ۱ب» قدیمی؛ دیگر هر کسب‌وکار به API خارجی وصل نمی‌شود.


۱. مسئله کاربر

کاربر می‌خواهد در تنظیمات کسب‌وکار بتواند:

  1. ثبت نرخ در «تسعیر ارز» (business_currency_rates) را خودکار و زمان‌بندی‌شده کند.
  2. برای هر ارز فرعی، نرخ نهایی را نسبت به نرخ مرجع بالاتر یا پایین‌تر تنظیم کند:
    • با درصد (مثلاً +۲٪ یا −۱٫۵٪)، یا
    • با مبلغ ثابت (مثلاً +۵۰٬۰۰۰ ریال روی هر واحد دلار).

هدف: بدون دخالت روزانه، نرخ‌های کسب‌وکار به‌روز بماند و حاشیهٔ تجاری/احتیاطی هم اعمال شود.


۲. اصل معماری (خیلی مهم)

لایه نقش
ادمین کل (فاز ۰) واکشی متمرکز از 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 را پیدا کند.
  • برای هر کدام:
    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 (مثل فاز ۰)

۵.۳ همپوشانی با فاز ۰

ترتیب زمانی ایده‌آل:

دقیقه ۰:  واکشی مرکزی 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 را شروع کرد.