Postać Docelowa Faktury

Po dwutygodniowym rejsie po Mazurach, czas wrócić do pracy nad projektem. Aby skończyć warstwę logiki biznesowej, muszę jeszcze tylko stworzyć klasę, która zajmie się zamianą przyczajonej w bazie danych, zdigitalizowanej do cna postaci faktury w nie mniej zdigitalizowaną, ale niepomiernie bardziej czytelną dla użytkownika programu, postać dokumentu PDF. Użyję w tym celu biblioteki PdfSharp.

BHP z ostrym narzędziem
Tworzenie dokumentów PDF przy pomocy PdfSharp nie jest trudne. Przygotowanie nowego dokumentu to tylko kilka linijek:

PdfDocument document = new PdfDocument(); // stworzenie nowego dokumentu
PdfPage page = document.AddPage(); // stworzenie nowej strony dokumentu
XGraphics graphics = XGraphics.FromPdfPage(page); // stworzenie obiektu odpowiedzialnego za wygląd strony

Obiektem odpowiedzialnym za umieszczanie grafiki i treści na stronach dokumentu jest instancja klasy XGraphics. Na wygląd faktury będą się składać wyłącznie prostokąty i tekst, dlatego przedstawię sposób generowania tylko tych elementów.
Za tworzenie prostokątów odpowiada metoda DrawRectangle. Przykładowo, kod:

XPen pen = new XPen(XColors.Black, 1); // obramowanie prostokąta
XBrush brush = XBrushes.Red; // wypełnienie prostokąta
XRect rect = new XRect(0, 0, 60, 20); // położenie i wymiary prostokąta (x, y, szerokość, wysokość)
graphics.DrawRectangle(pen, brush, rect); // narysowanie prostokąta

narysuje w lewym górnym rogu strony czerwony prostokąt z czarnym obramowaniem.
Umieszczaniem na stronie tekstu zajmuje się metoda DrawString. I tak, kod:

XFont font = new XFont("Arial", 10, XFontStyle.Bold); // krój, rozmiar i styl czcionki
graphics.DrawString("Poufne", font, XBrushes.Black, rect, XStringFormats.Center); // dodanie czarnego napisu w środku stworzonego wcześniej prostokąta

sprawi, że nasz dokument, niezależnie od dalszej treści, będzie mógł być wykorzystany jako rekwizyt w dowolnym filmie szpiegowskim.

Przyda mi się także metoda MeasureString, zwracająca rozmiar napisu pisanego zadaną czcionką:

XSize size = graphics.MeasureString("<napis do zmierzenia>", czcionka);

Zapisanie dokumentu na dysku to nic więcej, niż napisanie:

document.Save("<nazwa pliku>");

Te metody (i ich przeciążenia) w zupełności wystarczą do wystawienia faktury VAT.

