Skip to content

Commit

Permalink
Add comments describing the controlNumber generation
Browse files Browse the repository at this point in the history
  • Loading branch information
ArtBIT committed May 16, 2023
1 parent 4549479 commit 88c2f1f
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 19 deletions.
2 changes: 1 addition & 1 deletion lib/schema.js
Expand Up @@ -90,7 +90,7 @@ module.exports = yup.object({
// IPS QR code requires 97, 22, 11 as model, if no model is used it needs to be indicated by prepending it with 00
// https://web.archive.org/web/20220109065457/https://nbs.rs/QRcode/info.html
const model = value.slice(0, 2);
if (["97", "22", "11"].contains(model)) {
if (["97", "22", "11"].includes(model)) {
return value;
}
return `00${value}`;
Expand Down
70 changes: 54 additions & 16 deletions lib/utils.js
Expand Up @@ -25,6 +25,11 @@ const validateReferenceNumber = (input) => {
pozivNaBroj = inputString.substring(5, inputString.length - 1);
control = inputString.substr(inputString.length - 1);
return control == mod22(pozivNaBroj);
case "00":
// no control number
return true;
default:
console.warn(`Model ${model} is not supported.`);
}
return true;
};
Expand All @@ -33,25 +38,47 @@ const validateReferenceNumber = (input) => {
// platnih naloga za izvršenje platnih transakcija u dinarima - "Sl.
// glasnik RS", br. 55/15, 78/15, 82/17, 65/18, 78/18, 22/19, 125/20) i
// Pravilniku o poreskom računovodstvu ("Sl. glasnik RS", br. 103/11).
//
// Struktura broja tekućeg računa u Srbiji je definisana Odlukom
// guvernera Narodne banke Srbije, ima 18 cifara (3+13+2) i sačinjavaju je:
// - fiksni broj banke - 3 cifre
// - broj računa - 13 cifara
// - kontrolni broj - 2 cifre
const validateBankAccount = (input) => {
const control = input.slice(-2);
// console.log(input, control, calculateBankAccountControlNumber(input));
return control == calculateBankAccountControlNumber(input);
const controlNumber = input.slice(-2);
const calculatedControlNumber = calculateBankAccountControlNumber(input);
const isValid = controlNumber == calculatedControlNumber;
if (!isValid) {
console.log(input, controlNumber, calculatedControlNumber);
}
return isValid;
};

// Kontrolni broj se sastoji od dve cifre, koje se izračunavaju na po
// međunarodnom standardu ISO 7064, МОО111-97, na sledeći način:
// Od broja 98 se oduzme ostatak deljenja broja koji se sastoji od prvih 16
// cifara (broj banke i broj računa) pomnoženih sa 100 i broja 97
// (kontr.br. = 98 - mod(2520000000123456 * 100, 97)
const calculateBankAccountControlNumber = (input) =>
98 - modulo(input.slice(0, -2) + "00", 97);

// U elektronskoj formi numerička oznaka računa se koristi isključivo
// kao niz od 18 cifara (npr. 200000000012345600), dok je u pisanim i
// štampanim dokumentima dopušteno pisanje numeričke oznake računa
// koristeći povlake između njegovih sastavnih delova, a dopušteno je i
// ispuštanje "vodećih nula" u broju računa (npr. 200-123456-00)
//
// Ova funkcija normalizuje broj računa tako što osigurava da broj računa
// ima 18 cifara, tako što se ne-numerički karakteri ignorišu a vodeće nule dodaju.
const normalizeBankAccount = (input) => {
const numerals = input.replace(/[^0-9]/g, "");
const bank = numerals.slice(0, 3);
const branch = numerals.slice(3, 8);
const account = numerals.slice(8, 18);
const control = numerals.slice(18, 20);
const rest = `${branch}${account}${control}`;
return `${bank}${rest.padStart(15, "0")}`;
const control = numerals.slice(-2);
const account = numerals.slice(3, -2);
return `${bank}${account.padStart(13, "0")}${control}`;
};

// Modulo 97-10
const modulo = (divident, divisor) => {
var partLength = 10;
while (divident.length > partLength) {
Expand Down Expand Up @@ -121,20 +148,31 @@ const mod97 = (input, base = 100) => {

return 98 - controlNumber;
};

// Izračunavanje kontrolnog broja po modulu 22
//
// Kontrolni broj se sastoji od jedne cifre i izračunava se tako što
// se svaka cifra pomnoži sa svojim rednim mestom zdesna na levo, počevši
// od cifre na poslednjem mestu, a zatim se dobijeni proizvodi sabiraju.
//
// Primer:
//
// Za ulaz 2345671 kontrolni broj je 5, jer je:
// 1 * 1 + 7 * 2 + 6 * 3 + 5 * 4 + 4 * 5 + 3 * 6 + 2 * 7 = 105
// 105 % 11 = 6
// 11 - 6 = 5
// 10 % 10 = 0

const mod22 = (input) => {
let controlNumber = 0;
let base = 0;
[...String(input)]
.reverse()
.map((char) => parseInt(char))
.forEach((char) => {
controlNumber += (7 - base) * char;
base = ++base % 7;
});
.forEach((char, index) => (controlNumber += (index + 1) * char));

controlNumber = 11 - (controlNumber % 11);
if (controlNumber > 9) controlNumber = controlNumber - 10;

return controlNumber;
// kontrolni broj mora biti jedna cifra
return controlNumber % 10;
};

const renameObjectKeys = (obj, keyMap) => {
Expand Down
11 changes: 9 additions & 2 deletions lib/utils.test.js
Expand Up @@ -8,6 +8,7 @@ const {
describe("Validacija bankovnog racuna", () => {
it("should validate these bank accounts as correct", () => {
const racuni = [
"840000071711184351",
"115038169338697697",
"190000000001513090",
"170003004062800050",
Expand All @@ -22,7 +23,13 @@ describe("Validacija bankovnog racuna", () => {
racuni.forEach((racun) => expect(validateBankAccount(racun)).toBe(false));
});
it("should validate short bank account notation as correct", () => {
const racuni = ["12345682", "265-1234-88", "205-12345-10", "325-123456-98"];
const racuni = [
"12345682",
"265-1234-88",
"205-12345-10",
"325-123456-98",
"265-600594-97",
];
racuni.forEach((racun) =>
expect(validateBankAccount(normalizeBankAccount(racun))).toBe(true)
);
Expand All @@ -37,7 +44,7 @@ describe("Validacija kontrolnog broja", () => {
"9728-12345a",
"972812345A",
"9747-21750940-2204",
"45-21730800-2204",
"0045-21730800-2204",
"22-223-23456715",
];
inputs.forEach((input) =>
Expand Down

0 comments on commit 88c2f1f

Please sign in to comment.