Bladeren bron

feat: adiciona funcionalidade de criacao de contas a receber do aluno

ebagabee 2 weken geleden
bovenliggende
commit
fac716ade5

+ 4 - 3
app/Http/Requests/StudentContractRequest.php

@@ -23,11 +23,12 @@ public function rules(): array
             'second_end_time'          => 'sometimes|nullable|date_format:H:i',
             'due_day'                  => 'sometimes|nullable|integer|min:1|max:31',
             'tax_register'             => 'sometimes|nullable|numeric|min:0',
-            'down_payment'             => 'sometimes|nullable|numeric|min:0',
             'installments'             => 'sometimes|nullable|integer|min:1|max:12',
+            'enrollment_due_date'      => 'sometimes|nullable|date_format:Y-m-d',
+            'package_value'            => 'sometimes|nullable|numeric|min:0',
+            'package_installments'     => 'sometimes|nullable|integer|min:1|max:13',
+            'package_due_date'         => 'sometimes|nullable|date_format:Y-m-d',
             'early_payment_discount'   => 'sometimes|nullable|numeric|min:0|max:100',
-            'material_value'           => 'sometimes|nullable|numeric|min:0',
-            'material_installments'    => 'sometimes|nullable|integer|min:1|max:12',
             'interest_rate'            => 'sometimes|nullable|numeric|min:0',
             'payment_method'           => 'sometimes|nullable|string|in:pix,credit_card,debit_card',
             'fine_cancelled'           => 'sometimes|nullable|numeric|min:0|max:100',

+ 8 - 3
app/Http/Resources/StudentContractResource.php

@@ -30,11 +30,16 @@ public function toArray(Request $request): array
             'second_end_time'         => $this->second_end_time,
             'due_day'                 => $this->recurring_day,
             'tax_register'            => $this->tax_register,
-            'down_payment'            => $this->down_payment,
             'installments'            => $this->installments,
+            'enrollment_due_date'     => $this->enrollment_due_date
+                                            ? Carbon::parse($this->enrollment_due_date)->format('d/m/Y')
+                                            : null,
+            'package_value'           => $this->package_value,
+            'package_installments'    => $this->package_installments,
+            'package_due_date'        => $this->package_due_date
+                                            ? Carbon::parse($this->package_due_date)->format('d/m/Y')
+                                            : null,
             'early_payment_discount'  => $this->early_payment_discount,
-            'material_value'          => $this->material_value,
-            'material_installments'   => $this->material_installments,
             'interest_rate'           => $this->interest_rate,
             'payment_method'          => $this->payment_method,
             'fine_cancelled'          => $this->fine_cancelled,

+ 11 - 5
app/Models/StudentContract.php

@@ -66,11 +66,12 @@ class StudentContract extends Model
     protected $guarded = ['id'];
 
     protected $casts = [
-        'signature_date'  => 'date:Y-m-d',
-        'end_date'        => 'date:Y-m-d',
-        'due_date'        => 'date:Y-m-d',
-        'created_at'      => 'datetime',
-        'updated_at'      => 'datetime',
+        'signature_date'      => 'date:Y-m-d',
+        'end_date'            => 'date:Y-m-d',
+        'enrollment_due_date' => 'date:Y-m-d',
+        'package_due_date'    => 'date:Y-m-d',
+        'created_at'          => 'datetime',
+        'updated_at'          => 'datetime',
     ];
 
     public function student(): BelongsTo
@@ -92,4 +93,9 @@ public function medias(): HasMany
     {
         return $this->hasMany(StudentMedia::class);
     }
+
+    public function installments(): HasMany
+    {
+        return $this->hasMany(StudentContractInstallment::class);
+    }
 }

