feat(discount): admin Discount Management tab + uuid-based rule targets

Switch rule target references from int ids to uuids (frontend-friendly; the
engine compares uuids directly) via a migration. Add a Discount Management
tab to the subscription page with a full CRUD UI (DiscountTab): table + modal
form with per-type dynamic target fields (tenant tag / service cascade /
amount / visit count / specific patient / occasion + validity window),
priority and combinable/active toggles. Verified TSC + build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-17 12:02:03 +03:30
co-authored by Claude Fable 5
parent b3b1dad832
commit 18d9cc9812
8 changed files with 451 additions and 39 deletions
+17 -17
View File
@@ -79,15 +79,15 @@ class DiscountRule
#[ORM\Column(name: 'valid_to', type: 'integer', nullable: true)]
private ?int $validTo = null;
// ── target fields (بسته به type فقط یکی معنی‌دار است) ──────────────────────
#[ORM\Column(name: 'target_tag_id', type: 'integer', nullable: true)]
private ?int $targetTagId = null;
// ── target fields (بسته به type فقط یکی معنی‌دار است؛ uuid برای سازگاری با UI) ──
#[ORM\Column(name: 'target_tag_uuid', type: 'string', length: 36, nullable: true)]
private ?string $targetTagUuid = null;
#[ORM\Column(name: 'target_record_id', type: 'integer', nullable: true)]
private ?int $targetRecordId = null;
#[ORM\Column(name: 'target_record_uuid', type: 'string', length: 36, nullable: true)]
private ?string $targetRecordUuid = null;
#[ORM\Column(name: 'target_service_item_id', type: 'integer', nullable: true)]
private ?int $targetServiceItemId = null;
#[ORM\Column(name: 'target_service_item_uuid', type: 'string', length: 36, nullable: true)]
private ?string $targetServiceItemUuid = null;
#[ORM\Column(name: 'min_amount_rials', type: 'integer', nullable: true)]
private ?int $minAmountRials = null;
@@ -128,9 +128,9 @@ class DiscountRule
public function isActive(): bool { return $this->active; }
public function getValidFrom(): ?int { return $this->validFrom; }
public function getValidTo(): ?int { return $this->validTo; }
public function getTargetTagId(): ?int { return $this->targetTagId; }
public function getTargetRecordId(): ?int { return $this->targetRecordId; }
public function getTargetServiceItemId(): ?int { return $this->targetServiceItemId; }
public function getTargetTagUuid(): ?string { return $this->targetTagUuid; }
public function getTargetRecordUuid(): ?string { return $this->targetRecordUuid; }
public function getTargetServiceItemUuid(): ?string { return $this->targetServiceItemUuid; }
public function getMinAmountRials(): ?int { return $this->minAmountRials; }
public function getMinVisitCount(): ?int { return $this->minVisitCount; }
public function getOccasionKind(): ?string { return $this->occasionKind; }
@@ -144,9 +144,9 @@ class DiscountRule
public function setActive(bool $v): self { $this->active = $v; $this->touch(); return $this; }
public function setValidFrom(?int $v): self { $this->validFrom = $v; $this->touch(); return $this; }
public function setValidTo(?int $v): self { $this->validTo = $v; $this->touch(); return $this; }
public function setTargetTagId(?int $v): self { $this->targetTagId = $v; $this->touch(); return $this; }
public function setTargetRecordId(?int $v): self { $this->targetRecordId = $v; $this->touch(); return $this; }
public function setTargetServiceItemId(?int $v): self { $this->targetServiceItemId = $v; $this->touch(); return $this; }
public function setTargetTagUuid(?string $v): self { $this->targetTagUuid = $v; $this->touch(); return $this; }
public function setTargetRecordUuid(?string $v): self { $this->targetRecordUuid = $v; $this->touch(); return $this; }
public function setTargetServiceItemUuid(?string $v): self { $this->targetServiceItemUuid = $v; $this->touch(); return $this; }
public function setMinAmountRials(?int $v): self { $this->minAmountRials = $v; $this->touch(); return $this; }
public function setMinVisitCount(?int $v): self { $this->minVisitCount = $v; $this->touch(); return $this; }
public function setOccasionKind(?string $v): self { $this->occasionKind = $v; $this->touch(); return $this; }
@@ -166,10 +166,10 @@ class DiscountRule
'active' => $this->active,
'valid_from' => $this->validFrom,
'valid_to' => $this->validTo,
'target_tag_id' => $this->targetTagId,
'target_record_id' => $this->targetRecordId,
'target_service_item_id' => $this->targetServiceItemId,
'min_amount_rials' => $this->minAmountRials,
'target_tag_uuid' => $this->targetTagUuid,
'target_record_uuid' => $this->targetRecordUuid,
'target_service_item_uuid' => $this->targetServiceItemUuid,
'min_amount_rials' => $this->minAmountRials,
'min_visit_count' => $this->minVisitCount,
'occasion_kind' => $this->occasionKind,
'created_at' => $this->createdAt,