From bac44df6276a066830388b16df505dd864cbae08 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 3 Jul 2026 16:14:41 +0700 Subject: [PATCH] feat: implement Customer API for creating customers, refactor existing CustomerController, and update routes for API integration --- .../Admin/Master/CustomerController.php | 11 ----- .../Api/Master/CustomerApiController.php | 25 +++++++++++ bootstrap/app.php | 1 + .../orders/form/CustomerQuickCreateModal.vue | 4 +- routes/api.php | 15 +++++++ routes/web.php | 4 -- tests/Feature/Admin/Master/CustomerTest.php | 45 +++++++++++++++++++ 7 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 app/Http/Controllers/Api/Master/CustomerApiController.php create mode 100644 routes/api.php diff --git a/app/Http/Controllers/Admin/Master/CustomerController.php b/app/Http/Controllers/Admin/Master/CustomerController.php index 14677da..86d3a58 100644 --- a/app/Http/Controllers/Admin/Master/CustomerController.php +++ b/app/Http/Controllers/Admin/Master/CustomerController.php @@ -8,7 +8,6 @@ use App\Http\Requests\Admin\Master\CustomerRequest; use App\Models\Customer; use App\Services\Master\CustomerService; -use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Inertia\Inertia; @@ -58,14 +57,4 @@ public function destroy(Customer $customer): RedirectResponse return redirect()->route('admin.master.customers.index'); } - - public function storeApi(CustomerRequest $request): JsonResponse - { - $customer = $this->customerService->createAndReturn($request->validated()); - - return response()->json([ - 'id' => $customer->id, - 'name' => $customer->name, - ]); - } } diff --git a/app/Http/Controllers/Api/Master/CustomerApiController.php b/app/Http/Controllers/Api/Master/CustomerApiController.php new file mode 100644 index 0000000..ad13fef --- /dev/null +++ b/app/Http/Controllers/Api/Master/CustomerApiController.php @@ -0,0 +1,25 @@ +customerService->createAndReturn($request->validated()); + + return response()->json([ + 'id' => $customer->id, + 'name' => $customer->name, + ]); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 6bae236..b1f8a06 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -16,6 +16,7 @@ return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', + api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) diff --git a/resources/js/pages/admin/manage/orders/form/CustomerQuickCreateModal.vue b/resources/js/pages/admin/manage/orders/form/CustomerQuickCreateModal.vue index 550113e..ce183e0 100644 --- a/resources/js/pages/admin/manage/orders/form/CustomerQuickCreateModal.vue +++ b/resources/js/pages/admin/manage/orders/form/CustomerQuickCreateModal.vue @@ -22,7 +22,7 @@ import { FIELD_LIMITS } from '@/lib/field-limits'; import { Save } from '@lucide/vue'; import { reactive, ref } from 'vue'; import { toast } from 'vue-sonner'; -import { api } from '@/routes/admin/master/customers/store'; +import { store } from '@/routes/api/master/customers'; const open = defineModel('open', { default: false }); @@ -52,7 +52,7 @@ async function submit() { try { const customer = await apiFetch<{ id: number; name: string }>( - api.url(), + store.url(), { method: 'POST', body: JSON.stringify({ diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..8e8520f --- /dev/null +++ b/routes/api.php @@ -0,0 +1,15 @@ +name('api.')->group(function () { + Route::prefix('master')->name('master.')->group(function () { + Route::prefix('customers')->name('customers.')->group(function () { + Route::post('/', [CustomerApiController::class, 'store']) + ->middleware('permission:'.Permission::CUSTOMERS_CREATE->value) + ->name('store'); + }); + }); +}); diff --git a/routes/web.php b/routes/web.php index 0b1c443..b0f9be5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -203,10 +203,6 @@ ->middleware('permission:'.Permission::CUSTOMERS_CREATE->value) ->name('store'); - Route::post('api', [CustomerController::class, 'storeApi']) - ->middleware('permission:'.Permission::CUSTOMERS_CREATE->value) - ->name('store.api'); - Route::put('{customer}', [CustomerController::class, 'update']) ->middleware('permission:'.Permission::CUSTOMERS_UPDATE->value) ->name('update'); diff --git a/tests/Feature/Admin/Master/CustomerTest.php b/tests/Feature/Admin/Master/CustomerTest.php index f8d02f3..6feaed3 100644 --- a/tests/Feature/Admin/Master/CustomerTest.php +++ b/tests/Feature/Admin/Master/CustomerTest.php @@ -329,6 +329,51 @@ function createCustomerUserWithPermission(PermissionEnum ...$permissions): User $this->assertDatabaseHas('customers', ['id' => $customer->id]); }); }); +// ─── Customer API Store ─────────────────────────────────── + +describe('Customer API Store', function () { + test('authenticated user with permission can create a customer via api', function () { + $user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW, PermissionEnum::CUSTOMERS_CREATE); + + $response = $this->actingAs($user) + ->postJson(route('api.master.customers.store'), [ + 'name' => 'Ujang API', + 'phone_number' => '081234567890', + 'address' => 'Jl. Merdeka No. 1', + ]); + + $response->assertOk() + ->assertJsonStructure(['id', 'name']) + ->assertJson([ + 'name' => 'Ujang API', + ]); + + $this->assertDatabaseHas('customers', [ + 'name' => 'Ujang API', + 'phone_number' => '081234567890', + 'address' => 'Jl. Merdeka No. 1', + ]); + }); + + test('guest cannot create a customer via api', function () { + $response = $this->postJson(route('api.master.customers.store'), [ + 'name' => 'Ujang API', + ]); + + $response->assertUnauthorized(); + }); + + test('user without create permission is forbidden via api', function () { + $user = createCustomerUserWithPermission(PermissionEnum::CUSTOMERS_VIEW); + + $response = $this->actingAs($user) + ->postJson(route('api.master.customers.store'), [ + 'name' => 'Ujang API', + ]); + + $response->assertForbidden(); + }); +}); // ─── Customer Model ───────────────────────────────────────