Skip to content

Commit 5064a95

Browse files
committed
VATidBGCheckDigit mit LOG (in commons-validator ohne LOG)
1 parent a3db440 commit 5064a95

File tree

2 files changed

+293
-0
lines changed

2 files changed

+293
-0
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.validator.routines.checkdigit;
18+
19+
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
21+
import org.apache.commons.validator.GenericTypeValidator;
22+
import org.apache.commons.validator.GenericValidator;
23+
import org.apache.commons.validator.routines.DateValidator;
24+
25+
/**
26+
* Bulgarian VAT identification number (VATIN) Check Digit calculation/validation.
27+
* <p>
28+
* Dank dobawena stoinost (DDS)
29+
* The Bulgarian VAT (Данък върху добавената стойност) number is either 9 (for legal entities)
30+
* or 10 digits long (ЕГН for physical persons, foreigners and others).
31+
* Each type of number has its own check digit algorithm.
32+
* </p>
33+
* <p>
34+
* See <a href="https://en.wikipedia.org/wiki/VAT_identification_number">Wikipedia VATIN</a>
35+
* and <a href="https://en.wikipedia.org/wiki/Unique_citizenship_number">ЕГН (civil number)</a>
36+
* for more details.
37+
* </p>
38+
*
39+
* @since 1.10.0
40+
*/
41+
public final class VATidBGCheckDigit extends ModulusCheckDigit {
42+
43+
private static final long serialVersionUID = -8667365895223831325L;
44+
private static final Log LOG = LogFactory.getLog(VATidBGCheckDigit.class);
45+
46+
/** Singleton Check Digit instance */
47+
private static final VATidBGCheckDigit INSTANCE = new VATidBGCheckDigit();
48+
49+
/**
50+
* Gets the singleton instance of this validator.
51+
* @return A singleton instance of the class.
52+
*/
53+
public static CheckDigit getInstance() {
54+
return INSTANCE;
55+
}
56+
57+
/**
58+
* There are three length
59+
* <ul>
60+
* <li>9 for legal entities (standard DDS),</li>
61+
* <li>10 for physical persons (civil number),</li>
62+
* <li>13 for legal entities with branch number (not used for VATIN),</li>
63+
* </ul>
64+
*/
65+
static final int LEN = 9; // with Check Digit
66+
static final int LENCN = 10; // with Check Digit
67+
static final int LEN13 = 13; // with Check Digit
68+
69+
/**
70+
* Constructs a modulus Check Digit routine.
71+
*/
72+
private VATidBGCheckDigit() {
73+
super(MODULUS_11);
74+
}
75+
76+
/** Weighting for physical persons given to digits depending on their left position */
77+
private static final int[] POSITION_WEIGHT = { 2, 4, 8, 5, 10, 9, 7, 3, 6 };
78+
/** Weighting for DDS with branches */
79+
private static final int[] BRANCH_WEIGHT = { 2, 7, 3, 5 };
80+
81+
/**
82+
* Calculates the <i>weighted</i> value of a character in the
83+
* code at a specified position.
84+
*
85+
* <p>For VATID digits are weighted by their position from left to right.</p>
86+
*
87+
* @param charValue The numeric value of the character.
88+
* @param leftPos The position of the character in the code, counting from left to right
89+
* @param rightPos The positionof the character in the code, counting from right to left
90+
* @return The weighted value of the character.
91+
*/
92+
@Override
93+
protected int weightedValue(final int charValue, final int leftPos, final int rightPos) {
94+
final int weight = POSITION_WEIGHT[(leftPos - 1)];
95+
return charValue * weight;
96+
}
97+
98+
private int calculateDDStotal(final String code, final boolean recalculate) throws CheckDigitException {
99+
final boolean standard = code.length() < LEN;
100+
int total = 0;
101+
for (int i = standard ? 0 : LEN - 1; i < code.length(); i++) {
102+
final int leftPos = i + 1;
103+
final int charValue = toInt(code.charAt(i), leftPos, -1);
104+
if (standard) {
105+
total += charValue * (recalculate ? 2 + leftPos : leftPos);
106+
} else {
107+
final int weight = BRANCH_WEIGHT[(leftPos - LEN)];
108+
total += charValue * (recalculate ? 2 + weight : weight);
109+
}
110+
}
111+
return total;
112+
}
113+
114+
/**
115+
* {@inheritDoc}
116+
*/
117+
@Override
118+
public String calculate(final String code) throws CheckDigitException {
119+
if (GenericValidator.isBlankOrNull(code)) {
120+
throw new CheckDigitException(CheckDigitException.MISSING_CODE);
121+
}
122+
if (GenericTypeValidator.formatLong(code) == 0) {
123+
throw new CheckDigitException(CheckDigitException.ZERO_SUM);
124+
}
125+
if (code.length() + 1 == LEN) { // DDS for legal entities
126+
int total = calculateDDStotal(code, false);
127+
if ((total % MODULUS_11) == MODULUS_10) {
128+
// recalculate with increased weights
129+
total = calculateDDStotal(code, true);
130+
}
131+
return toCheckDigit(total % MODULUS_11 % MODULUS_10);
132+
} else if (code.length() + 1 == LEN13) {
133+
if (!isValid(code.substring(0, LEN))) {
134+
throw new CheckDigitException("Invalid DDC subcode " + code.substring(0, LEN));
135+
}
136+
int total = calculateDDStotal(code, false);
137+
if ((total % MODULUS_11) == MODULUS_10) {
138+
// recalculate with increased weights
139+
total = calculateDDStotal(code, true);
140+
}
141+
return toCheckDigit(total % MODULUS_11 % MODULUS_10);
142+
} else if (code.length() + 1 == LENCN) {
143+
// ЕГН for physical persons, foreigners and others (aka civil number)
144+
checkCivilNumber(code);
145+
final int calculateModulus = INSTANCE.calculateModulus(code, false);
146+
return toCheckDigit(calculateModulus % MODULUS_10);
147+
}
148+
throw new CheckDigitException("Invalid DDC " + code);
149+
}
150+
151+
private static final int BORN_BEFORE_1900_MOD = 20; // month modifier
152+
private static final int BORN_AFTER_2000_MOD = 40; // month modifier
153+
154+
/**
155+
* Check ЕГН (civil number), which contains a coded birth date.
156+
* <p>
157+
* The initial six digits correspond to the birth date.
158+
* The first two digits are the last two digits of the year,
159+
* and the last two digits are the day of the month.
160+
* For people born between 1900 and 1999 the middle digits are the month number: YYMMDD.
161+
* For people born before 1900, 20 is added to the month: YY M+20 DD.
162+
* For people born from 2000, 40 is added to the month: YY M+40 DD.
163+
* </p>
164+
* @param code
165+
* @return true for valid coded date
166+
* @throws CheckDigitException
167+
*/
168+
private boolean checkCivilNumber(final String code) throws CheckDigitException {
169+
final int m1 = toInt(code.charAt(2), 3, -1);
170+
final int m0 = toInt(code.charAt(3), 4, -1);
171+
final int mm = 10 * m1 + m0;
172+
String yyborn = "19" + code.substring(0, 2);
173+
int mmborn = mm;
174+
if (mm > BORN_BEFORE_1900_MOD) {
175+
yyborn = "18" + code.substring(0, 2);
176+
mmborn = mm - BORN_BEFORE_1900_MOD;
177+
if (mm > BORN_AFTER_2000_MOD) {
178+
yyborn = "20" + code.substring(0, 2);
179+
mmborn = mm - BORN_AFTER_2000_MOD;
180+
}
181+
}
182+
final DateValidator dateValidator = new DateValidator();
183+
final String date = String.format("%02d", mmborn) + "/" + code.substring(4, 6) + "/" + yyborn; // CHECKSTYLE IGNORE MagicNumber
184+
if (dateValidator.validate(date, "MM/dd/yyyy") == null) {
185+
throw new CheckDigitException("Invalid date " + date + " - Invalid DDC " + code);
186+
}
187+
188+
// The next three digits designate the birth order number,
189+
// the third digit being even for males and odd for females.
190+
if (LOG.isDebugEnabled()) {
191+
final int sexValue = toInt(code.charAt(8), 9, -1);
192+
final String sex = (sexValue & 1) == 0 ? "male" : "female";
193+
LOG.debug(code + " is ЕГН for a " + sex + " person born " + date);
194+
}
195+
return true;
196+
}
197+
198+
/**
199+
* {@inheritDoc}
200+
*/
201+
@Override
202+
public boolean isValid(final String code) {
203+
if (GenericValidator.isBlankOrNull(code)) {
204+
return false;
205+
}
206+
if (code.length() < LEN) {
207+
return false;
208+
}
209+
try {
210+
final String cd = calculate(code.substring(0, code.length() - 1));
211+
return code.endsWith(cd);
212+
} catch (final CheckDigitException ex) {
213+
return false;
214+
}
215+
}
216+
217+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.validator.routines.checkdigit;
18+
19+
import org.junit.jupiter.api.BeforeEach;
20+
21+
/**
22+
* BG VAT Id Check Digit Tests.
23+
* <pre>
24+
25+
BG 108511243 : gültig ПАНОРАМА-2000 - ООД
26+
BG 206425654 : gültig https://nutrinitylabs.com/contacts
27+
BG 131324923 : gültig МАКСИМА БЪЛГАРИЯ - ЕООД, Adresse БОТЕВГРАДСКО ШОСЕ №247 ет.2 обл.СОФИЯ, гр.СОФИЯ 1517
28+
BG 121759222 : gültig ОМВ БЪЛГАРИЯ - ООД, Adresse Гжк Малинова долинаДонка Ушлинова №2 бл.сграда 4 ет.+1 ап.помещение 411 обл.СОФИЯ, гр.СОФИЯ 1766
29+
BG 207402758 : gültig Тегел Груп - ЕООД, Adresse Георги Кондолов №38 обл.БУРГАС, с.ЛОЗЕНЕЦ 8277
30+
BG 205153347 : gültig ВАЛЕРИЯ-64 - ООД, Adresse Първи май №74 обл.КЪРДЖАЛИ, гр.КЪРДЖАЛИ 6600
31+
BG 204407312 : gültig ИВКОН СТРОЙ - ООД, Adresse ул. Богдан №20 вх.А ет.4 ап.12 обл.СОФИЯ, гр.СОФИЯ 1000
32+
BG 201334001 : gültig КАРТ 8 - ЕООД, Adresse ул. "Балкан" №23 обл.СТАРА ЗАГОРА, с.ЕЛХОВО 6174
33+
BG 131129282 : gültig КАУФЛАНД БЪЛГАРИЯ ЕООД ЕНД КО - КД (Kaufland)
34+
BG 203519454 : gültig 43-ти километър - ЕООД
35+
BG 207350980 : gültig АИД-8485 - ЕООД, Adresse жк МЕСТНОСТ ЗАХАРИДЕВО №608 Б ет.1 ап.2 обл.ПЛОВДИВ, с.МАРКОВО 4108
36+
BG 207546057 : gültig Глобал Ексчейндж Къренси Ексчейндж България - ЕООД, Adresse Проф. Фритьоф Нансен №37А ет.5 обл.СОФИЯ, гр.СОФИЯ 1142
37+
BG 831650349 : gültig (Nestlé) НЕСТЛЕ БЪЛГАРИЯ - АД
38+
BG 207839658 : gültig ЕКОНТ ЕКСПРЕС - АД
39+
BG 175074752 : gültig ПРОФИ КРЕДИТ БЪЛГАРИЯ - ЕООД. Aus old.formvalidation.io
40+
BG 7523169263 8032056031 7542011030 7111042925: valide ???, aber ungültig (gleiche Quelle)
41+
42+
Примери:
43+
ЕГН Значение
44+
7524169268 Мъж, с дата на раждане 16.04.1875 г.
45+
7501010010 Жена, с дата на раждане 01.01.1975 г.
46+
7552010005 Мъж, с дата на раждане 01.12.2075 г.
47+
8032056031 Жена, с дата на раждане 05.12.1880 г.
48+
8001010008 Мъж, с дата на раждане 01.01.1980 г.
49+
7552011038 Жена, с дата на раждане 01.12.2075 г.
50+
8141010016 Жена, с дата на раждане 01.01.2081 г
51+
52+
* </pre>
53+
*/
54+
public class VATidBGCheckDigitTest extends AbstractCheckDigitTest {
55+
56+
/**
57+
* Sets up routine & valid codes.
58+
*/
59+
@BeforeEach
60+
protected void setUp() {
61+
routine = VATidBGCheckDigit.getInstance();
62+
valid = new String[] {"108511243", "206425654", "131324923", "121759222", "207402758"
63+
, "205153347", "204407312", "201334001", "131129282", "203519454"
64+
, "207350980", "207546057", "831650349", "207839658", "175074752"
65+
, "217839654", "175074767", "474074760" // increased weights
66+
, "8319195360016", "8319195360048" // Unicredit Bulbank with branches 001 004
67+
// 10 digits : ЕГН (civil number), which contains a coded birth date
68+
, "7524169268", "7501010010", "7552010005", "8032056031", "8001010008", "7552011038", "8141010016"
69+
};
70+
invalid = new String[] { "10851124" // to short
71+
, "7502300013" // invalid date 30 Feb
72+
, "8319195370016" // Invalid DDC subcode
73+
};
74+
}
75+
76+
}

0 commit comments

Comments
 (0)