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!
Mhhh chcialo Ci sie bawic w PDFsharp? mozna jeszcze wykorzystac SQL ReportViewer, do tego sql nie jest potrzebny.
tworzy sie normalnie raport ukladasz go wizualnie jak chcesz, nastepnie z kodu podpinasz pod niego zrodlo danych a nastepnie generujesz do PDFa.
IMO prosciej potem jest modyfikowac taka fakture na przyklad trzeba bedzie dodac pole do gornego prawego kwadratu, zmienic czcionke w dolnym lewym itp itd. latwiej jest operowac wizualnie niz w kodzie zmianiac polozenie i czcionki
IMO oczywiscie 
Dzięki za radę. Teraz to już po ptakach, ale przy następnej okazji nie omieszkam się temu przyjrzeć
Gratuluję wytrwałości! Zerknąłem na kod i widzę, że musiało Cię to sporo czasu kosztować
Ale efekt bardzo zgrabny, profesjonalne faktury.
Dziękuję
Naprawdę widać, że się napracowałeś. Ale czy przypadkiem generowanie PDF to nie jest już warstwa prezentacji?
http://en.wikipedia.org/wiki/Three-tier_(computing)#Three-tier_architecture
zawstydziłeś mnie tym kodem. Ale Ty dobrze kodujesz
@Agares: Z jednej strony, generowanie PDF na pewno jest związane z prezentacją (“…translate tasks and results to something the user can understand”). Jednak z drugiej strony, na pewno nie jest częścią interfejsu użytkownika (“The top-most level of the application is the user interface.”) – to przemawia za umieszczeniem tej funkcjonalności gdzieś poniżej warstwy prezentacji.
Chociaż głównym (praktycznym) powodem, dla którego wrzuciłem generowanie PDF do BLL, jest perspektywa stworzenia kilku wersji interfejsu użytkownika (oprócz ASP.NET MVC, na przykład WPF) – chcę wszystko, co da się odseparować od interfejsu, trzymać w osobnej dll-ce.
(cytaty wzięte z podanego linka)
@Michał Aniserowicz: W zasadzie, to można by się kłócić, jest pewnie kilka różnych zdań na ten temat. Grunt, że jesteś świadom tego co robisz, dlaczego i jakie są tego konsekwencje.
Osobiście nie przepadam za programowym budowaniem PDFów, generowanie faktur czy zamówień w ten sposób to makabra. Polecam TallComponents, gdzie cały dokument PDF można zbudować z arkusza XSL i dokumentu XML z danymi.
@okrycyusz: Miałem już kiedyś styczność z XSL (XML -> HTML), rzeczywiście tak byłoby wygodniej.
Zanosi się na to, że ukończenie projektu zajmie mi mniej niż wymagane przez regulamin konkursu 10 tygodni, więc porównanie PdfSharp, ReportViewer i TallComponents wydaje się być dobrym pomysłem na spożytkowanie pozostałego czasu.
Z chęcią przeczytałbym takie porównanie, gdyż w niedługim czasie również czeka mnie generowanie PDF’a na podstawie danych i jeszcze nie wiem do końca z czego skorzystać 😉
Kod jest ładny, jakkolwiek założyłeś że treść pozycji faktury nie będzie zbyt długa (skalowanie czcionek).. to niestety może się nie sprawdzić (mam dokładnie taki przypadek) – wiec pozostaje dość żmudny algorytm mierzenia długości napisu, dzielenia go na substringi i wydłużania rectangla (o kolejne linijki) przy jednoczesnym pilnowaniu przekroczenia obszaru strony. Altenatywą przy pozostaniu w pdfsharp jest rozszerzenie(*) pdfsharpa – MigraDoc, która pozwala na layout typu flow, a potem eksport do pdf.
Gdybyś jednak nie poszedł w stronę MigraDoc’a, podrzucam interesujący fragment kodu, który przyda się przy przy cięciu przydługiego tekstu do rectangla o zwiększonej wysokości:
static string Wrap(string text, int max)
{
return Regex.Replace(text, @”(^| +)([^rn]{0,” + max + @”}(?![wp{P}]))”, “$2rn”, RegexOptions.Multiline);
}
Pozdrawiam,
Dzięki za sugestie, prawdopodobnie w niedalekiej przyszłości spróbuję ulepszyć generowanie faktur.
Cięcia tekstu na linijki używam przy umieszczaniu na fakturze uwag i kwoty do zapłaty (słownie). Po pozostałych pozycjach rzeczywiście nie spodziewam się długiej treści – skromnie założyłem, że z programu nie będą korzystać korporacje, o długich nazwach i adresach (zapewne Walijskie), wystawiające faktury na miliardy jednostek monetarnych :).