cache last DataStore object for bolus wizard
This commit is contained in:
parent
c802e7e4a2
commit
86edb3dfae
16 changed files with 95 additions and 42 deletions
|
@ -32,11 +32,10 @@ interface IobCobCalculator {
|
|||
/**
|
||||
* Calculate CobInfo to now()
|
||||
*
|
||||
* @param waitForCalculationFinish access autosens data synchronized (wait for result if calculation is running)
|
||||
* @param reason caller identification
|
||||
* @return CobInfo
|
||||
*/
|
||||
fun getCobInfo(waitForCalculationFinish: Boolean, reason: String): CobInfo
|
||||
fun getCobInfo(reason: String): CobInfo
|
||||
|
||||
/**
|
||||
* Calculate IobTotal from boluses and extended boluses to now().
|
||||
|
|
|
@ -107,7 +107,7 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
|
|||
|
||||
fun isActive(): Boolean = time.secondsFromMidnight() >= validFrom() && time.secondsFromMidnight() <= validTo() && forDevice(DEVICE_PHONE)
|
||||
|
||||
fun doCalc(profile: Profile, profileName: String, lastBG: InMemoryGlucoseValue, _synchronized: Boolean): BolusWizard {
|
||||
fun doCalc(profile: Profile, profileName: String, lastBG: InMemoryGlucoseValue): BolusWizard {
|
||||
val dbRecord = persistenceLayer.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
|
||||
val tempTarget = if (dbRecord is ValueWrapper.Existing) dbRecord.value else null
|
||||
//BG
|
||||
|
@ -117,7 +117,7 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
|
|||
}
|
||||
// COB
|
||||
val cob =
|
||||
if (useCOB() == YES) iobCobCalculator.getCobInfo(_synchronized, "QuickWizard COB").displayCob ?: 0.0
|
||||
if (useCOB() == YES) iobCobCalculator.getCobInfo("QuickWizard COB").displayCob ?: 0.0
|
||||
else 0.0
|
||||
// Bolus IOB
|
||||
var bolusIOB = false
|
||||
|
|
|
@ -219,7 +219,7 @@ class OverviewDataImpl @Inject constructor(
|
|||
|
||||
override fun bolusIob(iobCobCalculator: IobCobCalculator): IobTotal = iobCobCalculator.calculateIobFromBolus().round()
|
||||
override fun basalIob(iobCobCalculator: IobCobCalculator): IobTotal = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
|
||||
override fun cobInfo(iobCobCalculator: IobCobCalculator): CobInfo = iobCobCalculator.getCobInfo(true, "Overview COB")
|
||||
override fun cobInfo(iobCobCalculator: IobCobCalculator): CobInfo = iobCobCalculator.getCobInfo("Overview COB")
|
||||
|
||||
override val lastCarbsTime: Long
|
||||
get() = repository.getLastCarbsRecordWrapped().blockingGet().let { lastCarbs ->
|
||||
|
|
|
@ -38,7 +38,7 @@ class TriggerCOB(injector: HasAndroidInjector) : Trigger(injector) {
|
|||
}
|
||||
|
||||
override fun shouldRun(): Boolean {
|
||||
val cobInfo = iobCobCalculator.getCobInfo(false, "AutomationTriggerCOB")
|
||||
val cobInfo = iobCobCalculator.getCobInfo("AutomationTriggerCOB")
|
||||
if (cobInfo.displayCob == null) {
|
||||
return if (comparator.value === Comparator.Compare.IS_NOT_AVAILABLE) {
|
||||
aapsLogger.debug(LTag.AUTOMATION, "Ready for execution: " + friendlyDescription())
|
||||
|
|
|
@ -21,7 +21,7 @@ class TriggerCOBTest : TriggerTestBase() {
|
|||
|
||||
@Test fun shouldRunTest() {
|
||||
// COB value is 6
|
||||
`when`(iobCobCalculator.getCobInfo(false, "AutomationTriggerCOB")).thenReturn(CobInfo(0, 6.0, 2.0))
|
||||
`when`(iobCobCalculator.getCobInfo("AutomationTriggerCOB")).thenReturn(CobInfo(0, 6.0, 2.0))
|
||||
var t: TriggerCOB = TriggerCOB(injector).setValue(1.0).comparator(Comparator.Compare.IS_EQUAL)
|
||||
Assert.assertFalse(t.shouldRun())
|
||||
t = TriggerCOB(injector).setValue(6.0).comparator(Comparator.Compare.IS_EQUAL)
|
||||
|
|
|
@ -133,7 +133,7 @@ class DataBroadcastPlugin @Inject constructor(
|
|||
bundle.putDouble("basalIob", basalIob.basaliob)
|
||||
bundle.putDouble("iob", bolusIob.iob + basalIob.basaliob) // total IOB
|
||||
|
||||
val cob = iobCobCalculator.getCobInfo(false, "broadcast")
|
||||
val cob = iobCobCalculator.getCobInfo("broadcast")
|
||||
bundle.putDouble("cob", cob.displayCob ?: -1.0) // COB [g] or -1 if N/A
|
||||
bundle.putDouble("futureCarbs", cob.futureCarbs) // future scheduled carbs
|
||||
}
|
||||
|
|
|
@ -511,7 +511,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
|
|||
val quickWizardEntry = quickWizard.getActive()
|
||||
if (quickWizardEntry != null && actualBg != null && profile != null) {
|
||||
binding.buttonsLayout.quickWizardButton.visibility = View.VISIBLE
|
||||
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg, true)
|
||||
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg)
|
||||
if (wizard.calculatedTotalInsulin > 0.0 && quickWizardEntry.carbs() > 0.0) {
|
||||
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(quickWizardEntry.carbs())).value()
|
||||
activity?.let {
|
||||
|
@ -539,7 +539,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
|
|||
_binding ?: return@runOnUiThread
|
||||
if (quickWizardEntry != null && lastBG != null && profile != null && pump.isInitialized() && !pump.isSuspended() && !loop.isDisconnected) {
|
||||
binding.buttonsLayout.quickWizardButton.visibility = View.VISIBLE
|
||||
val wizard = quickWizardEntry.doCalc(profile, profileName, lastBG, false)
|
||||
val wizard = quickWizardEntry.doCalc(profile, profileName, lastBG)
|
||||
binding.buttonsLayout.quickWizardButton.text = quickWizardEntry.buttonText() + "\n" + rh.gs(info.nightscout.core.graph.R.string.format_carbs, quickWizardEntry.carbs()) +
|
||||
" " + rh.gs(info.nightscout.interfaces.R.string.format_insulin_units, wizard.calculatedTotalInsulin)
|
||||
if (wizard.calculatedTotalInsulin <= 0) binding.buttonsLayout.quickWizardButton.visibility = View.GONE
|
||||
|
|
|
@ -148,12 +148,10 @@ class PersistentNotificationPlugin @Inject constructor(
|
|||
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
|
||||
line2 =
|
||||
rh.gs(info.nightscout.core.ui.R.string.treatments_iob_label_string) + " " + DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U " + rh.gs(info.nightscout.core.ui.R.string.cob) + ": " + iobCobCalculator.getCobInfo(
|
||||
false,
|
||||
"PersistentNotificationPlugin"
|
||||
).generateCOBString()
|
||||
val line2aa =
|
||||
rh.gs(info.nightscout.core.ui.R.string.treatments_iob_label_string) + " " + DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U. " + rh.gs(info.nightscout.core.ui.R.string.cob) + ": " + iobCobCalculator.getCobInfo(
|
||||
false,
|
||||
"PersistentNotificationPlugin"
|
||||
).generateCOBString() + "."
|
||||
line3 = DecimalFormatter.to2Decimal(pump.baseBasalRate) + " U/h"
|
||||
|
|
|
@ -365,7 +365,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
|||
if (glucoseStatus != null) reply += rh.gs(R.string.sms_delta) + " " + Profile.toUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) + " " + units + ", "
|
||||
val bolusIob = iobCobCalculator.calculateIobFromBolus().round()
|
||||
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
|
||||
val cobInfo = iobCobCalculator.getCobInfo(false, "SMS COB")
|
||||
val cobInfo = iobCobCalculator.getCobInfo("SMS COB")
|
||||
reply += (rh.gs(R.string.sms_iob) + " " + DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U ("
|
||||
+ rh.gs(R.string.sms_bolus) + " " + DecimalFormatter.to2Decimal(bolusIob.iob) + "U "
|
||||
+ rh.gs(R.string.sms_basal) + " " + DecimalFormatter.to2Decimal(basalIob.basaliob) + "U), "
|
||||
|
|
|
@ -16,7 +16,13 @@ import info.nightscout.core.wizard.BolusWizard
|
|||
import info.nightscout.core.wizard.QuickWizard
|
||||
import info.nightscout.core.wizard.QuickWizardEntry
|
||||
import info.nightscout.database.ValueWrapper
|
||||
import info.nightscout.database.entities.*
|
||||
import info.nightscout.database.entities.Bolus
|
||||
import info.nightscout.database.entities.GlucoseValue
|
||||
import info.nightscout.database.entities.TemporaryBasal
|
||||
import info.nightscout.database.entities.TemporaryTarget
|
||||
import info.nightscout.database.entities.TotalDailyDose
|
||||
import info.nightscout.database.entities.UserEntry
|
||||
import info.nightscout.database.entities.ValueWithUnit
|
||||
import info.nightscout.database.entities.interfaces.end
|
||||
import info.nightscout.database.impl.AppRepository
|
||||
import info.nightscout.database.impl.transactions.CancelCurrentTemporaryTargetIfAnyTransaction
|
||||
|
@ -60,7 +66,9 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
|
|||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import java.util.LinkedList
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.stream.Collectors
|
||||
import javax.inject.Inject
|
||||
|
@ -369,7 +377,7 @@ class DataHandlerMobile @Inject constructor(
|
|||
sendError(rh.gs(info.nightscout.core.ui.R.string.wizard_no_actual_bg))
|
||||
return
|
||||
}
|
||||
val cobInfo = iobCobCalculator.getCobInfo(false, "Wizard wear")
|
||||
val cobInfo = iobCobCalculator.getCobInfo("Wizard wear")
|
||||
if (cobInfo.displayCob == null) {
|
||||
sendError(rh.gs(info.nightscout.core.ui.R.string.wizard_no_cob))
|
||||
return
|
||||
|
@ -436,7 +444,7 @@ class DataHandlerMobile @Inject constructor(
|
|||
sendError(rh.gs(info.nightscout.core.ui.R.string.wizard_no_active_profile))
|
||||
return
|
||||
}
|
||||
val cobInfo = iobCobCalculator.getCobInfo(false, "QuickWizard wear")
|
||||
val cobInfo = iobCobCalculator.getCobInfo("QuickWizard wear")
|
||||
if (cobInfo.displayCob == null) {
|
||||
sendError(rh.gs(info.nightscout.core.ui.R.string.wizard_no_cob))
|
||||
return
|
||||
|
@ -447,7 +455,7 @@ class DataHandlerMobile @Inject constructor(
|
|||
return
|
||||
}
|
||||
|
||||
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg, true)
|
||||
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg)
|
||||
|
||||
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(quickWizardEntry.carbs())).value()
|
||||
if (carbsAfterConstraints != quickWizardEntry.carbs()) {
|
||||
|
@ -877,7 +885,7 @@ class DataHandlerMobile @Inject constructor(
|
|||
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
|
||||
iobSum = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob)
|
||||
iobDetail = "(${DecimalFormatter.to2Decimal(bolusIob.iob)}|${DecimalFormatter.to2Decimal(basalIob.basaliob)})"
|
||||
cobString = iobCobCalculator.getCobInfo(false, "WatcherUpdaterService").generateCOBString()
|
||||
cobString = iobCobCalculator.getCobInfo("WatcherUpdaterService").generateCOBString()
|
||||
currentBasal =
|
||||
iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis())?.toStringShort() ?: rh.gs(info.nightscout.core.ui.R.string.pump_base_basal_rate, profile.getBasal())
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ class StatusLinePlugin @Inject constructor(
|
|||
status += " " + (if (bgi >= 0) "+" else "") + DecimalFormatter.to2Decimal(bgi)
|
||||
}
|
||||
// COB
|
||||
status += " " + iobCobCalculator.getCobInfo(false, "StatusLinePlugin").generateCOBString()
|
||||
status += " " + iobCobCalculator.getCobInfo("StatusLinePlugin").generateCOBString()
|
||||
return status
|
||||
}
|
||||
}
|
|
@ -292,10 +292,8 @@ class IobCobCalculatorPlugin @Inject constructor(
|
|||
return ads.getLastAutosensData(reason, aapsLogger, dateUtil)
|
||||
}
|
||||
|
||||
override fun getCobInfo(waitForCalculationFinish: Boolean, reason: String): CobInfo {
|
||||
val autosensData =
|
||||
if (waitForCalculationFinish) getLastAutosensDataWithWaitForCalculationFinish(reason)
|
||||
else ads.getLastAutosensData(reason, aapsLogger, dateUtil)
|
||||
override fun getCobInfo(reason: String): CobInfo {
|
||||
val autosensData = ads.getLastAutosensData(reason, aapsLogger, dateUtil)
|
||||
var displayCob: Double? = null
|
||||
var futureCarbs = 0.0
|
||||
val now = dateUtil.now()
|
||||
|
|
|
@ -116,30 +116,34 @@ class AutosensDataStoreObject : AutosensDataStore {
|
|||
}
|
||||
}
|
||||
|
||||
// during recalculation autosensDataTable is cleared and not available
|
||||
// for providing COB, which is an serious issue in BolusWizard
|
||||
// So let save last value after every calculation and use it
|
||||
// if autosensDataTable is not available
|
||||
var storedLastAutosensResult: AutosensData? = null
|
||||
get() = field?.let { if (it.time < System.currentTimeMillis() - 11 * 60 * 1000) it else null }
|
||||
|
||||
override fun getLastAutosensData(reason: String, aapsLogger: AAPSLogger, dateUtil: DateUtil): AutosensData? {
|
||||
synchronized(dataLock) {
|
||||
if (autosensDataTable.size() < 1) {
|
||||
aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA null: autosensDataTable empty ($reason)")
|
||||
return null
|
||||
return storedLastAutosensResult
|
||||
}
|
||||
val data: AutosensData? = try {
|
||||
val data: AutosensData = try {
|
||||
autosensDataTable.valueAt(autosensDataTable.size() - 1)
|
||||
} catch (e: Exception) {
|
||||
// data can be processed on the background
|
||||
// in this rare case better return null and do not block UI
|
||||
// APS plugin should use getLastAutosensDataSynchronized where the blocking is not an issue
|
||||
aapsLogger.error("AUTOSENSDATA null: Exception caught ($reason)")
|
||||
return null
|
||||
return storedLastAutosensResult
|
||||
}
|
||||
if (data == null) {
|
||||
aapsLogger.error("AUTOSENSDATA null: data==null")
|
||||
return null
|
||||
}
|
||||
return if (data.time < System.currentTimeMillis() - 11 * 60 * 1000) {
|
||||
return if (data.time < dateUtil.now() - 11 * 60 * 1000) {
|
||||
aapsLogger.debug(LTag.AUTOSENS) { "AUTOSENSDATA null: data is old ($reason) size()=${autosensDataTable.size()} lastData=${dateUtil.dateAndTimeAndSecondsString(data.time)}" }
|
||||
null
|
||||
storedLastAutosensResult
|
||||
} else {
|
||||
aapsLogger.debug(LTag.AUTOSENS) { "AUTOSENSDATA ($reason) $data" }
|
||||
storedLastAutosensResult = data
|
||||
data
|
||||
}
|
||||
}
|
||||
|
@ -152,11 +156,8 @@ class AutosensDataStoreObject : AutosensDataStore {
|
|||
}
|
||||
var diff = abs(someTime - referenceTime)
|
||||
diff %= T.mins(5).msecs()
|
||||
if (diff > T.mins(2).plus(T.secs(30)).msecs()){
|
||||
return someTime + abs(diff - T.mins(5).msecs()) // Adjust to the future
|
||||
} else {
|
||||
return someTime - diff // adjust to the past
|
||||
}
|
||||
return if (diff > T.mins(2).plus(T.secs(30)).msecs()) someTime + abs(diff - T.mins(5).msecs()) // Adjust to the future
|
||||
else someTime - diff // adjust to the past
|
||||
}
|
||||
|
||||
fun isAbout5minData(aapsLogger: AAPSLogger): Boolean {
|
||||
|
|
|
@ -97,7 +97,7 @@ class SmsCommunicatorPluginTest : TestBaseWithProfile() {
|
|||
val bgList: MutableList<GlucoseValue> = ArrayList()
|
||||
bgList.add(reading)
|
||||
|
||||
`when`(iobCobCalculator.getCobInfo(false, "SMS COB")).thenReturn(CobInfo(0, 10.0, 2.0))
|
||||
`when`(iobCobCalculator.getCobInfo("SMS COB")).thenReturn(CobInfo(0, 10.0, 2.0))
|
||||
`when`(iobCobCalculator.ads).thenReturn(autosensDataStore)
|
||||
`when`(autosensDataStore.lastBg()).thenReturn(InMemoryGlucoseValue(reading))
|
||||
|
||||
|
|
|
@ -1,22 +1,48 @@
|
|||
package info.nightscout.plugins.iob
|
||||
|
||||
import android.content.Context
|
||||
import androidx.collection.LongSparseArray
|
||||
import dagger.android.AndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.TestBase
|
||||
import info.nightscout.database.entities.GlucoseValue
|
||||
import info.nightscout.interfaces.aps.AutosensData
|
||||
import info.nightscout.interfaces.profile.ProfileFunction
|
||||
import info.nightscout.plugins.iob.iobCobCalculator.data.AutosensDataObject
|
||||
import info.nightscout.plugins.iob.iobCobCalculator.data.AutosensDataStoreObject
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import info.nightscout.shared.sharedPreferences.SP
|
||||
import info.nightscout.shared.utils.DateUtil
|
||||
import info.nightscout.shared.utils.T
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
|
||||
class AutosensDataStoreTest : TestBase() {
|
||||
|
||||
@Mock lateinit var context: Context
|
||||
@Mock lateinit var sp: SP
|
||||
@Mock lateinit var rh: ResourceHelper
|
||||
@Mock lateinit var profileFunction: ProfileFunction
|
||||
@Mock lateinit var dateUtilMocked: DateUtil
|
||||
|
||||
private lateinit var dateUtil: DateUtil
|
||||
|
||||
private val autosensDataStore = info.nightscout.plugins.iob.iobCobCalculator.data.AutosensDataStoreObject()
|
||||
private val autosensDataStore = AutosensDataStoreObject()
|
||||
|
||||
private val injector = HasAndroidInjector {
|
||||
AndroidInjector {
|
||||
if (it is AutosensDataObject) {
|
||||
it.aapsLogger = aapsLogger
|
||||
it.sp = sp
|
||||
it.rh = rh
|
||||
it.profileFunction = profileFunction
|
||||
it.dateUtil = dateUtilMocked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun mock() {
|
||||
|
@ -1264,7 +1290,7 @@ class AutosensDataStoreTest : TestBase() {
|
|||
Assertions.assertEquals(67.0, autosensDataStore.bucketedData!![3].value, 1.0) // Recalculated data to 30min
|
||||
Assertions.assertEquals(45.0, autosensDataStore.bucketedData!![5].value, 1.0) // Recalculated data to 20min
|
||||
|
||||
// non 5min data without referenceTime set, should allign the data to the time of the last reading
|
||||
// non 5min data without referenceTime set, should align the data to the time of the last reading
|
||||
autosensDataStore.referenceTime = -1
|
||||
bgReadingList.clear()
|
||||
bgReadingList.add(
|
||||
|
@ -1491,4 +1517,27 @@ class AutosensDataStoreTest : TestBase() {
|
|||
Assertions.assertEquals(T.mins(20).msecs(), autosensDataStore.findPreviousTimeFromBucketedData(T.mins(20).msecs()))
|
||||
Assertions.assertEquals(T.mins(20).msecs(), autosensDataStore.findPreviousTimeFromBucketedData(T.mins(25).msecs()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getLastAutosensDataTest() {
|
||||
val now = 10000000L
|
||||
Mockito.`when`(dateUtilMocked.now()).thenReturn(now)
|
||||
val ads = AutosensDataStoreObject()
|
||||
ads.storedLastAutosensResult = AutosensDataObject(injector).apply { time = now - 10 }
|
||||
// empty array, return last stored
|
||||
ads.autosensDataTable = LongSparseArray<AutosensData>()
|
||||
Assertions.assertEquals(now - 10, ads.getLastAutosensData("test", aapsLogger, dateUtilMocked)?.time)
|
||||
|
||||
// data is there, return it
|
||||
ads.autosensDataTable.append(now - 1, AutosensDataObject(injector).apply { time = now - 1 })
|
||||
Assertions.assertEquals(now - 1, ads.getLastAutosensData("test", aapsLogger, dateUtilMocked)?.time)
|
||||
// and latest value should be saved
|
||||
Assertions.assertEquals(now - 1, ads.storedLastAutosensResult?.time)
|
||||
|
||||
// data is old, return last stored
|
||||
ads.storedLastAutosensResult = AutosensDataObject(injector).apply { time = now - 1 }
|
||||
ads.autosensDataTable = LongSparseArray<AutosensData>()
|
||||
ads.autosensDataTable.append(now - T.mins(20).msecs(), AutosensDataObject(injector).apply { time = now - T.mins(20).msecs() })
|
||||
Assertions.assertEquals(now - 1, ads.getLastAutosensData("test", aapsLogger, dateUtilMocked)?.time)
|
||||
}
|
||||
}
|
|
@ -434,7 +434,7 @@ class WizardDialog : DaggerDialogFragment() {
|
|||
// COB
|
||||
var cob = 0.0
|
||||
if (binding.cobCheckbox.isChecked) {
|
||||
val cobInfo = iobCobCalculator.getCobInfo(false, "Wizard COB")
|
||||
val cobInfo = iobCobCalculator.getCobInfo("Wizard COB")
|
||||
cobInfo.displayCob?.let { cob = it }
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue