Refresh

In Bill .Net Core API

     Bài viết này sẽ hướng dẫn cách thực hiện chức năng in ra giấy (Print) bằng API trong .Net Core, tích hợp chức năng in vào ứng dụng của bạn để cung cấp trải nghiệm của người dùng tốt nhất khi sử dụng dịch vụ của các bạn và mang lại nhiều lợi ích hiệu quả như là :

  1. Tính linh hoạt cho hệ thống.
  2. Tiết kiệm thời gian.
  3. Chính xác và tính toàn vẹn cao.
  4. Tích hợp dễ dàng.
  5. Tăng trải nghiệm người dùng.
Không lan man nửa, bắt đầu vô việc thôi nào.


- Bước 1: Chuẩn bị Máy In và Cài đặt Driver của máy.
     Tuỳ theo máy in mà có mỗi Driver khác nhau, máy mà tôi đang sử dụng là IPOS.VN:



- Bước 2 : Tiếp đến là cài đặt Driver cho máy in:
Mình sẽ để link Driver, có thể down về dùng nhé: 

https://drive.google.com/drive/folders/1DNbNP9CI3AM3e4rCN9BH4m7QRL5Ez4Jn?usp=sharing 

Khi download về thì Run  App bằng quyền Administrator để cài đặt. Mình đang dùng cổng USB để kết nối với Máy in nên setup như các bước như hình dưới: 

    + 1. Settings  =>  Bluetooth & devices => Chọn đến Devices Print mà đã Install.


    + 2. Chọn vào Printer properties.


    + 3.  Chọn General. Có thể thay đổi tên của Máy in, để có thể dễ nhận biết hơn.



    + 4. Chọn Preferences để setting => chọn Paper/Quality => Media : No Cash Drawer.



    + 5. Tiếp đến thì chọn Ports =>  Chọn vào USB (Máy in có 2 cách để kết nối với máy để in hoá đơn là Wifi và USB, do hiện tại mình đang sử dụng máy là USB nên chọn vào USB, còn kết nối Wifi thì các bạn tự tìm hiểu nhé!). Sau đó Apply và Click OK nhé.


    + 6. Chọn Printing preferences => Paper/Quality => chọn option No Cash Drawer.


    + 7. Sau đó để test thử máy in đã được kết nối và hoạt động hay chưa. Thì Click vào Print Test Page.



- Bước 3: Tiếp theo sẽ viết API để Print Bill.
    + Cài đặt thư viện: Thư viện ở đây chúng ta dùng đó là System.Drawing.Common.



Hoặc có thể sử dụng lệnh command sau : 

PM> Install-Package System.Drawing.Common

- Bước 4: Sử dụng thư viện vừa install và viết 1 Function để xử lý việc in này.

using Application.Dtos.Responses.Order;
using Application.Interfaces.Repositories.PrintBill;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Printing;
using System.Text;

