calculator = new DurationCalculator(); } /** آیتم ساختگی بدون دیتابیس: فقط چیزی که فرمول می‌خواند. */ private function item(int $id, ?int $solo, ?int $additional, int $price = 0): object { return new class ($id, $solo, $additional, $price) extends \App\ClinicService\Entity\ServiceItem { public function __construct( private readonly int $fakeId, private readonly ?int $solo, private readonly ?int $additional, private readonly int $price, ) {} public function getId(): ?int { return $this->fakeId; } public function getUuid(): string { return 'item-' . $this->fakeId; } public function getName(): string { return 'آیتم ' . $this->fakeId; } public function getPriceRials(): int { return $this->price; } public function getSoloDurationMinutes(): ?int { return $this->solo; } public function effectiveAdditionalMinutes(): ?int { return $this->additional ?? $this->solo; } }; } /** مثال خودِ مستند: صورت (۱۵/۸) + بیکینی (۱۲/۸) = ۲۳، نه ۲۷. */ public function testDocumentExample(): void { $total = $this->calculator->totalMinutes([ $this->item(1, 15, 8), $this->item(2, 12, 8), ]); self::assertSame(23, $total); } public function testASingleItemUsesItsSoloDuration(): void { self::assertSame(12, $this->calculator->totalMinutes([$this->item(2, 12, 8)])); } /** * ترتیب انتخاب نباید مدت را عوض کند — وگرنه بیمار با جابه‌جا کردن کلیک‌ها وقت * کوتاه‌تر می‌خرید. */ public function testResultIsIndependentOfSelectionOrder(): void { $a = $this->calculator->totalMinutes([$this->item(1, 15, 8), $this->item(2, 12, 8)]); $b = $this->calculator->totalMinutes([$this->item(2, 12, 8), $this->item(1, 15, 8)]); self::assertSame($a, $b); self::assertSame(23, $b); } /** آیتم بدون «مدت اضافه» همان مدت تنها را می‌گیرد — رفتار دادهٔ موجود. */ public function testMissingAdditionalFallsBackToSoloAndBehavesLikeAPlainSum(): void { $total = $this->calculator->totalMinutes([ $this->item(1, 15, null), $this->item(2, 12, null), ]); self::assertSame(27, $total, 'دادهٔ قدیمی دقیقاً مثل قبل جمع ساده می‌شود'); } public function testEmptySelectionIsZero(): void { self::assertSame(0, $this->calculator->totalMinutes([])); } /** مدت دستیِ کاربر بر فرمول مقدم است و پایه‌ای برای آن نمی‌شود. */ public function testExplicitOverrideWins(): void { $total = $this->calculator->totalMinutes( [$this->item(1, 15, 8), $this->item(2, 12, 8)], [], [1 => 40], ); self::assertSame(48, $total, '۴۰ دستی به‌عنوان لنگر + ۸ اضافهٔ دومی'); } public function testBreakdownNamesTheAnchor(): void { $rows = $this->calculator->breakdown([ $this->item(2, 12, 8), $this->item(1, 15, 8), ]); $byName = array_column($rows, 'counted_as', 'item_uuid'); self::assertSame('solo', $byName['item-1'], 'بزرگ‌ترین مدت تنها لنگر است'); self::assertSame('additional', $byName['item-2']); } public function testPriceIsAPlainSum(): void { $total = $this->calculator->totalPriceRials([ $this->item(1, 15, 8, 500_000), $this->item(2, 12, 8, 300_000), ]); self::assertSame(800_000, $total, 'قیمت برخلاف مدت جمع ساده است'); } }