ERP sistemleri, Delphi geliştirmenin tam merkezindedir: müşteriler, siparişler, faturalar, stok seviyeleri, üretim süreçleri. İş mantığı zaten Object Pascal'da yazılmıştır. Veritabanı bağlıdır. Raporlar çalışmaktadır. Genellikle eksik olan şey, yönetimin herhangi bir şey kurmadan bir tablette açabileceği, tarayıcı üzerinden erişilebilen bir panodur. Bu yazıda tam olarak bunun sgcHTML ile nasıl yapılacağını gösteriyoruz; gerçekçi bir senaryo üzerinden çalışıyoruz: canlı gelir rakamları, bir fatura tablosu, teslimat Kanban panosu ve yeni siparişler geldiğinde gerçek zamanlı anlık güncellemeler içeren bir satış ERP'si.
Tüm kod Delphi 10.4 Sydney ve sonraki sürümlerde derlenir. Sonuç olarak elde ettiğiniz şey, panoyu 8080 numaralı bağlantı noktasında sunan, Bootstrap 5 stil tasarımı, duyarlı düzen ve WebSocket canlı güncellemeleri içeren ve ikili dosyanın yanına dağıtılacak hiçbir JavaScript dosyası gerektirmeyen tek bir .exe dosyasıdır.
Sunucuyu kurma
sgcHTML, sgcWebSockets'tan bir TsgcWSHTTPServer bileşenini sarar. TsgcHTMLEngine_Server bileşenini veri modülüne HTTP sunucusunun yanına yerleştirin ve ikisini birbirine bağlayın. Motor, statik kaynak isteklerini (Bootstrap CSS/JS, Chart.js, htmx) otomatik olarak karşılar; geri kalanlar OnCommandGet işleyicinize yönlendirilir.
uses
sgcWebSocket_Server, sgcHTML_Engine_Server,
sgcHTML_Template_Bootstrap, sgcHTML_Page;
type
TERP_Server = class(TDataModule)
HTTPServer: TsgcWSHTTPServer;
HTMLEngine: TsgcHTMLEngine_Server;
private
procedure HandleGet(AContext: TIdContext;
AReq: TIdHTTPRequestInfo; AResp: TIdHTTPResponseInfo);
public
procedure Start(aPort: Integer);
end;
procedure TERP_Server.Start(aPort: Integer);
begin
HTTPServer.Port := aPort;
HTMLEngine.Server := HTTPServer;
HTTPServer.OnCommandGet := HandleGet;
HTTPServer.Active := True;
end;
procedure TERP_Server.HandleGet(AContext: TIdContext;
AReq: TIdHTTPRequestInfo; AResp: TIdHTTPResponseInfo);
begin
if AReq.Document = '/' then
ServeDashboard(AResp)
else if AReq.Document = '/invoices' then
ServeInvoices(AResp)
else if AReq.Document = '/kanban' then
ServeKanban(AResp)
else
AResp.ResponseNo := 404;
end;
KPI istatistik kartları
Panonun üst satırı dört KPI kartı gösterir: toplam gelir, açık faturalar, vadesi geçmiş fatura sayısı ve zamanında teslimat oranı. TsgcHTMLComponent_StatCard tüm görseli üstlenir: arka plan gradyanı, başlık, büyük değer, trend oku ve isteğe bağlı alt bilgi etiketi.
uses
sgcHTML_Component_StatCard;
procedure TERP_Server.BuildKPIRow(aPage: TsgcHTMLPage; aDB: TFDConnection);
function MakeCard(aPage: TsgcHTMLPage; const aTitle, aValue: string;
aColor: TsgcHTMLStatColor; aTrend: TsgcHTMLStatTrend;
const aTrendValue, aFooter: string; aOrder: Integer): TsgcHTMLComponent_StatCard;
begin
Result := TsgcHTMLComponent_StatCard.Create(nil);
Result.PageBuilder := aPage.PageBuilder;
Result.Section := 'kpi';
Result.SectionOrder := aOrder;
Result.ColumnWidth := cw3;
Result.Title := aTitle;
Result.Value := aValue;
Result.Color := aColor;
Result.Trend := aTrend;
Result.TrendValue := aTrendValue;
Result.FooterText := aFooter;
Result.Gradient := sgBlueViolet;
end;
var
oQ: TFDQuery;
begin
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text := 'SELECT SUM(total) FROM invoices WHERE YEAR(issued)=YEAR(NOW())';
oQ.Open;
MakeCard(aPage, 'YTD Revenue', FormatCurr('$#,##0', oQ.Fields[0].AsFloat),
scPrimary, stUp, '+14%', 'vs last year', 1);
oQ.Close;
oQ.SQL.Text := 'SELECT COUNT(*) FROM invoices WHERE status=''open''';
oQ.Open;
MakeCard(aPage, 'Open Invoices', oQ.Fields[0].AsString,
scInfo, stNeutral, '', 'awaiting payment', 2);
oQ.Close;
oQ.SQL.Text := 'SELECT COUNT(*) FROM invoices WHERE status=''overdue''';
oQ.Open;
MakeCard(aPage, 'Overdue', oQ.Fields[0].AsString,
scDanger, stDown, '-3', 'vs last month', 3);
oQ.Close;
oQ.SQL.Text := 'SELECT ROUND(100.0*SUM(on_time)/COUNT(*),1) FROM deliveries';
oQ.Open;
MakeCard(aPage, 'On-Time Delivery', oQ.Fields[0].AsString + '%',
scSuccess, stUp, '+2pp', 'last 30 days', 4);
oQ.Close;
finally
oQ.Free;
end;
end;
Gelir trendi grafiği
Gelir grafiği ikinci satıra yayılır. TsgcHTMLComponent_Chart, etiket dizilerini ve kenarlık ile dolgu renkleri olan veri kümelerini kabul eder. LoadFromDataSet, bir sorgu sonucunu tek bir çağrıyla grafik verisine eşler: veri kümesini, etiket alanını (ay adı) ve bir veya daha fazla değer alanını (gelir, maliyet) geçirin.
uses
sgcHTML_Component_Chart;
procedure TERP_Server.BuildRevenueChart(aPage: TsgcHTMLPage; aDB: TFDConnection);
var
oChart: TsgcHTMLComponent_Chart;
oQ: TFDQuery;
begin
oChart := TsgcHTMLComponent_Chart.Create(nil);
oChart.PageBuilder := aPage.PageBuilder;
oChart.Section := 'charts';
oChart.SectionTitle := 'Revenue';
oChart.SectionOrder := 1;
oChart.ColumnWidth := cw8;
oChart.ChartType := ctBar;
oChart.Title := 'Monthly Revenue vs Cost (last 12 months)';
oChart.ShowLegend := True;
oChart.Stacked := False;
oChart.Responsive := True;
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text :=
'SELECT DATE_FORMAT(issued,''%b'') AS month, ' +
' SUM(total) AS revenue, ' +
' SUM(cost) AS cost ' +
'FROM invoices ' +
'WHERE issued >= DATE_SUB(NOW(), INTERVAL 12 MONTH) ' +
'GROUP BY YEAR(issued), MONTH(issued) ' +
'ORDER BY YEAR(issued), MONTH(issued)';
oQ.Open;
// One call maps the query to labels + two datasets
oChart.LoadFromDataSet(oQ, 'month', ['revenue', 'cost']);
finally
oQ.Free;
end;
end;
Sayfalandırılmış fatura tablosu
TsgcHTMLComponent_DataTable, bir TsgcHTMLComponent_Grid ile bir TsgcHTMLComponent_Pagination bileşenini yerleşik arama kutusu, satır sayısı etiketi, isteğe bağlı dışa aktarma düğmesi ve yapılandırılabilir sayfa boyutlarıyla tek bir widget'ta bir araya getirir. LoadFromDataSet, sütun adlarını ve türlerini otomatik olarak okur; daha sonra Grid.Columns aracılığıyla başlık etiketlerini ve sütun genişliklerini geçersiz kılabilirsiniz.
uses
sgcHTML_Component_DataTable;
procedure TERP_Server.BuildInvoiceTable(aPage: TsgcHTMLPage; aDB: TFDConnection);
var
oTable: TsgcHTMLComponent_DataTable;
oQ: TFDQuery;
begin
oTable := TsgcHTMLComponent_DataTable.Create(nil);
oTable.PageBuilder := aPage.PageBuilder;
oTable.Section := 'invoices';
oTable.SectionTitle := 'Invoices';
oTable.SectionOrder := 1;
oTable.ColumnWidth := cw12;
oTable.Title := 'Recent Invoices';
oTable.ShowSearch := True;
oTable.ShowExport := True;
oTable.ShowPageSize := True;
oTable.PageSizes := '10,25,50';
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text :=
'SELECT number, customer, issued, due, total, status ' +
'FROM invoices ' +
'ORDER BY issued DESC ' +
'LIMIT 200';
oQ.Open;
oTable.LoadFromDataSet(oQ, 20);
finally
oQ.Free;
end;
end;
Sipariş karşılama için Kanban panosu
TsgcHTMLComponent_KanbanBoard, çok sütunlu, sürükle-bırak destekli bir panoyu temsil eder. Her sütun bir kart koleksiyonu barındırır; her kart bir başlık, isteğe bağlı açıklama, sorumlu kişi, etiket ve renk taşır. ERP bağlamında sütunlar doğal olarak sipariş durumlarıyla eşleşir: Alındı, Hazırlanıyor, Paketlendi, Gönderildi.
uses
sgcHTML_Component_KanbanBoard;
procedure TERP_Server.BuildKanban(aPage: TsgcHTMLPage; aDB: TFDConnection);
var
oBoard: TsgcHTMLComponent_KanbanBoard;
oCol: TsgcHTMLKanbanColumn;
oQ: TFDQuery;
vStatus: string;
begin
oBoard := TsgcHTMLComponent_KanbanBoard.Create(nil);
oBoard.PageBuilder := aPage.PageBuilder;
oBoard.Section := 'kanban';
oBoard.SectionTitle := 'Order Fulfilment';
oBoard.SectionOrder := 1;
oBoard.ColumnWidth := cw12;
// Create the four columns
for vStatus in ['Received', 'Picking', 'Packed', 'Shipped'] do
begin
oCol := oBoard.Columns.Add;
oCol.Title := vStatus;
oCol.Color := hcLight;
end;
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text :=
'SELECT order_no, customer, qty_total, assigned_to, status ' +
'FROM orders ' +
'WHERE status IN (''Received'',''Picking'',''Packed'',''Shipped'') ' +
'ORDER BY created DESC LIMIT 100';
oQ.Open;
while not oQ.Eof do
begin
// Find the column whose title matches the order status
oCol := oBoard.Columns.FindByTitle(oQ.FieldByName('status').AsString);
if Assigned(oCol) then
oCol.AddCard(
oQ.FieldByName('order_no').AsString,
oQ.FieldByName('customer').AsString + ' — ' +
oQ.FieldByName('qty_total').AsString + ' items',
hcLight,
oQ.FieldByName('assigned_to').AsString
);
oQ.Next;
end;
finally
oQ.Free;
end;
end;
Tam sayfayı bir araya getirme
Dört bölüm, tek bir ServeDashboard yordamı tarafından bir araya getirilir; bu yordam bir sayfa oluşturur, her oluşturucuyu çağırır, sonucu Bootstrap şablonuyla sarar ve yanıta yazar:
procedure TERP_Server.ServeDashboard(AResp: TIdHTTPResponseInfo);
var
oPage: TsgcHTMLPage;
oTemplate: TsgcHTMLTemplate_Bootstrap;
begin
oPage := TsgcHTMLPage.Create(nil);
oTemplate := TsgcHTMLTemplate_Bootstrap.Create(nil);
try
BuildKPIRow(oPage, FDB);
BuildRevenueChart(oPage, FDB);
BuildInvoiceTable(oPage, FDB);
BuildKanban(oPage, FDB);
oTemplate.Title := 'ERP Dashboard';
oTemplate.Page := oPage;
oTemplate.DarkMode := False;
oTemplate.BodyClass := 'bg-light';
AResp.ContentType := 'text/html; charset=utf-8';
AResp.ContentText := oTemplate.GetHTML;
AResp.ResponseNo := 200;
finally
oPage.Free;
oTemplate.Free;
end;
end;
WebSocket üzerinden canlı güncellemeler
Pano, canlı veri olmadan statik kalırdı. sgcHTML, sgcWebSockets'ın üzerine kurulduğundan, yeni bir sipariş verildiğinde bir parça güncellemesi yayımlamak tek bir yayın çağrısından ibarettir. İstemci tarafında istatistik kartı bir id niteliği taşır; htmx WebSocket uzantısı ise bir mesaj geldiğinde içeriğini değiştirir.
// Called from the order-processing thread when a new order is saved
procedure TERP_Server.OnNewOrderSaved(aOrder: TERPOrder);
var
oCard: TsgcHTMLComponent_StatCard;
begin
// Rebuild the "Open Invoices" KPI card with the updated count
oCard := TsgcHTMLComponent_StatCard.Create(nil);
try
oCard.CardID := 'kpi-open-invoices';
oCard.Title := 'Open Invoices';
oCard.Value := IntToStr(FDB.OpenInvoiceCount);
oCard.Color := scInfo;
oCard.Trend := stUp;
// Broadcast the new card HTML to all connected dashboard clients
HTTPServer.Broadcast(oCard.HTML, '/dashboard');
finally
oCard.Free;
end;
end;
Parça, sayfa yenilenmeksizin ve kullanıcının herhangi bir şeye tıklaması gerekmeksizin tarayıcıdaki eşleşen kartın yerini hemen alır. Bağlı olan her istemci güncellemeyi milisaniyeler içinde görür.
Sonuçta elde ettiğiniz şey
Tam örnek tek bir Delphi birimi ve bir veri modülünden oluşur. Derleyin ve çalıştırın: yapılandırılan bağlantı noktasında dört KPI kartı, 12 aylık gelir-maliyet karşılaştırmalı çubuk grafik, aranabilir ve sayfalandırılmış fatura tablosu ile sipariş karşılama için çok sütunlu bir Kanban panosu içeren, tarayıcı üzerinden erişilebilen bir ERP panosu görüntülenir. Yeni siparişler verildiğinde KPI kartları, bağlı her tarayıcı sekmesinde gerçek zamanlı olarak güncellenir. Yazdığınız ön yüz kodu: sıfır satır.
Aynı yaklaşım her türlü Delphi iş uygulamasında işe yarar: stok yönetimi, üretim planlaması, İK sistemleri, filo takibi. Veriler bir TDataSet'te bulunuyorsa bir sabahın içinde canlı bir web panosuna taşınabilir.
sgcHTML ücretsiz deneme sürümünü esegece.com/products/sgchtml/download adresinden indirin ve tam çalışan bir örnek için içinde yer alan ERP demosu, Admin Konsolu, Canlı İzleme ve Müşteri Portalı uygulamalarına göz atın.
Sorularınız mı var? Bizimle iletişime geçin. Kodu yazan kişilerden yanıt alacaksınız.
