Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/docs/MULTI_CURRENCY_IMPLEMENTATION_GUIDE.md

29 KiB
Raw Permalink Blame History

راهنمای فنی پیاده‌سازی چندارزی عملیاتی (Hesabix)

وضعیت: نسخه ۱ پیاده‌سازی‌شده (هسته عملیاتی) — برنامه نسخه ۲ در MULTI_CURRENCY_EXECUTION_PLAN.md بخش‌های ۱۶–۱۸
مخاطب: تیم توسعه Backend / Flutter
هدف: چندارزی عملیاتی با حفظ سادگی کامل برای کسب‌وکارهای تک‌ارزی.

برای وضعیت فازها و برنامه v2 به سند اجرایی مراجعه کنید؛ این راهنما قراردادهای طراحی را نگه می‌دارد.


۱. خلاصه اجرایی

امروز حسابیکس چندارزی در سطح سند دارد (ارز اصلی/فرعی، تاریخچه نرخ دستی، تسعیر فاکتور، فیلتر ارز در بسیاری گزارش‌ها). درخواست کاربر عمدتاً به چندارزی عملیاتی مربوط است:

قابلیت وضعیت فعلی هدف
نرخ روز در نوار ابزار + API خارجی ندارد دارد
قیمت دوگانه کالا + sync از نرخ ناقص دارد
جمع دوگانه فاکتور (ارزی + پایه + نرخ) ناقص دارد
پرداخت فاکتور با ارز دیگر + نرخ per قسط مسدود دارد
انتقال بانکی بین‌ارزی با نرخ دستی ندارد دارد
مانده شخص به تفکیک ارز ندارد دارد
فیلتر/نمایش دوگانه در اسناد و گزارش‌ها ناقص یکدست

اصل طلایی طراحی: کسب‌وکاری که فقط یک ارز دارد، باید UI و جریان کاری‌اش دقیقاً مثل قبل بماند و هیچ اثری از چندارزی نبیند.


۲. اصل Progressive Disclosure (مخفی‌سازی برای تک‌ارزی)

۲.۱ تعریف رسمی «حالت چندارزی فعال»

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 باشد مجاز است.

۲.۵ ویجت‌های مشترک (الگو)

/// الگوی پیشنهادی — پیاده‌سازی در فاز ۰
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)

۴.۱ مفاهیم

Base Currency     = ارز اصلی کسب‌وکار (default_currency_id)
Document Currency = ارز سند (فاکتور/دریافت/انتقال/...)
Payment Currency  = ارز حساب/وسیله پرداخت (ممکن است ≠ Document Currency)
Spot / Reference Rate = نرخ مرجع روز (۱ واحد ارز فرعی = rate × پایه)
Transaction Rate  = نرخ قفل‌شده روی همان تراکنش/قسط (ممکن است ≠ نرخ روز)

۴.۲ اصل حسابداری

  • مانده‌های حساب بانکی/صندوق/تنخواه همیشه به ارز همان حساب نگه داشته می‌شوند.
  • بدهی شخص بابت فاکتور به ارز فاکتور است.
  • وقتی پرداخت با ارز دیگر انجام شود، باید:
    1. مبلغ به ارز فاکتور (کاهش بدهی)،
    2. مبلغ به ارز پرداخت (حرکت حساب نقدی)،
    3. نرخ و مابه‌التفاوت تسعیر (در صورت نیاز طبق سیاست)، همگی ثبت و قابل گزارش باشند.

۴.۳ ذخیره نرخ روی تراکنش

هر جا نرخ مهم است، snapshot شود (مثل فاکتور فعلی با extra_info.fx):

{
  "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

  • endpoint خلاصه نرخ‌های جاری:
    GET /businesses/{id}/currency-rates/latest
  • POST .../currency-rates/bulk برای ثبت چند ارز یکجا از دیالوگ toolbar

Frontend

  • ویجت DailyFxRatesToolbarChip در shell کسب‌وکار
  • دیالوگ/شیت ثبت سریع نرخ + «از نرخ روز» (اسنپ‌شات مرکزی فاز ۰)
  • کش کوتاه‌مدت + 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 تبدیل تومان↔ریال صریح باشد.

مدل داده پیشنهادی (آیتم پرداخت)

{
  "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. واحد نمایش نرخ (ریال در برابر تومان) در تنظیمات کسب‌وکار.

فاز ۴ — انتقال بانکی بین‌ارزی با نرخ دستی

هدف: از حساب دلاری به حساب ریالی (یا بالعکس) با نرخ دستی.

مدل

{
  "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: فیلد نرخ + مبلغ مقصد (یا محاسبه خودکار).
  • هم‌ارز: فرم فعلی بدون تغییر ظاهری.

معیار پذیرش

  • انتقال هم‌ارز عین قبل.
  • انتقال بین‌ارزی با نرخ دستی موجودی دو حساب را درست جابه‌جا می‌کند.

فاز ۵ — مانده اشخاص به تفکیک ارز

هدف: خلاصه وضعیت مالی:

بدهکار: 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 ارز.
  • چندارزی: فیلتر کار می‌کند؛ در «همه» شفافیت ارز اصلی سند حفظ می‌شود.

۶. ماتریس وابستگی فازها

فاز ۰ (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

پیاده‌سازی‌شده:

  • 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 تنظیم کنید.