Jak odczytywać dane z plików PDF w ASP.NET Core
Problem z ręcznym generowaniem etykiet i formularzy spedycyjnych
Na podłodze magazynu przetwarzającej 500 zamówień dziennie na dwie zmiany, cykl pick-pack-ship nie znosi tarć. Drukowanie formularzy jeden po drugim z ekranu OMS, ręczne kopiowanie szczegółów zamówienia do szablonu etykiety, czy oczekiwanie na narzędzie do etykietowania, które nie integruje się czysto z backendem .NET - każda z tych rzeczy dodaje sekundy na zamówienie, które składają się na rzeczywiste utraty przepustowości do końca zmiany.
Ryzyko błędu potęguje się wraz z kosztem czasowym. Gdy adresy wysyłki są ręcznie przepisywane zamiast bezpośredniego wyciągania z rekordu zamówienia, zdarzają się nieporozumienia związane z wysyłką. Zwrócony przesył z powodu błędnego adresu kosztuje więcej w robociźnie i opłatach przewoźnika niż marża na większości indywidualnych zamówień. Narzędzia do etykietowania dostarczane przez przewoźnika rozwiązują problem adresu, ale nie produkują szczegółowego formularza spedycyjnego, którego potrzebuje magazyn do weryfikacji zawartości pudełka przed jego zamknięciem, które wciąż wymagają oddzielnego wydruku z innego systemu.
Drukowanie pakietowe to miejsce, gdzie większość podejść całkowicie się załamuje. Menadżer operacji, który uwalnia falę 200 zamówień, potrzebuje tych formularzy gotowych na początku zmiany, a nie generowanych jeden po drugim przez interfejs sieciowy, który otwiera nowe okno dialogowe wydruku dla każdego zamówienia. Format musi działać na każdej drukarce na podłodze, czy to laserowej, atramentowej, czy termicznej, bez instalacji galopowych sterowników na stanowiskach pracy w magazynie.
Scenariusze są spójne: marka skierowana bezpośrednio do konsumenta pakująca setki zamówień dziennie, dystrybutor B2B generujący formularze wielopoziomowe do wysyłki paletowej, serwis subskrypcyjny drukujący listy kontrolne przedmiotów dla pracowników linii montażowej, dostawca 3PL produkujący formularze markowe na rzecz wielu klientów przedsiębiorców z różnymi logotypami i adresami zwrotów.
Rozwiązanie: Pliki PDF gotowe do druku generowane bezpośrednio z danych zamówienia przy użyciu biblioteki IronPDF
IronPDF pozwala .NET OMS na generowanie gotowych do druku PDF formularzy spedycyjnych i etykiet bezpośrednio z danych zamówienia. Gdy zamówienie wchodzi w etap "gotowe do pakowania", aplikacja pobiera elementy linii, adres wysyłki, informacje przewoźnika i specjalne instrukcje, wypełnia szablon HTML o rozmiarze odpowiednim dla docelowego formatu papieru, a ChromePdfRenderer produkuje PDF.
Personel magazynu drukuje wynik na dowolnej standardowej lub termicznej drukarce, jeden dokument na zamówienie lub zbiorowy wielostronicowy PDF obejmujący całą falę zbiórki w jednym zadaniu wydruku. Nie ma oprogramowania do etykietowania markowego do zainstalowania, nie ma ręcznego kroku wprowadzania danych i nie ma opłaty za API na etykietę. Renderowanie uruchamia się w istniejącej aplikacji .NET jako jeden pakiet NuGet bez procesów zewnętrznych.
Jak to działa w praktyce
1. Zamówienia trafiają do kolejki pakowania
Zamówienie przechodzi do "gotowe do pakowania" automatycznie po potwierdzeniu płatności lub gdy lider magazynu uwalnia falę zbiórki z OMS. Aplikacja zapytuje bazę danych zamówień dla każdego zamówienia w partii, elementy linii z numerem SKU, nazwą produktu i ilością; Adres wysyłki; Przewoźnik i poziom usług; Wstępnie przypisany numer śledzenia, jeśli jest dostępny; Oraz wszelkie specjalne instrukcje obsługi lub wiadomości prezentowe.
Każde zamówienie w fali dostaje swój własny PDF wygenerowany z tego samego szablonu HTML, wypełniony swoimi specyficznymi danymi. Dla fali 200 zamówień to 200 renderów - każdy zajmujący milisekundy - zanim wyniki zostaną połączone w jeden dokument do zadania wydruku.
2. Szablony obsługują oba typy formatów
Szablon formularza spedycyjnego (letter lub A4) zawiera tabelę elementów linii z polami wyboru dla pakujących w celu zweryfikowania każdego przedmiotu, numer zamówienia, adres dostawy i skanowalny ID zamówienia. Szablon etykiety ma na celu wymiar 4×6 cali (standardowy rozmiar etykiety termicznej) i zawiera adres dostawy, adres zwrotny, obszar kodu kreskowego przewoźnika oraz wagę przesyłki.
Oba szablony to pliki HTML i CSS należące do zespołu aplikacji. Aktualizacja formularza spedycyjnego w celu dodania nowej kolumny, zmiana logo przedsiębiorcy na formularzu 3PL lub dostosowanie marginesów etykiety do nowego modelu drukarki termicznej to zmiana pliku szablonu, bez rekompilacji, bez ponownego wdrażania logiki biznesowej.
Przykładowy szablon pliku HTML

