tenantId(); $perPage = $params['per_page'] ?? 20; $query = TaxInvoice::query() ->where('tenant_id', $tenantId) ->orderBy('issue_date', 'desc') ->orderBy('id', 'desc'); // 방향 (매출/매입) if (! empty($params['direction'])) { $query->where('direction', $params['direction']); } // 상태 if (! empty($params['status'])) { $query->where('status', $params['status']); } // 세금계산서 유형 if (! empty($params['invoice_type'])) { $query->where('invoice_type', $params['invoice_type']); } // 발행 유형 if (! empty($params['issue_type'])) { $query->where('issue_type', $params['issue_type']); } // 기간 검색 if (! empty($params['issue_date_from'])) { $query->whereDate('issue_date', '>=', $params['issue_date_from']); } if (! empty($params['issue_date_to'])) { $query->whereDate('issue_date', '<=', $params['issue_date_to']); } // 거래처 검색 (공급자 또는 공급받는자) if (! empty($params['corp_num'])) { $query->where(function ($q) use ($params) { $q->where('supplier_corp_num', $params['corp_num']) ->orWhere('buyer_corp_num', $params['corp_num']); }); } // 거래처명 검색 if (! empty($params['corp_name'])) { $query->where(function ($q) use ($params) { $q->where('supplier_corp_name', 'like', '%'.$params['corp_name'].'%') ->orWhere('buyer_corp_name', 'like', '%'.$params['corp_name'].'%'); }); } // 국세청 승인번호 검색 if (! empty($params['nts_confirm_num'])) { $query->where('nts_confirm_num', 'like', '%'.$params['nts_confirm_num'].'%'); } return $query->paginate($perPage); } /** * 세금계산서 상세 조회 */ public function show(int $id): TaxInvoice { $tenantId = $this->tenantId(); return TaxInvoice::query() ->where('tenant_id', $tenantId) ->with(['creator', 'updater']) ->findOrFail($id); } // ========================================================================= // 세금계산서 생성/수정 // ========================================================================= /** * 세금계산서 생성 */ public function create(array $data): TaxInvoice { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); // 합계금액 계산 $data['total_amount'] = ($data['supply_amount'] ?? 0) + ($data['tax_amount'] ?? 0); $taxInvoice = TaxInvoice::create(array_merge($data, [ 'tenant_id' => $tenantId, 'status' => TaxInvoice::STATUS_DRAFT, 'created_by' => $userId, 'updated_by' => $userId, ])); return $taxInvoice->fresh(); } /** * 세금계산서 수정 */ public function update(int $id, array $data): TaxInvoice { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $taxInvoice = TaxInvoice::query() ->where('tenant_id', $tenantId) ->findOrFail($id); if (! $taxInvoice->canEdit()) { throw new \Symfony\Component\HttpKernel\Exception\BadRequestHttpException(__('error.tax_invoice.cannot_edit')); } // 합계금액 계산 if (isset($data['supply_amount']) || isset($data['tax_amount'])) { $supplyAmount = $data['supply_amount'] ?? $taxInvoice->supply_amount; $taxAmount = $data['tax_amount'] ?? $taxInvoice->tax_amount; $data['total_amount'] = $supplyAmount + $taxAmount; } $taxInvoice->fill(array_merge($data, ['updated_by' => $userId])); $taxInvoice->save(); return $taxInvoice->fresh(); } /** * 세금계산서 삭제 */ public function delete(int $id): bool { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $taxInvoice = TaxInvoice::query() ->where('tenant_id', $tenantId) ->findOrFail($id); if (! $taxInvoice->canEdit()) { throw new \Symfony\Component\HttpKernel\Exception\BadRequestHttpException(__('error.tax_invoice.cannot_delete')); } $taxInvoice->deleted_by = $userId; $taxInvoice->save(); return $taxInvoice->delete(); } // ========================================================================= // 발행/취소 // ========================================================================= /** * 세금계산서 발행 */ public function issue(int $id): TaxInvoice { $tenantId = $this->tenantId(); $taxInvoice = TaxInvoice::query() ->where('tenant_id', $tenantId) ->findOrFail($id); if (! $taxInvoice->canEdit()) { throw new \Symfony\Component\HttpKernel\Exception\BadRequestHttpException(__('error.tax_invoice.already_issued')); } return $this->barobillService->issueTaxInvoice($taxInvoice); } /** * 세금계산서 일괄 발행 * * @param array $ids 발행할 세금계산서 ID 배열 * @return array{issued: int, failed: int, errors: array} */ public function bulkIssue(array $ids): array { $tenantId = $this->tenantId(); $results = [ 'issued' => 0, 'failed' => 0, 'errors' => [], ]; $taxInvoices = TaxInvoice::query() ->where('tenant_id', $tenantId) ->whereIn('id', $ids) ->get(); foreach ($taxInvoices as $taxInvoice) { try { if (! $taxInvoice->canEdit()) { $results['errors'][$taxInvoice->id] = __('error.tax_invoice.already_issued'); $results['failed']++; continue; } $this->barobillService->issueTaxInvoice($taxInvoice); $results['issued']++; } catch (\Throwable $e) { $results['errors'][$taxInvoice->id] = $e->getMessage(); $results['failed']++; } } // 요청된 ID 중 찾지 못한 것들도 실패 처리 $foundIds = $taxInvoices->pluck('id')->toArray(); $notFoundIds = array_diff($ids, $foundIds); foreach ($notFoundIds as $notFoundId) { $results['errors'][$notFoundId] = __('error.tax_invoice.not_found'); $results['failed']++; } return $results; } /** * 세금계산서 취소 */ public function cancel(int $id, string $reason): TaxInvoice { $tenantId = $this->tenantId(); $taxInvoice = TaxInvoice::query() ->where('tenant_id', $tenantId) ->findOrFail($id); return $this->barobillService->cancelTaxInvoice($taxInvoice, $reason); } /** * 국세청 전송 상태 조회 */ public function checkStatus(int $id): TaxInvoice { $tenantId = $this->tenantId(); $taxInvoice = TaxInvoice::query() ->where('tenant_id', $tenantId) ->findOrFail($id); return $this->barobillService->checkNtsSendStatus($taxInvoice); } // ========================================================================= // 통계 // ========================================================================= /** * 세금계산서 요약 통계 */ public function summary(array $params): array { $tenantId = $this->tenantId(); $query = TaxInvoice::query() ->where('tenant_id', $tenantId); // 기간 필터 if (! empty($params['issue_date_from'])) { $query->whereDate('issue_date', '>=', $params['issue_date_from']); } if (! empty($params['issue_date_to'])) { $query->whereDate('issue_date', '<=', $params['issue_date_to']); } // 방향별 통계 $summary = $query->clone() ->select([ 'direction', DB::raw('COUNT(*) as count'), DB::raw('SUM(supply_amount) as supply_amount'), DB::raw('SUM(tax_amount) as tax_amount'), DB::raw('SUM(total_amount) as total_amount'), ]) ->groupBy('direction') ->get() ->keyBy('direction') ->toArray(); // 상태별 통계 $byStatus = $query->clone() ->select([ 'status', DB::raw('COUNT(*) as count'), ]) ->groupBy('status') ->get() ->keyBy('status') ->toArray(); return [ 'by_direction' => [ 'sales' => $summary[TaxInvoice::DIRECTION_SALES] ?? [ 'count' => 0, 'supply_amount' => 0, 'tax_amount' => 0, 'total_amount' => 0, ], 'purchases' => $summary[TaxInvoice::DIRECTION_PURCHASES] ?? [ 'count' => 0, 'supply_amount' => 0, 'tax_amount' => 0, 'total_amount' => 0, ], ], 'by_status' => [ TaxInvoice::STATUS_DRAFT => $byStatus[TaxInvoice::STATUS_DRAFT]['count'] ?? 0, TaxInvoice::STATUS_ISSUED => $byStatus[TaxInvoice::STATUS_ISSUED]['count'] ?? 0, TaxInvoice::STATUS_SENT => $byStatus[TaxInvoice::STATUS_SENT]['count'] ?? 0, TaxInvoice::STATUS_CANCELLED => $byStatus[TaxInvoice::STATUS_CANCELLED]['count'] ?? 0, TaxInvoice::STATUS_FAILED => $byStatus[TaxInvoice::STATUS_FAILED]['count'] ?? 0, ], ]; } }