Jelajahi Sumber

feat(contracts): add automatic sequential protocol generation and next-protocol endpoint

alvesantos 1 Minggu lalu
induk
melakukan
6bfc4f9e08

+ 7 - 0
app/Http/Controllers/StudentContractController.php

@@ -38,6 +38,13 @@ public function store(StudentContractRequest $request): JsonResponse
         return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.created'), code: 201);
     }
 
+    public function nextProtocol(): JsonResponse
+    {
+        return $this->successResponse(payload: [
+            'protocol' => $this->service->generateNextProtocol(),
+        ]);
+    }
+
     public function show(int $id): JsonResponse
     {
         $item = $this->service->findById($id);

+ 56 - 0
app/Services/StudentContractService.php

@@ -5,6 +5,7 @@
 use App\Models\StudentContract;
 use App\Models\StudentContractInstallment;
 use App\Models\StudentMedia;
+use App\Models\Unit;
 use Barryvdh\DomPDF\Facade\Pdf;
 use Carbon\Carbon;
 use Illuminate\Database\Eloquent\Collection;
@@ -62,6 +63,47 @@ public function findById(int $id): ?StudentContract
         return $contract;
     }
 
+    public function generateNextProtocol(): string
+    {
+        $nextNumber = $this->getMaxProtocolNumber() + 1;
+
+        return str_pad((string) $nextNumber, 6, '0', STR_PAD_LEFT);
+    }
+
+    public function getMaxProtocolNumber(): int
+    {
+        $driver = DB::connection()->getDriverName();
+
+        if ($driver === 'pgsql') {
+            $max = StudentContract::withTrashed()
+                ->whereNotNull('protocol')
+                ->whereRaw("protocol ~ '^[0-9]+$'")
+                ->selectRaw("COALESCE(MAX(CAST(protocol AS BIGINT)), 0) as max_protocol")
+                ->value('max_protocol');
+
+            return (int) $max;
+        }
+
+        if ($driver === 'mysql') {
+            $max = StudentContract::withTrashed()
+                ->whereNotNull('protocol')
+                ->whereRaw("protocol REGEXP '^[0-9]+$'")
+                ->selectRaw("COALESCE(MAX(CAST(protocol AS UNSIGNED)), 0) as max_protocol")
+                ->value('max_protocol');
+
+            return (int) $max;
+        }
+
+        $max = StudentContract::withTrashed()
+            ->whereNotNull('protocol')
+            ->get(['protocol'])
+            ->filter(fn ($contract) => ctype_digit((string) $contract->protocol))
+            ->map(fn ($contract) => (int) $contract->protocol)
+            ->max();
+
+        return (int) ($max ?? 0);
+    }
+
     public function create(array $data): StudentContract
     {
         if (! empty($data['due_day'])) {
@@ -70,6 +112,20 @@ public function create(array $data): StudentContract
 
         unset($data['due_day']);
 
+        $unitId = $data['unit_id'] ?? null;
+        $isAutomatic = true;
+
+        if ($unitId) {
+            $unit = Unit::find($unitId);
+            if ($unit && $unit->automatic_protocol === false) {
+                $isAutomatic = false;
+            }
+        }
+
+        if ($isAutomatic) {
+            $data['protocol'] = $this->generateNextProtocol();
+        }
+
         $contract = StudentContract::create($data);
 
         $this->generateInstallments($contract);

+ 1 - 0
routes/authRoutes/franchisee_contracts.php

@@ -6,6 +6,7 @@
 
 Route::prefix('franchisee/contracts')->group(function () {
     Route::get('/', [StudentContractController::class, 'index'])->middleware('permission:franchisee_contracts,view');
+    Route::get('/next-protocol', [StudentContractController::class, 'nextProtocol'])->middleware('permission:franchisee_contracts,view');
     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');