'Full Administrator', 'guard_name' => 'admin']); $this->admin = User::factory()->create(); $this->admin->assignRole('Full Administrator'); $customer = Customer::factory()->create(); $order = $this->makeOrder($customer); $item = OrderItem::create(['order_id' => $order->id, 'product_id' => null, 'billing_cycle' => 'monthly', 'months' => 1, 'quantity' => 1, 'price' => 100000]); $this->service = Service::create(['customer_id' => $customer->id, 'order_item_id' => $item->id, 'status' => 'active']); $this->invoice = $this->makeInvoice($customer, $order); $this->foreignInvoice = $this->makeInvoice($customer, $this->makeOrder($customer)); } public function test_upload_list_copy_to_invoice_and_delete(): void { $base = "/api/v1/administrator/services/{$this->service->id}/attachments"; $admin = $this->actingAs($this->admin, 'admin'); $id = $admin->post($base, ['file' => UploadedFile::fake()->create('spec.pdf', 10, 'application/pdf')], ['Accept' => 'application/json']) ->assertCreated() ->json('data.id'); $admin->getJson($base) ->assertOk() ->assertJsonPath('data.attachments.0.filename', 'spec.pdf') ->assertJsonCount(1, 'data.invoices') ->assertJsonPath('data.invoices.0.id', $this->invoice->id); $admin->postJson("$base/$id/copy-to-invoice", ['invoice_id' => $this->foreignInvoice->id])->assertNotFound(); $admin->postJson("$base/$id/copy-to-invoice", ['invoice_id' => $this->invoice->id])->assertCreated(); $admin->postJson("$base/$id/copy-to-invoice", ['invoice_id' => $this->invoice->id])->assertCreated(); $copy = InvoiceAttachment::where('invoice_id', $this->invoice->id)->sole(); $this->assertSame(InvoiceAttachment::TYPE_DEPLOYMENT, $copy->type); Storage::disk('public')->assertExists($copy->path); $admin->deleteJson("$base/$id")->assertOk(); $this->assertDatabaseCount('service_attachments', 0); Storage::disk('public')->assertExists($copy->path); } public function test_list_and_detail_expose_linked_order_and_invoices(): void { $admin = $this->actingAs($this->admin, 'admin'); $admin->getJson('/api/v1/administrator/services') ->assertOk() ->assertJsonPath('data.items.0.order_item.order.order_num', $this->invoice->order->order_num) ->assertJsonCount(1, 'data.items.0.linked_invoices') ->assertJsonPath('data.items.0.linked_invoices.0.invoice_number', $this->invoice->invoice_number); $admin->getJson("/api/v1/administrator/services/{$this->service->id}") ->assertOk() ->assertJsonPath('data.linked_invoices.0.id', $this->invoice->id); } public function test_rejects_unsupported_file_type(): void { $this->actingAs($this->admin, 'admin') ->post("/api/v1/administrator/services/{$this->service->id}/attachments", ['file' => UploadedFile::fake()->create('x.exe', 10)], ['Accept' => 'application/json']) ->assertStatus(422); } private function makeOrder(Customer $customer): Order { return Order::create(['order_num' => 'ORD-' . uniqid(), 'customer_id' => $customer->id, 'sub_total' => 100_000, 'total' => 100_000, 'status' => OrderStatus::PENDING->value]); } private function makeInvoice(Customer $customer, Order $order): Invoice { 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' => InvoiceStatus::UNPAID->value, 'due_date' => now()->addDays(5)->toDateString(), ]); } }