/
RecordLoanContract.kt
137 lines (128 loc) · 7.12 KB
/
RecordLoanContract.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package io.provenance.scope.loan.contracts
import io.dartinc.registry.v1beta1.ENote
import io.provenance.scope.contract.annotations.Function
import io.provenance.scope.contract.annotations.Input
import io.provenance.scope.contract.annotations.Participants
import io.provenance.scope.contract.annotations.Record
import io.provenance.scope.contract.annotations.ScopeSpecification
import io.provenance.scope.contract.annotations.SkipIfRecordExists
import io.provenance.scope.contract.proto.Specifications.PartyType
import io.provenance.scope.contract.spec.P8eContract
import io.provenance.scope.loan.LoanScopeFacts
import io.provenance.scope.loan.LoanScopeProperties.assetLoanKey
import io.provenance.scope.loan.LoanScopeProperties.assetMismoKey
import io.provenance.scope.loan.utility.ContractRequirementType.VALID_INPUT
import io.provenance.scope.loan.utility.documentValidation
import io.provenance.scope.loan.utility.eNoteInputValidation
import io.provenance.scope.loan.utility.fundingValidation
import io.provenance.scope.loan.utility.isSet
import io.provenance.scope.loan.utility.isValid
import io.provenance.scope.loan.utility.loanDocumentInputValidation
import io.provenance.scope.loan.utility.loanValidationInputValidation
import io.provenance.scope.loan.utility.orError
import io.provenance.scope.loan.utility.servicingRightsInputValidation
import io.provenance.scope.loan.utility.toFigureTechLoan
import io.provenance.scope.loan.utility.tryUnpackingAs
import io.provenance.scope.loan.utility.uliValidation
import io.provenance.scope.loan.utility.updateServicingData
import io.provenance.scope.loan.utility.validateRequirements
import tech.figure.asset.v1beta1.Asset
import tech.figure.loan.v1beta1.LoanDocuments
import tech.figure.loan.v1beta1.MISMOLoanMetadata
import tech.figure.servicing.v1beta1.LoanStateOuterClass.ServicingData
import tech.figure.servicing.v1beta1.ServicingRightsOuterClass.ServicingRights
import tech.figure.loan.v1beta1.Loan as FigureTechLoan
import tech.figure.validation.v1beta1.LoanValidation as LoanValidation_v1beta1
import tech.figure.validation.v1beta2.LoanValidation as LoanValidation_v1beta2
@Participants(roles = [PartyType.OWNER])
@ScopeSpecification(["tech.figure.loan"])
open class RecordLoanContract(
@Record(name = LoanScopeFacts.asset, optional = true) val existingAsset: Asset?,
@Record(name = LoanScopeFacts.eNote, optional = true) val existingENote: ENote?,
@Record(name = LoanScopeFacts.servicingData, optional = true) val existingServicingData: ServicingData?,
@Record(name = LoanScopeFacts.servicingRights, optional = true) val existingServicingRights: ServicingRights?,
) : P8eContract() {
@Function(invokedBy = PartyType.OWNER)
@Record(LoanScopeFacts.asset)
open fun recordAsset(@Input(LoanScopeFacts.asset) newAsset: Asset) = newAsset.also {
validateRequirements(VALID_INPUT) {
if (existingAsset.isSet()) {
requireThat(
(existingAsset!!.id == newAsset.id) orError "Cannot change asset ID",
(existingAsset.type == newAsset.type) orError "Cannot change asset type",
)
} else {
requireThat(
newAsset.id.isValid() orError "Asset must have valid ID",
newAsset.type.isNotBlank() orError "Asset is missing type",
)
}
if (newAsset.containsKv(assetLoanKey)) {
newAsset.kvMap[assetLoanKey]!!.let { newLoanValue ->
newLoanValue.tryUnpackingAs<FigureTechLoan, Unit>("input asset's \"${assetLoanKey}\"") { newLoan ->
if (existingAsset.isSet()) {
existingAsset!!.kvMap[assetLoanKey]?.toFigureTechLoan()?.also { existingLoan ->
requireThat(
(existingLoan.id == newLoan.id) orError "Cannot change loan ID",
(existingLoan.originatorName == newLoan.originatorName) orError "Cannot change loan originator name",
)
}
} else {
requireThat(
newLoan.id.isValid() orError "Loan must have valid ID",
newLoan.originatorUuid.isValid() orError "Loan must have valid originator ID",
newLoan.originatorName.isNotBlank() orError "Loan is missing originator name",
)
uliValidation(newLoan.uli)
newLoan.funding.takeIf { it.isSet() }?.let { newLoanFunding ->
fundingValidation(newLoanFunding)
}
}
}
}
} else {
raiseError("\"${assetLoanKey}\" must be a key in the input asset")
}
newAsset.kvMap[assetMismoKey]?.let { newMismoLoanValue ->
newMismoLoanValue.tryUnpackingAs<MISMOLoanMetadata, Unit>("input asset's \"${assetMismoKey}\"") { newLoan ->
documentValidation(newLoan.document)
}
}
}
}
@Function(invokedBy = PartyType.OWNER)
@Record(LoanScopeFacts.servicingRights)
@SkipIfRecordExists(LoanScopeFacts.servicingRights)
open fun recordServicingRights(@Input(LoanScopeFacts.servicingRights) servicingRights: ServicingRights) =
servicingRights.also(servicingRightsInputValidation)
@Function(invokedBy = PartyType.OWNER)
@Record(LoanScopeFacts.documents)
open fun recordDocuments(@Input(LoanScopeFacts.documents) documents: LoanDocuments) = documents.also(loanDocumentInputValidation)
@Function(invokedBy = PartyType.OWNER)
@Record(LoanScopeFacts.servicingData)
open fun recordServicingData(@Input(LoanScopeFacts.servicingData) servicingData: ServicingData) =
validateRequirements(VALID_INPUT) {
updateServicingData(
existingServicingData = existingServicingData ?: ServicingData.getDefaultInstance(),
newServicingData = servicingData,
)
}
@Suppress("deprecation")
@Function(invokedBy = PartyType.OWNER)
@Record(LoanScopeFacts.loanValidations)
/**
* Overwrites the value of the deprecated validation record.
*
* This function exists to provide backwards compatibility for scopes which used a validation record before the newer one was introduced.
*/
open fun recordOldValidationData(@Input(LoanScopeFacts.loanValidations) loanValidations: LoanValidation_v1beta1) = loanValidations
@Function(invokedBy = PartyType.OWNER)
@Record(LoanScopeFacts.loanValidationMetadata)
open fun recordValidationData(@Input(LoanScopeFacts.loanValidationMetadata) loanValidations: LoanValidation_v1beta2) = loanValidations.also(
loanValidationInputValidation
)
@Function(invokedBy = PartyType.OWNER)
@Record(LoanScopeFacts.eNote)
@SkipIfRecordExists(LoanScopeFacts.eNote)
open fun recordENote(@Input(LoanScopeFacts.eNote) eNote: ENote) = eNote.also(eNoteInputValidation)
}