fix(db): business-key unique constraints (M16-M19)

Add unique constraints (one migration, no dup data in either DB):
- users.email, users.national_code (M16) — NULLs still allowed.
- payments.gateway_token (M17).
- date_overrides (doctor_id, date) (M18) — was a non-unique index.
- financial_breakdowns (payment_id, source) (M19) — anti double-accounting.

Regression: tests/Database/UniqueConstraintsTest (4 duplicate-insert cases).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-28 21:03:54 +03:30
co-authored by Claude Opus 4.8
parent ccb71e4371
commit 96a86dbfed
7 changed files with 122 additions and 8 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: DateOverrideRepository::class)]
#[ORM\Table(name: 'date_overrides')]
#[ORM\Index(columns: ['doctor_id', 'date'], name: 'idx_date_overrides_doctor_date')]
#[ORM\UniqueConstraint(name: 'uniq_date_override_doctor_date', columns: ['doctor_id', 'date'])]
class DateOverride
{
#[ORM\Id]
+2 -2
View File
@@ -28,13 +28,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column(name: 'password_hash', type: 'string', length: 255, nullable: true)]
private ?string $passwordHash = null;
#[ORM\Column(type: 'string', length: 100, nullable: true)]
#[ORM\Column(type: 'string', length: 100, nullable: true, unique: true)]
private ?string $email = null;
#[ORM\Column(name: 'real_name', type: 'string', length: 100, nullable: true)]
private ?string $realName = null;
#[ORM\Column(name: 'national_code', type: 'string', length: 10, nullable: true)]
#[ORM\Column(name: 'national_code', type: 'string', length: 10, nullable: true, unique: true)]
private ?string $nationalCode = null;
#[ORM\Column(name: 'national_code_verified', type: 'boolean')]
+1 -1
View File
@@ -55,7 +55,7 @@ class Payment
#[ORM\Column(type: 'string', length: 30)]
private string $type;
#[ORM\Column(name: 'gateway_token', type: 'string', length: 255, nullable: true)]
#[ORM\Column(name: 'gateway_token', type: 'string', length: 255, nullable: true, unique: true)]
private ?string $gatewayToken = null;
#[ORM\Column(name: 'reference_id', type: 'string', length: 255, nullable: true, unique: true)]
@@ -12,6 +12,7 @@ use Symfony\Component\Uid\Uuid;
#[ORM\Table(name: 'financial_breakdowns')]
#[ORM\Index(columns: ['representation_id', 'created_at'], name: 'idx_breakdown_rep_date')]
#[ORM\Index(columns: ['source', 'created_at'], name: 'idx_breakdown_source_date')]
#[ORM\UniqueConstraint(name: 'uniq_breakdown_payment_source', columns: ['payment_id', 'source'])]
class FinancialBreakdown
{
public const SOURCE_APPOINTMENT = 'appointment';