Removed BankFinder code after moving them to a separate project
This commit is contained in:
parent
523e538ecb
commit
bdd28f2587
|
@ -1,87 +0,0 @@
|
|||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "maven-publish"
|
||||
}
|
||||
|
||||
|
||||
ext.artifactName = "bank-finder"
|
||||
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
compilations.all {
|
||||
kotlinOptions.jvmTarget = '1.8'
|
||||
}
|
||||
withJava()
|
||||
testRuns["test"].executionTask.configure {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
|
||||
// js(BOTH) {
|
||||
// browser {
|
||||
// commonWebpackConfig {
|
||||
// cssSupport.enabled = true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
ios {
|
||||
binaries {
|
||||
framework {
|
||||
baseName = "BankFinder"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// def hostOs = System.getProperty("os.name")
|
||||
// def isMingwX64 = hostOs.startsWith("Windows")
|
||||
// def nativeTarget
|
||||
// if (hostOs == "Mac OS X") nativeTarget = macosX64('native') { binaries.executable() }
|
||||
// else if (hostOs == "Linux") nativeTarget = linuxX64("native") { binaries.executable() }
|
||||
// else if (isMingwX64) nativeTarget = mingwX64("native") { binaries.executable() }
|
||||
// else throw new GradleException("Host OS is not supported in Kotlin/Native.")
|
||||
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":common")
|
||||
}
|
||||
}
|
||||
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation kotlin("test-common")
|
||||
implementation kotlin("test-annotations-common")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
jvmMain {
|
||||
dependencies {
|
||||
api "net.dankito.utils:java-utils:$javaUtilsVersion"
|
||||
}
|
||||
}
|
||||
|
||||
jvmTest {
|
||||
dependencies {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
iosMain {
|
||||
dependencies {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
iosTest {
|
||||
dependencies {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
|
||||
abstract class BankFinderBase : IBankFinder {
|
||||
|
||||
protected abstract fun findBankByNameBankCodeOrCityForNonEmptyQuery(query: String): List<BankInfo>
|
||||
|
||||
abstract fun searchBankByBic(bic: String): BankInfo?
|
||||
|
||||
|
||||
protected val cachedBanksByBic = mutableMapOf<String, BankInfo?>()
|
||||
|
||||
|
||||
override fun findBankByNameBankCodeOrCity(query: String?): List<BankInfo> {
|
||||
if (query.isNullOrBlank()) {
|
||||
return getBankList()
|
||||
}
|
||||
|
||||
if (query.toIntOrNull() != null) { // if query is an integer, then it can only be an bank code, but not a bank name or city
|
||||
return findBankByBankCode(query)
|
||||
}
|
||||
|
||||
return findBankByNameBankCodeOrCityForNonEmptyQuery(query)
|
||||
}
|
||||
|
||||
|
||||
override fun findBankByBic(bic: String): BankInfo? {
|
||||
cachedBanksByBic[bic]?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val bankForBic = searchBankByBic(bic)
|
||||
|
||||
cachedBanksByBic[bic] = bankForBic
|
||||
|
||||
return bankForBic
|
||||
}
|
||||
|
||||
|
||||
protected open fun loadBankList(): List<BankInfo> {
|
||||
return BankListDeserializer().loadBankList()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
|
||||
open class BankInfo(
|
||||
open var name: String,
|
||||
open val bankCode: String,
|
||||
open val bic: String,
|
||||
open val postalCode: String,
|
||||
open val city: String,
|
||||
open val pinTanAddress: String?,
|
||||
open val pinTanVersion: String?,
|
||||
open var branchesInOtherCities: List<String> = listOf() // to have only one entry per bank its branches's cities are now stored in branchesInOtherCities so that branches' cities are still searchable
|
||||
) {
|
||||
|
||||
protected constructor() : this("", "", "", "", "", null, "") // for object deserializers
|
||||
|
||||
|
||||
open val supportsPinTan: Boolean
|
||||
get() = pinTanAddress.isNullOrEmpty() == false
|
||||
|
||||
open val supportsFinTs3_0: Boolean
|
||||
get() = pinTanVersion == "FinTS V3.0"
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$bankCode $name $city"
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
|
||||
expect class BankListDeserializer() {
|
||||
|
||||
fun loadBankList(): List<BankInfo>
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
|
||||
open class DetailedBankInfo(
|
||||
name: String,
|
||||
bankCode: String,
|
||||
bic: String,
|
||||
postalCode: String,
|
||||
city: String,
|
||||
pinTanAddress: String?,
|
||||
pinTanVersion: String?,
|
||||
open val checksumMethod: String,
|
||||
open val oldBankCode: String?
|
||||
) : BankInfo(name, bankCode, bic, postalCode, city, pinTanAddress, pinTanVersion) {
|
||||
|
||||
protected constructor() : this("", "", "", "", "", null, "", "", null) // for object deserializers
|
||||
|
||||
|
||||
open val isBankCodeDeleted: Boolean
|
||||
get() = oldBankCode != null // TODO: this is not in all cases true, there are banks with new bank code which haven't been deleted
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
|
||||
interface IBankFinder {
|
||||
|
||||
fun getBankList(): List<BankInfo>
|
||||
|
||||
fun findBankByBankCode(query: String): List<BankInfo>
|
||||
|
||||
fun findBankByNameBankCodeOrCity(query: String?): List<BankInfo>
|
||||
|
||||
fun findBankByBic(bic: String): BankInfo?
|
||||
|
||||
fun preloadBankList()
|
||||
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
|
||||
open class InMemoryBankFinder() : BankFinderBase(), IBankFinder {
|
||||
|
||||
constructor(bankList: List<BankInfo>) : this() {
|
||||
this.bankListField = bankList
|
||||
}
|
||||
|
||||
|
||||
protected var bankListField: List<BankInfo>? = null
|
||||
|
||||
|
||||
override fun findBankByBankCode(query: String): List<BankInfo> {
|
||||
if (query.isEmpty()) {
|
||||
return getBankList()
|
||||
}
|
||||
|
||||
return getBankList().filter { it.bankCode.startsWith(query) }
|
||||
}
|
||||
|
||||
override fun findBankByNameBankCodeOrCityForNonEmptyQuery(query: String): List<BankInfo> {
|
||||
val queryPartsLowerCase = query.toLowerCase().split(" ", "-")
|
||||
|
||||
return getBankList().filter { bankInfo ->
|
||||
checkIfAllQueryPartsMatchBankNameBankCodeOrCity(queryPartsLowerCase, bankInfo)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun checkIfAllQueryPartsMatchBankNameBankCodeOrCity(queryPartsLowerCase: List<String>, bankInfo: BankInfo): Boolean {
|
||||
for (queryPartLowerCase in queryPartsLowerCase) {
|
||||
if (checkIfQueryMatchesBankNameBankCodeOrCity(bankInfo, queryPartLowerCase) == false) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
protected open fun checkIfQueryMatchesBankNameBankCodeOrCity(bankInfo: BankInfo, queryLowerCase: String): Boolean {
|
||||
return bankInfo.name.toLowerCase().contains(queryLowerCase)
|
||||
|| bankInfo.bankCode.startsWith(queryLowerCase)
|
||||
|| bankInfo.city.toLowerCase().startsWith(queryLowerCase)
|
||||
|| bankInfo.branchesInOtherCities.any { it.toLowerCase().startsWith(queryLowerCase) }
|
||||
}
|
||||
|
||||
|
||||
override fun searchBankByBic(bic: String): BankInfo? {
|
||||
return getBankList().firstOrNull { it.bic == bic }
|
||||
}
|
||||
|
||||
|
||||
override fun preloadBankList() {
|
||||
findBankByBankCode("")
|
||||
}
|
||||
|
||||
|
||||
override fun getBankList(): List<BankInfo> {
|
||||
bankListField?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val bankList = loadBankList()
|
||||
|
||||
this.bankListField = bankList
|
||||
|
||||
return bankList
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,73 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
abstract class BankFinderTestBase {
|
||||
|
||||
protected abstract fun createBankFinder(): IBankFinder
|
||||
|
||||
|
||||
protected val underTest = createBankFinder()
|
||||
|
||||
|
||||
@Test
|
||||
fun findBankByBankCode_10000000() {
|
||||
|
||||
// when
|
||||
val result = underTest.findBankByBankCode("10000000")
|
||||
|
||||
// then
|
||||
assertEquals(1, result.size)
|
||||
assertEquals("Bundesbank", result[0].name)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun findBankByNameBankCodeOrCity_Starnberg_MultipleResults() {
|
||||
|
||||
// when
|
||||
val result = underTest.findBankByNameBankCodeOrCity("starnberg")
|
||||
|
||||
// then
|
||||
assertEquals(71, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun findBankByNameBankCodeOrCity_mizUh_SingleResult() {
|
||||
|
||||
// when
|
||||
val result = underTest.findBankByNameBankCodeOrCity("mizUh")
|
||||
|
||||
// then
|
||||
assertEquals(1, result.size)
|
||||
|
||||
assertEquals("Mizuho Bank Filiale Düsseldorf", result.first().name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun findBankByNameBankCodeOrCity_MultiPartsQuery_BerlinSparkas() {
|
||||
|
||||
// when
|
||||
val result = underTest.findBankByNameBankCodeOrCity("Berlin Sparkas")
|
||||
|
||||
// then
|
||||
assertEquals(3, result.size)
|
||||
|
||||
assertEquals("Landesbank Berlin - Berliner Sparkasse", result.first().name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun findBankByNameBankCodeOrCity_MultiPartsQuery_SparkasBerlin() {
|
||||
|
||||
// when
|
||||
val result = underTest.findBankByNameBankCodeOrCity("Sparkas Berlin")
|
||||
|
||||
// then
|
||||
assertEquals(3, result.size)
|
||||
|
||||
assertEquals("Landesbank Berlin - Berliner Sparkasse", result.first().name)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
|
||||
class InMemoryBankFinderTest : BankFinderTestBase() {
|
||||
|
||||
override fun createBankFinder(): IBankFinder {
|
||||
return InMemoryBankFinder()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
import net.dankito.utils.multiplatform.getString
|
||||
import net.dankito.utils.multiplatform.getStringOrEmpty
|
||||
import net.dankito.utils.multiplatform.toList
|
||||
import platform.Foundation.*
|
||||
|
||||
|
||||
actual class BankListDeserializer {
|
||||
|
||||
actual fun loadBankList(): List<BankInfo> {
|
||||
val bundle = NSBundle.mainBundle
|
||||
|
||||
bundle.pathForResource("BankList", "json")?.let { bankListJsonPath ->
|
||||
NSData.dataWithContentsOfFile(bankListJsonPath, NSDataReadingMappedIfSafe, null)?.let { bankListData ->
|
||||
NSJSONSerialization.JSONObjectWithData(bankListData, NSJSONReadingMutableContainers, null)?.let { bankListJson ->
|
||||
(bankListJson as? NSArray)?.let {
|
||||
return bankListJson.toList<NSDictionary>().map {
|
||||
mapToBankInfo(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return listOf()
|
||||
}
|
||||
|
||||
private fun mapToBankInfo(bankInfoDict: NSDictionary): BankInfo {
|
||||
return BankInfo(
|
||||
bankInfoDict.getStringOrEmpty("name"),
|
||||
bankInfoDict.getStringOrEmpty("bankCode"),
|
||||
bankInfoDict.getStringOrEmpty("bic"),
|
||||
bankInfoDict.getStringOrEmpty("postalCode"),
|
||||
bankInfoDict.getStringOrEmpty("city"),
|
||||
bankInfoDict.getString("pinTanAddress"),
|
||||
bankInfoDict.getString("pinTanVersion"),
|
||||
bankInfoDict.mutableArrayValueForKey("branchesInOtherCities").mapNotNull { it as? String }
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
import net.dankito.utils.multiplatform.log.LoggerFactory
|
||||
import net.dankito.utils.serialization.JacksonJsonSerializer
|
||||
|
||||
|
||||
actual class BankListDeserializer {
|
||||
|
||||
companion object {
|
||||
const val BankListFileName = "BankList.json"
|
||||
|
||||
private val log = LoggerFactory.getLogger(BankListDeserializer::class)
|
||||
}
|
||||
|
||||
|
||||
actual fun loadBankList(): List<BankInfo> {
|
||||
try {
|
||||
val bankListString = readBankListFile()
|
||||
|
||||
JacksonJsonSerializer().deserializeList(bankListString, BankInfo::class.java)?.let {
|
||||
return it
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Could not load bank list" }
|
||||
}
|
||||
|
||||
return listOf()
|
||||
}
|
||||
|
||||
fun readBankListFile(): String {
|
||||
val inputStream = BankFinderBase::class.java.classLoader.getResourceAsStream(BankListFileName)
|
||||
|
||||
return inputStream.bufferedReader().readText()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.dankito.banking.bankfinder">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
</manifest>
|
|
@ -1,29 +0,0 @@
|
|||
apply plugin: 'java-library'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
|
||||
|
||||
dependencies {
|
||||
api project(':BankFinder')
|
||||
// why can't Gradle find BankFinder? If the same applies to you, publish BankFinder to Maven Local first and uncomment this line:
|
||||
// api "net.dankito.banking:BankFinder-jvm:$version"
|
||||
|
||||
implementation 'org.docx4j:docx4j-JAXB-ReferenceImpl:8.1.3'
|
||||
|
||||
|
||||
testImplementation "junit:junit:4.12"
|
||||
testImplementation "org.assertj:assertj-core:$assertJVersion"
|
||||
|
||||
testImplementation "ch.qos.logback:logback-core:$logbackVersion"
|
||||
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator
|
||||
|
||||
import net.dankito.banking.bankfinder.BankInfo
|
||||
import net.dankito.banking.banklistcreator.parser.DeutscheKreditwirtschaftBankListParser
|
||||
import net.dankito.banking.banklistcreator.prettifier.BankListPrettifier
|
||||
import net.dankito.banking.banklistcreator.prettifier.BankListPrettifierOption
|
||||
import net.dankito.utils.serialization.JacksonJsonSerializer
|
||||
import java.io.File
|
||||
|
||||
|
||||
open class BankListCreator @JvmOverloads constructor(
|
||||
protected open val parser: DeutscheKreditwirtschaftBankListParser = DeutscheKreditwirtschaftBankListParser(),
|
||||
protected open val prettifier: BankListPrettifier = BankListPrettifier()
|
||||
) {
|
||||
|
||||
open fun createBankListFromDeutscheKreditwirtschaftXlsxFile(deutscheKreditwirtschaftXlsxFile: File,
|
||||
bankListOutputFile: File) {
|
||||
|
||||
val banks = parser.parse(deutscheKreditwirtschaftXlsxFile)
|
||||
|
||||
saveBankListAsJson(banks, bankListOutputFile)
|
||||
}
|
||||
|
||||
open fun createDetailedAndPrettifiedBankListFromDeutscheKreditwirtschaftXlsxFile(
|
||||
deutscheKreditwirtschaftXlsxFile: File, detailedBankListOutputFile: File,
|
||||
prettifiedBankListOutputFile: File, prettifyOptions: List<BankListPrettifierOption>) {
|
||||
|
||||
val allBanks = parser.parse(deutscheKreditwirtschaftXlsxFile)
|
||||
|
||||
saveBankListAsJson(allBanks, detailedBankListOutputFile)
|
||||
|
||||
val mappedBanks = allBanks.map { BankInfo(it.name, it.bankCode, it.bic, it.postalCode, it.city, it.pinTanAddress, it.pinTanVersion) }
|
||||
val prettifiedBanks = prettifier.prettify(mappedBanks, prettifyOptions)
|
||||
saveBankListAsJson(prettifiedBanks, prettifiedBankListOutputFile)
|
||||
}
|
||||
|
||||
open fun saveBankListAsJson(banks: List<BankInfo>, bankListOutputFile: File) {
|
||||
JacksonJsonSerializer().serializeObject(banks, bankListOutputFile)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,280 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator.parser
|
||||
|
||||
import net.dankito.banking.bankfinder.DetailedBankInfo
|
||||
import net.dankito.banking.banklistcreator.parser.model.BankCodeListEntry
|
||||
import net.dankito.banking.banklistcreator.parser.model.ServerAddressesListEntry
|
||||
import org.docx4j.openpackaging.packages.SpreadsheetMLPackage
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.xlsx4j.org.apache.poi.ss.usermodel.DataFormatter
|
||||
import org.xlsx4j.sml.Cell
|
||||
import org.xlsx4j.sml.Row
|
||||
import org.xlsx4j.sml.SheetData
|
||||
import java.io.File
|
||||
|
||||
|
||||
/**
|
||||
* Parses the list of German banks from Deutsche Kreditwirtschaft you can retrieve by registering here:
|
||||
* https://www.hbci-zka.de/register/hersteller.htm
|
||||
*/
|
||||
open class DeutscheKreditwirtschaftBankListParser {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(DeutscheKreditwirtschaftBankListParser::class.java)
|
||||
}
|
||||
|
||||
|
||||
open fun parse(bankListFile: File): List<DetailedBankInfo> {
|
||||
val xlsxPkg = SpreadsheetMLPackage.load(bankListFile)
|
||||
|
||||
val workbookPart = xlsxPkg.getWorkbookPart()
|
||||
val sheets = workbookPart.contents.sheets.sheet
|
||||
val formatter = DataFormatter()
|
||||
|
||||
var serverAddressesList = listOf<ServerAddressesListEntry>()
|
||||
var bankCodesList = listOf<BankCodeListEntry>()
|
||||
|
||||
for (index in 0 until sheets.size) {
|
||||
log.info("\nParsing sheet ${sheets[index].name}:\n")
|
||||
val sheet = workbookPart.getWorksheet(index)
|
||||
val workSheetData = sheet.contents.sheetData
|
||||
|
||||
if (isListWithFinTsServerAddresses(workSheetData, formatter)) {
|
||||
serverAddressesList = parseListWithFinTsServerAddresses(workSheetData, formatter)
|
||||
}
|
||||
else if (isBankCodeList(workSheetData, formatter)) {
|
||||
bankCodesList = parseBankCodeList(workSheetData, formatter)
|
||||
}
|
||||
}
|
||||
|
||||
return mapBankCodeAndServerAddressesList(bankCodesList, serverAddressesList)
|
||||
}
|
||||
|
||||
protected open fun mapBankCodeAndServerAddressesList(banks: List<BankCodeListEntry>,
|
||||
serverAddresses: List<ServerAddressesListEntry>): List<DetailedBankInfo> {
|
||||
|
||||
val serverAddressesByBankCode = mutableMapOf<String, MutableList<ServerAddressesListEntry>>()
|
||||
serverAddresses.forEach { serverAddress ->
|
||||
if (serverAddressesByBankCode.containsKey(serverAddress.bankCode) == false) {
|
||||
serverAddressesByBankCode.put(serverAddress.bankCode, mutableListOf(serverAddress))
|
||||
}
|
||||
else {
|
||||
serverAddressesByBankCode[serverAddress.bankCode]!!.add(serverAddress)
|
||||
}
|
||||
}
|
||||
|
||||
return banks.map { mapToBankInfo(it, serverAddressesByBankCode as Map<String, List<ServerAddressesListEntry>>) }
|
||||
}
|
||||
|
||||
protected open fun mapToBankInfo(bank: BankCodeListEntry,
|
||||
serverAddressesByBankCode: Map<String, List<ServerAddressesListEntry>>): DetailedBankInfo {
|
||||
|
||||
val serverAddress = findServerAddress(bank, serverAddressesByBankCode)
|
||||
|
||||
return DetailedBankInfo(
|
||||
bank.bankName,
|
||||
bank.bankCode,
|
||||
bank.bic,
|
||||
bank.postalCode,
|
||||
bank.city,
|
||||
serverAddress?.pinTanAddress,
|
||||
serverAddress?.pinTanVersion,
|
||||
bank.checksumMethod,
|
||||
bank.oldBankCode
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun findServerAddress(bankCode: BankCodeListEntry,
|
||||
serverAddressesByBankCode: Map<String, List<ServerAddressesListEntry>>
|
||||
): ServerAddressesListEntry? {
|
||||
|
||||
serverAddressesByBankCode[bankCode.bankCode]?.let { serverAddresses ->
|
||||
serverAddresses.firstOrNull { it.city == bankCode.city }?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
return serverAddresses[0]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
protected open fun isListWithFinTsServerAddresses(workSheetData: SheetData, formatter: DataFormatter): Boolean {
|
||||
return hasHeaders(workSheetData, formatter, listOf("BLZ", "PIN/TAN-Zugang URL"))
|
||||
}
|
||||
|
||||
protected open fun parseListWithFinTsServerAddresses(workSheetData: SheetData, formatter: DataFormatter):
|
||||
List<ServerAddressesListEntry> {
|
||||
|
||||
val entries = mutableListOf<ServerAddressesListEntry>()
|
||||
|
||||
val headerRow = workSheetData.row[0]
|
||||
val headerNames = headerRow.c.map { getCellText(it, formatter) }
|
||||
|
||||
val bankNameColumnIndex = headerNames.indexOf("Institut")
|
||||
val bankCodeColumnIndex = headerNames.indexOf("BLZ")
|
||||
val bicColumnIndex = headerNames.indexOf("BIC")
|
||||
val cityColumnIndex = headerNames.indexOf("Ort")
|
||||
val pinTanAddressColumnIndex = headerNames.indexOf("PIN/TAN-Zugang URL")
|
||||
val pinTanVersionColumnIndex = headerNames.indexOf("Version")
|
||||
|
||||
for (row in workSheetData.row.subList(1, workSheetData.row.size)) { // removes header row
|
||||
parseToServerAddressesListEntry(row, formatter, bankNameColumnIndex, bankCodeColumnIndex, bicColumnIndex,
|
||||
cityColumnIndex, pinTanAddressColumnIndex, pinTanVersionColumnIndex)?.let { entry ->
|
||||
entries.add(entry)
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
protected open fun parseToServerAddressesListEntry(row: Row, formatter: DataFormatter, bankNameColumnIndex: Int,
|
||||
bankCodeColumnIndex: Int, bicColumnIndex: Int, cityColumnIndex: Int,
|
||||
pinTanAddressColumnIndex: Int, pinTanVersionColumnIndex: Int):
|
||||
ServerAddressesListEntry? {
|
||||
|
||||
try {
|
||||
if (row.c.size < pinTanVersionColumnIndex) { // a row with only the index number in first column, doesn't contain enough information for us to parse it to an ServerAddressesListEntry
|
||||
return null
|
||||
}
|
||||
|
||||
val bankCode = getCellText(row, bankCodeColumnIndex, formatter)
|
||||
|
||||
if (bankCode.isNotEmpty()) { // filter out empty rows
|
||||
|
||||
return ServerAddressesListEntry(
|
||||
getCellText(row, bankNameColumnIndex, formatter),
|
||||
bankCode,
|
||||
getCellText(row, bicColumnIndex, formatter),
|
||||
getCellText(row, cityColumnIndex, formatter),
|
||||
getCellText(row, pinTanAddressColumnIndex, formatter),
|
||||
getCellText(row, pinTanVersionColumnIndex, formatter)
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Could not parse row ${getRowAsString(row, formatter)} to BankCodeListEntry", e)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
protected open fun isBankCodeList(workSheetData: SheetData, formatter: DataFormatter): Boolean {
|
||||
return hasHeaders(workSheetData, formatter, listOf("Bankleitzahl", "Merkmal"))
|
||||
}
|
||||
|
||||
protected open fun parseBankCodeList(workSheetData: SheetData, formatter: DataFormatter): List<BankCodeListEntry> {
|
||||
val entries = mutableListOf<BankCodeListEntry>()
|
||||
|
||||
val headerRow = workSheetData.row[0]
|
||||
val headerNames = headerRow.c.map { getCellText(it, formatter) }
|
||||
|
||||
val bankNameColumnIndex = headerNames.indexOf("Bezeichnung")
|
||||
val bankCodeColumnIndex = headerNames.indexOf("Bankleitzahl")
|
||||
val bicColumnIndex = headerNames.indexOf("BIC")
|
||||
val postalCodeColumnIndex = headerNames.indexOf("PLZ")
|
||||
val cityColumnIndex = headerNames.indexOf("Ort")
|
||||
val checksumMethodColumnIndex = headerNames.indexOf("Prüfziffer-berechnungs-methode")
|
||||
val bankCodeDeletedColumnIndex = headerNames.indexOf("Bankleitzahl-löschung")
|
||||
val newBankCodeColumnIndex = headerNames.indexOf("Nachfolge-Bankleitzahl")
|
||||
|
||||
var lastParsedEntry: BankCodeListEntry? = null
|
||||
|
||||
for (row in workSheetData.row.subList(1, workSheetData.row.size)) {
|
||||
parseToBankCodeListEntry(row, formatter, bankNameColumnIndex, bankCodeColumnIndex, bicColumnIndex,
|
||||
postalCodeColumnIndex, cityColumnIndex, checksumMethodColumnIndex, bankCodeDeletedColumnIndex,
|
||||
newBankCodeColumnIndex)?.let { entry ->
|
||||
// if the following banks have the same BIC, the BIC is only given for the first bank -> get BIC from previous bank
|
||||
if (entry.bic.isEmpty() &&
|
||||
(entry.bankCode == lastParsedEntry?.bankCode || entry.bankCode == lastParsedEntry?.oldBankCode)) {
|
||||
entry.bic = lastParsedEntry?.bic!!
|
||||
}
|
||||
|
||||
entries.add(entry)
|
||||
|
||||
lastParsedEntry = entry
|
||||
}
|
||||
}
|
||||
|
||||
updateDeletedBanks(entries)
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
protected open fun parseToBankCodeListEntry(row: Row, formatter: DataFormatter, bankNameColumnIndex: Int,
|
||||
bankCodeColumnIndex: Int, bicColumnIndex: Int, postalCodeColumnIndex: Int,
|
||||
cityColumnIndex: Int, checksumMethodColumnIndex: Int,
|
||||
bankCodeDeletedColumnIndex: Int, newBankCodeColumnIndex: Int): BankCodeListEntry? {
|
||||
|
||||
try {
|
||||
val bankCode = getCellText(row, bankCodeColumnIndex, formatter)
|
||||
|
||||
if (bankCode.isNotEmpty()) { // filter out empty rows
|
||||
var newBankCode: String? = null
|
||||
val isBankCodeDeleted = getCellText(row, bankCodeDeletedColumnIndex, formatter) == "1"
|
||||
val newBankCodeCellText = getCellText(row, newBankCodeColumnIndex, formatter)
|
||||
if (isBankCodeDeleted && newBankCodeCellText.isNotEmpty() && newBankCodeCellText != "00000000") {
|
||||
newBankCode = newBankCodeCellText
|
||||
}
|
||||
|
||||
return BankCodeListEntry(
|
||||
getCellText(row, bankNameColumnIndex, formatter),
|
||||
newBankCode ?: bankCode,
|
||||
getCellText(row, bicColumnIndex, formatter),
|
||||
getCellText(row, postalCodeColumnIndex, formatter),
|
||||
getCellText(row, cityColumnIndex, formatter),
|
||||
getCellText(row, checksumMethodColumnIndex, formatter),
|
||||
if (newBankCode != null) bankCode else newBankCode
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Could not parse row ${getRowAsString(row, formatter)} to BankCodeListEntry", e)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Deleted banks may not have a BIC. This method fixes this
|
||||
*/
|
||||
protected open fun updateDeletedBanks(banks: MutableList<BankCodeListEntry>) {
|
||||
val banksByCode = banks.associateBy { it.bankCode }
|
||||
|
||||
val deletedBanks = banks.filter { it.isBankCodeDeleted }
|
||||
|
||||
for (deletedBank in deletedBanks) {
|
||||
banksByCode[deletedBank.bankCode]?.let { newBank ->
|
||||
deletedBank.bic = newBank.bic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected open fun hasHeaders(workSheetData: SheetData, formatter: DataFormatter, headerNames: List<String>): Boolean {
|
||||
if (workSheetData.row.isNotEmpty()) {
|
||||
val headerRow = workSheetData.row[0]
|
||||
|
||||
val rowHeaderNames = headerRow.c.map { getCellText(it, formatter) }
|
||||
|
||||
return rowHeaderNames.containsAll(headerNames)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
protected open fun getCellText(row: Row, columnIndex: Int, formatter: DataFormatter): String {
|
||||
return getCellText(row.c[columnIndex], formatter)
|
||||
}
|
||||
|
||||
protected open fun getCellText(cell: Cell, formatter: DataFormatter): String {
|
||||
if (cell.f != null) { // cell with formular
|
||||
return cell.v
|
||||
}
|
||||
return formatter.formatCellValue(cell)
|
||||
}
|
||||
|
||||
protected open fun getRowAsString(row: Row, formatter: DataFormatter): String {
|
||||
return row.c.joinToString("\t|", "|\t", "\t|") { getCellText(it, formatter) }
|
||||
}
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator.parser.model
|
||||
|
||||
|
||||
open class BankCodeListEntry(
|
||||
open val bankName: String,
|
||||
open val bankCode: String,
|
||||
open var bic: String, // TODO: make val again
|
||||
open val postalCode: String,
|
||||
open val city: String,
|
||||
open val checksumMethod: String,
|
||||
open val oldBankCode: String?
|
||||
) {
|
||||
|
||||
open val isBankCodeDeleted: Boolean
|
||||
get() = oldBankCode != null
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$bankCode $bankName ($bic, $city)"
|
||||
}
|
||||
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator.parser.model
|
||||
|
||||
|
||||
open class ServerAddressesListEntry(
|
||||
open val bankName: String,
|
||||
open val bankCode: String,
|
||||
open val bic: String,
|
||||
open val city: String,
|
||||
open val pinTanAddress: String?,
|
||||
open val pinTanVersion: String?
|
||||
) {
|
||||
|
||||
override fun toString(): String {
|
||||
return "$bankCode $bankName ($city, $pinTanAddress)"
|
||||
}
|
||||
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator.prettifier
|
||||
|
||||
import net.dankito.banking.bankfinder.BankInfo
|
||||
|
||||
|
||||
open class BankListPrettifier {
|
||||
|
||||
open fun prettify(banks: List<BankInfo>, options: List<BankListPrettifierOption>): List<BankInfo> {
|
||||
var prettifiedList = banks
|
||||
|
||||
if (options.contains(BankListPrettifierOption.RemoveInstitutionInternalBank)) {
|
||||
prettifiedList = removeInstitutionInternalBank(prettifiedList)
|
||||
}
|
||||
|
||||
if (options.contains(BankListPrettifierOption.RemoveBanksWithSameBankCodeAndPostalCode)) {
|
||||
prettifiedList = removeBanksWithSameBankCodeAndPostalCode(prettifiedList)
|
||||
}
|
||||
|
||||
if (options.contains(BankListPrettifierOption.RemoveBanksWithSameBankCodeAndCity)) {
|
||||
prettifiedList = removeBanksWithSameBankCodeAndCity(prettifiedList)
|
||||
}
|
||||
|
||||
if (options.contains(BankListPrettifierOption.MergeAllBranchesOfBankIntoOne)) {
|
||||
prettifiedList = mergeAllBranchesOfBankIntoOne(prettifiedList)
|
||||
}
|
||||
|
||||
if (options.contains(BankListPrettifierOption.MapBankNamesToWellKnownNames)) {
|
||||
prettifiedList = mapBankNamesToWellKnownNames(prettifiedList)
|
||||
}
|
||||
|
||||
return prettifiedList
|
||||
}
|
||||
|
||||
|
||||
open fun mapBankNamesToWellKnownNames(banks: List<BankInfo>): List<BankInfo> {
|
||||
banks.forEach { bank ->
|
||||
when {
|
||||
bank.name.contains("Postbank") -> bank.name = "Postbank"
|
||||
bank.name.startsWith("Deutsche Kreditbank") -> bank.name = "DKB (Deutsche Kreditbank)"
|
||||
bank.name.startsWith("Deutsche Bank") || bank.name.startsWith("DB Privat- und Firmenkundenbank") -> bank.name = "Deutsche Bank"
|
||||
bank.name.startsWith("Commerzbank") -> bank.name = "Commerzbank" // TODO: keep "vormals Dresdner Bank"?
|
||||
}
|
||||
}
|
||||
|
||||
return banks
|
||||
}
|
||||
|
||||
open fun removeInstitutionInternalBank(banks: List<BankInfo>): List<BankInfo> {
|
||||
return banks.filterNot {
|
||||
(it.name.contains("intern", true) && it.name.contains("international", true) == false)
|
||||
|| it.name.startsWith("UniCredit Bank - HVB Settlement")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open fun removeBanksWithSameBankCodeAndPostalCode(banks: List<BankInfo>): List<BankInfo> {
|
||||
val groupedByBankCodeAndPostalCode = banks.groupBy { it.bankCode + "_" + it.postalCode }
|
||||
|
||||
val banksToRemove = groupedByBankCodeAndPostalCode.values.flatMap { banksWithSameBankCodeAndPostalCode ->
|
||||
if (banksWithSameBankCodeAndPostalCode.size > 1) {
|
||||
val bankWithBestName = findBankWithShortestName(banksWithSameBankCodeAndPostalCode)
|
||||
val banksWithoutBankWithBestName = banksWithSameBankCodeAndPostalCode.toMutableList()
|
||||
|
||||
banksWithoutBankWithBestName.remove(bankWithBestName)
|
||||
return@flatMap banksWithoutBankWithBestName
|
||||
}
|
||||
|
||||
listOf<BankInfo>()
|
||||
}
|
||||
|
||||
val prettifiedList = banks.toMutableList()
|
||||
prettifiedList.removeAll(banksToRemove)
|
||||
|
||||
return prettifiedList
|
||||
}
|
||||
|
||||
// TODO: there are many banks like "Volksbank Nordmünsterland -alt-" and "Volksbank Nordmünsterland (Gf P2)" where each time "-alt-" gets selected
|
||||
protected open fun findBankWithShortestName(banks: List<BankInfo>): BankInfo {
|
||||
var bankWithBestName = banks.first()
|
||||
|
||||
for (i in 1 until banks.size) {
|
||||
val bank = banks[i]
|
||||
if (bank.name.length < bankWithBestName.name.length) {
|
||||
bankWithBestName = bank
|
||||
}
|
||||
}
|
||||
|
||||
return bankWithBestName
|
||||
}
|
||||
|
||||
|
||||
open fun removeBanksWithSameBankCodeAndCity(banks: List<BankInfo>): List<BankInfo> {
|
||||
val groupedByBankCodeAndCity = banks.groupBy { it.bankCode + "_" + it.city }
|
||||
|
||||
val banksToRemove = groupedByBankCodeAndCity.values.flatMap { banksWithSameBankCodeAndCity ->
|
||||
if (banksWithSameBankCodeAndCity.size > 1) {
|
||||
val banksToRemove = banksWithSameBankCodeAndCity.toMutableList()
|
||||
banksToRemove.remove(banksWithSameBankCodeAndCity.first())
|
||||
|
||||
return@flatMap banksToRemove
|
||||
}
|
||||
|
||||
listOf<BankInfo>()
|
||||
}
|
||||
|
||||
val prettifiedList = banks.toMutableList()
|
||||
prettifiedList.removeAll(banksToRemove)
|
||||
|
||||
return prettifiedList
|
||||
}
|
||||
|
||||
open fun mergeAllBranchesOfBankIntoOne(banks: List<BankInfo>): List<BankInfo> {
|
||||
val groupedByBankCodeAndPinTanAddress = banks.groupBy { it.bankCode + "_" + it.pinTanAddress }
|
||||
|
||||
val banksToRemove = groupedByBankCodeAndPinTanAddress.values.flatMap { banksWithSameBankCodeAndPinTanAddress ->
|
||||
if (banksWithSameBankCodeAndPinTanAddress.size > 1) {
|
||||
val banksToRemove = banksWithSameBankCodeAndPinTanAddress.toMutableList()
|
||||
val mainBranch = findMainBranch(banksWithSameBankCodeAndPinTanAddress)
|
||||
|
||||
banksToRemove.remove(banksWithSameBankCodeAndPinTanAddress.first())
|
||||
|
||||
mainBranch.branchesInOtherCities = banksToRemove.map { it.city }.toSet().toList()
|
||||
|
||||
return@flatMap banksToRemove
|
||||
}
|
||||
|
||||
listOf<BankInfo>()
|
||||
}
|
||||
|
||||
val prettifiedList = banks.toMutableList()
|
||||
prettifiedList.removeAll(banksToRemove)
|
||||
|
||||
return prettifiedList
|
||||
}
|
||||
|
||||
private fun findMainBranch(banksWithSameBankCodeAndPinTanAddress: List<BankInfo>): BankInfo {
|
||||
// banksWithSameBankCodeAndPinTanAddress.forEach { bank ->
|
||||
// if (bank.name.contains(bank.city)) {
|
||||
// println("${bank.name}: Picked ${bank.city} from ${banksWithSameBankCodeAndPinTanAddress.map { it.city }}")
|
||||
// return bank
|
||||
// }
|
||||
// }
|
||||
|
||||
// in most cases this turned out to be the best one
|
||||
return banksWithSameBankCodeAndPinTanAddress.first()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator.prettifier
|
||||
|
||||
|
||||
enum class BankListPrettifierOption {
|
||||
|
||||
/**
|
||||
* Maps e.g. 'DB Privat- und Firmenkundenbank ...' to 'Deutsche Bank' or 'Deutsche Kreditbank' to 'DKB'
|
||||
*/
|
||||
MapBankNamesToWellKnownNames,
|
||||
|
||||
/**
|
||||
* Often the same bank is contained multiple times but with a '(Gf 2)' etc. suffix in name. Filters these out that have the same bank code and postal code.
|
||||
*/
|
||||
RemoveBanksWithSameBankCodeAndPostalCode,
|
||||
|
||||
/**
|
||||
* Often the same bank is contained multiple times but with a '(Gf 2)' etc. suffix in name. Filters these out that have the same bank code and city.
|
||||
*/
|
||||
RemoveBanksWithSameBankCodeAndCity,
|
||||
|
||||
/**
|
||||
* By default each branch has its own BankInfo. With this option only main branch keeps its BankInfo and all branches get added to branchesInOtherCities.
|
||||
*/
|
||||
MergeAllBranchesOfBankIntoOne,
|
||||
|
||||
/**
|
||||
* Removes banks like 'Deutsche Bank (Gf intern)' or 'UniCredit Bank - HVB Settlement EAC01' etc.
|
||||
*/
|
||||
RemoveInstitutionInternalBank
|
||||
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator
|
||||
|
||||
import net.dankito.banking.banklistcreator.prettifier.BankListPrettifierOption
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Ignore
|
||||
import java.io.File
|
||||
|
||||
|
||||
@Ignore // not an automatic test, set your path to your bank list file in TestConfig.DeutscheKreditwirtschaftBankListXlsxFile
|
||||
class BankListCreatorTest {
|
||||
|
||||
private val underTest = BankListCreator()
|
||||
|
||||
|
||||
@Test
|
||||
fun createBankListJson() {
|
||||
// TODO: set path to bank list file from Deutsche Kreditwirtschaft in TestConfig.DeutscheKreditwirtschaftBankListXlsxFile
|
||||
underTest.createDetailedAndPrettifiedBankListFromDeutscheKreditwirtschaftXlsxFile(
|
||||
TestConfig.DeutscheKreditwirtschaftBankListXlsxFile,
|
||||
File("../BankFinder/src/commonMain/resources/DetailedBankList.json"),
|
||||
File("../BankFinder/src/commonMain/resources/BankList.json"),
|
||||
BankListPrettifierOption.values().toList()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator
|
||||
|
||||
import java.io.File
|
||||
|
||||
|
||||
open class TestConfig {
|
||||
|
||||
companion object {
|
||||
|
||||
// TODO: set path to bank list file from Deutsche Kreditwirtschaft here
|
||||
val DeutscheKreditwirtschaftBankListXlsxFile = File("")
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator.parser
|
||||
|
||||
import net.dankito.banking.banklistcreator.TestConfig
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
|
||||
@Ignore // not an automatic test, set your path to your bank list file in TestConfig.DeutscheKreditwirtschaftBankListXlsxFile
|
||||
class DeutscheKreditwirtschaftBankListParserTest {
|
||||
|
||||
private val underTest = DeutscheKreditwirtschaftBankListParser()
|
||||
|
||||
|
||||
@Test
|
||||
fun parse() {
|
||||
|
||||
// when
|
||||
// TODO: set path to bank list file from Deutsche Kreditwirtschaft in TestConfig.DeutscheKreditwirtschaftBankListXlsxFile
|
||||
val result = underTest.parse(TestConfig.DeutscheKreditwirtschaftBankListXlsxFile)
|
||||
|
||||
// then
|
||||
assertThat(result).hasSize(16282)
|
||||
|
||||
result.forEach { bankInfo ->
|
||||
assertThat(bankInfo.name).isNotEmpty()
|
||||
assertThat(bankInfo.bankCode).isNotEmpty()
|
||||
// assertThat(bankInfo.bic).isNotEmpty() // TODO: is there a way to find BICs for all banks?
|
||||
assertThat(bankInfo.postalCode).isNotEmpty()
|
||||
assertThat(bankInfo.city).isNotEmpty()
|
||||
assertThat(bankInfo.checksumMethod).isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package net.dankito.banking.banklistcreator.prettifier
|
||||
|
||||
import net.dankito.banking.banklistcreator.TestConfig
|
||||
import net.dankito.banking.banklistcreator.parser.DeutscheKreditwirtschaftBankListParser
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
|
||||
@Ignore // not an automatic test, set your path to your bank list file in TestConfig.DeutscheKreditwirtschaftBankListXlsxFile
|
||||
class BankListPrettifierTest {
|
||||
|
||||
private val underTest = BankListPrettifier()
|
||||
|
||||
private val allBanks = DeutscheKreditwirtschaftBankListParser().parse(TestConfig.DeutscheKreditwirtschaftBankListXlsxFile)
|
||||
|
||||
|
||||
@Test
|
||||
fun mapBankNamesToWellKnownNames() {
|
||||
|
||||
// when
|
||||
val result = underTest.mapBankNamesToWellKnownNames(allBanks)
|
||||
|
||||
// then
|
||||
assertThat(result).hasSize(allBanks.size)
|
||||
|
||||
val resultingBanksNames = result.map { it.name }
|
||||
|
||||
assertThat(resultingBanksNames.filter { it.contains("DB Privat- und Firmenkundenbank") }).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeBanksWithSameBankCodeAndPostalCode() {
|
||||
|
||||
// when
|
||||
val result = underTest.removeBanksWithSameBankCodeAndPostalCode(allBanks)
|
||||
|
||||
// then
|
||||
assertThat(result).hasSizeLessThan(allBanks.size)
|
||||
|
||||
val resultingBanksNames = result.map { it.name }
|
||||
|
||||
assertThat(resultingBanksNames.filter { it.contains("PSD Bank München (Gf P2)") }).isEmpty()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>DEBUG</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- Insert the current time formatted as "yyyyMMdd'T'HHmmss" under
|
||||
the key "bySecond" into the logger context. This value will be
|
||||
available to all subsequent configuration elements. -->
|
||||
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
|
||||
|
||||
<!-- Raise log level here if you don't want to see noisy log output -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
|
@ -1,26 +0,0 @@
|
|||
apply plugin: 'java-library'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
|
||||
ext.artifactName = "lucene-bank-finder"
|
||||
|
||||
sourceCompatibility = "7"
|
||||
targetCompatibility = "7"
|
||||
|
||||
|
||||
dependencies {
|
||||
api project(":BankFinder")
|
||||
|
||||
implementation "net.dankito.search:lucene-4-utils:$luceneUtilsVersion"
|
||||
|
||||
|
||||
// TODO: enable to reference BankFinder tests again to run LuceneBankFinderTest
|
||||
// testImplementation project(path: ':BankFinder', configuration: 'testOutput')
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter:$junit5Version"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
|
||||
|
||||
testImplementation "org.assertj:assertj-core:$assertJVersion"
|
||||
testImplementation "org.mockito:mockito-core:$mockitoVersion"
|
||||
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
package net.dankito.banking.bankfinder
|
||||
|
||||
import net.dankito.utils.hashing.HashAlgorithm
|
||||
import net.dankito.utils.hashing.HashService
|
||||
import net.dankito.utils.io.FileUtils
|
||||
import net.dankito.utils.lucene.index.DocumentsWriter
|
||||
import net.dankito.utils.lucene.index.FieldBuilder
|
||||
import net.dankito.utils.lucene.mapper.PropertyDescription
|
||||
import net.dankito.utils.lucene.mapper.PropertyType
|
||||
import net.dankito.utils.lucene.search.FieldMapper
|
||||
import net.dankito.utils.lucene.search.MappedSearchConfig
|
||||
import net.dankito.utils.lucene.search.QueryBuilder
|
||||
import net.dankito.utils.lucene.search.Searcher
|
||||
import org.apache.lucene.document.Document
|
||||
import org.apache.lucene.search.Query
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
|
||||
|
||||
open class LuceneBankFinder(indexFolder: File) : BankFinderBase(), IBankFinder {
|
||||
|
||||
companion object {
|
||||
|
||||
const val IndexedBankListFileHashIdFieldName = "IndexedBankListFileHashId"
|
||||
const val IndexedBankListFileHashIdFieldValue = "IndexedBankListFileHashValue"
|
||||
const val IndexedBankListFileHashFieldName = "IndexedBankListFileHash"
|
||||
|
||||
const val BankInfoNameFieldName = "name"
|
||||
const val BankInfoBankCodeFieldName = "bank_code"
|
||||
const val BankInfoBicFieldName = "bic"
|
||||
const val BankInfoCityIndexedFieldName = "city_indexed"
|
||||
const val BankInfoCityStoredFieldName = "city_stored"
|
||||
const val BankInfoPostalCodeFieldName = "postal_code"
|
||||
const val BankInfoPinTanServerAddressFieldName = "pin_tan_server_address"
|
||||
const val BankInfoPinTanVersionFieldName = "pin_tan_version"
|
||||
|
||||
val bankInfoProperties = listOf(
|
||||
PropertyDescription(PropertyType.String, BankInfoNameFieldName, BankInfo::name),
|
||||
PropertyDescription(PropertyType.String, BankInfoBankCodeFieldName, BankInfo::bankCode),
|
||||
PropertyDescription(PropertyType.String, BankInfoBicFieldName, BankInfo::bic),
|
||||
PropertyDescription(PropertyType.String, BankInfoPostalCodeFieldName, BankInfo::postalCode),
|
||||
PropertyDescription(PropertyType.String, BankInfoCityStoredFieldName, BankInfo::city),
|
||||
PropertyDescription(PropertyType.NullableString, BankInfoPinTanServerAddressFieldName, BankInfo::pinTanAddress),
|
||||
PropertyDescription(PropertyType.NullableString, BankInfoPinTanVersionFieldName, BankInfo::pinTanVersion)
|
||||
)
|
||||
|
||||
|
||||
private val log = LoggerFactory.getLogger(LuceneBankFinder::class.java)
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected val indexDir = File(indexFolder, "banklist")
|
||||
|
||||
|
||||
protected val fileUtils = FileUtils()
|
||||
|
||||
protected val hashService = HashService(fileUtils)
|
||||
|
||||
|
||||
protected val fields = FieldBuilder()
|
||||
|
||||
|
||||
protected val queries = QueryBuilder()
|
||||
|
||||
protected val mapper = FieldMapper()
|
||||
|
||||
protected val searcher = Searcher(indexDir)
|
||||
|
||||
|
||||
protected var bankFinderWhileUpdatingIndex: IBankFinder? = null
|
||||
|
||||
|
||||
override fun findBankByBankCode(query: String): List<BankInfo> {
|
||||
bankFinderWhileUpdatingIndex?.let {
|
||||
return it.findBankByBankCode(query)
|
||||
}
|
||||
|
||||
if (query.isBlank()) {
|
||||
return getBankList()
|
||||
}
|
||||
|
||||
val luceneQuery = queries.startsWith(BankInfoBankCodeFieldName, query)
|
||||
|
||||
return getBanksFromQuery(luceneQuery)
|
||||
}
|
||||
|
||||
override fun findBankByNameBankCodeOrCityForNonEmptyQuery(query: String): List<BankInfo> {
|
||||
bankFinderWhileUpdatingIndex?.let {
|
||||
return it.findBankByNameBankCodeOrCity(query)
|
||||
}
|
||||
|
||||
val luceneQuery = queries.createQueriesForSingleTerms(query.toLowerCase()) { singleTerm ->
|
||||
listOf(
|
||||
queries.fulltextQuery(BankInfoNameFieldName, singleTerm),
|
||||
queries.startsWith(BankInfoCityIndexedFieldName, singleTerm),
|
||||
queries.startsWith(BankInfoBankCodeFieldName, singleTerm)
|
||||
// TODO: add query for branchesInOtherCities
|
||||
)
|
||||
}
|
||||
|
||||
return getBanksFromQuery(luceneQuery)
|
||||
}
|
||||
|
||||
override fun searchBankByBic(bic: String): BankInfo? {
|
||||
(bankFinderWhileUpdatingIndex as? BankFinderBase)?.let {
|
||||
return it.searchBankByBic(bic)
|
||||
}
|
||||
|
||||
return getBanksFromQuery(queries.exact(BankInfoBicFieldName, bic)).firstOrNull()
|
||||
}
|
||||
|
||||
|
||||
override fun getBankList(): List<BankInfo> {
|
||||
bankFinderWhileUpdatingIndex?.let {
|
||||
return it.getBankList()
|
||||
}
|
||||
|
||||
return getBanksFromQuery(queries.allDocumentsThatHaveField(BankInfoNameFieldName))
|
||||
}
|
||||
|
||||
protected open fun getBanksFromQuery(query: Query): List<BankInfo> {
|
||||
// there are more than 16.000 banks in bank list -> 10.000 is too few
|
||||
return searcher.searchAndMapLazily(MappedSearchConfig(query, BankInfo::class.java, bankInfoProperties, 100_000))
|
||||
}
|
||||
|
||||
|
||||
override fun preloadBankList() {
|
||||
val hashSearchResult = searcher.search(
|
||||
queries.exact(IndexedBankListFileHashIdFieldName, IndexedBankListFileHashIdFieldValue, false))
|
||||
|
||||
val lastIndexedBankListFileHash = hashSearchResult.hits.firstOrNull()?.let {
|
||||
mapper.string(it, IndexedBankListFileHashFieldName)
|
||||
}
|
||||
|
||||
if (lastIndexedBankListFileHash == null) {
|
||||
updateIndex()
|
||||
}
|
||||
else {
|
||||
val currentBankListFileHash = calculateCurrentBankListFileHash()
|
||||
|
||||
if (currentBankListFileHash != lastIndexedBankListFileHash) {
|
||||
updateIndex(currentBankListFileHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun updateIndex() {
|
||||
updateIndex(calculateCurrentBankListFileHash())
|
||||
}
|
||||
|
||||
protected open fun updateIndex(bankListFileHash: String) {
|
||||
try {
|
||||
val banks = loadBankList()
|
||||
|
||||
// while indexing - which takes a long time on Android - use InMemoryBankFinder so that user sees at least some search results even though it's slower
|
||||
bankFinderWhileUpdatingIndex = InMemoryBankFinder(banks)
|
||||
|
||||
fileUtils.deleteFolderRecursively(indexDir) // delete current index
|
||||
indexDir.mkdirs()
|
||||
|
||||
writeBanksToIndex(banks, bankListFileHash)
|
||||
|
||||
bankFinderWhileUpdatingIndex = null // now use LuceneBankFinder again for searching
|
||||
} catch (e: Exception) {
|
||||
log.error("Could not update index", e)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun writeBanksToIndex(banks: List<BankInfo>, bankListFileHash: String) {
|
||||
DocumentsWriter(indexDir).use { writer ->
|
||||
writer.saveDocuments(banks.map {
|
||||
createDocumentForBank(it, writer)
|
||||
})
|
||||
|
||||
writer.updateDocument(
|
||||
IndexedBankListFileHashIdFieldName, IndexedBankListFileHashIdFieldValue,
|
||||
fields.storedField(IndexedBankListFileHashFieldName, bankListFileHash))
|
||||
|
||||
writer.optimizeIndex()
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun createDocumentForBank(bank: BankInfo, writer: DocumentsWriter): Document {
|
||||
val indexableFields = mutableListOf(
|
||||
fields.fullTextSearchField(BankInfoNameFieldName, bank.name, true),
|
||||
fields.keywordField(BankInfoBankCodeFieldName, bank.bankCode, true),
|
||||
fields.keywordField(BankInfoBicFieldName, bank.bic, true),
|
||||
fields.fullTextSearchField(BankInfoCityIndexedFieldName, bank.city, true),
|
||||
|
||||
fields.storedField(BankInfoCityStoredFieldName, bank.city),
|
||||
fields.storedField(BankInfoPostalCodeFieldName, bank.postalCode),
|
||||
fields.nullableStoredField(BankInfoPinTanServerAddressFieldName, bank.pinTanAddress),
|
||||
fields.nullableStoredField(BankInfoPinTanVersionFieldName, bank.pinTanVersion)
|
||||
|
||||
// TODO: index branchesInOtherCities
|
||||
)
|
||||
|
||||
bank.branchesInOtherCities.forEach { branchCity ->
|
||||
indexableFields.add(fields.storedField(BankInfoCityStoredFieldName, branchCity))
|
||||
}
|
||||
|
||||
return writer.createDocumentForNonNullFields(indexableFields)
|
||||
}
|
||||
|
||||
|
||||
protected open fun calculateCurrentBankListFileHash(): String {
|
||||
return calculateHash(BankListDeserializer().readBankListFile())
|
||||
}
|
||||
|
||||
protected open fun calculateHash(stringToHash: String): String {
|
||||
return hashService.hashString(HashAlgorithm.SHA512, stringToHash)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// TODO: enable to reference BankFinder tests again to run LuceneBankFinderTest
|
||||
|
||||
//package net.dankito.banking.bankfinder
|
||||
//
|
||||
//import net.dankito.utils.io.FileUtils
|
||||
//import org.junit.jupiter.api.AfterAll
|
||||
//import java.io.File
|
||||
//
|
||||
//
|
||||
//class LuceneBankFinderTest : BankFinderTestBase() {
|
||||
//
|
||||
// companion object {
|
||||
// private val IndexFolder = File("testData", "index")
|
||||
//
|
||||
//
|
||||
// @AfterAll
|
||||
// @JvmStatic
|
||||
// fun deleteIndex() {
|
||||
// FileUtils().deleteFolderRecursively(IndexFolder.parentFile)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// override fun createBankFinder(): IBankFinder {
|
||||
// return LuceneBankFinder(IndexFolder)
|
||||
// }
|
||||
//
|
||||
//
|
||||
// init {
|
||||
// underTest.preloadBankList()
|
||||
// }
|
||||
//
|
||||
//}
|
Loading…
Reference in New Issue