feat(discount): add DiscountRule entity and session discount-rule audit columns
New Discount domain: DiscountRule (generic per-tenant rule with 6 types, priority, combinable, validity window, and per-type target fields) plus its repository. PatientSession gains applied_discount_rule_id/label audit columns and setDiscount() now records the source rule. Migration creates the table and columns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace App\Discount\Entity;
|
||||
|
||||
use App\Discount\Repository\DiscountRuleRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
/**
|
||||
* قانون تخفیف عمومی، per-tenant (owner = doctor|clinic). موتور تخفیف
|
||||
* (DiscountEngine) این قوانین را برای یک پرونده ارزیابی میکند.
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: DiscountRuleRepository::class)]
|
||||
#[ORM\Table(name: 'discount_rules')]
|
||||
#[ORM\Index(columns: ['owner_type', 'owner_id', 'active'], name: 'idx_discount_rules_owner')]
|
||||
class DiscountRule
|
||||
{
|
||||
public const TYPE_PATIENT_TAG = 'patient_tag';
|
||||
public const TYPE_INVOICE_AMOUNT = 'invoice_amount';
|
||||
public const TYPE_SPECIFIC_PATIENT = 'specific_patient';
|
||||
public const TYPE_OCCASION = 'occasion';
|
||||
public const TYPE_SERVICE = 'service';
|
||||
public const TYPE_VISIT_COUNT = 'visit_count';
|
||||
|
||||
public const TYPES = [
|
||||
self::TYPE_PATIENT_TAG,
|
||||
self::TYPE_INVOICE_AMOUNT,
|
||||
self::TYPE_SPECIFIC_PATIENT,
|
||||
self::TYPE_OCCASION,
|
||||
self::TYPE_SERVICE,
|
||||
self::TYPE_VISIT_COUNT,
|
||||
];
|
||||
|
||||
public const DISCOUNT_PERCENT = 'percent';
|
||||
public const DISCOUNT_FIXED = 'fixed';
|
||||
|
||||
/** زیرنوع مناسبت: birthday | null (بازهی تاریخی) */
|
||||
public const OCCASION_BIRTHDAY = 'birthday';
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 36, unique: true)]
|
||||
private string $uuid;
|
||||
|
||||
#[ORM\Column(name: 'owner_type', type: 'string', length: 10)]
|
||||
private string $ownerType;
|
||||
|
||||
#[ORM\Column(name: 'owner_id', type: 'integer')]
|
||||
private int $ownerId;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 120)]
|
||||
private string $name;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 20)]
|
||||
private string $type;
|
||||
|
||||
#[ORM\Column(name: 'discount_type', type: 'string', length: 10)]
|
||||
private string $discountType = self::DISCOUNT_PERCENT;
|
||||
|
||||
/** مقدار خام: درصد (۰..۱۰۰) یا ریال، بسته به discountType */
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $value = 0;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $priority = 0;
|
||||
|
||||
#[ORM\Column(type: 'boolean', options: ['default' => false])]
|
||||
private bool $combinable = false;
|
||||
|
||||
#[ORM\Column(type: 'boolean', options: ['default' => true])]
|
||||
private bool $active = true;
|
||||
|
||||
#[ORM\Column(name: 'valid_from', type: 'integer', nullable: true)]
|
||||
private ?int $validFrom = null;
|
||||
|
||||
#[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;
|
||||
|
||||
#[ORM\Column(name: 'target_record_id', type: 'integer', nullable: true)]
|
||||
private ?int $targetRecordId = null;
|
||||
|
||||
#[ORM\Column(name: 'target_service_item_id', type: 'integer', nullable: true)]
|
||||
private ?int $targetServiceItemId = null;
|
||||
|
||||
#[ORM\Column(name: 'min_amount_rials', type: 'integer', nullable: true)]
|
||||
private ?int $minAmountRials = null;
|
||||
|
||||
#[ORM\Column(name: 'min_visit_count', type: 'integer', nullable: true)]
|
||||
private ?int $minVisitCount = null;
|
||||
|
||||
#[ORM\Column(name: 'occasion_kind', type: 'string', length: 20, nullable: true)]
|
||||
private ?string $occasionKind = null;
|
||||
|
||||
#[ORM\Column(name: 'created_at', type: 'integer')]
|
||||
private int $createdAt;
|
||||
|
||||
#[ORM\Column(name: 'updated_at', type: 'integer')]
|
||||
private int $updatedAt;
|
||||
|
||||
public function __construct(string $ownerType, int $ownerId, string $name, string $type)
|
||||
{
|
||||
$this->uuid = Uuid::v4()->toRfc4122();
|
||||
$this->ownerType = $ownerType;
|
||||
$this->ownerId = $ownerId;
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->createdAt = time();
|
||||
$this->updatedAt = time();
|
||||
}
|
||||
|
||||
public function getId(): ?int { return $this->id; }
|
||||
public function getUuid(): string { return $this->uuid; }
|
||||
public function getOwnerType(): string { return $this->ownerType; }
|
||||
public function getOwnerId(): int { return $this->ownerId; }
|
||||
public function getName(): string { return $this->name; }
|
||||
public function getType(): string { return $this->type; }
|
||||
public function getDiscountType(): string { return $this->discountType; }
|
||||
public function getValue(): int { return $this->value; }
|
||||
public function getPriority(): int { return $this->priority; }
|
||||
public function isCombinable(): bool { return $this->combinable; }
|
||||
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 getMinAmountRials(): ?int { return $this->minAmountRials; }
|
||||
public function getMinVisitCount(): ?int { return $this->minVisitCount; }
|
||||
public function getOccasionKind(): ?string { return $this->occasionKind; }
|
||||
|
||||
public function setName(string $v): self { $this->name = $v; $this->touch(); return $this; }
|
||||
public function setType(string $v): self { $this->type = $v; $this->touch(); return $this; }
|
||||
public function setDiscountType(string $v): self { $this->discountType = $v; $this->touch(); return $this; }
|
||||
public function setValue(int $v): self { $this->value = $v; $this->touch(); return $this; }
|
||||
public function setPriority(int $v): self { $this->priority = $v; $this->touch(); return $this; }
|
||||
public function setCombinable(bool $v): self { $this->combinable = $v; $this->touch(); return $this; }
|
||||
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 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; }
|
||||
|
||||
private function touch(): void { $this->updatedAt = time(); }
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'uuid' => $this->uuid,
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'discount_type' => $this->discountType,
|
||||
'value' => $this->value,
|
||||
'priority' => $this->priority,
|
||||
'combinable' => $this->combinable,
|
||||
'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,
|
||||
'min_visit_count' => $this->minVisitCount,
|
||||
'occasion_kind' => $this->occasionKind,
|
||||
'created_at' => $this->createdAt,
|
||||
'updated_at' => $this->updatedAt,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user