fix(representation): pay commission on online bookings from city sites

Three things kept a city-site booking from ever reaching its representative.

The domain never resolved. City sites carry their own domain on cities.domain
while a representative's coverage is a set of cities, and representations.domain
is normally only filled for a global agent. The resolver looked at that column
alone, so bookingRepId was always null and the commission guard rejected every
booking made through a city site. It now falls back to the active representative
covering that city, and stays null when two of them cover it — an ambiguous
money assignment has to be resolved in the data, not guessed.

Commission waited for confirmation. The money has already arrived when the
gateway callback succeeds; confirming the appointment is the doctor's or
secretary's job and may happen days later or never. It is now recorded on
payment, with the appointment still pending. Recording is idempotent, so the
confirmation path stays and creates nothing twice.

The dashboard counted every appointment of the representative's doctors,
including the ones a secretary typed into the panel. It now counts only
bookings that came from the representative's own site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-19 23:14:22 +03:30
co-authored by Claude Opus 5
parent 73a9351f14
commit 15dfbe61fd
7 changed files with 292 additions and 6 deletions
+12 -1
View File
@@ -165,6 +165,8 @@ Delete a representation.
Get monthly earnings dashboard for a representation.
`total_appointments` فقط نوبت‌های **آنلاین** را می‌شمارد — یعنی نوبت‌هایی که از سایتِ همین نماینده رزرو شده‌اند (`appointments.booking_representation_id` برابر همین نماینده). نوبتی که منشی در پنل ثبت می‌کند از سایت نیامده و در آمار نماینده نمی‌آید.
**Permission:** `AUTH` — must be the representation's user or `ROLE_ADMIN`
### Path Parameters
@@ -264,11 +266,20 @@ Get yearly earnings dashboard for a representation.
## قانون کمیسیون دامنه‌محور
کمیسیون (نوبت **و** اشتراک) فقط وقتی ثبت می‌شود که **هر دو** شرط برقرار باشد:
1. دامنه‌ی مبدأ خرید (`payment.frontend_address`) متعلق به یک نماینده‌ی فعال باشد (`representations.domain`).
1. دامنه‌ی مبدأ خرید (`payment.frontend_address`) به یک نماینده‌ی فعال برسد.
2. پزشک/کلینیکِ موضوع خرید، `representation_id` همان نماینده را داشته باشد.
دامنه به نماینده به این ترتیب می‌رسد:
- نماینده‌ای که همان دامنه را در `representations.domain` ثبت کرده (نماینده‌ی سراسری).
- وگرنه اگر دامنه، دامنه‌ی یک شهر باشد (`cities.domain`)، نماینده‌ی فعالِ همان شهر از `representation_cities`.
اگر دو نماینده‌ی فعال یک شهر را پوشش دهند، نماینده‌ای انتخاب نمی‌شود: انتساب پول مبهم است و باید در داده صریح شود.
در غیر این صورت هیچ کمیسیونی برای هیچ نماینده‌ای ثبت نمی‌شود (پرداخت بدون `frontend_address` هم کمیسیون ندارد). درصد: نوبت = `commission_percent` نماینده؛ اشتراک = تنظیم سراسری `upgrade_commission_percent`. نگاشت دامنه فقط از طریق `DomainContextResolver` انجام می‌شود.
**زمان ثبت:** کمیسیون نوبت در لحظه‌ی **پرداخت موفق** ثبت می‌شود، نه در لحظه‌ی تأیید نوبت. نوبتِ `pending` هم کمیسیون دارد؛ تأیید کارِ پزشک/منشی است و ممکن است هرگز انجام نشود. ثبت idempotent است و مسیر تأیید دوباره چیزی نمی‌سازد.
---
## پنل نماینده (ROLE_REPRESENTATION)