/** * 세금계산서 컨트롤러 * 세금계산서 API 엔드포인트 처리 */ import { Request, Response } from "express"; import { TaxInvoiceService } from "../services/taxInvoiceService"; import { logger } from "../utils/logger"; interface AuthenticatedRequest extends Request { user?: { userId: string; companyCode: string; }; } export class TaxInvoiceController { /** * 세금계산서 목록 조회 * GET /api/tax-invoice */ static async getList(req: AuthenticatedRequest, res: Response): Promise { try { const companyCode = req.user?.companyCode; if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다." }); return; } const { page = "1", pageSize = "20", invoice_type, invoice_status, start_date, end_date, search, buyer_name, cost_type, } = req.query; const result = await TaxInvoiceService.getList(companyCode, { page: parseInt(page as string, 10), pageSize: parseInt(pageSize as string, 10), invoice_type: invoice_type as "sales" | "purchase" | undefined, invoice_status: invoice_status as string | undefined, start_date: start_date as string | undefined, end_date: end_date as string | undefined, search: search as string | undefined, buyer_name: buyer_name as string | undefined, cost_type: cost_type as any, }); res.json({ success: true, data: result.data, pagination: { page: result.page, pageSize: result.pageSize, total: result.total, totalPages: Math.ceil(result.total / result.pageSize), }, }); } catch (error: any) { logger.error("세금계산서 목록 조회 실패:", error); res.status(500).json({ success: false, message: error.message || "세금계산서 목록 조회 중 오류가 발생했습니다.", }); } } /** * 세금계산서 상세 조회 * GET /api/tax-invoice/:id */ static async getById(req: AuthenticatedRequest, res: Response): Promise { try { const companyCode = req.user?.companyCode; if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다." }); return; } const { id } = req.params; const result = await TaxInvoiceService.getById(id, companyCode); if (!result) { res.status(404).json({ success: false, message: "세금계산서를 찾을 수 없습니다." }); return; } res.json({ success: true, data: result, }); } catch (error: any) { logger.error("세금계산서 상세 조회 실패:", error); res.status(500).json({ success: false, message: error.message || "세금계산서 조회 중 오류가 발생했습니다.", }); } } /** * 세금계산서 생성 * POST /api/tax-invoice */ static async create(req: AuthenticatedRequest, res: Response): Promise { try { const companyCode = req.user?.companyCode; const userId = req.user?.userId; if (!companyCode || !userId) { res.status(401).json({ success: false, message: "인증 정보가 없습니다." }); return; } const data = req.body; // 필수 필드 검증 if (!data.invoice_type) { res.status(400).json({ success: false, message: "세금계산서 유형은 필수입니다." }); return; } if (!data.invoice_date) { res.status(400).json({ success: false, message: "작성일자는 필수입니다." }); return; } if (data.supply_amount === undefined || data.supply_amount === null) { res.status(400).json({ success: false, message: "공급가액은 필수입니다." }); return; } const result = await TaxInvoiceService.create(data, companyCode, userId); res.status(201).json({ success: true, data: result, message: "세금계산서가 생성되었습니다.", }); } catch (error: any) { logger.error("세금계산서 생성 실패:", error); res.status(500).json({ success: false, message: error.message || "세금계산서 생성 중 오류가 발생했습니다.", }); } } /** * 세금계산서 수정 * PUT /api/tax-invoice/:id */ static async update(req: AuthenticatedRequest, res: Response): Promise { try { const companyCode = req.user?.companyCode; const userId = req.user?.userId; if (!companyCode || !userId) { res.status(401).json({ success: false, message: "인증 정보가 없습니다." }); return; } const { id } = req.params; const data = req.body; const result = await TaxInvoiceService.update(id, data, companyCode, userId); if (!result) { res.status(404).json({ success: false, message: "세금계산서를 찾을 수 없습니다." }); return; } res.json({ success: true, data: result, message: "세금계산서가 수정되었습니다.", }); } catch (error: any) { logger.error("세금계산서 수정 실패:", error); res.status(500).json({ success: false, message: error.message || "세금계산서 수정 중 오류가 발생했습니다.", }); } } /** * 세금계산서 삭제 * DELETE /api/tax-invoice/:id */ static async delete(req: AuthenticatedRequest, res: Response): Promise { try { const companyCode = req.user?.companyCode; const userId = req.user?.userId; if (!companyCode || !userId) { res.status(401).json({ success: false, message: "인증 정보가 없습니다." }); return; } const { id } = req.params; const result = await TaxInvoiceService.delete(id, companyCode, userId); if (!result) { res.status(404).json({ success: false, message: "세금계산서를 찾을 수 없습니다." }); return; } res.json({ success: true, message: "세금계산서가 삭제되었습니다.", }); } catch (error: any) { logger.error("세금계산서 삭제 실패:", error); res.status(500).json({ success: false, message: error.message || "세금계산서 삭제 중 오류가 발생했습니다.", }); } } /** * 세금계산서 발행 * POST /api/tax-invoice/:id/issue */ static async issue(req: AuthenticatedRequest, res: Response): Promise { try { const companyCode = req.user?.companyCode; const userId = req.user?.userId; if (!companyCode || !userId) { res.status(401).json({ success: false, message: "인증 정보가 없습니다." }); return; } const { id } = req.params; const result = await TaxInvoiceService.issue(id, companyCode, userId); if (!result) { res.status(404).json({ success: false, message: "세금계산서를 찾을 수 없거나 이미 발행된 상태입니다.", }); return; } res.json({ success: true, data: result, message: "세금계산서가 발행되었습니다.", }); } catch (error: any) { logger.error("세금계산서 발행 실패:", error); res.status(500).json({ success: false, message: error.message || "세금계산서 발행 중 오류가 발생했습니다.", }); } } /** * 세금계산서 취소 * POST /api/tax-invoice/:id/cancel */ static async cancel(req: AuthenticatedRequest, res: Response): Promise { try { const companyCode = req.user?.companyCode; const userId = req.user?.userId; if (!companyCode || !userId) { res.status(401).json({ success: false, message: "인증 정보가 없습니다." }); return; } const { id } = req.params; const { reason } = req.body; const result = await TaxInvoiceService.cancel(id, companyCode, userId, reason); if (!result) { res.status(404).json({ success: false, message: "세금계산서를 찾을 수 없거나 취소할 수 없는 상태입니다.", }); return; } res.json({ success: true, data: result, message: "세금계산서가 취소되었습니다.", }); } catch (error: any) { logger.error("세금계산서 취소 실패:", error); res.status(500).json({ success: false, message: error.message || "세금계산서 취소 중 오류가 발생했습니다.", }); } } /** * 월별 통계 조회 * GET /api/tax-invoice/stats/monthly */ static async getMonthlyStats(req: AuthenticatedRequest, res: Response): Promise { try { const companyCode = req.user?.companyCode; if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다." }); return; } const { year, month } = req.query; const now = new Date(); const targetYear = year ? parseInt(year as string, 10) : now.getFullYear(); const targetMonth = month ? parseInt(month as string, 10) : now.getMonth() + 1; const result = await TaxInvoiceService.getMonthlyStats(companyCode, targetYear, targetMonth); res.json({ success: true, data: result, period: { year: targetYear, month: targetMonth }, }); } catch (error: any) { logger.error("월별 통계 조회 실패:", error); res.status(500).json({ success: false, message: error.message || "통계 조회 중 오류가 발생했습니다.", }); } } /** * 비용 유형별 통계 조회 * GET /api/tax-invoice/stats/cost-type */ static async getCostTypeStats(req: AuthenticatedRequest, res: Response): Promise { try { const companyCode = req.user?.companyCode; if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다." }); return; } const { year, month } = req.query; const targetYear = year ? parseInt(year as string, 10) : undefined; const targetMonth = month ? parseInt(month as string, 10) : undefined; const result = await TaxInvoiceService.getCostTypeStats(companyCode, targetYear, targetMonth); res.json({ success: true, data: result, period: { year: targetYear, month: targetMonth }, }); } catch (error: any) { logger.error("비용 유형별 통계 조회 실패:", error); res.status(500).json({ success: false, message: error.message || "통계 조회 중 오류가 발생했습니다.", }); } } }