Sfoglia il codice sorgente

feat: add notification for payment credited in FranchisorReceivableService and ProcessAsaasWebhookJob

ebagabee 1 settimana fa
parent
commit
8c799ce2d0

+ 2 - 0
app/Enums/NotificationType.php

@@ -18,6 +18,7 @@ enum NotificationType: string
     case KANBAN_STATUS_CHANGED = 'kanban_status_changed';
     case TBR_MONTH_CLOSED = 'tbr_month_closed';
     case ACCOUNT_PAYABLE_CREATED = 'account_payable_created';
+    case PAYMENT_CREDITED = 'payment_credited';
 
     public function label(): string
     {
@@ -29,6 +30,7 @@ public function label(): string
             self::KANBAN_STATUS_CHANGED => 'Status da atividade atualizado',
             self::TBR_MONTH_CLOSED => 'Fechamento do mês',
             self::ACCOUNT_PAYABLE_CREATED => 'Nova conta a pagar',
+            self::PAYMENT_CREDITED => 'Pagamento creditado',
         };
     }
 }

+ 45 - 21
app/Jobs/ProcessAsaasWebhookJob.php

@@ -2,8 +2,10 @@
 
 namespace App\Jobs;
 
+use App\Enums\NotificationType;
 use App\Enums\ReceivableStatus;
 use App\Models\FranchiseeAccountReceive;
+use App\Services\NotificationService;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
@@ -16,11 +18,11 @@ class ProcessAsaasWebhookJob implements ShouldQueue
     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
     private const EVENT_STATUS_MAP = [
-        'PAYMENT_RECEIVED'  => ReceivableStatus::PAID,
+        'PAYMENT_RECEIVED' => ReceivableStatus::PAID,
         'PAYMENT_CONFIRMED' => ReceivableStatus::PAID,
-        'PAYMENT_OVERDUE'   => ReceivableStatus::OVERDUE,
-        'PAYMENT_DELETED'   => ReceivableStatus::CANCELLED,
-        'PAYMENT_REFUNDED'  => ReceivableStatus::CANCELLED,
+        'PAYMENT_OVERDUE' => ReceivableStatus::OVERDUE,
+        'PAYMENT_DELETED' => ReceivableStatus::CANCELLED,
+        'PAYMENT_REFUNDED' => ReceivableStatus::CANCELLED,
     ];
 
     public array $payload;
@@ -30,29 +32,32 @@ public function __construct(array $payload)
         $this->payload = $payload;
     }
 
