#API – Generator numeru dokumentu

Numery dokumentów typu faktura, paragon, mają zazwyczaj swoje specyficzne formaty. Ja korzystam z formatu: [litera/litery przedstawiające typ dokumentu][znak odstępu][numer dokumentu w miesiącu]/[miesiąc]/[rok]. Taki format wymaga odpowiedniego algorytmu, tak, aby można było generować kolejne numery dokumentów. W tym celu stworzyłem usługę, która ma generować numery dla kolejnych dokumentów.

Żeby wygenerować taki numer, musimy najpierw pobrać z bazy numer ostatnigo dokumentu. Repozytorium mam zdefiniowane jako usługę, więc muszę wywołać metodę usługi w usłudze. Aby to zrobić, podczas deklarowania nowej usługi w services.yml, muszę nadać jej odpowiedni argument:

//...
utils.document_number.generate:
    class: ApiBundle\Utils\DocumentNumberGenerator
    arguments: ['@service_container']
//...

Teraz tworzę katalog Utils w AppBundle, a w nim plik o nazwie takiej samej, jak nazwa usługi, czyli:

AppBundle/
├── /...
└── Utils/
    └── DocumentNumberGenerator.php

Teraz w pliku, tworzę klasę:

//AppBundle/Utils/DocumentNumberGenerator.php

namespace ApiBundle\Utils;

use Symfony\Component\DependencyInjection\ContainerInterface as Container;

class DocumentNumberGenerator
{
//...

Dopisuję od razu linijkę z use, w której dodaję przestrzeń nazw dla kontenera DI.

Na samym początku, muszę stworzyć metodę __construct(), przez którą zostanie przypisany kontener usług do zmiennej container.

//...
    private $container;

    public function __construct(Container $container) {
        $this->container = $container;
    }
//...

Teraz mogę pobierać usługi ze zmiennej container.

Tworzę następnie metodę, która zwróci mi wygenerowany numer:

//...
public function getNextNumber(int $userId, $type)
{
    //...
}

Przyjmuje ona dwie wartości, w pierwszej id zalogowanego użytkownika, a w drugiej typ dokumentu.

Teraz pobieram ostatni numer dokumentu z bazy:

$document = $this->container->get('crv.doctrine_entity_repository.document')
    ->createFindLastDocumentNumber($userId, $type)
    ->getResult();

Tutaj właśnie korzystam z usługi pobranej przez zmienną container. Wywołuję metodę createFindLastDocumentNumber($userId, $type), która przyjmuje również dwie wartości, te same, co główna metoda. Tak wygląda ta metoda w repozytorium:

public function createFindLastDocumentNumber(int $userId, $type)
{
    $query = $this->_em->createQuery(
        "
        SELECT d.number
        FROM ApiBundle:Document d
        WHERE d.userId = :userId
        AND d.type = :type
        ORDER BY d.number DESC
        "
    );

    $query->setParameter('userId', $userId);
    $query->setParameter('type', $type);
    $query->setMaxResults(1);

    return $query;
}

Tworzę zapytanie, które pobiera mi ostatni rekord dla zalogowanego użytkownika, gdzie typ jest równy temu z parametru funkcji.

Następnie sprawdzam typ i przypisuje odpowiednie pierwsze litery numeru oraz przypisuję sobie do zmiennych aktualny miesiąc i rok:

if($type=='Rachunek')
    $type='R';
else if($type=='FV')
    $type='FV';

$date = new \DateTime('now');
$month = $date->format('m');
$year = $date->format('Y');

Myślę, że ten fragment jest zrozumiały.

Teraz muszę upewnić się, czy na pewno otrzymałem numer dokumentu, w przypadku gdy go nie otrzymam, to znaczy, że w bazie nie ma dokumentów tego typu, więc przypisuję zmiennej $number wartość ‚001’:

if ($document == null) {
    $number = '001';
}else{
    $number = explode(" ", $document[0]['number']);
    $number = explode("/", $number[1]);

    if($number[1]==$month) {
        $number = intval($number[0])+1;

        if($number<10)
            $number = '00'.$number;
        else if($number<100)
            $number = '0'.$number;

     }else{
         $number = '001';
     }
}

Jeśli natomiast otrzymam numer, to muszę go rozbić na pojedyncze znaki, żeby otrzymać sam numer dokumentu w miesiącu. Najpierw rozdzielam go w miejscu spacji, więc otrzymuję pierwsze litery, oraz numer z datą.

Następnie numer z datą rozdzielam po „/” i otrzymuję tablicę, w której jako pierwszą wartość, otrzymuję numer dokumentu w miesiącu. Sprawdzam jeszcze przed nadaniem numeru, czy nie rozpoczął sie nowy miesiąc, więc prównuję wartość miesiąca ostatniego dokumentu, z aktualnym miesiącem. Jeśli się zgadza, to numer ostatniego dokumentu konwertuję na integer i zwiększam o 1, jeśli nie, to przypisuję wartość ‚001’.

Na koniec dodaję odpowiednią ilość zer przed cyfrą, tak aby zachować odpowiedni format.

Cała funkcja zwraca mi numer w odpowiednim formacie:

return $type.' '.$number.'/'.$month.'/'.$year;

I to wszystko, teraz mogę w kontrolerze, korzystać z mojej nowej usługi:

private function generateDocumentNumber($type)
{
    return $this->get('utils.document_number.generate')->getNextNumber($this->getUserId(), $type);
}

$number = $this->generateDocumentNumber('typ dokumentu');

Tutaj znajduje się cały plik usługi.

Mam już ukończony pierwszy kontroler i w tym tygodniu, powinienem zakończyć pisanie API i zająć się tworzeniem Frontendu. Jeśli nie napotkam żadnych problemów zdrowotnych i innych, to myślę, że wyrobię się w 10 tygodniach i będę miał jeszcze trochę czasu.

Pozdrawiam!
MTK

  • Cześć.
    Rozważ przypadek gdy dwóch użytkowników w jednym czasie wystawia dokument i dostają ten sam numer. Chyba, że numer dokumentu jest nadawany dopiero po jego zapisie ale i tak wtedy trzeba by go „zarezerwować”. Dodatkowo polecam nie przechowywanie numeru dokumentu w całkowitej postaci tylko z rozbiciem na pola typu Rodzaj|Numer|Miesiąc|Rok i składanie tego do postaci „wyświetlanej” gdzieś w innej warstwie. W ten sposób będziesz mógł w systemie konfigurować format numeracji i unikniesz podczas wyciągania kolejnego numeru „rozbijania” na poszczególne składowe.

    • MTK

      Nie muszę rozważać tego przypadku, ponieważ numeracja idzie od 1 dla każdego użytkownika osobno. Na zapis w osobnych polach też wpadłem, ale dopiero podczas tworzenia tego skryptu, jeszcze pomyślę, czy to zmienić, czy zostawić.

  • marcin484

    Zauwazaz, ze uzytkownik moze kilka okien naraz otworzyc, wiec rowniez moze wystapic dla tego samego uzytkownika ten sam numer 🙂 (znam z autopsji )

    • MTK

      Nawet jeśli otworzy kilka okien na raz, to nie zdąży kliknąć tak szybko w dwóch osobnych oknach. Ale fakt, może się zdarzyć. Nadałem teraz w bazie danych wartość „unique” dla numeru dokumentu, więc jeśli skrypt wygeneruje ten sam numer, to baza zwróci błąd, a aplikacja frontendowa poprosi o ponowne wysłanie formularza.

Proudly powered by WordPress | Theme: Baskerville 2 by Anders Noren.

Up ↑