+ 70 - 0
app/Models/StudentContractInstallment.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+/**
+ * @property int    $id
+ * @property int    $student_contract_id
+ * @property int    $unit_id
+ * @property int    $student_id
+ * @property string $type                  enrollment | package
+ * @property string $history               REF. MATRÍCULA | REF. PACOTE
+ * @property int    $installment_number
+ * @property int    $total_installments
+ * @property float  $value
+ * @property float  $paid_value
+ * @property float  $discount
+ * @property float  $fine
+ * @property string $due_date
+ * @property string|null $payment_date
+ * @property string|null $cancelled_at
+ * @property string|null $cancellation_reason
+ * @property string|null $asaas_id
+ * @property string $status               pending | paid | cancelled
+ */
+class StudentContractInstallment extends Model
+{
+    use HasFactory, SoftDeletes;
+
+    protected $table = 'student_contract_installments';
+
+    protected $guarded = ['id'];
+
+    protected $casts = [
+        'value'              => 'float',
+        'paid_value'         => 'float',
+        'discount'           => 'float',
+        'fine'               => 'float',
+        'due_date'           => 'date:Y-m-d',
+        'payment_date'       => 'date:Y-m-d',
+        'cancelled_at'       => 'datetime',
+        'created_at'         => 'datetime',
+        'updated_at'         => 'datetime',
+    ];
+
+    /** Retorna a ordem formatada, ex: "1/12" */
+    public function getOrderAttribute(): string
+    {
+        return "{$this->installment_number}/{$this->total_installments}";
+    }
+
+    public function studentContract(): BelongsTo
+    {
+        return $this->belongsTo(StudentContract::class);
+    }
+
+    public function unit(): BelongsTo
+    {
+        return $this->belongsTo(Unit::class);
+    }
+
+    public function student(): BelongsTo
+    {
+        return $this->belongsTo(Student::class);
+    }
+}

+ 105 - 1
app/Services/StudentContractService.php

@@ -3,7 +3,9 @@
 namespace App\Services;
 
 use App\Models\StudentContract;
+use App\Models\StudentContractInstallment;
 use App\Models\StudentMedia;
+use Carbon\Carbon;
 use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Support\Facades\Storage;
 
@@ -50,7 +52,109 @@ public function create(array $data): StudentContract
         }
         unset($data['due_day']);
 
-        return StudentContract::create($data);
+        $contract = StudentContract::create($data);
+
+        $this->generateInstallments($contract);
+
+        return $contract;
+    }
+
+    private function generateInstallments(StudentContract $contract): void
+    {
+        $recurringDay = $contract->recurring_day ?? 1;
+        $rows = [];
+        $now  = now();
+
+        // --- Matrícula ---
+        if ($contract->tax_register && $contract->installments && $contract->enrollment_due_date) {
+            $value = round($contract->tax_register / $contract->installments, 2);
+            $dates = $this->buildInstallmentDates(
+                $contract->enrollment_due_date->format('Y-m-d'),
+                $recurringDay,
+                $contract->installments,
+            );
+
+            foreach ($dates as $i => $date) {
+                $rows[] = [
+                    'student_contract_id' => $contract->id,
+                    'unit_id'             => $contract->unit_id,
+                    'student_id'          => $contract->student_id,
+                    'type'                => 'enrollment',
+                    'history'             => 'REF. MATRÍCULA',
+                    'installment_number'  => $i + 1,
+                    'total_installments'  => $contract->installments,
+                    'value'               => $value,
+                    'paid_value'          => 0,
+                    'discount'            => 0,
+                    'fine'                => 0,
+                    'due_date'            => $date->format('Y-m-d'),
+                    'status'              => 'pending',
+                    'created_at'          => $now,
+                    'updated_at'          => $now,
+                ];
+            }
+        }
+
+        // --- Pacote ---
+        if ($contract->package_value && $contract->package_installments && $contract->package_due_date) {
+            $value = round($contract->package_value / $contract->package_installments, 2);
+            $dates = $this->buildInstallmentDates(
+                $contract->package_due_date->format('Y-m-d'),
+                $recurringDay,
+                $contract->package_installments,
+            );
+
+            foreach ($dates as $i => $date) {
+                $rows[] = [
+                    'student_contract_id' => $contract->id,
+                    'unit_id'             => $contract->unit_id,
+                    'student_id'          => $contract->student_id,
+                    'type'                => 'package',
+                    'history'             => 'REF. PACOTE',
+                    'installment_number'  => $i + 1,
+                    'total_installments'  => $contract->package_installments,
+                    'value'               => $value,
+                    'paid_value'          => 0,
+                    'discount'            => 0,
+                    'fine'                => 0,
+                    'due_date'            => $date->format('Y-m-d'),
+                    'status'              => 'pending',
+                    'created_at'          => $now,
+                    'updated_at'          => $now,
+                ];
+            }
+        }
+
+        if (!empty($rows)) {
+            StudentContractInstallment::insert($rows);
+        }
+    }
+
+    /**
+     * Monta o array de datas para N parcelas.
+     *
+     * - 1ª parcela: usa exatamente $firstDate (data escolhida pelo usuário)
+     * - 2ª em diante: dia $recurringDay avançando mês a mês a partir do mês da 1ª
+     *
+     * Exemplo: firstDate=25/05/2026, recurringDay=5, count=3
+     *   → [25/05/2026, 05/06/2026, 05/07/2026]
+     */
+    private function buildInstallmentDates(string $firstDate, int $recurringDay, int $count): array
+    {
+        $dates     = [];
+        $first     = Carbon::createFromFormat('Y-m-d', $firstDate);
+        $dates[]   = $first->copy();
+
+        $baseMonth = $first->copy()->startOfMonth();
+
+        for ($i = 1; $i < $count; $i++) {
+            $next = $baseMonth->copy()->addMonths($i);
+            $day  = min($recurringDay, $next->daysInMonth);
+            $next->setDay($day);
+            $dates[] = $next;
+        }
+
+        return $dates;
     }
 
     public function update(int $id, array $data): ?StudentContract

