Add migration to enhance session_payments table with payment_method_uuid and reference fields for split-payment details

This commit is contained in:
hamed
2026-07-23 15:37:58 +03:30
parent 1d1c3d5f30
commit a0a2eb1799
23 changed files with 2257 additions and 1432 deletions
@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { screen, fireEvent } from '@testing-library/react';
import { screen, fireEvent, waitFor } from '@testing-library/react';
import { renderWithProviders } from '../../test/utils';
vi.mock('../../lib/api', () => ({
@@ -7,8 +7,12 @@ vi.mock('../../lib/api', () => ({
ApiError: class extends Error {},
}));
import { api } from '../../lib/api';
import ConfirmAppointmentModal from './ConfirmAppointmentModal';
const get = api.get as ReturnType<typeof vi.fn>;
const post = api.post as ReturnType<typeof vi.fn>;
const appointment = {
uuid: 'a1',
version: 1,
@@ -23,7 +27,17 @@ function render() {
);
}
beforeEach(() => vi.clearAllMocks());
beforeEach(() => {
vi.clearAllMocks();
// payment-methods (pos / bank-accounts) و بقیهٔ GETها
get.mockResolvedValue({ success: true, data: [] });
post.mockResolvedValue({ success: true, data: {} });
});
/** همهٔ فیلدهای مبلغ (تومان) در ردیف‌های پرداخت. */
function amountInputs() {
return screen.getAllByPlaceholderText('0') as HTMLInputElement[];
}
describe('ConfirmAppointmentModal', () => {
it('بیمار، اقلام هزینه و جمع کل را نشان می‌دهد', () => {
@@ -34,33 +48,70 @@ describe('ConfirmAppointmentModal', () => {
expect(screen.getByText('جمع کل')).toBeInTheDocument();
});
it('مبلغ پرداختی پیش‌فرض برابر باقی‌مانده (کل هزینه) است', () => {
it('ردیفِ اول پیش‌فرض برابر کل هزینه است و وضعیت «تسویه کامل» می‌شود', () => {
render();
// ۳٬۰۰۰٬۰۰۰ ریال = ۳۰۰٬۰۰۰ تومان
expect(screen.getByPlaceholderText('0')).toHaveValue('۳۰۰٬۰۰۰');
expect(amountInputs()[0]).toHaveValue('۳۰۰٬۰۰۰');
expect(screen.getByText('تسویه کامل')).toBeInTheDocument();
});
it('با تغییر دستی مبلغ، باقی‌مانده دوباره محاسبه می‌شود', () => {
it('با تغییر دستی مبلغ به کمتر از کل، وضعیت «پرداخت جزئی» می‌شود', () => {
render();
fireEvent.change(screen.getByPlaceholderText('0'), { target: { value: '100000' } });
fireEvent.change(amountInputs()[0], { target: { value: '100000' } });
expect(screen.getByText('پرداخت جزئی')).toBeInTheDocument();
// ۳۰۰٬۰۰۰ − ۱۰۰٬۰۰۰ تومان باقی‌مانده ⇒ ۲٬۰۰۰٬۰۰۰ ریال
expect(screen.getByText(/باقی‌مانده پس از این پرداخت/)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'بدون پرداخت' }));
expect(screen.getByText('بدون پرداخت', { selector: 'span' })).toBeInTheDocument();
});
it('دکمهٔ تأیید فقط با مبلغ بیشتر از جمع کل غیرفعال می‌شود', () => {
it('دکمهٔ تأیید فقط با مجموعِ بیشتر از جمع کل غیرفعال می‌شود', () => {
render();
const submit = screen.getByRole('button', { name: 'تأیید و قطعی کردن' });
expect(submit).not.toBeDisabled();
// مبلغ بالاتر از جمع کل (۳٬۰۰۰٬۰۰۰ ریال = ۳۰۰٬۰۰۰ تومان < کل؛ پس عدد بزرگ‌تر می‌دهیم)
fireEvent.change(screen.getByPlaceholderText('0'), { target: { value: '9000000' } });
expect(screen.getByText('مبلغ پرداخت از جمع کل بیشتر است.')).toBeInTheDocument();
fireEvent.change(amountInputs()[0], { target: { value: '9000000' } });
expect(screen.getByText('مجموع پرداخت‌ها از جمع کل بیشتر است.')).toBeInTheDocument();
expect(submit).toBeDisabled();
});
it('پرداخت جزئی مجاز است و همان یک روش را ثبت می‌کند', async () => {
render();
fireEvent.change(amountInputs()[0], { target: { value: '100000' } });
fireEvent.click(screen.getByRole('button', { name: 'تأیید و قطعی کردن' }));
await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/appointment/a1/confirm', {
version: 1,
payments: [{ method: 'cash', amount_rials: 1_000_000 }],
}));
});
it('تقسیم پرداخت بین دو روش: مجموع ردیف‌ها به‌صورت آرایه ثبت می‌شود', async () => {
render();
// ردیف اول را به ۲۰۰٬۰۰۰ تومان کم می‌کنیم
fireEvent.change(amountInputs()[0], { target: { value: '200000' } });
// افزودن روش دوم — پیش‌فرض با باقی‌ماندهٔ ۱۰۰٬۰۰۰ تومان پر می‌شود
fireEvent.click(screen.getByRole('button', { name: /افزودن روش/ }));
const inputs = amountInputs();
expect(inputs).toHaveLength(2);
expect(inputs[1]).toHaveValue('۱۰۰٬۰۰۰');
// مجموع = ۳۰۰٬۰۰۰ تومان = کل ⇒ تسویه کامل
expect(screen.getByText('تسویه کامل')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'تأیید و قطعی کردن' }));
await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/appointment/a1/confirm', {
version: 1,
payments: [
{ method: 'cash', amount_rials: 2_000_000 },
{ method: 'cash', amount_rials: 1_000_000 },
],
}));
});
it('حذف ردیف اضافه‌شده مجموع را دوباره محاسبه می‌کند', () => {
render();
fireEvent.click(screen.getByRole('button', { name: /افزودن روش/ }));
expect(amountInputs()).toHaveLength(2);
fireEvent.click(screen.getAllByLabelText('حذف روش پرداخت')[0]);
expect(amountInputs()).toHaveLength(1);
});
});
@@ -1,9 +1,10 @@
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { UserCircleIcon } from '@heroicons/react/24/outline';
import { UserCircleIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
import { api } from '../../lib/api';
import type { ApiResponse } from '../../lib/api';
import type { BankAccount, Pos } from '../../hooks/usePaymentMethods';
import { formatRial, rialToToman, tomanToRial } from '../../lib/utils';
import Modal from '../ui/Modal';
import PriceInput from '../ui/PriceInput';
@@ -41,6 +42,17 @@ interface Props {
queryKey?: unknown[];
}
/** یک ردیفِ پرداخت در تسویهٔ چندروشی. */
interface PaymentRow {
id: number;
method: string;
amountToman: number;
/** uuid کارت‌خوان (pos) یا حساب بانکیِ (card) ثبت‌شده. */
methodUuid: string;
/** شناسه تراکنش / شماره پیگیری. */
reference: string;
}
const rowStyle: React.CSSProperties = {
display: 'flex',
justifyContent: 'space-between',
@@ -59,10 +71,11 @@ const STATE_TONE: Record<string, { fg: string; bg: string }> = {
};
/**
* «قطعی کردن نوبت» — هزینه‌های نوبت را نشان می‌دهد، پرداخت کامل یا جزئی می‌گیرد و
* نوبت را از «ثبت شده» به «قطعی شده» می‌برد.
* «قطعی کردن نوبت» — هزینه‌های نوبت را نشان می‌دهد، پرداخت را بین چند روش
* (نقدی/کارت‌خوان/کارت‌به‌کارت/کیف پول) تقسیم می‌کند و نوبت را «قطعی» می‌کند.
*
* سرور همین یک درخواست را اتمیک انجام می‌دهد: وضعیت + پرونده/مراجعه + پرداخت‌ها.
* مبلغ پرداختی می‌تواند کمتر از جمع کل باشد (پرداخت جزئی)، ولی نباید بیشتر شود.
*/
export default function ConfirmAppointmentModal({
open,
@@ -72,9 +85,12 @@ export default function ConfirmAppointmentModal({
queryKey,
}: Props) {
const qc = useQueryClient();
const [method, setMethod] = useState('cash');
const [amountToman, setAmountToman] = useState(0);
/** تا وقتی کاربر مبلغ را دست نزده، فیلد با باقی‌ماندهٔ نوبت پر می‌ماند. */
const nextId = useRef(1);
const makeRow = (over: Partial<PaymentRow> = {}): PaymentRow => ({
id: nextId.current++, method: 'cash', amountToman: 0, methodUuid: '', reference: '', ...over,
});
const [rows, setRows] = useState<PaymentRow[]>([makeRow()]);
/** تا وقتی کاربر مبلغ را دست نزده، ردیفِ اول با کل مبلغ پر می‌ماند. */
const [touched, setTouched] = useState(false);
// وقتی صفحه‌ی میزبان نوبت را ندارد (مثل ردیف لیست) خودمان جزئیات را می‌گیریم:
@@ -85,6 +101,30 @@ export default function ConfirmAppointmentModal({
enabled: open && !appointment,
});
// روش‌های پرداختِ ثبت‌شده — فقط وقتی مودال باز است.
const posQuery = useQuery<ApiResponse<Pos[]>>({
queryKey: ['payment-methods', 'pos'],
queryFn: () => api.get('/api/v1/my/payment-methods/pos'),
enabled: open,
});
const bankQuery = useQuery<ApiResponse<BankAccount[]>>({
queryKey: ['payment-methods', 'bank-accounts'],
queryFn: () => api.get('/api/v1/my/payment-methods/bank-accounts'),
enabled: open,
});
const posOptions = useMemo(
() => ((posQuery.data?.data ?? []) as Pos[])
.filter(p => p.is_active)
.map(p => ({ value: p.uuid, label: `${p.bank_name}${p.terminal_number}` })),
[posQuery.data],
);
const bankOptions = useMemo(
() => ((bankQuery.data?.data ?? []) as BankAccount[])
.filter(b => b.is_active)
.map(b => ({ value: b.uuid, label: `${b.bank_name}${b.card_number ? `${b.card_number}` : ''}` })),
[bankQuery.data],
);
const appt: AppointmentLike | null = appointment
?? ((detailQuery.data?.data as any)?.data ?? detailQuery.data?.data ?? null);
@@ -96,18 +136,21 @@ export default function ConfirmAppointmentModal({
);
const total = visitPrice + servicesTotal;
// جمع کل تا لحظه‌ای که کاربر مبلغ را دستی تغییر ندهد پیش‌فرضِ «پرداخت کامل» است؛
// ردیفِ اول تا لحظه‌ای که کاربر مبلغ را دستی تغییر ندهد پیش‌فرضِ «پرداخت کامل» است؛
// نوبت هنوز session ندارد، پس باقی‌مانده‌اش برابر کل هزینه است.
useEffect(() => {
if (!open || touched || total <= 0) return;
setAmountToman(rialToToman(total));
setRows(prev => prev.map((r, i) => (i === 0 ? { ...r, amountToman: rialToToman(total) } : r)));
}, [open, touched, total]);
const amountRials = tomanToRial(amountToman);
const remaining = Math.max(0, total - amountRials);
const overpaid = amountRials > total;
const paidRials = useMemo(
() => rows.reduce((sum, r) => sum + tomanToRial(r.amountToman), 0),
[rows],
);
const remaining = Math.max(0, total - paidRials);
const overpaid = paidRials > total;
const paymentState = amountRials === 0
const paymentState = paidRials === 0
? 'بدون پرداخت'
: remaining === 0
? 'تسویه کامل'
@@ -117,7 +160,14 @@ export default function ConfirmAppointmentModal({
mutationFn: () =>
api.post<ApiResponse<unknown>>(`/api/v1/appointment/${appointmentUuid}/confirm`, {
version: appt?.version,
payments: amountRials > 0 ? [{ method, amount_rials: amountRials }] : [],
payments: rows
.filter(r => tomanToRial(r.amountToman) > 0)
.map(r => ({
method: r.method,
amount_rials: tomanToRial(r.amountToman),
...(r.methodUuid ? { payment_method_uuid: r.methodUuid } : {}),
...(r.reference.trim() ? { reference: r.reference.trim() } : {}),
})),
}),
onSuccess: () => {
toast.success('نوبت قطعی شد');
@@ -131,15 +181,30 @@ export default function ConfirmAppointmentModal({
});
function reset() {
setAmountToman(0);
setMethod('cash');
nextId.current = 1;
setRows([makeRow()]);
setTouched(false);
}
/** تغییر دستی مبلغ: از این به بعد پیش‌فرضِ خودکار دیگر روی فیلد ننشیند. */
function changeAmount(next: number) {
function patchRow(id: number, patch: Partial<PaymentRow>) {
setTouched(true);
setAmountToman(next);
setRows(prev => prev.map(r => (r.id === id ? { ...r, ...patch } : r)));
}
/** روش که عوض شد، جزئیاتِ مخصوصِ روشِ قبلی بی‌معنا می‌شود. */
function changeMethod(id: number, method: string) {
patchRow(id, { method, methodUuid: '', reference: '' });
}
function addRow() {
setTouched(true);
// ردیفِ جدید پیش‌فرض با باقی‌مانده پر می‌شود تا تسویه سریع‌تر باشد.
setRows(prev => [...prev, makeRow({ amountToman: rialToToman(remaining) })]);
}
function removeRow(id: number) {
setTouched(true);
setRows(prev => (prev.length > 1 ? prev.filter(r => r.id !== id) : prev));
}
function handleClose() {
@@ -217,51 +282,122 @@ export default function ConfirmAppointmentModal({
</div>
</div>
{/* پرداخت */}
<div
style={{
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
gap: 14, marginBottom: 10,
}}
>
<div className="field-block">
<label>روش پرداخت</label>
<SearchableSelect
value={method}
onChange={(v) => setMethod(String(v ?? 'cash'))}
options={METHOD_OPTIONS}
placeholder="روش پرداخت"
height={40}
/>
</div>
<div className="field-block">
<label>مبلغ پرداختی (تومان)</label>
<div className="field" style={overpaid ? { borderColor: 'var(--danger)' } : undefined}>
<PriceInput value={amountToman} onChange={changeAmount} suffix="تومان" max={rialToToman(total)} />
</div>
<span className="field-hint">باقیمانده پس از این پرداخت: {formatRial(remaining)}</span>
</div>
</div>
<div style={{ display: 'flex', gap: 8, marginBottom: 16, flexWrap: 'wrap' }}>
<button
type="button"
className="btn soft sm"
onClick={() => changeAmount(rialToToman(total))}
>
پرداخت کامل
</button>
{amountToman > 0 && (
<button type="button" className="btn ghost sm" onClick={() => changeAmount(0)}>
بدون پرداخت
{/* پرداخت‌ها — تقسیم بین چند روش */}
<div style={{ marginBottom: 12 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<label style={{ fontSize: 13.5, fontWeight: 700, color: 'var(--text)' }}>روشهای پرداخت</label>
<button type="button" className="btn ghost sm" onClick={addRow}>
<PlusIcon style={{ width: 15, height: 15 }} />
افزودن روش
</button>
)}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{rows.map((r) => (
<div
key={r.id}
style={{
border: '1px solid var(--border)', borderRadius: 'var(--r-sm)',
padding: 12, display: 'flex', flexDirection: 'column', gap: 10,
}}
>
<div style={{ display: 'flex', gap: 10, alignItems: 'flex-end' }}>
<div className="field-block" style={{ flex: 1 }}>
<label>روش پرداخت</label>
<SearchableSelect
value={r.method}
onChange={(v) => changeMethod(r.id, String(v ?? 'cash'))}
options={METHOD_OPTIONS}
placeholder="روش پرداخت"
height={40}
/>
</div>
<div className="field-block" style={{ flex: 1 }}>
<label>مبلغ (تومان)</label>
<div className="field">
<PriceInput
value={r.amountToman}
onChange={(v) => patchRow(r.id, { amountToman: v })}
suffix="تومان"
/>
</div>
</div>
{rows.length > 1 && (
<button
type="button"
className="btn ghost sm"
aria-label="حذف روش پرداخت"
onClick={() => removeRow(r.id)}
style={{ marginBottom: 2, color: 'var(--danger)' }}
>
<TrashIcon style={{ width: 16, height: 16 }} />
</button>
)}
</div>
{/* جزئیاتِ کارت‌خوان: انتخاب دستگاهِ ثبت‌شده + شناسه تراکنش */}
{r.method === 'pos' && (
<div style={{ display: 'flex', gap: 10 }}>
<div className="field-block" style={{ flex: 1 }}>
<label>کارتخوان</label>
<SearchableSelect
value={r.methodUuid}
onChange={(v) => patchRow(r.id, { methodUuid: String(v ?? '') })}
options={posOptions}
placeholder={posOptions.length ? 'انتخاب کارت‌خوان' : 'کارت‌خوانی ثبت نشده'}
height={40}
/>
</div>
<div className="field-block" style={{ flex: 1 }}>
<label>شناسه تراکنش (اختیاری)</label>
<div className="field">
<input
type="text"
value={r.reference}
onChange={(e) => patchRow(r.id, { reference: e.target.value })}
placeholder="شماره پیگیری"
style={{ direction: 'ltr' }}
/>
</div>
</div>
</div>
)}
{/* جزئیاتِ کارت‌به‌کارت: انتخاب حساب بانکیِ ثبت‌شده + شناسه تراکنش */}
{r.method === 'card' && (
<div style={{ display: 'flex', gap: 10 }}>
<div className="field-block" style={{ flex: 1 }}>
<label>حساب بانکی (اختیاری)</label>
<SearchableSelect
value={r.methodUuid}
onChange={(v) => patchRow(r.id, { methodUuid: String(v ?? '') })}
options={bankOptions}
placeholder={bankOptions.length ? 'انتخاب حساب' : 'حسابی ثبت نشده'}
height={40}
/>
</div>
<div className="field-block" style={{ flex: 1 }}>
<label>شناسه تراکنش (اختیاری)</label>
<div className="field">
<input
type="text"
value={r.reference}
onChange={(e) => patchRow(r.id, { reference: e.target.value })}
placeholder="شماره پیگیری"
style={{ direction: 'ltr' }}
/>
</div>
</div>
</div>
)}
</div>
))}
</div>
</div>
{overpaid && (
<p className="field-err" style={{ marginBottom: 14 }}>
مبلغ پرداخت از جمع کل بیشتر است.
مجموع پرداختها از جمع کل بیشتر است.
</p>
)}
@@ -274,7 +410,7 @@ export default function ConfirmAppointmentModal({
>
<div style={rowStyle}>
<span>پرداختشده</span>
<strong style={{ color: 'var(--text)' }}>{formatRial(Math.min(amountRials, total))}</strong>
<strong style={{ color: 'var(--text)' }}>{formatRial(Math.min(paidRials, total))}</strong>
</div>
<div style={{ ...rowStyle, borderTop: '1px solid var(--border)' }}>
<span>باقیمانده</span>
+10 -3
View File
@@ -531,8 +531,8 @@ transaction**. If any step fails nothing is committed.
{
"version": 3,
"payments": [
{ "method": "cash", "amount_rials": 2000000 },
{ "method": "pos", "amount_rials": 3000000 }
{ "method": "pos", "amount_rials": 3000000, "payment_method_uuid": "…pos-uuid…", "reference": "TRX-42" },
{ "method": "cash", "amount_rials": 2000000 }
]
}
```
@@ -540,7 +540,14 @@ transaction**. If any step fails nothing is committed.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `version` | integer | ❌ | Optimistic lock version; defaults to the stored one |
| `payments` | array | ❌ | Empty/absent = confirm without payment. Each row: `method``wallet\|pos\|cash\|card` and `amount_rials` > 0. Several rows are allowed (split payment). |
| `payments` | array | ❌ | Empty/absent = confirm without payment. Several rows allowed (split payment). |
| `payments[].method` | string | ✅ | ∈ `wallet\|pos\|cash\|card` |
| `payments[].amount_rials` | integer | ✅ | > 0 |
| `payments[].payment_method_uuid` | string | ❌ | uuid of a registered POS device (`pos`) or bank account (`card`) from `/api/v1/my/payment-methods/*`. Stored as-is (max 36). |
| `payments[].reference` | string | ❌ | Transaction / tracking id (max 255). |
Each stored payment keeps its `method`, `amount_rials`, `payment_method_uuid`, `reference`,
and `paid_at` (see the `session_payments[]` in the visit response).
The sum of `payments` may not exceed the visit's payable amount → `ERR_SESSION_PAYMENT_EXCEEDS`.
Partial payment is normal: the remainder stays as `remaining_rials` on the visit and can be
+2 -1
View File
@@ -612,11 +612,12 @@ POST /api/v1/session/{uuid}/payments
"patient_debt_rials": 0,
"paid_at": 1770000000,
"payments": [
{ "uuid": "...", "method": "cash", "amount_rials": 200000, "paid_at": 1770000000, "created_by_name": "...", "created_at": 1770000000 }
{ "uuid": "...", "method": "cash", "amount_rials": 200000, "payment_method_uuid": null, "reference": null, "paid_at": 1770000000, "created_by_name": "...", "created_at": 1770000000 }
]
}
}
```
> `payment_method_uuid` (uuid کارت‌خوان/حساب بانکیِ ثبت‌شده) و `reference` (شناسه تراکنش) از مسیرِ split-paymentِ «قطعی کردن نوبت» (`POST /api/v1/appointment/{uuid}/confirm`) پر می‌شوند؛ در پرداختِ تک‌روشیِ `POST /api/v1/session/{uuid}/payments` معمولاً `null` می‌مانند.
**Errors:**
+18 -1
View File
@@ -990,6 +990,7 @@
"988": "Community 988",
"989": "Community 989",
"990": "Community 990",
"991": "Community 991",
"992": "Community 992",
"993": "Community 993",
"994": "Community 994",
@@ -1000,18 +1001,34 @@
"999": "Community 999",
"1000": "Community 1000",
"1001": "Community 1001",
"1002": "Community 1002",
"1003": "Community 1003",
"1004": "Community 1004",
"1005": "Community 1005",
"1006": "Community 1006",
"1007": "Community 1007",
"1008": "Community 1008",
"1009": "Community 1009",
"1010": "Community 1010",
"1011": "Community 1011",
"1012": "Community 1012",
"1013": "Community 1013",
"1014": "Community 1014",
"1015": "Community 1015",
"1016": "Community 1016",
"1017": "Community 1017",
"1018": "Community 1018"
"1018": "Community 1018",
"1019": "Community 1019",
"1020": "Community 1020",
"1021": "Community 1021",
"1022": "Community 1022",
"1023": "Community 1023",
"1024": "Community 1024",
"1025": "Community 1025",
"1026": "Community 1026",
"1027": "Community 1027",
"1028": "Community 1028",
"1029": "Community 1029",
"1030": "Community 1030",
"1031": "Community 1031"
}
+246 -165
View File
@@ -1,16 +1,16 @@
# Graph Report - clinicpro (2026-07-23)
## Corpus Check
- 1193 files · ~853,838 words
- 1194 files · ~855,047 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 13952 nodes · 21775 edges · 1015 communities (777 shown, 238 thin omitted)
- 13967 nodes · 21802 edges · 1032 communities (793 shown, 239 thin omitted)
- Extraction: 98% EXTRACTED · 2% INFERRED · 0% AMBIGUOUS · INFERRED: 465 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `36f13ac7`
- Built from commit: `1d1c3d5f`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@@ -988,6 +988,7 @@
- [[_COMMUNITY_Community 988|Community 988]]
- [[_COMMUNITY_Community 989|Community 989]]
- [[_COMMUNITY_Community 990|Community 990]]
- [[_COMMUNITY_Community 991|Community 991]]
- [[_COMMUNITY_Community 992|Community 992]]
- [[_COMMUNITY_Community 993|Community 993]]
- [[_COMMUNITY_Community 994|Community 994]]
@@ -998,24 +999,40 @@
- [[_COMMUNITY_Community 999|Community 999]]
- [[_COMMUNITY_Community 1000|Community 1000]]
- [[_COMMUNITY_Community 1001|Community 1001]]
- [[_COMMUNITY_Community 1002|Community 1002]]
- [[_COMMUNITY_Community 1003|Community 1003]]
- [[_COMMUNITY_Community 1004|Community 1004]]
- [[_COMMUNITY_Community 1005|Community 1005]]
- [[_COMMUNITY_Community 1006|Community 1006]]
- [[_COMMUNITY_Community 1007|Community 1007]]
- [[_COMMUNITY_Community 1008|Community 1008]]
- [[_COMMUNITY_Community 1009|Community 1009]]
- [[_COMMUNITY_Community 1010|Community 1010]]
- [[_COMMUNITY_Community 1011|Community 1011]]
- [[_COMMUNITY_Community 1012|Community 1012]]
- [[_COMMUNITY_Community 1013|Community 1013]]
- [[_COMMUNITY_Community 1014|Community 1014]]
- [[_COMMUNITY_Community 1015|Community 1015]]
- [[_COMMUNITY_Community 1016|Community 1016]]
- [[_COMMUNITY_Community 1017|Community 1017]]
- [[_COMMUNITY_Community 1018|Community 1018]]
- [[_COMMUNITY_Community 1019|Community 1019]]
- [[_COMMUNITY_Community 1020|Community 1020]]
- [[_COMMUNITY_Community 1021|Community 1021]]
- [[_COMMUNITY_Community 1022|Community 1022]]
- [[_COMMUNITY_Community 1023|Community 1023]]
- [[_COMMUNITY_Community 1024|Community 1024]]
- [[_COMMUNITY_Community 1025|Community 1025]]
- [[_COMMUNITY_Community 1026|Community 1026]]
- [[_COMMUNITY_Community 1027|Community 1027]]
- [[_COMMUNITY_Community 1028|Community 1028]]
- [[_COMMUNITY_Community 1029|Community 1029]]
- [[_COMMUNITY_Community 1030|Community 1030]]
- [[_COMMUNITY_Community 1031|Community 1031]]
## God Nodes (most connected - your core abstractions)
1. `ApiTestCase` - 228 edges
2. `api` - 136 edges
2. `api` - 137 edges
3. `BaseController` - 96 edges
4. `formatRial()` - 92 edges
5. `useAuthStore` - 91 edges
@@ -1030,25 +1047,25 @@
.claude/skills/qa-clinicpro/driver.mjs → assets/admin/components/dashboard/dashboardIcons.tsx
- `shot()` --calls--> `S` [INFERRED]
.claude/skills/redesign-page/driver.mjs → assets/admin/components/dashboard/dashboardIcons.tsx
- `PrivateRoute()` --calls--> `useAuthStore` [EXTRACTED]
assets/admin/App.tsx → assets/admin/stores/authStore.ts
- `PublicRoute()` --calls--> `useAuthStore` [EXTRACTED]
assets/admin/App.tsx → assets/admin/stores/authStore.ts
- `ReplaceAppointmentModal()` --calls--> `rialToToman()` [EXTRACTED]
assets/admin/components/AppointmentActions.tsx → assets/admin/lib/utils.ts
- `ClinicDoctorsManager()` --calls--> `formatNumber()` [EXTRACTED]
assets/admin/components/ClinicDoctorsManager.tsx → assets/admin/lib/utils.ts
- `PatientCaseBanner()` --calls--> `formatDate()` [EXTRACTED]
assets/admin/components/PatientCaseBanner.tsx → assets/admin/lib/utils.ts
## Import Cycles
- None detected.
## Communities (1015 total, 238 thin omitted)
## Communities (1032 total, 239 thin omitted)
### Community 0 - "Community 0"
Cohesion: 0.05
Nodes (23): ClinicAppointmentAccessTest, ClinicOwnerScheduleAccessTest, ClinicDoctorPermissionTest, ClinicDoctorPermissionRepository, ClinicDoctorPermission, ClinicRecordAccessTest, ClinicDoctorPermissionChecker, Clinic (+15 more)
Cohesion: 0.06
Nodes (19): ClinicAppointmentAccessTest, ClinicDoctorPermissionTest, ClinicDoctorPermissionRepository, ClinicDoctorPermission, ClinicRecordAccessTest, ClinicDoctorPermissionChecker, Clinic, Doctor (+11 more)
### Community 1 - "Community 1"
Cohesion: 0.03
Nodes (67): ServiceItemFormModal(), latinDigitsField(), numericField(), NumericFieldProps, wrap(), iranMobileSchema, CitiesTab(), CityForm (+59 more)
Cohesion: 0.04
Nodes (44): ServiceItemFormModal(), numericField(), emptyFeatures(), FEATURE_KEYS, FEATURE_LABELS, PeriodForm, periodSchema, PLAN_DISPLAY (+36 more)
### Community 2 - "Community 2"
Cohesion: 0.10
@@ -1075,8 +1092,8 @@ Cohesion: 0.05
Nodes (11): DoctorServiceController, DoctorService, Clinic, DoctorServiceRepository, Collection, Doctor, self, User (+3 more)
### Community 8 - "Community 8"
Cohesion: 0.03
Nodes (111): AppointmentLike, ConfirmAppointmentModal(), METHOD_OPTIONS, Props, rowStyle, ServiceItem, STATE_TONE, AppointmentInfoModal() (+103 more)
Cohesion: 0.02
Nodes (94): DoctorTab, PickedService, ServicePick, ServiceSlot, ServiceSlotPicker(), get, services, td (+86 more)
### Community 9 - "Community 9"
Cohesion: 0.04
@@ -1087,28 +1104,28 @@ Cohesion: 0.17
Nodes (12): Clinic Address Management, `DELETE /api/v1/clinic/{clinicUuid}/address/{addressUuid}`, Errors, Errors, `GET /api/v1/clinic/{clinicUuid}/addresses`, `PATCH /api/v1/clinic/{clinicUuid}/address/{addressUuid}`, `POST /api/v1/clinic/{clinicUuid}/address`, Removed endpoint (+4 more)
### Community 11 - "Community 11"
Cohesion: 0.07
Nodes (23): InvoicePaymentRow, InvoiceRowStatus, MY_PAYMENTS_LIMIT, PatientInvoiceRow, PatientInvoicesPayload, PaymentFilters, PaymentRow, PaymentRowStatus (+15 more)
Cohesion: 0.35
Nodes (4): ClinicOwnerScheduleAccessTest, Clinic, Doctor, DoctorAddress
### Community 12 - "Community 12"
Cohesion: 0.04
Nodes (49): Account provisioning, Clinic Doctor Invitation API, Console: `app:invitations:repair`, DELETE `/api/v1/admin/clinic/invitation/{invUuid}`, Errors, Errors, Errors, Errors (+41 more)
Cohesion: 0.05
Nodes (44): Account provisioning, Clinic Doctor Invitation API, Console: `app:invitations:repair`, DELETE `/api/v1/admin/clinic/invitation/{invUuid}`, Errors, Errors, Errors, Errors (+36 more)
### Community 13 - "Community 13"
Cohesion: 0.02
Nodes (146): appointment, render(), appt, get, openMenu(), patch, ClinicDoctorItem, get (+138 more)
Nodes (118): appointment, get, post, render(), appt, get, openMenu(), patch (+110 more)
### Community 14 - "Community 14"
Cohesion: 0.09
Nodes (20): EntityContextResolver, AppointmentSettingsController, DateOverride, EntityContext, Holiday, DateOverrideRepository, HolidayRepository, Clinic (+12 more)
### Community 15 - "Community 15"
Cohesion: 0.25
Nodes (7): PaymentController, Appointment, JsonResponse, Payment, Request, Response, User
Cohesion: 0.05
Nodes (20): PaymentController, DomainContextResolver, LogPruneService, MaintenanceService, DomainCommissionTest, MaintenanceService, SubscriptionService, MaintenanceModeTest (+12 more)
### Community 16 - "Community 16"
Cohesion: 0.04
Nodes (11): PatientSession, SmsWallet, SecretaryAppointmentScopeTest, Appointment, Collection, InventoryPackage, PatientRecord, self (+3 more)
Nodes (12): AppointmentExpiryServiceTest, PatientSession, AppLog, SecretaryAppointmentScopeTest, Appointment, Collection, InventoryPackage, PatientRecord (+4 more)
### Community 17 - "Community 17"
Cohesion: 0.05
@@ -1127,12 +1144,12 @@ Cohesion: 0.12
Nodes (5): AdminApiController, RepresentationRepository, JsonResponse, Request, StreamedResponse
### Community 21 - "Community 21"
Cohesion: 0.03
Nodes (74): PatientTagsCell(), TenantTag, TauriStatCards(), PaymentConfig, PaymentGatewayInfo, usePaymentConfig(), AddPackageModal(), formatNumber() (+66 more)
Cohesion: 0.02
Nodes (89): PatientTagsCell(), TenantTag, ServiceTariffModal(), TariffResponse, TariffRow, SessionPaymentAccordion(), SessionPaymentData, paid (+81 more)
### Community 22 - "Community 22"
Cohesion: 0.29
Nodes (4): RatingController, JsonResponse, Request, User
Cohesion: 0.15
Nodes (9): RatingController, Like, LikeRepository, JsonResponse, Request, User, Comment, ManagerRegistry (+1 more)
### Community 23 - "Community 23"
Cohesion: 0.05
@@ -1239,8 +1256,8 @@ Cohesion: 0.11
Nodes (4): Payment, Appointment, self, User
### Community 49 - "Community 49"
Cohesion: 0.07
Nodes (32): ClinicDoctorsManager(), ClinicInvitation, HUES_LIST, INV_STATUS_MAP, buildInsurancePayload(), Contract, contractToForm(), EMPTY_FORM (+24 more)
Cohesion: 0.05
Nodes (42): AppointmentLike, ConfirmAppointmentModal(), METHOD_OPTIONS, PaymentRow, Props, rowStyle, ServiceItem, STATE_TONE (+34 more)
### Community 50 - "Community 50"
Cohesion: 0.07
@@ -1255,8 +1272,8 @@ Cohesion: 0.07
Nodes (26): الزامات UI, باگ‌فیکس صفحه نوبت‌ها, باگ ۱ — کرش تقویم, باگ ۲ — روز هفته در DateNavigator, باگ ۳ — پیام «slot نیست», باگ ۴ — نوبت جدید: نام اجباری + find-or-create patient, باگ ۵ — patient_mobile نشان می‌دهد موبایل پزشک, باگ ۶ — نوبت‌های رزرو شده در نمایش زمانبندی (+18 more)
### Community 53 - "Community 53"
Cohesion: 0.06
Nodes (20): AppointmentEvent, AppLogRepository, AppointmentEventRepository, ClaimItemRepository, DoctorClaimRequestRepository, DoctorInsuranceRepository, InventoryPackageRepository, PreRegistrationRepository (+12 more)
Cohesion: 0.07
Nodes (19): AppLogRepository, ClaimItemRepository, DoctorClaimRequestRepository, PreRegistrationRepository, SessionConsumableRepository, SubscriptionPeriodRepository, TaxRateHistoryRepository, ServiceEntityRepository (+11 more)
### Community 54 - "Community 54"
Cohesion: 0.10
@@ -1279,8 +1296,8 @@ Cohesion: 0.29
Nodes (5): BlogController, City, JsonResponse, Request, User
### Community 59 - "Community 59"
Cohesion: 0.07
Nodes (14): ClinicController, SpecialtyController, DoctorSpecialtyParentsTest, Specialty, SpecialtyRepository, Clinic, DoctorAddress, JsonResponse (+6 more)
Cohesion: 0.05
Nodes (18): ClinicController, SpecialtyController, DoctorSpecialtyParentsTest, Specialty, BackfillSpecialtyParentsStep, SpecialtyRepository, Clinic, DoctorAddress (+10 more)
### Community 60 - "Community 60"
Cohesion: 0.08
@@ -1291,12 +1308,12 @@ Cohesion: 0.17
Nodes (12): Add Session Payment (تسویه چندتکه), Create Patient Record, Create Session, Edit Session Payment, Endpoints, Get Patient Record, List Patient Appointments, List Patient Sessions (+4 more)
### Community 62 - "Community 62"
Cohesion: 0.11
Nodes (21): WalletTransactionModal(), BANK_KEY, BankAccount, BankAccountInput, Pos, POS_KEY, PosInput, useBankAccounts() (+13 more)
Cohesion: 0.09
Nodes (27): MethodOption, Props, QUICK_TOMANS, WalletModalSubmit, WalletMode, WalletTransactionModal(), BANK_KEY, BankAccount (+19 more)
### Community 63 - "Community 63"
Cohesion: 0.02
Nodes (164): ALLOWED_ROLES, PrivateRoute(), PublicRoute(), queryClient, toastStyle, PaginatedResponse, formatDate(), formatDateTime() (+156 more)
Nodes (136): PaginatedResponse, formatDateTime(), ALL_STATUSES, AppointmentDetailPage(), isoDay(), timeOf(), FormData, schema (+128 more)
### Community 64 - "Community 64"
Cohesion: 0.19
@@ -1355,8 +1372,8 @@ Cohesion: 0.12
Nodes (3): SubscriptionPlan, Collection, self
### Community 81 - "Community 81"
Cohesion: 0.07
Nodes (27): dependencies, @ckeditor/ckeditor5-build-classic, @ckeditor/ckeditor5-react, @fontsource/vazirmatn, @heroicons/react, @hookform/resolvers, jalaali-js, leaflet (+19 more)
Cohesion: 0.09
Nodes (23): dependencies, @ckeditor/ckeditor5-build-classic, @ckeditor/ckeditor5-react, @fontsource/vazirmatn, @heroicons/react, @hookform/resolvers, jalaali-js, leaflet (+15 more)
### Community 83 - "Community 83"
Cohesion: 0.09
@@ -1372,7 +1389,7 @@ Nodes (30): devDependencies, @babel/core, @babel/preset-env, @babel/preset-react
### Community 86 - "Community 86"
Cohesion: 0.03
Nodes (27): AppointmentExpiryServiceTest, BookingServicesPublicTest, LowTierFixesTest, SendCodeMobileRateLimitTest, ClaimsListNPlusOneTest, ClaimsListPaginationTest, CaptchaFlowTest, ServiceItemDeleteCleanupTest (+19 more)
Nodes (25): BookingModeImmutableTest, BookingServicesPublicTest, LowTierFixesTest, SendCodeMobileRateLimitTest, ClaimsListNPlusOneTest, ClaimsListPaginationTest, CaptchaFlowTest, ServiceItemDeleteCleanupTest (+17 more)
### Community 87 - "Community 87"
Cohesion: 0.10
@@ -1455,12 +1472,12 @@ Cohesion: 0.11
Nodes (18): Endpoint ها, gate check در تسک‌های بعدی, GET /api/v1/subscription/my, GET /api/v1/subscription/plans, POST /api/v1/admin/subscription/period, POST /api/v1/subscription-payment (موجود), POST /api/v1/subscription/trial, POST /api/v1/subscription/trial (خطا — قبلاً استفاده شده) (+10 more)
### Community 107 - "Community 107"
Cohesion: 0.24
Nodes (8): AppointmentController, Appointment, Clinic, Doctor, JsonResponse, Request, User, WeeklySchedule
Cohesion: 0.12
Nodes (16): AppointmentConfirmationService, BackfillAppointmentSessionsCommand, AppointmentController, AutoCreateSessionOnConfirmTest, InputInterface, OutputInterface, Appointment, Clinic (+8 more)
### Community 108 - "Community 108"
Cohesion: 0.09
Nodes (16): CaptchaController, BaseController, CategoryController, CategoryImportController, DoctorImportController, SiteContextController, JsonResponse, JsonResponse (+8 more)
Cohesion: 0.08
Nodes (17): CaptchaController, BaseController, CategoryController, ClinicInvitationWebController, DoctorImportController, SiteContextController, JsonResponse, ClinicDoctorInvitation (+9 more)
### Community 109 - "Community 109"
Cohesion: 0.29
@@ -1531,8 +1548,8 @@ Cohesion: 0.17
Nodes (4): Rate, Doctor, self, User
### Community 126 - "Community 126"
Cohesion: 0.07
Nodes (17): DEGREE_OPTIONS, DoctorFormPage(), FormValues, GENDER_OPTIONS, schema, SelectedEntry, SpecialtyOption, Breakdown (+9 more)
Cohesion: 0.13
Nodes (12): AdminUserDetail, AVATAR_COLORS, EditForm, editSchema, GENDER_LABELS, getPrimaryRole(), MARITAL_LABELS, MEDICAL_SECTIONS (+4 more)
### Community 128 - "Community 128"
Cohesion: 0.11
@@ -1563,8 +1580,8 @@ Cohesion: 0.12
Nodes (16): Endpoint ها, GET /api/v1/representation/filter/{id}, GET /api/v1/representation/filter/{representationId}, GET /api/v1/representation/my-appointments/{id}, GET /api/v1/representation/{uuid}, GET /api/v1/representation/yearly-income/{id}, GET /api/v1/representation/yearly-income/{representationId}, POST /api/v1/representations/{id}/bank-accounts (+8 more)
### Community 135 - "Community 135"
Cohesion: 0.07
Nodes (29): Access rule, Appointment Settings API, Available Locations, Booking context (`clinic_uuid`), Context additions (2026-07), Date overrides are always per-context, Day Index Convention, DELETE `/api/v1/booking-setting/{uuid}` (+21 more)
Cohesion: 0.12
Nodes (16): Day Index Convention, DELETE `/api/v1/booking-setting/{uuid}`, Errors, Errors, Errors, Errors, GET `/api/v1/appointment-settings/weekly-schedule/{uuid}`, PATCH `/api/v1/appointment-settings/weekly-schedule/{uuid}` (+8 more)
### Community 136 - "Community 136"
Cohesion: 0.12
@@ -1663,8 +1680,8 @@ Cohesion: 0.13
Nodes (15): Date Overrides, DELETE `/api/v1/appointment-settings/date-override/{uuid}`, Errors, Errors, GET `/api/v1/appointment-settings/date-override/list/{doctorUuid}`, GET `/api/v1/appointment-settings/date-override/{uuid}`, PATCH `/api/v1/appointment-settings/date-override/{uuid}`, POST `/api/v1/appointment-settings/date-override` (+7 more)
### Community 161 - "Community 161"
Cohesion: 0.04
Nodes (30): ApiError, { refreshMock, logoutMock }, replaceMock, displayDoctorName(), SecretaryDashboard(), AddrForm, addrSchema, AVATAR_COLORS (+22 more)
Cohesion: 0.02
Nodes (81): latinDigitsField(), NumericFieldProps, wrap(), cn(), digitsOnly(), displayDoctorName(), iranMobileOptionalSchema, iranMobileSchema (+73 more)
### Community 162 - "Community 162"
Cohesion: 0.10
@@ -1696,7 +1713,7 @@ Nodes (20): api.ir (استعلام هویت — Shahkar / IbanMatch), اتصال
### Community 169 - "Community 169"
Cohesion: 0.09
Nodes (10): SourceProfileIdTest, MellatGatewayTest, ErrorCodesTest, HealthControllerTest, TimezoneTest, ActivateTrialTest, SubscriptionService, TestCase (+2 more)
Nodes (9): SourceProfileIdTest, MellatGatewayTest, ErrorCodesTest, HealthControllerTest, TimezoneTest, KavehNegarProviderTest, TestCase, EntityManagerInterface (+1 more)
### Community 170 - "Community 170"
Cohesion: 0.13
@@ -1815,8 +1832,8 @@ Cohesion: 0.14
Nodes (13): Endpoint ها, PATCH /api/v1/secretary/{uuid}, POST /api/v1/secretary, تسک ۱۴: ماژول منشی, توضیح, زمان تخمینی, ساختار JSON, سیستم مجوزها — Resource-Based Permissions (مقیاس‌پذیر) (+5 more)
### Community 200 - "Community 200"
Cohesion: 0.04
Nodes (42): DoctorTab, PickedService, ServicePick, ServiceSlot, ServiceSlotPicker(), get, services, td (+34 more)
Cohesion: 0.18
Nodes (10): ClinicDoctorsManager(), ClinicInvitation, HUES_LIST, INV_STATUS_MAP, ACTION_COLUMNS, ACTION_HEADERS, ClinicDoctorPermissionPayload, DoctorPermissionsModal() (+2 more)
### Community 201 - "Community 201"
Cohesion: 0.11
@@ -1831,8 +1848,8 @@ Cohesion: 0.12
Nodes (16): Clinic Services API, DELETE /api/v1/service-item/{uuid}, DELETE /api/v1/service-section/{uuid}, GET /api/v1/service-item/{uuid}, GET /api/v1/service-item/{uuid}/audit-logs, GET /api/v1/service-items, GET /api/v1/service-items/{sectionUuid}, GET /api/v1/service-items/{uuid}/tariffs (+8 more)
### Community 204 - "Community 204"
Cohesion: 0.04
Nodes (46): RoleRoute(), usePermissions(), useSubscription(), AdminLayout(), avatarBg(), HUES, ProfileMenu(), Role (+38 more)
Cohesion: 0.02
Nodes (100): ALLOWED_ROLES, PrivateRoute(), PublicRoute(), RoleRoute(), queryClient, toastStyle, ClinicDoctorItem, get (+92 more)
### Community 205 - "Community 205"
Cohesion: 0.21
@@ -1907,8 +1924,8 @@ Cohesion: 0.17
Nodes (11): تشخیص عمیق down شدن سرور بعد از ~۱۰ سیکل + وریفای و تکمیل فیکس‌های پایداری, زمینه, فایل‌های مرتبط, مشکل / هدف, نکات مهم, وضعیت فعلی, وظایف, ۱. وریفای فیکس‌های repo (idempotent) (+3 more)
### Community 228 - "Community 228"
Cohesion: 0.04
Nodes (52): Bulk import / export, DELETE `/api/v1/admin/insurance/{id}`, DELETE `/api/v1/billing/tenant-insurances/{uuid}`, DELETE `/api/v1/insurance/{id}`, EntityInsurancePricing — قیمت‌گذاری ویزیت بر اساس بیمه, Errors, Errors, Errors (+44 more)
Cohesion: 0.15
Nodes (13): Bulk import / export, DELETE `/api/v1/admin/insurance/{id}`, DELETE `/api/v1/insurance/{id}`, EntityInsurancePricing — قیمت‌گذاری ویزیت بر اساس بیمه, GET `/api/v1/billing/tenant-insurances/{uuid}/service-coverage`, GET `/api/v1/insurance/{id}`, Insurance API, PUT `/api/v1/billing/tenant-insurances/{uuid}/service-coverage` (+5 more)
### Community 229 - "Community 229"
Cohesion: 0.14
@@ -2183,8 +2200,8 @@ Cohesion: 0.22
Nodes (9): require-dev, phpstan/phpstan, phpstan/phpstan-doctrine, phpstan/phpstan-symfony, phpunit/phpunit, symfony/browser-kit, symfony/css-selector, symfony/debug-bundle (+1 more)
### Community 300 - "Community 300"
Cohesion: 0.25
Nodes (6): DEFAULTS, EMPTY, Form, schema, TAG_COLORS, TenantTag
Cohesion: 0.27
Nodes (9): AddTurn(), PatientsCategoryView(), PatientsGridView(), SearchHeaderP(), TurnsFilter(), countFilters(), dayBound(), EMPTY (+1 more)
### Community 301 - "Community 301"
Cohesion: 0.15
@@ -2199,8 +2216,8 @@ Cohesion: 0.12
Nodes (16): آماده‌سازی پروژه ClinicPro برای دیپلوی روی Coolify با Docker Compose, زمینه, فایل‌های مرتبط, نکات مهم (محدودیت‌ها و edge caseها), هدف, وظایف, ۱. ساخت `Dockerfile` چندمرحله‌ای, ۱۰. ساخت راهنمای `docs/deploy/coolify.md` (+8 more)
### Community 304 - "Community 304"
Cohesion: 0.04
Nodes (56): 29. 🟢 `GET` clinic list 🆕, 30. 🟢 `GET` get 🆕, 31. 🟡 `PATCH` patch, 33. 🔵 `POST` image_clinic, 34. 🔵 `POST` image logo, 36. 🟢 `GET` get my rate, 37. 🔵 `POST` post, 39. 🟡 `PATCH` Comment confirmation (+48 more)
Cohesion: 0.22
Nodes (9): 29. 🟢 `GET` clinic list 🆕, 30. 🟢 `GET` get 🆕, 36. 🟢 `GET` get my rate, 6. کلینیک (Clinic), هدرهای اضافی, پارامترهای Query, پاسخ‌ها, پاسخ‌ها (+1 more)
### Community 306 - "Community 306"
Cohesion: 0.07
@@ -2236,7 +2253,7 @@ Nodes (4): InsuranceRepository, Insurance, InsuranceType, ManagerRegistry
### Community 314 - "Community 314"
Cohesion: 0.02
Nodes (118): AppointmentCardData, AppointmentTurnCard(), base, Invoice, InvoiceItem, InvoiceSummaryModal(), SessionConsumable, SessionData (+110 more)
Nodes (104): AppointmentCardData, AppointmentTurnCard(), base, Invoice, InvoiceItem, InvoiceSummaryModal(), SessionConsumable, SessionData (+96 more)
### Community 315 - "Community 315"
Cohesion: 0.20
@@ -2246,6 +2263,10 @@ Nodes (6): AuthController, RateLimiterFactory, ClinicDoctorPermission, JsonRespo
Cohesion: 0.12
Nodes (15): mapping صفحات clinicpro → فریم فیگما, الف-۱. توکن‌های رنگ (light + dark), الف-۲. سلکتور رنگ کاربر, الف-۳. ابعاد و رفتار layout, الف-۴. شعاع‌ها و input/button, الف-۵. کامپوننت‌های مشترک مطابق فیگما, ایندکس فریم‌های فیگما (۴ section، node-id دسکتاپ), بخش الف — سیستم طراحی مشترک (یک‌بار، پایه‌ی همه‌ی صفحات) (+7 more)
### Community 317 - "Community 317"
Cohesion: 0.29
Nodes (3): CorsRegexEnvProcessor, EnvVarProcessorInterface, CorsRegexEnvProcessorTest
### Community 318 - "Community 318"
Cohesion: 0.32
Nodes (4): PatientListFilterTest, Doctor, PatientRecord, TenantTag
@@ -2542,10 +2563,6 @@ Nodes (3): SessionConsumable, InventoryItem, PatientSession
Cohesion: 0.22
Nodes (7): initiate(), refund(), reverse(), verify(), PaymentInitResult, PaymentRefundResult, PaymentVerifyResult
### Community 399 - "Community 399"
Cohesion: 0.16
Nodes (7): AbstractMigration, Schema, Version20260614182950, Schema, Version20260619121047, Schema, Version20260624092459
### Community 401 - "Community 401"
Cohesion: 0.12
Nodes (15): `lib/utils.ts`, `SettingsPage.tsx` (ورودی‌ها ریال ذخیره می‌شوند), زمینه, فایل‌های مرتبط, مشکل / هدف, نمونهٔ نمایش (Subscription), نکات مهم, واحد پول = تومان در پنل ادمین (نمایش ÷۱۰ / ورودی ×۱۰) — ذخیره و درگاه ریال می‌ماند (+7 more)
@@ -2575,8 +2592,8 @@ Cohesion: 0.10
Nodes (20): edge cases, خلاصهٔ خطاها و اولویت, راه‌حل, راه‌حل, راه‌حل, راه‌حل, رفع خطاهای لاگ سرور (production) — ۱۴۰۵/۰۴/۲۰, ریشه (+12 more)
### Community 416 - "Community 416"
Cohesion: 0.09
Nodes (26): EMPTY_CATS, EMPTY_ITEMS, EMPTY_META, EMPTY_PACKAGES, EMPTY_STATS, InventoryItem, InventoryMeta, InventoryPackage (+18 more)
Cohesion: 0.04
Nodes (59): CoverageRow, Draft, KIND, TenantInsurance, EMPTY_FORM, ItemForm, itemSchema, Props (+51 more)
### Community 418 - "Community 418"
Cohesion: 0.17
@@ -2586,6 +2603,10 @@ Nodes (11): دیپلوی ClinicPro روی Coolify (Docker Compose), رفع اش
Cohesion: 0.12
Nodes (15): راه‌حل, راه‌حل, راه‌حل, رفع زوم نقشه در افزودن آدرس + کپچا و موبایل در claim + حذف پروفایل توسط مالک, ریشه‌ها, زمینه, فایل‌های مرتبط, نکات مهم (+7 more)
### Community 423 - "Community 423"
Cohesion: 0.16
Nodes (7): AbstractMigration, Schema, Version20260609130407, Schema, Version20260610183655, Schema, Version20260719044321
### Community 424 - "Community 424"
Cohesion: 0.35
Nodes (3): SessionConsumableTest, Doctor, InventoryItem
@@ -2607,8 +2628,8 @@ Cohesion: 0.18
Nodes (8): ErrorCodes, PatientController, JsonResponse, PatientRecord, PatientRecordScope, PatientSession, Request, User
### Community 435 - "Community 435"
Cohesion: 0.15
Nodes (10): Command, CancelExpiredAppointmentsCommand, RepairImportedDoctorsCommand, SystemOwnerCommand, InputInterface, OutputInterface, InputInterface, OutputInterface (+2 more)
Cohesion: 0.36
Nodes (3): RepairImportedDoctorsCommand, InputInterface, OutputInterface
### Community 436 - "Community 436"
Cohesion: 0.12
@@ -2655,8 +2676,8 @@ Cohesion: 0.07
Nodes (27): Bank accounts, Body, Body, Errors, Errors, Errors, Errors, Errors (+19 more)
### Community 451 - "Community 451"
Cohesion: 0.37
Nodes (5): AppointmentConfirmationService, AutoCreateSessionOnConfirmTest, Appointment, Clinic, Doctor
Cohesion: 0.31
Nodes (3): InventoryPackageRepository, InventoryPackage, ManagerRegistry
### Community 452 - "Community 452"
Cohesion: 0.15
@@ -2675,8 +2696,8 @@ Cohesion: 0.24
Nodes (10): gridItemStyle, JALALI_MONTHS, jalaliFirstWeekday(), jalaliToGregorian(), navBtnStyle, PersianCalendar(), pf, Props (+2 more)
### Community 459 - "Community 459"
Cohesion: 0.40
Nodes (5): API موجود (نیاز به تغییر ندارند), اپیک ۳ — منشی (Secretary) — تکمیل, تغییرات مورد نیاز, توضیح, نیازمندی‌های کارکردی
Cohesion: 0.33
Nodes (6): API موجود (نیاز به تغییر ندارند), اپیک‌ها, اپیک ۳ — منشی (Secretary) — تکمیل, تغییرات مورد نیاز, توضیح, نیازمندی‌های کارکردی
### Community 460 - "Community 460"
Cohesion: 0.33
@@ -2779,7 +2800,7 @@ Cohesion: 0.12
Nodes (15): `Modal.tsx` (بدون Portal), `PersianCalendar.tsx` (buttonها بدون `type`) — نمونه‌ها, باگ ۱ — علت, باگ ۲ — علت, رفع دو باگ Modal و تقویم شمسی در پنل ادمین, زمینه, فایل‌های مرتبط, مشکل / هدف (+7 more)
### Community 492 - "Community 492"
Cohesion: 0.24
Cohesion: 0.23
Nodes (5): AppointmentConfirmFlowTest, Appointment, Clinic, Doctor, User
### Community 493 - "Community 493"
@@ -2787,8 +2808,8 @@ Cohesion: 0.20
Nodes (9): Backend Audit Backlog — ClinicPro, ☐ CRITICAL, ✅ DONE (committed on backend-audit), ☐ EPICS (design debt — cross-repo, defer; do NOT quick-fix), ☐ HIGH, ☐ LOW, ☐ MEDIUM, Progress (this audit session) (+1 more)
### Community 494 - "Community 494"
Cohesion: 0.24
Nodes (8): ClaimSubmitterInterface, ClaimService, ManualClaimSubmitter, Claim, Invoice, User, Claim, ClaimSubmissionResult
Cohesion: 0.42
Nodes (4): ClaimService, Claim, Invoice, User
### Community 495 - "Community 495"
Cohesion: 0.12
@@ -2811,8 +2832,8 @@ Cohesion: 0.12
Nodes (16): `AppointmentSettingsPage.tsx` (بخش render), backend — نیازی به تغییر نیست (اول گشتم), `InsuranceModal.tsx` — انتخاب دستی نوع (حذف شود), `InsurancePricingPage.tsx`, `TenantInsuranceContracts.tsx` — یک جدول مسطح، خلاصه فقط در زیرنویس نام, بازطراحی صفحه بیمه و قیمت‌گذاری — تفکیک نوع بیمه با Tab + ردیف Expandable + انتقال ویزیت آزاد, زمینه, فایل‌های مرتبط (+8 more)
### Community 500 - "Community 500"
Cohesion: 0.33
Nodes (6): API موجود (نیاز به endpoint جدید ندارد), اپیک‌ها, اپیک ۷ — داشبورد هوشمند (Smart Dashboard), تغییر مورد نیاز, توضیح, نیازمندی‌های کارکردی
Cohesion: 0.40
Nodes (5): API موجود (نیاز به endpoint جدید ندارد), اپیک ۷ — داشبورد هوشمند (Smart Dashboard), تغییر مورد نیاز, توضیح, نیازمندی‌های کارکردی
### Community 501 - "Community 501"
Cohesion: 0.40
@@ -2907,8 +2928,8 @@ Cohesion: 0.33
Nodes (4): PatientMedicalRecord, PatientMedicalRecordRepository, ManagerRegistry, PatientRecord
### Community 529 - "Community 529"
Cohesion: 0.36
Nodes (4): ClinicInvitationWebController, ClinicDoctorInvitation, Request, Response
Cohesion: 0.22
Nodes (8): Access rule, Appointment Settings API, Available Locations, Booking context (`clinic_uuid`), Errors, `GET /api/v1/appointment-settings/available-locations/{doctorUuid}`, Response `200`, Slot Calculation Logic (Reference)
### Community 530 - "Community 530"
Cohesion: 0.11
@@ -2919,8 +2940,8 @@ Cohesion: 0.16
Nodes (3): BankAccount, self, User
### Community 533 - "Community 533"
Cohesion: 0.19
Nodes (6): BaseKernel, Closure, CorsRegexEnvProcessor, EnvVarProcessorInterface, MicroKernelTrait, Kernel
Cohesion: 0.31
Nodes (4): BaseKernel, Closure, MicroKernelTrait, Kernel
### Community 534 - "Community 534"
Cohesion: 0.34
@@ -2947,7 +2968,7 @@ Cohesion: 0.09
Nodes (22): تسک ۱ — رفع باگ واحد پول در ثبت پرداخت و تخفیف ثابت, تسک ۲ — نمایش کامل پرداخت‌های ثبت‌شده (تاریخ/ساعت + ثبت‌کننده), تسک ۳ — منوی «...» روی هر مراجعه: «مشاهده فاکتور» + «آرشیو», تسک ۴ — آرشیو مراجعات (backend + UI فیلتر), رفع باگ واحد پول پرداخت + اطلاعات پرداخت‌ها + منوی سرویس + آرشیو مراجعات, زمینه, فایل‌های مرتبط, قوانین عمومی (+14 more)
### Community 542 - "Community 542"
Cohesion: 0.14
Cohesion: 0.12
Nodes (4): SessionPayment, PatientSession, self, User
### Community 543 - "Community 543"
@@ -3015,8 +3036,8 @@ Cohesion: 0.50
Nodes (4): Errors, POST `/api/v1/admin/sms/template/{uuid}/reject`, Request Body, Response `200`
### Community 559 - "Community 559"
Cohesion: 0.25
Nodes (4): SmsMessageTemplateRepository, SmsTextResolver, SmsMessageTemplate, ManagerRegistry
Cohesion: 0.16
Nodes (7): SmsMessageController, SmsMessageTemplateRepository, SmsTextResolver, SmsMessageTemplate, JsonResponse, Request, ManagerRegistry
### Community 560 - "Community 560"
Cohesion: 0.50
@@ -3067,8 +3088,8 @@ Cohesion: 0.25
Nodes (8): ۲.۲ انواع دسته‌بندی (Category Types), ۲.۲.۱ تگ (Tag), ۲.۲.۲ استان (State), ۲.۲.۳ شهر (City), ۲.۲.۴ بیمه پایه (Basic Insurance), ۲.۲.۵ بیمه مکمل (Supplementary Insurance), ۲.۲.۶ تخصص دکتر (Doctor Specialty), ۲.۲.۷ خدمات دکتر (Doctor Services)
### Community 577 - "Community 577"
Cohesion: 0.07
Nodes (38): PatientFormValues, baseValues, options, setup(), useIssueInvoice(), dateStrToTs(), EDUCATION_OPTS, formValuesToPayload() (+30 more)
Cohesion: 0.05
Nodes (46): grid, PatientFormOptions, patientFormSchema, PatientFormValues, Props, baseValues, options, InvoicePayload (+38 more)
### Community 578 - "Community 578"
Cohesion: 0.36
@@ -3158,10 +3179,6 @@ Nodes (10): ایمپورت پزشکان نظام پزشکی به کلینیک‌
Cohesion: 0.34
Nodes (5): RepresentationController, JsonResponse, Representation, Request, User
### Community 608 - "Community 608"
Cohesion: 0.19
Nodes (4): LogPruneService, MaintenanceService, MaintenanceModeTest, SiteConfigRepository
### Community 612 - "Community 612"
Cohesion: 0.67
Nodes (3): بک‌اند, فرانت‌اند, وضعیت فعلی کد (مهم — قبل از تغییر بخوان)
@@ -3199,7 +3216,7 @@ Cohesion: 0.22
Nodes (5): ClaimsByPatientTest, Doctor, Invoice, PatientRecord, User
### Community 646 - "Community 646"
Cohesion: 0.13
Cohesion: 0.12
Nodes (3): NumericFieldNormalizerTest, PersianTextTest, PersianText
### Community 647 - "Community 647"
@@ -3279,8 +3296,8 @@ Cohesion: 0.31
Nodes (3): InventoryItemRepository, InventoryItem, ManagerRegistry
### Community 681 - "Community 681"
Cohesion: 0.15
Nodes (8): PurgeDoctorsCommandTest, PurgeUnclaimedDoctorsCommandTest, RepositoryClassMappingTest, KernelTestCase, DbLoggerTest, CommandTester, CommandTester, Doctor
Cohesion: 0.21
Nodes (6): PurgeUnclaimedDoctorsCommandTest, RepositoryClassMappingTest, KernelTestCase, DbLoggerTest, CommandTester, Doctor
### Community 683 - "Community 683"
Cohesion: 0.33
@@ -3307,8 +3324,8 @@ Cohesion: 0.12
Nodes (16): Errors, Errors, Errors, GET `/api/v1/admin/representations`, GET `/api/v1/admin/representations/{uuid}/appointments`, GET `/api/v1/admin/representations/{uuid}/doctors`, POST `/api/v1/admin/representations/{uuid}/doctors`, Query Parameters (+8 more)
### Community 693 - "Community 693"
Cohesion: 0.47
Nodes (3): CommissionService, Payment, Representation
Cohesion: 0.21
Nodes (7): FinancialBreakdown, FinancialBreakdownRepository, CommissionService, ManagerRegistry, Payment, Payment, Representation
### Community 694 - "Community 694"
Cohesion: 0.05
@@ -3367,8 +3384,8 @@ Cohesion: 0.12
Nodes (15): رفع باگ: نوبت‌های رزروشده در سایت عمومی «آزاد» نمایش داده می‌شوند, زمینه, فایل‌های مرتبط, مشکل / هدف, نکات مهم, وضعیت فعلی, وظایف, پروژه (+7 more)
### Community 717 - "Community 717"
Cohesion: 0.20
Nodes (4): TenantServiceCoverageRepository, TenantInsuranceCleanupService, ManagerRegistry, TenantServiceCoverage
Cohesion: 0.09
Nodes (10): ServiceCoverageNPlusOneTest, TenantInsuranceCleanupTest, SessionInsuranceShareTest, TenantServiceCoverageRepository, TenantInsuranceCleanupService, ManagerRegistry, TenantServiceCoverage, Doctor (+2 more)
### Community 718 - "Community 718"
Cohesion: 0.36
@@ -3379,8 +3396,8 @@ Cohesion: 0.33
Nodes (3): BlogCityScopeTest, Blog, City
### Community 724 - "Community 724"
Cohesion: 0.33
Nodes (5): DoctorImportResult, DoctorImportService, Collection, Doctor, User
Cohesion: 0.42
Nodes (3): ActivateTrialTest, SubscriptionService, SubscriptionPlan
### Community 725 - "Community 725"
Cohesion: 0.33
@@ -3402,10 +3419,6 @@ Nodes (3): PatientResolver, User, UserProfile
Cohesion: 0.15
Nodes (12): call siteهای فعلی PersianDateInput (نباید تغییر کنند — فقط برای اطمینان از سازگاری Props), زمینه, فایل‌های مرتبط, مشکل / هدف, نکات مهم, همه‌ی تقویم‌های پنل ادمین باید شمسی باشند (رفع تقویم میلادی PersianDateInput), وضعیت فعلی (کد مشکل‌دار), وظایف (+4 more)
### Community 733 - "Community 733"
Cohesion: 0.19
Nodes (5): UniqueConstraintsTest, FinancialBreakdown, FinancialBreakdownRepository, ManagerRegistry, Payment
### Community 737 - "Community 737"
Cohesion: 0.38
Nodes (3): PurgeDoctorsCommand, InputInterface, OutputInterface
@@ -3467,8 +3480,8 @@ Cohesion: 0.39
Nodes (3): ClinicStaffRepository, ClinicStaff, ManagerRegistry
### Community 761 - "Community 761"
Cohesion: 0.28
Nodes (4): SubscriptionService, ClinicSubscription, Payment, SubscriptionPlan
Cohesion: 0.43
Nodes (3): AppointmentEvent, AppointmentEventRepository, ManagerRegistry
### Community 763 - "Community 763"
Cohesion: 0.33
@@ -3527,8 +3540,8 @@ Cohesion: 0.24
Nodes (4): InventoryPackageItem, InventoryItem, InventoryPackage, self
### Community 782 - "Community 782"
Cohesion: 0.38
Nodes (3): AuditScheduleLocationsCommand, InputInterface, OutputInterface
Cohesion: 0.10
Nodes (16): Command, AuditScheduleLocationsCommand, CancelExpiredAppointmentsCommand, MaintenanceCommand, PruneLogsCommand, SeedCategoriesCommand, InputInterface, OutputInterface (+8 more)
### Community 783 - "Community 783"
Cohesion: 0.13
@@ -3547,8 +3560,8 @@ Cohesion: 0.50
Nodes (4): Errors, GET `/oauth/userinfo`, Headers, Response `200`
### Community 789 - "Community 789"
Cohesion: 0.12
Nodes (5): KavehNegarProvider, RanginehProvider, SmsService, SendSmsMessage, SmsProviderInterface
Cohesion: 0.18
Nodes (4): KavehNegarProvider, SmsService, SendSmsMessage, SmsProviderInterface
### Community 791 - "Community 791"
Cohesion: 0.40
@@ -3595,16 +3608,16 @@ Cohesion: 0.25
Nodes (8): Errors, GET `/api/v1/patient/{uuid}/payments`, GET `/api/v1/patient/{uuid}/wallet`, GET `/api/v1/patient/{uuid}/wallet/transactions`, PATCH `/api/v1/session/{uuid}` — پرداخت مراجعه از کیف پول, POST `/api/v1/patient/{uuid}/wallet/charge`, POST `/api/v1/patient/{uuid}/wallet/withdraw`, مالی بیمار (Financials: پرداخت / تراکنش / کیف‌پول)
### Community 809 - "Community 809"
Cohesion: 0.13
Nodes (3): LoggerInterface, ApiIrService, MaintenanceService
Cohesion: 0.15
Nodes (3): LoggerInterface, RanginehProvider, ApiIrService
### Community 810 - "Community 810"
Cohesion: 0.15
Nodes (12): زمینه, فایل‌های مرتبط, مشکل / هدف, نکات مهم, وضعیت فعلی, وظایف, ویرایش سرویس‌های مراجعه + ویرایش/حذف پرداخت + Audit Log مالی جامع, پروژه (+4 more)
### Community 813 - "Community 813"
Cohesion: 0.18
Nodes (5): BackfillSourceProfileIdStep, SourceProfileId, RepairOptions, RepairResult, SymfonyStyle
Cohesion: 0.12
Nodes (10): DoctorImportResult, BackfillSourceProfileIdStep, DoctorImportService, SourceProfileId, Collection, Doctor, User, RepairOptions (+2 more)
### Community 814 - "Community 814"
Cohesion: 0.38
@@ -3639,40 +3652,36 @@ Cohesion: 0.50
Nodes (4): autoload, files, psr-4, App\\
### Community 832 - "Community 832"
Cohesion: 0.30
Nodes (4): DomainContextResolver, DomainCommissionTest, Payment, Representation
Cohesion: 0.38
Nodes (3): SystemOwnerCommand, InputInterface, OutputInterface
### Community 833 - "Community 833"
Cohesion: 0.50
Nodes (4): extra, symfony, allow-contrib, require
### Community 834 - "Community 834"
Cohesion: 0.39
Nodes (4): SubscriptionPeriodRepository, ManagerRegistry, SubscriptionPeriod, SubscriptionPlan
Cohesion: 0.43
Nodes (3): CategoryImportController, JsonResponse, Request
### Community 835 - "Community 835"
Cohesion: 0.39
Nodes (3): UserRepository, ManagerRegistry, User
### Community 838 - "Community 838"
Cohesion: 0.53
Nodes (3): TaxRateHistoryRepository, ManagerRegistry, TaxRateHistory
Cohesion: 0.48
Nodes (3): DoctorInsuranceRepository, DoctorInsurance, ManagerRegistry
### Community 839 - "Community 839"
Cohesion: 0.47
Nodes (3): BookingContextResolver, Clinic, Doctor
### Community 840 - "Community 840"
Cohesion: 0.33
Nodes (5): Like, LikeRepository, Comment, ManagerRegistry, User
### Community 841 - "Community 841"
Cohesion: 0.36
Nodes (4): ClaimAmountBoundsTest, Claim, Doctor, User
### Community 842 - "Community 842"
Cohesion: 0.38
Nodes (3): BackfillAppointmentSessionsCommand, InputInterface, OutputInterface
Cohesion: 0.53
Nodes (4): ClaimSubmitterInterface, ManualClaimSubmitter, Claim, ClaimSubmissionResult
### Community 844 - "Community 844"
Cohesion: 0.15
@@ -3718,6 +3727,10 @@ Nodes (3): DiscountRuleRepository, DiscountRule, ManagerRegistry
Cohesion: 0.50
Nodes (4): GET `/api/v1/admin/settlements`, Query Parameters, Response `200`, Settlement Management
### Community 858 - "Community 858"
Cohesion: 0.11
Nodes (6): IrimcDegreeMappingTest, FixDegreeStep, IrimcDegreeMapper, RepairOptions, RepairResult, SymfonyStyle
### Community 861 - "Community 861"
Cohesion: 0.17
Nodes (11): Gotchas, Troubleshooting, بازطراحی صفحه پنل ادمین ClinicPro, پیش‌نیازها, چک‌لیست بازطراحی, گردش کار, ۱. اسکرین‌شات صفحه فعلی, ۲. نگاشت URL به سورس + آدیت (+3 more)
@@ -3750,10 +3763,6 @@ Nodes (3): CreateAdminCommand, InputInterface, OutputInterface
Cohesion: 0.43
Nodes (3): SessionPaymentRepository, ManagerRegistry, SessionPayment
### Community 870 - "Community 870"
Cohesion: 0.43
Nodes (3): SmsMessageController, JsonResponse, Request
### Community 871 - "Community 871"
Cohesion: 0.35
Nodes (3): DoctorListLocationTest, City, Doctor
@@ -3778,10 +3787,6 @@ Nodes (6): DELETE `/api/v1/patient/note/{uuid}`, Errors, GET `/api/v1/patient/{u
Cohesion: 0.33
Nodes (3): PurgeUnclaimedDoctorsCommand, InputInterface, OutputInterface
### Community 881 - "Community 881"
Cohesion: 0.38
Nodes (3): MaintenanceCommand, InputInterface, OutputInterface
### Community 884 - "Community 884"
Cohesion: 0.15
Nodes (12): زمینه, فایل‌های مرتبط, فرآیند ثبت و قطعی کردن نوبت (مودال پرداخت + پرونده), مشکل / هدف, نکات مهم, وضعیت فعلی, وظایف, پروژه (+4 more)
@@ -3810,17 +3815,13 @@ Nodes (5): Errors, GET `/api/v1/clinic/doctor-list/{clinicUuid}`, Path Parameter
Cohesion: 0.48
Nodes (3): SessionServiceRepository, ManagerRegistry, SessionService
### Community 904 - "Community 904"
Cohesion: 0.41
Nodes (4): SessionInsuranceShareTest, Doctor, Insurance, ServiceItem
### Community 906 - "Community 906"
Cohesion: 0.18
Nodes (10): زمینه, نکات مهم, هدف, وظایف, پاک‌سازی رکوردهای آلودهٔ پزشک و کلینیک, پروژه, ۱. گزارش دامنهٔ آلودگی (اول اندازه‌گیری، بعد حذف), ۲. پاک‌سازی (+2 more)
### Community 908 - "Community 908"
Cohesion: 0.42
Nodes (3): RepairImportedDoctorsCommandTest, CommandTester, Doctor
Cohesion: 0.36
Nodes (4): RepairImportedDoctorsCommandTest, DoctorRepairStep, CommandTester, Doctor
### Community 910 - "Community 910"
Cohesion: 0.50
@@ -3878,10 +3879,6 @@ Nodes (3): PatientRecordScopeResolver, PatientRecordScope, User
Cohesion: 0.43
Nodes (4): AppointmentConfirmationService, Appointment, PatientSession, User
### Community 932 - "Community 932"
Cohesion: 0.47
Nodes (3): PruneLogsCommand, InputInterface, OutputInterface
### Community 935 - "Community 935"
Cohesion: 0.38
Nodes (3): RepairAcceptedInvitationsCommand, InputInterface, OutputInterface
@@ -3891,24 +3888,24 @@ Cohesion: 0.38
Nodes (3): NormalizeScheduleFormatCommand, InputInterface, OutputInterface
### Community 939 - "Community 939"
Cohesion: 0.12
Nodes (9): DoctorRepairStep, BackfillSpecialtyParentsStep, BackfillSurrogateRoleStep, RepairOptions, RepairResult, SymfonyStyle, RepairOptions, RepairResult (+1 more)
Cohesion: 0.25
Nodes (4): BackfillSurrogateRoleStep, RepairOptions, RepairResult, SymfonyStyle
### Community 940 - "Community 940"
Cohesion: 0.47
Nodes (3): SeedCategoriesCommand, InputInterface, OutputInterface
Cohesion: 0.40
Nodes (5): Context additions (2026-07), Date overrides are always per-context, `GET /available-locations/{doctorUuid}`, Holidays are global by default, Response fields
### Community 941 - "Community 941"
Cohesion: 0.50
Nodes (4): Errors, GET `/api/v1/representation/doctors`, Query Parameters, Response `200`
### Community 942 - "Community 942"
Cohesion: 0.25
Nodes (4): FixDegreeStep, RepairOptions, RepairResult, SymfonyStyle
Cohesion: 0.40
Nodes (5): Errors, GET `/api/v1/admin/clinic/{uuid}/invitations`, Path Parameters, Query Parameters, Response `200`
### Community 943 - "Community 943"
Cohesion: 0.48
Nodes (3): SessionConsumableRepository, ManagerRegistry, SessionConsumable
Cohesion: 0.40
Nodes (5): DELETE `/api/v1/billing/tenant-insurances/{uuid}`, GET `/api/v1/billing/tenant-insurances`, PATCH `/api/v1/billing/tenant-insurances/{uuid}`, POST `/api/v1/billing/tenant-insurances`, TenantInsurance — قراردادهای بیمه‌ی tenant (فاز ۱ سیستم صورتحساب)
### Community 944 - "Community 944"
Cohesion: 0.67
@@ -3946,6 +3943,10 @@ Nodes (5): Errors, POST `/api/v1/appointment/{uuid}/confirm`, Request Body, Resp
Cohesion: 0.40
Nodes (5): DELETE `/api/v1/patient/attachment/{uuid}`, Errors, GET `/api/v1/patient/{uuid}/attachments`, POST `/api/v1/patient/{uuid}/attachment`, ضمیمه‌های بیمار (Attachments)
### Community 960 - "Community 960"
Cohesion: 0.40
Nodes (5): 31. 🟡 `PATCH` patch, Request Body, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 962 - "Community 962"
Cohesion: 0.53
Nodes (3): PaymentLog, PaymentLogRepository, ManagerRegistry
@@ -4022,6 +4023,10 @@ Nodes (3): ClaimStatusLog, ClaimStatusLogRepository, ManagerRegistry
Cohesion: 0.40
Nodes (5): Errors, PATCH `/api/v1/clinic-pro/doctor-address/{id}`, Path Parameters, Request Body, Response `200`
### Community 983 - "Community 983"
Cohesion: 0.40
Nodes (5): 37. 🔵 `POST` post, Request Body, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 985 - "Community 985"
Cohesion: 0.67
Nodes (3): GET `/api/v1/clinics`, Query Parameters, Response `200`
@@ -4034,10 +4039,18 @@ Nodes (4): run(), RepairOptions, RepairResult, SymfonyStyle
Cohesion: 0.33
Nodes (6): GET `/api/v1/admin/sms/logs`, GET `/api/v1/admin/sms/templates`, Query Parameters, Response `200`, Response `200`, SMS Management (Admin)
### Community 991 - "Community 991"
Cohesion: 0.40
Nodes (5): 41. 🟡 `PATCH` patch, Request Body, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 992 - "Community 992"
Cohesion: 0.47
Nodes (3): SeedSmsMessageTemplatesCommand, InputInterface, OutputInterface
### Community 997 - "Community 997"
Cohesion: 0.40
Nodes (4): license, overrides, lodash, private
### Community 999 - "Community 999"
Cohesion: 0.40
Nodes (5): GET `/api/v1/admin/financial-breakdowns`, GET `/api/v1/admin/financial-summary`, GET `/api/v1/admin/settings/tax-history`, GET `/api/v1/admin/settlement/{uuid}`, موتور مالی نمایندگی
@@ -4046,6 +4059,10 @@ Nodes (5): GET `/api/v1/admin/financial-breakdowns`, GET `/api/v1/admin/financia
Cohesion: 0.40
Nodes (5): 35. 🟡 `PATCH` patch, Request Body, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 1002 - "Community 1002"
Cohesion: 0.50
Nodes (4): Errors, GET `/api/v1/admin/insurances`, Query Parameters, Response `200`
### Community 1003 - "Community 1003"
Cohesion: 0.50
Nodes (4): DELETE `/api/v1/doctor/{uuid}`, Errors, Path Parameters, Response `200`
@@ -4062,6 +4079,10 @@ Nodes (4): 38. 🟢 `GET` Unapproved comments, هدرهای اضافی, پارا
Cohesion: 0.50
Nodes (4): Errors, GET `/api/v1/doctor/{uuid}`, Path Parameters, Response `200`
### Community 1009 - "Community 1009"
Cohesion: 0.50
Nodes (4): Errors, POST `/api/v1/admin/insurance`, Request Body (`application/json`), Response `201`
### Community 1010 - "Community 1010"
Cohesion: 0.50
Nodes (4): Errors, POST `/file/upload/clinic_pro/doctor/field_image`, Request, Response `200`
@@ -4071,32 +4092,92 @@ Cohesion: 0.50
Nodes (4): Errors, POST `/api/v1/clinic-pro/doctor-address`, Request Body, Response `201`
### Community 1012 - "Community 1012"
Cohesion: 0.25
Nodes (7): ForgotStep, LoginPage(), Mode, SmsStep, Altcha(), AltchaProps, IntrinsicElements
Cohesion: 0.50
Nodes (4): Errors, PATCH `/api/v1/admin/insurance/{id}`, Path Parameters, Response `200`
### Community 1013 - "Community 1013"
Cohesion: 0.50
Nodes (4): Errors, Path Parameters, POST `/api/v1/clinic-pro/doctor-address/from-clinic/{clinicUuid}`, Response `201`
### Community 1014 - "Community 1014"
Cohesion: 0.50
Nodes (4): Errors, POST `/api/v1/insurance/`, Request Body (`application/json`), Response `201`
### Community 1015 - "Community 1015"
Cohesion: 0.50
Nodes (4): Errors, PATCH `/api/v1/insurance/{id}`, Request Body, Response `200`
### Community 1019 - "Community 1019"
Cohesion: 0.50
Nodes (4): GET `/api/v1/insurance-pricing`, Query Parameters, Response `200`, خطاها
### Community 1020 - "Community 1020"
Cohesion: 0.50
Nodes (4): PUT `/api/v1/insurance-pricing`, Request Body, Response `200`, خطاها
### Community 1021 - "Community 1021"
Cohesion: 0.50
Nodes (4): 39. 🟡 `PATCH` Comment confirmation, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 1022 - "Community 1022"
Cohesion: 0.50
Nodes (4): 40. 🔵 `POST` post, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 1023 - "Community 1023"
Cohesion: 0.50
Nodes (4): 44. 🟢 `GET` list comment, هدرهای اضافی, پارامترهای Query, پاسخ‌ها
### Community 1024 - "Community 1024"
Cohesion: 0.50
Nodes (4): 45. 🔵 `POST` post, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 1025 - "Community 1025"
Cohesion: 0.50
Nodes (4): 46. 🟡 `PATCH` patch, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 1026 - "Community 1026"
Cohesion: 0.67
Nodes (3): GET `/api/v1/insurances`, Query Parameters, Response `200`
### Community 1027 - "Community 1027"
Cohesion: 0.67
Nodes (3): POST `/api/v1/admin/insurance/{id}/upload-logo`, Request, Response `200`
### Community 1028 - "Community 1028"
Cohesion: 0.67
Nodes (3): 33. 🔵 `POST` image_clinic, هدرهای اضافی, پاسخ‌ها
### Community 1029 - "Community 1029"
Cohesion: 0.67
Nodes (3): 34. 🔵 `POST` image logo, هدرهای اضافی, پاسخ‌ها
### Community 1030 - "Community 1030"
Cohesion: 0.67
Nodes (3): 42. 🔴 `DELETE` delete, هدرهای اضافی, پاسخ‌ها
### Community 1031 - "Community 1031"
Cohesion: 0.67
Nodes (3): 43. 🟢 `GET` get, هدرهای اضافی, پاسخ‌ها
## Knowledge Gaps
- **5271 isolated node(s):** `PORT`, `ROLES`, `[cmd, ...argv]`, `positional`, `opts` (+5266 more)
- **5274 isolated node(s):** `PORT`, `ROLES`, `[cmd, ...argv]`, `positional`, `opts` (+5269 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **238 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **239 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `Altcha` connect `Community 367` to `Community 1012`, `Community 485`?**
_High betweenness centrality (0.115) - this node is a cross-community bridge._
- **Why does `SiteConfigRepository` connect `Community 608` to `Community 832`, `Community 485`, `Community 809`, `Community 169`, `Community 205`, `Community 206`, `Community 15`, `Community 367`, `Community 693`, `Community 761`, `Community 637`, `Community 926`?**
_High betweenness centrality (0.103) - this node is a cross-community bridge._
- **Why does `BaseController` connect `Community 108` to `Community 771`, `Community 6`, `Community 7`, `Community 265`, `Community 138`, `Community 139`, `Community 14`, `Community 654`, `Community 655`, `Community 529`, `Community 15`, `Community 275`, `Community 20`, `Community 22`, `Community 26`, `Community 926`, `Community 676`, `Community 295`, `Community 433`, `Community 689`, `Community 438`, `Community 58`, `Community 59`, `Community 315`, `Community 699`, `Community 444`, `Community 64`, `Community 75`, `Community 77`, `Community 607`, `Community 739`, `Community 870`, `Community 104`, `Community 873`, `Community 107`, `Community 109`, `Community 245`, `Community 121`, `Community 122`, `Community 252`?**
_High betweenness centrality (0.057) - this node is a cross-community bridge._
- **Why does `Altcha` connect `Community 367` to `Community 204`, `Community 485`?**
_High betweenness centrality (0.110) - this node is a cross-community bridge._
- **Why does `SiteConfigRepository` connect `Community 15` to `Community 485`, `Community 809`, `Community 169`, `Community 205`, `Community 206`, `Community 367`, `Community 724`, `Community 693`, `Community 637`, `Community 926`?**
_High betweenness centrality (0.096) - this node is a cross-community bridge._
- **Why does `ApiTestCase` connect `Community 86` to `Community 0`, `Community 11`, `Community 15`, `Community 16`, `Community 531`, `Community 534`, `Community 535`, `Community 538`, `Community 539`, `Community 541`, `Community 562`, `Community 565`, `Community 59`, `Community 574`, `Community 575`, `Community 583`, `Community 594`, `Community 82`, `Community 609`, `Community 107`, `Community 633`, `Community 640`, `Community 641`, `Community 646`, `Community 652`, `Community 658`, `Community 659`, `Community 685`, `Community 690`, `Community 697`, `Community 710`, `Community 715`, `Community 717`, `Community 719`, `Community 733`, `Community 743`, `Community 744`, `Community 748`, `Community 765`, `Community 775`, `Community 779`, `Community 784`, `Community 804`, `Community 817`, `Community 826`, `Community 828`, `Community 318`, `Community 837`, `Community 841`, `Community 851`, `Community 854`, `Community 856`, `Community 870`, `Community 871`, `Community 887`, `Community 907`, `Community 403`, `Community 917`, `Community 918`, `Community 920`, `Community 921`, `Community 927`, `Community 928`, `Community 936`, `Community 424`, `Community 938`, `Community 431`, `Community 442`, `Community 445`, `Community 959`, `Community 971`, `Community 975`, `Community 980`, `Community 990`, `Community 480`, `Community 482`, `Community 483`, `Community 484`, `Community 486`, `Community 998`, `Community 1000`, `Community 492`, `Community 1005`, `Community 497`?**
_High betweenness centrality (0.044) - this node is a cross-community bridge._
- **What connects `PORT`, `ROLES`, `[cmd, ...argv]` to the rest of the system?**
_5271 weakly-connected nodes found - possible documentation gaps or missing edges._
_5274 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.05237171574678187 - nodes in this community are weakly interconnected._
_Cohesion score 0.06073871409028728 - nodes in this community are weakly interconnected._
- **Should `Community 1` be split into smaller, more focused modules?**
_Cohesion score 0.02696629213483146 - nodes in this community are weakly interconnected._
_Cohesion score 0.0420899854862119 - nodes in this community are weakly interconnected._
- **Should `Community 2` be split into smaller, more focused modules?**
_Cohesion score 0.09523809523809523 - nodes in this community are weakly interconnected._
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "users_hamed_pj_my_pj_clinic_pro_clinicpro_migrations_version20260723120122_php", "label": "Version20260723120122.php", "file_type": "code", "source_file": "migrations/Version20260723120122.php", "source_location": "L1"}, {"id": "migrations_version20260723120122_version20260723120122", "label": "Version20260723120122", "file_type": "code", "source_file": "migrations/Version20260723120122.php", "source_location": "L13"}, {"id": "abstractmigration", "label": "AbstractMigration", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "migrations_version20260723120122_version20260723120122_getdescription", "label": ".getDescription()", "file_type": "code", "source_file": "migrations/Version20260723120122.php", "source_location": "L15"}, {"id": "migrations_version20260723120122_version20260723120122_up", "label": ".up()", "file_type": "code", "source_file": "migrations/Version20260723120122.php", "source_location": "L20"}, {"id": "schema", "label": "Schema", "file_type": "code", "source_file": "migrations/Version20260723120122.php", "source_location": "L20"}, {"id": "migrations_version20260723120122_version20260723120122_down", "label": ".down()", "file_type": "code", "source_file": "migrations/Version20260723120122.php", "source_location": "L26"}], "edges": [{"source": "users_hamed_pj_my_pj_clinic_pro_clinicpro_migrations_version20260723120122_php", "target": "schema", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "migrations/Version20260723120122.php", "source_location": "L7", "weight": 1.0}, {"source": "users_hamed_pj_my_pj_clinic_pro_clinicpro_migrations_version20260723120122_php", "target": "abstractmigration", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "migrations/Version20260723120122.php", "source_location": "L8", "weight": 1.0}, {"source": "users_hamed_pj_my_pj_clinic_pro_clinicpro_migrations_version20260723120122_php", "target": "migrations_version20260723120122_version20260723120122", "relation": "contains", "confidence": "EXTRACTED", "source_file": "migrations/Version20260723120122.php", "source_location": "L13", "weight": 1.0}, {"source": "migrations_version20260723120122_version20260723120122", "target": "abstractmigration", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "migrations/Version20260723120122.php", "source_location": "L13", "weight": 1.0}, {"source": "migrations_version20260723120122_version20260723120122", "target": "migrations_version20260723120122_version20260723120122_getdescription", "relation": "method", "confidence": "EXTRACTED", "source_file": "migrations/Version20260723120122.php", "source_location": "L15", "weight": 1.0}, {"source": "migrations_version20260723120122_version20260723120122", "target": "migrations_version20260723120122_version20260723120122_up", "relation": "method", "confidence": "EXTRACTED", "source_file": "migrations/Version20260723120122.php", "source_location": "L20", "weight": 1.0}, {"source": "migrations_version20260723120122_version20260723120122_up", "target": "schema", "relation": "references", "confidence": "EXTRACTED", "source_file": "migrations/Version20260723120122.php", "source_location": "L20", "weight": 1.0, "context": "parameter_type"}, {"source": "migrations_version20260723120122_version20260723120122", "target": "migrations_version20260723120122_version20260723120122_down", "relation": "method", "confidence": "EXTRACTED", "source_file": "migrations/Version20260723120122.php", "source_location": "L26", "weight": 1.0}, {"source": "migrations_version20260723120122_version20260723120122_down", "target": "schema", "relation": "references", "confidence": "EXTRACTED", "source_file": "migrations/Version20260723120122.php", "source_location": "L26", "weight": 1.0, "context": "parameter_type"}], "raw_calls": [{"caller_nid": "migrations_version20260723120122_version20260723120122_up", "callee": "addSql", "is_member_call": true, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/migrations/Version20260723120122.php", "source_location": "L23", "receiver": null}, {"caller_nid": "migrations_version20260723120122_version20260723120122_down", "callee": "addSql", "is_member_call": true, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/migrations/Version20260723120122.php", "source_location": "L28", "receiver": null}]}
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1591 -1158
View File
File diff suppressed because it is too large Load Diff
+23 -18
View File
@@ -815,8 +815,8 @@
"semantic_hash": ""
},
"src/Appointment/Controller/AppointmentController.php": {
"mtime": 1784800330.9387112,
"ast_hash": "c9e5bad6526642fd698b24bf6c37d536",
"mtime": 1784808041.6931024,
"ast_hash": "7c741d1e7b86d61b92e31f3ed41fb7c6",
"semantic_hash": ""
},
"src/Appointment/Controller/AppointmentSettingsController.php": {
@@ -1345,8 +1345,8 @@
"semantic_hash": ""
},
"src/Patient/Service/PatientService.php": {
"mtime": 1784800330.943104,
"ast_hash": "996ca5b9d953fa8093c5cbdf8632590c",
"mtime": 1784808015.9772892,
"ast_hash": "8a0702a10a10308d88fccb2fe0265a69",
"semantic_hash": ""
},
"src/Payment/Controller/PaymentController.php": {
@@ -2335,8 +2335,8 @@
"semantic_hash": ""
},
"docs/api/appointment.md": {
"mtime": 1784806823.2681842,
"ast_hash": "f9fc3c259c0609e167fa68c012d7b1fd",
"mtime": 1784808391.3466892,
"ast_hash": "bc3df1cc2239f976541ac06d46800b5f",
"semantic_hash": ""
},
"docs/api/auth.md": {
@@ -2395,8 +2395,8 @@
"semantic_hash": ""
},
"docs/api/patient.md": {
"mtime": 1784800330.8704922,
"ast_hash": "d0de4655e7a33cb4b9f75c371361670f",
"mtime": 1784808420.0284448,
"ast_hash": "8e0f3288129892a8c121fdb806fa22b5",
"semantic_hash": ""
},
"docs/api/payment.md": {
@@ -5090,8 +5090,8 @@
"semantic_hash": ""
},
"src/Patient/Entity/SessionPayment.php": {
"mtime": 1784800330.9428656,
"ast_hash": "590d92516877e011b5362e9644d319ba",
"mtime": 1784808002.0035663,
"ast_hash": "4a57cb8b10b0dd24d3f9970613d4e07e",
"semantic_hash": ""
},
"src/Patient/Repository/SessionPaymentRepository.php": {
@@ -5620,8 +5620,8 @@
"semantic_hash": ""
},
"assets/admin/components/appointments/ConfirmAppointmentModal.tsx": {
"mtime": 1784800330.8685021,
"ast_hash": "21b4c13aa4b240e8cde0225c9bbe93af",
"mtime": 1784808232.4621327,
"ast_hash": "2033a4dd1eae8f4d84b567fcff5ea73d",
"semantic_hash": ""
},
"assets/admin/pages/ClaimPatientDetailPage.tsx": {
@@ -5660,8 +5660,8 @@
"semantic_hash": ""
},
"src/Appointment/Service/AppointmentConfirmationService.php": {
"mtime": 1784800330.9388812,
"ast_hash": "7e4509fa018215d1553d90b4eec1beab",
"mtime": 1784808027.1609445,
"ast_hash": "d9c81dffdb569ef4b2b096037ece006d",
"semantic_hash": ""
},
"src/Billing/Entity/ClaimStatusLog.php": {
@@ -5685,8 +5685,8 @@
"semantic_hash": ""
},
"tests/Appointment/AppointmentConfirmFlowTest.php": {
"mtime": 1784800330.9432747,
"ast_hash": "52bc5d0a0a36058d3a8be6a26ffbf421",
"mtime": 1784808338.3464715,
"ast_hash": "c2387464848d205b8fc87450015c4d85",
"semantic_hash": ""
},
"tests/Appointment/ClinicAppointmentAccessTest.php": {
@@ -5825,8 +5825,8 @@
"semantic_hash": ""
},
"assets/admin/components/appointments/ConfirmAppointmentModal.test.tsx": {
"mtime": 1784800330.8682916,
"ast_hash": "3e019b74d7358bd476bef3f1a8be4e47",
"mtime": 1784808299.6069503,
"ast_hash": "f87b3d9bbf89dd5f0911d6472a454d9e",
"semantic_hash": ""
},
"assets/admin/components/dashboard/DoctorAppointmentsPanel.tsx": {
@@ -6123,5 +6123,10 @@
"mtime": 1784801102.5859623,
"ast_hash": "a04969acb8b76ebb4d3452cbf804f0a3",
"semantic_hash": ""
},
"migrations/Version20260723120122.php": {
"mtime": 1784808111.6212187,
"ast_hash": "5cbc25ed3869790cdf5fc594e21ea3d6",
"semantic_hash": ""
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260723120122 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add payment_method_uuid + reference to session_payments for split-payment details (POS device / transaction id).';
}
public function up(Schema $schema): void
{
// پرداختِ چندروشیِ نوبت: جزئیاتِ هر پرداخت (کارت‌خوان/حساب بانکیِ ثبت‌شده + شناسه تراکنش).
$this->addSql('ALTER TABLE session_payments ADD payment_method_uuid VARCHAR(36) DEFAULT NULL, ADD reference VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE session_payments DROP payment_method_uuid, DROP reference');
}
}
@@ -1004,6 +1004,8 @@ class AppointmentController extends BaseController
), 422);
}
// پرداختِ چندروشی (split): هر ردیف یک روش + مبلغ، و به‌اختیار جزئیاتِ روش
// (uuid کارت‌خوان/حساب بانکیِ ثبت‌شده + شناسه تراکنش).
$payments = [];
foreach ((array) ($data['payments'] ?? []) as $row) {
$method = trim((string) ($row['method'] ?? ''));
@@ -1014,7 +1016,14 @@ class AppointmentController extends BaseController
if ($amount <= 0) {
return $this->error(ErrorCodes::ERR_SESSION_PAYMENT_INVALID, ErrorCodes::message(ErrorCodes::ERR_SESSION_PAYMENT_INVALID), 422, 'amount_rials');
}
$payments[] = ['method' => $method, 'amount_rials' => $amount];
$methodUuid = trim((string) ($row['payment_method_uuid'] ?? ''));
$reference = trim((string) ($row['reference'] ?? ''));
$payments[] = [
'method' => $method,
'amount_rials' => $amount,
'payment_method_uuid' => $methodUuid !== '' ? mb_substr($methodUuid, 0, 36) : null,
'reference' => $reference !== '' ? mb_substr($reference, 0, 255) : null,
];
}
try {
@@ -60,7 +60,7 @@ class AppointmentConfirmationService
* مودالی ایستاده که مبلغ نشان داده و منتظر تأیید است؛ «قطعی شد ولی پول ثبت نشد»
* بدترین خروجیِ ممکن است.
*
* @param array<int, array{method: string, amount_rials: int}> $payments
* @param array<int, array{method: string, amount_rials: int, payment_method_uuid?: ?string, reference?: ?string}> $payments
* @return PatientSession|null null یعنی این tenant قابلیت پرونده را ندارد
* (فقط وقتی مجاز است که پرداختی هم ارسال نشده باشد)
*/
@@ -91,6 +91,8 @@ class AppointmentConfirmationService
$payment['amount_rials'],
null,
$actor,
$payment['payment_method_uuid'] ?? null,
$payment['reference'] ?? null,
);
}
+20 -6
View File
@@ -35,6 +35,14 @@ class SessionPayment
#[ORM\Column(name: 'amount_rials', type: 'integer')]
private int $amountRials;
/** uuid یک کارت‌خوان (Pos) یا حساب بانکی (BankAccount) ثبت‌شده — برای pos/card. */
#[ORM\Column(name: 'payment_method_uuid', type: 'string', length: 36, nullable: true)]
private ?string $paymentMethodUuid = null;
/** شناسه تراکنش / شماره پیگیری پرداخت (اختیاری). */
#[ORM\Column(name: 'reference', type: 'string', length: 255, nullable: true)]
private ?string $reference = null;
#[ORM\Column(name: 'paid_at', type: 'integer')]
private int $paidAt;
@@ -63,6 +71,8 @@ class SessionPayment
public function getSession(): PatientSession { return $this->session; }
public function getMethod(): string { return $this->method; }
public function getAmountRials(): int { return $this->amountRials; }
public function getPaymentMethodUuid(): ?string { return $this->paymentMethodUuid; }
public function getReference(): ?string { return $this->reference; }
public function getPaidAt(): int { return $this->paidAt; }
public function getCreatedBy(): ?User { return $this->createdBy; }
public function getCreatedByName(): ?string { return $this->createdByName; }
@@ -72,17 +82,21 @@ class SessionPayment
public function setCreatedByName(?string $n): self { $this->createdByName = $n; return $this; }
public function setMethod(string $m): self { $this->method = $m; return $this; }
public function setAmountRials(int $v): self { $this->amountRials = $v; return $this; }
public function setPaymentMethodUuid(?string $v): self { $this->paymentMethodUuid = $v; return $this; }
public function setReference(?string $v): self { $this->reference = $v; return $this; }
public function setPaidAt(int $v): self { $this->paidAt = $v; return $this; }
public function toArray(): array
{
return [
'uuid' => $this->uuid,
'method' => $this->method,
'amount_rials' => $this->amountRials,
'paid_at' => $this->paidAt,
'created_by_name' => $this->createdByName,
'created_at' => $this->createdAt,
'uuid' => $this->uuid,
'method' => $this->method,
'amount_rials' => $this->amountRials,
'payment_method_uuid' => $this->paymentMethodUuid,
'reference' => $this->reference,
'paid_at' => $this->paidAt,
'created_by_name' => $this->createdByName,
'created_at' => $this->createdAt,
];
}
}
+5 -1
View File
@@ -596,6 +596,8 @@ class PatientService
int $amountRials,
?int $paidAt = null,
?User $actor = null,
?string $paymentMethodUuid = null,
?string $reference = null,
): SessionPayment {
if (!in_array($method, SessionPayment::METHODS, true)) {
throw new AppException(ErrorCodes::ERR_SESSION_PAYMENT_INVALID, null, 422, 'method');
@@ -627,7 +629,9 @@ class PatientService
$payment = new SessionPayment($session, $method, $amountRials, $paidAt);
$payment->setCreatedBy($actor)
->setCreatedByName($this->walletService->resolveActorName($actor));
->setCreatedByName($this->walletService->resolveActorName($actor))
->setPaymentMethodUuid($paymentMethodUuid !== null && $paymentMethodUuid !== '' ? $paymentMethodUuid : null)
->setReference($reference !== null && $reference !== '' ? $reference : null);
$this->sessionPaymentRepo->save($payment);
$session->addPayment($payment);
@@ -158,6 +158,33 @@ class AppointmentConfirmFlowTest extends ApiTestCase
self::assertTrue($res['data']['session']['is_paid']);
}
public function testConfirmPersistsPerPaymentMethodDetails(): void
{
$doctor = $this->makeDoctor();
$appointment = $this->makeAppointment($doctor);
$res = $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $doctor->getUser(), [
'version' => $appointment->getVersion(),
'payments' => [
['method' => 'pos', 'amount_rials' => 3_000_000, 'payment_method_uuid' => 'pos-uuid-1', 'reference' => 'TRX-42'],
['method' => 'cash', 'amount_rials' => 2_000_000],
],
]);
self::assertSame(200, $this->responseCode());
$session = $this->em->getRepository(PatientSession::class)->findOneBy(['uuid' => $res['data']['session']['uuid']]);
$payments = $this->em->getRepository(\App\Patient\Entity\SessionPayment::class)->findBy(['session' => $session]);
self::assertCount(2, $payments);
$pos = array_values(array_filter($payments, fn($p) => $p->getMethod() === 'pos'))[0];
$cash = array_values(array_filter($payments, fn($p) => $p->getMethod() === 'cash'))[0];
self::assertSame('pos-uuid-1', $pos->getPaymentMethodUuid());
self::assertSame('TRX-42', $pos->getReference());
self::assertNull($cash->getPaymentMethodUuid(), 'روش نقدی جزئیاتِ روش ندارد');
self::assertNull($cash->getReference());
}
// ── پرونده: استفادهٔ مجدد یا ساخت ────────────────────────────────────────
public function testConfirmReusesExistingRecordOfSameDoctor(): void