namespace Infrastructure.Repositories.PrintBill
{
    public class PrintBillRepository : IPrintBillRepository
    {
        public void PrintPage(object sender, PrintPageEventArgs e)
        {
            // Lấy dữ liệu của hoá đơn
            var data = PrintBillData();

            // Màu chữ
            SolidBrush printBrush = new SolidBrush(Color.Black);

            // Tên nhà hàng
            string restaurantName = "LA MAISON 1888";
            Font nameFont = new Font("Arial", 9, FontStyle.Bold);

            // Địa chỉ
            string address = "Địa chỉ: 100 Hoàng Diệu - Hải Châu - Đà Nẵng";
            Font addressFont = new Font("Arial", 9, FontStyle.Regular);

            // Điện thoại
            string phoneNumber = "Điện thoại: 0965.223.932";
            Font phoneFont = new Font("Arial", 10, FontStyle.Regular);

            // Đường gạch ngang
            Font lineFont = new Font("Arial", 11, FontStyle.Regular);

            // Đối tượng Pen cho đường gạch đứt
            Pen dashedPen = new Pen(Color.Black);
            dashedPen.DashStyle = DashStyle.Dash;

            // Kích thước giấy
            float paperWidth = 300;

            // Vị trí vẽ, chiều ngang căn giữa
            PointF startPoint = new PointF((paperWidth - e.Graphics.MeasureString(restaurantName, nameFont).Width) / 2, 10);

            // Vẽ chuỗi căn giữa
            e.Graphics.DrawString(restaurantName, nameFont, printBrush, startPoint);

            // Khoảng cách giữa các hàng tiếp theo (Cộng thêm chiều cao của đoạn trước để vẽ đoạn tiếp theo)
            startPoint.Y += e.Graphics.MeasureString(restaurantName, nameFont).Height;

            // In Địa chỉ
            e.Graphics.DrawString(address, addressFont, printBrush, new PointF((paperWidth - e.Graphics.MeasureString(address, addressFont).Width) / 2, startPoint.Y));

            // Khoảng cách giữa các hàng tiếp theo (Cộng thêm chiều cao của đoạn trước để vẽ đoạn tiếp theo)
            startPoint.Y += e.Graphics.MeasureString(address, addressFont).Height;

            // In Điện thoại
            e.Graphics.DrawString(phoneNumber, phoneFont, printBrush, new PointF((paperWidth - e.Graphics.MeasureString(phoneNumber, phoneFont).Width) / 2, startPoint.Y));

            // Khoảng cách giữa các hàng tiếp theo (Cộng thêm chiều cao của đoạn trước để vẽ đoạn tiếp theo)
            startPoint.Y += e.Graphics.MeasureString(phoneNumber, phoneFont).Height;

            // Đường gạch đứt
            float lineY = startPoint.Y + e.Graphics.MeasureString("__________________________________________________________", lineFont).Height / 2;
            float startX = 20;  // Thay đổi tùy thuộc vào vị trí bạn muốn bắt đầu vẽ đường gạch đứt
            float endX = paperWidth - 20;  // Thay đổi tùy thuộc vào vị trí bạn muốn kết thúc vẽ đường gạch đứt
            e.Graphics.DrawLine(dashedPen, startX, lineY, endX, lineY);

            // Ngày bán
            string saleDate = "Ngày bán: " + data.DateSale?.ToString("dd/MM/yyyy HH:mm");
            Font dateFont = new Font("Arial", 9, FontStyle.Regular);
            float dateY = startPoint.Y + e.Graphics.MeasureString("__________________________________________________________", lineFont).Height;
            float dateX = 20; // Điều chỉnh vị trí x cho ngày bán
            e.Graphics.DrawString(saleDate, dateFont, printBrush, new PointF(dateX, dateY));

            // Hoá đơn tạm tính
            string receiptHeader = "HĐ TẠM TÍNH";
            Font headerFont = new Font("Arial", 11, FontStyle.Bold);
            float headerY = dateY + e.Graphics.MeasureString(saleDate, dateFont).Height + 5; // Cộng thêm một khoảng trống
            e.Graphics.DrawString(receiptHeader, headerFont, printBrush, new PointF((paperWidth - e.Graphics.MeasureString(receiptHeader, headerFont).Width) / 2, headerY));

            // Người bán (giả sử bạn có một hàm để lấy thông tin từ cơ sở dữ liệu)
            string salesman = data.Seller;
            Font salesmanFont = new Font("Arial", 9, FontStyle.Regular);
            float salesmanY = headerY + e.Graphics.MeasureString(receiptHeader, headerFont).Height + 5; // Cộng thêm một khoảng trống
            float salesmanX = 20; // Điều chỉnh vị trí cho chữ người bán
            e.Graphics.DrawString("Người bán: " + salesman, salesmanFont, printBrush, new PointF(salesmanX, salesmanY));

            // Giờ vào
            string saleDateTime = "Giờ vào: " + data.TimeEnter?.ToString("dd/MM/yyyy HH:mm");
            Font dateTimeFont = new Font("Arial", 9, FontStyle.Regular);
            float dateTimeY = salesmanY + e.Graphics.MeasureString(salesman, salesmanFont).Height + 5; // Cộng thêm một khoảng trống
            float dateTimeX = 20; // Điều chỉnh vị trí cho giờ vào
            e.Graphics.DrawString(saleDateTime, dateTimeFont, printBrush, new PointF(dateTimeX, dateTimeY));

            // Bàn
            string tableOrder = data.TableOrder; 
            Font tableFont = new Font("Arial", 9, FontStyle.Regular);
            float tableSpacing = 8; // Tính chiều cao của đoạn văn bản "Bàn 5B"
            float tableTextHeight = e.Graphics.MeasureString(tableOrder, tableFont).Height; // Vị trí y cho thông tin "Bàn 5B"
            float tableY = dateTimeY + e.Graphics.MeasureString(saleDateTime, dateTimeFont).Height + tableSpacing; // Điều chỉnh vị trí x cho Bàn số 5
            float tableX = 20;
            e.Graphics.DrawString("Bàn:" + tableOrder, tableFont, printBrush, new PointF(tableX, tableY)); // Vẽ chuỗi thông tin "Bàn 5B"

            // Đường gạch đứt
            float lineY2 = tableY + tableTextHeight + tableSpacing; // Chuyển đoạn nét đứt xuống dưới thông tin "Bàn 5B"
            float startX2 = 20;  // Thay đổi tùy thuộc vào vị trí bạn muốn bắt đầu vẽ đường gạch đứt
            float endX2 = paperWidth - 20;  // Thay đổi tùy thuộc vào vị trí bạn muốn kết thúc vẽ đường gạch đứt
            e.Graphics.DrawLine(dashedPen, startX2, lineY2, endX2, lineY2);

            // Đơn giá
            string columnName1 = "Đơn giá";
            Font columnFont1 = new Font("Arial", 9, FontStyle.Bold);
            float columnWidth1 = e.Graphics.MeasureString(columnName1, columnFont1).Width;
            float columnX1 = 20;

            // SL (Số lượng)
            string columnName2 = "SL";
            Font columnFont2 = new Font("Arial", 9, FontStyle.Bold);
            float columnWidth2 = e.Graphics.MeasureString(columnName2, columnFont2).Width;
            float columnX2 = columnX1 + columnWidth1 + 60; // 20 là khoảng cách giữa các cột

            // Thành tiền
            string columnName3 = "Thành tiền";
            Font columnFont3 = new Font("Arial", 9, FontStyle.Bold);
            float columnWidth3 = e.Graphics.MeasureString(columnName3, columnFont3).Width;
            float columnX3 = columnX2 + columnWidth2 + 55; // 20 là khoảng cách giữa các cột

            // Vẽ cột "Đơn giá"
            e.Graphics.DrawString(columnName1, columnFont1, printBrush, new PointF(columnX1, lineY2 + 10));// 10 là khoảng cách giữa đường gạch và cột
            // Vẽ cột "SL"
            e.Graphics.DrawString(columnName2, columnFont2, printBrush, new PointF(columnX2, lineY2 + 10));// 10 là khoảng cách giữa đường gạch và cột
            // Vẽ cột "Thành tiền"
            e.Graphics.DrawString(columnName3, columnFont3, printBrush, new PointF(columnX3, lineY2 + 10));// 10 là khoảng cách giữa đường gạch và cột
            // Vẽ đường gạch dưới cột
            float lineY3 = lineY2 + 24 + e.Graphics.MeasureString(columnName1, columnFont1).Height;
            e.Graphics.DrawLine(dashedPen, columnX1, lineY3, columnX3 + columnWidth3, lineY3);
            float orderY = lineY3 + 20; // Set lại khoảng cách giữa các hàng tiếp theo.

            // Dữ liệu sản phẩm của hoá đơn đó.
            foreach (var item in data.OrderItems)
            {
                // Thông tin đơn hàng
                string productName = item.Name; // Tên sản phầm
                int? quantity = item.Quantity; // Số lượng
                decimal? unitPrice = item.Price; // Giá

                // Đo chiều dài của productName.
                float productNameWidth = e.Graphics.MeasureString(productName, new Font("Arial", 9, FontStyle.Regular)).Width;

                // Vị trí y cho thông tin sản phẩm và các cột
                float productInfoY = orderY;

                // Nếu chiều dài của productName vượt quá khối lượng giấy, tự động xuống dòng
                if (productNameWidth > paperWidth - columnWidth1 - columnWidth2 - columnWidth3)
                {
                    string[] productNameLines = WordWrap(productName, paperWidth, new Font("Arial", 9, FontStyle.Regular), e);

                    // Vẽ từng dòng của productName
                    foreach (string line in productNameLines)
                    {
                        e.Graphics.DrawString(line, new Font("Arial", 9, FontStyle.Regular), printBrush, new PointF(columnX1, productInfoY));

                        // Tăng vị trí y cho các cột
                        productInfoY += e.Graphics.MeasureString(line, new Font("Arial", 9, FontStyle.Regular)).Height;
                    }
                }
                else
                {
                    // Vẽ tên sản phẩm trên dòng hiện tại
                    e.Graphics.DrawString(productName, new Font("Arial", 9, FontStyle.Regular), printBrush, new PointF(columnX1, productInfoY));

                    // Tăng vị trí y cho các cột
                    productInfoY += e.Graphics.MeasureString(productName, new Font("Arial", 9, FontStyle.Regular)).Height;
                }

                // Vẽ giá trị của từng cột với font thông thường
                e.Graphics.DrawString(unitPrice?.ToString("N0"), new Font("Arial", 9, FontStyle.Regular), printBrush, new PointF(columnX1, productInfoY));
                e.Graphics.DrawString(quantity.ToString(), new Font("Arial", 9, FontStyle.Regular), printBrush, new PointF(columnX2, productInfoY));
                e.Graphics.DrawString((unitPrice * quantity)?.ToString("N0"), new Font("Arial", 9, FontStyle.Regular), printBrush, new PointF(columnX3, productInfoY));

                // Vẽ đường gạch dưới
                float lineItem = productInfoY + 10 + e.Graphics.MeasureString(unitPrice?.ToString("N0"), new Font("Arial", 9, FontStyle.Regular)).Height; // 24 là khoảng cách giữa dòng và đường gạch
                e.Graphics.DrawLine(dashedPen, columnX1, lineItem, columnX3 + columnWidth3, lineItem);

                orderY = lineItem + 10; // 10 là khoảng cách giữa dòng và dòng tiếp theo
            }

            // Tổng tiền hàng
            decimal? totalAmount = data.Total;
            string totalAmountText = "Tổng tiền hàng:";
            e.Graphics.DrawString(totalAmountText, columnFont1, printBrush, new PointF(columnX1 + 50, orderY));
            e.Graphics.DrawString(totalAmount?.ToString("N0"), columnFont3, printBrush, new PointF(columnX3, orderY));

            // Cập nhật giá trị orderY cho các dòng tiếp theo
            float totalAmountHeight = e.Graphics.MeasureString(totalAmountText, columnFont1).Height;
            orderY += totalAmountHeight + 5; // 20 là khoảng cách giữa dòng và dòng tiếp theo

            // Chiết khấu
            decimal? discount = data.Discount;
            string discountText = "Chiết khấu:";
            e.Graphics.DrawString(discountText, columnFont1, printBrush, new PointF(columnX1 + 50, orderY));
            e.Graphics.DrawString(discount?.ToString("N0"), columnFont3, printBrush, new PointF(columnX3, orderY));

            // Cập nhật giá trị orderY cho các dòng tiếp theo
            float discountHeight = e.Graphics.MeasureString(discountText, columnFont1).Height;
            orderY += discountHeight + 5; // 20 là khoảng cách giữa dòng và dòng tiếp theo

            // Tổng cộng
            decimal? grandTotal = totalAmount - discount;
            string grandTotalText = "Tổng cộng:";
            e.Graphics.DrawString(grandTotalText, columnFont1, printBrush, new PointF(columnX1 + 50, orderY));
            e.Graphics.DrawString(grandTotal?.ToString("N0"), columnFont3, printBrush, new PointF(columnX3, orderY));
        }
        //Chia một đoạn văn bản thành các dòng sao cho chiều rộng của mỗi dòng không vượt quá một giới hạn (maxWidth).
        private static string[] WordWrap(string text, float maxWidth, Font font, PrintPageEventArgs e)
        {
            List lines = new List();
            StringBuilder currentLine = new StringBuilder();
            string[] words = text.Split(' ');

            foreach (string word in words)
            {
                if (currentLine.Length == 0)
                {
                    currentLine.Append(word);
                }
                else
                {
                    string testString = currentLine.ToString() + " " + word;
                    if (e.Graphics.MeasureString(testString, font).Width < maxWidth)
                    {
                        currentLine.Append(" " + word);
                    }
                    else
                    {
                        lines.Add(currentLine.ToString());
                        currentLine.Clear();
                        currentLine.Append(word);
                    }
                }
            }

            lines.Add(currentLine.ToString());
            return lines.ToArray();
        }
    }
}