-    public function handle(): void
+    public function handle(NotificationService $notifications): void
     {
-        $event   = $this->payload['event'] ?? null;
+        $event = $this->payload['event'] ?? null;
         $payment = $this->payload['payment'] ?? [];
 
-        if (!$event || empty($payment)) {
+        if (! $event || empty($payment)) {
             Log::warning('Asaas Webhook Job: payload incompleto', $this->payload);
+
             return;
         }
 
         $newStatus = self::EVENT_STATUS_MAP[$event] ?? null;
 
-        if (!$newStatus) {
+        if (! $newStatus) {
             Log::info("Asaas Webhook Job: evento '{$event}' ignorado (não mapeado).");
+
             return;
         }
 
         $externalReference = $payment['externalReference'] ?? null;
-        $asaasId           = $payment['id'] ?? null;
+        $asaasId = $payment['id'] ?? null;
 
         // Cobrança de aluno (parcela) cobrada pela conta Asaas PRÓPRIA da unidade.
         if ($externalReference && str_starts_with($externalReference, 'STU_')) {
             $this->handleStudentInstallment($externalReference, $payment, $event, $newStatus);
+
             return;
         }
 
@@ -74,7 +79,7 @@ public function handle(): void
         }
 
         // Fallback por asaas_id caso externalReference venha nulo
-        if (!$receive && $asaasId) {
+        if (! $receive && $asaasId) {
             $receive = FranchiseeAccountReceive::where('asaas_id', $asaasId)->first();
 
             if ($receive) {
@@ -82,12 +87,13 @@ public function handle(): void
             }
         }
 
-        if (!$receive) {
-            Log::warning("Asaas Webhook Job: registro não encontrado", [
+        if (! $receive) {
+            Log::warning('Asaas Webhook Job: registro não encontrado', [
                 'externalReference' => $externalReference,
                 'asaas_id' => $asaasId,
                 'event' => $event,
             ]);
+
             return;
         }
 
@@ -98,17 +104,18 @@ public function handle(): void
         // Idempotência
         if ($currentStatus === ReceivableStatus::PAID->value && $newStatusValue === ReceivableStatus::PAID->value) {
             Log::info("Asaas Webhook Job: registro {$type} #{$receive->id} já está pago. Ignorando duplicata.");
+
             return;
         }
 
         $updateData = [
-            'status'      => $newStatusValue,
+            'status' => $newStatusValue,
             'asaas_status' => $payment['status'] ?? $event,
         ];
 
         if ($newStatusValue === ReceivableStatus::PAID->value) {
             $updateData['payment_date'] = $payment['paymentDate'] ?? $payment['confirmedDate'] ?? now();
-            $updateData['paid_value']   = $payment['value'] ?? $receive->value;
+            $updateData['paid_value'] = $payment['value'] ?? $receive->value;
         }
 
         $receive->update($updateData);
@@ -123,13 +130,28 @@ public function handle(): void
 
             if ($payable) {
                 $payable->update([
-                    'status'       => 'paid',
-                    'paid_value'   => $payable->value,
+                    'status' => 'paid',
+                    'paid_value' => $payable->value,
                     'payment_date' => $updateData['payment_date'] ?? now(),
                 ]);
 
                 Log::info("Asaas Webhook Job: Conta a Pagar da unidade #{$payable->id} baixada automaticamente (TBR pago).");
             }
+
+            if ($receive->unit_id) {
+                $notifications->dispatch([
+                    'origin_table' => 'franchisee_account_receives',
+                    'origin_id' => $receive->id,
+                    'unit_id' => $receive->unit_id,
+                    'type' => NotificationType::PAYMENT_CREDITED->value,
+                    'title' => 'Pagamento confirmado',
+                    'message' => "Sua cobrança {$receive->history} foi devidamente creditada e consta como paga.",
+                    'url' => '/financial/accounts-payable',
+                    'action_text' => 'Ver Contas a Pagar',
+                    'priority' => 'normal',
+                    'recipient_user_ids' => $notifications->unitUserIds($receive->unit_id),
+                ]);
+            }
         }
 
         Log::info("Asaas Webhook Job: {$type} #{$receive->id} atualizado para '{$newStatusValue}'", [
@@ -147,18 +169,19 @@ private function handleStudentInstallment(string $externalReference, array $paym
 
         $installment = \App\Models\StudentContractInstallment::find($id);
 
-        if (!$installment) {
+        if (! $installment) {
             Log::warning("Asaas Webhook Job: parcela {$externalReference} não encontrada.", [
                 'event' => $event,
                 'asaas_id' => $payment['id'] ?? null,
             ]);
+
             return;
         }
 
         // Mapeia o status do recebível para o vocabulário das parcelas.
         $statusMap = [
-            ReceivableStatus::PAID->value      => 'paid',
-            ReceivableStatus::OVERDUE->value   => 'overdue',
+            ReceivableStatus::PAID->value => 'paid',
+            ReceivableStatus::OVERDUE->value => 'overdue',
             ReceivableStatus::CANCELLED->value => 'cancelled',
         ];
         $installmentStatus = $statusMap[$newStatus->value] ?? $installment->status;
@@ -166,17 +189,18 @@ private function handleStudentInstallment(string $externalReference, array $paym
         // Idempotência: já pago e continua pago → ignora duplicata.
         if ($installment->status === 'paid' && $installmentStatus === 'paid') {
             Log::info("Asaas Webhook Job: parcela #{$installment->id} já paga. Ignorando duplicata.");
+
             return;
         }
 
         $update = [
-            'status'       => $installmentStatus,
+            'status' => $installmentStatus,
             'asaas_status' => $payment['status'] ?? $event,
         ];
 
         if ($installmentStatus === 'paid') {
             $update['payment_date'] = $payment['paymentDate'] ?? $payment['confirmedDate'] ?? now();
-            $update['paid_value']   = $payment['value'] ?? $installment->value;
+            $update['paid_value'] = $payment['value'] ?? $installment->value;
         }
 
         $installment->update($update);

+ 15 - 0
app/Services/FranchisorReceivableService.php

@@ -88,6 +88,21 @@ public function settle(FranchiseeAccountReceive $receivable, array $data): Franc
                     'payment_date' => $data['payment_date'] ?? Carbon::today()->format('Y-m-d'),
                 ]);
 
+            if ($receivable->unit_id) {
+                $this->notifications->dispatch([
+                    'origin_table' => 'franchisee_account_receives',
+                    'origin_id' => $receivable->id,
+                    'unit_id' => $receivable->unit_id,
+                    'type' => NotificationType::PAYMENT_CREDITED->value,
+                    'title' => 'Pagamento confirmado',
+                    'message' => "Sua cobrança {$receivable->history} foi devidamente creditada e consta como paga.",
+                    'url' => '/financial/accounts-payable',
+                    'action_text' => 'Ver Contas a Pagar',
+                    'priority' => 'normal',
+                    'recipient_user_ids' => $this->notifications->unitUserIds($receivable->unit_id),
+                ]);
+            }
+
             return $receivable->fresh(['unit', 'details', 'planAccount']);
         });
     }