diff --git a/src/settlements/dtos/settlement.history.dto.ts b/src/settlements/dtos/settlement.history.dto.ts index 3041cf0..a9eb880 100644 --- a/src/settlements/dtos/settlement.history.dto.ts +++ b/src/settlements/dtos/settlement.history.dto.ts @@ -10,14 +10,16 @@ export interface MonthlySalesItemDto { sale_price: number; settled_amount: number; fee: number; - status: 'Pending' | 'Succeed' | 'Failed'; + status: 'Pending' | 'Succeed' | 'Failed' | 'Refunded'; } export interface MonthlySalesSummaryDto { count: number; total_sales: number; - total_settled: number; + total_settled: number; // net — Succeed만, 환불 제외 total_fee: number; + refunded_count: number; + refunded_amount: number; } export interface MonthlySalesResponseDto { @@ -32,11 +34,13 @@ export interface MonthlySalesResponseDto { export interface YearlySettlementItemDto { year: number; count: number; - total_sales: number; - total_settled: number; + total_sales: number; // gross + total_settled: number; // net — 환불 제외, Succeed 합계 total_fee: number; succeeded_amount: number; pending_amount: number; + refunded_amount: number; + refunded_count: number; } export interface YearlySettlementResponseDto { diff --git a/src/settlements/repositories/settlement.history.repository.ts b/src/settlements/repositories/settlement.history.repository.ts index 6b06e02..d9d4a36 100644 --- a/src/settlements/repositories/settlement.history.repository.ts +++ b/src/settlements/repositories/settlement.history.repository.ts @@ -55,31 +55,35 @@ export const SettlementHistoryRepository = { Array<{ year: number; count: number; - total_sales: number; - total_settled: number; - total_fee: number; - succeeded_amount: number; - pending_amount: number; + total_sales: number; // gross — 환불 포함 (비교용) + total_settled: number; // net — Succeed 합계 (환불 제외) + total_fee: number; // gross fee (환불 포함) + succeeded_amount: number; // Succeed만 + pending_amount: number; // Pending만 + refunded_amount: number; // Refunded만 + refunded_count: number; }> > { const rows = await prisma.$queryRaw< Array<{ year: number; count: bigint; - total_settled: bigint | null; total_fee: bigint | null; succeeded_amount: bigint | null; pending_amount: bigint | null; + refunded_amount: bigint | null; + refunded_count: bigint; total_sales: bigint | null; }> >` SELECT YEAR(s.created_at) AS year, COUNT(*) AS count, - SUM(s.amount) AS total_settled, SUM(s.fee) AS total_fee, SUM(CASE WHEN s.status = ${Status.Succeed} THEN s.amount ELSE 0 END) AS succeeded_amount, SUM(CASE WHEN s.status = ${Status.Pending} THEN s.amount ELSE 0 END) AS pending_amount, + SUM(CASE WHEN s.status = ${Status.Refunded} THEN s.amount ELSE 0 END) AS refunded_amount, + SUM(CASE WHEN s.status = ${Status.Refunded} THEN 1 ELSE 0 END) AS refunded_count, SUM(p.amount) AS total_sales FROM Settlement s JOIN Payment pm ON pm.payment_id = s.payment_id @@ -89,14 +93,19 @@ export const SettlementHistoryRepository = { ORDER BY year DESC `; - return rows.map((r) => ({ - year: Number(r.year), - count: Number(r.count), - total_sales: Number(r.total_sales ?? 0), - total_settled: Number(r.total_settled ?? 0), - total_fee: Number(r.total_fee ?? 0), - succeeded_amount: Number(r.succeeded_amount ?? 0), - pending_amount: Number(r.pending_amount ?? 0), - })); + return rows.map((r) => { + const succeeded = Number(r.succeeded_amount ?? 0); + return { + year: Number(r.year), + count: Number(r.count), + total_sales: Number(r.total_sales ?? 0), + total_settled: succeeded, // net: 환불 제외, 정산 완료만 + total_fee: Number(r.total_fee ?? 0), + succeeded_amount: succeeded, + pending_amount: Number(r.pending_amount ?? 0), + refunded_amount: Number(r.refunded_amount ?? 0), + refunded_count: Number(r.refunded_count ?? 0), + }; + }); }, }; diff --git a/src/settlements/routes/settlement.route.ts b/src/settlements/routes/settlement.route.ts index cb0c751..a2808bc 100644 --- a/src/settlements/routes/settlement.route.ts +++ b/src/settlements/routes/settlement.route.ts @@ -470,9 +470,11 @@ router.post("/register/business", authenticateJwt, registerBusiness); * type: object * properties: * count: { type: integer, example: 3 } - * total_sales: { type: integer, example: 30000 } - * total_settled: { type: integer, example: 27000 } + * total_sales: { type: integer, description: 원래 판매가 합계 (환불 포함), example: 30000 } + * total_settled: { type: integer, description: net 정산 금액 (환불 제외), example: 18000 } * total_fee: { type: integer, example: 3000 } + * refunded_count: { type: integer, example: 1 } + * refunded_amount: { type: integer, example: 9000 } * items: * type: array * items: @@ -489,7 +491,7 @@ router.post("/register/business", authenticateJwt, registerBusiness); * sale_price: { type: integer } * settled_amount: { type: integer } * fee: { type: integer } - * status: { type: string, enum: [Pending, Succeed, Failed] } + * status: { type: string, enum: [Pending, Succeed, Failed, Refunded] } * statusCode: { type: integer, example: 200 } * 400: * description: 잘못된 year/month @@ -556,11 +558,13 @@ router.get("/sales/monthly", authenticateJwt, getMonthlySales); * properties: * year: { type: integer, example: 2026 } * count: { type: integer, example: 50 } - * total_sales: { type: integer, example: 500000 } - * total_settled: { type: integer, example: 450000 } + * total_sales: { type: integer, description: gross — 환불 포함, example: 500000 } + * total_settled: { type: integer, description: net — Succeed 합계 (환불 제외), example: 400000 } * total_fee: { type: integer, example: 50000 } * succeeded_amount: { type: integer, example: 400000 } * pending_amount: { type: integer, example: 50000 } + * refunded_amount: { type: integer, example: 50000 } + * refunded_count: { type: integer, example: 5 } * statusCode: { type: integer, example: 200 } * 401: * description: 로그인 필요 diff --git a/src/settlements/services/settlement.history.service.ts b/src/settlements/services/settlement.history.service.ts index 695f80f..f31b92e 100644 --- a/src/settlements/services/settlement.history.service.ts +++ b/src/settlements/services/settlement.history.service.ts @@ -19,13 +19,22 @@ export const SettlementHistoryService = { let total_sales = 0; let total_settled = 0; let total_fee = 0; + let refunded_count = 0; + let refunded_amount = 0; const items = rows.map((r) => { const sale_price = r.payment?.purchase?.amount ?? 0; total_sales += sale_price; - total_settled += r.amount; total_fee += r.fee; + // net 정산금: 환불은 제외 (회계상 판매자 net 수익) + if (r.status === 'Refunded') { + refunded_count += 1; + refunded_amount += r.amount; + } else { + total_settled += r.amount; + } + return { settlement_id: r.settlement_id, sold_at: r.created_at.toISOString(), @@ -38,7 +47,7 @@ export const SettlementHistoryService = { sale_price, settled_amount: r.amount, fee: r.fee, - status: r.status as 'Pending' | 'Succeed' | 'Failed', + status: r.status as 'Pending' | 'Succeed' | 'Failed' | 'Refunded', }; }); @@ -46,7 +55,14 @@ export const SettlementHistoryService = { message: '월별 판매 내역 조회 성공', year, month, - summary: { count: items.length, total_sales, total_settled, total_fee }, + summary: { + count: items.length, + total_sales, + total_settled, + total_fee, + refunded_count, + refunded_amount, + }, items, statusCode: 200, }; diff --git a/swagger.json b/swagger.json index a22e396..1471582 100644 --- a/swagger.json +++ b/swagger.json @@ -7756,15 +7756,25 @@ }, "total_sales": { "type": "integer", + "description": "원래 판매가 합계 (환불 포함)", "example": 30000 }, "total_settled": { "type": "integer", - "example": 27000 + "description": "net 정산 금액 (환불 제외)", + "example": 18000 }, "total_fee": { "type": "integer", "example": 3000 + }, + "refunded_count": { + "type": "integer", + "example": 1 + }, + "refunded_amount": { + "type": "integer", + "example": 9000 } } }, @@ -7815,7 +7825,8 @@ "enum": [ "Pending", "Succeed", - "Failed" + "Failed", + "Refunded" ] } } @@ -7925,11 +7936,13 @@ }, "total_sales": { "type": "integer", + "description": "gross — 환불 포함", "example": 500000 }, "total_settled": { "type": "integer", - "example": 450000 + "description": "net — Succeed 합계 (환불 제외)", + "example": 400000 }, "total_fee": { "type": "integer", @@ -7942,6 +7955,14 @@ "pending_amount": { "type": "integer", "example": 50000 + }, + "refunded_amount": { + "type": "integer", + "example": 50000 + }, + "refunded_count": { + "type": "integer", + "example": 5 } } }