# معماری شبکه بازاریابی و تسویه — نسخه ۰٫۲

این ماژول برای پورسانت فروش واقعی اشتراک و تمدید سالن است. پیوستن به شبکه، دعوت بازاریاب یا تعداد اعضا هیچ پاداشی ندارد. «هرمی» در این سند صرفاً شکل درخت ارتباط بازاریابان است؛ این پیاده‌سازی ارزیابی حقوقی مدل کسب‌وکار نیست.

## فرمول تفاضلی و سقف ۵۰٪

هر عضو یک سقف تجمعی دارد؛ بازه ۰ تا ۵۰ درصد با دقت ۰٫۰۱ درصد. در ذخیره‌سازی از واحد صدم درصد استفاده می‌شود تا محاسبات پولی float نباشند. درصد فرزند نمی‌تواند از والد بیشتر باشد؛ والد هم نمی‌تواند درصد خود را زیر بیشترین درصد فرزند کاهش دهد. مدیر مجاز نرخ‌های آینده را تغییر می‌دهد و سوابق پرداخت ثابت می‌مانند.

مثال زنجیره: رأس ۵۰٪، فرزند ۳۰٪، فروشنده ۲۰٪. برای پرداخت ۱۰٬۰۰۰٬۰۰۰ ریال و هزینه منتسب صفر:

| دریافت‌کننده | قاعده | پورسانت ریال |
|---|---|---:|
| فروشنده مستقیم | ۲۰٪ | ۲٬۰۰۰٬۰۰۰ |
| والد | ۳۰٪ منهای ۲۰٪ | ۱٬۰۰۰٬۰۰۰ |
| رأس | ۵۰٪ منهای ۳۰٪ | ۲٬۰۰۰٬۰۰۰ |
| سامانه | باقی‌مانده | ۵٬۰۰۰٬۰۰۰ |

۵۰٪ سقف تجمعی شاخه است، نه پورسانت اضافی رأس روی همه درصدهای پایین‌تر. رأس هنگام فروش مستقیم خودش می‌تواند ۵۰٪ بگیرد؛ هنگام فروش زیرمجموعه، فقط اختلاف نرخ را می‌گیرد. نرخ‌های برابر، سهم تفاضلی صفر می‌سازند. اگر نرخ رأس ۴۰٪ باشد، پورسانت کل حداکثر ۴۰٪ است و باقیمانده برای سامانه می‌ماند.

برای مبلغ پرداخت `G`، هزینه ثبت‌شده `C` و بالاترین نرخ زنجیره `R` بر حسب صدم درصد:

```
nominal = floor(G × R / 10000)
budget = min(nominal, floor(G / 2) - C)
platform_net = G - C - sum(commissions)
```

اگر `C > floor(G/2)` باشد، تأیید این پرداخت با شرط خالص ۵۰٪ سازگار نیست و سیستم آن را رد می‌کند. هزینه باید درست ثبت شود؛ صفر به معنای نبود هزینه در محاسبه است. با بودجه محدود، سهم‌های تفاضلی به نسبت کاهش می‌یابند. تخصیص با اختلاف مقادیر تجمعی گرد‌شده انجام می‌شود تا مجموع دقیقاً از بودجه تجاوز نکند. کسری ریال در همین ترتیب فروشنده به رأس تعیین تکلیف می‌شود؛ برای مبلغ فرد، سهم پایه سامانه رو به بالا گرد می‌شود.

مثال همان زنجیره با هزینه ۱٬۰۰۰٬۰۰۰ ریال: بودجه پورسانت ۴٬۰۰۰٬۰۰۰؛ فروشنده ۱٬۶۰۰٬۰۰۰، والد ۸۰۰٬۰۰۰، رأس ۱٬۶۰۰٬۰۰۰، خالص ثبت‌شده سامانه ۵٬۰۰۰٬۰۰۰ ریال.

**این کنترل تضمین سود خالص واقعی شرکت نیست.** هزینه ثبت‌نشده، مالیات، هزینه سربار تخصیص‌نیافته، هزینه بانکی برگشت‌ناپذیر یا وصول‌نشدن بدهی بازاریاب بعد از refund می‌تواند سود واقعی را کم کند. باید هزینه‌های کامل هر پرداخت در `cost_rial` لحاظ و دوره‌ای با حسابداری تطبیق داده شوند. سامانه فعلی دفترکل حسابداری کل شرکت ندارد.

