fix(accounting): verify and fix Zarrin driver on real v228 install

Tested 20/20 on the owner's live Zarrin v228 (870 accounts, 656 items):

Reads fixed:
- listKarSakhte: now pages by 100 via pageNum (was returning only first 100 of 656)
- goroohSakhte: HTTP 400 with a valid body is now accepted (was treated as missing)
- parvande «مانده از قبل» row is not a balance; skip it and anchor the running
  balance on the live listMandeHesabha balance, walked back through movements
- infogardeshsakhte: hand-printed JSON repaired, timeout raised to 90s
- document delete: now searches all ledger pages, not just the first one
- listhesabha names are «first * last»; the «*» is now stripped
- keep-alive ECONNRESET: a socket hang-up on a reused connection is retried once
  on a fresh connection (server closes idle connections)

Writes fixed (test account 953 «تست اپ گلدبانک»):
- temporary flag: set from shomareSanad («موقت»), not noeFactor
- noeFactor «فاکتور موقت» also flags a document left open in Zarrin's own UI

New: test script apps/api/scripts/test-accounting-zarrin.ts (13 read + 7 write checks)
Docs: §13 added to ACCOUNTING-INTEGRATION-PLAN.md; ACC-01 in ISSUES.md updated
New: docs/FRESH-SERVER-DEPLOY-AUDIT.md (fresh-server deploy checklist)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-12 15:23:24 +03:30
co-authored by Claude Sonnet 4.6
parent 9fc0886a57
commit 8b74c85a2a
7 changed files with 574 additions and 50 deletions
+39
View File
@@ -217,3 +217,42 @@
| ثبت سند سفارش | قانون `documents.triggerStatus` در تب «قابلیت‌ها و زمان‌بندی»، کنار کلید ثبت سند: سند **موقت (پیش‌فاکتور)** وقتی سفارش به این وضعیت **یا بعد از آن** برسد ثبت می‌شود (ثبت سفارش، تأیید مدیر، تأمین، آماده‌سازی، تحویل مسئول فروش [پیش‌فرض = رفتار قبلی]، تحویل مشتری). ارسال بلافاصله پس از commit |
جزئیات قانون سند: `SUSPENDED` هیچ‌وقت ماشه نیست؛ برگشت وضعیت به زیر قانون (برداشتن وضعیت در ادمین) یا لغو یا **حذف سفارش از ادمین** سند موقت را پس می‌گیرد (حذف فقط اگر هنوز موقت است). در ته‌حساب رسیدن دوباره به قانون سند تازه می‌فرستد (ترتیب صف: اول حذف)؛ در زرین هرگز دو بار (کلید ضد تکرار ندارد). کار حذف سند دیگر به ردیف سفارش نیاز ندارد (کد حساب از درخواست خود سند خوانده می‌شود). ثبت بدهی دفتر داخلی (`postOrderDebt`) دست نخورد.
## ۱۳. زرین روی نصب واقعی (۱۴۰۵/۰۷/۲۰ — برای بررسی‌های بعدی)
نصب مالک: زرین نسخه‌ی **۲۲۸**، حدود ۸۷۰ حساب، ۶۵۸ کارساخته. آدرس و توکن نزد مالک است و در مخزن نوشته نمی‌شود.
### تست دوباره
اسکریپت [apps/api/scripts/test-accounting-zarrin.ts](../apps/api/scripts/test-accounting-zarrin.ts) خودِ درایور را روی نصب واقعی اجرا می‌کند. دیتابیس اپ را دست نمی‌زند.
```
ZARRIN_URL=http://<host>:<port>/ZarrinService/accounting ZARRIN_TOKEN=<token> ZARRIN_ACCOUNT=499 \
npx ts-node --transpile-only scripts/test-accounting-zarrin.ts # فقط خواندن (۱۳ بررسی)
# + ZARRIN_WRITE_TEST=1 ZARRIN_TEST_ACCOUNT=953 # همراه نوشتن (۲۰ بررسی)
```
- **حساب آزمایشی ۹۵۳** «تست اپ گلدبانک» (موبایل 09990001111، گروه «متفرقه») با تأیید مالک برای همین تست ساخته شد. نوشتن **فقط روی همین حساب** مجاز است. زرین API حذف حساب و حذف کارساخته ندارد، پس اسکریپت هیچ‌وقت حساب یا کارساخته نمی‌سازد.
- درخواست‌ها با فاصله فرستاده شوند: سرور یک بار وقتی پشت سر هم درخواست گرفت، اتصال را قبول نکرد.
- نتیجه‌ی آخرین اجرا (۱۴۰۵/۰۷/۲۰): ۲۰ از ۲۰.
### رفتارهایی که در داکیومنت زرین نیست
| موضوع | واقعیت | در درایور |
|---|---|---|
| علامت مانده | منفی = بدهکار | پیش‌فرض صفحه‌ی اتصال درست است |
| سند موقت | **همان لحظه روی مانده اثر دارد** (برخلاف ته‌حساب) | — ؛ قانون ثبت سند را با این در نظر بگیرید |
| موقت بودن سند API | در `shomareSanad` = «موقت»؛ سندی که در خود زرین باز مانده: `noeFactor` = «فاکتور موقت» | هر دو خوانده می‌شود |
| `listKarSakhte` | صفحه‌های ۱۰۰تایی با `pageNum` | همه‌ی صفحه‌ها خوانده می‌شود |
| `listhesabha` | اسم‌ها «نام \* نام‌خانوادگی» | «\*» حذف می‌شود |
| `parvande` | ردیف اول «مانده از قبل» مانده نیست | کنار گذاشته می‌شود؛ مانده‌ی جاری از مانده‌ی واقعی (`listMandeHesabha`) به عقب حساب می‌شود |
| `parLastTenRadif.mandeG` | مانده‌ی **فعلی** حساب روی همه‌ی ردیف‌ها، نه مانده‌ی هر ردیف | برای مقایسه‌ی ردیف‌به‌ردیف قابل استفاده نیست |
| `goroohSakhte` | جواب درست با کد HTTP 400 | پذیرفته می‌شود |
| `infogardeshsakhte` | JSON دست‌نویس معیوب، کند (۵ تا ۳۳ ثانیه)، گاهی خطای ۵۰۰ از خود زرین | تعمیر JSON، مهلت ۹۰ ثانیه |
| اتصال‌های keep-alive | سرور اتصال بیکار را می‌بندد و درخواست بعدی `socket hang up` می‌گیرد | یک بار تکرار روی اتصال تازه |
| `addAccount` با موبایل تکراری | حساب نمی‌سازد | درایور قبل از ساخت با موبایل جستجو می‌کند |
### تست‌نشده
- ساخت کارساخته از ادمین (`addFelezsakhteModel`): مالک نخواست کارساخته‌ی آزمایشی دائمی ساخته شود.
- سنگ: هزینه‌ی سنگ در `mablagh` فرستاده می‌شود؛ با سندی که سنگ دارد بررسی نشده.
### امنیت
نصب مالک روی **http بدون رمزنگاری** و روی اینترنت باز است، با توکن ساده. توصیه: توکن قوی و https یا VPN.
+61
View File
@@ -0,0 +1,61 @@
# بررسی راه‌اندازی سرور با دیتابیس خالی
> وضعیت: **بررسی فقط خواندنی** انجام شد. هیچ کدی تغییر نکرده و هیچ داده‌ای نوشته یا منتقل نشده است.
> تاریخ: ۲۰۲۶-۱۰-۱۲
## هدف
روی سرور تولید فقط **یک** کاربر super admin باشد و هیچ داده‌ی دیگری (مشتری، محصول، سفارش، بدهی، نقش اضافه، حساب تست) وارد نشود. دیتابیس henza قدیمی در این مرحله منتقل نمی‌شود.
## یافته‌ها
### ۱. حساب ثابت در کد (مهم‌ترین مورد)
- در [auth.service.ts:21](../apps/api/src/modules/auth/auth.service.ts#L21) شماره `09128081392` ثابت شده و در [خط ۴۹–۵۳](../apps/api/src/modules/auth/auth.service.ts#L49) اگر شماره همین باشد و رمز هم برابر خود شماره باشد، `ensureStaticAdmin` اجرا می‌شود.
- [خط ۲۳۰–۲۴۵](../apps/api/src/modules/auth/auth.service.ts#L230) یک `SUPER_ADMIN` با نام «مدیر موقت» می‌سازد یا فعال می‌کند.
- این شرط محیطی ندارد، پس روی سرور تولید هم فعال است.
- همین شماره در `seed.ts` به‌عنوان «کاربر تست توسعه» هم آمده است.
### ۲. داده‌ی demo در seed
- [seed.ts](../apps/api/prisma/seed.ts) بخش demo را فقط وقتی اجرا می‌کند که `NODE_ENV` برابر `production` نباشد یا `SEED_DEMO_DATA=true` باشد.
- بخش demo شامل شهرها، شعبه‌ها، کارگاه، ادمین‌های تست، مشتری، محصول، سفارش و بدهی است.
- [bootstrap.ts](../apps/api/src/prisma/bootstrap.ts) در اولین boot، اگر `RUN_SEED=false` نباشد، seed را اجرا می‌کند.
### ۳. نقش‌ها و permissionها
- `seed.ts` همیشه کاربرتایپ‌های ۸۵ تا ۸۹ و permissionهایشان را می‌سازد.
- گاردها فقط بر اساس rid کار می‌کنند و برای `SUPER_ADMIN` بای‌پس ندارند ([guards.ts:17](../apps/api/src/modules/auth/guards.ts#L17)). پس نقش ۸۵ با تمام دسترسی‌ها **لازم** است و نقش‌های ۸۶ تا ۸۹ **لازم نیستند**.
### ۴. migrationهای تغییردهنده‌ی داده
روی دیتابیس خالی بدون اثر هستند (بر اساس خواندن SQL، هنوز روی دیتابیس اجرا نشده‌اند):
| migration | کار | روی دیتابیس خالی |
|---|---|---|
| `20260922120000_customer_name_parts` | ستون و trigger نام | فقط ساختار |
| `20260922180000_sales_expert_profile_and_admin_name_parts` | `INSERT` از `AdminUser` | no-op |
| `20261005120000_accounting_integration` | permission فقط اگر نقش ۸۵ باشد | no-op |
| `20261008090000_customer_identity_cleanup` | `UPDATE` روی `Customer` و `AdminUser` | no-op |
| `20261008120000_order_sales_agent` | `UPDATE` روی `Order` | no-op |
| `20261008150000_wage_formula_permission` | permission فقط اگر نقش ۸۵ باشد | no-op |
| `20261008160000_dashboard_permission_merge` | `INSERT` و `DELETE` روی permission | no-op |
| `20261008170000_city_tree` | ساخت استان از ردیف‌های موجود | no-op |
### ۵. شماره‌های ثابت در سرویس حسابداری
`09121234567` و `09891212345` فقط مثال در docstring و تابع نرمال‌سازی هستند ([accounting-provider.ts:272](../apps/api/src/modules/accounting-sync/providers/accounting-provider.ts#L272)، [tahesab.provider.ts:224](../apps/api/src/modules/accounting-sync/providers/tahesab.provider.ts#L224)). داده نیستند.
## کارهای پیشنهادی (هنوز انجام نشده)
1. seed فقط نقش ۸۵ با `ALL_RIDS` بسازد؛ نقش‌های ۸۶ تا ۸۹ حذف شوند.
2. حساب ثابت `09128081392` و `ensureStaticAdmin` از `auth.service.ts` حذف شود.
3. روی سرور: `NODE_ENV=production` و `RUN_SEED=false`، و `SEED_DEMO_DATA` ست نشود.
4. یک اسکریپت یک‌بار اجرا برای ساخت super admin: شماره و نام و رمز را کاربر وارد می‌کند؛ داخل کد یا seed ذخیره نمی‌شود.
5. روی یک دیتابیس خالی تست شود: migrationها از صفر اجرا شوند و فقط یک کاربر و نقش ۸۵ باقی بماند.
## تصمیم‌های باز
- شماره و نام super admin واقعی.
- نقش‌های ۸۶ تا ۸۹ حذف شوند یا بمانند؟
- حذف حساب ثابت از کد تأیید شود.
## بررسی‌نشده
- سطر به سطر بررسی نشده که migrationها روی دیتابیس واقعی خالی هیچ خطایی ندهند. این را باید با یک اجرای تست روی دیتابیس خالی تأیید کرد.
- ساختار `docs/SERVER-SETUP-CHECKLIST.md` برای تنظیم سرور هنوز مرور نشده است.
+1 -1
View File
@@ -184,7 +184,7 @@ Severity: `Critical` (money loss / security / data loss) · `High` · `Medium`
| ID | Severity | Where | Description | Evidence | Suggested fix |
|---|---|---|---|---|---|
| **ACC-01** | Medium (unverified) | TGT `accounting-sync/providers/zarrin.provider.ts` | The Zarrin driver follows the published docs only — there is no Zarrin installation to test on. Open points: which sign of `listMandeHesabha` means "owes" (a setting, default negative), whether stones belong in `mablagh` or `mablaghSang` on `foroushSakhte` (sent in `mablagh`), and the exact response envelopes. | Owner: «نه، فعلاً نداریم» (no test install). Unit tests use the docs' sample outputs. | Before real use: a trial on temporary documents on a real install (owner's decision 11), check one balance and one document against the Zarrin UI. |
| **ACC-01** | Medium (unverified) | TGT `accounting-sync/providers/zarrin.provider.ts` | The Zarrin driver follows the published docs only — there is no Zarrin installation to test on. Open points: which sign of `listMandeHesabha` means "owes" (a setting, default negative), whether stones belong in `mablagh` or `mablaghSang` on `foroushSakhte` (sent in `mablagh`), and the exact response envelopes. | Owner: «نه، فعلاً نداریم» (no test install). Unit tests use the docs' sample outputs. | Before real use: a trial on temporary documents on a real install (owner's decision 11), check one balance and one document against the Zarrin UI. **Update 2026-10-12 — reads verified on the owner's install (v228, 870 accounts, 656 items):** negative balance = owes (default is right); a «فاکتور موقت» moves the balance at once (unlike Tahesab, ACC-02); fixed: `listKarSakhte` pages by 100 via `pageNum` (only 100 of 656 were read), `goroohSakhte` answers HTTP 400 with a good body (was treated as missing), the statement now skips «مانده از قبل» and is anchored on the live balance (its last row now equals Zarrin's balance; ranges agree on shared rows), temporary rows flagged, `infogardeshsakhte`'s hand-printed JSON repaired (+90 s timeout; Zarrin itself sometimes answers 500), document delete searches every ledger page, and a request on a kept-alive connection the server already closed is retried once on a fresh one ("socket hang up"). **Writes verified 2026-10-12** on owner-approved test account 953 «تست اپ گلدبانک» (09990001111, group متفرقه — Zarrin has no API to delete it, so it stays): addAccount, searchAccount finds it, edithesab keeps the group; foroushSakhte (bonak, 2 lines × 1 g, 10 %, movaghat=1) moved the balance by exactly 2.2 g = our acceptance rule, stock fell 1 g per item; hazfRadifSanad (kol=1) removed every row, balance and stock back to start. Fixed from that test: a movaghat=1 document is marked in `shomareSanad` («موقت»), not `noeFactor`; listhesabha names are «first * last» (all 873) — the «*» is now dropped. addFelezsakhteModel NOT tested (owner: no permanent test item). Security: the install is plain http on the internet with token `123456`. |
| **ACC-02** | Low (by design) | TGT D27 | Order documents are sent **temporary** (owner ruling), and a temporary Tahesab document does **not** move the balance until the accountant finalises it. Until then the balance the customer sees from the accounting software does not include those orders. | Measured on the Tahesab test server: balance delta 0 for a temporary `VKHKar`, −Vazn×Ayar/750 once final. | Accountant finalises documents promptly; the report marks temporary rows «موقت — هنوز در مانده اثر ندارد». |
| **ACC-03** | ~~**High**~~ **FIXED 2026-10-08 (owner-approved)** — `LedgerService.syncOrderDebt`: on every status change the order's ORDER_STOCK entries are brought to what legacy's recomputed `sts > 4` gives (full weight + rial while DELIVERED_TO_AGENT/CUSTOMER, else 0), posting only the difference as a confirmed DEBIT/CREDIT. Also fixes: straight-to-DELIVERED_TO_CUSTOMER owed nothing; rollback/cancel left the debt. Removing the «تحویل به نماینده» status is allowed again, as in legacy. Existing data: 0 orders out of line in dev and the imported copy. | TGT `orders/orders.service.ts` `updateStatus()` | The debt guard is `DELIVERED_TO_AGENT && !order.deliveredAt`, but `deliveredAt` is only set at `DELIVERED_TO_CUSTOMER`. An order moved into DELIVERED_TO_AGENT a second time (e.g. PREPARING → DELIVERED_TO_AGENT again via «تغییر وضعیت») posts the gold and rial debt **twice**. | Read at `orders.service.ts:135-160`. The new accounting hook is not affected (it dedupes on the existing document row). | Guard on an existing `ORDER_STOCK` ledger entry for the order instead of `deliveredAt`; needs owner approval (PROTECTED area). |
| **ACC-04** | Low (pre-existing) | TGT `src/prisma/migrate.ts` | On a completely empty database `runMigrations` fails: `0002_admin_schema.sql` sorts before `20260910113720_first` and alters tables that do not exist yet. Existing databases are unaffected. | Reproduced while preparing the D27 throwaway test database (`relation "City" does not exist`). | Rename to sort after `first`, with a `LEGACY_NAME_ALIASES` entry so applied databases do not re-run it. |