Dưới các đoạn code thì đã có comment đoạn code đó chức năng và mục đích nó làm gì.
Sau đó tạo function với dữ liệu mẫu để test chức năng in như thế nào: 

public PrintBillResponse PrintBillData()
{
    var printBillResponse = new PrintBillResponse
    {
        Id = 1,
        DateSale = DateTime.Now,
        Seller = "Nguyễn Phước Lê Hiếu",
        TimeEnter = DateTime.Now.AddHours(-1),
        TableOrder = "Bàn 5",
        Total = 26660000,
        Discount = 0,
        TotalAmount = 26660000,
        OrderItems = new List
        {
            new OrderItem { Id = 1, Name = "Cua hoàng đế xanh (Blue King Crab)", Price = 2300000, Quantity = 2 },
            new OrderItem { Id = 1, Name = "Tôm hùm Alaska", Price = 1300000, Quantity = 2 },
            new OrderItem { Id = 1, Name = "Trứng cá tầm", Price = 4700000, Quantity = 1 },
            new OrderItem { Id = 1, Name = "Gà chín cựa", Price = 12000000, Quantity = 1 },
            new OrderItem { Id = 1, Name = "Bia ST.Sebastiaan Dark Ale", Price = 230000, Quantity = 12 },
        }
    };
    return printBillResponse;
}