## ساختار و قواعد شبکه

- `marketers`: کاربر، والد ثابت، کد معرفی، نرخ، وضعیت و عمق. هر کاربر فقط یک جایگاه دارد. سقف فنی عمق ۳۲ سطح است و مستقل از سقف پورسانت عمل می‌کند.
- `marketing_invites`: دعوت‌نامه یک‌بارمصرف، اعتبار هفت روز، نرخ پیشنهادی کمتر یا مساوی والد. در زمان پذیرش، وضعیت و نرخ والد دوباره بررسی می‌شوند. عضویت نیازمند ورود و پذیرش صریح شرایط است.
- `tenants.marketer_id`: معرف مستقیم و ثابت سالن. انتساب دستی فقط برای سالن بدون معرف و پیش از اولین پرداخت ممکن است. مالک نمی‌تواند معرف فروش به خودش باشد.
- لینک `/r/{code}`، اولین معرف معتبر را در session تا ۳۰ روز ثبت می‌کند؛ session ممکن است زودتر تمام شود. این روش ردیابی بین دستگاه‌ها یا کوکی دائمی ندارد. انتساب قطعی هنگام ایجاد سالن انجام می‌شود و تمدیدها از آن استفاده می‌کنند.
- والد پس از ثبت تغییر نمی‌کند؛ انتقال شاخه و حذف بازاریاب دارای سوابق فراهم نشده است. غیرفعال‌سازی، سابقه را حفظ می‌کند. سهم عضو غیرفعال یا مالک سالن برای سامانه می‌ماند و بین بالاسری‌ها بازتوزیع نمی‌شود.

## پرداخت، تمدید و برگشت

فعال‌سازی اشتراک، snapshot زنجیره و نرخ، محاسبه بودجه و ثبت دفتر پورسانت همگی در یک تراکنش انجام می‌شوند. هر `subscription_id` فقط یک `commission_sales` دارد. شناسه رسید در اشتراک یکتا است؛ تأیید دوباره همان رسید اعتبار جدید نمی‌سازد. برگشت به همان callback پس از تمدید یا refund نیز پورسانت را احیا نمی‌کند.

هر تمدید یک درخواست اشتراک مستقل دارد و بعد از تأیید پرداخت، snapshot جدید می‌گیرد. تمدید زودهنگام همان پلن، یک ماه به پایان اعتبار فعلی اضافه می‌کند. تغییر به پلن دیگر دوره جدید از اکنون می‌سازد؛ محاسبه اختلاف قیمت یا اعتبار ارتقا در این نسخه وجود ندارد.

پورسانت در `commission_entries` با مبلغ مثبت ثبت و ۱۴ روز بعد قابل تسویه می‌شود. برگشت کامل وجه، سند منفی متناظر ایجاد می‌کند؛ حذف یا بازنویسی سند قبلی ممنوع است. اگر قبلاً تسویه شده باشد، مانده می‌تواند بدهکار شود و درآمدهای بعدی با آن تهاتر می‌شوند. برگشت جزئی هنوز پشتیبانی نشده است. برگشت پول بانکی واقعی باید قبلاً توسط مسئول انجام شده باشد؛ فرم صرفاً سند و وضعیت اشتراک را ثبت می‌کند.

پرداخت‌های پیش از نصب این نسخه، خودکار پورسانت تاریخی نمی‌گیرند. برای backfill به سند پرداخت و توافق نرخ تاریخی معتبر نیاز است؛ این کار عمداً حدس زده نشده است.

## حریم دسترسی

| شخص | اطلاعات مجاز |
|---|---|
| بازاریاب | نام/وضعیت/تاریخ سالن‌های مستقیم خودش، نام و نرخ/وضعیت فرزندان مستقیم، دفتر حساب و درخواست‌های خودش |
| بازاریاب بالاسری | فقط پورسانت خودش از فروش شبکه؛ بدون نام سالن‌های فرزند یا مشتریان آن‌ها |
| همکار دارای `marketing.manage` | مدیریت شبکه و گزارش محاسبات مرکزی؛ بدون شماره شبا مگر مجوز پرداخت هم داشته باشد |
| همکار دارای `marketing.pay` | تعیین تکلیف درخواست و ثبت رسید؛ برای استفاده از صفحه مرکزی، `marketing.manage` نیز لازم است |
| مدیر کل | موارد بالا و تغییر حداقل تسویه |

