Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.concrete.buildup.domain.payroll.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* 4대보험 가입 여부 DTO
*
* <p>근로자의 4대보험 가입 여부 정보를 담는 DTO입니다.</p>
* <p>산재보험은 근로자 부담이 없으므로 포함하지 않습니다.</p>
*
* @author Build-Up Team
* @since 1.0
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "4대보험 가입 여부")
public class InsuranceEligibility {

@Schema(description = "고용보험 가입 여부", example = "true", required = true)
private Boolean employmentInsurance;

@Schema(description = "건강보험 가입 여부", example = "true", required = true)
private Boolean healthInsurance;

@Schema(description = "국민연금 가입 여부", example = "false", required = true)
private Boolean nationalPension;

// 산재보험은 근로자 부담이 없으므로 제외
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.concrete.buildup.domain.payroll.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
* 4대보험 요율 DTO
*
* <p>4대보험의 근로자 부담 요율 정보를 담는 DTO입니다.</p>
* <p>2025년 기준 요율: 국민연금 4.5%, 건강보험 3.545%, 장기요양 12.81%, 고용보험 0.9%</p>
*
* @author Build-Up Team
* @since 1.0
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "4대보험 요율 정보")
public class InsuranceRates {

@Schema(description = "국민연금 요율 (2025년 기준: 0.045)", example = "0.045", required = true)
private BigDecimal nationalPension;

@Schema(description = "건강보험 요율 (2025년 기준: 0.03545)", example = "0.03545", required = true)
private BigDecimal healthInsurance;

@Schema(description = "장기요양보험 요율 (건강보험료의 비율, 2025년 기준: 0.1281)", example = "0.1281", required = true)
private BigDecimal longTermCare;

@Schema(description = "고용보험 요율 (2025년 기준: 0.009)", example = "0.009", required = true)
private BigDecimal employmentInsurance;

// 산재보험은 사업주 전액 부담이므로 제외

/**
* 2025년 기준 기본 요율로 초기화된 InsuranceRates 생성
*
* @return 2025년 기준 요율
*/
public static InsuranceRates defaultRates2025() {
return InsuranceRates.builder()
.nationalPension(new BigDecimal("0.045"))
.healthInsurance(new BigDecimal("0.03545"))
.longTermCare(new BigDecimal("0.1281"))
.employmentInsurance(new BigDecimal("0.009"))
.build();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.concrete.buildup.domain.payroll.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* 가산수당 발생/지급 조건 DTO
*
* <p>연장/야간/휴일 가산수당의 지급 조건을 판단하기 위한 정보를 담는 DTO입니다.</p>
* <p>5인 이상 사업장은 가산수당 지급 의무, 5인 미만은 선택적 지급 가능합니다.</p>
*
* @author Build-Up Team
* @since 1.0
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "가산수당 발생/지급 조건")
public class OvertimeConditions {

// ========== 사업장 규모 ==========

@Schema(description = "상시근로자 수 (5인 이상이면 가산수당 의무 지급)", example = "10", required = true)
private Integer totalEmployees;
Comment on lines +27 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

totalEmployees가 null일 경우 NPE 발생 가능

PayrollCalculator.canPayOvertimeAllowance()에서 conditions.getTotalEmployees() >= 5 비교 시 totalEmployees가 null이면 NullPointerException이 발생합니다.

int 기본 타입 사용 또는 null-safe 처리를 권장합니다:

-    @Schema(description = "상시근로자 수 (5인 이상이면 가산수당 의무 지급)", example = "10", required = true)
-    private Integer totalEmployees;
+    @Schema(description = "상시근로자 수 (5인 이상이면 가산수당 의무 지급)", example = "10", required = true)
+    @Builder.Default
+    private Integer totalEmployees = 0;

또는 PayrollCalculator에서 null 체크를 추가해야 합니다.

🤖 Prompt for AI Agents
In src/main/java/com/concrete/buildup/domain/payroll/dto/OvertimeConditions.java
around lines 27-28, totalEmployees is declared as Integer which can be null and
causes an NPE when compared in PayrollCalculator; change totalEmployees to the
primitive int (or initialize it to a non-null default and add a validation
annotation like @NotNull/@Min(0)) so callers never receive null, or
alternatively update PayrollCalculator.canPayOvertimeAllowance() to null-check
conditions.getTotalEmployees() (treat null as 0) before comparing to 5; pick one
approach and apply consistently with input validation.


// ========== 5인 미만 사업장의 자율 지급 정책 ==========

@Schema(description = "연장수당 자율 지급 여부 (5인 미만일 때)", example = "true")
private Boolean autoPayOvertime;

@Schema(description = "야간수당 자율 지급 여부 (5인 미만일 때)", example = "true")
private Boolean autoPayNight;

@Schema(description = "휴일수당 자율 지급 여부 (5인 미만일 때)", example = "true")
private Boolean autoPayHoliday;

// ========== 연장근로 발생 조건 ==========

@Schema(description = "일 8시간 초과 여부", example = "true")
private Boolean exceedsDailyLimit;

@Schema(description = "주 40시간 초과 여부", example = "false")
private Boolean exceedsWeeklyLimit;

// ========== 야간/휴일 근로 존재 여부 ==========

@Schema(description = "야간근로 존재 여부 (22:00~06:00 구간 근로)", example = "true")
private Boolean hasNightWork;

@Schema(description = "휴일근로 존재 여부 (주휴일/법정공휴일 근로)", example = "false")
private Boolean hasHolidayWork;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.concrete.buildup.domain.payroll.dto;

import com.concrete.buildup.domain.contract.enums.EmpType;
import com.concrete.buildup.domain.contract.enums.PayPeriod;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
* 급여 계산 입력 DTO
*
* <p>급여 계산에 필요한 모든 입력 정보를 담는 DTO입니다.</p>
* <p>v1.1 급여 계산 로직의 입력 파라미터로 사용됩니다.</p>
*
* @author Build-Up Team
* @since 1.0
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "급여 계산 입력 정보")
public class PayrollCalculationInput {

// ========== 기본 정보 ==========

@Schema(description = "고용 형태 (DAILY: 일용직, PERMANENT: 상용직)", example = "DAILY", required = true)
private EmpType empType;

@Schema(description = "지급 주기 (DAILY: 일, WEEKLY: 주, MONTHLY: 월)", example = "DAILY", required = true)
private PayPeriod payPeriod;

@Schema(description = "시급", example = "10000", required = true)
private BigDecimal hourlyRate;

@Schema(description = "1일 소정근로시간", example = "8", required = true)
private BigDecimal dailyWorkHours;

@Schema(description = "근로일수", example = "20", required = true)
private Integer workDays;

// ========== 가산 시간 (발생 시간) ==========

@Schema(description = "연장근로시간", example = "10")
private BigDecimal overtimeHours;

@Schema(description = "야간근로시간", example = "5")
private BigDecimal nightHours;

@Schema(description = "휴일근로시간", example = "8")
private BigDecimal holidayHours;

// ========== 주휴수당 ==========

@Schema(description = "주휴수당 대상 여부", example = "true")
private Boolean weeklyHolidayEligible;

// ========== 4대보험 가입 여부 ==========

@Schema(description = "보험 가입 여부", required = true)
private InsuranceEligibility insurance;

// ========== 세금 관련 (상용직) ==========

@Schema(description = "부양가족 수 (상용직 세금 계산용)", example = "2")
private Integer dependents;

// ========== 요율 ==========

@Schema(description = "보험 요율 정보", required = true)
private InsuranceRates rates;

// ========== 가산수당 조건 ==========

@Schema(description = "가산수당 발생/지급 조건", required = true)
private OvertimeConditions overtimeConditions;

// ========== 일용직 세금 계산용 (추가) ==========

@Schema(description = "당월 근로일수 누적 (일용직 4대보험 적용 판단용)", example = "8")
private Integer monthlyWorkDaysAccumulated;

@Schema(description = "월 환산 소득 (일용직 4대보험 적용 판단용)", example = "2200000")
private BigDecimal monthlyEstimatedIncome;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package com.concrete.buildup.domain.payroll.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.util.Map;

/**
* 급여 계산 결과 DTO
*
* <p>급여 계산의 최종 결과를 담는 DTO입니다.</p>
* <p>지급 항목, 공제 항목, 실지급액을 모두 포함합니다.</p>
*
* @author Build-Up Team
* @since 1.0
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "급여 계산 결과")
public class PayrollCalculationResult {

// ========== 지급 항목 ==========

@Schema(description = "기본급 (시급 × 총 통상시간)", example = "1600000")
private BigDecimal basePay;

@Schema(description = "연장수당 (시급 × 연장시간 × 1.5)", example = "150000")
private BigDecimal overtimePay;

@Schema(description = "야간수당 (시급 × 야간시간 × 0.5)", example = "50000")
private BigDecimal nightPay;

@Schema(description = "휴일수당 (시급 × 휴일시간 × 1.5)", example = "120000")
private BigDecimal holidayPay;

@Schema(description = "주휴수당 (일용직 조건 충족 시)", example = "80000")
private BigDecimal weeklyHolidayPay;

@Schema(description = "총 지급액 (모든 지급 항목의 합)", example = "2000000")
private BigDecimal totalPay;

// ========== 공제 항목 ==========

@Schema(description = "소득세 (일용직: 일별 계산, 상용직: 간이세액표)", example = "33000")
private BigDecimal incomeTax;

@Schema(description = "지방소득세 (주민세, 소득세의 10%)", example = "3300")
private BigDecimal residentTax;

@Schema(description = "고용보험 (과세대상금액 × 0.9%)", example = "18000")
private BigDecimal employmentInsurance;

@Schema(description = "건강보험 (과세대상금액 × 3.545%)", example = "70900")
private BigDecimal healthInsurance;

@Schema(description = "장기요양보험 (건강보험료 × 12.81%)", example = "9084")
private BigDecimal longTermCareInsurance;

@Schema(description = "국민연금 (과세대상금액 × 4.5%)", example = "90000")
private BigDecimal nationalPension;

@Schema(description = "총 공제액 (모든 공제 항목의 합)", example = "224284")
private BigDecimal totalDeduction;

// ========== 최종 금액 ==========

@Schema(description = "실지급액 (총 지급액 - 총 공제액)", example = "1775716")
private BigDecimal netPay;

// ========== 계산 상세 (디버깅/로그용) ==========

@Schema(description = "계산 상세 정보 (디버깅용)", example = "{\"totalWorkHours\": 160, \"taxableIncome\": 2000000}")
private Map<String, Object> calculationDetails;

/**
* 총 지급액 계산
*
* @return 모든 지급 항목의 합
*/
public BigDecimal calculateTotalPay() {
BigDecimal total = BigDecimal.ZERO;
if (basePay != null) total = total.add(basePay);
if (overtimePay != null) total = total.add(overtimePay);
if (nightPay != null) total = total.add(nightPay);
if (holidayPay != null) total = total.add(holidayPay);
if (weeklyHolidayPay != null) total = total.add(weeklyHolidayPay);
return total;
}

/**
* 총 공제액 계산
*
* @return 모든 공제 항목의 합
*/
public BigDecimal calculateTotalDeduction() {
BigDecimal total = BigDecimal.ZERO;
if (incomeTax != null) total = total.add(incomeTax);
if (residentTax != null) total = total.add(residentTax);
if (employmentInsurance != null) total = total.add(employmentInsurance);
if (healthInsurance != null) total = total.add(healthInsurance);
if (longTermCareInsurance != null) total = total.add(longTermCareInsurance);
if (nationalPension != null) total = total.add(nationalPension);
return total;
}

/**
* 실지급액 계산
*
* @return 총 지급액 - 총 공제액
*/
public BigDecimal calculateNetPay() {
BigDecimal total = totalPay != null ? totalPay : calculateTotalPay();
BigDecimal deduction = totalDeduction != null ? totalDeduction : calculateTotalDeduction();
return total.subtract(deduction);
}
}
Loading
Loading