feat: 2.3 카드/계좌 관리 API 구현
- cards, bank_accounts 테이블 마이그레이션 - Card, BankAccount 모델 (카드번호 암호화) - CardService, BankAccountService - CardController, BankAccountController + FormRequest 4개 - API 엔드포인트 15개 (카드 7개, 계좌 8개) - Swagger 문서 (CardApi.php, BankAccountApi.php)
This commit is contained in:
108
app/Http/Controllers/Api/V1/BankAccountController.php
Normal file
108
app/Http/Controllers/Api/V1/BankAccountController.php
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\V1;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\V1\BankAccount\StoreBankAccountRequest;
|
||||||
|
use App\Http\Requests\V1\BankAccount\UpdateBankAccountRequest;
|
||||||
|
use App\Http\Responses\ApiResponse;
|
||||||
|
use App\Services\BankAccountService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class BankAccountController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly BankAccountService $service
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 목록
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$params = $request->only([
|
||||||
|
'search',
|
||||||
|
'status',
|
||||||
|
'assigned_user_id',
|
||||||
|
'is_primary',
|
||||||
|
'sort_by',
|
||||||
|
'sort_dir',
|
||||||
|
'per_page',
|
||||||
|
'page',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$accounts = $this->service->index($params);
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.fetched'), $accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 등록
|
||||||
|
*/
|
||||||
|
public function store(StoreBankAccountRequest $request)
|
||||||
|
{
|
||||||
|
$account = $this->service->store($request->validated());
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.created'), $account, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 상세
|
||||||
|
*/
|
||||||
|
public function show(int $id)
|
||||||
|
{
|
||||||
|
$account = $this->service->show($id);
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.fetched'), $account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 수정
|
||||||
|
*/
|
||||||
|
public function update(int $id, UpdateBankAccountRequest $request)
|
||||||
|
{
|
||||||
|
$account = $this->service->update($id, $request->validated());
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.updated'), $account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 삭제
|
||||||
|
*/
|
||||||
|
public function destroy(int $id)
|
||||||
|
{
|
||||||
|
$this->service->destroy($id);
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.deleted'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 상태 토글 (사용/정지)
|
||||||
|
*/
|
||||||
|
public function toggle(int $id)
|
||||||
|
{
|
||||||
|
$account = $this->service->toggleStatus($id);
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.updated'), $account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 대표계좌 설정
|
||||||
|
*/
|
||||||
|
public function setPrimary(int $id)
|
||||||
|
{
|
||||||
|
$account = $this->service->setPrimary($id);
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.updated'), $account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 활성 계좌 목록 (셀렉트박스용)
|
||||||
|
*/
|
||||||
|
public function active()
|
||||||
|
{
|
||||||
|
$accounts = $this->service->getActiveAccounts();
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.fetched'), $accounts);
|
||||||
|
}
|
||||||
|
}
|
||||||
97
app/Http/Controllers/Api/V1/CardController.php
Normal file
97
app/Http/Controllers/Api/V1/CardController.php
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\V1;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\V1\Card\StoreCardRequest;
|
||||||
|
use App\Http\Requests\V1\Card\UpdateCardRequest;
|
||||||
|
use App\Http\Responses\ApiResponse;
|
||||||
|
use App\Services\CardService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class CardController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly CardService $service
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 목록
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$params = $request->only([
|
||||||
|
'search',
|
||||||
|
'status',
|
||||||
|
'assigned_user_id',
|
||||||
|
'sort_by',
|
||||||
|
'sort_dir',
|
||||||
|
'per_page',
|
||||||
|
'page',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cards = $this->service->index($params);
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.fetched'), $cards);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 등록
|
||||||
|
*/
|
||||||
|
public function store(StoreCardRequest $request)
|
||||||
|
{
|
||||||
|
$card = $this->service->store($request->validated());
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.created'), $card, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 상세
|
||||||
|
*/
|
||||||
|
public function show(int $id)
|
||||||
|
{
|
||||||
|
$card = $this->service->show($id);
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.fetched'), $card);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 수정
|
||||||
|
*/
|
||||||
|
public function update(int $id, UpdateCardRequest $request)
|
||||||
|
{
|
||||||
|
$card = $this->service->update($id, $request->validated());
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.updated'), $card);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 삭제
|
||||||
|
*/
|
||||||
|
public function destroy(int $id)
|
||||||
|
{
|
||||||
|
$this->service->destroy($id);
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.deleted'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 상태 토글 (사용/정지)
|
||||||
|
*/
|
||||||
|
public function toggle(int $id)
|
||||||
|
{
|
||||||
|
$card = $this->service->toggleStatus($id);
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.updated'), $card);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 활성 카드 목록 (셀렉트박스용)
|
||||||
|
*/
|
||||||
|
public function active()
|
||||||
|
{
|
||||||
|
$cards = $this->service->getActiveCards();
|
||||||
|
|
||||||
|
return ApiResponse::handle(__('message.fetched'), $cards);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
app/Http/Requests/V1/BankAccount/StoreBankAccountRequest.php
Normal file
53
app/Http/Requests/V1/BankAccount/StoreBankAccountRequest.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\V1\BankAccount;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StoreBankAccountRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'bank_code' => ['required', 'string', 'max:10'],
|
||||||
|
'bank_name' => ['required', 'string', 'max:50'],
|
||||||
|
'account_number' => ['required', 'string', 'max:30', 'regex:/^[\d-]+$/'],
|
||||||
|
'account_holder' => ['required', 'string', 'max:50'],
|
||||||
|
'account_name' => ['required', 'string', 'max:100'],
|
||||||
|
'status' => ['nullable', 'string', 'in:active,inactive'],
|
||||||
|
'assigned_user_id' => ['nullable', 'integer', 'exists:users,id'],
|
||||||
|
'is_primary' => ['nullable', 'boolean'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'bank_code.required' => __('validation.required', ['attribute' => __('validation.attributes.bank_code')]),
|
||||||
|
'bank_name.required' => __('validation.required', ['attribute' => __('validation.attributes.bank_name')]),
|
||||||
|
'account_number.required' => __('validation.required', ['attribute' => __('validation.attributes.account_number')]),
|
||||||
|
'account_number.regex' => __('validation.account_number_format'),
|
||||||
|
'account_holder.required' => __('validation.required', ['attribute' => __('validation.attributes.account_holder')]),
|
||||||
|
'account_name.required' => __('validation.required', ['attribute' => __('validation.attributes.account_name')]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'bank_code' => __('validation.attributes.bank_code'),
|
||||||
|
'bank_name' => __('validation.attributes.bank_name'),
|
||||||
|
'account_number' => __('validation.attributes.account_number'),
|
||||||
|
'account_holder' => __('validation.attributes.account_holder'),
|
||||||
|
'account_name' => __('validation.attributes.account_name'),
|
||||||
|
'status' => __('validation.attributes.status'),
|
||||||
|
'assigned_user_id' => __('validation.attributes.assigned_user_id'),
|
||||||
|
'is_primary' => __('validation.attributes.is_primary'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\V1\BankAccount;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdateBankAccountRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'bank_code' => ['sometimes', 'string', 'max:10'],
|
||||||
|
'bank_name' => ['sometimes', 'string', 'max:50'],
|
||||||
|
'account_number' => ['sometimes', 'string', 'max:30', 'regex:/^[\d-]+$/'],
|
||||||
|
'account_holder' => ['sometimes', 'string', 'max:50'],
|
||||||
|
'account_name' => ['sometimes', 'string', 'max:100'],
|
||||||
|
'status' => ['sometimes', 'string', 'in:active,inactive'],
|
||||||
|
'assigned_user_id' => ['nullable', 'integer', 'exists:users,id'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'account_number.regex' => __('validation.account_number_format'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'bank_code' => __('validation.attributes.bank_code'),
|
||||||
|
'bank_name' => __('validation.attributes.bank_name'),
|
||||||
|
'account_number' => __('validation.attributes.account_number'),
|
||||||
|
'account_holder' => __('validation.attributes.account_holder'),
|
||||||
|
'account_name' => __('validation.attributes.account_name'),
|
||||||
|
'status' => __('validation.attributes.status'),
|
||||||
|
'assigned_user_id' => __('validation.attributes.assigned_user_id'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
53
app/Http/Requests/V1/Card/StoreCardRequest.php
Normal file
53
app/Http/Requests/V1/Card/StoreCardRequest.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\V1\Card;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StoreCardRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'card_company' => ['required', 'string', 'max:50'],
|
||||||
|
'card_number' => ['required', 'string', 'regex:/^\d{13,19}$/'],
|
||||||
|
'expiry_date' => ['required', 'string', 'regex:/^(0[1-9]|1[0-2])\/\d{2}$/'],
|
||||||
|
'card_password' => ['nullable', 'string', 'size:2', 'regex:/^\d{2}$/'],
|
||||||
|
'card_name' => ['required', 'string', 'max:100'],
|
||||||
|
'status' => ['nullable', 'string', 'in:active,inactive'],
|
||||||
|
'assigned_user_id' => ['nullable', 'integer', 'exists:users,id'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'card_company.required' => __('validation.required', ['attribute' => __('validation.attributes.card_company')]),
|
||||||
|
'card_number.required' => __('validation.required', ['attribute' => __('validation.attributes.card_number')]),
|
||||||
|
'card_number.regex' => __('validation.card_number_format'),
|
||||||
|
'expiry_date.required' => __('validation.required', ['attribute' => __('validation.attributes.expiry_date')]),
|
||||||
|
'expiry_date.regex' => __('validation.expiry_date_format'),
|
||||||
|
'card_password.size' => __('validation.card_password_format'),
|
||||||
|
'card_password.regex' => __('validation.card_password_format'),
|
||||||
|
'card_name.required' => __('validation.required', ['attribute' => __('validation.attributes.card_name')]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'card_company' => __('validation.attributes.card_company'),
|
||||||
|
'card_number' => __('validation.attributes.card_number'),
|
||||||
|
'expiry_date' => __('validation.attributes.expiry_date'),
|
||||||
|
'card_password' => __('validation.attributes.card_password'),
|
||||||
|
'card_name' => __('validation.attributes.card_name'),
|
||||||
|
'status' => __('validation.attributes.status'),
|
||||||
|
'assigned_user_id' => __('validation.attributes.assigned_user_id'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
49
app/Http/Requests/V1/Card/UpdateCardRequest.php
Normal file
49
app/Http/Requests/V1/Card/UpdateCardRequest.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\V1\Card;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdateCardRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'card_company' => ['sometimes', 'string', 'max:50'],
|
||||||
|
'card_number' => ['sometimes', 'string', 'regex:/^\d{13,19}$/'],
|
||||||
|
'expiry_date' => ['sometimes', 'string', 'regex:/^(0[1-9]|1[0-2])\/\d{2}$/'],
|
||||||
|
'card_password' => ['nullable', 'string', 'size:2', 'regex:/^\d{2}$/'],
|
||||||
|
'card_name' => ['sometimes', 'string', 'max:100'],
|
||||||
|
'status' => ['sometimes', 'string', 'in:active,inactive'],
|
||||||
|
'assigned_user_id' => ['nullable', 'integer', 'exists:users,id'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'card_number.regex' => __('validation.card_number_format'),
|
||||||
|
'expiry_date.regex' => __('validation.expiry_date_format'),
|
||||||
|
'card_password.size' => __('validation.card_password_format'),
|
||||||
|
'card_password.regex' => __('validation.card_password_format'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'card_company' => __('validation.attributes.card_company'),
|
||||||
|
'card_number' => __('validation.attributes.card_number'),
|
||||||
|
'expiry_date' => __('validation.attributes.expiry_date'),
|
||||||
|
'card_password' => __('validation.attributes.card_password'),
|
||||||
|
'card_name' => __('validation.attributes.card_name'),
|
||||||
|
'status' => __('validation.attributes.status'),
|
||||||
|
'assigned_user_id' => __('validation.attributes.assigned_user_id'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
130
app/Models/Tenants/BankAccount.php
Normal file
130
app/Models/Tenants/BankAccount.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Tenants;
|
||||||
|
|
||||||
|
use App\Models\Members\User;
|
||||||
|
use App\Traits\BelongsToTenant;
|
||||||
|
use App\Traits\ModelTrait;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 은행 계좌 모델
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property int $tenant_id
|
||||||
|
* @property string $bank_code
|
||||||
|
* @property string $bank_name
|
||||||
|
* @property string $account_number
|
||||||
|
* @property string $account_holder
|
||||||
|
* @property string $account_name
|
||||||
|
* @property string $status
|
||||||
|
* @property int|null $assigned_user_id
|
||||||
|
* @property bool $is_primary
|
||||||
|
* @property int|null $created_by
|
||||||
|
* @property int|null $updated_by
|
||||||
|
* @property int|null $deleted_by
|
||||||
|
*/
|
||||||
|
class BankAccount extends Model
|
||||||
|
{
|
||||||
|
use BelongsToTenant, ModelTrait, SoftDeletes;
|
||||||
|
|
||||||
|
protected $table = 'bank_accounts';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'tenant_id',
|
||||||
|
'bank_code',
|
||||||
|
'bank_name',
|
||||||
|
'account_number',
|
||||||
|
'account_holder',
|
||||||
|
'account_name',
|
||||||
|
'status',
|
||||||
|
'assigned_user_id',
|
||||||
|
'is_primary',
|
||||||
|
'created_by',
|
||||||
|
'updated_by',
|
||||||
|
'deleted_by',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'is_primary' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $attributes = [
|
||||||
|
'status' => 'active',
|
||||||
|
'is_primary' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 관계 정의
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 담당자
|
||||||
|
*/
|
||||||
|
public function assignedUser(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'assigned_user_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 생성자
|
||||||
|
*/
|
||||||
|
public function creator(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'created_by');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 수정자
|
||||||
|
*/
|
||||||
|
public function updater(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'updated_by');
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 헬퍼 메서드
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 마스킹된 계좌번호 조회
|
||||||
|
*/
|
||||||
|
public function getMaskedAccountNumber(): string
|
||||||
|
{
|
||||||
|
$length = strlen($this->account_number);
|
||||||
|
if ($length <= 4) {
|
||||||
|
return $this->account_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
$visibleEnd = substr($this->account_number, -4);
|
||||||
|
$maskedPart = str_repeat('*', $length - 4);
|
||||||
|
|
||||||
|
return $maskedPart.$visibleEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 활성 상태 여부
|
||||||
|
*/
|
||||||
|
public function isActive(): bool
|
||||||
|
{
|
||||||
|
return $this->status === 'active';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 상태 토글
|
||||||
|
*/
|
||||||
|
public function toggleStatus(): void
|
||||||
|
{
|
||||||
|
$this->status = $this->status === 'active' ? 'inactive' : 'active';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 대표계좌로 설정
|
||||||
|
*/
|
||||||
|
public function setAsPrimary(): void
|
||||||
|
{
|
||||||
|
$this->is_primary = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
156
app/Models/Tenants/Card.php
Normal file
156
app/Models/Tenants/Card.php
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Tenants;
|
||||||
|
|
||||||
|
use App\Models\Members\User;
|
||||||
|
use App\Traits\BelongsToTenant;
|
||||||
|
use App\Traits\ModelTrait;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 모델
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property int $tenant_id
|
||||||
|
* @property string $card_company
|
||||||
|
* @property string $card_number_encrypted
|
||||||
|
* @property string $card_number_last4
|
||||||
|
* @property string $expiry_date
|
||||||
|
* @property string|null $card_password_encrypted
|
||||||
|
* @property string $card_name
|
||||||
|
* @property string $status
|
||||||
|
* @property int|null $assigned_user_id
|
||||||
|
* @property int|null $created_by
|
||||||
|
* @property int|null $updated_by
|
||||||
|
* @property int|null $deleted_by
|
||||||
|
*/
|
||||||
|
class Card extends Model
|
||||||
|
{
|
||||||
|
use BelongsToTenant, ModelTrait, SoftDeletes;
|
||||||
|
|
||||||
|
protected $table = 'cards';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'tenant_id',
|
||||||
|
'card_company',
|
||||||
|
'card_number_encrypted',
|
||||||
|
'card_number_last4',
|
||||||
|
'expiry_date',
|
||||||
|
'card_password_encrypted',
|
||||||
|
'card_name',
|
||||||
|
'status',
|
||||||
|
'assigned_user_id',
|
||||||
|
'created_by',
|
||||||
|
'updated_by',
|
||||||
|
'deleted_by',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $hidden = [
|
||||||
|
'card_number_encrypted',
|
||||||
|
'card_password_encrypted',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $attributes = [
|
||||||
|
'status' => 'active',
|
||||||
|
];
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 관계 정의
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 담당자
|
||||||
|
*/
|
||||||
|
public function assignedUser(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'assigned_user_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 생성자
|
||||||
|
*/
|
||||||
|
public function creator(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'created_by');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 수정자
|
||||||
|
*/
|
||||||
|
public function updater(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'updated_by');
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 암호화/복호화 메서드
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드번호 암호화 설정
|
||||||
|
*/
|
||||||
|
public function setCardNumber(string $cardNumber): void
|
||||||
|
{
|
||||||
|
$this->card_number_encrypted = Crypt::encryptString($cardNumber);
|
||||||
|
$this->card_number_last4 = substr($cardNumber, -4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드번호 복호화 조회
|
||||||
|
*/
|
||||||
|
public function getDecryptedCardNumber(): string
|
||||||
|
{
|
||||||
|
return Crypt::decryptString($this->card_number_encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 비밀번호 암호화 설정
|
||||||
|
*/
|
||||||
|
public function setCardPassword(?string $password): void
|
||||||
|
{
|
||||||
|
$this->card_password_encrypted = $password
|
||||||
|
? Crypt::encryptString($password)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 비밀번호 복호화 조회
|
||||||
|
*/
|
||||||
|
public function getDecryptedCardPassword(): ?string
|
||||||
|
{
|
||||||
|
return $this->card_password_encrypted
|
||||||
|
? Crypt::decryptString($this->card_password_encrypted)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 헬퍼 메서드
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 마스킹된 카드번호 조회
|
||||||
|
*/
|
||||||
|
public function getMaskedCardNumber(): string
|
||||||
|
{
|
||||||
|
return '****-****-****-'.$this->card_number_last4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 활성 상태 여부
|
||||||
|
*/
|
||||||
|
public function isActive(): bool
|
||||||
|
{
|
||||||
|
return $this->status === 'active';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 상태 토글
|
||||||
|
*/
|
||||||
|
public function toggleStatus(): void
|
||||||
|
{
|
||||||
|
$this->status = $this->status === 'active' ? 'inactive' : 'active';
|
||||||
|
}
|
||||||
|
}
|
||||||
244
app/Services/BankAccountService.php
Normal file
244
app/Services/BankAccountService.php
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Tenants\BankAccount;
|
||||||
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class BankAccountService extends Service
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 계좌 목록 조회
|
||||||
|
*/
|
||||||
|
public function index(array $params): LengthAwarePaginator
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
|
||||||
|
$query = BankAccount::query()
|
||||||
|
->where('tenant_id', $tenantId);
|
||||||
|
|
||||||
|
// 검색 필터
|
||||||
|
if (! empty($params['search'])) {
|
||||||
|
$search = $params['search'];
|
||||||
|
$query->where(function ($q) use ($search) {
|
||||||
|
$q->where('account_name', 'like', "%{$search}%")
|
||||||
|
->orWhere('bank_name', 'like', "%{$search}%")
|
||||||
|
->orWhere('account_holder', 'like', "%{$search}%")
|
||||||
|
->orWhere('account_number', 'like', "%{$search}%");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상태 필터
|
||||||
|
if (! empty($params['status'])) {
|
||||||
|
$query->where('status', $params['status']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 담당자 필터
|
||||||
|
if (! empty($params['assigned_user_id'])) {
|
||||||
|
$query->where('assigned_user_id', $params['assigned_user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대표계좌만 필터
|
||||||
|
if (isset($params['is_primary']) && $params['is_primary']) {
|
||||||
|
$query->where('is_primary', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 정렬: 대표계좌 먼저, 그 다음 생성일순
|
||||||
|
$query->orderByDesc('is_primary')
|
||||||
|
->orderBy($params['sort_by'] ?? 'created_at', $params['sort_dir'] ?? 'desc');
|
||||||
|
|
||||||
|
// 페이지네이션
|
||||||
|
$perPage = $params['per_page'] ?? 20;
|
||||||
|
|
||||||
|
return $query->paginate($perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 상세 조회
|
||||||
|
*/
|
||||||
|
public function show(int $id): BankAccount
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
|
||||||
|
return BankAccount::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->with(['assignedUser:id,name'])
|
||||||
|
->findOrFail($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 등록
|
||||||
|
*/
|
||||||
|
public function store(array $data): BankAccount
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($data, $tenantId, $userId) {
|
||||||
|
// 첫 번째 계좌인 경우 자동으로 대표계좌 설정
|
||||||
|
$isFirst = BankAccount::where('tenant_id', $tenantId)->count() === 0;
|
||||||
|
$isPrimary = $data['is_primary'] ?? $isFirst;
|
||||||
|
|
||||||
|
// 대표계좌로 설정 시 기존 대표계좌 해제
|
||||||
|
if ($isPrimary) {
|
||||||
|
BankAccount::where('tenant_id', $tenantId)
|
||||||
|
->where('is_primary', true)
|
||||||
|
->update(['is_primary' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$account = BankAccount::create([
|
||||||
|
'tenant_id' => $tenantId,
|
||||||
|
'bank_code' => $data['bank_code'],
|
||||||
|
'bank_name' => $data['bank_name'],
|
||||||
|
'account_number' => $data['account_number'],
|
||||||
|
'account_holder' => $data['account_holder'],
|
||||||
|
'account_name' => $data['account_name'],
|
||||||
|
'status' => $data['status'] ?? 'active',
|
||||||
|
'assigned_user_id' => $data['assigned_user_id'] ?? null,
|
||||||
|
'is_primary' => $isPrimary,
|
||||||
|
'created_by' => $userId,
|
||||||
|
'updated_by' => $userId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 수정
|
||||||
|
*/
|
||||||
|
public function update(int $id, array $data): BankAccount
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($id, $data, $tenantId, $userId) {
|
||||||
|
$account = BankAccount::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
$account->fill([
|
||||||
|
'bank_code' => $data['bank_code'] ?? $account->bank_code,
|
||||||
|
'bank_name' => $data['bank_name'] ?? $account->bank_name,
|
||||||
|
'account_number' => $data['account_number'] ?? $account->account_number,
|
||||||
|
'account_holder' => $data['account_holder'] ?? $account->account_holder,
|
||||||
|
'account_name' => $data['account_name'] ?? $account->account_name,
|
||||||
|
'status' => $data['status'] ?? $account->status,
|
||||||
|
'assigned_user_id' => $data['assigned_user_id'] ?? $account->assigned_user_id,
|
||||||
|
'updated_by' => $userId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$account->save();
|
||||||
|
|
||||||
|
return $account->fresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 삭제
|
||||||
|
*/
|
||||||
|
public function destroy(int $id): bool
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($id, $tenantId, $userId) {
|
||||||
|
$account = BankAccount::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
// 대표계좌 삭제 시 다른 계좌를 대표로 설정
|
||||||
|
if ($account->is_primary) {
|
||||||
|
$nextPrimary = BankAccount::where('tenant_id', $tenantId)
|
||||||
|
->where('id', '!=', $id)
|
||||||
|
->where('status', 'active')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($nextPrimary) {
|
||||||
|
$nextPrimary->is_primary = true;
|
||||||
|
$nextPrimary->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$account->deleted_by = $userId;
|
||||||
|
$account->save();
|
||||||
|
$account->delete();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계좌 상태 토글 (사용/정지)
|
||||||
|
*/
|
||||||
|
public function toggleStatus(int $id): BankAccount
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($id, $tenantId, $userId) {
|
||||||
|
$account = BankAccount::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
$account->toggleStatus();
|
||||||
|
$account->updated_by = $userId;
|
||||||
|
$account->save();
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 대표계좌 설정
|
||||||
|
*/
|
||||||
|
public function setPrimary(int $id): BankAccount
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($id, $tenantId, $userId) {
|
||||||
|
// 기존 대표계좌 해제
|
||||||
|
BankAccount::where('tenant_id', $tenantId)
|
||||||
|
->where('is_primary', true)
|
||||||
|
->update(['is_primary' => false]);
|
||||||
|
|
||||||
|
// 새 대표계좌 설정
|
||||||
|
$account = BankAccount::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
$account->is_primary = true;
|
||||||
|
$account->updated_by = $userId;
|
||||||
|
$account->save();
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 활성 계좌 목록 조회 (셀렉트박스용)
|
||||||
|
*/
|
||||||
|
public function getActiveAccounts(): array
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
|
||||||
|
return BankAccount::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->where('status', 'active')
|
||||||
|
->orderByDesc('is_primary')
|
||||||
|
->orderBy('account_name')
|
||||||
|
->get(['id', 'account_name', 'bank_name', 'account_number', 'is_primary'])
|
||||||
|
->map(function ($account) {
|
||||||
|
return [
|
||||||
|
'id' => $account->id,
|
||||||
|
'account_name' => $account->account_name,
|
||||||
|
'bank_name' => $account->bank_name,
|
||||||
|
'display_number' => $account->getMaskedAccountNumber(),
|
||||||
|
'is_primary' => $account->is_primary,
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
201
app/Services/CardService.php
Normal file
201
app/Services/CardService.php
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Tenants\Card;
|
||||||
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class CardService extends Service
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 카드 목록 조회
|
||||||
|
*/
|
||||||
|
public function index(array $params): LengthAwarePaginator
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
|
||||||
|
$query = Card::query()
|
||||||
|
->where('tenant_id', $tenantId);
|
||||||
|
|
||||||
|
// 검색 필터
|
||||||
|
if (! empty($params['search'])) {
|
||||||
|
$search = $params['search'];
|
||||||
|
$query->where(function ($q) use ($search) {
|
||||||
|
$q->where('card_name', 'like', "%{$search}%")
|
||||||
|
->orWhere('card_company', 'like', "%{$search}%")
|
||||||
|
->orWhere('card_number_last4', 'like', "%{$search}%");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상태 필터
|
||||||
|
if (! empty($params['status'])) {
|
||||||
|
$query->where('status', $params['status']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 담당자 필터
|
||||||
|
if (! empty($params['assigned_user_id'])) {
|
||||||
|
$query->where('assigned_user_id', $params['assigned_user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 정렬
|
||||||
|
$sortBy = $params['sort_by'] ?? 'created_at';
|
||||||
|
$sortDir = $params['sort_dir'] ?? 'desc';
|
||||||
|
$query->orderBy($sortBy, $sortDir);
|
||||||
|
|
||||||
|
// 페이지네이션
|
||||||
|
$perPage = $params['per_page'] ?? 20;
|
||||||
|
|
||||||
|
return $query->paginate($perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 상세 조회
|
||||||
|
*/
|
||||||
|
public function show(int $id): Card
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
|
||||||
|
return Card::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->with(['assignedUser:id,name'])
|
||||||
|
->findOrFail($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 등록
|
||||||
|
*/
|
||||||
|
public function store(array $data): Card
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($data, $tenantId, $userId) {
|
||||||
|
$card = new Card;
|
||||||
|
$card->tenant_id = $tenantId;
|
||||||
|
$card->card_company = $data['card_company'];
|
||||||
|
$card->setCardNumber($data['card_number']);
|
||||||
|
$card->expiry_date = $data['expiry_date'];
|
||||||
|
$card->card_name = $data['card_name'];
|
||||||
|
$card->status = $data['status'] ?? 'active';
|
||||||
|
$card->assigned_user_id = $data['assigned_user_id'] ?? null;
|
||||||
|
$card->created_by = $userId;
|
||||||
|
$card->updated_by = $userId;
|
||||||
|
|
||||||
|
if (! empty($data['card_password'])) {
|
||||||
|
$card->setCardPassword($data['card_password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$card->save();
|
||||||
|
|
||||||
|
return $card;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 수정
|
||||||
|
*/
|
||||||
|
public function update(int $id, array $data): Card
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($id, $data, $tenantId, $userId) {
|
||||||
|
$card = Card::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
if (isset($data['card_company'])) {
|
||||||
|
$card->card_company = $data['card_company'];
|
||||||
|
}
|
||||||
|
if (isset($data['card_number'])) {
|
||||||
|
$card->setCardNumber($data['card_number']);
|
||||||
|
}
|
||||||
|
if (isset($data['expiry_date'])) {
|
||||||
|
$card->expiry_date = $data['expiry_date'];
|
||||||
|
}
|
||||||
|
if (isset($data['card_name'])) {
|
||||||
|
$card->card_name = $data['card_name'];
|
||||||
|
}
|
||||||
|
if (isset($data['status'])) {
|
||||||
|
$card->status = $data['status'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('assigned_user_id', $data)) {
|
||||||
|
$card->assigned_user_id = $data['assigned_user_id'];
|
||||||
|
}
|
||||||
|
if (isset($data['card_password'])) {
|
||||||
|
$card->setCardPassword($data['card_password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$card->updated_by = $userId;
|
||||||
|
$card->save();
|
||||||
|
|
||||||
|
return $card->fresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 삭제
|
||||||
|
*/
|
||||||
|
public function destroy(int $id): bool
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($id, $tenantId, $userId) {
|
||||||
|
$card = Card::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
$card->deleted_by = $userId;
|
||||||
|
$card->save();
|
||||||
|
$card->delete();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 상태 토글 (사용/정지)
|
||||||
|
*/
|
||||||
|
public function toggleStatus(int $id): Card
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($id, $tenantId, $userId) {
|
||||||
|
$card = Card::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
$card->toggleStatus();
|
||||||
|
$card->updated_by = $userId;
|
||||||
|
$card->save();
|
||||||
|
|
||||||
|
return $card;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 활성 카드 목록 조회 (셀렉트박스용)
|
||||||
|
*/
|
||||||
|
public function getActiveCards(): array
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
|
||||||
|
return Card::query()
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->where('status', 'active')
|
||||||
|
->orderBy('card_name')
|
||||||
|
->get(['id', 'card_name', 'card_company', 'card_number_last4'])
|
||||||
|
->map(function ($card) {
|
||||||
|
return [
|
||||||
|
'id' => $card->id,
|
||||||
|
'card_name' => $card->card_name,
|
||||||
|
'card_company' => $card->card_company,
|
||||||
|
'display_number' => '****-'.$card->card_number_last4,
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
353
app/Swagger/v1/BankAccountApi.php
Normal file
353
app/Swagger/v1/BankAccountApi.php
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Swagger\v1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Tag(name="BankAccounts", description="계좌 관리")
|
||||||
|
*
|
||||||
|
* @OA\Schema(
|
||||||
|
* schema="BankAccount",
|
||||||
|
* type="object",
|
||||||
|
* description="계좌 정보",
|
||||||
|
*
|
||||||
|
* @OA\Property(property="id", type="integer", example=1, description="계좌 ID"),
|
||||||
|
* @OA\Property(property="tenant_id", type="integer", example=1, description="테넌트 ID"),
|
||||||
|
* @OA\Property(property="bank_code", type="string", example="088", description="은행 코드"),
|
||||||
|
* @OA\Property(property="bank_name", type="string", example="신한은행", description="은행명"),
|
||||||
|
* @OA\Property(property="account_number", type="string", example="110-123-456789", description="계좌번호"),
|
||||||
|
* @OA\Property(property="account_holder", type="string", example="주식회사 샘", description="예금주"),
|
||||||
|
* @OA\Property(property="account_name", type="string", example="운영계좌", description="계좌 별칭"),
|
||||||
|
* @OA\Property(property="status", type="string", enum={"active","inactive"}, example="active", description="상태"),
|
||||||
|
* @OA\Property(property="assigned_user_id", type="integer", example=1, nullable=true, description="담당자 ID"),
|
||||||
|
* @OA\Property(property="assigned_user", type="object", nullable=true,
|
||||||
|
* @OA\Property(property="id", type="integer", example=1),
|
||||||
|
* @OA\Property(property="name", type="string", example="홍길동"),
|
||||||
|
* description="담당자 정보"
|
||||||
|
* ),
|
||||||
|
* @OA\Property(property="is_primary", type="boolean", example=true, description="대표계좌 여부"),
|
||||||
|
* @OA\Property(property="created_by", type="integer", example=1, nullable=true, description="생성자 ID"),
|
||||||
|
* @OA\Property(property="updated_by", type="integer", example=1, nullable=true, description="수정자 ID"),
|
||||||
|
* @OA\Property(property="created_at", type="string", format="date-time"),
|
||||||
|
* @OA\Property(property="updated_at", type="string", format="date-time")
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @OA\Schema(
|
||||||
|
* schema="BankAccountCreateRequest",
|
||||||
|
* type="object",
|
||||||
|
* required={"bank_code","bank_name","account_number","account_holder","account_name"},
|
||||||
|
* description="계좌 등록 요청",
|
||||||
|
*
|
||||||
|
* @OA\Property(property="bank_code", type="string", example="088", maxLength=10, description="은행 코드"),
|
||||||
|
* @OA\Property(property="bank_name", type="string", example="신한은행", maxLength=50, description="은행명"),
|
||||||
|
* @OA\Property(property="account_number", type="string", example="110-123-456789", maxLength=30, description="계좌번호"),
|
||||||
|
* @OA\Property(property="account_holder", type="string", example="주식회사 샘", maxLength=50, description="예금주"),
|
||||||
|
* @OA\Property(property="account_name", type="string", example="운영계좌", maxLength=100, description="계좌 별칭"),
|
||||||
|
* @OA\Property(property="status", type="string", enum={"active","inactive"}, example="active", description="상태"),
|
||||||
|
* @OA\Property(property="assigned_user_id", type="integer", example=1, nullable=true, description="담당자 ID"),
|
||||||
|
* @OA\Property(property="is_primary", type="boolean", example=false, description="대표계좌 여부")
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @OA\Schema(
|
||||||
|
* schema="BankAccountUpdateRequest",
|
||||||
|
* type="object",
|
||||||
|
* description="계좌 수정 요청",
|
||||||
|
*
|
||||||
|
* @OA\Property(property="bank_code", type="string", example="088", maxLength=10, description="은행 코드"),
|
||||||
|
* @OA\Property(property="bank_name", type="string", example="신한은행", maxLength=50, description="은행명"),
|
||||||
|
* @OA\Property(property="account_number", type="string", example="110-123-456789", maxLength=30, description="계좌번호"),
|
||||||
|
* @OA\Property(property="account_holder", type="string", example="주식회사 샘", maxLength=50, description="예금주"),
|
||||||
|
* @OA\Property(property="account_name", type="string", example="운영계좌", maxLength=100, description="계좌 별칭"),
|
||||||
|
* @OA\Property(property="status", type="string", enum={"active","inactive"}, example="active", description="상태"),
|
||||||
|
* @OA\Property(property="assigned_user_id", type="integer", example=1, nullable=true, description="담당자 ID")
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @OA\Schema(
|
||||||
|
* schema="BankAccountListItem",
|
||||||
|
* type="object",
|
||||||
|
* description="계좌 목록 아이템 (셀렉트박스용)",
|
||||||
|
*
|
||||||
|
* @OA\Property(property="id", type="integer", example=1, description="계좌 ID"),
|
||||||
|
* @OA\Property(property="account_name", type="string", example="운영계좌", description="계좌 별칭"),
|
||||||
|
* @OA\Property(property="bank_name", type="string", example="신한은행", description="은행명"),
|
||||||
|
* @OA\Property(property="display_number", type="string", example="*****6789", description="마스킹된 계좌번호"),
|
||||||
|
* @OA\Property(property="is_primary", type="boolean", example=true, description="대표계좌 여부")
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class BankAccountApi
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/v1/bank-accounts",
|
||||||
|
* tags={"BankAccounts"},
|
||||||
|
* summary="계좌 목록 조회",
|
||||||
|
* description="계좌 목록을 조회합니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="search", in="query", description="검색어 (계좌명, 은행명, 예금주, 계좌번호)", @OA\Schema(type="string")),
|
||||||
|
* @OA\Parameter(name="status", in="query", description="상태 필터", @OA\Schema(type="string", enum={"active","inactive"})),
|
||||||
|
* @OA\Parameter(name="assigned_user_id", in="query", description="담당자 ID", @OA\Schema(type="integer")),
|
||||||
|
* @OA\Parameter(name="is_primary", in="query", description="대표계좌만", @OA\Schema(type="boolean")),
|
||||||
|
* @OA\Parameter(name="sort_by", in="query", description="정렬 기준", @OA\Schema(type="string", enum={"account_name","bank_name","created_at"}, default="created_at")),
|
||||||
|
* @OA\Parameter(name="sort_dir", in="query", description="정렬 방향", @OA\Schema(type="string", enum={"asc","desc"}, default="desc")),
|
||||||
|
* @OA\Parameter(ref="#/components/parameters/Page"),
|
||||||
|
* @OA\Parameter(ref="#/components/parameters/Size"),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="조회 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(
|
||||||
|
* property="data",
|
||||||
|
* type="object",
|
||||||
|
* @OA\Property(property="current_page", type="integer", example=1),
|
||||||
|
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/BankAccount")),
|
||||||
|
* @OA\Property(property="per_page", type="integer", example=20),
|
||||||
|
* @OA\Property(property="total", type="integer", example=10)
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function index() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Post(
|
||||||
|
* path="/api/v1/bank-accounts",
|
||||||
|
* tags={"BankAccounts"},
|
||||||
|
* summary="계좌 등록",
|
||||||
|
* description="새로운 계좌를 등록합니다. 첫 번째 계좌는 자동으로 대표계좌로 설정됩니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\RequestBody(
|
||||||
|
* required=true,
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(ref="#/components/schemas/BankAccountCreateRequest")
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=201,
|
||||||
|
* description="등록 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", ref="#/components/schemas/BankAccount")
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=400, description="잘못된 요청", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function store() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/v1/bank-accounts/active",
|
||||||
|
* tags={"BankAccounts"},
|
||||||
|
* summary="활성 계좌 목록 (셀렉트박스용)",
|
||||||
|
* description="활성 상태의 계좌 목록을 간단한 형태로 조회합니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="조회 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/BankAccountListItem"))
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function active() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/v1/bank-accounts/{id}",
|
||||||
|
* tags={"BankAccounts"},
|
||||||
|
* summary="계좌 상세 조회",
|
||||||
|
* description="계좌 상세 정보를 조회합니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="id", in="path", required=true, description="계좌 ID", @OA\Schema(type="integer")),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="조회 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", ref="#/components/schemas/BankAccount")
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=404, description="계좌 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function show() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Put(
|
||||||
|
* path="/api/v1/bank-accounts/{id}",
|
||||||
|
* tags={"BankAccounts"},
|
||||||
|
* summary="계좌 수정",
|
||||||
|
* description="계좌 정보를 수정합니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="id", in="path", required=true, description="계좌 ID", @OA\Schema(type="integer")),
|
||||||
|
*
|
||||||
|
* @OA\RequestBody(
|
||||||
|
* required=true,
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(ref="#/components/schemas/BankAccountUpdateRequest")
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="수정 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", ref="#/components/schemas/BankAccount")
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=400, description="잘못된 요청", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=404, description="계좌 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function update() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Delete(
|
||||||
|
* path="/api/v1/bank-accounts/{id}",
|
||||||
|
* tags={"BankAccounts"},
|
||||||
|
* summary="계좌 삭제",
|
||||||
|
* description="계좌를 삭제합니다. (Soft Delete) 대표계좌 삭제 시 다른 활성 계좌가 대표계좌로 자동 설정됩니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="id", in="path", required=true, description="계좌 ID", @OA\Schema(type="integer")),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="삭제 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(ref="#/components/schemas/ApiResponse")
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=404, description="계좌 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function destroy() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Patch(
|
||||||
|
* path="/api/v1/bank-accounts/{id}/toggle",
|
||||||
|
* tags={"BankAccounts"},
|
||||||
|
* summary="계좌 상태 토글",
|
||||||
|
* description="계좌의 상태를 토글합니다. (active ↔ inactive)",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="id", in="path", required=true, description="계좌 ID", @OA\Schema(type="integer")),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="토글 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", ref="#/components/schemas/BankAccount")
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=404, description="계좌 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function toggle() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Patch(
|
||||||
|
* path="/api/v1/bank-accounts/{id}/set-primary",
|
||||||
|
* tags={"BankAccounts"},
|
||||||
|
* summary="대표계좌 설정",
|
||||||
|
* description="해당 계좌를 대표계좌로 설정합니다. 기존 대표계좌는 자동으로 해제됩니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="id", in="path", required=true, description="계좌 ID", @OA\Schema(type="integer")),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="설정 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", ref="#/components/schemas/BankAccount")
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=404, description="계좌 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function setPrimary() {}
|
||||||
|
}
|
||||||
315
app/Swagger/v1/CardApi.php
Normal file
315
app/Swagger/v1/CardApi.php
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Swagger\v1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Tag(name="Cards", description="카드 관리")
|
||||||
|
*
|
||||||
|
* @OA\Schema(
|
||||||
|
* schema="Card",
|
||||||
|
* type="object",
|
||||||
|
* description="카드 정보",
|
||||||
|
*
|
||||||
|
* @OA\Property(property="id", type="integer", example=1, description="카드 ID"),
|
||||||
|
* @OA\Property(property="tenant_id", type="integer", example=1, description="테넌트 ID"),
|
||||||
|
* @OA\Property(property="card_company", type="string", example="삼성카드", description="카드사"),
|
||||||
|
* @OA\Property(property="card_number_last4", type="string", example="1234", description="카드번호 끝 4자리"),
|
||||||
|
* @OA\Property(property="expiry_date", type="string", example="12/25", description="유효기간 (MM/YY)"),
|
||||||
|
* @OA\Property(property="card_name", type="string", example="법인카드 1", description="카드 별칭"),
|
||||||
|
* @OA\Property(property="status", type="string", enum={"active","inactive"}, example="active", description="상태"),
|
||||||
|
* @OA\Property(property="assigned_user_id", type="integer", example=1, nullable=true, description="담당자 ID"),
|
||||||
|
* @OA\Property(property="assigned_user", type="object", nullable=true,
|
||||||
|
* @OA\Property(property="id", type="integer", example=1),
|
||||||
|
* @OA\Property(property="name", type="string", example="홍길동"),
|
||||||
|
* description="담당자 정보"
|
||||||
|
* ),
|
||||||
|
* @OA\Property(property="created_by", type="integer", example=1, nullable=true, description="생성자 ID"),
|
||||||
|
* @OA\Property(property="updated_by", type="integer", example=1, nullable=true, description="수정자 ID"),
|
||||||
|
* @OA\Property(property="created_at", type="string", format="date-time"),
|
||||||
|
* @OA\Property(property="updated_at", type="string", format="date-time")
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @OA\Schema(
|
||||||
|
* schema="CardCreateRequest",
|
||||||
|
* type="object",
|
||||||
|
* required={"card_company","card_number","expiry_date","card_name"},
|
||||||
|
* description="카드 등록 요청",
|
||||||
|
*
|
||||||
|
* @OA\Property(property="card_company", type="string", example="삼성카드", maxLength=50, description="카드사"),
|
||||||
|
* @OA\Property(property="card_number", type="string", example="1234567890123456", description="카드번호 (13-19자리)"),
|
||||||
|
* @OA\Property(property="expiry_date", type="string", example="12/25", pattern="^(0[1-9]|1[0-2])/\\d{2}$", description="유효기간 (MM/YY)"),
|
||||||
|
* @OA\Property(property="card_password", type="string", example="12", maxLength=2, description="비밀번호 앞 2자리 (선택)"),
|
||||||
|
* @OA\Property(property="card_name", type="string", example="법인카드 1", maxLength=100, description="카드 별칭"),
|
||||||
|
* @OA\Property(property="status", type="string", enum={"active","inactive"}, example="active", description="상태"),
|
||||||
|
* @OA\Property(property="assigned_user_id", type="integer", example=1, nullable=true, description="담당자 ID")
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @OA\Schema(
|
||||||
|
* schema="CardUpdateRequest",
|
||||||
|
* type="object",
|
||||||
|
* description="카드 수정 요청",
|
||||||
|
*
|
||||||
|
* @OA\Property(property="card_company", type="string", example="삼성카드", maxLength=50, description="카드사"),
|
||||||
|
* @OA\Property(property="card_number", type="string", example="1234567890123456", description="카드번호 (변경 시)"),
|
||||||
|
* @OA\Property(property="expiry_date", type="string", example="12/25", pattern="^(0[1-9]|1[0-2])/\\d{2}$", description="유효기간 (MM/YY)"),
|
||||||
|
* @OA\Property(property="card_password", type="string", example="12", maxLength=2, description="비밀번호 앞 2자리"),
|
||||||
|
* @OA\Property(property="card_name", type="string", example="법인카드 1", maxLength=100, description="카드 별칭"),
|
||||||
|
* @OA\Property(property="status", type="string", enum={"active","inactive"}, example="active", description="상태"),
|
||||||
|
* @OA\Property(property="assigned_user_id", type="integer", example=1, nullable=true, description="담당자 ID")
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @OA\Schema(
|
||||||
|
* schema="CardListItem",
|
||||||
|
* type="object",
|
||||||
|
* description="카드 목록 아이템 (셀렉트박스용)",
|
||||||
|
*
|
||||||
|
* @OA\Property(property="id", type="integer", example=1, description="카드 ID"),
|
||||||
|
* @OA\Property(property="card_name", type="string", example="법인카드 1", description="카드 별칭"),
|
||||||
|
* @OA\Property(property="card_company", type="string", example="삼성카드", description="카드사"),
|
||||||
|
* @OA\Property(property="display_number", type="string", example="****-1234", description="마스킹된 카드번호")
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class CardApi
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/v1/cards",
|
||||||
|
* tags={"Cards"},
|
||||||
|
* summary="카드 목록 조회",
|
||||||
|
* description="카드 목록을 조회합니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="search", in="query", description="검색어 (카드명, 카드사, 끝4자리)", @OA\Schema(type="string")),
|
||||||
|
* @OA\Parameter(name="status", in="query", description="상태 필터", @OA\Schema(type="string", enum={"active","inactive"})),
|
||||||
|
* @OA\Parameter(name="assigned_user_id", in="query", description="담당자 ID", @OA\Schema(type="integer")),
|
||||||
|
* @OA\Parameter(name="sort_by", in="query", description="정렬 기준", @OA\Schema(type="string", enum={"card_name","card_company","created_at"}, default="created_at")),
|
||||||
|
* @OA\Parameter(name="sort_dir", in="query", description="정렬 방향", @OA\Schema(type="string", enum={"asc","desc"}, default="desc")),
|
||||||
|
* @OA\Parameter(ref="#/components/parameters/Page"),
|
||||||
|
* @OA\Parameter(ref="#/components/parameters/Size"),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="조회 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(
|
||||||
|
* property="data",
|
||||||
|
* type="object",
|
||||||
|
* @OA\Property(property="current_page", type="integer", example=1),
|
||||||
|
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/Card")),
|
||||||
|
* @OA\Property(property="per_page", type="integer", example=20),
|
||||||
|
* @OA\Property(property="total", type="integer", example=10)
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function index() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Post(
|
||||||
|
* path="/api/v1/cards",
|
||||||
|
* tags={"Cards"},
|
||||||
|
* summary="카드 등록",
|
||||||
|
* description="새로운 카드를 등록합니다. 카드번호는 암호화되어 저장됩니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\RequestBody(
|
||||||
|
* required=true,
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(ref="#/components/schemas/CardCreateRequest")
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=201,
|
||||||
|
* description="등록 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", ref="#/components/schemas/Card")
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=400, description="잘못된 요청", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function store() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/v1/cards/active",
|
||||||
|
* tags={"Cards"},
|
||||||
|
* summary="활성 카드 목록 (셀렉트박스용)",
|
||||||
|
* description="활성 상태의 카드 목록을 간단한 형태로 조회합니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="조회 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/CardListItem"))
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function active() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/v1/cards/{id}",
|
||||||
|
* tags={"Cards"},
|
||||||
|
* summary="카드 상세 조회",
|
||||||
|
* description="카드 상세 정보를 조회합니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="id", in="path", required=true, description="카드 ID", @OA\Schema(type="integer")),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="조회 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", ref="#/components/schemas/Card")
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=404, description="카드 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function show() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Put(
|
||||||
|
* path="/api/v1/cards/{id}",
|
||||||
|
* tags={"Cards"},
|
||||||
|
* summary="카드 수정",
|
||||||
|
* description="카드 정보를 수정합니다.",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="id", in="path", required=true, description="카드 ID", @OA\Schema(type="integer")),
|
||||||
|
*
|
||||||
|
* @OA\RequestBody(
|
||||||
|
* required=true,
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(ref="#/components/schemas/CardUpdateRequest")
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="수정 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", ref="#/components/schemas/Card")
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=400, description="잘못된 요청", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=404, description="카드 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function update() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Delete(
|
||||||
|
* path="/api/v1/cards/{id}",
|
||||||
|
* tags={"Cards"},
|
||||||
|
* summary="카드 삭제",
|
||||||
|
* description="카드를 삭제합니다. (Soft Delete)",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="id", in="path", required=true, description="카드 ID", @OA\Schema(type="integer")),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="삭제 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(ref="#/components/schemas/ApiResponse")
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=404, description="카드 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function destroy() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Patch(
|
||||||
|
* path="/api/v1/cards/{id}/toggle",
|
||||||
|
* tags={"Cards"},
|
||||||
|
* summary="카드 상태 토글",
|
||||||
|
* description="카드의 상태를 토글합니다. (active ↔ inactive)",
|
||||||
|
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||||
|
*
|
||||||
|
* @OA\Parameter(name="id", in="path", required=true, description="카드 ID", @OA\Schema(type="integer")),
|
||||||
|
*
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="토글 성공",
|
||||||
|
*
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* allOf={
|
||||||
|
*
|
||||||
|
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||||
|
* @OA\Schema(
|
||||||
|
*
|
||||||
|
* @OA\Property(property="data", ref="#/components/schemas/Card")
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=404, description="카드 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||||
|
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function toggle() {}
|
||||||
|
}
|
||||||
43
database/migrations/2025_12_17_120000_create_cards_table.php
Normal file
43
database/migrations/2025_12_17_120000_create_cards_table.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?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('cards', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
|
||||||
|
$table->string('card_company', 50)->comment('카드사');
|
||||||
|
$table->text('card_number_encrypted')->comment('암호화된 카드번호');
|
||||||
|
$table->string('card_number_last4', 4)->comment('카드번호 끝 4자리');
|
||||||
|
$table->string('expiry_date', 5)->comment('유효기간 (MM/YY)');
|
||||||
|
$table->text('card_password_encrypted')->nullable()->comment('암호화된 비밀번호 앞2자리');
|
||||||
|
$table->string('card_name', 100)->comment('카드 별칭');
|
||||||
|
$table->string('status', 20)->default('active')->comment('상태: active/inactive');
|
||||||
|
$table->unsignedBigInteger('assigned_user_id')->nullable()->comment('담당자 ID');
|
||||||
|
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID');
|
||||||
|
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID');
|
||||||
|
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID');
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->index('tenant_id', 'idx_cards_tenant');
|
||||||
|
$table->index('status', 'idx_cards_status');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('cards');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?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('bank_accounts', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
|
||||||
|
$table->string('bank_code', 10)->comment('은행 코드');
|
||||||
|
$table->string('bank_name', 50)->comment('은행명');
|
||||||
|
$table->string('account_number', 30)->comment('계좌번호');
|
||||||
|
$table->string('account_holder', 50)->comment('예금주');
|
||||||
|
$table->string('account_name', 100)->comment('계좌 별칭');
|
||||||
|
$table->string('status', 20)->default('active')->comment('상태: active/inactive');
|
||||||
|
$table->unsignedBigInteger('assigned_user_id')->nullable()->comment('담당자 ID');
|
||||||
|
$table->boolean('is_primary')->default(false)->comment('대표계좌 여부');
|
||||||
|
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID');
|
||||||
|
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID');
|
||||||
|
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID');
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->index('tenant_id', 'idx_bank_accounts_tenant');
|
||||||
|
$table->index('status', 'idx_bank_accounts_status');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('bank_accounts');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -49,6 +49,8 @@
|
|||||||
use App\Http\Controllers\Api\V1\RegisterController;
|
use App\Http\Controllers\Api\V1\RegisterController;
|
||||||
use App\Http\Controllers\Api\V1\RoleController;
|
use App\Http\Controllers\Api\V1\RoleController;
|
||||||
use App\Http\Controllers\Api\V1\RolePermissionController;
|
use App\Http\Controllers\Api\V1\RolePermissionController;
|
||||||
|
use App\Http\Controllers\Api\V1\BankAccountController;
|
||||||
|
use App\Http\Controllers\Api\V1\CardController;
|
||||||
use App\Http\Controllers\Api\V1\SiteController;
|
use App\Http\Controllers\Api\V1\SiteController;
|
||||||
use App\Http\Controllers\Api\V1\TenantController;
|
use App\Http\Controllers\Api\V1\TenantController;
|
||||||
use App\Http\Controllers\Api\V1\TenantFieldSettingController;
|
use App\Http\Controllers\Api\V1\TenantFieldSettingController;
|
||||||
@@ -269,6 +271,29 @@
|
|||||||
Route::delete('/{id}', [SiteController::class, 'destroy'])->whereNumber('id')->name('v1.sites.destroy');
|
Route::delete('/{id}', [SiteController::class, 'destroy'])->whereNumber('id')->name('v1.sites.destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Card API (카드 관리)
|
||||||
|
Route::prefix('cards')->group(function () {
|
||||||
|
Route::get('', [CardController::class, 'index'])->name('v1.cards.index');
|
||||||
|
Route::post('', [CardController::class, 'store'])->name('v1.cards.store');
|
||||||
|
Route::get('/active', [CardController::class, 'active'])->name('v1.cards.active');
|
||||||
|
Route::get('/{id}', [CardController::class, 'show'])->whereNumber('id')->name('v1.cards.show');
|
||||||
|
Route::put('/{id}', [CardController::class, 'update'])->whereNumber('id')->name('v1.cards.update');
|
||||||
|
Route::delete('/{id}', [CardController::class, 'destroy'])->whereNumber('id')->name('v1.cards.destroy');
|
||||||
|
Route::patch('/{id}/toggle', [CardController::class, 'toggle'])->whereNumber('id')->name('v1.cards.toggle');
|
||||||
|
});
|
||||||
|
|
||||||
|
// BankAccount API (계좌 관리)
|
||||||
|
Route::prefix('bank-accounts')->group(function () {
|
||||||
|
Route::get('', [BankAccountController::class, 'index'])->name('v1.bank-accounts.index');
|
||||||
|
Route::post('', [BankAccountController::class, 'store'])->name('v1.bank-accounts.store');
|
||||||
|
Route::get('/active', [BankAccountController::class, 'active'])->name('v1.bank-accounts.active');
|
||||||
|
Route::get('/{id}', [BankAccountController::class, 'show'])->whereNumber('id')->name('v1.bank-accounts.show');
|
||||||
|
Route::put('/{id}', [BankAccountController::class, 'update'])->whereNumber('id')->name('v1.bank-accounts.update');
|
||||||
|
Route::delete('/{id}', [BankAccountController::class, 'destroy'])->whereNumber('id')->name('v1.bank-accounts.destroy');
|
||||||
|
Route::patch('/{id}/toggle', [BankAccountController::class, 'toggle'])->whereNumber('id')->name('v1.bank-accounts.toggle');
|
||||||
|
Route::patch('/{id}/set-primary', [BankAccountController::class, 'setPrimary'])->whereNumber('id')->name('v1.bank-accounts.set-primary');
|
||||||
|
});
|
||||||
|
|
||||||
// Permission API
|
// Permission API
|
||||||
Route::prefix('permissions')->group(function () {
|
Route::prefix('permissions')->group(function () {
|
||||||
Route::get('departments/{dept_id}/menu-matrix', [PermissionController::class, 'deptMenuMatrix'])->name('v1.permissions.deptMenuMatrix'); // 부서별 권한 메트릭스
|
Route::get('departments/{dept_id}/menu-matrix', [PermissionController::class, 'deptMenuMatrix'])->name('v1.permissions.deptMenuMatrix'); // 부서별 권한 메트릭스
|
||||||
|
|||||||
Reference in New Issue
Block a user