Add and updated for whole the money modules with new style and some operators and computation

But money object not full implementation yet, because we need the exchange and filter the object states for money
This commit is contained in:
Sambo Chea 2021-02-08 12:39:52 +07:00
parent 19130727e3
commit 31760ee901
14 changed files with 240 additions and 71 deletions

View File

@ -1,17 +1,21 @@
package com.cubetiqs.money package com.cubetiqs.money
open class Money( open class Money(
var value: Double, private var value: Double = 0.0,
@SpecialString(trim = true, upperCase = true) private var currency: String = "USD" private val currency: StdMoney.Currency = MoneyCurrency.USD,
) : StdMoney { ) : StdMoney, StdMoney.Operator<Money>, StdMoney.ExchangeOperator {
//////////////////// - PROPERTIES - //////////////////// //////////////////// - PROPERTIES - ////////////////////
override fun getMoneyValue(): Double { override fun getMoneyValue(): Double {
return this.value 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 return this.currency
} }
@ -21,11 +25,48 @@ open class Money(
return "Money(value=${getMoneyValue()}, currency='${getMoneyCurrency()}')" 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 { companion object {
val ZERO: StdMoney val ZERO: StdMoney
get() = Money(value = 0.0) get() = Money()
val ONE: StdMoney val ONE: StdMoney
get() = Money(value = 1.0) get() = Money(value = 1.0)
val TEN: StdMoney val TEN: StdMoney
get() = Money(value = 10.0) get() = Money(value = 10.0)
@ -33,18 +74,22 @@ open class Money(
* Create a new money object with custom value * Create a new money object with custom value
* *
* @param value Double * @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) return Money(value = value, currency = currency)
} }
/** fun from(value: Double, currency: String): Money {
* Create a new money object with custom value return create(value, object : StdMoney.Currency {
* override fun getCurrency(): String {
* @param value Double return currency.toUpperCase().trim()
* @param currency MoneyCurrency }
*/ })
fun create(value: Double, currency: MoneyCurrency = MoneyCurrency.USD): StdMoney = create(value, currency.name) }
fun from(money: StdMoney): Money {
return create(value = money.getMoneyValue(), currency = money.getMoneyCurrency())
}
} }
} }

View File

@ -1,5 +1,6 @@
package com.cubetiqs.money package com.cubetiqs.money
fun StdMoney.addMoney(value: Double, currency: String): StdMoney { fun StdMoney.addMoney(value: Double, currency: StdMoney.Currency): StdMoney {
return this + Money.create(value, currency) this + Money.create(value, currency)
return this
} }

View File

