From d5bb98e9530d37ca59aeba125f86615dede6fafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sun, 22 Mar 2026 22:26:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[esign]=20=EC=A0=84=EC=9E=90=EC=84=9C?= =?UTF-8?q?=EB=AA=85=20=EA=B3=A0=EB=8F=84=ED=99=94=20-=20=ED=95=84?= =?UTF-8?q?=EA=B8=B0=20=EA=B2=80=EC=A6=9D=20=ED=85=9C=ED=94=8C=EB=A6=BF?= =?UTF-8?q?=C2=B7=EA=B2=80=EC=A6=9D=20=EA=B2=B0=EA=B3=BC=20=EB=A7=88?= =?UTF-8?q?=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20=EB=B0=8F=20?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ESign/EsignHandwritingVerification.php | 62 +++++++++++++++++++ .../ESign/EsignVerificationTemplate.php | 54 ++++++++++++++++ ...ate_esign_verification_templates_table.php | 32 ++++++++++ ..._esign_handwriting_verifications_table.php | 51 +++++++++++++++ 4 files changed, 199 insertions(+) create mode 100644 app/Models/ESign/EsignHandwritingVerification.php create mode 100644 app/Models/ESign/EsignVerificationTemplate.php create mode 100644 database/migrations/2026_03_22_100000_create_esign_verification_templates_table.php create mode 100644 database/migrations/2026_03_22_110000_create_esign_handwriting_verifications_table.php diff --git a/app/Models/ESign/EsignHandwritingVerification.php b/app/Models/ESign/EsignHandwritingVerification.php new file mode 100644 index 00000000..8fface6f --- /dev/null +++ b/app/Models/ESign/EsignHandwritingVerification.php @@ -0,0 +1,62 @@ + 'integer', + 'similarity_score' => 'decimal:2', + 'is_passed' => 'boolean', + 'hwr_confidence' => 'decimal:2', + 'hwr_raw_response' => 'array', + 'attempt_number' => 'integer', + 'verified_at' => 'datetime', + 'options' => 'array', + ]; + + public function contract(): BelongsTo + { + return $this->belongsTo(EsignContract::class, 'contract_id'); + } + + public function signer(): BelongsTo + { + return $this->belongsTo(EsignSigner::class, 'signer_id'); + } + + public function scopeForTenant($query, $tenantId) + { + return $query->where('tenant_id', $tenantId); + } + + public function scopePassed($query) + { + return $query->where('is_passed', true); + } +} diff --git a/app/Models/ESign/EsignVerificationTemplate.php b/app/Models/ESign/EsignVerificationTemplate.php new file mode 100644 index 00000000..c75bf4b3 --- /dev/null +++ b/app/Models/ESign/EsignVerificationTemplate.php @@ -0,0 +1,54 @@ + 'array', + 'pass_threshold' => 'decimal:2', + 'max_attempts' => 'integer', + 'is_active' => 'boolean', + 'options' => 'array', + ]; + + public function scopeForTenant($query, $tenantId) + { + return $query->where('tenant_id', $tenantId); + } + + public function scopeActive($query) + { + return $query->where('is_active', true); + } + + public function getOption(string $key, mixed $default = null): mixed + { + $options = $this->options ?? []; + + return $options[$key] ?? $default; + } + + public function setOption(string $key, mixed $value): void + { + $options = $this->options ?? []; + $options[$key] = $value; + $this->options = $options; + } +} diff --git a/database/migrations/2026_03_22_100000_create_esign_verification_templates_table.php b/database/migrations/2026_03_22_100000_create_esign_verification_templates_table.php new file mode 100644 index 00000000..6519c830 --- /dev/null +++ b/database/migrations/2026_03_22_100000_create_esign_verification_templates_table.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedBigInteger('tenant_id'); + $table->string('name', 100); + $table->string('category', 50)->nullable(); + $table->json('steps'); // [{ "order": 1, "text": "본인은 위 내용을 확인하였습니다", "threshold": 80 }] + $table->decimal('pass_threshold', 5, 2)->default(80.00); + $table->unsignedTinyInteger('max_attempts')->default(5); + $table->boolean('is_active')->default(true); + $table->unsignedBigInteger('created_by')->nullable(); + $table->json('options')->nullable(); + $table->timestamps(); + + $table->index(['tenant_id', 'is_active'], 'idx_tenant_active'); + }); + } + + public function down(): void + { + Schema::dropIfExists('esign_verification_templates'); + } +}; diff --git a/database/migrations/2026_03_22_110000_create_esign_handwriting_verifications_table.php b/database/migrations/2026_03_22_110000_create_esign_handwriting_verifications_table.php new file mode 100644 index 00000000..b0cd089b --- /dev/null +++ b/database/migrations/2026_03_22_110000_create_esign_handwriting_verifications_table.php @@ -0,0 +1,51 @@ +id(); + $table->unsignedBigInteger('tenant_id'); + $table->unsignedBigInteger('contract_id'); + $table->unsignedBigInteger('signer_id'); + $table->unsignedTinyInteger('step_order')->default(1); + $table->string('prompt_text', 200); + $table->string('recognized_text', 500)->nullable(); + $table->decimal('similarity_score', 5, 2)->nullable(); + $table->boolean('is_passed')->default(false); + $table->string('handwriting_image', 500)->nullable(); + $table->string('hwr_engine', 50)->nullable(); + $table->decimal('hwr_confidence', 5, 2)->nullable(); + $table->json('hwr_raw_response')->nullable(); + $table->unsignedTinyInteger('attempt_number')->default(1); + $table->timestamp('verified_at')->nullable(); + $table->string('ip_address', 45)->nullable(); + $table->string('user_agent', 500)->nullable(); + $table->json('options')->nullable(); + $table->timestamps(); + + $table->index(['contract_id', 'signer_id'], 'idx_contract_signer'); + $table->index('tenant_id', 'idx_tenant'); + + $table->foreign('contract_id') + ->references('id') + ->on('esign_contracts') + ->onDelete('cascade'); + + $table->foreign('signer_id') + ->references('id') + ->on('esign_signers') + ->onDelete('cascade'); + }); + } + + public function down(): void + { + Schema::dropIfExists('esign_handwriting_verifications'); + } +};