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
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<Money>, 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())
}
}
}

View File

@ -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
}

View File

@ -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!")
}

View File

@ -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<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
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
}
}

View File

@ -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)
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
// 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()
}
// 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)

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
*/
@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<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.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
}
}