add api crud account
WMS API/ERP-API/pipeline/head This commit looks good Details

This commit is contained in:
Husnu Setiawan 2025-02-12 15:29:39 +07:00
parent 6e5f30df4d
commit 43937cbb04
26 changed files with 904 additions and 2 deletions

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Accounting\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\Accounting\Account\DeleteRequest;
use App\Http\Resources\Accounting\Account\ListResource as RowResource;
use App\Repositories\Accounting\AccountRepository;
use App\Models\Account;
class DeleteController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(DeleteRequest $request, Account $account, AccountRepository $repository)
{
$data = $repository->delete($account);
return response()->json([
"success" => true
]);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Accounting\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\Accounting\Account\ListRequest;
use App\Http\Resources\Accounting\Account\ListResource;
use App\Repositories\Accounting\AccountRepository;
class ListController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(ListRequest $request, AccountRepository $repository)
{
$params = $request->validated();
$data = $repository->list($params);
return ListResource::collection($data);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Accounting\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\Accounting\Account\StoreRequest;
use App\Http\Resources\Accounting\Account\ListResource as RowResource;
use App\Repositories\Accounting\AccountRepository;
class StoreController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(StoreRequest $request, AccountRepository $repository)
{
$params = $request->validated();
$data = $repository->create($params);
return RowResource::make($data);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Accounting\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\Accounting\Account\UpdateRequest;
use App\Http\Resources\Accounting\Account\ListResource as RowResource;
use App\Repositories\Accounting\AccountRepository;
use App\Models\Account;
class UpdateController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(UpdateRequest $request, Account $account, AccountRepository $repository)
{
$params = $request->validated();
$data = $repository->update($account, $params);
return RowResource::make($data);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Requests\Accounting\Account;
use Illuminate\Foundation\Http\FormRequest;
class DeleteRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return auth()->user()->checkPermission("accounting.account:delete");
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
];
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Http\Requests\Accounting\Account;
use Illuminate\Foundation\Http\FormRequest;
class ListRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return auth()->user()->checkPermission("accounting.account:read");
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'limit' => 'nullable',
'offset' => 'nullable',
'search' => 'nullable',
'filter' => 'nullable|array',
'filter.*.column' => 'required|in:name',
'filter.*.operator' => 'nullable|in:eq,in',
'filter.*.query' => 'required',
'sort' => 'nullable|array',
'sort.column' => 'nullable|in:name',
'sort.dir' => 'nullable',
];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Accounting\Account;
use Illuminate\Foundation\Http\FormRequest;
class StoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return auth()->user()->checkPermission("accounting.account:create");
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'required|string',
'code' => 'required|string|unique:accounts',
'sheet' => 'required|string',
'type' => 'required|string',
'category' => 'required|string',
'subcategory' => 'nullable|string',
'structure' => 'required|string',
];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Accounting\Account;
use Illuminate\Foundation\Http\FormRequest;
class UpdateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return auth()->user()->checkPermission("accounting.account:update");
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'required|string',
'code' => 'required|string|unique:accounts,code,'.$this->account->id,
'sheet' => 'required|string',
'type' => 'required|string',
'category' => 'required|string',
'subcategory' => 'nullable|string',
'structure' => 'required|string',
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Resources\Accounting\Account;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ListResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
"id" => $this->id,
"name" => $this->name,
"code" => $this->code,
"sheet" => $this->sheet,
"type" => $this->type,
"category" => $this->category,
"subcategory" => $this->subcategory,
"structure" => $this->structure
];
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Resources\Accounting\Account;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class SimpleResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
"id" => $this->id,
"name" => $this->name,
];
}
}

23
app/Models/Account.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Account extends Model
{
use HasFactory;
use LogsActivity;
protected $fillable = ['code', 'name','sheet','type','category','subcategory','structure'];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
}

View File

