refactor: remove unused docblocks in CustomerService and add getSelectOptions method for enhanced customer data retrieval
This commit is contained in:
parent
8159e45e3a
commit
9a35c1cae2
@ -8,9 +8,6 @@
|
|||||||
|
|
||||||
class CustomerService
|
class CustomerService
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
|
||||||
*/
|
|
||||||
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
$query = Customer::query()
|
$query = Customer::query()
|
||||||
@ -30,17 +27,11 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
|||||||
->withQueryString();
|
->withQueryString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $validated
|
|
||||||
*/
|
|
||||||
public function create(array $validated): void
|
public function create(array $validated): void
|
||||||
{
|
{
|
||||||
Customer::create($validated);
|
Customer::create($validated);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $validated
|
|
||||||
*/
|
|
||||||
public function update(Customer $customer, array $validated): void
|
public function update(Customer $customer, array $validated): void
|
||||||
{
|
{
|
||||||
$customer->update($validated);
|
$customer->update($validated);
|
||||||
@ -51,6 +42,18 @@ public function delete(Customer $customer): void
|
|||||||
$customer->delete();
|
$customer->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSelectOptions(): array
|
||||||
|
{
|
||||||
|
return Customer::query()
|
||||||
|
->orderBy('name')
|
||||||
|
->get(['id', 'name'])
|
||||||
|
->map(fn (Customer $customer) => [
|
||||||
|
'value' => $customer->id,
|
||||||
|
'label' => $customer->name,
|
||||||
|
])
|
||||||
|
->all();
|
||||||
|
}
|
||||||
|
|
||||||
private function applySorting(Builder $query, string $sort, string $direction): void
|
private function applySorting(Builder $query, string $sort, string $direction): void
|
||||||
{
|
{
|
||||||
if (in_array($sort, ['name', 'phone_number', 'address'], true)) {
|
if (in_array($sort, ['name', 'phone_number', 'address'], true)) {
|
||||||
|
|||||||
@ -2,16 +2,16 @@
|
|||||||
import { Head } from '@inertiajs/vue3';
|
import { Head } from '@inertiajs/vue3';
|
||||||
import { Plus } from '@lucide/vue';
|
import { Plus } from '@lucide/vue';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { createColumns } from './table/columns';
|
|
||||||
import CustomerFormModal from './form/CustomerFormModal.vue';
|
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { useCan } from '@/composables/useCan';
|
import { useCan } from '@/composables/useCan';
|
||||||
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
||||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||||
import type { DataTableSort } from '@/types/data-table';
|
|
||||||
import type { CustomerListItem, PaginatedCustomers } from '@/types/customer';
|
import type { CustomerListItem, PaginatedCustomers } from '@/types/customer';
|
||||||
|
import type { DataTableSort } from '@/types/data-table';
|
||||||
|
import CustomerFormModal from './form/CustomerFormModal.vue';
|
||||||
|
import { createColumns } from './table/columns';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
customers: PaginatedCustomers;
|
customers: PaginatedCustomers;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { ColumnDef } from '@tanstack/vue-table';
|
import type { ColumnDef } from '@tanstack/vue-table';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { DataTableColumnHeader } from '@/components/data-table';
|
import { DataTableColumnHeader } from '@/components/data-table';
|
||||||
import DataTableActions from './data-table-actions.vue';
|
|
||||||
import type { CustomerListItem } from '@/types/customer';
|
import type { CustomerListItem } from '@/types/customer';
|
||||||
|
import DataTableActions from './data-table-actions.vue';
|
||||||
|
|
||||||
export function createColumns(onEdit: (customer: CustomerListItem) => void): ColumnDef<CustomerListItem>[] {
|
export function createColumns(onEdit: (customer: CustomerListItem) => void): ColumnDef<CustomerListItem>[] {
|
||||||
return [
|
return [
|
||||||
|
|||||||
349
tests/Feature/Admin/Master/CustomerTest.php
Normal file
349
tests/Feature/Admin/Master/CustomerTest.php
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\Permission as PermissionEnum;
|
||||||
|
use App\Models\Customer;
|
||||||
|
use App\Models\User;
|
||||||
|
use Database\Seeders\RolePermissionSeeder;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->seed(RolePermissionSeeder::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Helper ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
function createCustomerUserWithPermission(PermissionEnum ...$permissions): User
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$user->givePermissionTo(
|
||||||
|
array_merge(
|
||||||
|
[PermissionEnum::DASHBOARD_VIEW->value],
|
||||||
|
array_map(fn (PermissionEnum $p) => $p->value, $permissions)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$user->forgetCachedPermissions();
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Index ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Customer Index', function () {
|
||||||
|
test('authenticated user with permission can view customer index', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW);
|
||||||
|
|
||||||
|
Customer::factory()->count(3)->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.master.customers.index'))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest is redirected to login', function () {
|
||||||
|
$this->get(route('admin.master.customers.index'))
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without permission is forbidden', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.master.customers.index'))
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index returns customers with pagination', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW);
|
||||||
|
|
||||||
|
Customer::factory()->count(15)->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.master.customers.index'))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index can search customers by name', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW);
|
||||||
|
|
||||||
|
Customer::factory()->create(['name' => 'Budi']);
|
||||||
|
Customer::factory()->create(['name' => 'Ucup']);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.master.customers.index', ['search' => 'Maju']))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Store ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Customer Store', function () {
|
||||||
|
test('authenticated user with permission can create a customer', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.master.customers.store'), [
|
||||||
|
'name' => 'Ujang',
|
||||||
|
'phone_number' => '081234567890',
|
||||||
|
'address' => 'Jl. Merdeka No. 1',
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.master.customers.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('customers', [
|
||||||
|
'name' => 'Ujang',
|
||||||
|
'phone_number' => '081234567890',
|
||||||
|
'address' => 'Jl. Merdeka No. 1',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest cannot create a customer', function () {
|
||||||
|
$this->post(route('admin.master.customers.store'), [
|
||||||
|
'name' => 'Ujang',
|
||||||
|
])->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without create permission is forbidden', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.master.customers.store'), [
|
||||||
|
'name' => 'Ujang',
|
||||||
|
])
|
||||||
|
->assertForbidden();
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('customers', 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name is required', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.master.customers.store'), [
|
||||||
|
'name' => '',
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('name');
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('customers', 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name must not exceed 200 characters', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.master.customers.store'), [
|
||||||
|
'name' => str_repeat('a', 201),
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('name');
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('customers', 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phone number must start with 08', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.master.customers.store'), [
|
||||||
|
'name' => 'Ujang',
|
||||||
|
'phone_number' => '0211234567',
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('phone_number');
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('customers', 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phone number can be null', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.master.customers.store'), [
|
||||||
|
'name' => 'Ujang',
|
||||||
|
'phone_number' => null,
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.master.customers.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('customers', [
|
||||||
|
'name' => 'Ujang',
|
||||||
|
'phone_number' => null,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('address can be null', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.master.customers.store'), [
|
||||||
|
'name' => 'Ujang',
|
||||||
|
'address' => null,
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.master.customers.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('customers', [
|
||||||
|
'name' => 'Ujang',
|
||||||
|
'address' => null,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Update ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Customer Update', function () {
|
||||||
|
test('authenticated user with permission can update a customer', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_UPDATE);
|
||||||
|
|
||||||
|
$customer = Customer::factory()->create(['name' => 'Nama Lama']);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.master.customers.update', $customer), [
|
||||||
|
'name' => 'Nama Baru',
|
||||||
|
'phone_number' => '089876543210',
|
||||||
|
'address' => 'Jl. Baru No. 2',
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.master.customers.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('customers', [
|
||||||
|
'id' => $customer->id,
|
||||||
|
'name' => 'Nama Baru',
|
||||||
|
'phone_number' => '089876543210',
|
||||||
|
'address' => 'Jl. Baru No. 2',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest cannot update a customer', function () {
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
|
||||||
|
$this->put(route('admin.master.customers.update', $customer), [
|
||||||
|
'name' => 'Nama Baru',
|
||||||
|
])->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without update permission is forbidden', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW);
|
||||||
|
|
||||||
|
$customer = Customer::factory()->create(['name' => 'Nama Lama']);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.master.customers.update', $customer), [
|
||||||
|
'name' => 'Nama Baru',
|
||||||
|
])
|
||||||
|
->assertForbidden();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('customers', [
|
||||||
|
'id' => $customer->id,
|
||||||
|
'name' => 'Nama Lama',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name is required on update', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_UPDATE);
|
||||||
|
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.master.customers.update', $customer), [
|
||||||
|
'name' => '',
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name must not exceed 200 characters on update', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_UPDATE);
|
||||||
|
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.master.customers.update', $customer), [
|
||||||
|
'name' => str_repeat('a', 201),
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phone number validation on update', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_UPDATE);
|
||||||
|
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.master.customers.update', $customer), [
|
||||||
|
'name' => 'Nama Valid',
|
||||||
|
'phone_number' => '123invalid',
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('phone_number');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Destroy ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Customer Destroy', function () {
|
||||||
|
test('authenticated user with permission can delete a customer', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_DELETE);
|
||||||
|
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->delete(route('admin.master.customers.destroy', $customer))
|
||||||
|
->assertRedirect(route('admin.master.customers.index'));
|
||||||
|
|
||||||
|
$this->assertSoftDeleted('customers', [
|
||||||
|
'id' => $customer->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest cannot delete a customer', function () {
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
|
||||||
|
$this->delete(route('admin.master.customers.destroy', $customer))
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
|
||||||
|
$this->assertNotSoftDeleted('customers', [
|
||||||
|
'id' => $customer->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without delete permission is forbidden', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW);
|
||||||
|
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->delete(route('admin.master.customers.destroy', $customer))
|
||||||
|
->assertForbidden();
|
||||||
|
|
||||||
|
$this->assertNotSoftDeleted('customers', [
|
||||||
|
'id' => $customer->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deleted customer is soft deleted', function () {
|
||||||
|
$user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_DELETE);
|
||||||
|
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->delete(route('admin.master.customers.destroy', $customer));
|
||||||
|
|
||||||
|
$this->assertSoftDeleted('customers', ['id' => $customer->id]);
|
||||||
|
$this->assertDatabaseHas('customers', ['id' => $customer->id]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Customer Model ───────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Customer Model', function () {
|
||||||
|
test('customer uses soft deletes', function () {
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
|
||||||
|
$customer->delete();
|
||||||
|
|
||||||
|
expect($customer->trashed())->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('customer can have orders', function () {
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
|
||||||
|
expect($customer->orders)->toBeInstanceOf(Collection::class);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user