feat(billing): add command and tests for backfilling missing invoices for paid sessions
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Billing;
|
||||
|
||||
use App\Billing\Entity\Invoice;
|
||||
use App\Billing\Service\SessionBillingService;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Patient\Entity\PatientRecord;
|
||||
use App\Patient\Entity\PatientSession;
|
||||
use App\Patient\Service\PatientService;
|
||||
use App\Tests\ApiTestCase;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* جبران دادهٔ گذشته: مراجعههایی که پیش از اصلاحِ قاعده پول گرفتند ولی صورتحساب
|
||||
* نداشتند و برای همین در فهرست «پرداختها» دیده نمیشدند.
|
||||
*/
|
||||
class BackfillPaidSessionInvoicesTest extends ApiTestCase
|
||||
{
|
||||
/**
|
||||
* همیشه با --tenant اجرا میشود: db_test بین اجراها پاک نمیشود و بدون محدودکردن
|
||||
* محیط، هر تست کلِ دادهٔ قدیمیِ پایگاه را هم بکفیل میکرد.
|
||||
*/
|
||||
private function runCommand(int $doctorId, bool $dryRun = false): string
|
||||
{
|
||||
$application = new Application(static::$kernel);
|
||||
$tester = new CommandTester($application->find('app:billing:backfill-paid-invoices'));
|
||||
|
||||
$args = ['--tenant' => 'doctor:' . $doctorId];
|
||||
if ($dryRun) {
|
||||
$args['--dry-run'] = true;
|
||||
}
|
||||
$tester->execute($args);
|
||||
|
||||
return $tester->getDisplay();
|
||||
}
|
||||
|
||||
/** @return array{0: PatientSession, 1: int} مراجعهٔ پرداختشدهٔ بدون صورتحساب و شناسهٔ پزشکش */
|
||||
private function makePaidSessionWithoutInvoice(): array
|
||||
{
|
||||
$user = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($user, 'دکتر بکفیل');
|
||||
$doctor->setMobileNumber($user->getMobileNumber());
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$record = new PatientRecord('doctor', $doctor->getId(), $this->createUser(), 'doctor', $doctor->getId());
|
||||
$this->em->persist($record);
|
||||
|
||||
$session = new PatientSession($record);
|
||||
$session->setVisitPriceRials(3_000_000);
|
||||
$session->applyShares(3_000_000, 0, 0, 3_000_000);
|
||||
$this->em->persist($session);
|
||||
$this->em->flush();
|
||||
|
||||
// مسیر واقعی ثبت پول، ولی صورتحسابی که همان مسیر میسازد عمداً حذف میشود
|
||||
// تا وضعیتِ پیش از اصلاح بازسازی شود.
|
||||
static::getContainer()->get(PatientService::class)
|
||||
->addSessionPayment($session, 'cash', 1_000_000);
|
||||
|
||||
foreach ($this->em->getRepository(Invoice::class)->findBy(['patientSessionId' => $session->getId()]) as $invoice) {
|
||||
$this->em->remove($invoice);
|
||||
}
|
||||
$this->em->flush();
|
||||
|
||||
return [$session, $doctor->getId()];
|
||||
}
|
||||
|
||||
public function testBackfillCreatesTheMissingFinalizedInvoice(): void
|
||||
{
|
||||
[$session, $doctorId] = $this->makePaidSessionWithoutInvoice();
|
||||
|
||||
$this->runCommand($doctorId);
|
||||
$this->em->clear();
|
||||
|
||||
$invoices = $this->em->getRepository(Invoice::class)->findBy(['patientSessionId' => $session->getId()]);
|
||||
self::assertCount(1, $invoices);
|
||||
self::assertSame(Invoice::STATUS_FINALIZED, $invoices[0]->getStatus());
|
||||
self::assertSame(3_000_000, $invoices[0]->getTotalRials());
|
||||
}
|
||||
|
||||
public function testDryRunWritesNothing(): void
|
||||
{
|
||||
[$session, $doctorId] = $this->makePaidSessionWithoutInvoice();
|
||||
|
||||
$display = $this->runCommand($doctorId, dryRun: true);
|
||||
$this->em->clear();
|
||||
|
||||
self::assertStringContainsString((string) $session->getId(), $display);
|
||||
self::assertSame([], $this->em->getRepository(Invoice::class)->findBy(['patientSessionId' => $session->getId()]));
|
||||
}
|
||||
|
||||
public function testRunningTwiceDoesNotDuplicateInvoices(): void
|
||||
{
|
||||
[$session, $doctorId] = $this->makePaidSessionWithoutInvoice();
|
||||
|
||||
$this->runCommand($doctorId);
|
||||
$this->runCommand($doctorId);
|
||||
$this->em->clear();
|
||||
|
||||
self::assertCount(1, $this->em->getRepository(Invoice::class)->findBy(['patientSessionId' => $session->getId()]));
|
||||
}
|
||||
|
||||
/** مرزی: مراجعهٔ بدون پرداخت نباید صورتحساب بگیرد. */
|
||||
public function testUnpaidSessionIsLeftAlone(): void
|
||||
{
|
||||
$user = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($user, 'دکتر بدون پرداخت');
|
||||
$doctor->setMobileNumber($user->getMobileNumber());
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$record = new PatientRecord('doctor', $doctor->getId(), $this->createUser(), 'doctor', $doctor->getId());
|
||||
$this->em->persist($record);
|
||||
|
||||
$session = new PatientSession($record);
|
||||
$session->setVisitPriceRials(1_000_000);
|
||||
$session->applyShares(1_000_000, 0, 0, 1_000_000);
|
||||
$this->em->persist($session);
|
||||
$this->em->flush();
|
||||
|
||||
$this->runCommand($doctor->getId());
|
||||
$this->em->clear();
|
||||
|
||||
self::assertSame([], $this->em->getRepository(Invoice::class)->findBy(['patientSessionId' => $session->getId()]));
|
||||
}
|
||||
|
||||
/** رفتار سرویس، مستقل از کامند: پرداختدار یعنی صورتحساب لازم. */
|
||||
public function testServiceCreatesInvoiceOnlyWhenMoneyOrInsuranceExists(): void
|
||||
{
|
||||
$billing = static::getContainer()->get(SessionBillingService::class);
|
||||
[$session] = $this->makePaidSessionWithoutInvoice();
|
||||
$record = $session->getRecord();
|
||||
|
||||
$invoice = $billing->ensureFinalizedInvoice($session, $record->getEntityType(), $record->getEntityId());
|
||||
|
||||
self::assertNotNull($invoice);
|
||||
self::assertSame(Invoice::STATUS_FINALIZED, $invoice->getStatus());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user