feat: 어음 관리(Bill Management) API 추가
- BillController: 어음 CRUD + 상태변경 + 요약 API - BillService: 비즈니스 로직 (멀티테넌트 지원) - Bill, BillInstallment 모델: 날짜 포맷(Y-m-d) toArray 오버라이드 - FormRequest: Store/Update/UpdateStatus 유효성 검사 - Swagger 문서: BillApi.php - 마이그레이션: bills, bill_installments 테이블 - DummyBillSeeder: 테스트 데이터 30건 + 차수 12건 - API Routes: /api/v1/bills 엔드포인트 7개
This commit is contained in:
112
app/Http/Controllers/Api/V1/BillController.php
Normal file
112
app/Http/Controllers/Api/V1/BillController.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\V1\Bill\StoreBillRequest;
|
||||
use App\Http\Requests\V1\Bill\UpdateBillRequest;
|
||||
use App\Http\Requests\V1\Bill\UpdateBillStatusRequest;
|
||||
use App\Services\BillService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BillController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BillService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 어음 목록
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$params = $request->only([
|
||||
'search',
|
||||
'bill_type',
|
||||
'status',
|
||||
'client_id',
|
||||
'is_electronic',
|
||||
'issue_start_date',
|
||||
'issue_end_date',
|
||||
'maturity_start_date',
|
||||
'maturity_end_date',
|
||||
'sort_by',
|
||||
'sort_dir',
|
||||
'per_page',
|
||||
'page',
|
||||
]);
|
||||
|
||||
$bills = $this->service->index($params);
|
||||
|
||||
return ApiResponse::success($bills, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 등록
|
||||
*/
|
||||
public function store(StoreBillRequest $request)
|
||||
{
|
||||
$bill = $this->service->store($request->validated());
|
||||
|
||||
return ApiResponse::success($bill, __('message.created'), [], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 상세
|
||||
*/
|
||||
public function show(int $id)
|
||||
{
|
||||
$bill = $this->service->show($id);
|
||||
|
||||
return ApiResponse::success($bill, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 수정
|
||||
*/
|
||||
public function update(int $id, UpdateBillRequest $request)
|
||||
{
|
||||
$bill = $this->service->update($id, $request->validated());
|
||||
|
||||
return ApiResponse::success($bill, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 삭제
|
||||
*/
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$this->service->destroy($id);
|
||||
|
||||
return ApiResponse::success(null, __('message.deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 상태 변경
|
||||
*/
|
||||
public function updateStatus(int $id, UpdateBillStatusRequest $request)
|
||||
{
|
||||
$bill = $this->service->updateStatus($id, $request->validated()['status']);
|
||||
|
||||
return ApiResponse::success($bill, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 요약 (기간별 합계)
|
||||
*/
|
||||
public function summary(Request $request)
|
||||
{
|
||||
$params = $request->only([
|
||||
'bill_type',
|
||||
'issue_start_date',
|
||||
'issue_end_date',
|
||||
'maturity_start_date',
|
||||
'maturity_end_date',
|
||||
]);
|
||||
|
||||
$summary = $this->service->summary($params);
|
||||
|
||||
return ApiResponse::success($summary, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
67
app/Http/Requests/V1/Bill/StoreBillRequest.php
Normal file
67
app/Http/Requests/V1/Bill/StoreBillRequest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\V1\Bill;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreBillRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'bill_number' => ['nullable', 'string', 'max:50'],
|
||||
'bill_type' => ['required', 'string', 'in:received,issued'],
|
||||
'client_id' => ['nullable', 'integer', 'exists:clients,id'],
|
||||
'client_name' => ['nullable', 'string', 'max:100'],
|
||||
'amount' => ['required', 'numeric', 'min:0'],
|
||||
'issue_date' => ['required', 'date'],
|
||||
'maturity_date' => ['required', 'date', 'after_or_equal:issue_date'],
|
||||
'status' => ['nullable', 'string', 'in:stored,maturityAlert,maturityResult,paymentComplete,dishonored,collectionRequest,collectionComplete,suing'],
|
||||
'reason' => ['nullable', 'string', 'max:255'],
|
||||
'installment_count' => ['nullable', 'integer', 'min:0'],
|
||||
'note' => ['nullable', 'string', 'max:1000'],
|
||||
'is_electronic' => ['nullable', 'boolean'],
|
||||
'bank_account_id' => ['nullable', 'integer', 'exists:bank_accounts,id'],
|
||||
'installments' => ['nullable', 'array'],
|
||||
'installments.*.date' => ['required_with:installments', 'date'],
|
||||
'installments.*.amount' => ['required_with:installments', 'numeric', 'min:0'],
|
||||
'installments.*.note' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'bill_type.required' => __('validation.required', ['attribute' => __('validation.attributes.bill_type')]),
|
||||
'bill_type.in' => __('validation.in', ['attribute' => __('validation.attributes.bill_type')]),
|
||||
'amount.required' => __('validation.required', ['attribute' => __('validation.attributes.amount')]),
|
||||
'amount.min' => __('validation.min.numeric', ['attribute' => __('validation.attributes.amount'), 'min' => 0]),
|
||||
'issue_date.required' => __('validation.required', ['attribute' => __('validation.attributes.issue_date')]),
|
||||
'maturity_date.required' => __('validation.required', ['attribute' => __('validation.attributes.maturity_date')]),
|
||||
'maturity_date.after_or_equal' => __('validation.after_or_equal', ['attribute' => __('validation.attributes.maturity_date'), 'date' => __('validation.attributes.issue_date')]),
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'bill_number' => __('validation.attributes.bill_number'),
|
||||
'bill_type' => __('validation.attributes.bill_type'),
|
||||
'client_id' => __('validation.attributes.client_id'),
|
||||
'client_name' => __('validation.attributes.client_name'),
|
||||
'amount' => __('validation.attributes.amount'),
|
||||
'issue_date' => __('validation.attributes.issue_date'),
|
||||
'maturity_date' => __('validation.attributes.maturity_date'),
|
||||
'status' => __('validation.attributes.status'),
|
||||
'reason' => __('validation.attributes.reason'),
|
||||
'note' => __('validation.attributes.note'),
|
||||
'is_electronic' => __('validation.attributes.is_electronic'),
|
||||
'bank_account_id' => __('validation.attributes.bank_account_id'),
|
||||
];
|
||||
}
|
||||
}
|
||||
63
app/Http/Requests/V1/Bill/UpdateBillRequest.php
Normal file
63
app/Http/Requests/V1/Bill/UpdateBillRequest.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\V1\Bill;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateBillRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'bill_number' => ['nullable', 'string', 'max:50'],
|
||||
'bill_type' => ['nullable', 'string', 'in:received,issued'],
|
||||
'client_id' => ['nullable', 'integer', 'exists:clients,id'],
|
||||
'client_name' => ['nullable', 'string', 'max:100'],
|
||||
'amount' => ['nullable', 'numeric', 'min:0'],
|
||||
'issue_date' => ['nullable', 'date'],
|
||||
'maturity_date' => ['nullable', 'date', 'after_or_equal:issue_date'],
|
||||
'status' => ['nullable', 'string', 'in:stored,maturityAlert,maturityResult,paymentComplete,dishonored,collectionRequest,collectionComplete,suing'],
|
||||
'reason' => ['nullable', 'string', 'max:255'],
|
||||
'installment_count' => ['nullable', 'integer', 'min:0'],
|
||||
'note' => ['nullable', 'string', 'max:1000'],
|
||||
'is_electronic' => ['nullable', 'boolean'],
|
||||
'bank_account_id' => ['nullable', 'integer', 'exists:bank_accounts,id'],
|
||||
'installments' => ['nullable', 'array'],
|
||||
'installments.*.date' => ['required_with:installments', 'date'],
|
||||
'installments.*.amount' => ['required_with:installments', 'numeric', 'min:0'],
|
||||
'installments.*.note' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'bill_type.in' => __('validation.in', ['attribute' => __('validation.attributes.bill_type')]),
|
||||
'amount.min' => __('validation.min.numeric', ['attribute' => __('validation.attributes.amount'), 'min' => 0]),
|
||||
'maturity_date.after_or_equal' => __('validation.after_or_equal', ['attribute' => __('validation.attributes.maturity_date'), 'date' => __('validation.attributes.issue_date')]),
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'bill_number' => __('validation.attributes.bill_number'),
|
||||
'bill_type' => __('validation.attributes.bill_type'),
|
||||
'client_id' => __('validation.attributes.client_id'),
|
||||
'client_name' => __('validation.attributes.client_name'),
|
||||
'amount' => __('validation.attributes.amount'),
|
||||
'issue_date' => __('validation.attributes.issue_date'),
|
||||
'maturity_date' => __('validation.attributes.maturity_date'),
|
||||
'status' => __('validation.attributes.status'),
|
||||
'reason' => __('validation.attributes.reason'),
|
||||
'note' => __('validation.attributes.note'),
|
||||
'is_electronic' => __('validation.attributes.is_electronic'),
|
||||
'bank_account_id' => __('validation.attributes.bank_account_id'),
|
||||
];
|
||||
}
|
||||
}
|
||||
35
app/Http/Requests/V1/Bill/UpdateBillStatusRequest.php
Normal file
35
app/Http/Requests/V1/Bill/UpdateBillStatusRequest.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\V1\Bill;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateBillStatusRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'status' => ['required', 'string', 'in:stored,maturityAlert,maturityResult,paymentComplete,dishonored,collectionRequest,collectionComplete,suing'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'status.required' => __('validation.required', ['attribute' => __('validation.attributes.status')]),
|
||||
'status.in' => __('validation.in', ['attribute' => __('validation.attributes.status')]),
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'status' => __('validation.attributes.status'),
|
||||
];
|
||||
}
|
||||
}
|
||||
175
app/Models/Tenants/Bill.php
Normal file
175
app/Models/Tenants/Bill.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Bill extends Model
|
||||
{
|
||||
use BelongsToTenant, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'bill_number',
|
||||
'bill_type',
|
||||
'client_id',
|
||||
'client_name',
|
||||
'amount',
|
||||
'issue_date',
|
||||
'maturity_date',
|
||||
'status',
|
||||
'reason',
|
||||
'installment_count',
|
||||
'note',
|
||||
'is_electronic',
|
||||
'bank_account_id',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'issue_date' => 'date',
|
||||
'maturity_date' => 'date',
|
||||
'amount' => 'decimal:2',
|
||||
'client_id' => 'integer',
|
||||
'bank_account_id' => 'integer',
|
||||
'installment_count' => 'integer',
|
||||
'is_electronic' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* 배열/JSON 변환 시 날짜 형식 지정
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = parent::toArray();
|
||||
|
||||
// 날짜 필드를 Y-m-d 형식으로 변환
|
||||
if (isset($array['issue_date']) && $this->issue_date) {
|
||||
$array['issue_date'] = $this->issue_date->format('Y-m-d');
|
||||
}
|
||||
if (isset($array['maturity_date']) && $this->maturity_date) {
|
||||
$array['maturity_date'] = $this->maturity_date->format('Y-m-d');
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 구분 목록
|
||||
*/
|
||||
public const BILL_TYPES = [
|
||||
'received' => '수취',
|
||||
'issued' => '발행',
|
||||
];
|
||||
|
||||
/**
|
||||
* 수취 어음 상태 목록
|
||||
*/
|
||||
public const RECEIVED_STATUSES = [
|
||||
'stored' => '보관중',
|
||||
'maturityAlert' => '만기입금(7일전)',
|
||||
'maturityResult' => '만기결과',
|
||||
'paymentComplete' => '결제완료',
|
||||
'dishonored' => '부도',
|
||||
];
|
||||
|
||||
/**
|
||||
* 발행 어음 상태 목록
|
||||
*/
|
||||
public const ISSUED_STATUSES = [
|
||||
'stored' => '보관중',
|
||||
'maturityAlert' => '만기입금(7일전)',
|
||||
'collectionRequest' => '추심의뢰',
|
||||
'collectionComplete' => '추심완료',
|
||||
'suing' => '추소중',
|
||||
'dishonored' => '부도',
|
||||
];
|
||||
|
||||
/**
|
||||
* 거래처 관계
|
||||
*/
|
||||
public function client(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Orders\Client::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 입금/출금 계좌 관계
|
||||
*/
|
||||
public function bankAccount(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BankAccount::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 차수 관계
|
||||
*/
|
||||
public function installments(): HasMany
|
||||
{
|
||||
return $this->hasMany(BillInstallment::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 생성자 관계
|
||||
*/
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Members\User::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래처명 조회 (회원/비회원 통합)
|
||||
*/
|
||||
public function getDisplayClientNameAttribute(): string
|
||||
{
|
||||
if ($this->client) {
|
||||
return $this->client->name;
|
||||
}
|
||||
|
||||
return $this->client_name ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 구분 라벨
|
||||
*/
|
||||
public function getBillTypeLabelAttribute(): string
|
||||
{
|
||||
return self::BILL_TYPES[$this->bill_type] ?? $this->bill_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 라벨
|
||||
*/
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
if ($this->bill_type === 'received') {
|
||||
return self::RECEIVED_STATUSES[$this->status] ?? $this->status;
|
||||
}
|
||||
|
||||
return self::ISSUED_STATUSES[$this->status] ?? $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 만기까지 남은 일수
|
||||
*/
|
||||
public function getDaysToMaturityAttribute(): int
|
||||
{
|
||||
return now()->diffInDays($this->maturity_date, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 만기 7일 전 여부
|
||||
*/
|
||||
public function isMaturityAlertPeriod(): bool
|
||||
{
|
||||
$days = $this->days_to_maturity;
|
||||
|
||||
return $days >= 0 && $days <= 7;
|
||||
}
|
||||
}
|
||||
53
app/Models/Tenants/BillInstallment.php
Normal file
53
app/Models/Tenants/BillInstallment.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class BillInstallment extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'bill_id',
|
||||
'installment_date',
|
||||
'amount',
|
||||
'note',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'installment_date' => 'date',
|
||||
'amount' => 'decimal:2',
|
||||
'bill_id' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* 배열/JSON 변환 시 날짜 형식 지정
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = parent::toArray();
|
||||
|
||||
if (isset($array['installment_date']) && $this->installment_date) {
|
||||
$array['installment_date'] = $this->installment_date->format('Y-m-d');
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 관계
|
||||
*/
|
||||
public function bill(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Bill::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 생성자 관계
|
||||
*/
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Members\User::class, 'created_by');
|
||||
}
|
||||
}
|
||||
352
app/Services/BillService.php
Normal file
352
app/Services/BillService.php
Normal file
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Tenants\Bill;
|
||||
use App\Models\Tenants\BillInstallment;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BillService extends Service
|
||||
{
|
||||
/**
|
||||
* 어음 목록 조회
|
||||
*/
|
||||
public function index(array $params): LengthAwarePaginator
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$query = Bill::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->with(['client:id,name', 'bankAccount:id,bank_name,account_name', 'installments']);
|
||||
|
||||
// 검색어 필터
|
||||
if (! empty($params['search'])) {
|
||||
$search = $params['search'];
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('bill_number', 'like', "%{$search}%")
|
||||
->orWhere('client_name', 'like', "%{$search}%")
|
||||
->orWhere('note', 'like', "%{$search}%")
|
||||
->orWhereHas('client', function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 어음 구분 필터 (received/issued)
|
||||
if (! empty($params['bill_type'])) {
|
||||
$query->where('bill_type', $params['bill_type']);
|
||||
}
|
||||
|
||||
// 상태 필터
|
||||
if (! empty($params['status'])) {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
// 거래처 필터
|
||||
if (! empty($params['client_id'])) {
|
||||
$query->where('client_id', $params['client_id']);
|
||||
}
|
||||
|
||||
// 전자어음 필터
|
||||
if (isset($params['is_electronic']) && $params['is_electronic'] !== '') {
|
||||
$query->where('is_electronic', (bool) $params['is_electronic']);
|
||||
}
|
||||
|
||||
// 발행일 범위 필터
|
||||
if (! empty($params['issue_start_date'])) {
|
||||
$query->where('issue_date', '>=', $params['issue_start_date']);
|
||||
}
|
||||
if (! empty($params['issue_end_date'])) {
|
||||
$query->where('issue_date', '<=', $params['issue_end_date']);
|
||||
}
|
||||
|
||||
// 만기일 범위 필터
|
||||
if (! empty($params['maturity_start_date'])) {
|
||||
$query->where('maturity_date', '>=', $params['maturity_start_date']);
|
||||
}
|
||||
if (! empty($params['maturity_end_date'])) {
|
||||
$query->where('maturity_date', '<=', $params['maturity_end_date']);
|
||||
}
|
||||
|
||||
// 정렬
|
||||
$sortBy = $params['sort_by'] ?? 'issue_date';
|
||||
$sortDir = $params['sort_dir'] ?? 'desc';
|
||||
$query->orderBy($sortBy, $sortDir);
|
||||
|
||||
// 페이지네이션
|
||||
$perPage = $params['per_page'] ?? 20;
|
||||
|
||||
return $query->paginate($perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 상세 조회
|
||||
*/
|
||||
public function show(int $id): Bill
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
return Bill::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->with(['client:id,name', 'bankAccount:id,bank_name,account_name', 'installments', 'creator:id,name'])
|
||||
->findOrFail($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 등록
|
||||
*/
|
||||
public function store(array $data): Bill
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
return DB::transaction(function () use ($data, $tenantId, $userId) {
|
||||
// 어음번호 자동 생성 (없을 경우)
|
||||
$billNumber = $data['bill_number'] ?? $this->generateBillNumber($tenantId);
|
||||
|
||||
$bill = new Bill;
|
||||
$bill->tenant_id = $tenantId;
|
||||
$bill->bill_number = $billNumber;
|
||||
$bill->bill_type = $data['bill_type'];
|
||||
$bill->client_id = $data['client_id'] ?? null;
|
||||
$bill->client_name = $data['client_name'] ?? null;
|
||||
$bill->amount = $data['amount'];
|
||||
$bill->issue_date = $data['issue_date'];
|
||||
$bill->maturity_date = $data['maturity_date'];
|
||||
$bill->status = $data['status'] ?? 'stored';
|
||||
$bill->reason = $data['reason'] ?? null;
|
||||
$bill->installment_count = $data['installment_count'] ?? 0;
|
||||
$bill->note = $data['note'] ?? null;
|
||||
$bill->is_electronic = $data['is_electronic'] ?? false;
|
||||
$bill->bank_account_id = $data['bank_account_id'] ?? null;
|
||||
$bill->created_by = $userId;
|
||||
$bill->updated_by = $userId;
|
||||
$bill->save();
|
||||
|
||||
// 차수 관리 저장
|
||||
if (! empty($data['installments'])) {
|
||||
foreach ($data['installments'] as $installment) {
|
||||
BillInstallment::create([
|
||||
'bill_id' => $bill->id,
|
||||
'installment_date' => $installment['date'],
|
||||
'amount' => $installment['amount'],
|
||||
'note' => $installment['note'] ?? null,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
}
|
||||
// 차수 카운트 업데이트
|
||||
$bill->installment_count = count($data['installments']);
|
||||
$bill->save();
|
||||
}
|
||||
|
||||
return $bill->load(['client:id,name', 'bankAccount:id,bank_name,account_name', 'installments']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 수정
|
||||
*/
|
||||
public function update(int $id, array $data): Bill
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
return DB::transaction(function () use ($id, $data, $tenantId, $userId) {
|
||||
$bill = Bill::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->findOrFail($id);
|
||||
|
||||
if (isset($data['bill_number'])) {
|
||||
$bill->bill_number = $data['bill_number'];
|
||||
}
|
||||
if (isset($data['bill_type'])) {
|
||||
$bill->bill_type = $data['bill_type'];
|
||||
}
|
||||
if (array_key_exists('client_id', $data)) {
|
||||
$bill->client_id = $data['client_id'];
|
||||
}
|
||||
if (array_key_exists('client_name', $data)) {
|
||||
$bill->client_name = $data['client_name'];
|
||||
}
|
||||
if (isset($data['amount'])) {
|
||||
$bill->amount = $data['amount'];
|
||||
}
|
||||
if (isset($data['issue_date'])) {
|
||||
$bill->issue_date = $data['issue_date'];
|
||||
}
|
||||
if (isset($data['maturity_date'])) {
|
||||
$bill->maturity_date = $data['maturity_date'];
|
||||
}
|
||||
if (isset($data['status'])) {
|
||||
$bill->status = $data['status'];
|
||||
}
|
||||
if (array_key_exists('reason', $data)) {
|
||||
$bill->reason = $data['reason'];
|
||||
}
|
||||
if (array_key_exists('note', $data)) {
|
||||
$bill->note = $data['note'];
|
||||
}
|
||||
if (isset($data['is_electronic'])) {
|
||||
$bill->is_electronic = $data['is_electronic'];
|
||||
}
|
||||
if (array_key_exists('bank_account_id', $data)) {
|
||||
$bill->bank_account_id = $data['bank_account_id'];
|
||||
}
|
||||
|
||||
$bill->updated_by = $userId;
|
||||
$bill->save();
|
||||
|
||||
// 차수 관리 업데이트 (전체 교체)
|
||||
if (isset($data['installments'])) {
|
||||
// 기존 차수 삭제
|
||||
$bill->installments()->delete();
|
||||
|
||||
// 새 차수 추가
|
||||
foreach ($data['installments'] as $installment) {
|
||||
BillInstallment::create([
|
||||
'bill_id' => $bill->id,
|
||||
'installment_date' => $installment['date'],
|
||||
'amount' => $installment['amount'],
|
||||
'note' => $installment['note'] ?? null,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
}
|
||||
// 차수 카운트 업데이트
|
||||
$bill->installment_count = count($data['installments']);
|
||||
$bill->save();
|
||||
}
|
||||
|
||||
return $bill->fresh(['client:id,name', 'bankAccount:id,bank_name,account_name', 'installments']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 삭제
|
||||
*/
|
||||
public function destroy(int $id): bool
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
return DB::transaction(function () use ($id, $tenantId, $userId) {
|
||||
$bill = Bill::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->findOrFail($id);
|
||||
|
||||
$bill->deleted_by = $userId;
|
||||
$bill->save();
|
||||
$bill->delete();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 상태 변경
|
||||
*/
|
||||
public function updateStatus(int $id, string $status): Bill
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
$bill = Bill::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->findOrFail($id);
|
||||
|
||||
$bill->status = $status;
|
||||
$bill->updated_by = $userId;
|
||||
$bill->save();
|
||||
|
||||
return $bill->fresh(['client:id,name', 'bankAccount:id,bank_name,account_name', 'installments']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음 요약 (기간별 합계)
|
||||
*/
|
||||
public function summary(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$query = Bill::query()
|
||||
->where('tenant_id', $tenantId);
|
||||
|
||||
// 어음 구분 필터
|
||||
if (! empty($params['bill_type'])) {
|
||||
$query->where('bill_type', $params['bill_type']);
|
||||
}
|
||||
|
||||
// 발행일 범위 필터
|
||||
if (! empty($params['issue_start_date'])) {
|
||||
$query->where('issue_date', '>=', $params['issue_start_date']);
|
||||
}
|
||||
if (! empty($params['issue_end_date'])) {
|
||||
$query->where('issue_date', '<=', $params['issue_end_date']);
|
||||
}
|
||||
|
||||
// 만기일 범위 필터
|
||||
if (! empty($params['maturity_start_date'])) {
|
||||
$query->where('maturity_date', '>=', $params['maturity_start_date']);
|
||||
}
|
||||
if (! empty($params['maturity_end_date'])) {
|
||||
$query->where('maturity_date', '<=', $params['maturity_end_date']);
|
||||
}
|
||||
|
||||
// 전체 합계
|
||||
$total = (clone $query)->sum('amount');
|
||||
$count = (clone $query)->count();
|
||||
|
||||
// 구분별 합계
|
||||
$byType = (clone $query)
|
||||
->select('bill_type', DB::raw('SUM(amount) as total'), DB::raw('COUNT(*) as count'))
|
||||
->groupBy('bill_type')
|
||||
->get()
|
||||
->keyBy('bill_type')
|
||||
->toArray();
|
||||
|
||||
// 상태별 합계
|
||||
$byStatus = (clone $query)
|
||||
->select('status', DB::raw('SUM(amount) as total'), DB::raw('COUNT(*) as count'))
|
||||
->groupBy('status')
|
||||
->get()
|
||||
->keyBy('status')
|
||||
->toArray();
|
||||
|
||||
// 만기 임박 (7일 이내)
|
||||
$maturityAlert = (clone $query)
|
||||
->where('maturity_date', '>=', now()->toDateString())
|
||||
->where('maturity_date', '<=', now()->addDays(7)->toDateString())
|
||||
->whereNotIn('status', ['paymentComplete', 'collectionComplete', 'dishonored'])
|
||||
->sum('amount');
|
||||
|
||||
return [
|
||||
'total_amount' => (float) $total,
|
||||
'total_count' => $count,
|
||||
'by_type' => $byType,
|
||||
'by_status' => $byStatus,
|
||||
'maturity_alert_amount' => (float) $maturityAlert,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 어음번호 자동 생성
|
||||
*/
|
||||
private function generateBillNumber(int $tenantId): string
|
||||
{
|
||||
$prefix = date('Ym');
|
||||
$lastBill = Bill::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('bill_number', 'like', $prefix.'%')
|
||||
->orderBy('bill_number', 'desc')
|
||||
->first();
|
||||
|
||||
if ($lastBill) {
|
||||
$lastNumber = (int) substr($lastBill->bill_number, strlen($prefix));
|
||||
$nextNumber = $lastNumber + 1;
|
||||
} else {
|
||||
$nextNumber = 1;
|
||||
}
|
||||
|
||||
return $prefix.str_pad((string) $nextNumber, 6, '0', STR_PAD_LEFT);
|
||||
}
|
||||
}
|
||||
347
app/Swagger/v1/BillApi.php
Normal file
347
app/Swagger/v1/BillApi.php
Normal file
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
namespace App\Swagger\v1;
|
||||
|
||||
/**
|
||||
* @OA\Tag(
|
||||
* name="Bills",
|
||||
* description="어음 관리 API"
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="Bill",
|
||||
* type="object",
|
||||
* required={"id", "tenant_id", "bill_number", "bill_type", "amount", "issue_date", "maturity_date", "status"},
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="tenant_id", type="integer", example=1),
|
||||
* @OA\Property(property="bill_number", type="string", example="202412000001", description="어음번호"),
|
||||
* @OA\Property(property="bill_type", type="string", enum={"received", "issued"}, example="received", description="어음 구분: received=수취, issued=발행"),
|
||||
* @OA\Property(property="client_id", type="integer", nullable=true, example=10, description="거래처 ID"),
|
||||
* @OA\Property(property="client_name", type="string", nullable=true, example="(주)삼성전자", description="거래처명 (비회원용)"),
|
||||
* @OA\Property(property="amount", type="number", format="float", example=10000000, description="금액"),
|
||||
* @OA\Property(property="issue_date", type="string", format="date", example="2024-12-01", description="발행일"),
|
||||
* @OA\Property(property="maturity_date", type="string", format="date", example="2025-03-01", description="만기일"),
|
||||
* @OA\Property(property="status", type="string", example="stored", description="상태: stored/maturityAlert/maturityResult/paymentComplete/dishonored/collectionRequest/collectionComplete/suing"),
|
||||
* @OA\Property(property="reason", type="string", nullable=true, example="거래 대금", description="사유"),
|
||||
* @OA\Property(property="installment_count", type="integer", example=0, description="차수"),
|
||||
* @OA\Property(property="note", type="string", nullable=true, example="메모 내용", description="메모/비고"),
|
||||
* @OA\Property(property="is_electronic", type="boolean", example=false, description="전자어음 여부"),
|
||||
* @OA\Property(property="bank_account_id", type="integer", nullable=true, example=5, description="입금/출금 계좌 ID"),
|
||||
* @OA\Property(property="created_by", type="integer", nullable=true, example=1),
|
||||
* @OA\Property(property="updated_by", type="integer", nullable=true, example=1),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time"),
|
||||
* @OA\Property(property="updated_at", type="string", format="date-time"),
|
||||
* @OA\Property(
|
||||
* property="client",
|
||||
* type="object",
|
||||
* nullable=true,
|
||||
* @OA\Property(property="id", type="integer", example=10),
|
||||
* @OA\Property(property="name", type="string", example="(주)삼성전자")
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="bank_account",
|
||||
* type="object",
|
||||
* nullable=true,
|
||||
* @OA\Property(property="id", type="integer", example=5),
|
||||
* @OA\Property(property="bank_name", type="string", example="국민은행"),
|
||||
* @OA\Property(property="account_name", type="string", example="운영계좌")
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="installments",
|
||||
* type="array",
|
||||
* @OA\Items(ref="#/components/schemas/BillInstallment")
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="BillInstallment",
|
||||
* type="object",
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="bill_id", type="integer", example=1),
|
||||
* @OA\Property(property="installment_date", type="string", format="date", example="2024-12-15", description="차수 일자"),
|
||||
* @OA\Property(property="amount", type="number", format="float", example=5000000, description="차수 금액"),
|
||||
* @OA\Property(property="note", type="string", nullable=true, example="1차 분할", description="비고"),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time"),
|
||||
* @OA\Property(property="updated_at", type="string", format="date-time")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="BillPagination",
|
||||
* type="object",
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* type="array",
|
||||
* @OA\Items(ref="#/components/schemas/Bill")
|
||||
* ),
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="last_page", type="integer", example=5),
|
||||
* @OA\Property(property="per_page", type="integer", example=20),
|
||||
* @OA\Property(property="total", type="integer", example=100),
|
||||
* @OA\Property(property="from", type="integer", example=1),
|
||||
* @OA\Property(property="to", type="integer", example=20)
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="BillCreateRequest",
|
||||
* type="object",
|
||||
* required={"bill_type", "amount", "issue_date", "maturity_date"},
|
||||
* @OA\Property(property="bill_number", type="string", nullable=true, example="202412000001", description="어음번호 (미입력시 자동생성)"),
|
||||
* @OA\Property(property="bill_type", type="string", enum={"received", "issued"}, example="received", description="어음 구분"),
|
||||
* @OA\Property(property="client_id", type="integer", nullable=true, example=10, description="거래처 ID"),
|
||||
* @OA\Property(property="client_name", type="string", nullable=true, example="(주)삼성전자", description="거래처명"),
|
||||
* @OA\Property(property="amount", type="number", example=10000000, description="금액"),
|
||||
* @OA\Property(property="issue_date", type="string", format="date", example="2024-12-01", description="발행일"),
|
||||
* @OA\Property(property="maturity_date", type="string", format="date", example="2025-03-01", description="만기일"),
|
||||
* @OA\Property(property="status", type="string", nullable=true, example="stored", description="상태"),
|
||||
* @OA\Property(property="reason", type="string", nullable=true, example="거래 대금", description="사유"),
|
||||
* @OA\Property(property="note", type="string", nullable=true, example="메모 내용", description="메모"),
|
||||
* @OA\Property(property="is_electronic", type="boolean", nullable=true, example=false, description="전자어음 여부"),
|
||||
* @OA\Property(property="bank_account_id", type="integer", nullable=true, example=5, description="계좌 ID"),
|
||||
* @OA\Property(
|
||||
* property="installments",
|
||||
* type="array",
|
||||
* nullable=true,
|
||||
* @OA\Items(
|
||||
* type="object",
|
||||
* @OA\Property(property="date", type="string", format="date", example="2024-12-15"),
|
||||
* @OA\Property(property="amount", type="number", example=5000000),
|
||||
* @OA\Property(property="note", type="string", nullable=true, example="1차 분할")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="BillUpdateRequest",
|
||||
* type="object",
|
||||
* @OA\Property(property="bill_number", type="string", nullable=true, example="202412000001"),
|
||||
* @OA\Property(property="bill_type", type="string", enum={"received", "issued"}, nullable=true),
|
||||
* @OA\Property(property="client_id", type="integer", nullable=true),
|
||||
* @OA\Property(property="client_name", type="string", nullable=true),
|
||||
* @OA\Property(property="amount", type="number", nullable=true),
|
||||
* @OA\Property(property="issue_date", type="string", format="date", nullable=true),
|
||||
* @OA\Property(property="maturity_date", type="string", format="date", nullable=true),
|
||||
* @OA\Property(property="status", type="string", nullable=true),
|
||||
* @OA\Property(property="reason", type="string", nullable=true),
|
||||
* @OA\Property(property="note", type="string", nullable=true),
|
||||
* @OA\Property(property="is_electronic", type="boolean", nullable=true),
|
||||
* @OA\Property(property="bank_account_id", type="integer", nullable=true),
|
||||
* @OA\Property(property="installments", type="array", nullable=true, @OA\Items(type="object"))
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="BillSummary",
|
||||
* type="object",
|
||||
* @OA\Property(property="total_amount", type="number", example=50000000, description="총 금액"),
|
||||
* @OA\Property(property="total_count", type="integer", example=10, description="총 건수"),
|
||||
* @OA\Property(
|
||||
* property="by_type",
|
||||
* type="object",
|
||||
* description="구분별 합계",
|
||||
* @OA\AdditionalProperties(
|
||||
* type="object",
|
||||
* @OA\Property(property="bill_type", type="string"),
|
||||
* @OA\Property(property="total", type="number"),
|
||||
* @OA\Property(property="count", type="integer")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="by_status",
|
||||
* type="object",
|
||||
* description="상태별 합계"
|
||||
* ),
|
||||
* @OA\Property(property="maturity_alert_amount", type="number", example=10000000, description="만기 임박 금액 (7일 이내)")
|
||||
* )
|
||||
*/
|
||||
class BillApi
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/bills",
|
||||
* operationId="getBills",
|
||||
* tags={"Bills"},
|
||||
* summary="어음 목록 조회",
|
||||
* description="어음 목록을 페이지네이션하여 조회합니다.",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="search", in="query", description="검색어 (어음번호, 거래처명, 메모)", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="bill_type", in="query", description="어음 구분", @OA\Schema(type="string", enum={"received", "issued"})),
|
||||
* @OA\Parameter(name="status", in="query", description="상태", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="client_id", in="query", description="거래처 ID", @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="is_electronic", in="query", description="전자어음 여부", @OA\Schema(type="boolean")),
|
||||
* @OA\Parameter(name="issue_start_date", in="query", description="발행일 시작", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="issue_end_date", in="query", description="발행일 종료", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="maturity_start_date", in="query", description="만기일 시작", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="maturity_end_date", in="query", description="만기일 종료", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="sort_by", in="query", description="정렬 기준", @OA\Schema(type="string", default="issue_date")),
|
||||
* @OA\Parameter(name="sort_dir", in="query", description="정렬 방향", @OA\Schema(type="string", enum={"asc", "desc"}, default="desc")),
|
||||
* @OA\Parameter(name="per_page", in="query", description="페이지당 건수", @OA\Schema(type="integer", default=20)),
|
||||
* @OA\Parameter(name="page", in="query", description="페이지 번호", @OA\Schema(type="integer", default=1)),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="데이터를 조회했습니다."),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/BillPagination")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/bills",
|
||||
* operationId="storeBill",
|
||||
* tags={"Bills"},
|
||||
* summary="어음 등록",
|
||||
* description="새로운 어음을 등록합니다.",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/BillCreateRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=201,
|
||||
* description="생성 성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="생성되었습니다."),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Bill")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=422, description="유효성 검사 실패")
|
||||
* )
|
||||
*/
|
||||
public function store() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/bills/{id}",
|
||||
* operationId="showBill",
|
||||
* tags={"Bills"},
|
||||
* 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(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="데이터를 조회했습니다."),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Bill")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="어음을 찾을 수 없음")
|
||||
* )
|
||||
*/
|
||||
public function show() {}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/api/v1/bills/{id}",
|
||||
* operationId="updateBill",
|
||||
* tags={"Bills"},
|
||||
* 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/BillUpdateRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="수정 성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="수정되었습니다."),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Bill")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="어음을 찾을 수 없음"),
|
||||
* @OA\Response(response=422, description="유효성 검사 실패")
|
||||
* )
|
||||
*/
|
||||
public function update() {}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/bills/{id}",
|
||||
* operationId="deleteBill",
|
||||
* tags={"Bills"},
|
||||
* 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(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="삭제되었습니다."),
|
||||
* @OA\Property(property="data", type="null")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="어음을 찾을 수 없음")
|
||||
* )
|
||||
*/
|
||||
public function destroy() {}
|
||||
|
||||
/**
|
||||
* @OA\Patch(
|
||||
* path="/api/v1/bills/{id}/status",
|
||||
* operationId="updateBillStatus",
|
||||
* tags={"Bills"},
|
||||
* 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(
|
||||
* required={"status"},
|
||||
* @OA\Property(property="status", type="string", example="paymentComplete", description="변경할 상태")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="상태 변경 성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="수정되었습니다."),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Bill")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="어음을 찾을 수 없음"),
|
||||
* @OA\Response(response=422, description="유효성 검사 실패")
|
||||
* )
|
||||
*/
|
||||
public function updateStatus() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/bills/summary",
|
||||
* operationId="getBillSummary",
|
||||
* tags={"Bills"},
|
||||
* summary="어음 요약 조회",
|
||||
* description="기간별 어음 요약 정보를 조회합니다.",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="bill_type", in="query", description="어음 구분", @OA\Schema(type="string", enum={"received", "issued"})),
|
||||
* @OA\Parameter(name="issue_start_date", in="query", description="발행일 시작", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="issue_end_date", in="query", description="발행일 종료", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="maturity_start_date", in="query", description="만기일 시작", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="maturity_end_date", in="query", description="만기일 종료", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="데이터를 조회했습니다."),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/BillSummary")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function summary() {}
|
||||
}
|
||||
Reference in New Issue
Block a user