diff --git a/src/main/kotlin/com/cubetiqs/money/Money.kt b/src/main/kotlin/com/cubetiqs/money/Money.kt index bee555f..6e22ac9 100644 --- a/src/main/kotlin/com/cubetiqs/money/Money.kt +++ b/src/main/kotlin/com/cubetiqs/money/Money.kt @@ -1,17 +1,21 @@ package com.cubetiqs.money open class Money( - var value: Double, - @SpecialString(trim = true, upperCase = true) private var currency: String = "USD" -) : StdMoney { - + private var value: Double = 0.0, + private val currency: StdMoney.Currency = MoneyCurrency.USD, +) : StdMoney, StdMoney.Operator, StdMoney.ExchangeOperator { //////////////////// - PROPERTIES - //////////////////// override fun getMoneyValue(): Double { return this.value } - override fun getMoneyCurrency(): String { + // not imply with exchange rate yet + override fun StdMoney.getExchangedTo(currency: StdMoney.Currency): Double { + return getMoneyValue() + } + + override fun getMoneyCurrency(): StdMoney.Currency { return this.currency } @@ -21,11 +25,48 @@ open class Money( return "Money(value=${getMoneyValue()}, currency='${getMoneyCurrency()}')" } + override fun inc(): Money = apply { + this.value += 1 + } + + override fun dec(): Money = apply { + this.value -= 1 + } + + override fun plus(other: StdMoney): Money { + val temp = this.value + other.getExchangedTo(this.currency) + return Money(value = temp, currency = this.currency) + } + + override fun divide(other: StdMoney): Money { + val temp = this.value / other.getExchangedTo(this.currency) + return Money(value = temp, currency = this.currency) + } + + override fun multiply(other: StdMoney): Money { + val temp = this.value * other.getExchangedTo(this.getMoneyCurrency()) + return Money(value = temp, currency = this.currency) + } + + override fun plusAssign(other: StdMoney) { + this.value = this.value + other.getExchangedTo(this.currency) + } + + override fun divideAssign(other: StdMoney) { + this.value = this.value / other.getExchangedTo(this.currency) + } + + override fun multiplyAssign(other: StdMoney) { + this.value = this.value * other.getExchangedTo(this.getMoneyCurrency()) + } + companion object { val ZERO: StdMoney - get() = Money(value = 0.0) + get() = Money() + val ONE: StdMoney get() = Money(value = 1.0) + val TEN: StdMoney get() = Money(value = 10.0) @@ -33,18 +74,22 @@ open class Money( * Create a new money object with custom value * * @param value Double - * @param currency String + * @param currency MoneyCurrency */ - fun create(value: Double, currency: String = MoneyCurrency.USD.name): StdMoney { + fun create(value: Double, currency: StdMoney.Currency): Money { return Money(value = value, currency = currency) } - /** - * Create a new money object with custom value - * - * @param value Double - * @param currency MoneyCurrency - */ - fun create(value: Double, currency: MoneyCurrency = MoneyCurrency.USD): StdMoney = create(value, currency.name) + fun from(value: Double, currency: String): Money { + return create(value, object : StdMoney.Currency { + override fun getCurrency(): String { + return currency.toUpperCase().trim() + } + }) + } + + fun from(money: StdMoney): Money { + return create(value = money.getMoneyValue(), currency = money.getMoneyCurrency()) + } } } diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyArithmetic.kt b/src/main/kotlin/com/cubetiqs/money/MoneyArithmetic.kt index 0d410e2..10b3f82 100644 --- a/src/main/kotlin/com/cubetiqs/money/MoneyArithmetic.kt +++ b/src/main/kotlin/com/cubetiqs/money/MoneyArithmetic.kt @@ -1,5 +1,6 @@ package com.cubetiqs.money -fun StdMoney.addMoney(value: Double, currency: String): StdMoney { - return this + Money.create(value, currency) +fun StdMoney.addMoney(value: Double, currency: StdMoney.Currency): StdMoney { + this + Money.create(value, currency) + return this } \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyConfig.kt b/src/main/kotlin/com/cubetiqs/money/MoneyConfig.kt index ef919fa..9f3b433 100644 --- a/src/main/kotlin/com/cubetiqs/money/MoneyConfig.kt +++ b/src/main/kotlin/com/cubetiqs/money/MoneyConfig.kt @@ -67,8 +67,8 @@ object MoneyConfig { fun getConfig() = config @Throws(MoneyCurrencyStateException::class) - fun getRate(currency: String): Double { - return getConfig()[currency.toUpperCase()] + fun getRate(currency: StdMoney.Currency): Double { + return getConfig()[currency.getCurrency().toUpperCase()] ?: throw MoneyCurrencyStateException("money currency $currency is not valid!") } diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyCurrency.kt b/src/main/kotlin/com/cubetiqs/money/MoneyCurrency.kt index 3bdbf45..fdf7934 100644 --- a/src/main/kotlin/com/cubetiqs/money/MoneyCurrency.kt +++ b/src/main/kotlin/com/cubetiqs/money/MoneyCurrency.kt @@ -1,7 +1,43 @@ package com.cubetiqs.money -enum class MoneyCurrency { - USD, - KHR, - EUR +import java.io.Serializable + +/** + * Money Currency Object with flexible currency based on data and configs + * + * @author sombochea + * @since 1.0 + */ +open class MoneyCurrency( + private val name: String, + private val symbol: String? = null, + private val configs: Map? = null, +) : Serializable, StdMoney.Currency { + override fun getCurrency(): String { + return name.toUpperCase().trim() + } + + fun getSymbol(): String? { + return symbol ?: getConfigs()["symbol"]?.toString() + } + + fun getConfigs(): Map { + return configs ?: emptyMap() + } + + override fun toString(): String { + return "MoneyCurrency(name='$name', symbol=$symbol, configs=$configs)" + } + + companion object { + fun create(name: String): MoneyCurrency { + return MoneyCurrency(name = name) + } + + val USD + get() = create("USD") + + val KHR + get() = create("KHR") + } } \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyExchangeAdapter.kt b/src/main/kotlin/com/cubetiqs/money/MoneyExchangeAdapter.kt deleted file mode 100644 index d2cbdb9..0000000 --- a/src/main/kotlin/com/cubetiqs/money/MoneyExchangeAdapter.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.cubetiqs.money - -interface MoneyExchangeAdapter { - fun getRate(currency: String): Double -} \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyExchangeProvider.kt b/src/main/kotlin/com/cubetiqs/money/MoneyExchangeProvider.kt new file mode 100644 index 0000000..96547b2 --- /dev/null +++ b/src/main/kotlin/com/cubetiqs/money/MoneyExchangeProvider.kt @@ -0,0 +1,5 @@ +package com.cubetiqs.money + +interface MoneyExchangeProvider { + fun getRate(currency: StdMoney.Currency): Double +} \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyExchangeUtils.kt b/src/main/kotlin/com/cubetiqs/money/MoneyExchangeUtils.kt index 0b2f01d..7f6a3b5 100644 --- a/src/main/kotlin/com/cubetiqs/money/MoneyExchangeUtils.kt +++ b/src/main/kotlin/com/cubetiqs/money/MoneyExchangeUtils.kt @@ -1,7 +1,7 @@ package com.cubetiqs.money object MoneyExchangeUtils { - fun exchange(exchangeFrom: StdMoney, exchangeToCurrency: String): StdMoney { + fun exchange(exchangeFrom: StdMoney, exchangeToCurrency: StdMoney.Currency): StdMoney { val rateFrom = MoneyConfig.getRate(exchangeFrom.getMoneyCurrency()) val rateTo = MoneyConfig.getRate(exchangeToCurrency) return Money(value = computeRate(rateFrom, rateTo, amountFrom = exchangeFrom.getMoneyValue()), currency = exchangeToCurrency) @@ -10,4 +10,12 @@ object MoneyExchangeUtils { private fun computeRate(rateFrom: Double, rateTo: Double, baseRate: Double = 1.0, amountFrom: Double = 1.0): Double { return amountFrom * ((baseRate / rateFrom) / (baseRate / rateTo)) } + + fun getBaseCurrency(): MoneyCurrency { + return MoneyCurrency.USD + } + + fun getExchangeRate(currency: MoneyCurrency): Double { + return 0.0 + } } \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyExtension.kt b/src/main/kotlin/com/cubetiqs/money/MoneyExtension.kt index e4d2783..2afabf3 100644 --- a/src/main/kotlin/com/cubetiqs/money/MoneyExtension.kt +++ b/src/main/kotlin/com/cubetiqs/money/MoneyExtension.kt @@ -1,7 +1,7 @@ package com.cubetiqs.money -fun StdMoney.exchangeTo(currency: String): StdMoney { +fun StdMoney.exchangeTo(currency: StdMoney.Currency): StdMoney { return MoneyExchangeUtils.exchange(this, currency) } -fun StdMoney.isMatchedCurrency(currency: String) = this.getMoneyCurrency().equals(currency, ignoreCase = true) \ No newline at end of file +fun StdMoney.isMatchedCurrency(currency: StdMoney.Currency) = this.getMoneyCurrency().getCurrency().equals(currency.getCurrency(), ignoreCase = true) \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyHistory.kt b/src/main/kotlin/com/cubetiqs/money/MoneyHistory.kt new file mode 100644 index 0000000..5af4193 --- /dev/null +++ b/src/main/kotlin/com/cubetiqs/money/MoneyHistory.kt @@ -0,0 +1,15 @@ +package com.cubetiqs.money + +import java.io.Serializable + +/** + * @author sombochea + * @email sombochea@cubetiqs.com> + * @date 08/02/21 + * @since 1.0 + */ +data class MoneyHistory( + val valueOn: String? = null, + val valueOf: String? = null, + val currency: String? = null +) : Serializable \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyObject.kt b/src/main/kotlin/com/cubetiqs/money/MoneyObject.kt new file mode 100644 index 0000000..00514bb --- /dev/null +++ b/src/main/kotlin/com/cubetiqs/money/MoneyObject.kt @@ -0,0 +1,47 @@ +package com.cubetiqs.money + +import java.io.Serializable + +sealed class MoneyObject : Serializable, StdMoney { + private var value: Double = 0.0 + private var currency: MoneyCurrency = MoneyCurrency.USD + private val moneyStates: MutableMap = mutableMapOf() + + constructor(value: Double, currency: MoneyCurrency) { + this.value = value + this.currency = currency + isComputed = false + } + + /** + * Check if computed, set it to true. Because we no need to add the same value again after once + * computed. + */ + @Transient + var isComputed = false + private set + + @Transient + var isInit = false + private set + + /** Calculate the value. Must be call after complete add money. */ + fun compute(): MoneyObject = apply { + + } + + companion object { + @JvmStatic + private fun defaultCurrency(): MoneyCurrency { + return MoneyExchangeUtils.getBaseCurrency() + } + } + + override fun getMoneyValue(): Double { + return value + } + + override fun getMoneyCurrency(): StdMoney.Currency { + return currency + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyOperator.kt b/src/main/kotlin/com/cubetiqs/money/MoneyOperator.kt index 193b67c..d862856 100644 --- a/src/main/kotlin/com/cubetiqs/money/MoneyOperator.kt +++ b/src/main/kotlin/com/cubetiqs/money/MoneyOperator.kt @@ -1,12 +1,17 @@ package com.cubetiqs.money +// unary operators operator fun StdMoney.unaryMinus() = (-getMoneyValue()) operator fun StdMoney.unaryPlus() = (+getMoneyValue()) -operator fun Money.inc() = Money(value++) -operator fun Money.dec() = Money(value--) -operator fun StdMoney.plus(other: StdMoney) = Money(getMoneyValue() + other.getMoneyValue()) -operator fun StdMoney.times(other: StdMoney) = Money(getMoneyValue() * other.getMoneyValue()) -operator fun StdMoney.div(other: StdMoney) = Money(getMoneyValue() / other.getMoneyValue()) -operator fun Money.timesAssign(other: StdMoney) { - this.value = this.getMoneyValue() * other.getMoneyValue() -} \ No newline at end of file + +// operators +operator fun StdMoney.inc() = Money.from(this).inc() +operator fun StdMoney.dec() = Money.from(this).dec() +operator fun StdMoney.plus(other: StdMoney) = Money.from(this).plusAssign(other) +operator fun StdMoney.times(other: StdMoney) = Money.from(this).multiplyAssign(other) +operator fun StdMoney.div(other: StdMoney) = Money.from(this).divideAssign(other) + +// assign operators +operator fun Money.timesAssign(other: StdMoney) = this.multiplyAssign(other) +operator fun Money.plusAssign(other: StdMoney) = this.plusAssign(other) +operator fun Money.divAssign(other: StdMoney) = this.divideAssign(other) \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/money/MoneyState.kt b/src/main/kotlin/com/cubetiqs/money/MoneyState.kt new file mode 100644 index 0000000..463728e --- /dev/null +++ b/src/main/kotlin/com/cubetiqs/money/MoneyState.kt @@ -0,0 +1,15 @@ +package com.cubetiqs.money + +import java.io.Serializable + +/** + * @author sombochea + * @email sombochea@cubetiqs.com + * @date 08/02/21 + * @since 1.0 + */ +data class MoneyState( + var value: String? = null, + var currency: String? = null, + var history: MoneyHistory? = null +) : Serializable \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/money/StdMoney.kt b/src/main/kotlin/com/cubetiqs/money/StdMoney.kt index 29d20cc..33074c9 100644 --- a/src/main/kotlin/com/cubetiqs/money/StdMoney.kt +++ b/src/main/kotlin/com/cubetiqs/money/StdMoney.kt @@ -20,6 +20,30 @@ interface StdMoney { * * @return String */ - @SpecialString - fun getMoneyCurrency(): String + fun getMoneyCurrency(): Currency + + /** + * Allow for money currency called and implemented + */ + interface Currency { + fun getCurrency(): String + } + + interface ExchangeOperator { + fun StdMoney.getExchangedTo(currency: Currency): Double + } + + interface Operator { + fun plus(other: StdMoney): T + fun divide(other: StdMoney): T + + fun inc(): T + fun dec(): T + fun multiply(other: StdMoney): T + + // assign operators + fun plusAssign(other: StdMoney) + fun divideAssign(other: StdMoney) + fun multiplyAssign(other: StdMoney) + } } \ No newline at end of file diff --git a/src/test/kotlin/MoneyTests.kt b/src/test/kotlin/MoneyTests.kt index 56fda11..353e46c 100644 --- a/src/test/kotlin/MoneyTests.kt +++ b/src/test/kotlin/MoneyTests.kt @@ -2,23 +2,10 @@ import com.cubetiqs.money.Money import com.cubetiqs.money.MoneyConfig import com.cubetiqs.money.MoneyCurrency import com.cubetiqs.money.MoneyExchangeUtils -import com.cubetiqs.money.SpecialStringProcessor import org.junit.Assert import org.junit.Test class MoneyTests { - @Test - fun money_operator_test() { - // val money = Money(10.0) - // val money2 = Money(20.0) - // money *= money - // println((money + money2) * money2) - // Assert.assertEquals(100.0, money.value, 0.0) - - val test = SpecialStringProcessor().serialize(Money(1.0, " usd ")) - println(test) - } - @Test fun exchange_2usd_to_khr_test() { val properties = MoneyConfig @@ -32,27 +19,13 @@ class MoneyTests { .setProperties(properties) .parse("USD:1,KHR:4000") + // Is valid for money config? Assert.assertTrue(MoneyConfig.isValid()) - println(MoneyConfig.getConfig()) - val moneyUsd = Money(2.0) - val moneyKhr = MoneyExchangeUtils.exchange(moneyUsd, "KHR") + val moneyKhr = MoneyExchangeUtils.exchange(moneyUsd, MoneyCurrency.create("KHR")) + // Is correct exchange? Assert.assertEquals(8000.0, moneyKhr.getMoneyValue(), 0.0) } - - @Test - fun money_exchange_config_builder_test() { - MoneyConfig.propertiesBuilder - .setDeliEqual('=') - .setDeliSplit(';') - - MoneyConfig.parse("USD:1,KHR=4000,EUR=0.99") - - val moneyUsd = Money.ONE - val moneyKhr = Money.create(20000.0, MoneyCurrency.KHR) - - val result = moneyUsd - } } \ No newline at end of file