@ -5,9 +5,14 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Role extends Model
{
use HasFactory;
use LogsActivity;
/**
* The attributes that are mass assignable.
*
@ -20,4 +25,9 @@ class Role extends Model
public function permissions(){
return $this->belongsToMany(Permission::class,"role_permission");
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
}

View File

@ -9,9 +9,13 @@ use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Cache;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
use LogsActivity;
/**
* The attributes that are mass assignable.
@ -44,6 +48,11 @@ class User extends Authenticatable
'password' => 'hashed',
];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
public function roles(){
return $this->belongsToMany(Role::class,'user_role')->with("permissions");
}

View File

@ -36,6 +36,11 @@ class RouteServiceProvider extends ServiceProvider
->namespace("{$this->apiNamespace}\Auth")
->group(base_path('routes/auth.php'));
Route::middleware('api')
->prefix('accounting')
->namespace("{$this->apiNamespace}\Accounting")
->group(base_path('routes/accounting.php'));
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));

View File

@ -0,0 +1,51 @@
<?php
namespace App\Repositories\Accounting;
use App\Models\Account;
class AccountRepository
{
public function list($params){
$limit = @$params["limit"] ?? 10;
$offset = @$params["offset"] ?? 0;
$sortColumn = @$params["sort"]["column"] ?? "id";
$sortDir = @$params["sort"]["dir"] ?? "desc";
return Account::skip($offset)
->orderBy($sortColumn, $sortDir)
->when(@$params["filter"], function ($query) use ($params) {
foreach ($params["filter"] as $filter) {
$query->where($filter["column"], $filter["query"]);
}
})
->paginate($limit);
}
public function create($params){
$params["password"] = "-";
$model = Account::create($params);
if (@$params["permissions"]){
$model->permissions()->sync($params["permissions"]);
}
return $model;
}
public function update($model, $params){
$model->update($params);
if (@$params["permissions"]){
$model->permissions()->sync($params["permissions"]);
}
return $model;
}
public function delete($model){
$model->delete();
}
}

View File

@ -10,7 +10,8 @@
"laravel/envoy": "^2.10",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8"
"laravel/tinker": "^2.8",
"spatie/laravel-activitylog": "^4.10"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",

153
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "215e4584bd80d9667c89109f18cd6fbf",
"content-hash": "2feee7537e769f923d55b248c506fe92",
"packages": [
{
"name": "brick/math",
@ -3244,6 +3244,157 @@
],
"time": "2024-04-27T21:32:50+00:00"
},
{
"name": "spatie/laravel-activitylog",
"version": "4.10.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-activitylog.git",
"reference": "466f30f7245fe3a6e328ad5e6812bd43b4bddea5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/466f30f7245fe3a6e328ad5e6812bd43b4bddea5",
"reference": "466f30f7245fe3a6e328ad5e6812bd43b4bddea5",
"shasum": ""
},
"require": {
"illuminate/config": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
"illuminate/database": "^8.69 || ^9.27 || ^10.0 || ^11.0 || ^12.0",
"illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
"php": "^8.1",
"spatie/laravel-package-tools": "^1.6.3"
},
"require-dev": {
"ext-json": "*",
"orchestra/testbench": "^6.23 || ^7.0 || ^8.0 || ^9.0 || ^10.0",
"pestphp/pest": "^1.20 || ^2.0 || ^3.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\Activitylog\\ActivitylogServiceProvider"
]
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Spatie\\Activitylog\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
},
{
"name": "Sebastian De Deyne",
"email": "sebastian@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
},
{
"name": "Tom Witkowski",
"email": "dev.gummibeer@gmail.com",
"homepage": "https://gummibeer.de",
"role": "Developer"
}
],
"description": "A very simple activity logger to monitor the users of your website or application",
"homepage": "https://github.com/spatie/activitylog",
"keywords": [
"activity",
"laravel",
"log",
"spatie",
"user"
],
"support": {
"issues": "https://github.com/spatie/laravel-activitylog/issues",
"source": "https://github.com/spatie/laravel-activitylog/tree/4.10.1"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
},
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2025-02-10T15:38:25+00:00"
},
{
"name": "spatie/laravel-package-tools",
"version": "1.19.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-package-tools.git",
"reference": "1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa",
"reference": "1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa",
"shasum": ""
},
"require": {
"illuminate/contracts": "^9.28|^10.0|^11.0|^12.0",
"php": "^8.0"
},
"require-dev": {
"mockery/mockery": "^1.5",
"orchestra/testbench": "^7.7|^8.0|^9.0|^10.0",
"pestphp/pest": "^1.23|^2.1|^3.1",
"phpunit/phpunit": "^9.5.24|^10.5|^11.5",
"spatie/pest-plugin-test-time": "^1.1|^2.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\LaravelPackageTools\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"role": "Developer"
}
],
"description": "Tools for creating Laravel packages",
"homepage": "https://github.com/spatie/laravel-package-tools",
"keywords": [
"laravel-package-tools",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/laravel-package-tools/issues",
"source": "https://github.com/spatie/laravel-package-tools/tree/1.19.0"
},
"funding": [
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2025-02-06T14:58:20+00:00"
},
{
"name": "symfony/console",
"version": "v6.4.17",

View File

@ -0,0 +1,29 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Account>
*/
class AccountFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'code' => fake()->name(),
'sheet' => fake()->randomElement(["balance","income"]),
'type' => fake()->randomElement(["header","posting","begin-total","end-total"]),
'category' => fake()->name(),
'subcategory' => fake()->name(),
'structure' => fake()->name(),
];
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('accounts', function (Blueprint $table) {
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->string('sheet'); // Balance/Income
$table->string('category'); // Assets, Equity, Liabilities, Income, Expense, Cost of Goods
$table->string('subcategory');
$table->string('type'); // Header, Begin-Total, Posting, End-Total
$table->string('structure');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('accounts');
}
};