3. Renderowanie pakietowe i łączenie w jedno zadanie wydruku
using IronPdf;
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;
renderer.RenderingOptions.MarginTop = 10;
renderer.RenderingOptions.MarginBottom = 10;
string slipTemplate = await File.ReadAllTextAsync("Templates/packing-slip.html");
var slipPdfs = new List<PdfDocument>();
foreach (var order in pickWave)
{
string lineItemRows = string.Concat(order.Items.Select(i =>
$"<tr><td><input type='checkbox'></td><td>{i.Sku}</td><td>{i.ProductName}</td><td>{i.Quantity}</td></tr>"));
string html = slipTemplate
.Replace("{{OrderNumber}}", order.OrderNumber)
.Replace("{{ShipTo}}", order.ShippingAddress.Formatted)
.Replace("{{LineItems}}", lineItemRows)
.Replace("{{SpecialInstructions}}", order.SpecialInstructions ?? "None");
slipPdfs.Add(renderer.RenderHtmlAsPdf(html));
}
PdfDocument waveBatch = PdfDocument.Merge(slipPdfs);
waveBatch.SaveAs($"wave-{pickWave.WaveId}-slips.pdf");
using IronPdf;
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;
renderer.RenderingOptions.MarginTop = 10;
renderer.RenderingOptions.MarginBottom = 10;
string slipTemplate = await File.ReadAllTextAsync("Templates/packing-slip.html");
var slipPdfs = new List<PdfDocument>();
foreach (var order in pickWave)
{
string lineItemRows = string.Concat(order.Items.Select(i =>
$"<tr><td><input type='checkbox'></td><td>{i.Sku}</td><td>{i.ProductName}</td><td>{i.Quantity}</td></tr>"));
string html = slipTemplate
.Replace("{{OrderNumber}}", order.OrderNumber)
.Replace("{{ShipTo}}", order.ShippingAddress.Formatted)
.Replace("{{LineItems}}", lineItemRows)
.Replace("{{SpecialInstructions}}", order.SpecialInstructions ?? "None");
slipPdfs.Add(renderer.RenderHtmlAsPdf(html));
}
PdfDocument waveBatch = PdfDocument.Merge(slipPdfs);
waveBatch.SaveAs($"wave-{pickWave.WaveId}-slips.pdf");
Imports IronPdf
Imports System.IO
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter
renderer.RenderingOptions.MarginTop = 10
renderer.RenderingOptions.MarginBottom = 10
Dim slipTemplate As String = Await File.ReadAllTextAsync("Templates/packing-slip.html")
Dim slipPdfs As New List(Of PdfDocument)()
For Each order In pickWave
Dim lineItemRows As String = String.Concat(order.Items.Select(Function(i) $"<tr><td><input type='checkbox'></td><td>{i.Sku}</td><td>{i.ProductName}</td><td>{i.Quantity}</td></tr>"))
Dim html As String = slipTemplate _
.Replace("{{OrderNumber}}", order.OrderNumber) _
.Replace("{{ShipTo}}", order.ShippingAddress.Formatted) _
.Replace("{{LineItems}}", lineItemRows) _
.Replace("{{SpecialInstructions}}", If(order.SpecialInstructions, "None"))
slipPdfs.Add(renderer.RenderHtmlAsPdf(html))
Next
Dim waveBatch As PdfDocument = PdfDocument.Merge(slipPdfs)
waveBatch.SaveAs($"wave-{pickWave.WaveId}-slips.pdf")
Przykładowy dokument wyjściowy PDF
PdfDocument.Merge() łączy każdy indywidualny formularz w jeden wielostronicowy PDF. Zespół magazynowy wysyła jedno zadanie wydruku dla całej fali, jedno kliknięcie na stacji roboczej, jedna taca wyjściowa do zebrania.
4. Wymiary etykiet ustawione przez opcje renderowania
using IronPdf;
var renderer = new ChromePdfRenderer();
// 4x6 inch thermal label — 288 x 432 points
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Custom;
renderer.RenderingOptions.CustomPaperWidth = 4;
renderer.RenderingOptions.CustomPaperHeight = 6;
renderer.RenderingOptions.MarginTop = 0;
renderer.RenderingOptions.MarginBottom = 0;
renderer.RenderingOptions.MarginLeft = 0;
renderer.RenderingOptions.MarginRight = 0;
string labelHtml = $@"
<div style='font-family:monospace; padding:4px;'>
<p><strong>SHIP TO:</strong><br/>{order.ShippingAddress.Formatted}</p>
<p><strong>FROM:</strong><br/>{order.ReturnAddress.Formatted}</p>
<p>Order: {order.OrderNumber} | Weight: {order.WeightLbs:F1} lbs</p>
<p>Carrier: {order.CarrierName} — {order.ServiceLevel}</p>
</div>";
PdfDocument label = renderer.RenderHtmlAsPdf(labelHtml);
label.SaveAs($"label-{order.OrderNumber}.pdf");
using IronPdf;
var renderer = new ChromePdfRenderer();
// 4x6 inch thermal label — 288 x 432 points
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Custom;
renderer.RenderingOptions.CustomPaperWidth = 4;
renderer.RenderingOptions.CustomPaperHeight = 6;
renderer.RenderingOptions.MarginTop = 0;
renderer.RenderingOptions.MarginBottom = 0;
renderer.RenderingOptions.MarginLeft = 0;
renderer.RenderingOptions.MarginRight = 0;
string labelHtml = $@"
<div style='font-family:monospace; padding:4px;'>
<p><strong>SHIP TO:</strong><br/>{order.ShippingAddress.Formatted}</p>
<p><strong>FROM:</strong><br/>{order.ReturnAddress.Formatted}</p>
<p>Order: {order.OrderNumber} | Weight: {order.WeightLbs:F1} lbs</p>
<p>Carrier: {order.CarrierName} — {order.ServiceLevel}</p>
</div>";
PdfDocument label = renderer.RenderHtmlAsPdf(labelHtml);
label.SaveAs($"label-{order.OrderNumber}.pdf");
Imports IronPdf
Dim renderer As New ChromePdfRenderer()
' 4x6 inch thermal label — 288 x 432 points
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Custom
renderer.RenderingOptions.CustomPaperWidth = 4
renderer.RenderingOptions.CustomPaperHeight = 6
renderer.RenderingOptions.MarginTop = 0
renderer.RenderingOptions.MarginBottom = 0
renderer.RenderingOptions.MarginLeft = 0
renderer.RenderingOptions.MarginRight = 0
Dim labelHtml As String = $"
<div style='font-family:monospace; padding:4px;'>
<p><strong>SHIP TO:</strong><br/>{order.ShippingAddress.Formatted}</p>
<p><strong>FROM:</strong><br/>{order.ReturnAddress.Formatted}</p>
<p>Order: {order.OrderNumber} | Weight: {order.WeightLbs:F1} lbs</p>
<p>Carrier: {order.CarrierName} — {order.ServiceLevel}</p>
</div>"
Dim label As PdfDocument = renderer.RenderHtmlAsPdf(labelHtml)
label.SaveAs($"label-{order.OrderNumber}.pdf")
Przykładowy plik wyjściowy PDF i etykieta wysyłkowa
Zerowe marginesy pozwalają na to, aby CSS kontrolował całe odstępy do krawędzi etykiety, co jest krytyczne dla formatów etykiet termicznych, gdzie obszar kodu kreskowego przewoźnika musi sięgnąć w ciągu kilku milimetrów od granicy papieru.
Korzyści w praktyce
Wydajność pakietowa. Cała fala zbiórki połączona w jeden PDF oznacza jedno zadanie drukowania zamiast setek. Czas rozpoczęcia zmiany skraca się, gdy personel magazynu nie czeka na załadowanie poszczególnych okien dialogowych wydruku dla każdego zamówienia.
Kompatybilność drukowania. PDF renderuje się na każdej drukarce laserowej, atramentowej i termicznej bez instalacji galopowych sterowników lub oprogramowania etykiet na stanowiskach magazynowych. Format, który produkuje OMS, to format, każdy drukarka na podłożu akceptuje.
Dokładność. Elementy linii i adresy są bezpośrednio pobierane z rekordu zamówienia. Nie ma kroku ręcznej transkrypcji i nie ma ścieżki, przez którą adres lub SKU może zostać wprowadzony niepoprawnie, zanim trafi do pakującego.
Elastyczne formaty. Ta sama linia generowania produkuje formularze pakowania w rozmiarze listu i etykiety 4×6, zmieniając szablon HTML i opcje rozmiaru strony. Bez oddzielnego narzędzia, bez oddzielnej integracji.
Branding wielu wynajmów. Dostawcy 3PL wypełniają zmienną merchantId w ścieżce szablonu i ładują HTML pliku specyficznego dla przedsiębiorcy, każda partia klientów nosi swoje własne logo, adres zwrotny i kolory marki przez tę samą linię, bez dodatkowej logiki.
Brak kosztów za etykietę. Renderowanie działa w procesie. Nie ma trzeciego API etykietowego wywoływanego przy każdym wydruku, nie ma mierzenia użycia i nie ma modelu cenowego, który sprawia, że zmiana dużej objętości jest droższa niż zmiana małej objętości.
Zakończenie
Cykl pick-pack-ship to problem przepustowości. Każdy krok ręczny między wejściem zamówienia do kolejki pakowania a wyjściem oznaczonego pudełka z dokiem jest ograniczeniem, a generowanie formularzy spedycyjnych i etykiet to jeden z nich, które można łatwo zautomatyzować wewnątrz samego OMS.
Jeden szablon HTML na format, jedno wywołanie renderowania na zamówienie, jeden scalony PDF na falę, jedno zadanie wydruku na zmianę. IronPDF obsługuje cały cykl życia generowania PDF w C# na ironpdf.com, od renderowania HTML szablonów po łączenie, zapisywanie i przesyłanie dokumentów strumieniowych. Jeśli jesteś gotowy do przetestowania linii na własnych danych zamówień i konfiguracji druku, rozpocznij darmową 30-dniową próbę i uruchom działający generator formularzy zbiorczych przed końcem tygodnia.