@ -67,8 +67,8 @@ object MoneyConfig {
fun getConfig() = config fun getConfig() = config
@Throws(MoneyCurrencyStateException::class) @Throws(MoneyCurrencyStateException::class)
fun getRate(currency: String): Double { fun getRate(currency: StdMoney.Currency): Double {
return getConfig()[currency.toUpperCase()] return getConfig()[currency.getCurrency().toUpperCase()]
?: throw MoneyCurrencyStateException("money currency $currency is not valid!") ?: throw MoneyCurrencyStateException("money currency $currency is not valid!")
} }

View File

@ -1,7 +1,43 @@
package com.cubetiqs.money package com.cubetiqs.money
enum class MoneyCurrency { import java.io.Serializable
USD,
KHR, /**
EUR * 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<String, Any?>? = null,
) : Serializable, StdMoney.Currency {
override fun getCurrency(): String {
return name.toUpperCase().trim()
}
fun getSymbol(): String? {
return symbol ?: getConfigs()["symbol"]?.toString()
}
fun getConfigs(): Map<String, Any?> {
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")
}
} }

View File

@ -1,5 +0,0 @@
package com.cubetiqs.money
interface MoneyExchangeAdapter {
fun getRate(currency: String): Double
}

View File

@ -0,0 +1,5 @@
package com.cubetiqs.money
interface MoneyExchangeProvider {
fun getRate(currency: StdMoney.Currency): Double
}

View File

@ -1,7 +1,7 @@
package com.cubetiqs.money package com.cubetiqs.money
object MoneyExchangeUtils { object MoneyExchangeUtils {
fun exchange(exchangeFrom: StdMoney, exchangeToCurrency: String): StdMoney { fun exchange(exchangeFrom: StdMoney, exchangeToCurrency: StdMoney.Currency): StdMoney {
val rateFrom = MoneyConfig.getRate(exchangeFrom.getMoneyCurrency()) val rateFrom = MoneyConfig.getRate(exchangeFrom.getMoneyCurrency())
val rateTo = MoneyConfig.getRate(exchangeToCurrency) val rateTo = MoneyConfig.getRate(exchangeToCurrency)
return Money(value = computeRate(rateFrom, rateTo, amountFrom = exchangeFrom.getMoneyValue()), currency = 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 { private fun computeRate(rateFrom: Double, rateTo: Double, baseRate: Double = 1.0, amountFrom: Double = 1.0): Double {
return amountFrom * ((baseRate / rateFrom) / (baseRate / rateTo)) return amountFrom * ((baseRate / rateFrom) / (baseRate / rateTo))
} }
fun getBaseCurrency(): MoneyCurrency {
return MoneyCurrency.USD
}
fun getExchangeRate(currency: MoneyCurrency): Double {
return 0.0
}
} }

View File

@ -1,7 +1,7 @@
package com.cubetiqs.money package com.cubetiqs.money
fun StdMoney.exchangeTo(currency: String): StdMoney { fun StdMoney.exchangeTo(currency: StdMoney.Currency): StdMoney {
return MoneyExchangeUtils.exchange(this, currency) return MoneyExchangeUtils.exchange(this, currency)
} }
fun StdMoney.isMatchedCurrency(currency: String) = this.getMoneyCurrency().equals(currency, ignoreCase = true) fun StdMoney.isMatchedCurrency(currency: StdMoney.Currency) = this.getMoneyCurrency().getCurrency().equals(currency.getCurrency(), ignoreCase = true)

View File

@ -0,0 +1,15 @@
package com.cubetiqs.money
import java.io.Serializable
/**
* @author sombochea <Sambo Chea>
* @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

View File

@ -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<MoneyCurrency, MoneyState> = 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
}
}

View File

@ -1,12 +1,17 @@
package com.cubetiqs.money package com.cubetiqs.money
// unary operators
operator fun StdMoney.unaryMinus() = (-getMoneyValue()) operator fun StdMoney.unaryMinus() = (-getMoneyValue())
operator fun StdMoney.unaryPlus() = (+getMoneyValue()) operator fun StdMoney.unaryPlus() = (+getMoneyValue())
operator fun Money.inc() = Money(value++)
operator fun Money.dec() = Money(value--) // operators
operator fun StdMoney.plus(other: StdMoney) = Money(getMoneyValue() + other.getMoneyValue()) operator fun StdMoney.inc() = Money.from(this).inc()
operator fun StdMoney.times(other: StdMoney) = Money(getMoneyValue() * other.getMoneyValue()) operator fun StdMoney.dec() = Money.from(this).dec()
operator fun StdMoney.div(other: StdMoney) = Money(getMoneyValue() / other.getMoneyValue()) operator fun StdMoney.plus(other: StdMoney) = Money.from(this).plusAssign(other)
operator fun Money.timesAssign(other: StdMoney) { operator fun StdMoney.times(other: StdMoney) = Money.from(this).multiplyAssign(other)
this.value = this.getMoneyValue() * other.getMoneyValue() 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)

View File

@ -0,0 +1,15 @@
package com.cubetiqs.money
import java.io.Serializable
/**
* @author sombochea <Sambo Chea>
* @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

View File

@ -20,6 +20,30 @@ interface StdMoney {
* *
* @return String * @return String
*/ */
@SpecialString fun getMoneyCurrency(): Currency
fun getMoneyCurrency(): String
/**
* Allow for money currency called and implemented
*/
interface Currency {
fun getCurrency(): String
}
interface ExchangeOperator {
fun StdMoney.getExchangedTo(currency: Currency): Double
}
interface Operator<T : StdMoney> {
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)
}
} }

View File

@ -2,23 +2,10 @@ import com.cubetiqs.money.Money
import com.cubetiqs.money.MoneyConfig import com.cubetiqs.money.MoneyConfig
import com.cubetiqs.money.MoneyCurrency import com.cubetiqs.money.MoneyCurrency
import com.cubetiqs.money.MoneyExchangeUtils import com.cubetiqs.money.MoneyExchangeUtils
import com.cubetiqs.money.SpecialStringProcessor
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
class MoneyTests { 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 @Test
fun exchange_2usd_to_khr_test() { fun exchange_2usd_to_khr_test() {
val properties = MoneyConfig val properties = MoneyConfig
@ -32,27 +19,13 @@ class MoneyTests {
.setProperties(properties) .setProperties(properties)
.parse("USD:1,KHR:4000") .parse("USD:1,KHR:4000")
// Is valid for money config?
Assert.assertTrue(MoneyConfig.isValid()) Assert.assertTrue(MoneyConfig.isValid())
println(MoneyConfig.getConfig())
val moneyUsd = Money(2.0) 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) 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
}
} }