Преглед изворни кода

feat: add geracao de contrato do estudante

Gustavo Mantovani пре 2 дана
родитељ
комит
b9dc51d333

+ 373 - 0
_ide_helper.php

@@ -22800,6 +22800,377 @@ public static function flushMacros()
             }
     }
 
+namespace Barryvdh\DomPDF\Facade {
+    /**
+     * @method static BasePDF setBaseHost(string $baseHost)
+     * @method static BasePDF setBasePath(string $basePath)
+     * @method static BasePDF setCanvas(\Dompdf\Canvas $canvas)
+     * @method static BasePDF setCallbacks(array<string, mixed> $callbacks)
+     * @method static BasePDF setCss(\Dompdf\Css\Stylesheet $css)
+     * @method static BasePDF setDefaultView(string $defaultView, array<string, mixed> $options)
+     * @method static BasePDF setDom(\DOMDocument $dom)
+     * @method static BasePDF setFontMetrics(\Dompdf\FontMetrics $fontMetrics)
+     * @method static BasePDF setHttpContext(resource|array<string, mixed> $httpContext)
+     * @method static BasePDF setPaper(string|float[] $paper, string $orientation = 'portrait')
+     * @method static BasePDF setProtocol(string $protocol)
+     * @method static BasePDF setTree(\Dompdf\Frame\FrameTree $tree)
+     */
+    class Pdf {
+        /**
+         * Get the DomPDF instance
+         *
+         * @static
+         */
+        public static function getDomPDF()
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->getDomPDF();
+        }
+
+        /**
+         * Show or hide warnings
+         *
+         * @static
+         */
+        public static function setWarnings($warnings)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->setWarnings($warnings);
+        }
+
+        /**
+         * Load a HTML string
+         *
+         * @param string|null $encoding Not used yet
+         * @static
+         */
+        public static function loadHTML($string, $encoding = null)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->loadHTML($string, $encoding);
+        }
+
+        /**
+         * Load a HTML file
+         *
+         * @static
+         */
+        public static function loadFile($file)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->loadFile($file);
+        }
+
+        /**
+         * Add metadata info
+         *
+         * @param array<string, string> $info
+         * @static
+         */
+        public static function addInfo($info)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->addInfo($info);
+        }
+
+        /**
+         * Load a View and convert to HTML
+         *
+         * @param array<string, mixed> $data
+         * @param array<string, mixed> $mergeData
+         * @param string|null $encoding Not used yet
+         * @static
+         */
+        public static function loadView($view, $data = [], $mergeData = [], $encoding = null)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->loadView($view, $data, $mergeData, $encoding);
+        }
+
+        /**
+         * Set/Change an option (or array of options) in Dompdf
+         *
+         * @param array<string, mixed>|string $attribute
+         * @param null|mixed $value
+         * @static
+         */
+        public static function setOption($attribute, $value = null)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->setOption($attribute, $value);
+        }
+
+        /**
+         * Replace all the Options from DomPDF
+         *
+         * @param array<string, mixed> $options
+         * @static
+         */
+        public static function setOptions($options, $mergeWithDefaults = false)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->setOptions($options, $mergeWithDefaults);
+        }
+
+        /**
+         * Output the PDF as a string.
+         * 
+         * The options parameter controls the output. Accepted options are:
+         * 
+         * 'compress' = > 1 or 0 - apply content stream compression, this is
+         *    on (1) by default
+         *
+         * @param array<string, int> $options
+         * @return string The rendered PDF as string
+         * @static
+         */
+        public static function output($options = [])
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->output($options);
+        }
+
+        /**
+         * Save the PDF to a file
+         *
+         * @static
+         */
+        public static function save($filename, $disk = null)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->save($filename, $disk);
+        }
+
+        /**
+         * Make the PDF downloadable by the user
+         *
+         * @static
+         */
+        public static function download($filename = 'document.pdf')
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->download($filename);
+        }
+
+        /**
+         * Return a response with the PDF to show in the browser
+         *
+         * @static
+         */
+        public static function stream($filename = 'document.pdf')
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->stream($filename);
+        }
+
+        /**
+         * Render the PDF
+         *
+         * @static
+         */
+        public static function render()
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->render();
+        }
+
+        /**
+         * @param array<string> $pc
+         * @static
+         */
+        public static function setEncryption($password, $ownerpassword = '', $pc = [])
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->setEncryption($password, $ownerpassword, $pc);
+        }
+
+            }
+    /**
+     * @method static BasePDF setBaseHost(string $baseHost)
+     * @method static BasePDF setBasePath(string $basePath)
+     * @method static BasePDF setCanvas(\Dompdf\Canvas $canvas)
+     * @method static BasePDF setCallbacks(array<string, mixed> $callbacks)
+     * @method static BasePDF setCss(\Dompdf\Css\Stylesheet $css)
+     * @method static BasePDF setDefaultView(string $defaultView, array<string, mixed> $options)
+     * @method static BasePDF setDom(\DOMDocument $dom)
+     * @method static BasePDF setFontMetrics(\Dompdf\FontMetrics $fontMetrics)
+     * @method static BasePDF setHttpContext(resource|array<string, mixed> $httpContext)
+     * @method static BasePDF setPaper(string|float[] $paper, string $orientation = 'portrait')
+     * @method static BasePDF setProtocol(string $protocol)
+     * @method static BasePDF setTree(\Dompdf\Frame\FrameTree $tree)
+     */
+    class Pdf {
+        /**
+         * Get the DomPDF instance
+         *
+         * @static
+         */
+        public static function getDomPDF()
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->getDomPDF();
+        }
+
+        /**
+         * Show or hide warnings
+         *
+         * @static
+         */
+        public static function setWarnings($warnings)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->setWarnings($warnings);
+        }
+
+        /**
+         * Load a HTML string
+         *
+         * @param string|null $encoding Not used yet
+         * @static
+         */
+        public static function loadHTML($string, $encoding = null)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->loadHTML($string, $encoding);
+        }
+
+        /**
+         * Load a HTML file
+         *
+         * @static
+         */
+        public static function loadFile($file)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->loadFile($file);
+        }
+
+        /**
+         * Add metadata info
+         *
+         * @param array<string, string> $info
+         * @static
+         */
+        public static function addInfo($info)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->addInfo($info);
+        }
+
+        /**
+         * Load a View and convert to HTML
+         *
+         * @param array<string, mixed> $data
+         * @param array<string, mixed> $mergeData
+         * @param string|null $encoding Not used yet
+         * @static
+         */
+        public static function loadView($view, $data = [], $mergeData = [], $encoding = null)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->loadView($view, $data, $mergeData, $encoding);
+        }
+
+        /**
+         * Set/Change an option (or array of options) in Dompdf
+         *
+         * @param array<string, mixed>|string $attribute
+         * @param null|mixed $value
+         * @static
+         */
+        public static function setOption($attribute, $value = null)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->setOption($attribute, $value);
+        }
+
+        /**
+         * Replace all the Options from DomPDF
+         *
+         * @param array<string, mixed> $options
+         * @static
+         */
+        public static function setOptions($options, $mergeWithDefaults = false)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->setOptions($options, $mergeWithDefaults);
+        }
+
+        /**
+         * Output the PDF as a string.
+         * 
+         * The options parameter controls the output. Accepted options are:
+         * 
+         * 'compress' = > 1 or 0 - apply content stream compression, this is
+         *    on (1) by default
+         *
+         * @param array<string, int> $options
+         * @return string The rendered PDF as string
+         * @static
+         */
+        public static function output($options = [])
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->output($options);
+        }
+
+        /**
+         * Save the PDF to a file
+         *
+         * @static
+         */
+        public static function save($filename, $disk = null)
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->save($filename, $disk);
+        }
+
+        /**
+         * Make the PDF downloadable by the user
+         *
+         * @static
+         */
+        public static function download($filename = 'document.pdf')
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->download($filename);
+        }
+
+        /**
+         * Return a response with the PDF to show in the browser
+         *
+         * @static
+         */
+        public static function stream($filename = 'document.pdf')
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->stream($filename);
+        }
+
+        /**
+         * Render the PDF
+         *
+         * @static
+         */
+        public static function render()
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->render();
+        }
+
+        /**
+         * @param array<string> $pc
+         * @static
+         */
+        public static function setEncryption($password, $ownerpassword = '', $pc = [])
+        {
+            /** @var \Barryvdh\DomPDF\PDF $instance */
+            return $instance->setEncryption($password, $ownerpassword, $pc);
+        }
+
+            }
+    }
+
 namespace Illuminate\Http {
     /**
      */
@@ -27696,6 +28067,8 @@ class Uri extends \Illuminate\Support\Uri {}
     class Validator extends \Illuminate\Support\Facades\Validator {}
     class View extends \Illuminate\Support\Facades\View {}
     class Vite extends \Illuminate\Support\Facades\Vite {}
+    class PDF extends \Barryvdh\DomPDF\Facade\Pdf {}
+    class Pdf extends \Barryvdh\DomPDF\Facade\Pdf {}
 }
 
 

