- Extract import logic from AdminApiController into DoctorImportService (thin DoctorImportController keeps the same route/contract) - Surrogate users get marker role ROLE_UNCLAIMED_DOCTOR (+ backfill command app:doctors:backfill-surrogate-role) enabling safe deletion after claim - DB-level UNIQUE (source, medical_system_code) + concurrent-import retry - Doctor profile claim flow (climed.md): shahkar + PersonInfo identity checks via existing ApiIrService, Persian name normalization (PersianText), pessimistic-lock race protection, DoctorClaimRequest audit table (national code hashed, mobile masked), doctor_claim rate limiter, public claim-info endpoint, welcome SMS - Admin support tools: manual transfer endpoint + paginated doctor-claims audit list + owner_status filter/fields in admin doctors list - Least privilege: system owner now gets ROLE_IMPORTER (ROLE_ADMIN stripped), import endpoint accepts ADMIN|IMPORTER, isStaff includes IMPORTER - Headless crawler login: X-Service-Token header bypasses captcha only (rate limit + password checks intact; empty env = no bypass) - docs: doctor-claim.md (new), doctor-import.md, admin.md, doctor.md - tests: DoctorImportTest (6), DoctorClaimTest (11), PersianTextTest (5) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
51 lines
1.9 KiB
PHP
51 lines
1.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace DoctrineMigrations;
|
|
|
|
use Doctrine\DBAL\Schema\Schema;
|
|
use Doctrine\Migrations\AbstractMigration;
|
|
|
|
/**
|
|
* جدول ممیزی درخواستهای تصاحب پروفایل پزشک (claim).
|
|
* دادهٔ حساس خام ذخیره نمیشود: کد ملی hash (sha256) و موبایل mask.
|
|
*/
|
|
final class Version20260711151000 extends AbstractMigration
|
|
{
|
|
public function getDescription(): string
|
|
{
|
|
return 'doctor_claim_requests audit table for the profile-claim flow';
|
|
}
|
|
|
|
public function up(Schema $schema): void
|
|
{
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE doctor_claim_requests (
|
|
id INT AUTO_INCREMENT NOT NULL,
|
|
uuid VARCHAR(36) NOT NULL,
|
|
doctor_id INT NOT NULL,
|
|
user_id INT DEFAULT NULL,
|
|
status VARCHAR(20) NOT NULL,
|
|
national_code_hash VARCHAR(64) NOT NULL,
|
|
mobile_masked VARCHAR(15) NOT NULL,
|
|
verification_method VARCHAR(40) NOT NULL,
|
|
failure_reason VARCHAR(100) DEFAULT NULL,
|
|
created_at INT NOT NULL,
|
|
completed_at INT DEFAULT NULL,
|
|
UNIQUE INDEX UNIQ_claim_uuid (uuid),
|
|
INDEX idx_claim_doctor_status (doctor_id, status),
|
|
INDEX IDX_claim_user (user_id),
|
|
PRIMARY KEY(id)
|
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
|
SQL);
|
|
$this->addSql('ALTER TABLE doctor_claim_requests ADD CONSTRAINT FK_claim_doctor FOREIGN KEY (doctor_id) REFERENCES doctors (id) ON DELETE CASCADE');
|
|
$this->addSql('ALTER TABLE doctor_claim_requests ADD CONSTRAINT FK_claim_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL');
|
|
}
|
|
|
|
public function down(Schema $schema): void
|
|
{
|
|
$this->addSql('DROP TABLE doctor_claim_requests');
|
|
}
|
|
}
|