feat: Add online share functionality for secretaries

- Introduced `online_share_enabled` and `online_share_percent` fields in the `doctor_secretaries` table to manage secretary shares from online appointments.
- Added `bank_account` field in the `profiles` table to store user-level IBANs for settlements.
- Created `secretary_earnings` table to track earnings per secretary from online appointments, including a foreign key relationship with `financial_breakdowns`.
- Implemented `SecretaryEarning` entity and repository for managing secretary earnings.
- Developed `SecretaryShareResolver` service to determine which secretaries earn from online payments.
- Added `UserIbanResolver` service to handle user IBAN retrieval and management.
- Created `HasIbansTrait` for entities to manage IBANs in a JSON format.
- Implemented tests for secretary earnings and API endpoints for managing secretary shares and IBANs.
This commit is contained in:
hamed
2026-07-25 18:34:18 +03:30
parent 73a608c3dd
commit 8d2b0d908a
33 changed files with 2564 additions and 76 deletions
+11 -2
View File
@@ -1,11 +1,12 @@
import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { TrashIcon, PencilIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { TrashIcon, PencilIcon, MagnifyingGlassIcon, CurrencyDollarIcon } from '@heroicons/react/24/outline';
import { Link } from 'react-router-dom';
import { toast } from 'sonner';
import { api } from '../lib/api';
import type { ApiResponse, PaginatedResponse } from '../lib/api';
import type { Secretary, SecretaryPermissions } from '../types';
import { formatDate, maskMobile } from '../lib/utils';
import { formatDate, formatNumber, maskMobile } from '../lib/utils';
import DataTable, { Column } from '../components/ui/DataTable';
import { ActiveBadge } from '../components/ui/StatusBadge';
import Pagination from '../components/ui/Pagination';
@@ -274,6 +275,11 @@ export default function SecretariesPage() {
{ key: 'mobile_number', header: 'موبایل', render: (s) => <span dir="ltr">{maskMobile(s.mobile_number)}</span> },
{ key: 'doctor_name', header: 'پزشک', render: (s) => s.doctor_name },
{ key: 'is_active', header: 'وضعیت', render: (s) => <ActiveBadge active={s.is_active} /> },
{ key: 'online_share_percent', header: 'سهم آنلاین', render: (s) => (
s.online_share_enabled
? <span style={{ fontSize: 12.5, color: 'var(--primary)', fontWeight: 700 }}>{formatNumber(s.online_share_percent ?? 0)}٪</span>
: <span className="muted"></span>
) },
{ key: 'created_at', header: 'تاریخ ثبت', render: (s) => formatDate(s.created_at) },
];
@@ -309,6 +315,9 @@ export default function SecretariesPage() {
emptyMessage="هیچ منشی‌ای یافت نشد"
actions={(sec) => (
<>
<Link to={`/admin/secretaries/${sec.uuid}`} className="mini-btn" title="جزئیات و سهم درآمد">
<CurrencyDollarIcon style={{ width: 15, height: 15 }} />
</Link>
<button onClick={() => openEdit(sec)} className="mini-btn" title="ویرایش دسترسی‌ها">
<PencilIcon style={{ width: 15, height: 15 }} />
</button>