+ 6 - 5
app/Http/Controllers/StudentContractController.php

@@ -88,23 +88,23 @@ public function franchisorFrozen(): JsonResponse
 
     //
 
-    public function attachFile(Request $request, int $id): JsonResponse
+    public function generateFile(int $id): JsonResponse
     {
-        $request->validate(['file' => 'required|file|mimes:jpeg,png,gif,pdf,mp4,mov,avi|max:20480']);
-        $item = $this->service->attachFile($id, $request->file('file'));
+        $item = $this->service->generateFile($id);
 
         return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.updated'));
     }
 
     public function attachSignedFile(Request $request, int $id): JsonResponse
     {
-        $request->validate(['file' => 'required|file|mimes:jpeg,png,gif,pdf,mp4,mov,avi|max:20480']);
+        $request->validate(['file' => 'required|file|mimes:jpeg,png,pdf|max:20480']);
+
         $item = $this->service->attachFile($id, $request->file('file'), signed: true);
 
         return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.updated'));
     }
 
-   //
+    //
 
     public function installments(int $id): JsonResponse
     {
@@ -124,6 +124,7 @@ public function freeze(int $id): JsonResponse
         }
 
         $contract = $this->service->findById($id);
+
         if (! $contract) {
             return $this->errorResponse(message: 'Contrato não encontrado.', code: 404);
         }

+ 49 - 0
app/Services/StudentContractService.php

@@ -5,6 +5,7 @@
 use App\Models\StudentContract;
 use App\Models\StudentContractInstallment;
 use App\Models\StudentMedia;
+use Barryvdh\DomPDF\Facade\Pdf;
 use Carbon\Carbon;
 use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Http\UploadedFile;
@@ -220,6 +221,54 @@ public function attachFile(int $id, UploadedFile $file, bool $signed = false): ?
         return $model->fresh();
     }
 
+    public function generateFile(int $id): ?StudentContract
+    {
+        return DB::transaction(function () use ($id): ?StudentContract {
+            $model = StudentContract::query()->lockForUpdate()->find($id);
+
+            if (! $model || $model->file_url) {
+                return $model?->fresh();
+            }
+
+            $model->load([
+                'classPackageUnit',
+                'student.city',
+                'student.state',
+                'student.responsibles.city',
+                'student.responsibles.state',
+                'unit.city',
+                'unit.state',
+            ]);
+
+            $path = sprintf(
+                'student-contracts/%d/contract-v%d.pdf',
+                $model->id,
+                $model->version,
+            );
+
+            $contents = Pdf::loadView('student-contracts.contract', [
+                'contract' => $model,
+            ])->setPaper('a4')->output();
+
+            Storage::put($path, $contents);
+
+            StudentMedia::create([
+                'student_id'          => $model->student_id,
+                'student_contract_id' => $model->id,
+                'url'                 => $path,
+                'file_type'           => 'application/pdf',
+                'type'                => 'contract',
+            ]);
+
+            $model->update([
+                'file_url'  => $path,
+                'file_type' => 'application/pdf',
+            ]);
+
+            return $model->fresh();
+        });
+    }
+
     //
 
     public function cancel(int $id, ?int $createdByUserId = null): ?StudentContract

+ 1 - 0
composer.json

