Merge branch 'replace_mongodb' into tesobe-rdbms

This commit is contained in:
Everett Sochowski 2015-02-24 11:52:40 +01:00
commit ed9061ebaa
29 changed files with 1271 additions and 33 deletions

View File

@ -32,6 +32,13 @@ Berlin 13359, Germany
package bootstrap.liftweb
import code.api.sandbox.SandboxApiCalls
import code.metadata.comments.MappedComment
import code.metadata.counterparties.{MappedCounterpartyWhereTag, MappedCounterpartyMetadata}
import code.metadata.narrative.MappedNarrative
import code.metadata.tags.MappedTag
import code.metadata.transactionimages.MappedTransactionImage
import code.metadata.wheretags.MappedWhereTag
import code.metrics.MappedMetric
import code.bankbranches.{MappedBankBranch, MappedDataLicense}
import code.customerinfo.{MappedCustomerMessage, MappedCustomerInfo}
import code.tesobe.{ImporterAPI, CashAccountAPI}
@ -48,8 +55,8 @@ import net.liftweb.util.Helpers
import java.io.FileInputStream
import java.io.File
import javax.mail.internet.MimeMessage
import code.model.{Nonce, Consumer, Token, dataAccess}
import dataAccess._
import code.model._
import code.model.dataAccess._
import code.api._
import code.snippet.{OAuthAuthorisation, OAuthWorkedThanks}
@ -326,6 +333,9 @@ class Boot extends Loggable{
object ToSchemify {
val models = List(OBPUser, Admin, Nonce, Token, Consumer,
ViewPrivileges, ViewImpl, APIUser, MappedAccountHolder,
MappedCustomerInfo, MappedCustomerMessage,
MappedComment, MappedNarrative, MappedTag,
MappedTransactionImage, MappedWhereTag, MappedCounterpartyMetadata,
MappedCounterpartyWhereTag, MappedBank, MappedBankAccount, MappedTransaction,
MappedMetric, MappedCustomerInfo, MappedCustomerMessage,
MappedBankBranch, MappedDataLicense)
}

View File

@ -18,7 +18,7 @@ object Connector extends SimpleInjector {
val connector = new Inject(buildOne _) {}
def buildOne: Connector = LocalConnector
def buildOne: Connector = LocalMappedConnector
}

View File

@ -0,0 +1,190 @@
package code.bankconnectors
import code.metadata.counterparties.Counterparties
import code.model._
import code.model.dataAccess.{UpdatesRequestSender, MappedBankAccount, MappedAccountHolder, MappedBank}
import code.util.Helper
import com.tesobe.model.UpdateBankAccount
import net.liftweb.common.{Loggable, Full, Box}
import net.liftweb.mapper._
import net.liftweb.util.Helpers._
import net.liftweb.util.Props
import scala.concurrent.ops._
object LocalMappedConnector extends Connector {
type AccountType = MappedBankAccount
//gets a particular bank handled by this connector
override def getBank(bankId: BankId): Box[Bank] =
getMappedBank(bankId)
private def getMappedBank(bankId: BankId): Box[MappedBank] =
MappedBank.find(By(MappedBank.permalink, bankId.value))
//gets banks handled by this connector
override def getBanks: List[Bank] =
MappedBank.findAll
override def getTransaction(bankId: BankId, accountID: AccountId, transactionId: TransactionId): Box[Transaction] = {
updateAccountTransactions(bankId, accountID)
MappedTransaction.find(
By(MappedTransaction.bank, bankId.value),
By(MappedTransaction.account, accountID.value),
By(MappedTransaction.transactionId, transactionId.value)).flatMap(_.toTransaction)
}
override def getTransactions(bankId: BankId, accountID: AccountId, queryParams: OBPQueryParam*): Box[List[Transaction]] = {
val limit = queryParams.collect { case OBPLimit(value) => MaxRows[MappedTransaction](value) }.headOption
val offset = queryParams.collect { case OBPOffset(value) => StartAt[MappedTransaction](value) }.headOption
val fromDate = queryParams.collect { case OBPFromDate(date) => By_>=(MappedTransaction.tFinishDate, date) }.headOption
val toDate = queryParams.collect { case OBPToDate(date) => By_<=(MappedTransaction.tFinishDate, date) }.headOption
val ordering = queryParams.collect {
//we don't care about the intended sort field and only sort on finish date for now
case OBPOrdering(_, direction) =>
direction match {
case OBPAscending => OrderBy(MappedTransaction.tFinishDate, Ascending)
case OBPDescending => OrderBy(MappedTransaction.tFinishDate, Descending)
}
}
val optionalParams : Seq[QueryParam[MappedTransaction]] = Seq(limit.toSeq, offset.toSeq, fromDate.toSeq, toDate.toSeq, ordering.toSeq).flatten
val mapperParams = Seq(By(MappedTransaction.bank, bankId.value), By(MappedTransaction.account, accountID.value)) ++ optionalParams
val mappedTransactions = MappedTransaction.findAll(mapperParams: _*)
updateAccountTransactions(bankId, accountID)
for (account <- getBankAccount(bankId, accountID))
yield mappedTransactions.flatMap(_.toTransaction(account))
}
/**
*
* refreshes transactions via hbci if the transaction info is sourced from hbci
*
* Checks if the last update of the account was made more than one hour ago.
* if it is the case we put a message in the message queue to ask for
* transactions updates
*
* It will be used each time we fetch transactions from the DB. But the test
* is performed in a different thread.
*/
private def updateAccountTransactions(bankId : BankId, accountId : AccountId) = {
for {
bank <- getMappedBank(bankId)
account <- getBankAccountType(bankId, accountId)
} {
spawn{
val useMessageQueue = Props.getBool("messageQueue.updateBankAccountsTransaction", false)
val outDatedTransactions = now after time(account.lastUpdate.get.getTime + hours(1))
if(outDatedTransactions && useMessageQueue) {
UpdatesRequestSender.sendMsg(UpdateBankAccount(account.accountNumber.get, bank.national_identifier.get))
}
}
}
}
override def getBankAccountType(bankId: BankId, accountId: AccountId): Box[MappedBankAccount] = {
MappedBankAccount.find(
By(MappedBankAccount.bank, bankId.value),
By(MappedBankAccount.theAccountId, accountId.value))
}
//gets the users who are the legal owners/holders of the account
override def getAccountHolders(bankId: BankId, accountID: AccountId): Set[User] =
MappedAccountHolder.findAll(
By(MappedAccountHolder.accountBankPermalink, bankId.value),
By(MappedAccountHolder.accountPermalink, accountID.value)).map(accHolder => accHolder.user.obj).flatten.toSet
def getOtherBankAccount(thisAccountBankId : BankId, thisAccountId : AccountId, metadata : OtherBankAccountMetadata) : Box[OtherBankAccount] = {
//because we don't have a db backed model for OtherBankAccounts, we need to construct it from an
//OtherBankAccountMetadata and a transaction
for { //find a transaction with this counterparty
t <- MappedTransaction.find(
By(MappedTransaction.bank, thisAccountBankId.value),
By(MappedTransaction.account, thisAccountId.value),
By(MappedTransaction.counterpartyAccountHolder, metadata.getHolder),
By(MappedTransaction.counterpartyAccountNumber, metadata.getAccountNumber))
} yield {
new OtherBankAccount(
//counterparty id is defined to be the id of its metadata as we don't actually have an id for the counterparty itself
id = metadata.metadataId,
label = metadata.getHolder,
nationalIdentifier = t.counterpartyNationalId.get,
swift_bic = None,
iban = t.getCounterpartyIban(),
number = metadata.getAccountNumber,
bankName = t.counterpartyBankName.get,
kind = t.counterpartyAccountKind.get,
originalPartyBankId = thisAccountBankId,
originalPartyAccountId = thisAccountId,
alreadyFoundMetadata = Some(metadata)
)
}
}
override def getOtherBankAccounts(bankId: BankId, accountID: AccountId): List[OtherBankAccount] =
Counterparties.counterparties.vend.getMetadatas(bankId, accountID).flatMap(getOtherBankAccount(bankId, accountID, _))
override def getOtherBankAccount(bankId: BankId, accountID: AccountId, otherAccountID: String): Box[OtherBankAccount] =
Counterparties.counterparties.vend.getMetadata(bankId, accountID, otherAccountID).flatMap(getOtherBankAccount(bankId, accountID, _))
override def getPhysicalCards(user: User): Set[PhysicalCard] =
Set.empty
override def getPhysicalCardsForBank(bankId: BankId, user: User): Set[PhysicalCard] =
Set.empty
override def makePaymentImpl(fromAccount: MappedBankAccount, toAccount: MappedBankAccount, amt: BigDecimal): Box[TransactionId] = {
val fromTransAmt = -amt //from account balance should decrease
val toTransAmt = amt //to account balance should increase
//we need to save a copy of this payment as a transaction in each of the accounts involved, with opposite amounts
val sentTransactionId = saveTransaction(fromAccount, toAccount, fromTransAmt)
saveTransaction(toAccount, fromAccount, toTransAmt)
sentTransactionId
}
/**
* Saves a transaction with amount @amt and counterparty @counterparty for account @account. Returns the id
* of the saved transaction.
*/
private def saveTransaction(account : MappedBankAccount, counterparty : BankAccount, amt : BigDecimal) : Box[TransactionId] = {
val transactionTime = now
val currency = account.currency
//update the balance of the account for which a transaction is being created
val newAccountBalance : Long = account.accountBalance.get + Helper.convertToSmallestCurrencyUnits(amt, account.currency)
account.accountBalance(newAccountBalance).save()
val mappedTransaction = MappedTransaction.create
.bank(account.bankId.value)
.account(account.accountId.value)
.transactionType("sandbox-payment")
.amount(Helper.convertToSmallestCurrencyUnits(amt, currency))
.newAccountBalance(newAccountBalance)
.currency(currency)
.tStartDate(transactionTime)
.tFinishDate(transactionTime)
.description("")
.counterpartyAccountHolder(counterparty.accountHolder)
.counterpartyAccountNumber(counterparty.number)
.counterpartyAccountKind(counterparty.accountType)
.counterpartyBankName(counterparty.bankName)
.counterpartyIban(counterparty.iban.getOrElse(""))
.counterpartyNationalId(counterparty.nationalIdentifier).saveMe
Full(mappedTransaction.theTransactionId)
}
}

View File

@ -9,7 +9,7 @@ object Comments extends SimpleInjector {
val comments = new Inject(buildOne _) {}
def buildOne: Comments = MongoTransactionComments
def buildOne: Comments = MappedComments
}
@ -17,6 +17,7 @@ trait Comments {
def getComments(bankId : BankId, accountId : AccountId, transactionId : TransactionId)(viewId : ViewId) : List[Comment]
def addComment(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(userId: UserId, viewId : ViewId, text : String, datePosted : Date) : Box[Comment]
//TODO: should commentId be unique among all comments, removing the need for the other parameters?
def deleteComment(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(commentId : String) : Box[Unit]
}

View File

@ -0,0 +1,80 @@
package code.metadata.comments
import java.util.{UUID, Date}
import code.model._
import code.model.dataAccess.APIUser
import code.util.MappedUUID
import net.liftweb.common.{Failure, Full, Box}
import net.liftweb.mapper._
import net.liftweb.util.Helpers.tryo
private object MappedComments extends Comments {
override def getComments(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(viewId: ViewId): List[Comment] = {
MappedComment.findAll(
By(MappedComment.bank, bankId.value),
By(MappedComment.account, accountId.value),
By(MappedComment.transaction, transactionId.value),
By(MappedComment.view, viewId.value))
}
override def deleteComment(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(commentId: String): Box[Unit] = {
val deleted = for {
comment <- MappedComment.find(By(MappedComment.bank, bankId.value),
By(MappedComment.account, accountId.value),
By(MappedComment.transaction, transactionId.value),
By(MappedComment.apiId, commentId))
} yield comment.delete_!
deleted match {
case Full(true) => Full(Unit)
case _ => Failure("Could not delete comment")
}
}
override def addComment(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(userId: UserId, viewId: ViewId, text: String, datePosted: Date): Box[Comment] = {
tryo {
MappedComment.create
.bank(bankId.value)
.account(accountId.value)
.transaction(transactionId.value)
.poster(userId.value)
.view(viewId.value)
.text_(text)
.date(datePosted).saveMe
}
}
}
class MappedComment extends Comment with LongKeyedMapper[MappedComment] with IdPK with CreatedUpdated {
def getSingleton = MappedComment
object apiId extends MappedUUID(this)
object text_ extends MappedText(this) {
override def defaultValue = ""
}
object poster extends MappedLongForeignKey(this, APIUser)
object replyTo extends MappedUUID(this) {
override def defaultValue = ""
}
object view extends MappedString(this, 255)
object date extends MappedDateTime(this)
object bank extends MappedString(this, 255)
object account extends MappedString(this, 255)
object transaction extends MappedString(this, 255)
override def id_ : String = apiId.get
override def text: String = text_.get
override def postedBy: Box[User] = poster.obj
override def replyToID: String = replyTo.get
override def viewId: ViewId = ViewId(view.get)
override def datePosted: Date = date.get
}
object MappedComment extends MappedComment with LongKeyedMetaMapper[MappedComment] {
override def dbIndexes = UniqueIndex(apiId) :: Index(view, bank, account, transaction) :: super.dbIndexes
}

View File

@ -1,5 +1,6 @@
package code.metadata.counterparties
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
import code.model.{AccountId, BankId, OtherBankAccountMetadata, OtherBankAccount}
@ -7,7 +8,7 @@ object Counterparties extends SimpleInjector {
val counterparties = new Inject(buildOne _) {}
def buildOne: Counterparties = MongoCounterparties
def buildOne: Counterparties = MapperCounterparties
}
@ -17,4 +18,6 @@ trait Counterparties {
//get all counterparty metadatas for a single OBP account
def getMetadatas(originalPartyBankId: BankId, originalPartyAccountId : AccountId) : List[OtherBankAccountMetadata]
def getMetadata(originalPartyBankId: BankId, originalPartyAccountId : AccountId, counterpartyMetadataId : String) : Box[OtherBankAccountMetadata]
}

View File

@ -0,0 +1,218 @@
package code.metadata.counterparties
import java.util.Date
import code.model._
import code.model.dataAccess.APIUser
import code.util.MappedUUID
import net.liftweb.common.{Box, Full}
import net.liftweb.mapper._
import net.liftweb.util.Helpers.tryo
object MapperCounterparties extends Counterparties {
override def getOrCreateMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, otherParty: OtherBankAccount): OtherBankAccountMetadata = {
/**
* Generates a new alias name that is guaranteed not to collide with any existing public alias names
* for the account in question
*/
def newPublicAliasName(): String = {
import scala.util.Random
val firstAliasAttempt = "ALIAS_" + Random.nextLong().toString.take(6)
/**
* Returns true if @publicAlias is already the name of a public alias within @account
*/
def isDuplicate(publicAlias: String) : Boolean = {
MappedCounterpartyMetadata.find(
By(MappedCounterpartyMetadata.thisAccountBankId, originalPartyBankId.value),
By(MappedCounterpartyMetadata.thisAccountId, originalPartyAccountId.value),
By(MappedCounterpartyMetadata.publicAlias, publicAlias)
).isDefined
}
/**
* Appends things to @publicAlias until it a unique public alias name within @account
*/
def appendUntilUnique(publicAlias: String): String = {
val newAlias = publicAlias + Random.nextLong().toString.take(1)
if (isDuplicate(newAlias)) appendUntilUnique(newAlias)
else newAlias
}
if (isDuplicate(firstAliasAttempt)) appendUntilUnique(firstAliasAttempt)
else firstAliasAttempt
}
//can't find by MappedCounterpartyMetadata.counterpartyId = otherParty.id because in this implementation
//if the metadata doesn't exist, the id field of ther OtherBankAccount is not known yet, and will be empty
def findMappedCounterpartyMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId,
otherParty: OtherBankAccount) : Box[MappedCounterpartyMetadata] = {
MappedCounterpartyMetadata.find(
By(MappedCounterpartyMetadata.thisAccountBankId, originalPartyBankId.value),
By(MappedCounterpartyMetadata.thisAccountId, originalPartyAccountId.value),
By(MappedCounterpartyMetadata.holder, otherParty.label),
By(MappedCounterpartyMetadata.accountNumber, otherParty.number))
}
val existing = findMappedCounterpartyMetadata(originalPartyBankId, originalPartyAccountId, otherParty)
existing match {
case Full(e) => e
case _ => MappedCounterpartyMetadata.create
.thisAccountBankId(originalPartyBankId.value)
.thisAccountId(originalPartyAccountId.value)
.holder(otherParty.label)
.publicAlias(newPublicAliasName())
.accountNumber(otherParty.number).saveMe
}
}
//get all counterparty metadatas for a single OBP account
override def getMetadatas(originalPartyBankId: BankId, originalPartyAccountId: AccountId): List[OtherBankAccountMetadata] = {
MappedCounterpartyMetadata.findAll(
By(MappedCounterpartyMetadata.thisAccountBankId, originalPartyBankId.value),
By(MappedCounterpartyMetadata.thisAccountId, originalPartyAccountId.value)
)
}
override def getMetadata(originalPartyBankId: BankId, originalPartyAccountId: AccountId, counterpartyMetadataId: String): Box[OtherBankAccountMetadata] = {
/**
* This particular implementation requires the metadata id to be the same as the otherParty (OtherBankAccount) id
*/
MappedCounterpartyMetadata.find(
By(MappedCounterpartyMetadata.thisAccountBankId, originalPartyBankId.value),
By(MappedCounterpartyMetadata.thisAccountId, originalPartyAccountId.value),
By(MappedCounterpartyMetadata.counterpartyId, counterpartyMetadataId)
)
}
}
class MappedCounterpartyMetadata extends OtherBankAccountMetadata with LongKeyedMapper[MappedCounterpartyMetadata] with IdPK with CreatedUpdated {
override def getSingleton = MappedCounterpartyMetadata
object counterpartyId extends MappedUUID(this)
//these define the obp account to which this counterparty belongs
object thisAccountBankId extends MappedString(this, 255)
object thisAccountId extends MappedString(this, 255)
//these define the counterparty
object holder extends MappedString(this, 255)
object accountNumber extends MappedString(this, 100)
//this is the counterparty's metadata
object publicAlias extends MappedString(this, 100)
object privateAlias extends MappedString(this, 100)
object moreInfo extends MappedString(this, 100)
object url extends MappedString(this, 100)
object imageUrl extends MappedString(this, 100)
object openCorporatesUrl extends MappedString(this, 100)
object physicalLocation extends MappedLongForeignKey(this, MappedCounterpartyWhereTag)
object corporateLocation extends MappedLongForeignKey(this, MappedCounterpartyWhereTag)
/**
* Evaluates f, and then attempts to save. If no exceptions are thrown and save executes successfully,
* true is returned. If an exception is thrown or if the save fails, false is returned.
* @param f the expression to evaluate (e.g. imageUrl("http://example.com/foo.png")
* @return If saving the model worked after having evaluated f
*/
private def trySave(f : => Any) : Boolean =
tryo{
f
save()
}.getOrElse(false)
private def setWhere(whereTag : Box[MappedCounterpartyWhereTag])
(userId: UserId, datePosted : Date, longitude : Double, latitude : Double) : Box[MappedCounterpartyWhereTag] = {
val toUpdate = whereTag match {
case Full(c) => c
case _ => MappedCounterpartyWhereTag.create
}
tryo{
toUpdate
.user(userId.value)
.date(datePosted)
.geoLongitude(longitude)
.geoLatitude(latitude)
.saveMe
}
}
def setCorporateLocation(userId: UserId, datePosted : Date, longitude : Double, latitude : Double) : Boolean = {
//save where tag
val savedWhere = setWhere(corporateLocation.obj)(userId, datePosted, longitude, latitude)
//set where tag for counterparty
savedWhere.map(location => trySave{corporateLocation(location)}).getOrElse(false)
}
def setPhysicalLocation(userId: UserId, datePosted : Date, longitude : Double, latitude : Double) : Boolean = {
//save where tag
val savedWhere = setWhere(physicalLocation.obj)(userId, datePosted, longitude, latitude)
//set where tag for counterparty
savedWhere.map(location => trySave{physicalLocation(location)}).getOrElse(false)
}
override def metadataId: String = counterpartyId.get
override def getAccountNumber: String = accountNumber.get
override def getHolder: String = holder.get
override def getPublicAlias: String = publicAlias.get
override def getCorporateLocation: Option[GeoTag] =
corporateLocation.obj
override def getOpenCorporatesURL: String = openCorporatesUrl.get
override def getMoreInfo: String = moreInfo.get
override def getPrivateAlias: String = privateAlias.get
override def getImageURL: String = imageUrl.get
override def getPhysicalLocation: Option[GeoTag] =
physicalLocation.obj
override def getUrl: String = url.get
override val addPhysicalLocation: (UserId, Date, Double, Double) => Boolean = setPhysicalLocation _
override val addCorporateLocation: (UserId, Date, Double, Double) => Boolean = setCorporateLocation _
override val addPrivateAlias: (String) => Boolean = (x) =>
trySave{privateAlias(x)}
override val addURL: (String) => Boolean = (x) =>
trySave{url(x)}
override val addMoreInfo: (String) => Boolean = (x) =>
trySave{moreInfo(x)}
override val addPublicAlias: (String) => Boolean = (x) =>
trySave{publicAlias(x)}
override val addOpenCorporatesURL: (String) => Boolean = (x) =>
trySave{openCorporatesUrl(x)}
override val addImageURL: (String) => Boolean = (x) =>
trySave{imageUrl(x)}
override val deleteCorporateLocation = () =>
corporateLocation.obj.map(_.delete_!).getOrElse(false)
override val deletePhysicalLocation = () =>
physicalLocation.obj.map(_.delete_!).getOrElse(false)
}
object MappedCounterpartyMetadata extends MappedCounterpartyMetadata with LongKeyedMetaMapper[MappedCounterpartyMetadata] {
override def dbIndexes =
UniqueIndex(counterpartyId) ::
Index(thisAccountBankId, thisAccountId, holder, accountNumber) ::
super.dbIndexes
}
class MappedCounterpartyWhereTag extends GeoTag with LongKeyedMapper[MappedCounterpartyWhereTag] with IdPK with CreatedUpdated {
def getSingleton = MappedCounterpartyWhereTag
object user extends MappedLongForeignKey(this, APIUser)
object date extends MappedDateTime(this)
//TODO: require these to be valid latitude/longitudes
object geoLatitude extends MappedDouble(this)
object geoLongitude extends MappedDouble(this)
override def postedBy: Box[User] = user.obj
override def datePosted: Date = date.get
override def latitude: Double = geoLatitude.get
override def longitude: Double = geoLongitude.get
}
object MappedCounterpartyWhereTag extends MappedCounterpartyWhereTag with LongKeyedMetaMapper[MappedCounterpartyWhereTag]

View File

@ -1,8 +1,11 @@
package code.metadata.counterparties
import code.model.{AccountId, BankId, OtherBankAccountMetadata, OtherBankAccount}
import net.liftweb.common.Loggable
import net.liftweb.common.{Box, Loggable}
import com.mongodb.QueryBuilder
import net.liftweb.util.Helpers.tryo
import net.liftweb.common.Full
import org.bson.types.ObjectId
object MongoCounterparties extends Counterparties with Loggable {
@ -12,21 +15,25 @@ object MongoCounterparties extends Counterparties with Loggable {
Metadata.findAll(query)
}
def getMetadata(originalPartyBankId: BankId, originalPartyAccountId : AccountId, counterpartyMetadataId : String) : Box[OtherBankAccountMetadata] = {
/**
* This particular implementation requires the metadata id to be the same as the otherParty (OtherBankAccount) id
*/
for {
objId <- tryo { new ObjectId(counterpartyMetadataId) }
query = QueryBuilder.start("originalPartyBankId").is(originalPartyBankId.value).put("originalPartyAccountId").is(originalPartyAccountId.value).
put("_id").is(objId).get()
m <- Metadata.find(query)
} yield m
}
def getOrCreateMetadata(originalPartyBankId: BankId, originalPartyAccountId : AccountId, otherParty : OtherBankAccount) : OtherBankAccountMetadata = {
import net.liftweb.util.Helpers.tryo
import net.liftweb.common.Full
import org.bson.types.ObjectId
/**
* This particular implementation requires the metadata id to be the same as the otherParty (OtherBankAccount) id
*/
val existing = for {
objId <- tryo { new ObjectId(otherParty.id) }
query = QueryBuilder.start("originalPartyBankId").is(originalPartyBankId.value).put("originalPartyAccountId").is(originalPartyAccountId.value).
put("_id").is(objId).get()
m <- Metadata.find(query)
} yield m
val existing = getMetadata(originalPartyBankId, originalPartyAccountId, otherParty.id)
val metadata = existing match {
case Full(m) => m

View File

@ -0,0 +1,53 @@
package code.metadata.narrative
import code.model.{TransactionId, AccountId, BankId}
import net.liftweb.common.Full
import net.liftweb.mapper._
object MappedNarratives extends Narrative {
private def getMappedNarrative(bankId: BankId, accountId: AccountId, transactionId: TransactionId) = {
MappedNarrative.find(By(MappedNarrative.bank, bankId.value),
By(MappedNarrative.account, accountId.value),
By(MappedNarrative.transaction, transactionId.value))
}
override def getNarrative(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(): String = {
val found = getMappedNarrative(bankId: BankId, accountId: AccountId, transactionId: TransactionId)
found.map(_.narrative.get).getOrElse("")
}
override def setNarrative(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(narrative: String): Unit = {
val existing = getMappedNarrative(bankId: BankId, accountId: AccountId, transactionId: TransactionId)
if(narrative.isEmpty) {
//if the new narrative is empty, we can just delete the existing one
existing.foreach(_.delete_!)
} else {
val mappedNarrative = existing match {
case Full(n) => n
case _ => MappedNarrative.create
.bank(bankId.value)
.account(accountId.value)
.transaction(transactionId.value)
}
mappedNarrative.narrative(narrative).save
}
}
}
class MappedNarrative extends LongKeyedMapper[MappedNarrative] with IdPK with CreatedUpdated {
def getSingleton = MappedNarrative
object bank extends MappedString(this, 255)
object account extends MappedString(this, 255)
object transaction extends MappedString(this, 255)
object narrative extends MappedText(this)
}
object MappedNarrative extends MappedNarrative with LongKeyedMetaMapper[MappedNarrative] {
override def dbIndexes = Index(bank, account, transaction) :: super.dbIndexes
}

View File

@ -7,7 +7,7 @@ object Narrative extends SimpleInjector {
val narrative = new Inject(buildOne _) {}
def buildOne: Narrative = MongoTransactionNarrative
def buildOne: Narrative = MappedNarratives
}

View File

@ -0,0 +1,66 @@
package code.metadata.tags
import java.util.Date
import code.model._
import code.model.dataAccess.APIUser
import code.util.MappedUUID
import net.liftweb.common.Box
import net.liftweb.util.Helpers.tryo
import net.liftweb.mapper._
object MappedTags extends Tags {
override def getTags(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(viewId: ViewId): List[TransactionTag] = {
MappedTag.findAll(MappedTag.findQuery(bankId, accountId, transactionId, viewId): _*)
}
override def addTag(bankId: BankId, accountId: AccountId, transactionId: TransactionId)
(userId: UserId, viewId: ViewId, tagText: String, datePosted: Date): Box[TransactionTag] = {
tryo{
MappedTag.create
.bank(bankId.value)
.account(accountId.value)
.transaction(transactionId.value)
.view(viewId.value)
.user(userId.value)
.tag(tagText)
.date(datePosted).saveMe
}
}
override def deleteTag(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(tagId: String): Box[Unit] = {
//tagId is always unique so we actually don't need to use bankId, accountId, or transactionId
MappedTag.find(By(MappedTag.tagId, tagId)).map(_.delete_!).map(x => ()) //TODO: this should return something more useful than Box[Unit]
}
}
class MappedTag extends TransactionTag with LongKeyedMapper[MappedTag] with IdPK with CreatedUpdated {
def getSingleton = MappedTag
object bank extends MappedString(this, 255)
object account extends MappedString(this, 255)
object transaction extends MappedString(this, 255)
object view extends MappedString(this, 255)
object tagId extends MappedUUID(this)
object user extends MappedLongForeignKey(this, APIUser)
object tag extends MappedString(this, 255)
object date extends MappedDateTime(this)
override def id_ : String = tagId.get
override def postedBy: Box[User] = user.obj
override def value: String = tag.get
override def viewId: ViewId = ViewId(view.get)
override def datePosted: Date = date.get
}
object MappedTag extends MappedTag with LongKeyedMetaMapper[MappedTag] {
override def dbIndexes = Index(bank, account, transaction, view) :: UniqueIndex(tagId) :: super.dbIndexes
def findQuery(bankId: BankId, accountId: AccountId, transactionId: TransactionId, viewId: ViewId) =
By(MappedTag.bank, bankId.value) ::
By(MappedTag.account, accountId.value) ::
By(MappedTag.transaction, transactionId.value) ::
By(MappedTag.view, viewId.value) :: Nil
}

View File

@ -9,7 +9,7 @@ object Tags extends SimpleInjector {
val tags = new Inject(buildOne _) {}
def buildOne: Tags = MongoTransactionTags
def buildOne: Tags = MappedTags
}
@ -17,6 +17,7 @@ trait Tags {
def getTags(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(viewId : ViewId) : List[TransactionTag]
def addTag(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(userId: UserId, viewId : ViewId, tagText : String, datePosted : Date) : Box[TransactionTag]
//TODO: viewId? should tagId always be unique -> in that case bankId, accountId, and transactionId would not be required
def deleteTag(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(tagId : String) : Box[Unit]
}

View File

@ -0,0 +1,73 @@
package code.metadata.transactionimages
import java.net.URL
import java.util.Date
import code.model._
import code.model.dataAccess.APIUser
import code.util.MappedUUID
import net.liftweb.common.Box
import net.liftweb.mapper._
import net.liftweb.util.Helpers.tryo
object MapperTransactionImages extends TransactionImages {
override def getImagesForTransaction(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(viewId: ViewId): List[TransactionImage] = {
MappedTransactionImage.findAll(
By(MappedTransactionImage.bank, bankId.value),
By(MappedTransactionImage.account, accountId.value),
By(MappedTransactionImage.transaction, transactionId.value),
By(MappedTransactionImage.view, viewId.value)
)
}
override def deleteTransactionImage(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(imageId: String): Box[Unit] = {
//imageId is unique, so we don't need bankId, accountId, and transactionId
//TODO: this should return something more useful than Box[Unit]
MappedTransactionImage.find(By(MappedTransactionImage.imageId, imageId)).map(_.delete_!).map(x => ())
}
override def addTransactionImage(bankId: BankId, accountId: AccountId, transactionId: TransactionId)
(userId: UserId, viewId: ViewId, description: String, datePosted: Date, imageURL: URL): Box[TransactionImage] = {
tryo {
MappedTransactionImage.create
.bank(bankId.value)
.account(accountId.value)
.transaction(transactionId.value)
.view(viewId.value)
.user(userId.value)
.imageDescription(description)
.url(imageURL.toString)
.date(datePosted).saveMe
}
}
}
class MappedTransactionImage extends TransactionImage with LongKeyedMapper[MappedTransactionImage] with IdPK with CreatedUpdated {
def getSingleton = MappedTransactionImage
object bank extends MappedString(this, 255)
object account extends MappedString(this, 255)
object transaction extends MappedString(this, 255)
object view extends MappedString(this, 255)
object imageId extends MappedUUID(this)
object user extends MappedLongForeignKey(this, APIUser)
object date extends MappedDateTime(this)
object url extends MappedText(this)
object imageDescription extends MappedText(this)
override def id_ : String = imageId.get
override def postedBy: Box[User] = user.obj
override def description: String = imageDescription.get
override def imageUrl: URL = tryo {new URL(url.get)} getOrElse MappedTransactionImage.notFoundUrl
override def viewId: ViewId = ViewId(view.get)
override def datePosted: Date = date.get
}
object MappedTransactionImage extends MappedTransactionImage with LongKeyedMetaMapper[MappedTransactionImage] {
override def dbIndexes = Index(bank, account, transaction, view) :: UniqueIndex(imageId) :: super.dbIndexes
val notFoundUrl = new URL("http://example.com/notfound.png") //TODO: Make this image exist?
}

View File

@ -10,7 +10,7 @@ object TransactionImages extends SimpleInjector {
val transactionImages = new Inject(buildOne _) {}
def buildOne: TransactionImages = MongoTransactionImages
def buildOne: TransactionImages = MapperTransactionImages
}

View File

@ -0,0 +1,79 @@
package code.metadata.wheretags
import java.util.Date
import code.model._
import code.model.dataAccess.APIUser
import net.liftweb.util.Helpers.tryo
import net.liftweb.common.Box
import net.liftweb.mapper._
object MapperWhereTags extends WhereTags {
private def findMappedWhereTag(bankId: BankId, accountId: AccountId, transactionId: TransactionId, viewId : ViewId) = {
MappedWhereTag.find(
By(MappedWhereTag.bank, bankId.value),
By(MappedWhereTag.account, accountId.value),
By(MappedWhereTag.transaction, transactionId.value),
By(MappedWhereTag.view, viewId.value))
}
override def addWhereTag(bankId: BankId, accountId: AccountId, transactionId: TransactionId)
(userId: UserId, viewId: ViewId, datePosted: Date, longitude: Double, latitude: Double): Boolean = {
val found = findMappedWhereTag(bankId, accountId, transactionId, viewId)
val toUpdate = found.getOrElse {
MappedWhereTag.create
.bank(bankId.value)
.account(accountId.value)
.transaction(transactionId.value)
.view(viewId.value)
}
toUpdate
.user(userId.value)
.date(datePosted)
.geoLatitude(latitude)
.geoLongitude(longitude)
tryo{toUpdate.saveMe}.isDefined
}
override def deleteWhereTag(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(viewId: ViewId): Boolean = {
val found = findMappedWhereTag(bankId, accountId, transactionId, viewId)
found.map(_.delete_!).getOrElse(false)
}
override def getWhereTagForTransaction(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(viewId: ViewId): Option[GeoTag] = {
findMappedWhereTag(bankId: BankId, accountId: AccountId, transactionId: TransactionId, viewId: ViewId)
}
}
class MappedWhereTag extends GeoTag with LongKeyedMapper[MappedWhereTag] with IdPK with CreatedUpdated {
def getSingleton = MappedWhereTag
object bank extends MappedString(this, 255)
object account extends MappedString(this, 255)
object transaction extends MappedString(this, 255)
object view extends MappedString(this, 255)
object user extends MappedLongForeignKey(this, APIUser)
object date extends MappedDateTime(this)
//TODO: require these to be valid latitude/longitudes
object geoLatitude extends MappedDouble(this)
object geoLongitude extends MappedDouble(this)
override def datePosted: Date = date.get
override def postedBy: Box[User] = user.obj
override def latitude: Double = geoLatitude.get
override def longitude: Double = geoLongitude.get
}
object MappedWhereTag extends MappedWhereTag with LongKeyedMetaMapper[MappedWhereTag] {
override def dbIndexes = Index(bank, account, transaction, view) :: super.dbIndexes
}

View File

@ -8,7 +8,7 @@ object WhereTags extends SimpleInjector {
val whereTags = new Inject(buildOne _) {}
def buildOne: WhereTags = MongoTransactionWhereTags
def buildOne: WhereTags = MapperWhereTags
}

View File

@ -7,7 +7,7 @@ object APIMetrics extends SimpleInjector {
val apiMetrics = new Inject(buildOne _) {}
def buildOne: APIMetrics = MongoAPIMetric
def buildOne: APIMetrics = MappedMetrics
/**
* Returns a Date which is at the start of the day of the date

View File

@ -0,0 +1,39 @@
package code.metrics
import java.util.Date
import net.liftweb.mapper._
object MappedMetrics extends APIMetrics {
override def saveMetric(url: String, date: Date): Unit = {
MappedMetric.create.url(url).date(date).save
}
override def getAllGroupedByDay(): Map[Date, List[APIMetric]] = {
//TODO: do this all at the db level using an actual group by query
MappedMetric.findAll.groupBy(APIMetrics.getMetricDay)
}
override def getAllGroupedByUrl(): Map[String, List[APIMetric]] = {
//TODO: do this all at the db level using an actual group by query
MappedMetric.findAll.groupBy(_.getUrl())
}
}
class MappedMetric extends APIMetric with LongKeyedMapper[MappedMetric] with IdPK {
override def getSingleton = MappedMetric
object url extends MappedText(this)
object date extends MappedDateTime(this)
override def getUrl(): String = url.get
override def getDate(): Date = date.get
}
object MappedMetric extends MappedMetric with LongKeyedMetaMapper[MappedMetric] {
override def dbIndexes = Index(url) :: Index(date) :: super.dbIndexes
}

View File

@ -164,7 +164,9 @@ class AccountOwner(
case class BankAccountUID(bankId : BankId, accountId : AccountId)
trait BankAccount extends Loggable {
trait BankAccount {
@transient protected val log = Logger(this.getClass)
@deprecated
def uuid : String
@ -192,7 +194,6 @@ trait BankAccount extends Loggable {
final def owners: Set[User] = {
val accountHolders = Connector.connector.vend.getAccountHolders(bankId, accountId)
if(accountHolders.isEmpty) {
//account holders are not all set up in the db yet, so we might not get any back.
//In this case, we just use the previous behaviour, which did not return very much information at all
@ -215,7 +216,7 @@ trait BankAccount extends Loggable {
user match {
case Full(u) => u.permittedViews(this)
case _ =>{
logger.info("no user was found in the permittedViews")
log.info("no user was found in the permittedViews")
publicViews
}
}
@ -353,7 +354,7 @@ trait BankAccount extends Loggable {
val view = Views.views.vend.createView(this, v)
if(view.isDefined) {
logger.info("user: " + userDoingTheCreate.idGivenByProvider + " at provider " + userDoingTheCreate.provider + " created view: " + view.get +
log.info("user: " + userDoingTheCreate.idGivenByProvider + " at provider " + userDoingTheCreate.provider + " created view: " + view.get +
" for account " + accountId + "at bank " + bankId)
}
@ -368,7 +369,7 @@ trait BankAccount extends Loggable {
val view = Views.views.vend.updateView(this, viewId, v)
if(view.isDefined) {
logger.info("user: " + userDoingTheUpdate.idGivenByProvider + " at provider " + userDoingTheUpdate.provider + " updated view: " + view.get +
log.info("user: " + userDoingTheUpdate.idGivenByProvider + " at provider " + userDoingTheUpdate.provider + " updated view: " + view.get +
" for account " + accountId + "at bank " + bankId)
}
@ -383,7 +384,7 @@ trait BankAccount extends Loggable {
val deleted = Views.views.vend.removeView(viewId, this)
if(deleted.isDefined) {
logger.info("user: " + userDoingTheRemove.idGivenByProvider + " at provider " + userDoingTheRemove.provider + " deleted view: " + viewId +
log.info("user: " + userDoingTheRemove.idGivenByProvider + " at provider " + userDoingTheRemove.provider + " deleted view: " + viewId +
" for account " + accountId + "at bank " + bankId)
}
@ -442,7 +443,6 @@ trait BankAccount extends Loggable {
else
viewNotAllowed(view)
@deprecated(Helper.deprecatedJsonGenerationMessage)
final def overviewJson(user: Box[User]): JObject = {
val views = permittedViews(user)

View File

@ -0,0 +1,128 @@
package code.model
import java.util.UUID
import code.bankconnectors.Connector
import code.metadata.counterparties.Counterparties
import code.util.{Helper, MappedUUID}
import net.liftweb.common.{Logger, Box}
import net.liftweb.mapper._
class MappedTransaction extends LongKeyedMapper[MappedTransaction] with IdPK with CreatedUpdated with TransactionUUID {
private val logger = Logger(classOf[MappedTransaction])
def getSingleton = MappedTransaction
object bank extends MappedString(this, 255)
object account extends MappedString(this, 255)
object transactionId extends MappedString(this, 255) {
override def defaultValue = UUID.randomUUID().toString
}
//TODO: review the need for this
object transactionUUID extends MappedUUID(this)
object transactionType extends MappedString(this, 20)
//amount/new balance use the smallest unit of currency! e.g. cents, yen, pence, øre, etc.
object amount extends MappedLong(this)
object newAccountBalance extends MappedLong(this)
object currency extends MappedString(this, 10)
object tStartDate extends MappedDateTime(this)
object tFinishDate extends MappedDateTime(this)
object description extends MappedText(this)
object counterpartyAccountNumber extends MappedString(this, 60)
object counterpartyAccountHolder extends MappedString(this, 100)
//still unclear exactly how what this is defined to mean
object counterpartyNationalId extends MappedString(this, 40)
//this should eventually be calculated using counterpartyNationalId
object counterpartyBankName extends MappedString(this, 100)
//this should eventually either generate counterpartyAccountNumber or be generated
object counterpartyIban extends MappedString(this, 100)
object counterpartyAccountKind extends MappedString(this, 40)
override def theTransactionId = TransactionId(transactionId.get)
override def theAccountId = AccountId(account.get)
override def theBankId = BankId(bank.get)
def getCounterpartyIban() = {
val i = counterpartyIban.get
if(i.isEmpty) None else Some(i)
}
def toTransaction(account: BankAccount): Option[Transaction] = {
val tBankId = theBankId
val tAccId = theAccountId
if (tBankId != account.bankId || tAccId != account.accountId) {
logger.warn("Attempted to convert MappedTransaction to Transaction using unrelated existing BankAccount object")
None
} else {
val label = {
val d = description.get
if (d.isEmpty) None else Some(d)
}
val transactionCurrency = currency.get
val amt = Helper.smallestCurrencyUnitToBigDecimal(amount.get, transactionCurrency)
val newBalance = Helper.smallestCurrencyUnitToBigDecimal(newAccountBalance.get, transactionCurrency)
def createOtherBankAccount(alreadyFoundMetadata : Option[OtherBankAccountMetadata]) = {
new OtherBankAccount(
id = alreadyFoundMetadata.map(_.metadataId).getOrElse(""),
label = counterpartyAccountHolder.get,
nationalIdentifier = counterpartyNationalId.get,
swift_bic = None, //TODO: need to add this to the json/model
iban = getCounterpartyIban(),
number = counterpartyAccountNumber.get,
bankName = counterpartyBankName.get,
kind = counterpartyAccountKind.get,
originalPartyBankId = theBankId,
originalPartyAccountId = theAccountId,
alreadyFoundMetadata = alreadyFoundMetadata
)
}
//it's a bit confusing what's going on here, as normally metadata should be automatically generated if
//it doesn't exist when an OtherBankAccount object is created. The issue here is that for legacy reasons
//otherAccount ids are metadata ids, so the metadata needs to exist before we created the OtherBankAccount
//so that we know what id to give it.
//creates a dummy OtherBankAccount without an OtherBankAccountMetadata, which results in one being generated (in OtherBankAccount init)
val dummyOtherBankAccount = createOtherBankAccount(None)
//and create the proper OtherBankAccount with the correct "id" attribute set to the metadataId of the OtherBankAccountMetadata object
//note: as we are passing in the OtherBankAccountMetadata we don't incur another db call to get it in OtherBankAccount init
val otherAccount = createOtherBankAccount(Some(dummyOtherBankAccount.metadata))
Some(new Transaction(
transactionUUID.get,
theTransactionId,
account,
otherAccount,
transactionType.get,
amt,
transactionCurrency,
label,
tStartDate.get,
tFinishDate.get,
newBalance))
}
}
def toTransaction : Option[Transaction] = {
for {
acc <- Connector.connector.vend.getBankAccount(theBankId, theAccountId)
transaction <- toTransaction(acc)
} yield transaction
}
}
object MappedTransaction extends MappedTransaction with LongKeyedMetaMapper[MappedTransaction] {
override def dbIndexes = UniqueIndex(transactionId, bank, account) :: Index(bank, account) :: super.dbIndexes
}

View File

@ -0,0 +1,29 @@
package code.model.dataAccess
import code.model.{BankId, Bank}
import net.liftweb.mapper._
class MappedBank extends Bank with LongKeyedMapper[MappedBank] with IdPK with CreatedUpdated {
def getSingleton = MappedBank
object permalink extends MappedString(this, 255)
object fullBankName extends MappedString(this, 255)
object shortBankName extends MappedString(this, 100)
object logoURL extends MappedString(this, 255)
object websiteURL extends MappedString(this, 255)
object national_identifier extends MappedString(this, 255)
override def bankId: BankId = BankId(permalink.get)
override def fullName: String = fullBankName.get
override def shortName: String = shortBankName.get
override def logoUrl: String = logoURL.get
override def websiteUrl: String = websiteURL.get
override def nationalIdentifier: String = national_identifier.get
}
object MappedBank extends MappedBank with LongKeyedMetaMapper[MappedBank] {
override def dbIndexes = Index(permalink) :: super.dbIndexes
def findByBankId(bankId : BankId) =
MappedBank.find(By(MappedBank.permalink, bankId.value))
}

View File

@ -0,0 +1,54 @@
package code.model.dataAccess
import java.math.MathContext
import code.model._
import code.util.Helper
import net.liftweb.mapper._
class MappedBankAccount extends BankAccount with LongKeyedMapper[MappedBankAccount] with IdPK with CreatedUpdated {
override def getSingleton = MappedBankAccount
object bank extends MappedString(this, 255)
object theAccountId extends MappedString(this, 255)
object accountIban extends MappedString(this, 50)
object accountCurrency extends MappedString(this, 10)
object accountSwiftBic extends MappedString(this, 50)
object accountNumber extends MappedString(this, 50)
@deprecated
object holder extends MappedString(this, 100)
//this is the smallest unit of currency! e.g. cents, yen, pence, øre, etc.
object accountBalance extends MappedLong(this)
object accountName extends MappedString(this, 255)
object kind extends MappedString(this, 40)
object accountLabel extends MappedString(this, 255)
//the last time this account was updated via hbci
object lastUpdate extends MappedDateTime(this)
override def accountId: AccountId = AccountId(theAccountId.get)
override def iban: Option[String] = {
val i = accountIban.get
if(i.isEmpty) None else Some(i)
}
override def bankId: BankId = BankId(bank.get)
override def currency: String = accountCurrency.get
override def swift_bic: Option[String] = {
val sb = accountSwiftBic.get
if(sb.isEmpty) None else Some(sb)
}
override def number: String = accountNumber.get
override def balance: BigDecimal = Helper.smallestCurrencyUnitToBigDecimal(accountBalance.get, currency)
override def name: String = accountName.get
override def accountType: String = kind.get
override def label: String = accountLabel.get
override def accountHolder: String = holder.get
}
object MappedBankAccount extends MappedBankAccount with LongKeyedMetaMapper[MappedBankAccount] {
override def dbIndexes = UniqueIndex(bank, theAccountId) :: super.dbIndexes
}

View File

@ -0,0 +1,100 @@
package code.sandbox
import code.metadata.counterparties.{MappedCounterpartyMetadata}
import code.model.dataAccess.{MappedBankAccount, MappedBank}
import code.model.{MappedTransaction, AccountId, BankId}
import code.util.Helper.convertToSmallestCurrencyUnits
import net.liftweb.common.{Full, Failure, Box}
import net.liftweb.mapper.Mapper
import net.liftweb.util.Helpers._
case class MappedSaveable[T <: Mapper[_]](value : T) extends Saveable[T] {
def save() = value.save()
}
object LocalMappedConnectorDataImport extends OBPDataImport with CreateViewImpls with CreateOBPUsers {
type BankType = MappedBank
type AccountType = MappedBankAccount
type MetadataType = MappedCounterpartyMetadata
type TransactionType = MappedTransaction
protected def createSaveableBanks(data : List[SandboxBankImport]) : Box[List[Saveable[BankType]]] = {
val mappedBanks = data.map(bank => {
MappedBank.create
.permalink(bank.id)
.fullBankName(bank.full_name)
.shortBankName(bank.short_name)
.logoURL(bank.logo)
.websiteURL(bank.website)
})
val validationErrors = mappedBanks.flatMap(_.validate)
if(validationErrors.nonEmpty) {
Failure(s"Errors: ${validationErrors.map(_.msg)}")
} else {
Full(mappedBanks.map(MappedSaveable(_)))
}
}
protected def createSaveableAccount(acc : SandboxAccountImport, banks : List[BankType]) : Box[Saveable[AccountType]] = {
val mappedAccount = for {
balance <- tryo{BigDecimal(acc.balance.amount)} ?~ s"Invalid balance: ${acc.balance.amount}"
currency = acc.balance.currency
} yield {
MappedBankAccount.create
.theAccountId(acc.id)
.bank(acc.bank)
.accountLabel(acc.label)
.accountNumber(acc.number)
.kind(acc.`type`)
.accountIban(acc.IBAN)
.accountCurrency(currency)
.accountBalance(convertToSmallestCurrencyUnits(balance, currency))
}
val validationErrors = mappedAccount.map(_.validate).getOrElse(Nil)
if(validationErrors.nonEmpty) {
Failure(s"Errors: ${validationErrors.map(_.msg)}")
} else {
mappedAccount.map(MappedSaveable(_))
}
}
override protected def createSaveableTransaction(t : SandboxTransactionImport, createdBanks : List[BankType], createdAccounts : List[AccountType]):
Box[Saveable[TransactionType]] = {
for {
createdAcc <- Box(createdAccounts.find(acc => acc.accountId == AccountId(t.this_account.id) && acc.bankId == BankId(t.this_account.bank))) ?~ {
logger.warn("Data import failed because a created account was not found for a transaction when it should have been")
"Server Error"
}
currency = createdAcc.currency
newBalanceValueAsBigDecimal <- tryo{BigDecimal(t.details.new_balance)} ?~ s"Invalid new balance: ${t.details.new_balance}"
tValueAsBigDecimal <- tryo{BigDecimal(t.details.value)} ?~ s"Invalid transaction value: ${t.details.value}"
postedDate <- tryo{dateFormat.parse(t.details.posted)} ?~ s"Invalid date format: ${t.details.posted}. Expected pattern $datePattern"
completedDate <-tryo{dateFormat.parse(t.details.completed)} ?~ s"Invalid date format: ${t.details.completed}. Expected pattern $datePattern"
} yield {
val mappedTransaction = MappedTransaction.create
.bank(t.this_account.bank)
.account(t.this_account.id)
.transactionId(t.id)
.transactionType(t.details.`type`)
.amount(convertToSmallestCurrencyUnits(tValueAsBigDecimal, currency))
.newAccountBalance(convertToSmallestCurrencyUnits(newBalanceValueAsBigDecimal, currency))
.currency(currency)
.tStartDate(postedDate)
.tFinishDate(completedDate)
.description(t.details.description)
.counterpartyAccountHolder(t.counterparty.flatMap(_.name).getOrElse(""))
.counterpartyAccountNumber(t.counterparty.flatMap(_.account_number).getOrElse(""))
MappedSaveable(mappedTransaction)
}
}
}

View File

@ -15,7 +15,7 @@ object OBPDataImport extends SimpleInjector {
val importer = new Inject(buildOne _) {}
def buildOne : OBPDataImport = LocalConnectorDataImport
def buildOne : OBPDataImport = LocalMappedConnectorDataImport
}

View File

@ -58,4 +58,40 @@ object Helper{
}
val deprecatedJsonGenerationMessage = "json generation handled elsewhere as it changes from api version to api version"
/**
* Converts a number representing the smallest unit of a currency into a big decimal formatted according to the rules of
* that currency. E.g. JPY: 1000 units (yen) => 1000, EUR: 1000 units (cents) => 10.00
*/
def smallestCurrencyUnitToBigDecimal(units : Long, currencyCode : String) = {
BigDecimal(units, currencyDecimalPlaces(currencyCode))
}
/**
* Returns the number of decimal places a currency has. E.g. "EUR" -> 2, "JPY" -> 0
* @param currencyCode
* @return
*/
def currencyDecimalPlaces(currencyCode : String) = {
//this data was sourced from Wikipedia, so it might not all be correct,
//and some banking systems may still retain different units (e.g. CZK?)
//notable it doesn't cover non-traditional currencies (e.g. cryptocurrencies)
currencyCode match {
//TODO: handle MRO and MGA, which are non-decimal
case "CZK" | "JPY" | "KRW" => 0
case "KWD" | "OMR" => 3
case _ => 2
}
}
/**
* E.g.
* amount: BigDecimal("12.45"), currencyCode : "EUR" => 1245
* amount: BigDecimal("9034"), currencyCode : "JPY" => 9034
*/
def convertToSmallestCurrencyUnits(amount : BigDecimal, currencyCode : String) : Long = {
val decimalPlaces = Helper.currencyDecimalPlaces(currencyCode)
(amount * BigDecimal("10").pow(decimalPlaces)).toLong
}
}

View File

@ -1,4 +1,4 @@
package code.api
//Set the default connector setup here by extending it
trait DefaultConnectorTestSetup extends LocalConnectorTestSetup
trait DefaultConnectorTestSetup extends LocalMappedConnectorTestSetup

View File

@ -0,0 +1,72 @@
package code.api
import java.util.Date
import bootstrap.liftweb.ToSchemify
import code.model.dataAccess._
import code.model._
import net.liftweb.mapper.MetaMapper
import net.liftweb.util.Helpers._
import scala.util.Random
trait LocalMappedConnectorTestSetup extends LocalConnectorTestSetup {
override protected def createBank(id : String) : Bank = {
MappedBank.create
.fullBankName(randomString(5))
.shortBankName(randomString(5))
.permalink(id)
.national_identifier(randomString(5)).saveMe
}
override protected def createAccount(bankId: BankId, accountId : AccountId, currency : String) : BankAccount = {
MappedBankAccount.create
.bank(bankId.value)
.theAccountId(accountId.value)
.accountCurrency(currency)
.accountBalance(10000)
.holder(randomString(4))
.accountNumber(randomString(4))
.accountLabel(randomString(4)).saveMe
}
override protected def createTransaction(account: BankAccount, startDate: Date, finishDate: Date) = {
//ugly
val mappedBankAccount = account.asInstanceOf[MappedBankAccount]
val accountBalanceBefore = mappedBankAccount.accountBalance.get
val transactionAmount = Random.nextInt(1000).toLong
val accountBalanceAfter = accountBalanceBefore + transactionAmount
mappedBankAccount.accountBalance(accountBalanceAfter).save
MappedTransaction.create
.bank(account.bankId.value)
.account(account.accountId.value)
.transactionType(randomString(5))
.tStartDate(startDate)
.tFinishDate(finishDate)
.currency(account.currency)
.amount(transactionAmount)
.newAccountBalance(accountBalanceAfter)
.description(randomString(5))
.counterpartyAccountHolder(randomString(5))
.counterpartyAccountKind(randomString(5))
.counterpartyAccountNumber(randomString(5))
.counterpartyBankName(randomString(5))
.counterpartyIban(randomString(5))
.counterpartyNationalId(randomString(5))
.saveMe
.toTransaction.get
}
override protected def wipeTestData() = {
//returns true if the model should not be wiped after each test
def exclusion(m : MetaMapper[_]) = {
m == Nonce || m == Token || m == Consumer || m == OBPUser || m == APIUser
}
//empty the relational db tables after each test
ToSchemify.models.filterNot(exclusion).foreach(_.bulkDelete_!!())
}
}

View File

@ -56,6 +56,7 @@ trait ServerSetupWithTestData extends ServerSetup with DefaultConnectorTestSetup
override def beforeEach() = {
super.beforeEach()
implicit val dateFormats = net.liftweb.json.DefaultFormats
//create fake data for the tests
//fake banks

View File

@ -4,7 +4,6 @@ import java.text.SimpleDateFormat
import java.util.Date
import code.api.test.ServerSetup
import net.liftweb.mongodb._
class MetricsTest extends ServerSetup with WipeMetrics {
@ -106,7 +105,6 @@ class MetricsTest extends ServerSetup with WipeMetrics {
*/
trait WipeMetrics {
def wipeAllExistingMetrics() = {
//just drops all mongodb databases
MongoDB.getDb(DefaultMongoIdentifier).foreach(_.dropDatabase())
MappedMetric.bulkDelete_!!()
}
}