feat: Phase 5 API 개발 완료 (사용자 초대, 알림설정, 계정관리, 거래명세서)

5.1 사용자 초대 기능:
- UserInvitation 마이그레이션, 모델, 서비스, 컨트롤러, Swagger
- 초대 발송/수락/취소/재발송 API

5.2 알림설정 확장:
- NotificationSetting 마이그레이션, 모델, 서비스, 컨트롤러, Swagger
- 채널별/유형별 알림 설정 관리

5.3 계정정보 수정 API:
- 회원탈퇴, 사용중지, 약관동의 관리
- AccountService, AccountController, Swagger

5.4 매출 거래명세서 API:
- 거래명세서 조회/발행/이메일발송
- SaleService 확장, Swagger 문서화
This commit is contained in:
2025-12-19 14:52:53 +09:00
parent c7b25710a0
commit 3020026abf
31 changed files with 2735 additions and 8 deletions

View File

@@ -275,4 +275,155 @@ private function generateSaleNumber(int $tenantId, string $saleDate): string
return $prefix.str_pad($newSeq, 4, '0', STR_PAD_LEFT);
}
/**
* 거래명세서 조회
*/
public function getStatement(int $id): array
{
$tenantId = $this->tenantId();
$sale = Sale::query()
->where('tenant_id', $tenantId)
->with(['client', 'deposit', 'creator:id,name'])
->findOrFail($id);
// 거래명세서 데이터 구성
return [
'statement_number' => $this->generateStatementNumber($sale),
'issued_at' => $sale->statement_issued_at,
'sale' => $sale,
'seller' => $this->getSellerInfo($tenantId),
'buyer' => $this->getBuyerInfo($sale->client),
'items' => $this->getSaleItems($sale),
'summary' => [
'supply_amount' => $sale->supply_amount,
'tax_amount' => $sale->tax_amount,
'total_amount' => $sale->total_amount,
],
];
}
/**
* 거래명세서 발행
*/
public function issueStatement(int $id): array
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
return DB::transaction(function () use ($id, $tenantId, $userId) {
$sale = Sale::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
// 확정된 매출만 거래명세서 발행 가능
if ($sale->status !== 'confirmed') {
throw new \Exception(__('error.sale.statement_requires_confirmed'));
}
// 발행 시간 기록
$sale->statement_issued_at = now();
$sale->statement_issued_by = $userId;
$sale->save();
return [
'statement_number' => $this->generateStatementNumber($sale),
'issued_at' => $sale->statement_issued_at->toIso8601String(),
];
});
}
/**
* 거래명세서 이메일 발송
*/
public function sendStatement(int $id, array $data): array
{
$tenantId = $this->tenantId();
$sale = Sale::query()
->where('tenant_id', $tenantId)
->with(['client'])
->findOrFail($id);
// 수신자 이메일 확인
$recipientEmail = $data['email'] ?? $sale->client?->email;
if (empty($recipientEmail)) {
throw new \Exception(__('error.sale.recipient_email_required'));
}
// TODO: 실제 이메일 발송 로직 구현
// Mail::to($recipientEmail)->send(new SaleStatementMail($sale, $data['message'] ?? null));
return [
'sent_to' => $recipientEmail,
'sent_at' => now()->toIso8601String(),
'statement_number' => $this->generateStatementNumber($sale),
];
}
/**
* 거래명세서 번호 생성
*/
private function generateStatementNumber(Sale $sale): string
{
return 'ST'.$sale->sale_number;
}
/**
* 판매자 정보 조회
*/
private function getSellerInfo(int $tenantId): array
{
$tenant = \App\Models\Tenants\Tenant::find($tenantId);
return [
'name' => $tenant->name ?? '',
'business_number' => $tenant->business_number ?? '',
'representative' => $tenant->representative ?? '',
'address' => $tenant->address ?? '',
'tel' => $tenant->tel ?? '',
'fax' => $tenant->fax ?? '',
'email' => $tenant->email ?? '',
];
}
/**
* 구매자 정보 조회
*/
private function getBuyerInfo($client): array
{
if (! $client) {
return [];
}
return [
'name' => $client->name ?? '',
'business_number' => $client->business_number ?? '',
'representative' => $client->representative ?? '',
'address' => $client->address ?? '',
'tel' => $client->tel ?? '',
'fax' => $client->fax ?? '',
'email' => $client->email ?? '',
];
}
/**
* 매출 품목 조회 (향후 확장)
*/
private function getSaleItems(Sale $sale): array
{
// TODO: 매출 품목 테이블이 있다면 조회
// 현재는 기본 매출 정보만 반환
return [
[
'description' => $sale->description ?? '매출',
'quantity' => 1,
'unit_price' => $sale->supply_amount,
'supply_amount' => $sale->supply_amount,
'tax_amount' => $sale->tax_amount,
'total_amount' => $sale->total_amount,
],
];
}
}