First commit of the Hedena Gold product (separate from gold-bank): branding, package names, and deploy defaults aligned to the new brand; legacy Henza trees stay local-only via .gitignore.
38 KiB
طرح اتصال به نرمافزار حسابداری (تهحساب / زرین)
وضعیت: تأیید شد (2026-10-05) — در حال اجرا. پیشرفت در بخش ۱۰ («وضعیت اجرا»). تصمیم: D27 در
docs/parity/DECISIONS.md. تاریخ: 2026-10-05
۰. زمینه
الان اپ هیچ اتصال خارجی به حسابداری ندارد: مانده، گزارش و بدهی سفارش همه از دفتر داخلی (LedgerEntry) است (D24). سیستم قدیم (henza) به تهحساب وصل بود ولی با توکن هاردکد، TLS خاموش و بدون چک مالکیت (SEC-06/16/17). حالا باید اپ به یکی از دو نرمافزار حسابداری وصل شود و در پنل ادمین با یک سوییچ انتخاب شود کدام. خروجی: همگامسازی مشتری، مانده، کارساخته/محصول، و ثبت سند سفارش — امن، idempotent و قابل پیگیری از ادمین.
۱. خلاصهی دو API (از داکیومنتها)
| تهحساب | زرین | |
|---|---|---|
| مستند | OpenAPI 3.1 کامل، ۶۶ متد (tahesab.ir/APiDoc/openapi.json) |
۸۲ API (zarrin.ir/wp-content/themes/Hirad/data/api.json) |
| نصب | Self-hosted روی سیستم/سرور مشتری؛ پورت https 8081 / http 9550 | Self-hosted، http://host:8000/ZarrinService/accounting |
| فراخوانی | POST /<Method> با بدنهی {"<Method>": [آرگومانها به ترتیب اندیس]} |
GET ?kind=<api>&token=…¶m=… |
| احراز هویت | Authorization: Bearer + هدر DBname |
token در query string |
| ضد تکرار | Factor_Code یکتای ≥۲۰ کاراکتر + DoNewSanadInquiry-By-Factor-Code + DoDeleteSanad |
فقط codeRahgiri در sanadPooli؛ foroushSakhte کلید ضد تکرار ندارد |
| سرور تست | دارد (api-test.tahesab.ir، توکن عمومی در خود داکیومنت) |
ندارد (مالک: فعلاً دسترسی نداریم) |
نگاشت قابلیتها به متدها:
| قابلیت | تهحساب | زرین |
|---|---|---|
| لیست/جستجوی مشتری | DoListMoshtari-ByCode، -ByPhone، DoListGorooh |
listhesabha / Get100Account، getinfoByPhone، listGorooheHesabha |
| ساخت/ویرایش مشتری | DoNewMoshtari (کد -1 = خودکار)، DoEditMoshtari |
addAccount (fkNoe=گروه)، edithesab |
| مانده | GetMandeHesabByCode (لیست کدها، دستهای) → MandeyeVazni، MandeyeMali |
listMandeHesabha (همهی حسابها یکجا) / mandeHesabGeramiPooliTedadi |
| ریز اسناد (گزارش) | DoListAsnad (با ماندهی ردیفبهردیف TahesabVazni/Mali) |
parvande (صفحهبندی، ردیف «مانده از قبل») |
| کارساخته: لیست/اطلاعات | DoListNameKarsakhte (فقط کارهای دارای سند) |
listKarSakhte (کند: ~۶ ثانیه)، infosakhte، goroohSakhte |
| کارساخته: موجودی | GetMojoodiKarSakhte (با نام کار) |
mojoodiKala |
| کارساخته: گردش | DoListAsnad با فیلتر نوع سند (باید روی سرور تست تأیید شود) |
infogardeshsakhte |
| کارساخته: ساخت | متد ندارد — نام کار با اولین سند ساخته میشود | addFelezsakhteModel (ویرایش ندارد) |
| سند سفارش (کارساخته، بنکداری) | DoNewSanadVKHKar (خروج کارساخته، چندردیفی با MultiRadif) |
foroushSakhte با bonak=t (ردیف اول با idHesab، بقیه با idFactor) |
| موقت/حذف سند | Sabte_Kol_Or_Movaghat=0، DoDeleteSanad |
movaghat=1، hazfRadifSanad (فقط ردیفهای API) |
۲. تصمیمهای مالک (این جلسه)
- سه محور + کارساخته: سینک مشتری، سینک مانده، ثبت سند سفارش، و همگامسازی کارساخته↔محصول. بقیهی چیزها از اپ خودمان (اسناد دریافت/آبشده/چک در اپ میمانند و ارسال نمیشوند).
- ماندهی حسابداری به مشتری نمایش داده میشود؛ سقف اعتبار از دفتر داخلی اپ میماند.
- گزارش (ریز اسناد) هم از حسابداری خوانده شود تا با مانده یکی باشد.
- مشتری جدید اپ بعد از احراز هویت (VERIFIED) در حسابداری ساخته میشود؛ مشتریهای موجود با موبایل لینک میشوند. مشتریای که فقط در حسابداری است در اپ ساخته شود — اولویت با موبایل است، نه کد ملی.
- سند سفارش در لحظهی ثبت بدهی (تحویل مسئول فروش) و بهصورت موقت ثبت میشود.
- فعلاً فقط کارساخته؛ معاملات طلا/سکه بعداً (لایه طوری ساخته میشود که با یک متد اضافه شود).
- کارساختهی حسابداری = محصول اپ. چه در ادمین ثبت شود چه در حسابداری، باید همگام شود.
- فیلدهای مشترک (عنوان، کد، موجودی…) مال حسابداری است؛ عکس، توضیحات، واریانتها، دستهبندی و درصد اجرت مال اپ (درصد حسابداری فقط پیشفرض واریانت جدید).
- بدون عکس در اپ نمایش داده نشود — خودکار، برای همهی محصولات.
- موجودی حسابداری در ادمین نمایش داده میشود و اگر صفر شد محصول در اپ ناموجود میشود (مگر پیشسفارش).
- محیط تست زرین نداریم → درایور زرین با دادههای نمونهی داکیومنت تست میشود و روشنکردنش روی نصب واقعی فقط بعد از یک دورهی آزمایشی با سند موقت.
۳. معماری
۳.۱ ماژول و درایورها
ماژول جدید apps/api/src/modules/accounting-sync/ با همان الگوی درایور پیامک (apps/api/src/modules/sms/drivers/ — یک فایل برای هر درایور + یک رجیستری):
providers/accounting-provider.ts— اینترفیس مشترک:health(),listCustomerGroups(),findCustomersByPhone(),listCustomers(cursor),createCustomer(),updateCustomer(),getBalances(codes[]),getStatement(code, from, to, page),listKarSakhte(),getKarSakhteStock(ref),getKarSakhteMovement(from, to),createKarSakhte()(اختیاری —capabilitiesاعلام میکند)،postOrderDocument(order),inquireDocument(ref),deleteDocument(ref).providers/tahesab.provider.ts،providers/zarrin.provider.ts،providers/index.ts(رجیستری).accounting-http.ts— کلاینت HTTP با timeout (AbortController، مثلdeliverViaTrez)، TLS همیشه روشن با امکان دادن گواهی CA اختصاصی برای نصبهای self-signed (نهwithoutVerifyingسیستم قدیم — SEC-17). پاکسازی پاسخ خراب تهحساب (BOM/کاراکتر کنترلی) مثلTahesabController::testقدیم.jalali.ts— تبدیل تاریخ باIntl(-u-ca-persian)، بدون وابستگی جدید؛ تهحساب سال/ماه/روز شمسی جدا میخواهد.accounting-config.service.ts,accounting-sync.service.ts(منطق همگامسازی)،accounting-outbox.worker.ts،accounting-admin.controller.ts.
۳.۲ تنظیمات (ادمین، نه env)
یک ردیف Setting با کلید accounting.config: provider: 'none' | 'tahesab' | 'zarrin'، اتصال هر درایور (آدرس، DBname، توکن، CA)، نگاشتها، کلید روشن/خاموش هر قابلیت و بازههای زمانبندی.
- راز (توکن) write-only: هیچ API آن را برنمیگرداند (فقط
••••+ «تنظیم شده»)، با کلید envACCOUNTING_SECRET_KEY(خواندهشده باenvOrطبق D25) رمز میشود. - حفرهی موجود را میبندیم:
GET/PUT/DELETE constدر admin-misc.service.ts همهی ردیفهایSettingرا به هر کسی باconfig.staticsمیدهد؛ کلیدهایaccounting.*از این مسیرها حذف/رد میشوند. - تعویض درایور: صف کارهای درایور قبلی متوقف (نه حذف) میشود؛ لینکها per-provider هستند، پس برگشت به درایور قبلی بیخطر است. ادمین هنگام تعویض هشدار میبیند.
۳.۳ تغییرات اسکیما (apps/api/prisma/schema.prisma + migration)
AccountingLink—provider,entityType (CUSTOMER|PRODUCT),entityId,externalCode,externalName,snapshot Json,syncedAt؛ یکتا روی(provider, entityType, entityId)و(provider, entityType, externalCode).AccountingOutbox—provider,kind (CUSTOMER_PUSH|ORDER_DOCUMENT|ORDER_DOCUMENT_DELETE|PRODUCT_PUSH),entityId,status (PENDING|SENT|FAILED|NEEDS_REVIEW|PAUSED),attempts,nextAttemptAt,lastError,externalRef,progress Json(برای سند چندردیفی زرین)،request/responseخلاصه بدون توکن.Customer:accountingGoldBalance,accountingRialBalance(Decimal)،accountingBalanceAt.Product:accountingStockWeight,accountingStockCount(Decimal?)،accountingStockAt.
۳.۴ صف خروجی (Outbox) و زمانبند
- رویدادها داخل همان تراکنش کار اصلی در
AccountingOutboxنوشته میشوند (مثلاً سند سفارش در همان تراکنشی کهpostOrderDebtاجرا میشود) → هیچ رویدادی گم نمیشود و قطعی حسابداری سفارش را متوقف نمیکند. - Worker درونپردازهای با
setInterval+pg_try_advisory_lock(چند نمونهی API همزمان اجرا نکنند) — بدون وابستگی جدید. retry با backoff نمایی؛ بعد از N بار →FAILEDو نمایش در ادمین. - ابهام timeout: تهحساب → قبل از تلاش دوباره
DoNewSanadInquiry-By-Factor-Code؛ زرین (بدون کلید ضد تکرار) →NEEDS_REVIEWبهجای ارسال کور دوباره. - کارهای pull دورهای (مانده، کارساخته، مشتری) همین worker با بازههای قابل تنظیم.
۴. قابلیتها
F1 — مشتری
- Pull (حسابداری → اپ): حسابهای گروههای انتخابشده در ادمین (مثلاً «مشتري» GID 13 در تهحساب، «مشتری (خریدار)» در زرین) خوانده میشوند؛ موبایل نرمال میشود (
faToEn+ تبدیل912…→0912…).- موبایل در اپ هست → لینک.
- نیست → مشتری اپ ساخته میشود:
nationalCode= کد ملی حسابداری اگر معتبر و آزاد بود، وگرنه خود موبایل (همان fallback موجود درcustomer-auth.service.ts:258وadmin-customers.service.ts)؛passwordHash: ''(ورود با OTP)؛UNVERIFIED، سقف اعتبار ۰ (بقیه مال اپ است). - حساب بدون موبایل، یا چند حساب با یک موبایل → لیست «مغایرت» در ادمین، بدون اقدام خودکار.
- Push (اپ → حسابداری): وقتی ادمین مشتری را VERIFIED میکند و لینک ندارد → اول جستجو با موبایل (لینک اگر پیدا شد)، وگرنه ساخت (
DoNewMoshtari/addAccount) در گروه انتخابشده. ویرایش نام/تلفن/آدرس مشتری لینکشده →DoEditMoshtari/edithesab. همه از طریق Outbox. - ثبتنام با موبایلی که قبلاً از حسابداری وارد شده → همان ردیف (موبایل یکتاست)، بدون حساب تکراری.
F2 — مانده
- دستهای و دورهای (پیشفرض ۱۰ دقیقه): تهحساب
GetMandeHesabByCodeبا تکههای ۱۰۰تایی کدها؛ زرینlistMandeHesabhaیکجا. ذخیره درCustomer.accountingGold/RialBalance. - تازهسازی تکی وقتی مشتری داشبورد را باز میکند و مانده قدیمیتر از X ثانیه است (timeout ۳ ثانیه، در خطا همان مقدار ذخیرهشده).
- قرارداد علامت: تهحساب منفی = بدهکار (کد قدیم
* -1میکرد)؛ به قرارداد اپ (مثبت = بدهکار) تبدیل میشود. روی سرور تست تأیید میشود. - نمایش:
balances()در customer-report.service.ts وقتی اتصال روشن است ماندهی حسابداری + زمان آخرین سینک را برمیگرداند (همان شکل{arz, mandeh}→ اپها بدون تغییر). پنل ادمین هر دو مانده را کنار هم نشان میدهد. - سقف اعتبار دست نمیخورد: چک در orders.service.ts:88-92 همچنان با
goldDebtGramsداخلی.
F3 — سند سفارش (کارساخته)
- ماشه: شاخهی
DELIVERED_TO_AGENT && !order.deliveredAtدرupdateStatus(orders.service.ts:154-157) — همان تراکنشpostOrderDebt— یک ردیفORDER_DOCUMENTدر Outbox. پیشسفارش تبدیلشده هم چون Order میشود از همین مسیر میرود. - همهی اسناد موقت ثبت میشوند تا حسابدار نهایی کند.
- تهحساب: هر قلم یک ردیف
DoNewSanadVKHKar(خروج کارساخته،IsVoroodOrKhorooj=1)،MultiRadif=1،Radif_Number۱..n،Factor_Codeقطعی =GB-ORD-<order.id>(≥۲۰ کاراکتر → ضد تکرار)،Vazn=وزن راش×تعداد،Ayar=750 (قابل تنظیم)،Count،Name_Kar=نام کار لینکشده، درصد اجرت مؤثر (اجرت − تخفیف)،PoolSang=هزینهی ریالی ثابت. - زرین:
foroushSakhteباbonak=t,pd=-1,movaghat=1؛ قلم اول باidHesab→idFactor، اقلام بعد باidFactor؛idSakhte=کارساختهی لینکشده،vazn،darsad=اجرت مؤثر،vaznSang/mablaghSang. پیشرفت هر قلم درprogressذخیره میشود تا شکست وسط کار از همانجا ادامه یابد. - پذیرش: بعد از ثبت، تغییر ماندهی وزنی حسابداری باید دقیقاً برابر
order.totalGoldWeightGramsباشد (اینکه اجرت در کدام فیلد برود —Darsad_Talayeeیا وزن محاسبهشده — با همین معیار روی سرور تست تعیین میشود). - لغو سفارش بعد از ارسال سند →
ORDER_DOCUMENT_DELETE(DoDeleteSanad/hazfRadifSanad kol=1) فقط اگر سند هنوز موقت است؛ وگرنهNEEDS_REVIEW. - محصولِ بدون لینک موقع سند: چون طبق F4 هر محصول لینک دارد، این حالت استثناست →
NEEDS_REVIEWبا پیام روشن.
F4 — کارساخته ↔ محصول
- Pull (حسابداری → اپ) دورهای (پیشفرض ۳۰ دقیقه چون
listKarSakhteکند است): کارساختهی جدید →Productجدید (عنوان = نام،productCode= کد حسابداری، دسته از نگاشت «گروه ساخته → دستهبندی» در ادمین) بدون عکس و بدون واریانت → در اپ دیده نمیشود تا ادمین عکس و واریانت (وزن/تعداد) بگذارد. درصد فروش حسابداری فقط پیشفرض واریانت جدید. - محصول لینکشده: عنوان و کد از حسابداری بهروز میشوند و در فرم ادمین قفلاند؛ عکس، توضیحات، واریانت، دسته و اجرت آزاد.
- Push (ادمین → حسابداری):
- زرین: محصول جدید →
addFelezsakhteModel(noeKarوgorooheKarاز نگاشت ادمین) → لینکidKar. چون زرین ویرایش ندارد، عنوان بعد از لینک فقط از حسابداری عوض میشود. - تهحساب: متد ساخت ندارد →
Name_Kar= عنوان محصول؛ کار با اولین سند در تهحساب پدید میآید و از آن به بعد درDoListNameKarsakhteدیده و لینک میشود. (محدودیت API، نه انتخاب ما.)
- زرین: محصول جدید →
- موجودی:
mojoodiKala/GetMojoodiKarSakhte→Product.accountingStock*؛ ستون در لیست محصولات ادمین. اگر ≤ ۰ → محصول در اپ ناموجود (واریانتهای پیشسفارش مستثنا) — هم در کاتالوگ هم درcreateOrderاعمال میشود. تعداد واریانتها همچنان در ادمین. - گردش کارساخته: صفحهی گزارش در ادمین (بازهی تاریخ) — زرین
infogardeshsakhte؛ تهحساب ازDoListAsnadاگر روی سرور تست فیلتر نوع سند جواب داد، وگرنه در این صفحه «پشتیبانی نمیشود».
F5 — قانون عکس
- catalog.service.ts (تنها مسیر لیست محصول برای مشتری) فیلتر
images: { some: {} }میگیرد؛createOrderواریانتِ محصول بیعکس را رد میکند. - قبل از انتشار، تعداد محصولات فعلی بیعکس که پنهان میشوند گزارش میشود.
F6 — گزارش از حسابداری
- وقتی اتصال روشن است،
summary/detailدرcustomer-report.service.tsاز درایور خوانده میشود و به همان شکلhenza_pwa/src/type/accounting.tsنگاشت میشود (اپها بدون تغییر):- تهحساب
DoListAsnad(Count_Last=-1برای ماندهی ردیفبهردیف) →tahesab_T=TahesabVazni،tahesab_P=TahesabMali،factor_Code،sh_factor،tarikh… - زرین
parvande→ وزن/پول دریافت و پرداخت؛ ماندهی جاری از ردیف «مانده از قبل» جمع زده میشود.
- تهحساب
- SEC-16 حفظ میشود: جزئیات سند فقط اگر کد حساب سند = کد لینکشدهی همین مشتری (از توکن).
- ستونهایی که REP-01 خالی گذاشته بود (نام کارساخته، جنس فلز…) حالا منبع دارند.
- اتصال خاموش یا خطا → همان گزارش دفتر داخلی فعلی (با برچسب منبع).
۵. پنل ادمین
- صفحهی جدید
/config/manage/accounting«اتصال حسابداری»، مجوز جدیدconfig.accounting(درapps/api/src/modules/auth/acl.constants.ts،apps/admin-panel/src/views/user/permissionConst.ts،layouts/navMenuItems.ts،router/admin.ts)، با الگوی Ant Design صفحهیviews/manage/wallet.vue:- اتصال: سوییچ «بدون اتصال / تهحساب / زرین»، فیلدهای همان درایور، دکمهی «تست اتصال» (
CheckHealth/version). - نگاشت: گروههای مشتری برای ورود، گروه مشتری جدید، گروه ساخته ↔ دستهبندی، نوع کارساختهی جدید (زرین)، عیار پیشفرض.
- قابلیتها و زمانبندی: روشن/خاموش هر قابلیت و بازهها + دکمههای «سینک الان».
- لاگ: جدول Outbox با فیلتر وضعیت، تلاش دوباره، «حلشده»، مشاهدهی درخواست/پاسخ (بدون توکن).
- مغایرتها: موبایل تکراری/خالی، کارساختهی بیدسته، سند
NEEDS_REVIEW.
- اتصال: سوییچ «بدون اتصال / تهحساب / زرین»، فیلدهای همان درایور، دکمهی «تست اتصال» (
- صفحهی مشتری: کد حسابداری + هر دو مانده + «لینک دستی با کد».
- لیست/فرم محصول: کد حسابداری، موجودی حسابداری، قفل عنوان/کد، نشان «بدون عکس — در اپ پنهان».
۶. فازها
| فاز | محتوا |
|---|---|
| P0 | مستندات (این طرح در docs/، D27، بهروزرسانی REP-01/SEC-17)، migration، تنظیمات + راز + بستن حفرهی const، اینترفیس درایور، کلاینت HTTP، Outbox/worker، تب «اتصال» + تست اتصال |
| P1 | F1 مشتری |
| P2 | F2 مانده + F6 گزارش |
| P3 | F4 کارساخته/محصول + F5 قانون عکس |
| P4 | F3 سند سفارش |
هر دو درایور در هر فاز ساخته میشوند؛ تهحساب روی سرور تست عمومی تأیید میشود، زرین با fixtureهای داکیومنت و برای نصب واقعی با دورهی آزمایشی سند موقت (تصمیم ۱۱).
۷. فایلهای اصلی
- جدید:
apps/api/src/modules/accounting-sync/**،apps/admin-panel/src/views/manage/accounting.vue، migration جدید Prisma. - تغییر:
apps/api/prisma/schema.prisma،apps/api/src/app.module.ts،modules/orders/orders.service.ts(Outbox + قفل ناموجود)،modules/admin-customers/admin-customers.service.ts(هوک VERIFIED/ویرایش، لینک دستی)،modules/customer-report/customer-report.service.ts،modules/catalog/catalog.service.ts،modules/admin-products/admin-products.service.ts(push + قفل فیلد)،modules/admin-misc/admin-misc.service.ts(حفاظتaccounting.*)،modules/auth/acl.constants.ts، فایلهای router/nav/permission پنل ادمین، صفحههای مشتری و محصول ادمین. - مستندات/استقرار:
docs/parity/DECISIONS.md،ISSUES.md،DELIVERY.md،.env.example،docker-compose.yml(ACCOUNTING_SECRET_KEY).
۸. آزمون
- واحد (Jest، کنار
sms.service.spec.ts): هر درایور باfetchماکشده و پاسخهای نمونهی خود داکیومنتها: ترتیب آرگومانهای تهحساب، هدرها، تاریخ شمسی، علامت مانده،Factor_Code، ادامهی سند چندردیفی زرین، رفتار timeout/500/JSON خراب. - Outbox: نوشتن در همان تراکنش، retry/backoff، inquiry پیش از تکرار (تهحساب)،
NEEDS_REVIEW(زرین)، توقف هنگام تعویض درایور، قفل advisory. - یکپارچه با سرور تست تهحساب (اسکریپت در
apps/api/scripts/، کنارauth-smoke.sh): ساخت مشتری → خواندن مانده → سند موقتVKHKar→ inquiry → بررسی تغییر مانده = وزن سفارش → حذف سند. - e2e محلی: سفارش آزمایشی تا «تحویل مسئول فروش» → ردیف Outbox →
SENT؛ سقف اعتبار همچنان با دفتر داخلی؛ محصول بیعکس درcatalog/productsنیست؛GET constکلیدaccounting.configرا برنمیگرداند و توکن در هیچ پاسخ/لاگی نیست. pnpm test،pnpm build، lint، و یک دور دستی در پنل ادمین (اتصال، تست، سینک الان، لاگ).
۹. مواردی که روی سرور تست بسته میشوند (سؤال از شما نیست)
- علامت و واحد ماندهی وزنی (گرم ۷۵۰؟) در هر دو.
- فیلد درست اجرت در
VKHKar(Darsad_Talayeeیا وزن محاسبهشده) با معیار پذیرش F3. - قالب تاریخ هر API زرین (بعضی میلادی، بعضی شمسیاند).
- شناسههای
fkNoe/noeKarزرین روی نصب واقعی.
۱۰. وضعیت اجرا (2026-10-05)
| فاز | وضعیت |
|---|---|
| P0 | ✅ اسکیما + migration، تنظیمات با توکن رمزشده و write-only، بستن مسیرهای const، اینترفیس درایور، کلاینت HTTP، Outbox + worker با lease، صفحهی «اتصال حسابداری» |
| P1 | ✅ مشتری: ورود/لینک با موبایل، ساخت در حسابداری پس از VERIFIED، ویرایش، لیست مغایرت |
| P2 | ✅ مانده (دورهای + تازهسازی ۳ ثانیهای در /customer/me) و گزارش از حسابداری با بازگشت به دفتر داخلی |
| P3 | ✅ کارساخته ↔ محصول، قفل عنوان/کد، ستون موجودی، قفل ناموجود، قانون عکس |
| P4 | ✅ سند موقت در «تحویل مسئول فروش»، حذف با لغو (فقط سند موقت) |
آزمون: ۴۳ تست واحد درایورها/ابزارها (کل API: ۱۸۰ تست سبز)، و apps/api/scripts/test-accounting-tahesab.ts روی سرور تست واقعی تهحساب و یک دیتابیس دورریختنی: ۲۹/۲۹ — از جمله لینک دوبارهی همان حساب با موبایل در اجرای دوم، و سندی که دقیقاً به اندازهی وزن سفارش جابهجا میکند. سند آزمایشی در پایان حذف میشود.
نتیجهی موارد بخش ۹ (اندازهگیری روی سرور تست تهحساب)
- علامت مانده: «خروج» ماندهی وزنی را کم میکند → منفی = بدهکار. در درایور برعکس میشود.
- اجرت: اثر سند روی مانده = وزن × عیار ÷ ۷۵۰؛
Darsad_TalayeeوOjratفقط ذخیره میشوند و اثری ندارند. پس اجرت بهصورت افزایش عیار فرستاده میشود (۷۵۰ با ۱۰٪ → ۸۲۵) و وزن فیزیکی دست نمیخورد. - سنگ:
PoolSangروی ماندهی ریالی اثر ندارد؛ هزینهی ثابت ریالی درMablaghKolمیرود. - سند موقت تا نهاییشدن روی مانده اثر ندارد (ACC-02).
- ضد تکرار:
Factor_Codeکوچکحرف ذخیره میشود؛ ارسال دوبارهی همان ردیف خطای duplicate میدهد → تلاش دوباره امن است. استعلام سند چندردیفیDuplicateبرمیگرداند (= پیدا شد). - زرین: هنوز روی نصب واقعی آزموده نشده (ACC-01).
یافتههای خارج از دامنه (در ISSUES)
- ACC-03 (High): ثبت دوبارهی بدهی سفارش اگر دو بار به «تحویل مسئول فروش» برود — ناحیهی محافظتشده، اصلاح نشد.
- ACC-04:
runMigrationsروی دیتابیس کاملاً خالی بهخاطر ترتیب نام0002_admin_schemaمیشکند.
۱۱. همگامسازی لحظهای (حکم مالک، ۱۴۰۵/۰۷/۱۳ — جایگزین «سینک دورهای»)
مالک: «این باید ریلتایم باشد، نه با تأخیر یا سینک دستی». هیچکدام از دو API وبهوک ندارند، پس تأخیر با سه لایه به چند ثانیه میرسد:
| مسیر | قبل | حالا |
|---|---|---|
| اپ ← حسابداری (سند سفارش، مشتری تأییدشده/ویرایششده) | منتظر tick ۳۰ ثانیهای | بعد از commit همان تراکنش (≈۰٫۷ ثانیه) صف ارسال را بیدار میکند؛ حلقهی سریع هر ۲ ثانیه هم صف را میخواند |
| حسابداری ← اپ (مانده، موجودی کارساخته) | ۱۰ و ۳۰ دقیقه | فید تغییرات هر ۵ ثانیه (ثابت در کد؛ تنظیم ادمینش به خواست مالک حذف شد)؛ فقط حسابها/کارساختههای تغییرکرده دوباره خوانده میشوند |
| ماندهای که مشتری میبیند | تازهسازی اگر قدیمیتر از ۶۰ ثانیه | اگر قدیمیتر از ۵ ثانیه، همان لحظه از حسابداری (مهلت ۳ ثانیه؛ در خطا آخرین مقدار) |
| موجودی هنگام ثبت سفارش | مقدار ذخیرهشده | مستقیم از حسابداری (مهلت ۲٫۵ ثانیه)؛ اگر پاسخ ندهد، سفارش با آخرین موجودی معلوم ثبت میشود و تغییر بعدی اصلاحش میکند (حکم مالک) |
فید تغییرات (getChanges در هر درایور؛ مکاننما در حافظه، اولین poll فقط مبدأ را میگیرد):
- تهحساب:
DoListUpdatedAsnad(200)(≈۴۰۰ms). سند «جدید» =IDبزرگتر از مکاننما یاZamanEditدیرتر؛ از هر سندMCode(حساب) و برای اسناد کارساختهNo_Kar(نام کار). روی سرور تست واقعی ثابت شد: سند ثبتشد و در poll بعدی (۰٫۷ ثانیه) حساب و کارساختهاش گزارش شد. - زرین:
parLastTenRadifبدون حساب (تازهترین ردیفهای روزنامه،idRadifصعودی) — هنوز روی نصب واقعی دیده نشده (ACC-01)؛ نام کارساختهی تغییرکرده را نمیدهد، پس موجودی در زرین فقط با بازهی کارساخته و خواندن لحظهی سفارش تازه میشود. - چیزی که فید نمیبیند (حذف سند/حساب، مشتری جدید بیسند، ویرایش مشخصات مشتری): حلقهی ۳۰ ثانیهای قبلی با بازههای دقیقهای بهعنوان شبکهی اطمینان میماند. اگر پنجرهی فید سرریز شود (همهی ردیفها جدید) یا حسابی/کاری لینکنشده دیده شود، همان لحظه کار کامل مربوط (مانده/کارساخته/مشتری، مشتری حداکثر دقیقهای یک بار) اجرا میشود.
- حلقهی سریع جدا از حلقهی کند است؛ import طولانی مشتریان آن را نگه نمیدارد.
محدودیت: تهحساب سند موقت را روی مانده اعمال نمیکند (ACC-02)؛ لحظهای بودن یعنی «تغییر ثبتشده در حسابداری چند ثانیه بعد در اپ است»، نه اینکه سند موقت مانده را عوض کند.
۱۲. دکمههای سینک، ماندهی پروفایل، قانون زمان ثبت سند (حکم مالک، ۱۴۰۵/۰۷/۱۳)
حکمها: دکمهها و ماندهی پروفایل مستقل از کلیدهای قابلیت کار میکنند (فقط اتصال برقرار باشد)؛ قانون سند در همان صفحهی «اتصال حسابداری».
| جا | رفتار |
|---|---|
| «لیست مشتریان عمده» | دکمهی «سینک با …» (فقط وقتی اتصال برقرار است): POST admin/customers-omde/accounting-sync (مجوز همان لیست) — ورود/لینک مشتریان (اگر گروه انتخاب شده باشد) + ماندهی همه؛ منتظر میماند و نتیجهی هر بخش را نشان میدهد |
| «لیست محصولات» | دکمهی مشابه: POST admin/products/accounting-sync (مجوز product.list) — کارساختهها و موجودی |
| پروفایل اپ (هر دو اپ) | هر بار باز شدن صفحه یک درخواست جدا GET customer/me/accounting-balance ماندهی همان مشتری را همان لحظه از حسابداری میخواند و در بخش «ماندهی حساب» (طلا و ریال، بدهکار/بستانکار) نشان میدهد؛ اگر حسابداری پاسخ ندهد «آخرین ماندهی ثبتشده» |
| ثبت سند سفارش | قانون documents.triggerStatus در تب «قابلیتها و زمانبندی»، کنار کلید ثبت سند: سند موقت (پیشفاکتور) وقتی سفارش به این وضعیت یا بعد از آن برسد ثبت میشود (ثبت سفارش، تأیید مدیر، تأمین، آمادهسازی، تحویل مسئول فروش [پیشفرض = رفتار قبلی]، تحویل مشتری). ارسال بلافاصله پس از commit |
جزئیات قانون سند: SUSPENDED هیچوقت ماشه نیست؛ برگشت وضعیت به زیر قانون (برداشتن وضعیت در ادمین) یا لغو یا حذف سفارش از ادمین سند موقت را پس میگیرد (حذف فقط اگر هنوز موقت است). در تهحساب رسیدن دوباره به قانون سند تازه میفرستد (ترتیب صف: اول حذف)؛ در زرین هرگز دو بار (کلید ضد تکرار ندارد). کار حذف سند دیگر به ردیف سفارش نیاز ندارد (کد حساب از درخواست خود سند خوانده میشود). ثبت بدهی دفتر داخلی (postOrderDebt) دست نخورد.
۱۳. زرین روی نصب واقعی (۱۴۰۵/۰۷/۲۰ — برای بررسیهای بعدی)
نصب مالک: زرین نسخهی ۲۲۸، حدود ۸۷۰ حساب، ۶۵۸ کارساخته. آدرس و توکن نزد مالک است و در مخزن نوشته نمیشود.
تست دوباره
اسکریپت 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.