@@ -9,6 +9,7 @@
     "license": "MIT",
     "require": {
         "php": "^8.3",
+        "barryvdh/laravel-dompdf": "^3.1",
         "kalnoy/nestedset": "^6.0",
         "laravel/framework": "^12.0",
         "laravel/sanctum": "^4.0",

+ 524 - 2
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "2eaa837ed7d3c567db3034d8fe361eb0",
+    "content-hash": "a78219f104b971a51606834db740b289",
     "packages": [
         {
             "name": "aws/aws-crt-php",
@@ -157,6 +157,83 @@
             },
             "time": "2026-04-13T18:11:10+00:00"
         },
+        {
+            "name": "barryvdh/laravel-dompdf",
+            "version": "v3.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/barryvdh/laravel-dompdf.git",
+                "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc",
+                "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc",
+                "shasum": ""
+            },
+            "require": {
+                "dompdf/dompdf": "^3.0",
+                "illuminate/support": "^9|^10|^11|^12|^13.0",
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "larastan/larastan": "^2.7|^3.0",
+                "orchestra/testbench": "^7|^8|^9.16|^10|^11.0",
+                "phpro/grumphp": "^2.5",
+                "squizlabs/php_codesniffer": "^3.5"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "aliases": {
+                        "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
+                        "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
+                    },
+                    "providers": [
+                        "Barryvdh\\DomPDF\\ServiceProvider"
+                    ]
+                },
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Barryvdh\\DomPDF\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "A DOMPDF Wrapper for Laravel",
+            "keywords": [
+                "dompdf",
+                "laravel",
+                "pdf"
+            ],
+            "support": {
+                "issues": "https://github.com/barryvdh/laravel-dompdf/issues",
+                "source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.2"
+            },
+            "funding": [
+                {
+                    "url": "https://fruitcake.nl",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/barryvdh",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-02-21T08:51:10+00:00"
+        },
         {
             "name": "brick/math",
             "version": "0.13.1",
@@ -608,6 +685,161 @@
             ],
             "time": "2024-02-05T11:56:58+00:00"
         },
+        {
+            "name": "dompdf/dompdf",
+            "version": "v3.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dompdf/dompdf.git",
+                "reference": "6d4b4eb8500f7a786da8868ba463a71b725a4005"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dompdf/dompdf/zipball/6d4b4eb8500f7a786da8868ba463a71b725a4005",
+                "reference": "6d4b4eb8500f7a786da8868ba463a71b725a4005",
+                "shasum": ""
+            },
+            "require": {
+                "dompdf/php-font-lib": "^1.0.0",
+                "dompdf/php-svg-lib": "^1.0.0",
+                "ext-dom": "*",
+                "ext-mbstring": "*",
+                "masterminds/html5": "^2.0",
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "ext-gd": "*",
+                "ext-json": "*",
+                "ext-zip": "*",
+                "mockery/mockery": "^1.3",
+                "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
+                "squizlabs/php_codesniffer": "^3.5",
+                "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
+            },
+            "suggest": {
+                "ext-gd": "Needed to process images",
+                "ext-gmagick": "Improves image processing performance",
+                "ext-imagick": "Improves image processing performance",
+                "ext-zlib": "Needed for pdf stream compression"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Dompdf\\": "src/"
+                },
+                "classmap": [
+                    "lib/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1"
+            ],
+            "authors": [
+                {
+                    "name": "The Dompdf Community",
+                    "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
+                }
+            ],
+            "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
+            "homepage": "https://github.com/dompdf/dompdf",
+            "support": {
+                "issues": "https://github.com/dompdf/dompdf/issues",
+                "source": "https://github.com/dompdf/dompdf/tree/v3.1.6"
+            },
+            "time": "2026-07-20T12:29:38+00:00"
+        },
+        {
+            "name": "dompdf/php-font-lib",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dompdf/php-font-lib.git",
+                "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a",
+                "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "FontLib\\": "src/FontLib"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "The FontLib Community",
+                    "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
+                }
+            ],
+            "description": "A library to read, parse, export and make subsets of different types of font files.",
+            "homepage": "https://github.com/dompdf/php-font-lib",
+            "support": {
+                "issues": "https://github.com/dompdf/php-font-lib/issues",
+                "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2"
+            },
+            "time": "2026-01-20T14:10:26+00:00"
+        },
+        {
+            "name": "dompdf/php-svg-lib",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dompdf/php-svg-lib.git",
+                "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1",
+                "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "php": "^7.1 || ^8.0",
+                "sabberworm/php-css-parser": "^8.4 || ^9.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Svg\\": "src/Svg"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-3.0-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "The SvgLib Community",
+                    "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
+                }
+            ],
+            "description": "A library to read, parse and export to PDF SVG files.",
+            "homepage": "https://github.com/dompdf/php-svg-lib",
+            "support": {
+                "issues": "https://github.com/dompdf/php-svg-lib/issues",
+                "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2"
+            },
+            "time": "2026-01-02T16:01:13+00:00"
+        },
         {
             "name": "dragonmantank/cron-expression",
             "version": "v3.4.0",
@@ -2603,6 +2835,73 @@
             },
             "time": "2022-12-02T22:17:43+00:00"
         },
+        {
+            "name": "masterminds/html5",
+            "version": "2.10.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Masterminds/html5-php.git",
+                "reference": "fd5018f6815fff903946d0564977b44ce8010e29"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fd5018f6815fff903946d0564977b44ce8010e29",
+                "reference": "fd5018f6815fff903946d0564977b44ce8010e29",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9 || ^10"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Masterminds\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Matt Butcher",
+                    "email": "technosophos@gmail.com"
+                },
+                {
+                    "name": "Matt Farina",
+                    "email": "matt@mattfarina.com"
+                },
+                {
+                    "name": "Asmir Mustafic",
+                    "email": "goetas@gmail.com"
+                }
+            ],
+            "description": "An HTML5 parser and serializer.",
+            "homepage": "http://masterminds.github.io/html5-php",
+            "keywords": [
+                "HTML5",
+                "dom",
+                "html",
+                "parser",
+                "querypath",
+                "serializer",
+                "xml"
+            ],
+            "support": {
+                "issues": "https://github.com/Masterminds/html5-php/issues",
+                "source": "https://github.com/Masterminds/html5-php/tree/2.10.1"
+            },
+            "time": "2026-06-23T18:43:15+00:00"
+        },
         {
             "name": "monolog/monolog",
             "version": "3.9.0",
@@ -4043,6 +4342,86 @@
             },
             "time": "2025-06-25T14:20:11+00:00"
         },
