diff --git a/docs/audit-backlog.md b/docs/audit-backlog.md index 4463eece..f49c7722 100644 --- a/docs/audit-backlog.md +++ b/docs/audit-backlog.md @@ -66,9 +66,9 @@ _None outstanding._ | M10 | Unbounded list: `listMine` settlements `findByUser` no limit | src/Settlement/Controller/SettlementController.php:193 | perf-pagination | GET settlement list → paginate | | M11 | Unbounded list: admin `pendingComments` `findPending` no limit | src/Rating/Controller/RatingController.php:350 | perf-pagination | admin moderation large backlog → paginate | | M12 | Unbounded list: public `listComments` per-doctor no limit | src/Rating/Controller/RatingController.php:270 | perf-pagination | popular doctor comments → paginate | -| M13 | Missing index: `ServiceItem.section_id` FK fully unindexed (entity has zero indexes) | src/ClinicService/Entity/ServiceItem.php:23-24 | perf-index | EXPLAIN findBySection → no full scan | -| M14 | Missing index: `WalletTransaction(user_id, type)` composite for per-type SUM | src/Settlement/Entity/WalletTransaction.php:38-39 | perf-index | EXPLAIN balance SUM query | -| M15 | Missing index: `ClinicDoctorInvitation.doctor_id` FK unindexed | src/ClinicInvitation/Entity/ClinicDoctorInvitation.php:41-42 | perf-index | EXPLAIN findPendingByDoctor | +| ⚠️M13 | Missing index: `ServiceItem.section_id` FK fully unindexed (entity has zero indexes) | src/ClinicService/Entity/ServiceItem.php:23-24 | perf-index | EXPLAIN findBySection → no full scan | +| ✅M14 | Missing index: `WalletTransaction(user_id, type)` composite for per-type SUM | src/Settlement/Entity/WalletTransaction.php:38-39 | perf-index | **DONE** — added composite `idx_wallet_user_type` + migration. `InfraSmokeTest::testWalletUserTypeIndexExists`. | +| ⚠️M15 | Missing index: `ClinicDoctorInvitation.doctor_id` FK unindexed | src/ClinicInvitation/Entity/ClinicDoctorInvitation.php:41-42 | perf-index | **FALSE POSITIVE** — `doctor_id` has a FK → auto-indexed (IDX_26DCCFEB87F4FB17 exists). No change. | | M16 | `User.email` and `User.nationalCode` not unique → duplicate identities | src/Auth/Entity/User.php:31-32,37-38 | db-unique | Insert two users same email/national_code → 2nd rejected. ⚠️ check dup data first | | M17 | `Payment.gatewayToken` not unique | src/Payment/Entity/Payment.php:58-59 | db-unique | Two payments same gateway_token → rejected | | M18 | `DateOverride` no UNIQUE `(doctor_id, date)` → ambiguous schedule | src/Appointment/Entity/DateOverride.php:12 | db-unique | Two overrides same doctor+date → rejected | diff --git a/migrations/Version20260628165710.php b/migrations/Version20260628165710.php new file mode 100644 index 00000000..0c5c938f --- /dev/null +++ b/migrations/Version20260628165710.php @@ -0,0 +1,31 @@ +addSql('CREATE INDEX idx_wallet_user_type ON wallet_transactions (user_id, type)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX idx_wallet_user_type ON wallet_transactions'); + } +} diff --git a/src/Settlement/Entity/WalletTransaction.php b/src/Settlement/Entity/WalletTransaction.php index f61b2a32..b07a312d 100644 --- a/src/Settlement/Entity/WalletTransaction.php +++ b/src/Settlement/Entity/WalletTransaction.php @@ -11,6 +11,7 @@ use Symfony\Component\Uid\Uuid; #[ORM\Entity(repositoryClass: WalletTransactionRepository::class)] #[ORM\Table(name: 'wallet_transactions')] #[ORM\Index(columns: ['user_id', 'created_at'], name: 'idx_wallet_user_date')] +#[ORM\Index(columns: ['user_id', 'type'], name: 'idx_wallet_user_type')] class WalletTransaction { public const TYPE_CREDIT = 'credit'; diff --git a/tests/Smoke/InfraSmokeTest.php b/tests/Smoke/InfraSmokeTest.php index 183fe5e6..0eb22ecc 100644 --- a/tests/Smoke/InfraSmokeTest.php +++ b/tests/Smoke/InfraSmokeTest.php @@ -31,4 +31,16 @@ class InfraSmokeTest extends ApiTestCase $this->assertTrue($body['success']); $this->assertSame($user->getUuid(), $body['data']['uuid']); } + + public function testWalletUserTypeIndexExists(): void + { + $conn = $this->em->getConnection(); + $count = (int) $conn->fetchOne( + "SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'wallet_transactions' + AND INDEX_NAME = 'idx_wallet_user_type'" + ); + $this->assertGreaterThan(0, $count, 'composite index idx_wallet_user_type is missing'); + } }