Files
portal-api/tests/Feature/Console/InvoiceReminderTest.php
2026-10-08 11:25:28 +07:00

136 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Console;
use App\Enums\Invoice\InvoiceStatus;
use App\Enums\Order\OrderStatus;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\Order;
use App\Jobs\Mail\SendTemplateEmailJob;
use App\Services\InvoiceReminderService;
use App\Support\AutoEmailGate;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class InvoiceReminderTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// EmailTemplateService::send() đẩy qua SendTemplateEmailJob — fake queue để test
// không phụ thuộc template có trong DB hay không.
Queue::fake();
config(['billing.reminder_offsets' => [-7, -3, 0, 3]]);
$this->setAutoDisabled([]);
}
private function setAutoDisabled(array $scenarios): void
{
DB::table('setting')->updateOrInsert(['key' => 'email_settings'], ['value' => json_encode(['auto_disabled' => $scenarios])]);
}
public function test_disabled_automatic_reminder_is_not_sent_and_step_stays(): void
{
$this->setAutoDisabled([InvoiceReminderService::SCENARIO]);
$invoice = $this->makeInvoice(now()->addDays(5));
$this->artisan('invoices:send-reminders')->assertExitCode(0);
$this->assertSame(0, (int) $invoice->fresh()->reminder_step);
Queue::assertNotPushed(SendTemplateEmailJob::class);
}
public function test_manual_reminder_is_sent_while_automatic_is_disabled(): void
{
$this->setAutoDisabled([InvoiceReminderService::SCENARIO]);
$invoice = $this->makeInvoice(now()->addDays(5));
AutoEmailGate::markManual();
$this->assertTrue(app(InvoiceReminderService::class)->remind($invoice, force: true));
}
public function test_no_reminder_is_sent_before_the_first_offset(): void
{
$invoice = $this->makeInvoice(now()->addDays(30));
$this->artisan('invoices:send-reminders')->assertExitCode(0);
$this->assertSame(0, (int) $invoice->fresh()->reminder_step);
}
public function test_reminder_step_advances_once_per_offset_reached(): void
{
// Hạn còn 5 ngày → đã qua mốc -7, chưa tới mốc -3.
$invoice = $this->makeInvoice(now()->addDays(5));
$this->artisan('invoices:send-reminders')->assertExitCode(0);
$this->assertSame(1, (int) $invoice->fresh()->reminder_step);
// Chạy lại trong ngày không được gửi trùng.
$this->artisan('invoices:send-reminders')->assertExitCode(0);
$this->assertSame(1, (int) $invoice->fresh()->reminder_step);
}
public function test_an_overdue_invoice_reaches_the_later_offsets(): void
{
$invoice = $this->makeInvoice(now()->subDays(4), InvoiceStatus::OVERDUE->value);
$this->artisan('invoices:send-reminders')->assertExitCode(0);
// Đã qua cả 4 mốc: -7, -3, 0, +3.
$this->assertSame(4, (int) $invoice->fresh()->reminder_step);
}
public function test_a_paid_invoice_is_never_reminded(): void
{
$invoice = $this->makeInvoice(now()->subDays(10), InvoiceStatus::PAID->value);
$this->artisan('invoices:send-reminders')->assertExitCode(0);
$this->assertSame(0, (int) $invoice->fresh()->reminder_step);
$this->assertFalse(app(InvoiceReminderService::class)->isRemindable($invoice->fresh()));
}
/** Nút nhắc thủ công của kế toán gửi được kể cả khi mốc đó đã gửi rồi. */
public function test_manual_reminder_sends_even_when_the_offset_was_already_used(): void
{
$invoice = $this->makeInvoice(now()->addDays(5));
$reminders = app(InvoiceReminderService::class);
$this->assertTrue($reminders->remind($invoice));
$this->assertFalse($reminders->remind($invoice->fresh()));
$this->assertTrue($reminders->remind($invoice->fresh(), force: true));
}
private function makeInvoice(\Carbon\Carbon $dueDate, int $status = InvoiceStatus::UNPAID->value): Invoice
{
$customer = Customer::factory()->create();
$order = Order::create([
'order_num' => 'ORD-' . uniqid(),
'customer_id' => $customer->id,
'sub_total' => 100_000,
'total' => 100_000,
'status' => OrderStatus::PENDING->value,
]);
return Invoice::create([
'invoice_number' => 'INV-' . uniqid(),
'customer_id' => $customer->id,
'order_id' => $order->id,
'invoice_type' => 1,
'sub_total' => 100_000,
'tax' => 0,
'total' => 100_000,
'status' => $status,
'due_date' => $dueDate->toDateString(),
]);
}
}