diff --git a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/util/AmountUtil.java b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/util/AmountUtil.java new file mode 100644 index 0000000..89ff568 --- /dev/null +++ b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/util/AmountUtil.java @@ -0,0 +1,438 @@ +package com.klaro.acquirecore.framework.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.util.Objects; + +/** + * Static utility class for amount/money operations migrated from legacy/util_amount.c + * Uses java.math.BigDecimal for precise monetary calculations. + */ +public final class AmountUtil { + + public static final int DEFAULT_SCALE = 2; + public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_UP; + public static final String KOREAN_WON_SYMBOL = "₩"; + public static final String USDollar_SYMBOL = "$"; + public static final String EURO_SYMBOL = "€"; + + private static final DecimalFormat KOREAN_WON_FORMAT = new DecimalFormat("#,###"); + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,###.##"); + private static final DecimalFormat CURRENCY_FORMAT = new DecimalFormat("#,###.00"); + + private AmountUtil() { + throw new UnsupportedOperationException("Utility class cannot be instantiated"); + } + + /** + * Creates a BigDecimal from a string value. + * + * @param value the string value to convert + * @return the BigDecimal value + * @throws NumberFormatException if the string is not a valid number + */ + public static BigDecimal of(String value) { + Objects.requireNonNull(value, "value must not be null"); + return new BigDecimal(value.trim()); + } + + /** + * Creates a BigDecimal from a long value. + * + * @param value the long value to convert + * @return the BigDecimal value + */ + public static BigDecimal of(long value) { + return BigDecimal.valueOf(value); + } + + /** + * Creates a BigDecimal from a double value. + * + * @param value the double value to convert + * @return the BigDecimal value + */ + public static BigDecimal of(double value) { + return BigDecimal.valueOf(value); + } + + /** + * Adds two BigDecimal values. + * + * @param a the first operand + * @param b the second operand + * @return the sum + */ + public static BigDecimal add(BigDecimal a, BigDecimal b) { + Objects.requireNonNull(a, "a must not be null"); + Objects.requireNonNull(b, "b must not be null"); + return a.add(b); + } + + /** + * Subtracts the second BigDecimal from the first. + * + * @param a the minuend + * @param b the subtrahend + * @return the difference + */ + public static BigDecimal subtract(BigDecimal a, BigDecimal b) { + Objects.requireNonNull(a, "a must not be null"); + Objects.requireNonNull(b, "b must not be null"); + return a.subtract(b); + } + + /** + * Multiplies two BigDecimal values. + * + * @param a the first operand + * @param b the second operand + * @return the product + */ + public static BigDecimal multiply(BigDecimal a, BigDecimal b) { + Objects.requireNonNull(a, "a must not be null"); + Objects.requireNonNull(b, "b must not be null"); + return a.multiply(b); + } + + /** + * Divides the first BigDecimal by the second. + * + * @param dividend the dividend + * @param divisor the divisor + * @return the quotient + * @throws ArithmeticException if divisor is zero + */ + public static BigDecimal divide(BigDecimal dividend, BigDecimal divisor) { + Objects.requireNonNull(dividend, "dividend must not be null"); + Objects.requireNonNull(divisor, "divisor must not be null"); + if (BigDecimal.ZERO.compareTo(divisor) == 0) { + throw new ArithmeticException("Division by zero"); + } + return dividend.divide(divisor, DEFAULT_SCALE, DEFAULT_ROUNDING_MODE); + } + + /** + * Divides the first BigDecimal by the second with specified scale and rounding mode. + * + * @param dividend the dividend + * @param divisor the divisor + * @param scale the scale + * @param roundingMode the rounding mode + * @return the quotient + * @throws ArithmeticException if divisor is zero + */ + public static BigDecimal divide(BigDecimal dividend, BigDecimal divisor, int scale, RoundingMode roundingMode) { + Objects.requireNonNull(dividend, "dividend must not be null"); + Objects.requireNonNull(divisor, "divisor must not be null"); + Objects.requireNonNull(roundingMode, "roundingMode must not be null"); + if (BigDecimal.ZERO.compareTo(divisor) == 0) { + throw new ArithmeticException("Division by zero"); + } + return dividend.divide(divisor, scale, roundingMode); + } + + /** + * Rounds a BigDecimal to the default scale (2) using default rounding mode (HALF_UP). + * + * @param value the value to round + * @return the rounded value + */ + public static BigDecimal round(BigDecimal value) { + Objects.requireNonNull(value, "value must not be null"); + return value.setScale(DEFAULT_SCALE, DEFAULT_ROUNDING_MODE); + } + + /** + * Rounds a BigDecimal to the specified scale using the specified rounding mode. + * + * @param value the value to round + * @param scale the scale + * @param roundingMode the rounding mode + * @return the rounded value + */ + public static BigDecimal round(BigDecimal value, int scale, RoundingMode roundingMode) { + Objects.requireNonNull(value, "value must not be null"); + Objects.requireNonNull(roundingMode, "roundingMode must not be null"); + return value.setScale(scale, roundingMode); + } + + /** + * Calculates percentage of a value. + * + * @param value the base value + * @param percentage the percentage (e.g., 10 for 10%) + * @return the percentage amount + */ + public static BigDecimal percentage(BigDecimal value, BigDecimal percentage) { + Objects.requireNonNull(value, "value must not be null"); + Objects.requireNonNull(percentage, "percentage must not be null"); + return value.multiply(percentage).divide(BigDecimal.valueOf(100), DEFAULT_SCALE, DEFAULT_ROUNDING_MODE); + } + + /** + * Calculates percentage of a value and adds it. + * + * @param value the base value + * @param percentage the percentage to add + * @return the value plus percentage amount + */ + public static BigDecimal addPercentage(BigDecimal value, BigDecimal percentage) { + Objects.requireNonNull(value, "value must not be null"); + Objects.requireNonNull(percentage, "percentage must not be null"); + return value.add(percentage(value, percentage)); + } + + /** + * Calculates percentage of a value and subtracts it. + * + * @param value the base value + * @param percentage the percentage to subtract + * @return the value minus percentage amount + */ + public static BigDecimal subtractPercentage(BigDecimal value, BigDecimal percentage) { + Objects.requireNonNull(value, "value must not be null"); + Objects.requireNonNull(percentage, "percentage must not be null"); + return value.subtract(percentage(value, percentage)); + } + + /** + * Formats a BigDecimal as a Korean Won amount (e.g., "₩1,000"). + * + * @param amount the amount to format + * @return the formatted string + */ + public static String formatKoreanWon(BigDecimal amount) { + Objects.requireNonNull(amount, "amount must not be null"); + return KOREAN_WON_SYMBOL + KOREAN_WON_FORMAT.format(amount.setScale(0, RoundingMode.DOWN)); + } + + /** + * Formats a BigDecimal as a currency amount with 2 decimal places. + * + * @param amount the amount to format + * @return the formatted string + */ + public static String formatCurrency(BigDecimal amount) { + Objects.requireNonNull(amount, "amount must not be null"); + return CURRENCY_FORMAT.format(amount); + } + + /** + * Formats a BigDecimal as a decimal number. + * + * @param amount the amount to format + * @return the formatted string + */ + public static String formatDecimal(BigDecimal amount) { + Objects.requireNonNull(amount, "amount must not be null"); + return DECIMAL_FORMAT.format(amount); + } + + /** + * Formats a BigDecimal with a custom pattern. + * + * @param amount the amount to format + * @param pattern the DecimalFormat pattern + * @return the formatted string + */ + public static String format(BigDecimal amount, String pattern) { + Objects.requireNonNull(amount, "amount must not be null"); + Objects.requireNonNull(pattern, "pattern must not be null"); + DecimalFormat formatter = new DecimalFormat(pattern); + return formatter.format(amount); + } + + /** + * Compares two BigDecimal values. + * + * @param a the first value + * @param b the second value + * @return -1 if a < b, 0 if a == b, 1 if a > b + */ + public static int compare(BigDecimal a, BigDecimal b) { + Objects.requireNonNull(a, "a must not be null"); + Objects.requireNonNull(b, "b must not be null"); + return a.compareTo(b); + } + + /** + * Checks if the first value is greater than the second. + * + * @param a the first value + * @param b the second value + * @return true if a > b + */ + public static boolean isGreaterThan(BigDecimal a, BigDecimal b) { + return compare(a, b) > 0; + } + + /** + * Checks if the first value is less than the second. + * + * @param a the first value + * @param b the second value + * @return true if a < b + */ + public static boolean isLessThan(BigDecimal a, BigDecimal b) { + return compare(a, b) < 0; + } + + /** + * Checks if the first value is greater than or equal to the second. + * + * @param a the first value + * @param b the second value + * @return true if a >= b + */ + public static boolean isGreaterThanOrEqual(BigDecimal a, BigDecimal b) { + return compare(a, b) >= 0; + } + + /** + * Checks if the first value is less than or equal to the second. + * + * @param a the first value + * @param b the second value + * @return true if a <= b + */ + public static boolean isLessThanOrEqual(BigDecimal a, BigDecimal b) { + return compare(a, b) <= 0; + } + + /** + * Checks if the value is zero. + * + * @param value the value to check + * @return true if value is zero + */ + public static boolean isZero(BigDecimal value) { + Objects.requireNonNull(value, "value must not be null"); + return BigDecimal.ZERO.compareTo(value) == 0; + } + + /** + * Checks if the value is positive (greater than zero). + * + * @param value the value to check + * @return true if value > 0 + */ + public static boolean isPositive(BigDecimal value) { + return compare(value, BigDecimal.ZERO) > 0; + } + + /** + * Checks if the value is negative (less than zero). + * + * @param value the value to check + * @return true if value < 0 + */ + public static boolean isNegative(BigDecimal value) { + return compare(value, BigDecimal.ZERO) < 0; + } + + /** + * Returns the absolute value. + * + * @param value the value + * @return the absolute value + */ + public static BigDecimal abs(BigDecimal value) { + Objects.requireNonNull(value, "value must not be null"); + return value.abs(); + } + + /** + * Negates the value. + * + * @param value the value + * @return the negated value + */ + public static BigDecimal negate(BigDecimal value) { + Objects.requireNonNull(value, "value must not be null"); + return value.negate(); + } + + /** + * Returns the maximum of two values. + * + * @param a the first value + * @param b the second value + * @return the maximum value + */ + public static BigDecimal max(BigDecimal a, BigDecimal b) { + Objects.requireNonNull(a, "a must not be null"); + Objects.requireNonNull(b, "b must not be null"); + return a.max(b); + } + + /** + * Returns the minimum of two values. + * + * @param a the first value + * @param b the second value + * @return the minimum value + */ + public static BigDecimal min(BigDecimal a, BigDecimal b) { + Objects.requireNonNull(a, "a must not be null"); + Objects.requireNonNull(b, "b must not be null"); + return a.min(b); + } + + /** + * Parses a string that may contain currency symbols and commas. + * + * @param value the string to parse + * @return the parsed BigDecimal + */ + public static BigDecimal parseAmount(String value) { + Objects.requireNonNull(value, "value must not be null"); + String cleaned = value.replace(KOREAN_WON_SYMBOL, "") + .replace(USDollar_SYMBOL, "") + .replace(EURO_SYMBOL, "") + .replace(",", "") + .replace(" ", "") + .trim(); + return new BigDecimal(cleaned); + } + + /** + * Validates if a string is a valid amount. + * + * @param value the string to validate + * @return true if valid, false otherwise + */ + public static boolean isValidAmount(String value) { + if (value == null || value.isEmpty()) { + return false; + } + try { + parseAmount(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * Converts cents/won to decimal amount. + * + * @param cents the amount in cents + * @return the decimal amount + */ + public static BigDecimal fromCents(long cents) { + return BigDecimal.valueOf(cents).divide(BigDecimal.valueOf(100), DEFAULT_SCALE, DEFAULT_ROUNDING_MODE); + } + + /** + * Converts decimal amount to cents/won. + * + * @param amount the decimal amount + * @return the amount in cents + */ + public static long toCents(BigDecimal amount) { + Objects.requireNonNull(amount, "amount must not be null"); + return amount.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.DOWN).longValue(); + } +}