Coverage Summary for Class: IbanRuGenerator (ru.eda.plgn.bizgen.core.generator.impl)

Class Method, % Branch, % Line, % Instruction, %
IbanRuGenerator 100% (2/2) 100% (5/5) 100% (25/25)
IbanRuGenerator$IbanGenerator 100% (3/3) 62.5% (10/16) 94.7% (18/19) 97.2% (175/180)
Total 100% (5/5) 62.5% (10/16) 95.8% (23/24) 97.6% (200/205)


 package ru.eda.plgn.bizgen.core.generator.impl
 
 import ru.eda.plgn.bizgen.core.generator.GeneratorResult
 import ru.eda.plgn.bizgen.core.generator.GeneratorResultWithEscape
 import ru.eda.plgn.bizgen.core.generator.GeneratorStr
 import ru.eda.plgn.bizgen.core.generator.impl.IbanRuGenerator.IbanGenerator.generateRussianIBAN
 import java.math.BigInteger
 
 /**
  * IBAN (International Bank Account Number) - международный номер банковского счёта, используемый для международных переводов. Формат
  * регулируется стандартом ISO 13616.
  *
  * **Формат российского IBAN (33 символа):** *RU КК XXXX XXXX XXXX XXXX XXXX XXXX X*
  * - *2 буквы — код страны (RU)*
  * - *2 цифры — контрольное число (рассчитывается)*
  * - *29 цифр — BBAN (базовый номер счёта)*
  *
  * **Структура BBAN (29 цифр):** *BBBBB SSSS NNNNNNNNNNNNNNNNNNNN*
  * - *BBBBB — код банка из БИК (5 цифр)*
  * - *SSSS — код филиала (4 цифры, часто 0000)*
  * - *NN...N — номер счёта (20 цифр)*
  *
  * **See Also:** [IBAN](https://ru.wikipedia.org/wiki/IBAN)
  *
  * @author Dmitry_Emelyanenko
  */
 class IbanRuGenerator : GeneratorStr {
   override val uniqueDistance: Int = 130
 
   override fun generate(): GeneratorResult<String> {
     val bik = BikGenerator.randomBik()
     val accountNumber = BankAccountGenerator.randomCorrespondentAccount(bik)
     return GeneratorResultWithEscape(data = generateRussianIBAN(accountNumber, bik))
   }
 
   /**
    * IBAN (International Bank Account Number) - международный номер банковского счёта, используемый для международных переводов. Формат
    * регулируется стандартом ISO 13616.
    *
    * **See Also:** [IBAN](https://ru.wikipedia.org/wiki/IBAN)
    *
    * @author Dmitry_Emelyanenko
    */
   internal object IbanGenerator {
 
     /**
      * Генерирует российский IBAN.
      *
      * @param accountNumber Номер счёта (20 цифр).
      * @param bik БИК банка (9 цифр). Если не указан, генерируется случайный.
      * @return Строка IBAN (формат RUXXXXXXXXXXXXXXXXXXXXXXXXX).
      */
     fun generateRussianIBAN(
       accountNumber: String,
       bik: String? = null,
     ): String {
       // 1. Код банка — последние 5 цифр БИК
       val selectedBik = bik ?: BikGenerator.randomBik()
       require(selectedBik.length == 9 && selectedBik.all { it.isDigit() }) {
         "БИК должен содержать 9 цифр"
       }
       val bankCode = selectedBik.substring(4, 9)
 
       // 2. Формируем BBAN (5 + 4 + 20 = 29 цифр)
       val bban = bankCode + "0000" + accountNumber
 
       require(bban.length == 29) { "BBAN must be 29 but was ${bban.length}" }
 
       // 3. Вычисляем контрольное число для IBAN
       val controlNumber = calculateIBANControlNumber("RU", bban)
 
       // 4. Собираем итоговый IBAN (33 символа)
       return "RU$controlNumber$bban".also {
         require(it.length == 33) { "IBAN must be 33 but was ${it.length}" }
       }
     }
 
     /** Вычисляет контрольное число IBAN (алгоритм mod-97). */
     @Suppress("SameParameterValue")
     private fun calculateIBANControlNumber(countryCode: String, bban: String): String {
       val tempIban = "$countryCode${"00"}$bban" // RU + 00 + BBAN
       val moved = tempIban.substring(4) + tempIban.substring(0, 4) // Перемещаем первые 4 символа в конец
       val numericIban = moved.map { char ->
         if (char.isLetter()) (char.uppercaseChar() - 'A' + 10).toString()
         else char.toString()
       }.joinToString("")
       val mod97 = BigInteger(numericIban).mod(BigInteger("97")).toInt()
       return "%02d".format(98 - mod97)
     }
   }
 }