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:
@@ -163,6 +163,36 @@ class DoctorSecretaryRepository extends ServiceEntityRepository
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* رابطههای فعالی که سهم درآمد نوبت آنلاین برایشان روشن است — برای کلینیک همهٔ
|
||||
* منشیهای همان کلینیک، برای مطب شخصی منشیهای همان پزشک.
|
||||
*
|
||||
* @return DoctorSecretary[]
|
||||
*/
|
||||
public function findOnlineShareRows(Doctor $doctor, ?Clinic $clinic): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('s')
|
||||
->addSelect('sec')
|
||||
->join('s.secretary', 'sec')
|
||||
->where('s.active = true')
|
||||
->andWhere('s.onlineShareEnabled = true')
|
||||
->andWhere('s.onlineSharePercent > 0');
|
||||
|
||||
if ($clinic !== null) {
|
||||
$qb->andWhere('s.clinic = :clinic')
|
||||
->andWhere('s.ownerType = :type')
|
||||
->setParameter('clinic', $clinic)
|
||||
->setParameter('type', DoctorSecretary::OWNER_CLINIC);
|
||||
} else {
|
||||
$qb->andWhere('s.doctor = :doctor')
|
||||
->andWhere('s.ownerType = :type')
|
||||
->setParameter('doctor', $doctor)
|
||||
->setParameter('type', DoctorSecretary::OWNER_DOCTOR);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function save(DoctorSecretary $entity, bool $flush = true): void
|
||||
{
|
||||
$this->getEntityManager()->persist($entity);
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Secretary\Repository;
|
||||
|
||||
use App\Auth\Entity\User;
|
||||
use App\Secretary\Entity\SecretaryEarning;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class SecretaryEarningRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, SecretaryEarning::class);
|
||||
}
|
||||
|
||||
public function save(SecretaryEarning $earning, bool $flush = true): void
|
||||
{
|
||||
$this->getEntityManager()->persist($earning);
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/** مجموع سهم یک منشی در یک بازه؛ بدون بازه = کل. */
|
||||
public function sumFor(User $secretary, ?int $from = null, ?int $to = null): int
|
||||
{
|
||||
$qb = $this->createQueryBuilder('e')
|
||||
->select('COALESCE(SUM(e.shareRials), 0)')
|
||||
->where('e.secretary = :user')
|
||||
->setParameter('user', $secretary);
|
||||
|
||||
if ($from !== null) {
|
||||
$qb->andWhere('e.createdAt >= :from')->setParameter('from', $from);
|
||||
}
|
||||
if ($to !== null) {
|
||||
$qb->andWhere('e.createdAt <= :to')->setParameter('to', $to);
|
||||
}
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countFor(User $secretary): int
|
||||
{
|
||||
return (int) $this->createQueryBuilder('e')
|
||||
->select('COUNT(e.id)')
|
||||
->where('e.secretary = :user')
|
||||
->setParameter('user', $secretary)
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* گزارش سطر-به-سطر برای پنل منشی: هر ردیف با تفکیک مالیِ همان پرداخت و نوبت.
|
||||
*
|
||||
* @return array{items: list<array<string, mixed>>, total: int}
|
||||
*/
|
||||
public function reportFor(User $secretary, int $page, int $limit, ?int $from = null, ?int $to = null): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('e')
|
||||
->select(
|
||||
'e.uuid, e.sharePercent, e.shareRials, e.createdAt,
|
||||
b.grossRials, b.smsFeeRials, b.taxRials, b.netAfterTaxRials,
|
||||
a.uuid AS appointment_uuid, doc.name AS doctor_name'
|
||||
)
|
||||
->join('e.breakdown', 'b')
|
||||
->join('b.payment', 'p')
|
||||
->leftJoin('p.appointment', 'a')
|
||||
->leftJoin('a.doctor', 'doc')
|
||||
->where('e.secretary = :user')
|
||||
->setParameter('user', $secretary)
|
||||
->orderBy('e.createdAt', 'DESC');
|
||||
|
||||
if ($from !== null) {
|
||||
$qb->andWhere('e.createdAt >= :from')->setParameter('from', $from);
|
||||
}
|
||||
if ($to !== null) {
|
||||
$qb->andWhere('e.createdAt <= :to')->setParameter('to', $to);
|
||||
}
|
||||
|
||||
$total = (int) (clone $qb)->select('COUNT(e.id)')->resetDQLPart('orderBy')
|
||||
->getQuery()->getSingleScalarResult();
|
||||
|
||||
$rows = $qb->setFirstResult(($page - 1) * $limit)->setMaxResults($limit)
|
||||
->getQuery()->getArrayResult();
|
||||
|
||||
$items = array_map(static fn(array $r) => [
|
||||
'uuid' => $r['uuid'],
|
||||
'appointment_uuid' => $r['appointment_uuid'] ?? null,
|
||||
'doctor_name' => $r['doctor_name'] ?? null,
|
||||
'gross_rials' => (int) $r['grossRials'],
|
||||
'sms_fee_rials' => (int) $r['smsFeeRials'],
|
||||
'tax_rials' => (int) $r['taxRials'],
|
||||
'net_after_tax_rials' => (int) $r['netAfterTaxRials'],
|
||||
'share_percent' => (float) $r['sharePercent'],
|
||||
'share_rials' => (int) $r['shareRials'],
|
||||
'created_at' => (int) $r['createdAt'],
|
||||
], $rows);
|
||||
|
||||
return ['items' => $items, 'total' => $total];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user