ERP-node/backend-node/src/controllers/taxInvoiceController.ts

366 lines
11 KiB
TypeScript

/**
* 세금계산서 컨트롤러
* 세금계산서 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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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 || "통계 조회 중 오류가 발생했습니다.",
});
}
}
}