+        {
+            "name": "sabberworm/php-css-parser",
+            "version": "v9.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
+                "reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f",
+                "reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f",
+                "shasum": ""
+            },
+            "require": {
+                "ext-iconv": "*",
+                "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
+                "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4"
+            },
+            "require-dev": {
+                "php-parallel-lint/php-parallel-lint": "1.4.0",
+                "phpstan/extension-installer": "1.4.3",
+                "phpstan/phpstan": "1.12.33 || 2.2.2",
+                "phpstan/phpstan-phpunit": "1.4.2 || 2.0.16",
+                "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.11",
+                "phpunit/phpunit": "8.5.52",
+                "rawr/phpunit-data-provider": "3.3.1",
+                "rector/rector": "1.2.10 || 2.4.6",
+                "rector/type-perfect": "1.0.0 || 2.1.3",
+                "squizlabs/php_codesniffer": "4.0.1",
+                "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.3"
+            },
+            "suggest": {
+                "ext-mbstring": "for parsing UTF-8 CSS"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "9.5.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/Rule/Rule.php",
+                    "src/RuleSet/RuleContainer.php"
+                ],
+                "psr-4": {
+                    "Sabberworm\\CSS\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Raphael Schweikert"
+                },
+                {
+                    "name": "Oliver Klee",
+                    "email": "github@oliverklee.de"
+                },
+                {
+                    "name": "Jake Hotson",
+                    "email": "jake.github@qzdesign.co.uk"
+                }
+            ],
+            "description": "Parser for CSS Files written in PHP",
+            "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
+            "keywords": [
+                "css",
+                "parser",
+                "stylesheet"
+            ],
+            "support": {
+                "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
+                "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.4.0"
+            },
+            "time": "2026-06-18T15:10:53+00:00"
+        },
         {
             "name": "symfony/clock",
             "version": "v7.3.0",
@@ -6348,6 +6727,149 @@
             ],
             "time": "2025-06-27T19:55:54+00:00"
         },
+        {
+            "name": "thecodingmachine/safe",
+            "version": "v3.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thecodingmachine/safe.git",
+                "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19",
+                "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "php-parallel-lint/php-parallel-lint": "^1.4",
+                "phpstan/phpstan": "^2",
+                "phpunit/phpunit": "^10",
+                "squizlabs/php_codesniffer": "^3.2"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "lib/special_cases.php",
+                    "generated/apache.php",
+                    "generated/apcu.php",
+                    "generated/array.php",
+                    "generated/bzip2.php",
+                    "generated/calendar.php",
+                    "generated/classobj.php",
+                    "generated/com.php",
+                    "generated/cubrid.php",
+                    "generated/curl.php",
+                    "generated/datetime.php",
+                    "generated/dir.php",
+                    "generated/eio.php",
+                    "generated/errorfunc.php",
+                    "generated/exec.php",
+                    "generated/fileinfo.php",
+                    "generated/filesystem.php",
+                    "generated/filter.php",
+                    "generated/fpm.php",
+                    "generated/ftp.php",
+                    "generated/funchand.php",
+                    "generated/gettext.php",
+                    "generated/gmp.php",
+                    "generated/gnupg.php",
+                    "generated/hash.php",
+                    "generated/ibase.php",
+                    "generated/ibmDb2.php",
+                    "generated/iconv.php",
+                    "generated/image.php",
+                    "generated/imap.php",
+                    "generated/info.php",
+                    "generated/inotify.php",
+                    "generated/json.php",
+                    "generated/ldap.php",
+                    "generated/libxml.php",
+                    "generated/lzf.php",
+                    "generated/mailparse.php",
+                    "generated/mbstring.php",
+                    "generated/misc.php",
+                    "generated/mysql.php",
+                    "generated/mysqli.php",
+                    "generated/network.php",
+                    "generated/oci8.php",
+                    "generated/opcache.php",
+                    "generated/openssl.php",
+                    "generated/outcontrol.php",
+                    "generated/pcntl.php",
+                    "generated/pcre.php",
+                    "generated/pgsql.php",
+                    "generated/posix.php",
+                    "generated/ps.php",
+                    "generated/pspell.php",
+                    "generated/readline.php",
+                    "generated/rnp.php",
+                    "generated/rpminfo.php",
+                    "generated/rrd.php",
+                    "generated/sem.php",
+                    "generated/session.php",
+                    "generated/shmop.php",
+                    "generated/sockets.php",
+                    "generated/sodium.php",
+                    "generated/solr.php",
+                    "generated/spl.php",
+                    "generated/sqlsrv.php",
+                    "generated/ssdeep.php",
+                    "generated/ssh2.php",
+                    "generated/stream.php",
+                    "generated/strings.php",
+                    "generated/swoole.php",
+                    "generated/uodbc.php",
+                    "generated/uopz.php",
+                    "generated/url.php",
+                    "generated/var.php",
+                    "generated/xdiff.php",
+                    "generated/xml.php",
+                    "generated/xmlrpc.php",
+                    "generated/yaml.php",
+                    "generated/yaz.php",
+                    "generated/zip.php",
+                    "generated/zlib.php"
+                ],
+                "classmap": [
+                    "lib/DateTime.php",
+                    "lib/DateTimeImmutable.php",
+                    "lib/Exceptions/",
+                    "generated/Exceptions/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "PHP core functions that throw exceptions instead of returning FALSE on error",
+            "support": {
+                "issues": "https://github.com/thecodingmachine/safe/issues",
+                "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/OskarStark",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/shish",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/silasjoisten",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/staabm",
+                    "type": "github"
+                }
+            ],
+            "time": "2026-02-04T18:08:13+00:00"
+        },
         {
             "name": "tijsverkoyen/css-to-inline-styles",
             "version": "v2.3.0",
@@ -9079,5 +9601,5 @@
         "php": "^8.3"
     },
     "platform-dev": {},
