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:
@@ -73,6 +73,17 @@ class DoctorSecretary
|
||||
#[ORM\Column(type: 'boolean')]
|
||||
private bool $active = true;
|
||||
|
||||
/**
|
||||
* سهم منشی از نوبتهای آنلاینِ همین پزشک/کلینیک فعال است؟ تنظیم per-relation است:
|
||||
* یک منشی میتواند برای یک پزشک سهم داشته باشد و برای دیگری نه.
|
||||
*/
|
||||
#[ORM\Column(name: 'online_share_enabled', type: 'boolean', options: ['default' => false])]
|
||||
private bool $onlineShareEnabled = false;
|
||||
|
||||
/** درصد سهم منشی از «خالصِ پس از مالیات» نوبت آنلاین. */
|
||||
#[ORM\Column(name: 'online_share_percent', type: 'decimal', precision: 5, scale: 2, options: ['default' => '0.00'])]
|
||||
private string $onlineSharePercent = '0.00';
|
||||
|
||||
#[ORM\Column(name: 'created_at', type: 'integer')]
|
||||
private int $createdAt;
|
||||
|
||||
@@ -101,6 +112,13 @@ class DoctorSecretary
|
||||
public function getNationalCode(): ?string { return $this->nationalCode; }
|
||||
public function getAddress(): ?string { return $this->address; }
|
||||
public function isActive(): bool { return $this->active; }
|
||||
public function isOnlineShareEnabled(): bool { return $this->onlineShareEnabled; }
|
||||
public function getOnlineSharePercent(): float { return (float) $this->onlineSharePercent; }
|
||||
/** سهم مؤثر: درصد فقط وقتی معنا دارد که رابطه فعال و سهم روشن باشد. */
|
||||
public function effectiveOnlineSharePercent(): float
|
||||
{
|
||||
return ($this->active && $this->onlineShareEnabled) ? (float) $this->onlineSharePercent : 0.0;
|
||||
}
|
||||
public function getCreatedAt(): int { return $this->createdAt; }
|
||||
public function getUpdatedAt(): int { return $this->updatedAt; }
|
||||
|
||||
@@ -108,6 +126,8 @@ class DoctorSecretary
|
||||
public function setPermissions(array $v): self { $this->permissions = $v; $this->touch(); return $this; }
|
||||
public function setNationalCode(?string $v): self { $this->nationalCode = $v; $this->touch(); return $this; }
|
||||
public function setAddress(?string $v): self { $this->address = $v; $this->touch(); return $this; }
|
||||
public function setOnlineShareEnabled(bool $v): self { $this->onlineShareEnabled = $v; $this->touch(); return $this; }
|
||||
public function setOnlineSharePercent(float $v): self { $this->onlineSharePercent = (string) $v; $this->touch(); return $this; }
|
||||
|
||||
/** Deep merge: only provided resources/actions are updated */
|
||||
public function mergePermissions(array $patch): void
|
||||
@@ -144,6 +164,8 @@ class DoctorSecretary
|
||||
'owner_type' => $this->ownerType,
|
||||
'clinic_uuid' => $this->clinic?->getUuid(),
|
||||
'is_active' => $this->active,
|
||||
'online_share_enabled' => $this->onlineShareEnabled,
|
||||
'online_share_percent' => (float) $this->onlineSharePercent,
|
||||
'national_code' => $this->nationalCode,
|
||||
'address' => $this->address,
|
||||
'permissions' => $this->getPermissions()['resources'] ?? $this->getPermissions(),
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Secretary\Entity;
|
||||
|
||||
use App\Auth\Entity\User;
|
||||
use App\Secretary\Repository\SecretaryEarningRepository;
|
||||
use App\Settlement\Entity\FinancialBreakdown;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
/**
|
||||
* سهم یک منشی از یک نوبت آنلاین.
|
||||
*
|
||||
* جدول جداست (نه JSON روی FinancialBreakdown) چون گزارش پنل منشی باید per-user
|
||||
* فیلتر و جمعبندی شود؛ یک پرداخت میتواند چند منشیِ سهمبر داشته باشد.
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: SecretaryEarningRepository::class)]
|
||||
#[ORM\Table(name: 'secretary_earnings')]
|
||||
#[ORM\Index(columns: ['secretary_user_id', 'created_at'], name: 'idx_secretary_earnings_user_time')]
|
||||
class SecretaryEarning
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 36, unique: true)]
|
||||
private string $uuid;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: FinancialBreakdown::class)]
|
||||
#[ORM\JoinColumn(name: 'breakdown_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
|
||||
private FinancialBreakdown $breakdown;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(name: 'secretary_user_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
|
||||
private User $secretary;
|
||||
|
||||
/** رابطهٔ منشی–پزشک/کلینیکی که سهم از آن آمده (snapshot، برای ردگیری). */
|
||||
#[ORM\Column(name: 'relation_uuid', type: 'string', length: 36)]
|
||||
private string $relationUuid;
|
||||
|
||||
#[ORM\Column(name: 'share_percent', type: 'decimal', precision: 5, scale: 2)]
|
||||
private string $sharePercent;
|
||||
|
||||
#[ORM\Column(name: 'share_rials', type: 'integer')]
|
||||
private int $shareRials;
|
||||
|
||||
#[ORM\Column(name: 'created_at', type: 'integer')]
|
||||
private int $createdAt;
|
||||
|
||||
public function __construct(
|
||||
FinancialBreakdown $breakdown,
|
||||
User $secretary,
|
||||
string $relationUuid,
|
||||
float $sharePercent,
|
||||
int $shareRials,
|
||||
) {
|
||||
$this->uuid = Uuid::v4()->toRfc4122();
|
||||
$this->breakdown = $breakdown;
|
||||
$this->secretary = $secretary;
|
||||
$this->relationUuid = $relationUuid;
|
||||
$this->sharePercent = number_format($sharePercent, 2, '.', '');
|
||||
$this->shareRials = $shareRials;
|
||||
$this->createdAt = time();
|
||||
}
|
||||
|
||||
public function getId(): ?int { return $this->id; }
|
||||
public function getUuid(): string { return $this->uuid; }
|
||||
public function getBreakdown(): FinancialBreakdown { return $this->breakdown; }
|
||||
public function getSecretary(): User { return $this->secretary; }
|
||||
public function getRelationUuid(): string { return $this->relationUuid; }
|
||||
public function getSharePercent(): float { return (float) $this->sharePercent; }
|
||||
public function getShareRials(): int { return $this->shareRials; }
|
||||
public function getCreatedAt(): int { return $this->createdAt; }
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'uuid' => $this->uuid,
|
||||
'relation_uuid' => $this->relationUuid,
|
||||
'share_percent' => (float) $this->sharePercent,
|
||||
'share_rials' => $this->shareRials,
|
||||
'created_at' => $this->createdAt,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user