Very Arduous Task
Przepisy określają informacje, jakie muszą się znaleźć na fakturze:

  • nazwy i adresy sprzedawcy i nabywcy oraz ich numery NIP
  • datę dokonania sprzedaży oraz datę wystawienia dokumentu
  • napis Faktura VAT oraz numer kolejny faktury
  • nazwę towaru lub usługi (przedmiotu transakcji)
  • jednostkę miary i ilość sprzedanych towarów lub rodzaj wykonanych usług
  • cenę jednostkową towaru lub usługi bez kwoty podatku (cenę jednostkową netto)
  • wartość towarów lub wykonanych usług, których dotyczy sprzedaż, bez kwoty podatku (wartość sprzedaży netto)
  • stawki podatku
  • sumę wartości sprzedaży netto towarów lub wykonanych usług z podziałem na poszczególne stawki podatku, zwolnionych z podatku oraz niepodlegających opodatkowaniu
  • kwotę podatku od sumy wartości sprzedaży netto, z podziałem na kwoty dotyczące poszczególnych stawek podatku
  • wartość sprzedaży towarów lub wykonanych usług wraz z kwotą podatku (wartość sprzedaży brutto), z podziałem na kwoty dotyczące poszczególnych stawek podatku, zwolnionych z podatku lub niepodlegających opodatkowaniu
  • kwotę należności ogółem wraz z należnym podatkiem (brutto), wyrażoną cyframi i słownie
  • źródło: wikipedia

    Nie opisują jednak, jak dokładnie faktury mają wyglądać. Te wystawiane za pomocą InvoiceInvoker będą podobne do tworzonych przez program inFakt (jak już wcześniej wspominałem, zamierzam się czasem na nim wzorować), co, mam nadzieję, nie jest wykroczeniem. Po wielu godzinach tworzenia prostokątów, zmieniania ich rozmiarów i przesuwania o piksel, a także wypełniania tekstem o żmudnie dopasowywanych czcionkach, uzyskałem kod generujący akceptowalnie wyglądające dokumenty. Oto wyniki testów:
    [uwaga: arytmetyka nie była przedmiotem testów, dlatego nie należy szukać sensu w liczbach widniejących na fakturach]
    test_CreatesNormalInvoice
    test_HandlesVeryLongStrings
    test_SplitsProducts
    test_SplitsGeneralAmountsInfo
    test_SplitsPaymentInfo
    test_SplitsRemarks
    test_SplitsSignatures.

    Kodu klasy nie zamieszczam z oczywistego powodu: jest długi i nudny. Wytrwałym śmiałkom podpowiem jednak, że można go znaleźć tutaj. Wszystkim, którzy szukają sposobu na przeistoczenie kodu w dokument PDF, mogę natomiast polecić dokładniejsze zapoznanie się z PdfSharp.
    Wygląda na to, że warstwa logiki biznesowej jest już ukończona. Wkraczam więc na niepewny grunt: ASP.NET MVC, którego opanowanie jest jednym z głównych celów mojego udziału w konkursie. Stay tuned!

    Księga pierwsza: projektowanie bazy danych

    Zacznę od pytania: co chcę przechowywać w bazie? I natychmiast odpowiem: na pewno faktury. Zerknięcie na założenia projektowe (pkt. 1) pozwala rozszerzyć odpowiedź. Potrzebował będę również takich tabel jak: klienci, produkty, szablony faktur. Przydałoby się również miejsce na przechowanie danych o użytkownikach programu – tabela sprzedawcy.
    Mam już ogólny zarys bazy danych, więc zadaję kolejne pytanie: co powinna zawierać faktura VAT? Odpowiedź, już nie natychmiastową, przynoszą: wikipedia, serwis Moja firma, aplikacja inFakt (na której, nie ukrywam, zamierzam się podczas swojej pracy wzorować) i przykładowa faktura. Takie zagłębienie się w arkana ekonomii daje mi wiedzę potrzebną do stworzenia tabeli Invoices:
    1. Id [int] – identyfikator faktury
    2. SellerId [int] – sprzedawca [relacja]
    3. CustomerId [int] – klient [relacja]
    4. Number [nvarchar] – numer kolejny faktury
    5. CreationDate [datetime] – data wystawienia faktury
    6. SaleDate [datetime] – data sprzedaży
    7. PaymentType [nvarchar] – sposób płatności
    8. PaymentDeadline [datetime] – termin płatności
    9. PaymentCurrency [nvarchar] – waluta
    10. ToPay [money] – do zapłaty
    11. ToPayInWords [nvarchar] – słownie do zapłaty
    12. Paid [money] – zapłacono
    13. LeftToPay [money] – pozostało do zapłaty
    14. Remarks [nvarchar] – uwagi
    15. Status [nvarchar] – status faktury (utworzona / wystawiona / zapłacona)
    EDIT: 16. RegisteredSellerId [int] – użytkownik programu [relacja] /EDIT
    Nie ma tu pola mówiącego o produktach, ponieważ produkty zostaną połączone z fakturą relacją wiele (produktów) do jednej (faktury).

    Jak na razie idzie jak po maś taniej margarynie, a takiej nie można ufać. Dlatego przełknijmy i przetrawmy spokojnie to, co już zaprojektowałem. W utworzonej tabeli pojawiają się perspektywy relacji (np. relacja produkty – faktury), co nie powinno dziwić, ale powinno zastanowić. Wyobraźmy sobie bowiem taką sytuację: użytkownik wystawia fakturę na jakiś produkt, niech to będzie ta tania margaryna. Po miesiącu postanawia jednak podnieść jej cenę. Przy obecnej strukturze bazy danych, taka operacja spowodowałaby zmianę kwoty faktury sprzed miesiąca – hańba! Problem leży w tym, że produkty zdefiniowane przez użytkownika (podatne na edycję) są jednocześnie produktami przypisywanymi do faktur (ich parametrów zmieniać nie należy). Rozwiązaniem może być zastosowanie dwóch tabel produktów:
    1. produkty definiowane przez użytkownika,
    2. produkty przypisywane do faktur, niezmienne (niedostępne dla użytkownika);
    i tak też uczynię. Oczywiście podobnego zabiegu wymagają tabele klientów i sprzedawców.

    Możemy zatem zająć się zawartością kolejnych tabel. Podlinkowane wcześniej źródła wiedzy w zupełności wystarczą, w końcu baza będzie zawierać (prawie) jedynie dane potrzebne do wystawienia faktury. Godzinę (lektury, buszowania po inFakt i projektowania) później, mam (tabele Registered to tabele dla danych definiowanych przez użytkownika, pozostałe – dla danych „niezmiennych”, przypisywanych do faktur):

    RegisteredSellers:
    1. Id [int] – identyfikator sprzedawcy
    2. FirstName [nvarchar] – imię sprzedawcy
    3. LastName [nvarchar] – nazwisko sprzedacy
    4. CompanyName [nvarchar] – nazwa firmy sprzedawcy
    5. Street [nvarchar] – ulica [adres firmy]
    6. City [nvarchar] – miasto [adres firmy]
    7. PostalCode [nvarchar] – kod pocztowy [adres firmy]
    8. BankName [nvarchar] – nazwa banku
    9. BankAccountNumber [nvarchar] – numer konta firmy
    10. BankSwift [nvarchar] – swift banku (nieobowiązkowy)
    11. Nip [nvarchar] – NIP sprzedawcy
    12. Regon [nvarchar] – REGON firmy (nieobowiązkowy)
    13. InvoiceNumberFormat [nvarchar] – format numerów faktur (patrz pkt. 2 założeń projektu)
    14. LastInvoiceNumber [nvarchar] – numer kolejny ostatniej faktury

    Sellers: (opis kolumn jw.; zawiera tylko dane, które znajdą się na fakturze)
    1. Id [int]
    2. SellerName [nvarchar]
    3. CompanyName [nvarchar]
    4. Street [nvarchar]
    5. City [nvarchar]
    6. PostalCode [nvarchar]
    7. BankName [nvarchar]
    8. BankAccountNumber [nvarchar]
    9. BankSwift [nvarchar]
    10. Nip [nvarchar]

    RegisteredCustomers:
    1. Id [int] – identyfikator klienta
    2. CustomerName [nvarchar] – imię i nazwisko klienta
    3. CompanyName [nvarchar] – nazwa firmy klienta
    4. Street [nvarchar] – ulica [adres firmy]
    5. City [nvarchar] – miasto [adres firmy]
    6. PostalCode [nvarchar] – kod pocztowy [adres firmy]
    7. Nip [nvarchar] – NIP klienta
    8. Email [nvarchar] – e-mail klienta (nieobowiązkowy)
    9. Www [nvarchar] – strona www klienta (nieobowiązkowa)
    10. Phone [nvarchar] – telefon klienta (niebowiązkowy)
    11. Remarks [nvarchar] – uwagi (nieobowiązkowe)
    EDIT: 12. RegisteredSellerId [int] – użytkownik programu [relacja] /EDIT

    Customers: (opis kolumn jw.; zawiera tylko dane, które znajdą się na fakturze)
    1. Id [int]
    2. CustomerName [nvarchar]
    3. CompanyName [nvarchar]
    4. Street [nvarchar]
    5. City [nvarchar]
    6. PostalCode [nvarchar]
    7. Nip [nvarchar]

    RegisteredProducts: (opis kolumn niżej)
    1. Id [int]
    2. Name [nvarchar]
    3. Pkwiu [nvarchar]
    4. MeasureUnit [nvarchar]
    5. NetPrice [money]
    6. VatRate [nvarchar]
    EDIT: 7. RegisteredSellerId [int] – użytkownik programu [relacja] /EDIT

    Products: (wyjątkowo zawiera więcej kolumn niż tabela Registered)
    – Id [int] – identyfikator produktu
    – InvoiceId [int] – faktura [relacja]
    – Name [nvarchar] – nazwa produktu
    – Pkwiu [nvarchar] – klasyfikacja PKWiU produktu
    – MeasureUnit [nvarchar] – jednostka miary produktu
    – Quantity [money] – ilość (typ money dla uniknięcia konfliktu typów przy mnożeniu)
    – NetPrice [money] – cena netto produktu
    – NetValue [money] – wartość netto produktu (ilość * cena)
    – VatRate [nvarchar] – stawka VAT produktu
    – VatValue [money] – wartość VAT (wartość netto * stawka VAT)
    – GrossValue [money] – wartość brutto produktu (wartość netto + wartość VAT)

    Trochę się tego uzbierało, nie ma co. Ostatnie elementy podam na sucho, bez barwnego wprowadzenia, bo nie sądzę, żeby którykolwiek z czytelników dobrnął aż tutaj.

    InvoicePatterns: (tabela zawierająca szablony faktur)
    1. Id [int] – identyfikator szablonu
    2. RegisteredSellerId [int] – sprzedawca (z tabeli Registered) [relacja]
    3. RegisteredCustomerId [int] – klient (jw.) [relacja]
    4. PaymentType [nvarchar] – sposób płatności
    5. PaymentCurrency [nvarchar] – waluta
    6. Remarks [nvarchar] – uwagi

    InvoicePatternRegisteredProducts: (realizuje relację RegisteredProducts – InvoicePatterns)
    – InvoicePatternId [int] – szablon faktury [relacja]
    – RegisteredProductId [int] – produkt (z tabeli Registered) [relacja]

    Na deser (spodziewam się, że niewielu śmiałków dotrwa do deseru – niech żałują!) wypiszę zastosowane relacje:

    1. Sellers – Invoices (jeden do jednego)
    2. Customers – Invoices (jeden do jednego)
    3. Invoices – Products (jeden do wielu)

    4. RegisteredSellers – InvoicePatterns (jeden do wielu)
    5. RegisteredCustomers – InvoicePatterns (jeden do wielu)
    6. RegisteredProducts – InvoicePatterns (wiele do wielu)

    Et voila! Winszuję wytrwałym czytelnikom, zapowiadając jednocześnie, że kolejne wpisy nie będą tak długie i nużące.

    EDIT:
    Przejażdżka rowerem i świeże powietrze sprowadziły na mnie olśnienie: w żadnym miejscu nie przypisałem faktury do użytkownika! Niedopatrzenie naprawiłem dodając kolumnę RegisteredSellerId [int] do tabeli Invoices i relację RegisteredSellers – Invoices (jeden do wielu).
    Posłuszny celnej sugestii czytelnika tomash2310, zamieszczam również miły oku schemat bazy danych.

    EDIT 2:
    Zdałem sobie sprawę, że błąd popełniłem nie tylko w przypadku faktur, ale również klientów i produktów. Dodałem zatem odpowiednie kolumny (RegisteredSellerId [int] do tabel: RegisteredCustomers i RegisteredProducts) i relacje (RegisteredSellers – RegisteredCustomers i RegisteredSellers – RegisteredProducts). Zaktualizowałem też graficzny schemat bazy danych, kończąc tym samym, przynajmniej tymczasowo, pracę nad jej strukturą:

    database