get(SiteConfigRepository::class); // کسورات را قطعی می‌کنیم تا انتظارِ عددی تست پایدار بماند. $config->set('sms_panel_fee_rials', '0'); $config->set('tax_enabled', '0'); $config->set('tax_percent', '0'); } private function makeDoctor(): Doctor { $user = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']); $doctor = new Doctor($user, 'دکتر تست سهم منشی'); $this->em->persist($doctor); $this->em->flush(); return $doctor; } private function makeSecretary(Doctor $doctor, float $percent, bool $enabled = true, ?Clinic $clinic = null): DoctorSecretary { $user = $this->createUser(['ROLE_USER', 'ROLE_SECRETARY']); $relation = new DoctorSecretary($doctor, $user, $clinic); $relation->setOnlineShareEnabled($enabled)->setOnlineSharePercent($percent); $this->em->persist($relation); $this->em->flush(); return $relation; } private function makeOnlinePayment(Doctor $doctor, ?Clinic $clinic = null): Payment { $start = strtotime('+30 days') + random_int(0, 500_000) * 7; $appointment = $this->newAppointment($doctor, $this->createUser(), $start, $start + 900, $clinic); $this->em->persist($appointment); $payment = new Payment($this->createUser(), self::GROSS, 'mock', Payment::TYPE_APPOINTMENT, ''); $payment->setAppointment($appointment); $this->stampTenant($payment); $this->em->persist($payment); $this->em->flush(); return $payment; } private function commission(): CommissionService { return static::getContainer()->get(CommissionService::class); } private function earnings(): SecretaryEarningRepository { return static::getContainer()->get(SecretaryEarningRepository::class); } public function testSecretaryEarnsItsPercentWithoutAnyRepresentation(): void { $doctor = $this->makeDoctor(); $relation = $this->makeSecretary($doctor, 5.0); $payment = $this->makeOnlinePayment($doctor); $this->commission()->processAppointment($payment, null, null, $doctor->getId()); $breakdown = static::getContainer()->get(FinancialBreakdownRepository::class)->findOneBy(['payment' => $payment]); self::assertNotNull($breakdown, 'نوبتِ بدون نماینده هم باید تفکیک مالی بسازد'); self::assertSame(500_000, $breakdown->getSecretaryShareRials()); // ۵٪ از ۱۰,۰۰۰,۰۰۰ self::assertSame(self::GROSS - 500_000, $breakdown->toArray()['system_share_rials']); self::assertSame(500_000, $this->earnings()->sumFor($relation->getSecretary())); self::assertSame( 500_000, static::getContainer()->get(SettlementRepository::class)->getWalletBalance($relation->getSecretary()), ); } public function testDisabledOrZeroPercentEarnsNothing(): void { $doctor = $this->makeDoctor(); $off = $this->makeSecretary($doctor, 5.0, enabled: false); $zero = $this->makeSecretary($doctor, 0.0); $payment = $this->makeOnlinePayment($doctor); $this->commission()->processAppointment($payment, null, null, $doctor->getId()); self::assertFalse(static::getContainer()->get(FinancialBreakdownRepository::class)->existsForPayment($payment)); self::assertSame(0, $this->earnings()->sumFor($off->getSecretary())); self::assertSame(0, $this->earnings()->sumFor($zero->getSecretary())); } public function testEachSecretaryGetsItsOwnPercent(): void { $doctor = $this->makeDoctor(); $first = $this->makeSecretary($doctor, 5.0); $second = $this->makeSecretary($doctor, 10.0); $payment = $this->makeOnlinePayment($doctor); $this->commission()->processAppointment($payment, null, null, $doctor->getId()); self::assertSame(500_000, $this->earnings()->sumFor($first->getSecretary())); self::assertSame(1_000_000, $this->earnings()->sumFor($second->getSecretary())); } public function testPaymentIsNeverSplitTwice(): void { $doctor = $this->makeDoctor(); $relation = $this->makeSecretary($doctor, 5.0); $payment = $this->makeOnlinePayment($doctor); $this->commission()->processAppointment($payment, null, null, $doctor->getId()); $this->commission()->processAppointment($payment, null, null, $doctor->getId()); self::assertSame(500_000, $this->earnings()->sumFor($relation->getSecretary())); self::assertSame(1, $this->earnings()->countFor($relation->getSecretary())); } /** مجموع درصدها > ۱۰۰ → کلیپ می‌شود و سهم سیستم منفی نمی‌شود. */ public function testSharesAreClippedSoTheSystemShareStaysPositive(): void { $doctor = $this->makeDoctor(); $a = $this->makeSecretary($doctor, 80.0); $b = $this->makeSecretary($doctor, 60.0); $payment = $this->makeOnlinePayment($doctor); $this->commission()->processAppointment($payment, null, null, $doctor->getId()); $breakdown = static::getContainer()->get(FinancialBreakdownRepository::class)->findOneBy(['payment' => $payment]); self::assertGreaterThanOrEqual(0, $breakdown->toArray()['system_share_rials']); self::assertSame(self::GROSS, $breakdown->getSecretaryShareRials() + $breakdown->toArray()['system_share_rials']); self::assertGreaterThan(0, $this->earnings()->sumFor($a->getSecretary())); self::assertGreaterThan(0, $this->earnings()->sumFor($b->getSecretary())); } public function testClinicAppointmentCreditsTheClinicSecretaries(): void { $doctor = $this->makeDoctor(); $owner = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']); $clinic = new Clinic($owner); $clinic->setName('کلینیک تست سهم منشی'); $clinic->getDoctors()->add($doctor); $this->em->persist($clinic); $this->em->flush(); $clinicSecretary = $this->makeSecretary($doctor, 7.0, clinic: $clinic); $officeSecretary = $this->makeSecretary($doctor, 5.0); // مطب شخصی — نباید سهم بگیرد $payment = $this->makeOnlinePayment($doctor, $clinic); $this->commission()->processAppointment($payment, null, null, $doctor->getId()); self::assertSame(700_000, $this->earnings()->sumFor($clinicSecretary->getSecretary())); self::assertSame(0, $this->earnings()->sumFor($officeSecretary->getSecretary())); } /** سهم از «خالص» گرفته می‌شود: با مالیات و هزینهٔ پیامک، مبنا کمتر است. */ public function testShareIsCalculatedOnTheNetAmount(): void { $config = static::getContainer()->get(SiteConfigRepository::class); $config->set('sms_panel_fee_rials', '1000000'); $config->set('tax_enabled', '1'); $config->set('tax_percent', '10'); $doctor = $this->makeDoctor(); $relation = $this->makeSecretary($doctor, 10.0); $payment = $this->makeOnlinePayment($doctor); $this->commission()->processAppointment($payment, null, null, $doctor->getId()); // ۱۰,۰۰۰,۰۰۰ − ۱,۰۰۰,۰۰۰ = ۹,۰۰۰,۰۰۰ ؛ مالیات = 9,000,000×10/110 = 818,182 // خالص = 8,181,818 ؛ سهم ۱۰٪ = 818,182 self::assertSame(818_182, $this->earnings()->sumFor($relation->getSecretary())); } }