View File

@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::statement("CREATE EXTENSION IF NOT EXISTS ltree");
}
/**
* Reverse the migrations.
*/
public function down(): void
{
DB::statement("DROP EXTENSION ltree");
}
};

View File

@ -8,3 +8,8 @@ Auth,Role,*,auth.role:read,Show list data role
Auth,Role,*,auth.role:create,Create data role
Auth,Role,*,auth.role:update,Update data role
Auth,Role,*,auth.role:delete,Delete data role
Accounting,Account,*,accounting.account:*,All operation data account
Accounting,Account,*,accounting.account:read,Show list data account
Accounting,Account,*,accounting.account:create,Create data account
Accounting,Account,*,accounting.account:update,Update data account
Accounting,Account,*,accounting.account:delete,Delete data account

1 Auth User * auth.user:* All operation data user
8 Auth Role * auth.role:create Create data role
9 Auth Role * auth.role:update Update data role
10 Auth Role * auth.role:delete Delete data role
11 Accounting Account * accounting.account:* All operation data account
12 Accounting Account * accounting.account:read Show list data account
13 Accounting Account * accounting.account:create Create data account
14 Accounting Account * accounting.account:update Update data account
15 Accounting Account * accounting.account:delete Delete data account

11
routes/accounting.php Normal file
View File

@ -0,0 +1,11 @@
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::group(["namespace" => "Account", "prefix" => "account", "middleware" => "auth:sanctum"], function () {
Route::get('/', 'ListController')->middleware("auth:sanctum");
Route::post('/', 'StoreController')->middleware("auth:sanctum");
Route::post('/{account}', 'UpdateController')->middleware("auth:sanctum");
Route::post('/{account}/delete', 'DeleteController')->middleware("auth:sanctum");
});

View File