-    "plugin-api-version": "2.9.0"
+    "plugin-api-version": "2.6.0"
 }

+ 315 - 0
resources/views/student-contracts/contract.blade.php

@@ -0,0 +1,315 @@
+<!DOCTYPE html>
+<html lang="pt-BR">
+<head>
+    <meta charset="UTF-8">
+    <title>Contrato Ginástica do Cérebro {{ $contract->protocol ?: $contract->id }}</title>
+    <style>
+        @page { margin: 82px 72px 68px; }
+        * { box-sizing: border-box; }
+        body { color: #000; font-family: DejaVu Sans, sans-serif; font-size: 9px; line-height: 1.48; margin: 0; }
+        .header { border-bottom: 1px solid #000; font-size: 9px; left: 0; padding-bottom: 9px; position: fixed; right: 0; top: -55px; }
+        .footer { bottom: -42px; font-size: 8px; position: fixed; right: 0; text-align: right; }
+        .footer:after { content: counter(page); }
+        h1 { font-size: 14px; margin: 0 0 12px; }
+        h2 { font-size: 10px; margin: 14px 0 7px; text-align: left; }
+        p { margin: 0 0 7px; text-align: justify; }
+        table { border-collapse: collapse; margin-bottom: 9px; page-break-inside: avoid; width: 100%; }
+        td, th { border: 1px solid #000; padding: 5px 6px; vertical-align: top; }
+        th { font-size: 10px; text-align: center; }
+        .party-table td { border: 0; padding: 1px 8px 1px 0; width: 50%; }
+        .party-table { border: 1px solid #000; padding: 5px; }
+        .party-table .cell { border: 0; padding: 5px 7px; }
+        .line { margin-bottom: 2px; }
+        .summary td { padding: 4px 6px; }
+        .summary .label, .line strong { font-weight: bold; }
+        .clause-title { font-size: 9px; font-weight: normal; margin: 12px 0 7px; }
+        .objectives { margin: 0 0 8px 18px; padding: 0; }
+        .objectives li { margin-bottom: 2px; }
+        .signatures { margin-top: 38px; }
+        .signatures td { border: 0; padding: 0 14px; text-align: center; width: 50%; }
+        .signature-line { border-top: 1px solid #000; margin-bottom: 4px; }
+        .witnesses { margin-top: 25px; }
+        .witnesses td { border: 0; padding: 4px 10px 4px 0; width: 50%; }
+        .contract-date { margin-top: 34px; }
+    </style>
+</head>
+<body>
+@php
+    $student = $contract->student;
+    $unit = $contract->unit;
+    $responsible = $student?->responsibles?->first();
+    $contractor = $responsible ?: $student;
+    $package = $contract->classPackageUnit;
+
+    $weekdays = [
+        0 => 'Domingo',
+        1 => 'Segunda',
+        2 => 'Terça',
+        3 => 'Quarta',
+        4 => 'Quinta',
+        5 => 'Sexta',
+        6 => 'Sábado',
+    ];
+
+    $money = fn ($value) => 'R$ '.number_format((float) ($value ?? 0), 2, ',', '.');
+    $percentage = fn ($value) => number_format((float) ($value ?? 0), 2, ',', '.').'%';
+    $date = fn ($value) => $value?->format('d/m/Y') ?? '';
+    $onlyDigits = fn ($value) => preg_replace('/\D+/', '', (string) ($value ?? ''));
+
+    $formatDocument = function ($value) use ($onlyDigits) {
+        $digits = $onlyDigits($value);
+
+        if (strlen($digits) === 11) {
+            return preg_replace('/(\d{3})(\d{3})(\d{3})(\d{2})/', '$1.$2.$3-$4', $digits);
+        }
+
+        if (strlen($digits) === 14) {
+            return preg_replace('/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/', '$1.$2.$3/$4-$5', $digits);
+        }
+
+        return (string) ($value ?? '');
+    };
+
+    $formatPostalCode = function ($value) use ($onlyDigits) {
+        $digits = $onlyDigits($value);
+
+        return strlen($digits) === 8
+            ? preg_replace('/(\d{5})(\d{3})/', '$1-$2', $digits)
+            : (string) ($value ?? '');
+    };
+
+    $formatPhone = function ($value) use ($onlyDigits) {
+        $digits = $onlyDigits($value);
+
+        if (strlen($digits) === 11) {
+            return preg_replace('/(\d{2})(\d{5})(\d{4})/', '($1) $2-$3', $digits);
+        }
+
+        if (strlen($digits) === 10) {
+            return preg_replace('/(\d{2})(\d{4})(\d{4})/', '($1) $2-$3', $digits);
+        }
+
+        return (string) ($value ?? '');
+    };
+
+    $address = fn ($model) => collect([
+        $model?->street,
+        $model?->address_number,
+    ])->filter()->implode(', ');
+
+    $schedule = collect([
+        $contract->weekday !== null
+            ? ($weekdays[$contract->weekday] ?? '').' das '.substr((string) $contract->start_time, 0, 5).'h às '.substr((string) $contract->end_time, 0, 5).'h'
+            : null,
+        $contract->second_weekday !== null
+            ? ($weekdays[$contract->second_weekday] ?? '').' das '.substr((string) $contract->second_start_time, 0, 5).'h às '.substr((string) $contract->second_end_time, 0, 5).'h'
+            : null,
+    ])->filter()->implode(' e ');
+
+    $materialValue = (float) ($package?->contract_material_value ?? 0);
+    $classValue = (float) ($contract->package_value ?? 0);
+    $enrollmentValue = (float) ($contract->tax_register ?? 0);
+    $totalContract = $materialValue + $classValue + $enrollmentValue;
+    $classDiscount = (float) ($package?->contrat_discount_value ?? 0);
+    $classOriginalValue = $classValue + $classDiscount;
+    $classDiscountPercentage = $classOriginalValue > 0
+        ? ($classDiscount / $classOriginalValue) * 100
+        : 0;
+    $packageInstallments = max(1, (int) ($contract->package_installments ?? 1));
+    $enrollmentInstallments = max(0, (int) ($contract->installments ?? 0));
+    $classInstallmentValue = $classValue / $packageInstallments;
+    $punctualityValue = $classInstallmentValue * (1 - ((float) ($contract->early_payment_discount ?? 0) / 100));
+    $totalInstallments = $packageInstallments + $enrollmentInstallments;
+    $installmentDescription = $enrollmentInstallments > 0
+        ? ' (Matrícula + '.$packageInstallments.'x)'
+        : '';
+
+    $issueDate = ($contract->signature_date ?? now())->copy();
+    $studentAge = $student?->birth_date
+        ? (int) floor($student->birth_date->floatDiffInYears($issueDate))
+        : null;
+    $cityName = $unit?->city?->name ?: $student?->city?->name ?: '';
+    $longDate = $issueDate->locale('pt_BR')->translatedFormat('l, d \d\e F \d\e Y');
+@endphp
+
+<div class="header">Contrato Ginástica do Cérebro</div>
+<div class="footer"></div>
+
+<h1>Contrato Ginástica do Cérebro número: {{ $contract->protocol ?: $contract->id }}</h1>
+
+<table class="party-table">
+    <tr>
+        <td class="cell">
+            <div class="line"><strong>CONTRATADA:</strong> {{ $unit?->social_reason }}</div>
+            <div class="line"><strong>UNIDADE:</strong> {{ $unit?->fantasy_name ?: $unit?->name }}</div>
+            <div class="line"><strong>ENDEREÇO:</strong> {{ $address($unit) }}</div>
+            <div class="line"><strong>CEP:</strong> {{ $formatPostalCode($unit?->postal_code) }}</div>
+            <div class="line"><strong>E-MAIL:</strong> {{ $unit?->email }}</div>
+        </td>
+        <td class="cell">
+            <div class="line"><strong>CNPJ:</strong> {{ $formatDocument($unit?->cnpj) }}</div>
+            <div class="line"><strong>BAIRRO:</strong> {{ $unit?->neighborhood }}</div>
+            <div class="line"><strong>FONE:</strong> {{ $formatPhone($unit?->phone_number ?: $unit?->cell_number) }}</div>
+        </td>
+    </tr>
+</table>
+
+<table class="party-table">
+    <tr>
+        <td class="cell">
+            <div class="line"><strong>CONTRATANTE:</strong> {{ $contractor?->name }}</div>
+            <div class="line"><strong>PARENTESCO:</strong> {{ $responsible?->degree }}</div>
+            <div class="line"><strong>DATA NASC.:</strong> {{ $date($contractor?->birth_date) }}</div>
+            <div class="line"><strong>PROFISSÃO:</strong></div>
+            <div class="line"><strong>ENDEREÇO:</strong> {{ $address($contractor) }}</div>
+            <div class="line"><strong>BAIRRO:</strong> {{ $contractor?->neighborhood }}</div>
+            <div class="line"><strong>FONE RESIDENCIAL:</strong></div>
+            <div class="line"><strong>CELULAR:</strong> {{ $formatPhone($contractor?->phone) }}</div>
+        </td>
+        <td class="cell">
+            <div class="line"><strong>CPF:</strong> {{ $formatDocument($responsible?->cpf ?: $student?->document_number) }}</div>
+            <div class="line"><strong>RG:</strong></div>
+            <div class="line"><strong>E-MAIL:</strong> {{ $contractor?->email }}</div>
+            <div class="line"><strong>EMPRESA:</strong></div>
+            <div class="line"><strong>COMPLEMENTO:</strong> {{ $contractor?->complement }}</div>
+            <div class="line"><strong>CEP:</strong> {{ $formatPostalCode($contractor?->postal_code) }}</div>
+            <div class="line"><strong>COMERCIAL:</strong></div>
+        </td>
+    </tr>
+</table>
+
+<table class="party-table">
+    <tr>
+        <td class="cell">
+            <div class="line"><strong>ALUNO:</strong> {{ $student?->name }}</div>
+            <div class="line"><strong>DATA NASC.:</strong> {{ $date($student?->birth_date) }}</div>
+            <div class="line"><strong>IDADE:</strong> {{ $studentAge !== null ? $studentAge.' anos' : '' }}</div>
+            <div class="line"><strong>ENDEREÇO:</strong> {{ $address($student) }}</div>
+            <div class="line"><strong>BAIRRO:</strong> {{ $student?->neighborhood }}</div>
+            <div class="line"><strong>FONE RESIDENCIAL:</strong></div>
+            <div class="line"><strong>CELULAR:</strong> {{ $formatPhone($student?->phone) }}</div>
+        </td>
+        <td class="cell">
+            <div class="line"><strong>CPF:</strong> {{ $formatDocument($student?->document_number) }}</div>
+            <div class="line"><strong>E-MAIL:</strong> {{ $student?->email }}</div>
+            <div class="line"><strong>COMPLEMENTO:</strong> {{ $student?->complement }}</div>
+            <div class="line"><strong>CEP:</strong> {{ $formatPostalCode($student?->postal_code) }}</div>
+            <div class="line"><strong>COMERCIAL:</strong></div>
+        </td>
+    </tr>
+</table>
+
+<table class="summary">
+    <tr><th colspan="3">QUADRO DE RESUMO</th></tr>
+    <tr><td colspan="3"><span class="label">Dia(s) e horário(s):</span> {{ $schedule }}</td></tr>
+    <tr>
+        <td colspan="2"><span class="label">DURAÇÃO:</span> DE {{ $date($contract->signature_date) }} ATÉ {{ $date($contract->end_date) }}</td>
+        <td><span class="label">MODALIDADE:</span> {{ $package?->name }}</td>
+    </tr>
+    <tr>
+        <td><span class="label">TOTAL CONTRATO:</span> {{ $money($totalContract) }}</td>
+        <td><span class="label">DESCONTO AULAS:</span> {{ $money($classDiscount) }} ({{ $percentage($classDiscountPercentage) }})</td>
+        <td><span class="label">AULAS COM DESCONTO:</span> {{ $money($classValue) }}</td>
+    </tr>
+    <tr>
+        <td colspan="3">
+            <div class="line"><span class="label">TAXA DE MATRÍCULA:</span> {{ $money($enrollmentValue) }}</div>
+            <div class="line"><span class="label">MATERIAL:</span> {{ $money($materialValue) }} EM 1 vez</div>
+            <div class="line"><span class="label">AULAS (- {{ $percentage($classDiscountPercentage) }}):</span> {{ $money($classValue) }}</div>
+            <div class="line"><span class="label">Nº DE PARCELAS:</span> {{ $totalInstallments }} vezes{{ $installmentDescription }}</div>
+            <div class="line"><span class="label">VALOR:</span> {{ $money($classInstallmentValue) }}</div>
+            <div class="line"><span class="label">VENCIMENTO:</span> todo dia {{ str_pad((string) ($contract->recurring_day ?? ''), 2, '0', STR_PAD_LEFT) }}</div>
+            <div class="line"><span class="label">DESCONTO POR PONTUALIDADE:</span> {{ $percentage($contract->early_payment_discount) }}</div>
+            <div class="line"><span class="label">VALOR DA PARCELA SE PAGO ATÉ O VENCIMENTO:</span> {{ $money($punctualityValue) }}</div>
+        </td>
+    </tr>
+</table>
+
+<h2>TERMOS E CONDIÇÕES GERAIS</h2>
+<p>As partes deste contrato têm entre si, justo e acertado, o presente Contrato de Prestação de Serviço de Cursos que se regerá pelas cláusulas seguintes e pelas condições descritas no presente.</p>
+
+<div class="clause-title">Cláusula 1ª - DO OBJETO DO CONTRATO</div>
+<p>1.1 - O presente contrato tem como OBJETO a prestação, pelo CONTRATADO, do serviço de curso livre de neuroaprendizagem que visa desenvolver habilidades cognitivas, emocional e social, através de atividades que estimulam o aluno a pensar e interagir com ambiente em seu entorno de forma mais ativa, e estimulando a memória e o raciocínio lógico através de desafio para potencializar o cérebro integralmente nos dias, horários e local descrito no Quadro Resumo.</p>
+<p>1.2 - O objetivo do curso contratado é:</p>
+<ul class="objectives">
+    <li>Proporcionar melhoria no desempenho da fase escolar;</li>
+    <li>Potencializa os conceitos de raciocínio lógico matemático;</li>
+    <li>Trabalhar adaptação às mudanças;</li>
+    <li>Ensinar a lidar com a vitória e a derrota;</li>
+    <li>Ensinar a lidar com estratégias.</li>
+    <li>Reforçar o respeito às regras;</li>
+    <li>Trabalhar a memória;</li>
+    <li>Ampliar a capacidade de atenção e foco;</li>
+    <li>Desenvolver habilidades motoras;</li>
+    <li>Apoiar ao tratamento de TDA e TDAH;</li>
+    <li>Desenvolver segurança emocional.</li>
+</ul>
+<p>1.3 - O curso será desenvolvido através de materiais próprios com utilização de atividades, ferramentas e jogos para estimular e desafiar habilidades motoras e cognitivas, visando capacitar diversos aspectos.</p>
+
+<div class="clause-title">Cláusula 2ª - DOS SERVIÇOS</div>
+<p>2.1 - O CONTRATADO disponibilizará profissional (is) treinado(s) e capacitado(s), ora denominados INSTRUTORES, para acompanhar no curso contratado pelo CONTRATANTE, cujo valor consta no Quadro Forma de Pagamento.</p>
+<p>2.1.1 - O material exigido pelo curso faz parte do programa de desenvolvimento, sendo obrigatória sua aquisição para utilização e bom andamento das atividades citadas acima. O valor dos materiais está descrito no quadro resumo.</p>
+<p>2.2 - Cabe ao CONTRATADO selecionar, treinar e supervisionar os serviços executados pelos INSTRUTORES podendo o CONTRATANTE a qualquer momento informar qualquer problema que por ventura possa acontecer diretamente com a coordenação pedagógica da GINÁSTICA DO CÉREBRO.</p>
+<p>2.3 - Os serviços das modalidades ofertadas pela GINÁSTICA DO CÉREBRO não substituem os serviços de psicologia, psicopedagogia, fonoaudiologia, psiquiatria, neuropediatria e geriatria que eventualmente o CONTRATANTE venha a necessitar.</p>
+
+<div class="clause-title">Cláusula 3ª - DOS CANCELAMENTOS</div>
+<p>3.1 - Pelo fato das modalidades serem desenvolvidas em grupo, não será abatido do valor do curso aulas canceladas mesmo sob aviso prévio, contudo, se houver outra turma em andamento na mesma modalidade em níveis semelhantes, será feito o remanejamento da aula perdida, e uma eventual reposição das aulas perdidas que forem comunicadas previamente poderão ser feitas de acordo com as regras da empresa.</p>
+<p>3.1.1 - Somente será feita a reposição de aula se o CONTRATANTE avisar com 24 horas de antecedência, exceto em casos de acidentes ou sob atestado médico.</p>
+<p>3.2 - No evento de atraso do CONTRATANTE, a aula será encerrada no mesmo horário combinado, não fazendo jus a compensação do tempo perdido.</p>
+<p>3.3 - O custo por reposição de aula sem atestado terá um custo de R$ 30,00 (trinta reais) cada aula, e o pagamento deve ser feito no ato do agendamento em dinheiro.</p>
+<p>3.4 - O agendamento de aulas deve ser feito de acordo com a disponibilidade de horários da Unidade.</p>
+
+<div class="clause-title">Cláusula 4ª - DO VALOR E FORMA DE PAGAMENTO E MULTA</div>
+<p>4.1 - Pela realização dos serviços contratados relacionados neste instrumento, a CONTRATANTE pagará ao CONTRATADO o valor descrito no Quadro Forma de Pagamento, de acordo com as condições também ali descritas.</p>
+<p>4.2 - A parte que infringir quaisquer das cláusulas do presente contrato responsabilizar-se-á pela multa do equivalente a {{ $percentage($contract->fine_cancelled ?: 20) }} do saldo a pagar do contrato.</p>
+
+<div class="clause-title">Cláusula 5ª - DA VIGÊNCIA</div>
+<p>5.1 - O presente contrato vigorará pelo prazo descrito no quadro resumo, e até que esgotem todas as aulas do módulo contratado.</p>
+<p>5.1.1 - Serão consideradas aulas dadas àquelas agendadas em que o CONTRATANTE não compareça sem cancelamento com prazo superior a 24 horas, conforme item 3.1.1.</p>
+<p>5.1.2 - Após o prazo descrito no Quadro Forma de Pagamento, o CONTRATANTE perderá o direito de usufruir as aulas não concretizadas.</p>
+
+<div class="clause-title">Cláusula 6ª - DA RESCISÃO</div>
+<p>6.1 - O presente contrato poderá ser rescindido nas seguintes condições:</p>
+<p>6.1.1 - por morte, insolvência dissolução judicial ou extrajudicial, pedido de recuperação judicial ou extrajudicial ou declaração de falência de quaisquer das partes.</p>
+<p>6.1.2 - impossibilidade de sua continuidade, motivada por força maior ou caso fortuito, assim definido pela lei.</p>
+<p>6.1.3 - por inadimplemento do contratante no pagamento de suas obrigações contratuais, de qualquer das parcelas, por mais de 30 (trinta) dias.</p>
+<p>6.1.4 - por desistência da CONTRATANTE.</p>
+<p>6.2 - A rescisão baseada nos itens 6.1.1 e 6.1.2 não acarretará multa para as partes e tampouco quaisquer outros ônus.</p>
+<p>6.3 - Para a efetivação da rescisão contratual, todas as aulas dadas e as aulas não canceladas (item 3.1.1) deverão ser pagas.</p>
+<p>6.4. E em caso de rescisão de contrato por desistência do CONTRATANTE ou pela inadimplência, hipóteses das cláusulas 6.1.3 e 6.1.4, além do pagamento das aulas dadas e não canceladas, deverá efetuar o pagamento da multa prevista na cláusula 4.2.</p>
+
+<div class="clause-title">Cláusula 7ª - CONDIÇÕES GERAIS</div>
+<p>7.1 - No caso de não pagamento dos valores acertados no Quadro Forma de Pagamento, fica ajustada a multa de 2% e os juros monetários de {{ $percentage($contract->interest_rate ?: 1) }} ao mês, ambos incidentes sobre o valor principal em débito, mais honorários advocatícios de 10% em caso de cobrança extrajudicial realizada por advogado da CONTRATADA e honorários advocatícios a serem arbitrado pelo juiz em caso de cobrança judicial.</p>
+<p>7.2 - O presente contrato não poderá ser cedido, nem transferido, sem a prévia e expressa anuência das partes.</p>
+<p>7.3 O CONTRATANTE autoriza a CONTRATADA a utilizar-se livre de quaisquer ônus, da imagem do aluno para fins exclusivos de divulgação da empresa, seus cursos e suas atividades podendo, para tanto, reproduzi-la ou divulga-la junto à Internet, jornais internos ou não e todos os demais meios de comunicação público ou privado.</p>
+<p>7.4. No final do ano, as aulas do curso serão suspensas durante o período de festas, de acordo com comunicado a ser enviado pela CONTRATADA para o CONTRATANTE.</p>
+
+<div class="clause-title">Cláusula 8ª – POLÍTICA DE PRIVACIDADE E PROTEÇÃO DE DADOS</div>
+<p>8.1 - Em cumprimento à Lei Geral de Proteção de Dados Pessoais - LGPD (com a redação dada pela Lei nº 13.853/2019), o CONTRATANTE declara ter ciência da necessidade dos dados aqui coletados e autoriza o uso dos seus dados pela CONTRATADA para a finalidade de atingir o fim objeto do presente contrato. Tal operação de tratamento de dados é e sempre será realizada unicamente em apoio e promoção às atividades técnicas e intelectuais desenvolvidas internamente pela CONTRATADA.</p>
+
+<div class="clause-title">Cláusula 9ª - DO FORO</div>
+<p>9.1 - Para dirimir quaisquer controvérsias oriundas do CONTRATO, as partes elegem o foro da comarca do CONTRATANTE descrita no QUADRO RESUMO.</p>
+
+<p>Por estarem assim justos e contratados, firmam o presente instrumento em duas vias de igual teor, e na presença de duas testemunhas.</p>
+
+<table class="signatures">
+    <tr>
+        <td><div class="signature-line"></div><strong>CONTRATADO</strong></td>
+        <td><div class="signature-line"></div><strong>CONTRATANTE</strong></td>
+    </tr>
+</table>
+
+<table class="witnesses">
+    <tr>
+        <td>Testemunha: __________________________________</td>
+        <td>Testemunha: __________________________________</td>
+    </tr>
+    <tr>
+        <td>Doc. Ident.: _________________________________</td>
+        <td>Doc. Ident.: _________________________________</td>
+    </tr>
+</table>
+
+<p class="contract-date">{{ $cityName }}{{ $cityName ? ', ' : '' }}{{ $longDate }}</p>
+</body>
+</html>

+ 1 - 1
routes/authRoutes/franchisee_contracts.php

@@ -9,7 +9,7 @@
     Route::post('/', [StudentContractController::class, 'store'])->middleware('permission:franchisee_contracts,add');
     Route::get('/{id}', [StudentContractController::class, 'show'])->whereNumber('id')->middleware('permission:franchisee_contracts,view');
     Route::put('/{id}', [StudentContractController::class, 'update'])->whereNumber('id')->middleware('permission:franchisee_contracts,edit');
-    Route::post('/{id}/file', [StudentContractController::class, 'attachFile'])->whereNumber('id')->middleware('permission:franchisee_contracts,edit');
+    Route::post('/{id}/generate-file', [StudentContractController::class, 'generateFile'])->whereNumber('id')->middleware('permission:franchisee_contracts,edit');
     Route::post('/{id}/signed-file', [StudentContractController::class, 'attachSignedFile'])->whereNumber('id')->middleware('permission:franchisee_contracts,edit');
     Route::get('/{id}/installments', [StudentContractController::class, 'installments'])->whereNumber('id')->middleware('permission:franchisee_contracts,view');
     Route::post('/{id}/freeze', [StudentContractController::class, 'freeze'])->whereNumber('id')->middleware('permission:franchisee_contracts,edit');