172 lines
5.0 KiB
Markdown
172 lines
5.0 KiB
Markdown
# معماری — تسک ۱۴: پنل پیامکی
|
|
|
|
## ساختار فایلها
|
|
|
|
```
|
|
src/Sms/
|
|
├── Controller/
|
|
│ ├── SmsController.php ← موجود (تغییر نمیکند)
|
|
│ └── SmsWalletController.php ← جدید
|
|
├── Entity/
|
|
│ ├── SmsLog.php ← موجود
|
|
│ ├── SmsTemplate.php ← موجود
|
|
│ ├── SmsWallet.php ← جدید
|
|
│ └── SmsWalletTransaction.php ← جدید
|
|
│ └── SmsSettings.php ← جدید
|
|
├── Repository/
|
|
│ └── SmsWalletRepository.php ← جدید
|
|
└── Service/
|
|
├── SmsService.php ← موجود — باید کسر wallet اضافه شود
|
|
└── SmsWalletService.php ← جدید
|
|
```
|
|
|
|
**فایلهایی که تغییر میکنند:**
|
|
- `src/Payment/Entity/Payment.php` — اضافه کردن `const TYPE_SMS_WALLET = 'sms_wallet'`
|
|
- `src/Payment/Controller/PaymentController.php` — callback برای `sms_wallet` type، شارژ wallet
|
|
- `src/Sms/Service/SmsService.php` — قبل از ارسال، balance بررسی و کسر شود
|
|
|
|
## Entity: SmsWallet
|
|
|
|
```php
|
|
#[ORM\Entity]
|
|
#[ORM\Table(name: 'sms_wallets')]
|
|
#[ORM\UniqueConstraint(name: 'uniq_sms_wallet_entity', columns: ['entity_type', 'entity_id'])]
|
|
class SmsWallet
|
|
{
|
|
#[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')]
|
|
private ?int $id = null;
|
|
|
|
#[ORM\Column(type: 'string', length: 10)]
|
|
private string $entityType;
|
|
|
|
#[ORM\Column(type: 'integer')]
|
|
private int $entityId;
|
|
|
|
#[ORM\Column(type: 'integer')]
|
|
private int $balanceRials = 0;
|
|
|
|
#[ORM\Column(type: 'integer')]
|
|
private int $createdAt;
|
|
|
|
#[ORM\Column(type: 'integer')]
|
|
private int $updatedAt;
|
|
}
|
|
```
|
|
|
|
## Entity: SmsWalletTransaction
|
|
|
|
```php
|
|
#[ORM\Entity]
|
|
#[ORM\Table(name: 'sms_wallet_transactions')]
|
|
#[ORM\Index(columns: ['sms_wallet_id', 'created_at'], name: 'idx_sms_wallet_tx')]
|
|
class SmsWalletTransaction
|
|
{
|
|
public const TYPE_CREDIT = 'credit';
|
|
public const TYPE_DEBIT = 'debit';
|
|
|
|
#[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: SmsWallet::class)]
|
|
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
|
private SmsWallet $wallet;
|
|
|
|
#[ORM\Column(type: 'string', length: 10)]
|
|
private string $type; // 'credit' | 'debit'
|
|
|
|
#[ORM\Column(type: 'integer')]
|
|
private int $amountRials;
|
|
|
|
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
|
private ?string $description = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: \App\Payment\Entity\Payment::class)]
|
|
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
|
|
private ?\App\Payment\Entity\Payment $payment = null;
|
|
|
|
#[ORM\Column(type: 'integer')]
|
|
private int $createdAt;
|
|
}
|
|
```
|
|
|
|
## Entity: SmsSettings
|
|
|
|
```php
|
|
#[ORM\Entity]
|
|
#[ORM\Table(name: 'sms_settings')]
|
|
#[ORM\UniqueConstraint(name: 'uniq_sms_settings_entity', columns: ['entity_type', 'entity_id'])]
|
|
class SmsSettings
|
|
{
|
|
#[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')]
|
|
private ?int $id = null;
|
|
|
|
#[ORM\Column(type: 'string', length: 10)]
|
|
private string $entityType;
|
|
|
|
#[ORM\Column(type: 'integer')]
|
|
private int $entityId;
|
|
|
|
#[ORM\Column(type: 'boolean')]
|
|
private bool $reminderEnabled = false;
|
|
|
|
#[ORM\Column(type: 'smallint')]
|
|
private int $reminderHoursBefore = 2;
|
|
|
|
#[ORM\Column(type: 'boolean')]
|
|
private bool $postVisitEnabled = false;
|
|
|
|
#[ORM\Column(type: 'text', nullable: true)]
|
|
private ?string $postVisitText = null;
|
|
|
|
#[ORM\Column(type: 'integer')]
|
|
private int $updatedAt;
|
|
}
|
|
```
|
|
|
|
## SmsWalletService
|
|
|
|
```php
|
|
class SmsWalletService
|
|
{
|
|
public function getOrCreate(string $entityType, int $entityId): SmsWallet
|
|
{
|
|
// findOneBy([entityType, entityId]) یا ایجاد جدید با balance=0
|
|
}
|
|
|
|
public function charge(SmsWallet $wallet, int $amountRials, Payment $payment): void
|
|
{
|
|
$wallet->setBalanceRials($wallet->getBalanceRials() + $amountRials);
|
|
// ثبت SmsWalletTransaction با type=credit
|
|
}
|
|
|
|
public function deduct(SmsWallet $wallet, int $amountRials, string $description): bool
|
|
{
|
|
if ($wallet->getBalanceRials() < $amountRials) return false;
|
|
$wallet->setBalanceRials($wallet->getBalanceRials() - $amountRials);
|
|
// ثبت SmsWalletTransaction با type=debit
|
|
return true;
|
|
}
|
|
}
|
|
```
|
|
|
|
## تغییر SmsService::send()
|
|
|
|
```php
|
|
public function send(string $phone, string $message, string $entityType, int $entityId): bool
|
|
{
|
|
$priceRials = (int) $this->siteConfigRepo->getValue('sms_price_rials', '0');
|
|
$wallet = $this->walletService->getOrCreate($entityType, $entityId);
|
|
|
|
if (!$this->walletService->deduct($wallet, $priceRials, 'ارسال پیامک')) {
|
|
// لاگ: ارسال نشد — موجودی ناکافی
|
|
return false;
|
|
}
|
|
|
|
// ارسال از طریق Provider موجود ...
|
|
return true;
|
|
}
|
|
```
|