هیچ عضویت بازاریابی مجوز `Membership` سالن ایجاد نمی‌کند. فهرست از کاربر احرازشده استخراج می‌شود، نه `marketer_id` ارسالی مرورگر. مسیر لغو درخواست هم بر اساس بازاریاب جاری محدود است و درخواست شخص دیگر ۴۰۴ می‌گیرد. وب‌سایت عمومی سالن همچنان عمومی است؛ این محدودیت به داده‌های خصوصی و فهرست مدیریتی مربوط است.

## درخواست تسویه

حداقل اولیه ۱٬۰۰۰٬۰۰۰ ریال است و مدیر کل می‌تواند آن را عوض کند. UI آن را به تومان نمایش می‌دهد و فیلد ورود مبلغ صریحاً ریال است. تغییر حد نصاب روی درخواست‌های قبلی اثر ندارد؛ حد نصاب هنگام ثبت درخواست snapshot می‌شود.

```
eligible = max(0, min(total_ledger_balance, matured_ledger_balance))
reserved = sum(pending_payout_requests)
available = max(0, eligible - reserved)
```

درخواست باید هم به حد نصاب برسد و هم از مانده آزاد بیشتر نباشد. مبلغ در انتظار درخواست، رزرو می‌شود. کلید UUID درخواست از ثبت دوباره ناشی از دوبار کلیک جلوگیری می‌کند؛ کلید تکراری با اطلاعات متفاوت خطاست.

شماره شبای ایرانی با قالب و checksum بررسی و با APP_KEY رمزنگاری می‌شود. این بررسی مالکیت حساب را تأیید نمی‌کند؛ مسئول تسویه باید نام صاحب حساب را با اطلاعات بانکی تطبیق دهد. شماره شبا در session خطای فرم ذخیره نمی‌شود. مدیر شبکه بدون مجوز تسویه هم آن را نمی‌بیند.

وضعیت‌ها: در انتظار → پرداخت‌شده / ردشده / لغوشده. بازاریاب فقط درخواست در انتظار خودش را لغو می‌کند. رد درخواست به دلیل نیاز دارد و مبلغ رزرو را آزاد می‌کند. پرداخت، مانده و وضعیت را دوباره در تراکنش بررسی می‌کند؛ رسید بانکی یکتا فقط یک بار مانده را بدهکار می‌کند. برگشت وجه در زمان انتظار می‌تواند تسویه را مسدود کند. مانده و وضعیت فعلی را قبل از انتقال بانکی واقعی بررسی کنید؛ این نسخه API انتقال بانکی ندارد و پرداخت بانکی/ثبت سند یک تراکنش اتمیک مشترک نیستند.

## همزمانی و یکپارچگی

عملیات شبکه، انتساب، فعال‌سازی، تسویه و برگشت ابتدا یک mutex مرکزی دیتابیس می‌گیرند. این انتخاب محافظه‌کارانه از رقابت نرخ و snapshot، برداشت تکراری و تغییر همزمان شبکه جلوگیری می‌کند؛ در مقیاس بالا باید با حفظ ترتیب قفل‌ها به قفل شاخه/حساب تقسیم شود. PostgreSQL برای production توصیه می‌شود. unique constraintهای رسید، سند اشتراک و event key لایه دوم دفاع‌اند.

SQLite و PostgreSQL دارای trigger عدم ویرایش/حذف دفتر پورسانت، عدم ویرایش snapshot و عدم تغییر والد هستند. محدودیت نرخ ۵۰٪ و بودجه خالص نیز در دیتابیس اعمال شده است. SQLite برای تست محلی استفاده شده؛ آزمون رقابت چندپردازه و اجرای این migrationها روی PostgreSQL باید پیش از انتشار production انجام شود. برای موتورهای دیتابیس دیگر این نسخه تأیید نشده است.