- Ở Controller tạo API  => và gọi hàm PrintPage() ở Repository là xong.
 
using Application.Interfaces.Repositories.PrintBill;
using Microsoft.AspNetCore.Mvc;
using System.Drawing.Printing;

namespace WebApi.Controllers.V1
{
    [Route("api/v{version:apiVersion}/print-bill")]
    [ApiController]
    public class PrintController : Controller
    {
        private readonly IPrintBillRepository _printBillRepository;

        public PrintController(IPrintBillRepository printBillRepository)
        {
            _printBillRepository = printBillRepository;
        }

        [HttpGet]
        public void PrintBill()
        {
            PrintDocument pd = new PrintDocument();
            pd.PrintPage += new PrintPageEventHandler(_printBillRepository.PrintPage);
            // Chọn máy in mặc định
            pd.PrinterSettings.PrinterName = PrinterSettings.InstalledPrinters[0];
            // Kích thước giấy và hướng in
            pd.DefaultPageSettings.PaperSize = new PaperSize("Custom", 300, 0);
            pd.DefaultPageSettings.Landscape = false;
            // In
            pd.Print();
        }
    }
}

Vậy là xong hoàn thành phần code, giờ test thôi:


Và kết quả sau khi submit API:


Vậy là đã oke rồi nhé. Bài viết còn nhiều thiếu sót mong mọi người góp ý, để mình cải thiện hơn. Cảm ơn !

Chú ý: Thư viện này hiện tại không được hổ trợ trên Linux mọi người nhé.

2 Nhận xét

Mới hơn Cũ hơn