Wrzesień
20
2009

Walidator NIP

Słowa kluczowe: , , , , , | Kategorie: Seam Framework
No Gravatar

Opisując tworzenie nowego walidatora Hibernate wspomniałem o parametrach adnotacji. Postanowiłem utworzyć przykład takiej walidacji dla Numerów Identyfikacji Podatkowej.

NIP osoby fizycznej ma format: XXX-XXX-XX-XX, gdzie X jest dowolną cyfrą. Jednak dla osoby prawnej, jest to format: XXX-XX-XX-XXX. Dlatego w adnotacji @NIP uwzględniłem typ osoby (Person.UNSPECIFIED – dowolny podmiot, Person.NATURAL – osoba fizyczna, Person.LEGAL – osoba prawna):


package pl.info.czerwinski;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.hibernate.validator.ValidatorClass;

@ValidatorClass(NIPValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NIP {
    String message() default "Nieprawidłowy NIP";
    public enum Person {
        UNSPECIFIED,
        NATURAL,
        LEGAL
    }
    Person person() default Person.UNSPECIFIED;
}

Domyślnie NIP będzie dotyczyć dowolnego podmiotu.

Klasa walidatora dla tej adnotacji będzie sprawdzała format numeru, zależnie od wartości parametru person:


package pl.info.czerwinski;

import java.util.regex.Pattern;
import org.hibernate.validator.Validator;

public class NIPValidator implements Validator<NIP> {

    private NIP.Person person;

    public void initialize(NIP annotation) {
        person = annotation.person();
    }

    public boolean isValid(Object value) {
        if (value instanceof String) {
            if (person == NIP.Person.NATURAL) {
                return Pattern.matches(
                        "^[0-9]{3}-[0-9]{3}-[0-9]{2}-[0-9]{2}$",
                        value.toString());
            } else if (person == NIP.Person.LEGAL) {
                return Pattern.matches(
                        "^[0-9]{3}-[0-9]{2}-[0-9]{2}-[0-9]{3}$",
                        value.toString());
            } else {
                return Pattern.matches(
                        "^[0-9]{3}-[0-9]{3}-[0-9]{2}-[0-9]{2}$",
                        value.toString()) ||
                    Pattern.matches(
                        "^[0-9]{3}-[0-9]{2}-[0-9]{2}-[0-9]{3}$",
                        value.toString());
            }
        }
        return false;
    }

}

Jak widać, pominąłem obliczanie cyfry kontrolnej – dokładny algorytm opisuje Wikipedia.

Testy jednostkowe dla tego walidatora będą wyglądały następująco:


package pl.info.czerwinski;

import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class NIPValidatorTest {

    private NIPValidator validator;

    @NIP
    public String nip;

    @NIP(person=NIP.Person.NATURAL)
    public String naturalPersonNIP;

    @NIP(person=NIP.Person.LEGAL)
    public String legalPersonNIP;

    @BeforeClass
    public void setUp() {
        validator = new NIPValidator();
    }

    public NIP getAnnotation(String fieldName) {
        try {
            return getClass().getField(
                    fieldName).getAnnotation(NIP.class);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            return null;
        }
    }

    @DataProvider(name="nipValidator.isValid")
    public Object[][] getIsValidData() {
        return new Object[][] {
            {getAnnotation("nip"), "123-456-78-90", true},
            {getAnnotation("nip"), "123-45-67-890", true},
            {getAnnotation("naturalPersonNIP"), "123-456-78-90", true},
            {getAnnotation("naturalPersonNIP"), "123-45-67-890", false},
            {getAnnotation("legalPersonNIP"), "123-456-78-90", false},
            {getAnnotation("legalPersonNIP"), "123-45-67-890", true},
            {getAnnotation("naturalPersonNIP"), "1234567890", false},
            {getAnnotation("legalPersonNIP"), "1234567890", false},
            {getAnnotation("nip"), "1234567890", false},
            {getAnnotation("naturalPersonNIP"), "123-456-78-9a", false},
            {getAnnotation("legalPersonNIP"), "123-45-67-89a", false},
            {getAnnotation("nip"), "123-456-78-9a", false},
            {getAnnotation("nip"), "123-45-67-89a", false},
        };
    }

    @Test(dataProvider="nipValidator.isValid")
    public void testIsValid(
            NIP annotation, Object value, boolean valid) {
        validator.initialize(annotation);
        assert validator.isValid(value) == valid :
            "Validation result is wrong.";
    }

}

Działanie przypadków testowych jest analogiczne do walidatora nazwy użytkownika, więc pozostawię ten przykład bez dodatkowych komentarzy.


5 odpowiedzi do “Walidator NIP”

  1. Testowanie walidatorów nie jest zbyt skomplikowane, na wstęp bardzo dobre i nakreśla problem. Czekam jednak na testy akcji, które uważam tak naprawdę za warte uwagi i stosowania (walidatora napiszemy i raczej nie będziemy go tak często zmieniać jak niektóre akcje).

  2. Myślę że warto dodać do Twojego validatora weryfikację cyfry kontrolnej, która jest zarówno w NIP, PESEL jak i REGON. Algorytmy są proste a ich opis jest dostępny na polskiej Wikipedii.

    Pozdrawiam,
    Łukasz

  3. Do repozytorium http://maven2.czerwinski.info.pl/ dodałem nowy projekt:

    <groupId>pl.info.czerwinski</groupId>
    <artifactId>additional-hibernate-validators</artifactId>
    <version>1.0-SNAPSHOT</version>

    Na razie uzupełniłem walidator NIP, ale planuję dodać też odpowiednie klasy dla PESEL i REGON.

  4. Forma zapisu z myślnikami nie ma znaczenia; obecnie to zaszłość i w walidatorach numeru NIP nie powinno się jej brać pod uwagę; zresztą to samo podpowiada zdrowy rozsądek. Powinno się za to brać pod uwagę liczbę kontrolną – Twój walidator jej nie analizuje, myślę, że warto byłoby to doimplementować. Poza tym dopuszczalny jest format z prefiksem PL jako oznaczenie kraju. Czyli PLxxxxxxxxxx, gdzie x to liczby całkowite z zakresu {0-9} może być poprawnym numerem NIP, pod warunkiem, że zostanie spełniony warunek o sumie kontrolnej.

  5. We wpisie Dodatkowe walidatory Hibernate opisuję projekt, w którym uwzględniłem formę bez myślników i sprawdzam cyfrę kontrolną. Jeszcze kilka lat temu księgowi zwracali szczególną uwagę na położenie myślników, gdyż zdaniem niektórych mogły wystąpić dwa identyczne NIP – jeden dla osoby fizycznej, drugi dla osoby prawnej.

    Jeżeli chodzi o prefiks „PL”, to nie spotkałem się z nim do tej pory; nie ma też o nim mowy w Wikipedii. Postaram się go uwzględnić w bibliotece jak najszybciej.

    Pozdrawiam,
    Sławek

Napisz Komentarz

*