945 lines
49 KiB
Markdown
945 lines
49 KiB
Markdown
# سند اجرایی فازبهفاز چندارزی عملیاتی — حسابیکس
|
||
|
||
**نوع سند:** برنامه اجرایی (Execution Plan)
|
||
**وضعیت:** پیشنویس اجرایی — بدون اعمال کد در این مرحله
|
||
**مخاطب:** مالک محصول، معمار، Backend، Flutter، QA
|
||
**مرجع وضعیت فعلی:** گزارش تحلیل چندارزی + `MULTI_CURRENCY_IMPLEMENTATION_GUIDE.md`
|
||
**اصل غیرقابلمذاکره:** کسبوکار تکارزی نباید هیچ UI/جریان کاری چندارزی غیرضروری ببیند.
|
||
|
||
---
|
||
|
||
## ۰. هدف و تعریف موفقیت
|
||
|
||
### ۰.۱ هدف نهایی
|
||
رساندن حسابیکس از «چندارزی سطح سند + نرخ + گزارش محدود» به «چندارزی عملیاتی قابلاعتماد» با این قابلیتها:
|
||
|
||
1. فاکتور/خرید/دریافت/پرداخت/انتقال با ارز مناسب
|
||
2. تسویه بینارزی با نرخ تراکنش
|
||
3. مانده طرفحساب به تفکیک ارز (+ معادل پایه)
|
||
4. گزارشهای یکدست (فیلتر ارز / همه ارزها با تسعیر شفاف)
|
||
5. تسعیر پایان دوره (در صورت تصمیم محصول)
|
||
6. رفتار صفرِ اصطکاک برای کسبوکار تکارزی
|
||
|
||
### ۰.۲ تعریف رسمی حالتها
|
||
|
||
```text
|
||
MC = OFF ⇔ کسبوکار فقط default_currency دارد (بدون ارز فرعی در business_currencies)
|
||
MC = ON ⇔ حداقل یک ارز فرعی ≠ default_currency_id در business_currencies
|
||
```
|
||
|
||
منبع حقیقت کلاینت: `AuthStore.isMultiCurrency` از فیلد API `is_multi_currency`.
|
||
|
||
### ۰.۳ معیار موفقیت کلی (Definition of Done محصول)
|
||
|
||
| معیار | تکارزی (MC=OFF) | چندارزی (MC=ON) |
|
||
|------|------------------|-----------------|
|
||
| UI | عین امروز؛ بدون فیلد/منوی FX | قابلیتهای فازهای منتشرشده دیده میشود |
|
||
| داده قدیمی | بدون تغییر معنایی | خواندنی و گزارشپذیر با قوانین مهاجرت |
|
||
| پرداخت همارز | بدون تغییر | بدون تغییر |
|
||
| پرداخت بینارزی | غیرقابلدسترسی | طبق سیاست فاز ۳ |
|
||
| گزارش | بدون dropdown ارز | فیلتر + حالت «همه» شفاف |
|
||
|
||
---
|
||
|
||
## ۱. وضعیت پایه (Baseline) — چه چیزی از قبل هست
|
||
|
||
| قطعه | وضعیت | یادداشت |
|
||
|------|--------|---------|
|
||
| کاتالوگ ارز + business_currencies | ✅ | |
|
||
| ارز پایه کسبوکار | ✅ | پس از تنظیم اولیه قابل تغییر نیست |
|
||
| `is_multi_currency` + MultiCurrencyGate | ✅ | فاز ۰ |
|
||
| تاریخچه نرخ `business_currency_rates` | ✅ | یک نرخ واحد نسبت به پایه |
|
||
| snapshot فاکتور `extra_info.fx` | ✅ | |
|
||
| نرخ روز toolbar + apply-from-global | ✅ | فاز ۱ |
|
||
| auto-sync نرخ از اسنپشات مرکزی | ✅ | فاز ۱ب |
|
||
| ارز روی Document / Bank / Cash / Check | ✅ | |
|
||
| پرداخت بینارزی | ❌ مسدود | `PAYMENT_CURRENCY_MISMATCH` |
|
||
| انتقال بینارزی | ❌ | |
|
||
| مبالغ پایه روی DocumentLine | ❌ | |
|
||
| مانده شخص per currency | ❌ | |
|
||
| سند تسعیر پایان دوره | ❌ | فقط برچسب حساب در CoA |
|
||
| تست automated MC | ❌ تقریباً | |
|
||
|
||
---
|
||
|
||
## ۲. تحلیل حیاتی: فعالسازی چندارزی روی خطوط سند و دادهٔ قبلی
|
||
|
||
این بخش پاسخ مستقیم به سؤال: *اگر روی خطوط سند چندارزی را فعال کنیم، برای اطلاعات قبلی چه میشود؟*
|
||
|
||
### ۲.۱ مدل فعلی خطوط
|
||
|
||
امروز `document_lines` فقط دارد:
|
||
|
||
- `debit`, `credit` → مبلغ به **ارز سند** (`documents.currency_id`)
|
||
- بدون `currency_id` خط
|
||
- بدون `exchange_rate`
|
||
- بدون `debit_base` / `credit_base`
|
||
- بدون `currency_amount` جدا
|
||
|
||
نرخ فاکتور (در صورت وجود) فقط در `documents.extra_info.fx` است، نه روی خط.
|
||
|
||
### ۲.۲ گزینههای طراحی خط چندارزی
|
||
|
||
#### گزینه A — ستونهای اختیاری روی خط (پیشنهاد اجرایی این سند)
|
||
|
||
افزودن ستونهای nullable:
|
||
|
||
| ستون | معنی |
|
||
|------|------|
|
||
| `currency_id` | ارز مبلغ خط (پیشفرض = ارز سند) |
|
||
| `exchange_rate` | نرخ قفلشده نسبت به ارز پایه در لحظه ثبت |
|
||
| `debit_base` | بدهکار به ارز پایه |
|
||
| `credit_base` | بستانکار به ارز پایه |
|
||
|
||
یا معادل فشردهتر:
|
||
|
||
| ستون | معنی |
|
||
|------|------|
|
||
| `exchange_rate` | نرخ سند/خط به پایه |
|
||
| `debit_base` / `credit_base` | مبالغ پایه |
|
||
|
||
**قانون سازگاری عقبرو:** ستونها nullable باشند؛ کد خواندن همیشه fallback داشته باشد.
|
||
|
||
#### گزینه B — فقط JSON در `document_lines.extra_info`
|
||
|
||
سریعتر، ولی برای گزارش/ایندکس/بستن سال ضعیفتر است. برای هسته حسابداری توصیه نمیشود.
|
||
|
||
#### گزینه C — بدون persist پایه؛ فقط convert-on-read (وضعیت نزدیک به امروز)
|
||
|
||
برای گزارش لحظهای کافی است، برای audit، بستن سال، و تسویه بینارزی کافی نیست.
|
||
|
||
**تصمیم پیشنهادی سند:** گزینه A با nullable + backfill تدریجی.
|
||
|
||
### ۲.۳ چه بر سر دادهٔ قبلی میآید؟
|
||
|
||
| نوع داده تاریخی | نیاز به تغییر اجباری؟ | رفتار پیشنهادی |
|
||
|-----------------|------------------------|----------------|
|
||
| کسبوکارهای تکارزی (اکثریت) | **خیر** | ستونهای جدید `NULL`؛ در runtime: `rate=1` و `*_base = debit/credit` |
|
||
| اسناد ارزی با `extra_info.fx` | **اختیاری ولی توصیهشده** | backfill: `exchange_rate` و `*_base` از `fx.rate` |
|
||
| اسناد ارزی بدون `fx` | **اختیاری با ریسک** | backfill با `resolve_rate_to_base(as_of=document_date)` یا علامت `fx_inferred=true` |
|
||
| اسناد پایه (currency = default) | **خیر** | `NULL` یا پر کردن با rate=1 در backfill سبک |
|
||
| سالهای بستهشده | **حساس** | یا دستنخورده بمانند (خواندن با fallback) یا backfill فقطخواندنی بدون تغییر مانده منطقی |
|
||
|
||
### ۲.۴ آیا دادهٔ قبلی «خراب» میشود؟
|
||
|
||
**اگر درست پیاده شود: خیر.**
|
||
|
||
شرایط ایمنی:
|
||
|
||
1. ستونهای جدید **nullable** باشند و migration فقط ADD COLUMN باشد (بدون NOT NULL فوری).
|
||
2. همه خوانندهها این اولویت را رعایت کنند:
|
||
|
||
```text
|
||
اگر debit_base/credit_base موجود → استفاده کن
|
||
وگرنه اگر document.extra_info.fx.rate موجود → amount × rate
|
||
وگرنه اگر rate book as-of موجود → amount × resolved_rate
|
||
وگرنه اگر ارز سند = ارز پایه → amount
|
||
وگرنه → سیاست when_no_rate (block در ثبت جدید؛ برای تاریخ: flag + rate=1 فقط با لاگ هشدار)
|
||
```
|
||
|
||
3. نوشتههای جدید (پس از فاز مربوطه) همیشه base را persist کنند.
|
||
4. هیچ jobی مانده سال بستهشده را دوباره «بازمحاسبه و بازنویسی سند بستن» نکند مگر با ابزار صریح ادمین.
|
||
|
||
### ۲.۵ آیا backfill اجباری است؟
|
||
|
||
| سناریو | اجباری؟ | توضیح |
|
||
|--------|---------|--------|
|
||
| فقط افزودن ستون برای آینده | خیر | سیستم با fallback کار میکند |
|
||
| گزارش dual دقیق تاریخی | تقریباً بله | بدون backfill، نرخ ممکن است بعداً عوض شده باشد و معادل پایه «بازسازیشده» با snapshot تاریخی یکی نباشد |
|
||
| تسویه بینارزی روی فاکتورهای قدیمی | بله برای همان فاکتورها | بهتر است قبل از تسویه، fx خط/سند قفل شود |
|
||
| کسبوکارهای تکارزی | خیر | هیچ backfill سنگینی لازم نیست |
|
||
|
||
### ۲.۶ استراتژی مهاجرت پیشنهادی (۳ لایه)
|
||
|
||
```text
|
||
لایه 1 — Schema only (ایمن، سریع)
|
||
ADD nullable columns + indexes
|
||
هیچ UPDATE انبوهی نیست
|
||
|
||
لایه 2 — Soft backfill (پسزمینه، idempotent)
|
||
برای اسنادی که extra_info.fx دارند:
|
||
line.exchange_rate = fx.rate
|
||
line.debit_base = debit * rate
|
||
line.credit_base = credit * rate
|
||
برای اسناد ارز پایه:
|
||
اختیاری: rate=1 و base=amount یا NULL بماند
|
||
|
||
لایه 3 — Inferred backfill (اختیاری، پرچمدار)
|
||
اسناد ارزی بدون fx:
|
||
resolve از rate book
|
||
developer_data/extra_info: { "fx_source": "inferred_backfill", ... }
|
||
گزارشها بتوانند «نرخ استنباطی» را متمایز کنند
|
||
```
|
||
|
||
### ۲.۷ ریسکهای دادهٔ قبلی (باید در QA پوشش داده شود)
|
||
|
||
1. **تغییر نرخ بعد از ثبت سند** → بدون snapshot، گزارش تاریخی عوض میشود. به همین دلیل persist پایه مهم است.
|
||
2. **fallback نرخ ۱** در کد فعلی مانده شخص/بستن سال → پس از فعالسازی خط، باید برای دادهٔ جدید ممنوع و برای قدیم قابلردیابی باشد.
|
||
3. **سال بستهشده** → backfill نباید سند افتتاحیه سال بعد را جابهجا کند.
|
||
4. **اسناد ترکیبی آینده (پرداخت بینارزی)** → مدل «یک ارز برای کل سند» ممکن است کافی نباشد؛ فاز ۳ باید تصمیم D4/D1 را قطعی کند قبل از schema نهایی.
|
||
|
||
### ۲.۸ حکم اجرایی برای خطوط سند
|
||
|
||
> فعالسازی چندارزی روی خطوط **نیازمند تغییر اجباری و دستی دادهٔ قبلی برای تکارزی نیست**.
|
||
> برای چندارزی تاریخی، **مهاجرت نرم (لایه ۱+۲) کافی و توصیهشده** است.
|
||
> بازطراحی اجباری همه اسناد قدیمی لازم نیست، به شرط وجود fallback یکسان در سرویسهای گزارش/مانده/بستن سال.
|
||
|
||
---
|
||
|
||
## ۳. اصل Progressive Disclosure (الزام هر فاز)
|
||
|
||
### ۳.۱ قوانین UI وقتی MC=OFF
|
||
|
||
| محل | رفتار اجباری |
|
||
|-----|----------------|
|
||
| Toolbar | بدون chip نرخ |
|
||
| منوی تنظیمات | بدون تسعیر/نرخ/auto-sync (یا غیرفعال با توضیح) |
|
||
| فاکتور فروش/خرید | بدون انتخابگر ارز، بدون فیلد نرخ، بدون جمع دوگانه |
|
||
| پرداخت فاکتور | بدون ارز دوم / نرخ تبدیل |
|
||
| دریافت و پرداخت | بدون UI تبدیل |
|
||
| انتقال | بدون نرخ بینارزی |
|
||
| اشخاص | بدون کارت مانده per currency |
|
||
| اسناد/گزارشها | بدون فیلتر ارز و بدون «همه ارزها» |
|
||
| چاپ فاکتور | بدون بلوک معادل پایه |
|
||
| کالا | بدون بخش قیمت ارزی/sync نرخ |
|
||
|
||
### ۳.۲ قوانین Backend وقتی MC=OFF
|
||
|
||
- APIهای فقط-MC → `400 MULTI_CURRENCY_REQUIRED` یا no-op امن طبق قرارداد endpoint
|
||
- `currency_id` اسناد = `default_currency_id`
|
||
- تلاش برای پرداخت با ارز ≠ فاکتور → رد (مثل امروز)
|
||
- ستونهای base روی خط اگر پر شوند باید با rate=1 و ارز پایه سازگار باشند (دفاع در عمق)
|
||
|
||
### ۳.۳ چکلیست اجباری قبل از merge هر PR
|
||
|
||
- [ ] تست دستی session با کسبوکار تکارزی
|
||
- [ ] تست API تکارزی برای endpointهای جدید
|
||
- [ ] عدم نشت ویجت پشت `MultiCurrencyGate`
|
||
- [ ] عدم تغییر رفتار مسیر همارز موجود
|
||
|
||
---
|
||
|
||
## ۴. نقشه فازها (خلاصه اجرایی)
|
||
|
||
| فاز | عنوان | پیشنیاز | وضعیت تقریبی | اولویت انتشار |
|
||
|-----|--------|----------|--------------|----------------|
|
||
| P0 | Gate + قرارداد MC | — | ✅ v1 | — |
|
||
| P1 | نرخ روز دستی + toolbar | P0 | ✅ v1 | — |
|
||
| P1b | auto-sync نرخ | P1 | ✅ v1 | — |
|
||
| P2 | جمع دوگانه فاکتور + نمایش نرخ | P1 | ✅ v1 | — |
|
||
| P2.5 | ستونهای پایه روی خط + مهاجرت نرم | P2 | ✅ v1 | — |
|
||
| P3 | پرداخت/دریافت بینارزی + اختلاف نرخ | P2.5 + D1 | ✅ هسته v1 | — |
|
||
| P4 | انتقال بینارزی | P2.5 + D4 | ✅ هسته API v1 | — |
|
||
| P5 | مانده شخص per currency | P1 | ✅ v1 | — |
|
||
| P6 | قیمت کالا دوگانه + sync نرخ | P1 | ✅ v1 | — |
|
||
| P7 | یکدستسازی گزارشها/اسناد | P2.5 | ✅ جزئی v1 | تکمیل در v2 |
|
||
| P8 | تسعیر پایان دوره | P2.5 + P7 | ✅ محدود v1 | گسترش در v2 |
|
||
| P9 | تست، مشاهدهپذیری، سختسازی | همهفازها | ⬜ مستمر | v1+v2 |
|
||
|
||
ترتیب انتشار پیشنهادی:
|
||
|
||
```text
|
||
P2 → P2.5 → P5 → P3 → P7 → P4 → P6 → P8
|
||
P9 در هر PR
|
||
```
|
||
|
||
---
|
||
|
||
## ۵. فازها بهصورت دقیق
|
||
|
||
---
|
||
|
||
### فاز P2 — جمع دوگانه فاکتور (ارزی + پایه + نرخ)
|
||
|
||
**هدف:** کاربر چندارزی در ثبت/نمایش/چاپ فاکتور ارزی، جمع ارز، معادل پایه و نرخ را ببیند.
|
||
**تکارزی:** هیچ بلوک جدیدی نباشد.
|
||
|
||
#### دامنه
|
||
- فروش و خرید و برگشتیها روی مسیر invoice
|
||
- فقط وقتی `MC=ON` و `document.currency_id ≠ default_currency_id`
|
||
|
||
#### Backend
|
||
1. در پاسخ جزئیات فاکتور فیلدهای محاسبهشده پایدار:
|
||
- `totals.foreign`
|
||
- `totals.base`
|
||
- `fx` (از snapshot؛ اگر نبود از resolve — ترجیح: snapshot اجباری بماند)
|
||
2. اطمینان از پر شدن `extra_info.fx` در create/update همه زیرنوعهای invoice موردنیاز
|
||
3. تست: تکارزی → بدون الزام فیلدهای dual؛ چندارزی ارزی → fx و totals.base موجود
|
||
|
||
#### Frontend
|
||
1. بلوک جمع دوگانه در فرم فاکتور (پشت Gate)
|
||
2. چاپ و لینک عمومی همان منطق
|
||
3. عدم نمایش وقتی ارز = پایه یا MC=OFF
|
||
|
||
#### معیار پذیرش
|
||
- [ ] MC=OFF: UI فاکتور بدون تغییر ظاهری FX
|
||
- [ ] MC=ON + فاکتور USD: `1,000 USD` + معادل پایه + نرخ
|
||
- [ ] ویرایش فاکتور نرخ snapshot را حفظ/بهروز میکند طبق سیاست
|
||
|
||
#### ریسک
|
||
نمایش تبدیل بدون persist پایه همچنان به snapshot هدر وابسته است — با P2.5 کامل میشود.
|
||
|
||
#### تخمین نسبی
|
||
کوچک تا متوسط (عمدتاً UI + تقویت response)
|
||
|
||
---
|
||
|
||
### فاز P2.5 — ستونهای چندارزی روی خط سند + مهاجرت نرم
|
||
|
||
**هدف:** زیرساخت persist مبالغ پایه بدون شکستن دادهٔ قبلی و بدون تأثیر ظاهری روی تکارزی.
|
||
|
||
#### تصمیم Schema (پیشنهاد قفلشونده قبل از کد)
|
||
|
||
```text
|
||
document_lines:
|
||
exchange_rate Numeric(24,10) NULL
|
||
debit_base Numeric(24, 6 یا 2) NULL
|
||
credit_base Numeric(24, 6 یا 2) NULL
|
||
# اختیاری اگر خط بتواند ارزی غیر از سند داشته باشد (برای P3):
|
||
# currency_id INT NULL FK currencies
|
||
```
|
||
|
||
> توصیه این سند: در P2.5 ابتدا `exchange_rate` + `debit_base` + `credit_base` اضافه شود.
|
||
> `line.currency_id` فقط اگر در طراحی P3 اثبات شد که یک سند چندارزِ خطی لازم است؛ وگرنه ارز همان `document.currency_id` بماند.
|
||
|
||
#### Backend
|
||
1. Migration لایه ۱ (nullable only)
|
||
2. Helper واحد: `line_amounts_to_base(document, line) -> (debit_base, credit_base, rate, source)`
|
||
3. در create/update اسناد (حداقل invoice؛ سپس receipts/transfers/expenses):
|
||
- اگر ارز سند = پایه → میتوان NULL گذاشت یا rate=1 پر کرد (ترجیح: برای سادگی گزارش، rate=1 و base=amount)
|
||
- اگر ارزی → از `extra_info.fx.rate` پر کردن اجباری
|
||
4. Job backfill لایه ۲ (idempotent، batch، قابل توقف)
|
||
5. خواندن گزارش/مانده/year-end از helper واحد (جایگزینی تدریجی `_person_line_amount_to_base`)
|
||
|
||
#### تأثیر دادهٔ قبلی (خلاصه عملیاتی)
|
||
| کسبوکار | اقدام |
|
||
|----------|--------|
|
||
| تکارزی | هیچ؛ ستون NULL؛ رفتار عین قبل |
|
||
| چندارزی با fx روی فاکتور | backfill لایه ۲ توصیهشده |
|
||
| چندارزی بدون fx | لایه ۳ اختیاری + هشدار در گزارش |
|
||
|
||
#### Frontend
|
||
- در این فاز **الزامی به UI جدید نیست** (مگر نمایش debug/ادمین اختیاری پشت Gate)
|
||
- تکارزی هیچ تفاوتی نمیبیند
|
||
|
||
#### معیار پذیرش
|
||
- [ ] Migration روی DB بزرگ بدون downtime طولانی (ADD COLUMN)
|
||
- [ ] اسناد قدیمی بدون backfill همچنان خوانده میشوند
|
||
- [ ] فاکتور جدید ارزی خطوط با base پر میشوند
|
||
- [ ] مانده شخص قبل/بعد backfill برای نمونه طلایی اختلاف غیرمنتظره ندارد (تلورانس گرد کردن)
|
||
- [ ] MC=OFF: هیچ تغییر UX
|
||
|
||
#### ریسکها و کنترل
|
||
- اختلاف اعشار: استفاده از `currency_quant`
|
||
- سال بستهشده: backfill فقط خطوط؛ بازتولید سند بستن ممنوع مگر ابزار جدا
|
||
- عملکرد: ایندکس لازم نیست روی همه ستونهای مبلغ؛ فیلتر همچنان روی `documents.currency_id`
|
||
|
||
#### تخمین نسبی
|
||
متوسط تا بزرگ (schema + سرویس مرکزی + backfill + جایگزینی خوانندهها)
|
||
|
||
---
|
||
|
||
### فاز P3 — پرداخت و دریافت بینارزی (هسته عملیاتی)
|
||
|
||
**هدف:** فاکتور دلاری، پرداخت ریالی (یا بالعکس) با نرخ per قسط؛ ثبت حسابداری صحیح.
|
||
|
||
#### پیشنیاز تصمیم محصول (باید قبل از کد قفل شود)
|
||
|
||
| کد | سؤال | گزینهها | پیشنهاد سند |
|
||
|----|------|----------|-------------|
|
||
| D1 | اختلاف نرخ پرداخت vs نرخ فاکتور؟ | ثبت GL سود/زیان تسعیر / فقط اطلاعاتی | **ثبت GL** اگر حساب تسعیر در CoA کسبوکار فعال است؛ وگرنه informational + هشدار |
|
||
| D2 | واحد نمایش نرخ | ریال / تومان نمایشی | برچسب از ارز پایه؛ تبدیل تومان فقط UI اگر تنظیم باشد |
|
||
| D6 | حذف آخرین ارز فرعی | قفل اگر سند/حساب/کالای ارزی هست | **بله قفل (V2-P7)** |
|
||
|
||
#### مدل داده آیتم پرداخت (پیشنهاد)
|
||
|
||
```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` به ارز حساب جابهجا میشود
|
||
- اختلاف طبق D1 ثبت یا فقط نمایش داده میشود
|
||
|
||
#### Backend
|
||
1. شل کردن `_validate_invoice_payment_item_currency` فقط وقتی:
|
||
- `MC=ON`
|
||
- و فیلدهای settles/fx معتبر
|
||
2. مسیر همارز فعلی دستنخورده بماند
|
||
3. تولید خطوط سند با base پرشده (وابسته به P2.5)
|
||
4. ذخیره fx per payment item در `extra_info`
|
||
5. همان الگو برای `receipt_payment_service` (اقساط/تهاتر)
|
||
6. خطاهای راهنما به فارسی وقتی نرخ وارد نشده
|
||
|
||
#### Frontend
|
||
1. در ویجت پرداخت فاکتور: اگر حساب ≠ ارز فاکتور و MC=ON → فیلد نرخ + مبلغ تسویه ارزی
|
||
2. لیست پرداختها با نمایش دوگانه
|
||
3. MC=OFF: صفر فیلد جدید
|
||
|
||
#### معیار پذیرش
|
||
- [ ] سناریو: فاکتور 1000 USD، دو پرداخت ریالی با دو نرخ مختلف
|
||
- [ ] مانده فاکتور به USD درست کم شود
|
||
- [ ] موجودی حساب ریالی درست کم/زیاد شود
|
||
- [ ] تکارزی: رفتار پرداخت عین قبل
|
||
- [ ] تلاش API تکارزی برای cross-currency → رد امن
|
||
|
||
#### وابستگی دادهٔ قبلی
|
||
فاکتورهای قدیمی بدون fx قبل از اولین تسویه بینارزی باید fx بگیرند (resolve یا ورود دستی)، وگرنه تسویه مسدود با پیام واضح.
|
||
|
||
#### تخمین نسبی
|
||
بزرگ (بحرانیترین فاز محصول)
|
||
|
||
---
|
||
|
||
### فاز P4 — انتقال بانکی/صندوق بینارزی
|
||
|
||
**هدف:** انتقال از حساب دلاری به ریالی با نرخ دستی.
|
||
|
||
#### تصمیم D4 (قبل از کد)
|
||
ارز سند انتقال:
|
||
- پیشنهادی: سند با ارز پایه + دو مبلغ در extra، یا
|
||
- سند بدون فرض تکمبلغ و خطوط هرکدام منطبق با ارز حساب مبدأ/مقصد + base
|
||
|
||
#### Backend / Frontend
|
||
- فقط وقتی ارز مبدأ ≠ مقصد و MC=ON
|
||
- همارز: فرم فعلی بدون تغییر
|
||
|
||
#### معیار پذیرش
|
||
- [ ] موجودی دو حساب درست
|
||
- [ ] MC=OFF بدون UI نرخ
|
||
|
||
#### تخمین نسبی
|
||
متوسط
|
||
|
||
---
|
||
|
||
### فاز P5 — مانده اشخاص به تفکیک ارز
|
||
|
||
**هدف:** نمایش بدهی/بستانکاری per currency + معادل پایه.
|
||
|
||
#### Backend
|
||
- `GET .../persons/{id}/balances-by-currency`
|
||
- تجمیع بدون تبدیل per `document.currency_id`
|
||
- `base_equivalent` اختیاری با نرخ latest یا as-of
|
||
|
||
#### Frontend
|
||
- کارت خلاصه فقط MC=ON
|
||
- لیست اشخاص: برای تکارزی همان یک عدد
|
||
|
||
#### معیار پذیرش
|
||
- [ ] تکارزی بدون کارت جدید
|
||
- [ ] چندارزی: IRR و USD جدا + جمع معادل شفاف
|
||
|
||
#### نکته دادهٔ قبلی
|
||
نیاز به تغییر schema خط ندارد؛ از `document.currency_id` فعلی کار میکند. دقت معادل پایه با P2.5 بهتر میشود.
|
||
|
||
#### تخمین نسبی
|
||
کوچک تا متوسط — مناسب انتشار زود هنگام برای ارزش کاربر
|
||
|
||
---
|
||
|
||
### فاز P6 — قیمت دوگانه کالا + sync از نرخ
|
||
|
||
**هدف:** قیمت خرید/فروش پایه و ارزی؛ آپدیت اختیاری از نرخ روز.
|
||
|
||
#### تصمیم D3
|
||
- A: فیلد روی Product (UX سادهتر)
|
||
- B: فقط PriceList موجود
|
||
|
||
پیشنهاد: A برای فرم کالا + همگام اختیاری با لیستقیمت پیشفرض.
|
||
|
||
#### معیار پذیرش
|
||
- [ ] MC=OFF فقط قیمت فعلی
|
||
- [ ] MC=ON دو قیمت + دکمه بروزرسانی از نرخ
|
||
|
||
#### تخمین نسبی
|
||
متوسط
|
||
|
||
---
|
||
|
||
### فاز P7 — یکدستسازی اسناد و گزارشها
|
||
|
||
**هدف:** فیلتر ارز یکسان + حالت «همه ارزها» با تسعیر شفاف.
|
||
|
||
#### گزارشها
|
||
| گزارش | رفتار هدف |
|
||
|-------|-----------|
|
||
| تراز آزمایشی | فیلتر ارز؛ در «همه» جمع از `*_base` |
|
||
| دفتر کل / روزنامه | فیلتر + نمایش مبلغ اصلی کنار پایه در «همه» |
|
||
| ترازنامه | مانند TB از base |
|
||
| سود و زیان | یکسان با موتور base واحد |
|
||
| گردش حساب | دوگانه در MC |
|
||
|
||
#### قوانین
|
||
- موتور تبدیل = همان helper P2.5
|
||
- ممنوع جمع خام چند ارز بدون تبدیل
|
||
- MC=OFF: بدون dropdown
|
||
|
||
#### معیار پذیرش
|
||
- [ ] نمونه طلایی چند سند USD/IRR در TB «همه» با جمع پایه صحیح
|
||
- [ ] تکارزی بدون فیلتر ارز
|
||
|
||
#### تخمین نسبی
|
||
متوسط تا بزرگ
|
||
|
||
---
|
||
|
||
### فاز P8 — تسعیر پایان دوره (اختیاری / تصمیم محصول)
|
||
|
||
**هدف:** سند تعدیلی سود/زیان تسعیر تحققنیافته برای ماندههای ارزی باز.
|
||
|
||
#### پیشنیاز
|
||
- حسابهای درآمد/هزینه تسعیر در CoA (برچسبها امروز هستند)
|
||
- مانده per currency (P5) و base lines (P2.5)
|
||
- سیاست: کدام حسابها تسعیر میشوند (پولی: بانک، شخص، چک؛ غیرپولی: موجودی؟)
|
||
|
||
#### خارج از محدوده اولیه اگر محصول نخواست
|
||
میتوان P8 را به نسخه بعدی موکول کرد؛ بستن سال فعلی فعلاً به پایه collapse میکند.
|
||
|
||
#### معیار پذیرش
|
||
- [ ] تولید سند تسعیر قابلبازبینی
|
||
- [ ] MC=OFF: منو دیده نشود
|
||
- [ ] سال بستهشده بدون ابزار ادمین تغییر نکند
|
||
|
||
#### تخمین نسبی
|
||
بزرگ — فقط پس از پایدار شدن P3/P7
|
||
|
||
---
|
||
|
||
### فاز P9 — تست، مشاهدهپذیری، سختسازی (مستمر)
|
||
|
||
حداقل پوشش هر فاز:
|
||
|
||
1. API تکارزی (باید امن/مخفی)
|
||
2. API چندارزی happy path
|
||
3. رگرسیون پرداخت همارز
|
||
4. گرد کردن اعشار
|
||
5. golden test برای Gate در Flutter (در صورت امکان)
|
||
6. لاگ نرخ و currency ids در مسیرهای حساس
|
||
|
||
ابزار QA داده:
|
||
- اسکریپت مقایسه مانده شخص قبل/بعد backfill
|
||
- گزارش اسناد ارزی بدون fx
|
||
|
||
---
|
||
|
||
## ۶. ماتریس تأثیر دادهٔ قبلی به تفکیک فاز
|
||
|
||
| فاز | آیا داده قدیمی باید عوض شود؟ | اگر نشود چه میشود؟ |
|
||
|-----|------------------------------|---------------------|
|
||
| P2 | خیر | نمایش از snapshot/resolve |
|
||
| P2.5 | توصیه به backfill لایه ۲ | fallback خواندن؛ گزارش تاریخی کمی ناپایدارتر |
|
||
| P3 | فقط فاکتورهای هدف تسویه بینارزی باید fx داشته باشند | تسویه مسدود با پیام |
|
||
| P4 | خیر اجباری | — |
|
||
| P5 | خیر | کار میکند؛ معادل پایه با resolve |
|
||
| P6 | خیر | — |
|
||
| P7 | بهتر است P2.5 انجام شده باشد | حالت «همه» دقیق نیست |
|
||
| P8 | بله برای کیفیت | تسعیر پایان دوره قابل اعتماد نیست |
|
||
|
||
**جمعبندی:** فعالسازی خط چندارزی = **schema سازگار با گذشته**؛ نه بازنویسی اجباری تاریخچه تکارزی.
|
||
|
||
---
|
||
|
||
## ۷. تصمیمهای باز محصول (باید قبل از فاز مربوطه بسته شوند)
|
||
|
||
| کد | موضوع | فاز | وضعیت |
|
||
|----|--------|-----|--------|
|
||
| D1 | ثبت حسابداری سود/زیان تسعیر در پرداخت؟ | P3 | ✅ v1 — ثبت GL در 60204/70801 |
|
||
| D2 | نمایش نرخ ریال یا تومان؟ | P2/P3 | **کد شد (V2-P7)** — `rate_display_unit` |
|
||
| D3 | قیمت کالا روی Product یا PriceList؟ | P6 | ✅ v1 — گزینه A (فیلد روی Product) |
|
||
| D4 | مدل ارز سند انتقال بینارزی | P4 | ✅ v1 — سند پایه + `account_currency_amount` |
|
||
| D5 | Provider دوم (Mesghal و …) | اختیاری | **کد شد (V2-P7)** — mesghal JSON/Tala |
|
||
| D6 | قفل حذف آخرین ارز فرعی اگر سند ارزی هست | P0/P3 | **کد شد (V2-P7)** — usage گسترده + قفل حذف |
|
||
| D7 | آیا `line.currency_id` لازم است؟ | P2.5/P3 | ✅ v1 بدون آن؛ **بازنگری v2** اگر USD↔EUR لازم شود |
|
||
| D8 | تسعیر پایان دوره در نسخه جاری؟ | P8 | ✅ v1 — شipped محدود (بانک/صندوق/تنخواه) |
|
||
| D9 | backfill لایه ۳ (inferred) پیشفرض روشن؟ | P2.5 | پیشنهاد: خاموش؛ دستی ادمین (اختیاری v2) |
|
||
|
||
پس از تصمیم، در همین جدول وضعیت را بهروز کنید.
|
||
|
||
---
|
||
|
||
## ۸. برنامه انتشار پیشنهادی (Release Train)
|
||
|
||
### موج ۱ — ارزش سریع بدون ریسک داده
|
||
1. P2 نمایش دوگانه فاکتور
|
||
2. P5 مانده شخص per currency
|
||
|
||
### موج ۲ — زیرساخت حسابداری
|
||
3. P2.5 ستون پایه + helper واحد + backfill لایه ۲
|
||
4. P7 یکدستسازی گزارشها روی helper جدید
|
||
|
||
### موج ۳ — عملیاتی
|
||
5. P3 پرداخت/دریافت بینارزی (پس از D1)
|
||
6. P4 انتقال بینارزی (پس از D4)
|
||
|
||
### موج ۴ — تکمیل
|
||
7. P6 قیمت کالا
|
||
8. P8 تسعیر پایان دوره (اگر D8 تأیید شد)
|
||
9. سختسازی P9 و پاکسازی fallback نرخ ۱ در مسیرهای حیاتی
|
||
|
||
هر موج فقط وقتی merge میشود که چکلیست تکارزی سبز باشد.
|
||
|
||
---
|
||
|
||
## ۹. معیارهای پذیرش سراسری (هر موج)
|
||
|
||
### ۹.۱ کسبوکار تکارزی
|
||
- هیچ منو/فیلد/فیلتر FX جدید
|
||
- ماندهها و گزارشها عدد قبلی را حفظ میکنند (تلورانس گرد کردن صفر برای ارز بدون اعشار)
|
||
- APIهای جدید MC برای این کسبوکار امناند
|
||
|
||
### ۹.۲ کسبوکار چندارزی
|
||
- نرخ و ارز در تراکنشهای جدید snapshot میشوند
|
||
- گزارش «یک ارز» و «همه» معنای شفاف دارند
|
||
- پرداخت همارز رگرسیون ندارد
|
||
|
||
### ۹.۳ داده تاریخی
|
||
- اسناد قدیمی بدون crash خوانده میشوند
|
||
- backfill قابل تکرار و قابل گزارش است
|
||
- سال بستهشده بدون ابزار صریح تغییر نمیکند
|
||
|
||
---
|
||
|
||
## ۱۰. خارج از محدوده این برنامه اجرایی
|
||
|
||
- چند ارز پایه همزمان برای یک کسبوکار
|
||
- رمزارز / نرخ لحظهای معاملات
|
||
- تغییر قوانین سامانه مودیان برای فاکتور غیرریالی (مسیر قانونی جدا)
|
||
- اجازه تغییر ارز پایه پس از شروع عملیات بدون پروژه مهاجرت مستقل
|
||
|
||
---
|
||
|
||
## ۱۱. نحوه پیشبرد کار روی این سند
|
||
|
||
1. تصمیمهای باز موردنیاز موج بعدی را قفل کنید.
|
||
2. برای هر فاز issue/PR با عنوان `MC-Exec-P#: ...` و لینک به بخش همین فایل باز کنید.
|
||
3. قبل از شروع P2.5، این سند را با D7 (لزوم `line.currency_id`) بهروز کنید.
|
||
4. پس از اتمام هر فاز، چکباکسها و وضعیت جدول بخش ۴ را بهروز کنید و commit/PR را ثبت کنید.
|
||
5. تا وقتی چکلیست Progressive Disclosure سبز نشده، فاز بعدی منتشر نشود.
|
||
|
||
---
|
||
|
||
## ۱۲. جمعبندی یکصفحهای برای مدیریت
|
||
|
||
- امروز حسابیکس چندارزی **سندمحور + نرخ** دارد؛ چندارزی **عملیاتی** ناقص است.
|
||
- فعالسازی مبالغ پایه روی خطوط سند **داده تکارزی را نمیشکند** اگر ستونها nullable و خواندن با fallback باشد.
|
||
- داده چندارزی قدیمی بهتر است با backfill از `extra_info.fx` غنی شود؛ اجباری برای روشنکردن سیستم نیست، برای دقت تاریخی و تسویه بینارزی هست.
|
||
- ترتیب درست اجرا: نمایش دوگانه → persist پایه → مانده per ارز → پرداخت بینارزی → گزارش یکدست → انتقال → قیمت → تسعیر پایان دوره.
|
||
- در تمام فازها، کسبوکار تکارزی پشت Gate میماند و جریان کاریاش نباید تغییر کند.
|
||
|
||
---
|
||
|
||
## ۱۴. وضعیت اجرای موج ۱–۲ (بهروز ۲۰۲۶-۰۷-۲۵)
|
||
|
||
**بکاپ قبل از اجرا:** `/opt/hesabix/backups/hesabix_pre_mc_exec_20260725_155942.dump`
|
||
|
||
| فاز | وضعیت | شرح |
|
||
|-----|--------|------|
|
||
| P2 | ✅ | `fx_totals` در پاسخ فاکتور؛ بنر جمع دوگانه؛ Gate |
|
||
| P2.5 | ✅ | ستونهای پایه روی خطوط؛ backfill؛ helper واحد |
|
||
|
||
---
|
||
|
||
## ۱۵. وضعیت اجرای موج ۳ (بهروز ۲۰۲۶-۰۷-۲۵)
|
||
|
||
**بکاپ:** `/opt/hesabix/backups/hesabix_pre_mc_p5p7_*.dump`
|
||
|
||
| فاز | وضعیت | شرح |
|
||
|-----|--------|------|
|
||
| P5 | ✅ | `GET /persons/{id}/balances-by-currency` + کارت UI پشت Gate در جزئیات شخص |
|
||
| P7 | ✅ جزئی | تراز/مانده حساب وقتی فیلتر ارز نباشد از `debit_base/credit_base` جمع میزند |
|
||
| P3 | ✅ هسته | پرداخت بینارزی وقتی یکی از ارزها پایه است؛ `settles_amount` + نرخ؛ ثبت سود/زیان تسعیر 60204/70801؛ `fx_settlement` روی خط شخص |
|
||
| P4 | ✅ هسته | انتقال بینارزی با `destination_amount`/`fx_rate`؛ مبالغ بومی در `account_currency_amount`؛ موجودی بانک بومیمحور |
|
||
|
||
### محدودیتهای آگاهانه نسخه ۱
|
||
- پرداخت/انتقال فقط وقتی **یکی از دو ارز = ارز پایه** باشد
|
||
- تسعیر پایان دوره فعلاً برای حسابهای پولی بانک/صندوق/تنخواه (نه شخص/چک)
|
||
|
||
### تکمیل موج ۴ (۲۰۲۶-۰۷-۲۵)
|
||
| مورد | وضعیت |
|
||
|------|--------|
|
||
| موجودی صندوق/تنخواه با منطق بومی `account_currency_amount` | ✅ |
|
||
| UI پرداخت بینارزی (`settles_amount` + `fx_rate`) پشت Gate | ✅ |
|
||
| P6 قیمت دوگانه کالا + sync از نرخ | ✅ |
|
||
| P8 تسعیر پایان دوره (پیشنمایش + سند دستی) | ✅ |
|
||
|
||
**بکاپ موج ۴:** `/opt/hesabix/backups/hesabix_pre_mc_v1fix_*.dump`
|
||
|
||
### اصل تکارزی
|
||
بدون ارز فرعی، مسیرهای بینارزی مسدود میمانند و UI چندارزی دیده نمیشود.
|
||
|
||
---
|
||
|
||
## ۱۶. جمعبندی نسخه ۱ (مرز محصول)
|
||
|
||
نسخه ۱ چندارزی **عملیاتی پایه** را تحویل داد: Gate، نرخ، جمع دوگانه فاکتور، مبالغ پایه روی خط، مانده شخص per ارز، پرداخت/انتقال وقتی یکی از ارزها پایه است، قیمت ارزی کالا، تسعیر پایان دوره برای حسابهای نقدی، و جمع پایه در تراز وقتی فیلتر ارز نیست.
|
||
|
||
**مرز آگاهانه v1 (ورودی مستقیم v2):**
|
||
|
||
| # | محدودیت v1 |
|
||
|---|------------|
|
||
| L1 | ~~پرداخت/انتقال فقط وقتی یکی پایه~~ → v2 فرعی↔فرعی با E1/E2 |
|
||
| L2 | ~~UI انتقال بینارزی ناقص~~ → فرم انتقال destination/fx در V2-P0 |
|
||
| L3 | ~~تسعیر پایان دوره فقط بانک/صندوق/تنخواه~~ → در v2 شخص و چک باز هم پوشش داده شد |
|
||
| L4 | ~~روزنامه «همه» جمع خام~~ → V2-P1 از `*_base` |
|
||
| L5 | ~~فیلتر ارز گزارش پشت Gate~~ → V2-P0 برای TB/روزنامه/PnL/BS |
|
||
| L6 | ~~دریافت/پرداخت مستقل مسیر بینارزی~~ → V2-P4 |
|
||
| L7 | ~~چاپ/لینک عمومی جمع دوگانه~~ → PDF و صفحه عمومی در V2-P5 |
|
||
| L8 | ~~حذف آخرین ارز فرعی قفل نیست (D6)~~ → V2-P7 usage گسترده |
|
||
|
||
---
|
||
|
||
## ۱۷. برنامه اجرایی نسخه ۲ (MC-v2)
|
||
|
||
**وضعیت سند:** اجرایی — ۲۰۲۶-۰۷-۲۵
|
||
**پیشنیاز:** پایدار بودن v1 در محیط واقعی + بکاپ قبل از هر موج
|
||
**اصل طلایی:** Progressive Disclosure برای تکارزی بدون تغییر؛ هیچ فاز v2 نباید MC=OFF را شلوغ کند.
|
||
**پیشرفت موج فعلی:** V2-P0…P7 ✅ (شامل PriceList sync، D5 mesghal، D2 نمایش، D6) · V2-P8 جزئی ✅ (health/backfill admin + stamp بدون نرخ ۱) · باقی V2-P8 کاملتر
|
||
|
||
### ۱۷.۱ هدف نسخه ۲
|
||
|
||
رساندن محصول از «چندارزی عملیاتی پایه (با قید ارز پایه)» به «چندارزی عملیاتی کاملتر برای کسبوکارهای چندارز واقعی»:
|
||
|
||
1. بستن شکافهای UI/گزارش v1 که API دارد ولی کاربر نمیبیند
|
||
2. تسویه و انتقال **فرعی↔فرعی** (مثلاً USD↔EUR) با مدل نرخ شفاف
|
||
3. تسعیر پایان دوره برای ماندههای پولی کاملتر (شخص، چک)
|
||
4. یکدستسازی گزارشها و دفاع در عمق Gate
|
||
5. سختسازی داده و قفلهای ایمنی ارز
|
||
|
||
### ۱۷.۲ نقشه فازهای v2
|
||
|
||
| فاز | عنوان | اولویت | وابستگی | تخمین نسبی |
|
||
|-----|--------|--------|----------|-------------|
|
||
| V2-P0 ✅ | تکمیل UI انتقال بینارزی + Gate گزارشها | فوری | L2, L5 | کوچک |
|
||
| V2-P1 ✅ | گزارشها «همه ارزها» = پایه + نمایش دوگانه | بالا | L4, P7 | متوسط |
|
||
| V2-P2 ✅ | تسعیر پایان دوره برای شخص (+ چک) | بالا | L3, P5, P8 | متوسط–بزرگ |
|
||
| V2-P3 ✅ | پرداخت/انتقال فرعی↔فرعی (برداشتن BASE_REQUIRED) | بالا | L1, تصمیم E1/E2 | بزرگ |
|
||
| V2-P4 | دریافت/پرداخت مستقل بینارزی | متوسط | V2-P0/P3 | متوسط |
|
||
| V2-P5 ✅ | چاپ/لینک عمومی + کشفپذیری مانده ارز در لیست اشخاص | متوسط | P2, P5 | کوچک |
|
||
| V2-P6 | قیمت کالا: دکمه sync فوری + همگام PriceList | متوسط | P6 | کوچک–متوسط |
|
||
| V2-P7 | ایمنی ارز (D6) + Provider دوم (D5) + نمایش واحد (D2) | متوسط | — | کوچک–متوسط |
|
||
| V2-P8 | سختسازی P9، backfill inferred اختیاری، تست طلایی | مستمر | همه | متوسط |
|
||
|
||
ترتیب انتشار پیشنهادی:
|
||
|
||
```text
|
||
V2-P0 → V2-P1 → V2-P2 → V2-P5
|
||
↘ V2-P3 (پس از E1/E2) → V2-P4
|
||
V2-P6 / V2-P7 موازی نسبی
|
||
V2-P8 در هر PR
|
||
```
|
||
|
||
---
|
||
|
||
### ۱۷.۳ فازها بهتفصیل
|
||
|
||
#### V2-P0 — تکمیل شکاف UI انتقال + Gate گزارش (سریعترین ارزش)
|
||
|
||
**هدف:** قابلیتی که backend v1 دارد، در Flutter قابل استفاده شود؛ تکارزی فیلتر ارز نبیند.
|
||
|
||
**کارها**
|
||
- [x] فرم انتقال: حذف فیلتر اجباری همارزی وقتی `isMultiCurrency`
|
||
- [x] فیلدهای `destination_amount` و `fx_rate` وقتی ارز مبدأ ≠ مقصد
|
||
- [x] اعتبارسنجی همان قید v1 تا قبل از V2-P3 (یکی پایه باشد) با پیام واضح
|
||
- [x] گزارشهای TB / روزنامه / سودوزیان / ترازنامه: مخفیسازی dropdown ارز وقتی MC=OFF
|
||
- [x] تست رگرسیون انتقال همارز
|
||
|
||
**معیار پذیرش**
|
||
- MC=ON: انتقال USD→IRR از UI بدون API دستی
|
||
- MC=OFF: هیچ فیلتر ارز در گزارشهای هدف
|
||
|
||
---
|
||
|
||
#### V2-P1 — یکدستسازی گزارشها (تکمیل P7)
|
||
|
||
**هدف:** در حالت «همه ارزها» هیچجا جمع خام چند ارز رخ ندهد.
|
||
|
||
**کارها**
|
||
- [x] روزنامه / دفتر کل: در «همه» جمع از `debit_base`/`credit_base` (+ fallback helper)
|
||
- [ ] نمایش اختیاری مبلغ بومی کنار پایه در ردیفها (فرمت مشترک)
|
||
- [x] ترازنامه و هر گزارش باقیمانده همان موتور (`account_balance_core` وقتی `currency_id is None`)
|
||
- [ ] نمونه طلایی USD+IRR در TB و journal
|
||
|
||
**معیار پذیرش**
|
||
- گزارش «همه» برای کسبوکار نمونه با دو ارز = جمع پایه صحیح (± تلورانس گرد)
|
||
- فیلتر یک ارز = فقط مبالغ بومی همان ارز
|
||
|
||
---
|
||
|
||
#### V2-P2 — تسعیر پایان دوره: شخص و چک
|
||
|
||
**هدف:** سود/زیان تحققنیافته برای AR/AP ارزی و چکهای ارزی باز.
|
||
|
||
**پیشنیاز محصول**
|
||
- تأیید حسابهای 60204/70801 فعال در CoA کسبوکارها
|
||
- سیاست: آیا چک در محدوده پولی هست؟ (پیشنهاد: بله برای چکهای وصولنشده)
|
||
|
||
**کارها**
|
||
- [x] گسترش `period_end_fx_revaluation_service` به مانده شخص per currency (غیرپایه)
|
||
- [x] خط تعدیل بدون تغییر مانده بومی ارز (همان الگوی `fx_settlement`/`account_currency_amount=0`)
|
||
- [x] چکهای باز با `currency_id ≠ base` (در صورت تأیید)
|
||
- [x] هشدار وقتی `book_base` ناقص است (نیاز backfill) — بدون ایجاد تعدیل جعلی (adj=0 + warning)
|
||
- [x] UI پیشنمایش گروهبندیشده (نقدی / اشخاص / چک)
|
||
|
||
**معیار پذیرش**
|
||
- پیشنمایش شخص ارزی با adj غیرصفر قابل ایجاد سند
|
||
- مانده بومی ارز شخص پس از سند تغییر نکند؛ فقط ارزش پایه
|
||
|
||
**ریسک**
|
||
جهت بدهکار/بستانکار شخص و نوع فاکتور؛ نیاز به تست طلایی چند سناریو.
|
||
|
||
---
|
||
|
||
#### V2-P3 — تسویه/انتقال فرعی↔فرعی (برداشتن قید پایه)
|
||
|
||
**هدف:** پرداخت فاکتور USD از حساب EUR (و انتقال مشابه) بدون اجبار یکیبودن با ارز پایه.
|
||
|
||
**تصمیمهای قفلشونده قبل از کد**
|
||
|
||
| کد | موضوع | گزینهها | پیشنهاد |
|
||
|----|--------|----------|---------|
|
||
| E1 ✅ | مدل نرخ جفت ارز | (A) دو نرخ به پایه و ضرب متقاطع (B) نرخ مستقیم جفت در جدول جدید | **قفل A** — بدون schema نرخ جفت؛ سازگار با `business_currency_rates` |
|
||
| E2 ✅ | ارز سند حسابداری | (A) همیشه پایه (B) ارز پرداخت (C) `line.currency_id` | **قفل A** مثل P4؛ سند بینارزی همیشه پایه |
|
||
| E3 ✅ | تلورانس و سود/زیان | GL تسعیر مثل v1 | ادامه GL روی 60204/70801 |
|
||
|
||
**کارها**
|
||
- [x] بازنویسی `resolve_cross_currency_payment_plan` / transfer برای مسیر غیرپایه↔غیرپایه
|
||
- [x] محاسبه: `amount_base = foreign × rate_to_base` برای هر طرف؛ `fx_diff` روی پایه
|
||
- [x] UI: حذف قید پایه؛ پیشنهاد مقصد از نرخ متقاطع؛ نرخهای به پایه از جدول
|
||
- [x] تست: USD invoice + EUR bank؛ انتقال USD→EUR
|
||
|
||
**معیار پذیرش**
|
||
- دیگر `CROSS_CURRENCY_BASE_REQUIRED` برای MC=ON در مسیرهای پوششدادهشده
|
||
- سند متوازن به پایه؛ مانده بومی هر حساب درست
|
||
|
||
**ریسک حسابداری:** بالاترین فاز v2 — فقط پس از V2-P0 و نمونههای طلایی v1.
|
||
|
||
---
|
||
|
||
#### V2-P4 — دریافت/پرداخت مستقل بینارزی
|
||
|
||
**هدف:** اسناد receipt/payment خارج از فاکتور همان قابلیت تسویه بینارزی را داشته باشند.
|
||
|
||
**کارها**
|
||
- [x] استفاده مجدد از helper تسویه در `receipt_payment_service`
|
||
- [x] UI دیالوگ دریافت/پرداخت مشابه فاکتور
|
||
- [x] لینک به فاکتور اختیاری + `settles_amount` وقتی ارز فرق دارد
|
||
|
||
---
|
||
|
||
#### V2-P5 — چاپ، لینک عمومی، کشفپذیری
|
||
|
||
**کارها**
|
||
- [x] بلوک جمع دوگانه در قالب چاپ و صفحه عمومی فاکتور (Gate)
|
||
- [x] ستون/نشانگر ساده «چندارزی» یا ماندههای غیرپایه در لیست اشخاص (اختیاری، فشرده)
|
||
- [x] دکمه «بروزرسانی از نرخ» در فرم کالا (علاوه بر auto) — جزئیات در V2-P6
|
||
|
||
---
|
||
|
||
#### V2-P6 — تکمیل قیمت کالا
|
||
|
||
**کارها**
|
||
- [x] دکمه sync فوری در فرم کالا → API موجود
|
||
- [x] همگام اختیاری با PriceList پیشفرض هنگام sync
|
||
- [x] Gate صریح `MultiCurrencyGate` بهجای فقط `currencies.length > 1`
|
||
|
||
---
|
||
|
||
#### V2-P7 — ایمنی و محصول جانبی
|
||
|
||
**کارها**
|
||
- [x] D6: جلوگیری از حذف/غیرفعالسازی آخرین ارز فرعی وقتی سند/حساب با آن ارز وجود دارد
|
||
- [x] D5: Provider دوم (mesghal بهصورت JSON عمومی / پیشفرض سازگار با api.tala.ir)
|
||
- [x] D2: تنظیم نمایش ریال/تومان برای نرخ (`rate_display_unit` در سیاست تسعیر)
|
||
- [x] قفل تغییر `default_currency_id` پس از وجود اسناد (خارج از محدوده تغییر پایه — فقط دفاع)
|
||
|
||
---
|
||
|
||
#### V2-P8 — سختسازی و مشاهدهپذیری
|
||
|
||
**کارها**
|
||
- [x] تستهای integration برای مسیرهای V2-P0…P3 (پوشش تستهای `test_v2_*`)
|
||
- [x] گزارش ادمین: اسناد ارزی بدون `fx` / خطوط بدون `*_base` (`GET /admin/fx-providers/data-health/{business_id}`)
|
||
- [x] ابزار اختیاری backfill inferred (D9) با dry-run (`POST .../backfill-base`)
|
||
- [x] کاهش/حذف fallback نرخ ۱ در مسیر stamp خطوط (نوشتن) — خواندن گزارش هنوز با هشدار/fallback محافظهکارانه
|
||
|
||
---
|
||
|
||
### ۱۷.۴ ماتریس تأثیر داده در v2
|
||
|
||
| فاز | تغییر داده قدیمی؟ | اگر نشود |
|
||
|-----|-------------------|----------|
|
||
| V2-P0 | خیر | — |
|
||
| V2-P1 | بهتر است backfill P2.5 کامل باشد | «همه» برای خطوط بدون base ناپایدارتر |
|
||
| V2-P2 | خیر اجباری؛ کیفیت وابسته به `*_base` | هشدار needs_backfill |
|
||
| V2-P3 | خیر schema اجباری در پیشنهاد E1/E2-A | — |
|
||
| V2-P7 D6 | بله | قفل حذف با usage گسترده (اسناد، حساب، کالا) |
|
||
|
||
---
|
||
|
||
### ۱۷.۵ تصمیمهای باز مخصوص v2 (باید قبل از فاز قفل شوند)
|
||
|
||
| کد | موضوع | فاز | پیشنهاد |
|
||
|----|--------|-----|---------|
|
||
| E1 ✅ | نرخ جفت فرعی↔فرعی | V2-P3 | متقاطع از دو نرخ به پایه — **قفل شد** |
|
||
| E2 ✅ | ارز سند بینارزی | V2-P3 | همیشه پایه — **قفل شد** |
|
||
| E3 ✅ | ادامه GL تسعیر | V2-P3 | بله — **قفل شد** |
|
||
| E4 ✅ | چک در تسعیر پایان دوره؟ | V2-P2 | بله برای چک باز |
|
||
| E5 | ایجاد تسعیر وقتی book_base ناقص؟ | V2-P2 | warning + adj=0 (بدون تعدیل جعلی) |
|
||
| D2 | ریال/تومان نمایشی | V2-P7 | **کد شد** — `rate_display_unit` در سیاست تسعیر |
|
||
| D5 | Provider دوم | V2-P7 | **کد شد** — mesghal (JSON عمومی / Tala-compatible) |
|
||
| D6 | قفل حذف ارز فرعی | V2-P7 | بله |
|
||
|
||
---
|
||
|
||
### ۱۷.۶ خارج از محدوده نسخه ۲ (همچنان)
|
||
|
||
همان بخش ۱۰ این سند، بهعلاوه:
|
||
|
||
- چند ارز پایه همزمان
|
||
- تغییر ارز پایه پس از عملیات (پروژه مهاجرت جدا)
|
||
- رمزارز / نرخ لحظهای معاملاتی
|
||
- قوانین مودیان برای فاکتور غیرریالی (مسیر قانونی جدا)
|
||
- حسابداری ارزی موجودی کالا (تسعیر غیرپولی) — مگر تصمیم محصول صریح
|
||
|
||
---
|
||
|
||
### ۱۷.۷ معیار پذیرش سراسری v2
|
||
|
||
**تکارزی:** بدون تغییر نسبت به پس از v1.
|
||
**چندارزی:** مسیرهای L1–L8 یا بسته شدهاند یا با تصمیم محصول صریحاً خارج ماندهاند.
|
||
**حسابداری:** هر سند بینارزی متوازن است؛ مانده بومی حساب/شخص خراب نمیشود.
|
||
|
||
---
|
||
|
||
### ۱۷.۸ چکلیست شروع کار v2
|
||
|
||
1. بکاپ DB قبل از موج اول v2
|
||
2. قفل E1/E2 قبل از شروع V2-P3
|
||
3. قفل E4/E5 قبل از V2-P2
|
||
4. issue/PR با پیشوند `MC-v2-P#`
|
||
5. پس از هر فاز، همین بخش ۱۷ را بهروز کنید
|
||
|
||
---
|
||
|
||
## ۱۸. جمعبندی یکصفحهای برای مدیریت (v2)
|
||
|
||
- **v1** چندارزی عملیاتی را با قید «یکی از ارزها باید پایه باشد» و تسعیر نقدی تحویل داد.
|
||
- **v2** باید اول UI/گزارشهای نصفه را ببندد، بعد تسعیر اشخاص، بعد فرعی↔فرعی.
|
||
- پرریسکترین کار: **V2-P3** (USD↔EUR) — بدون قفل E1/E2 شروع نشود.
|
||
- تکارزی همچنان پشت Gate میماند.
|