[-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(), ]); } }