+ 45 - 0
database/migrations/2026_05_26_123703_clean_and_update_student_contracts_table.php

@@ -0,0 +1,45 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::table('student_contracts', function (Blueprint $table) {
+          $table->dropForeign(['contract_id']);
+          $table->dropForeign(['payment_method_id']);
+          $table->dropForeign(['modality_id']);
+
+          $table->dropColumn([
+              'contract_id',
+              'started_date',
+              'maximum_time_to_freeze_registration',
+              'payment_method_id',
+              'modality_id',
+              'due_date',
+              'down_payment',
+              'material_value',
+              'material_installments',
+          ]);
+
+          $table->date('enrollment_due_date')->nullable();
+          $table->decimal('package_value', 10, 2)->nullable();
+          $table->tinyInteger('package_installments')->nullable();
+          $table->date('package_due_date')->nullable();
+      });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        //
+    }
+};

+ 50 - 0
database/migrations/2026_05_26_133047_create_student_contract_installments_table.php

@@ -0,0 +1,50 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('student_contract_installments', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('student_contract_id')
+                ->constrained('student_contracts')
+                ->cascadeOnDelete();
+            $table->foreignId('unit_id')->constrained('units');
+            $table->foreignId('student_id')->constrained('students');
+            $table->string('type', 20);                        // enrollment | package
+            $table->string('history', 100);                    // REF. MATRÍCULA | REF. PACOTE
+            $table->unsignedSmallInteger('installment_number');
+            $table->unsignedSmallInteger('total_installments');
+            $table->decimal('value', 10, 2);
+            $table->decimal('paid_value', 10, 2)->default(0);
+            $table->decimal('discount', 10, 2)->default(0);
+            $table->decimal('fine', 10, 2)->default(0);
+            $table->date('due_date');
+            $table->date('payment_date')->nullable();
+            $table->timestamp('cancelled_at')->nullable();
+            $table->string('cancellation_reason', 255)->nullable();
+            $table->string('asaas_id', 100)->nullable();
+            $table->string('status', 20)->default('pending'); // pending | paid | cancelled
+            $table->timestamps();
+            $table->softDeletes();
+
+            $table->index('student_contract_id');
+            $table->index('unit_id');
+            $table->index('student_id');
+            $table->index('due_date');
+            $table->index('status');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('student_contract_installments');
+    }
+};