@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Accounting\Account;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\Account;
use App\Models\Role;
use App\Models\Permission;
use App\Models\User;
use Laravel\Sanctum\Sanctum;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class DeleteTest extends TestCase
{
use DatabaseTransactions;
/**
* A basic feature test example.
*/
public function test_success(): void
{
$permission = Permission::where("code","accounting.account:delete")->first();
$role = Role::factory()->create();
$role->permissions()->attach($permission->id);
$user = User::factory()->create();
$user->roles()->attach($role->id);
Sanctum::actingAs($user);
$data = Account::factory()->create();
$response = $this->post('/accounting/account/'.$data->id.'/delete');
$response->assertStatus(200);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Tests\Feature\Accounting\Account;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\Account;
use App\Models\Role;
use App\Models\Permission;
use App\Models\User;
use Laravel\Sanctum\Sanctum;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ListTest extends TestCase
{
use DatabaseTransactions;
/**
* A basic feature test example.
*/
public function test_success(): void
{
$permission = Permission::where("code","accounting.account:read")->first();
$role = Role::factory()->create();
$role->permissions()->attach($permission->id);
$user = User::factory()->create();
$user->roles()->attach($role->id);
Sanctum::actingAs($user);
$data = Account::factory()->create();
$response = $this->get('/accounting/account/');
$response->assertStatus(200);
$response->assertJson([
"data" => [
[
"id" => $data->id,
"name" => $data->name
]
]
]);
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace Tests\Feature\Accounting\Account;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\Role;
use App\Models\User;
use App\Models\Permission;
use App\Models\Account;
use Laravel\Sanctum\Sanctum;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class StoreTest extends TestCase
{
use DatabaseTransactions;
/**
* A basic feature test example.
*/
public function test_success(): void
{
$permission = Permission::where("code","accounting.account:create")->first();
$role = Role::factory()->create();
$role->permissions()->attach($permission->id);
$user = User::factory()->create();
$user->roles()->attach($role->id);
Sanctum::actingAs($user);
$response = $this->post('/accounting/account/',[
"name" => "new role",
"code" => "code",
"sheet" => "sheet",
"structure" => "structure",
"type" => "type",
"category" => "category",
"subcategory" => "subcategory",
]);
$response->assertStatus(201);
$response->assertJson([
"data" => [
"name" => "new role"
]
]);
}
/**
* A basic feature test example.
*/
public function test_with_roles_success(): void
{
$permission = Permission::where("code","accounting.account:create")->first();
$role = Role::factory()->create();
$role->permissions()->attach($permission->id);
$user = User::factory()->create();
$user->roles()->attach($role->id);
$role2 = Role::factory()->create();
Sanctum::actingAs($user);
$response = $this->post('/accounting/account/',[
"name" => "new role",
"code" => "code",
"sheet" => "sheet",
"structure" => "structure",
"type" => "type",
"category" => "category",
"subcategory" => "subcategory",
]);
$response->assertStatus(201);
$response->assertJson([
"data" => [
"name" => "new role"
]
]);
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace Tests\Feature\Accounting\Account;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\Account;
use App\Models\Role;
use App\Models\Permission;
use App\Models\User;
use Laravel\Sanctum\Sanctum;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class UpdateTest extends TestCase
{
use DatabaseTransactions;
/**
* A basic feature test example.
*/
public function test_success(): void
{
$permission = Permission::where("code","accounting.account:update")->first();
$role = Role::factory()->create();
$role->permissions()->attach($permission->id);
$user = User::factory()->create();
$user->roles()->attach($role->id);
Sanctum::actingAs($user);
$data = Account::factory()->create();
$response = $this->post('/accounting/account/'.$data->id,[
"name" => "update role",
"code" => "code",
"sheet" => "sheet",
"structure" => "structure",
"type" => "type",
"category" => "category",
"subcategory" => "subcategory",
]);
$response->assertStatus(200);
$response->assertJson([
"data" => [
"name" => "update role",
]
]);
}
/**
* A basic feature test example.
*/
public function test_with_roles_success(): void
{
$permission = Permission::where("code","accounting.account:update")->first();
$role = Role::factory()->create();
$role->permissions()->attach($permission->id);
$user = User::factory()->create();
$user->roles()->attach($role->id);
Sanctum::actingAs($user);
$data = Account::factory()->create();
$role2 = Role::factory()->create();
$response = $this->post('/accounting/account/'.$data->id,[
"name" => "update role",
"code" => "code",
"sheet" => "sheet",
"structure" => "structure",
"type" => "type",
"category" => "category",
"subcategory" => "subcategory",
]);
$response->assertStatus(200);
$response->assertJson([
"data" => [
"name" => "update role"
]
]);
}
}