Software Development · โปรเจกต์ทีม

BONUS SUKI
POS

ระบบหน้าร้านสุกี้ชาบูบุฟเฟต์
เปิดโต๊ะ · สั่งอาหาร · ชำระเงิน · หลังบ้านผู้จัดการ

Java 17Swing (NetBeans GUI Builder)SQLite + JDBCJFreeChart
หน้าล็อกอิน: พื้นแดงซ้ายมีโลโก้ร้าน การ์ดล็อกอินขวามีปุ่มเลือกผู้จัดการหรือพนักงาน

01 · โจทย์

ร้านต้องการระบบขายหน้าร้านที่ทำตามกฎของร้านได้จริง

กฎสำคัญจากขอบเขตงาน (TOR)

  • เปิดโต๊ะต้องระบุจำนวนลูกค้า และห้ามเปิดโต๊ะที่ยังไม่ว่าง
  • 1 บิลใช้โปรโมชั่นได้ 1 โปร · สมาชิกได้แต้มสะสม
  • จ่ายด้วยเงินสดหรือ QR · เงินทอนแม่นยำทศนิยม 2 ตำแหน่ง
  • จ่ายแล้วตัดวัตถุดิบตามสูตรอาหาร (BOM) อัตโนมัติ
  • ผู้จัดการเห็นทุกหน้า · พนักงานเห็นเฉพาะขายกับลงเวลา

Figma

หน้าตาเป็นอย่างไร · สีแบรนด์ แดง #B30004

Google Sheet

ใครทำงานอะไร ส่งเมื่อไร · 50 งาน 8 คน

รูปเล่มฐานข้อมูล

เก็บข้อมูลอะไร · 15 ตาราง + กฎร้าน

02 · ภาพรวม

18 หน้าจอ แบ่งตามคนใช้งาน

หน้าเมนูหลัก มีแถบเมนูสีแดงด้านซ้ายและการ์ดสรุปยอดวันนี้
เมนูหลัก + แถบซ้าย
หน้าขาย POS การ์ดเมนูด้านซ้าย รายการอาหารของโต๊ะด้านขวา
ขายหน้าร้าน (POS)
Dashboard ตัวเลขสรุปและกราฟยอดขาย
Dashboard
หน้า CRUD สมาชิก ฟอร์มซ้าย ตารางขวา
CRUD สมาชิก (แม่แบบทุกหน้า CRUD)
หน้าลงเวลาทำงาน นาฬิกาตัวใหญ่และปุ่มเข้างานออกงาน
ลงเวลาทำงาน
หน้าบิลย้อนหลัง ตารางบิลพร้อมยอดสุทธิ
บิลย้อนหลัง

03 · สถาปัตยกรรม

แบ่งโค้ดเป็น 4 ชั้น แต่ละชั้นคุยกับชั้นถัดลงไปเท่านั้น

Lab 8 · หัวข้อ 0.1
uiหน้าจอ
MemberPanel · PosPanel · LoginFrame — เรียก service เท่านั้น ห้ามแตะ SQL
เรียกเมธอด
serviceกฎร้าน
MemberService.add() ตรวจเบอร์ซ้ำ · PosService.checkout() คิดเงิน
เรียก DAO
daoSQL ทั้งหมด
1 ตาราง = 1 DAO · MemberDao รู้ชื่อคอลัมน์ของตาราง Member คนเดียว
ขอ Connection
helperตัวช่วยกลาง
DatabaseHelper ถือ Connection เดียวของทั้งแอป · Session · DateUtil
bonus_suki.dbไฟล์ SQLite
18 ตาราง · เปิด foreign key ทุกครั้งที่เชื่อมต่อ
model

"กล่องข้อมูล" 1 แถว
เช่น Member, Bill

วิ่งผ่านทุกชั้น
จากฐานข้อมูลถึงหน้าจอ

04 · ฐานข้อมูล

SQLite 18 ตาราง: 15 จากรูปเล่ม + 3 ที่ทีมเพิ่ม

Lab 8 · ช่วง 2

ขายหน้าร้าน

BillBill_ProductDining_TablePromotionMember

สินค้า + สต็อก

ProductBOMStock_ItemImportLot_DetailVendorCheck_StockCheck_Stock_Detail

คนในร้าน

EmployeeUser_AccountWork_TimePayrollExpense
สีเทา = ตารางที่เพิ่ม หน้า Login ต้องมี username/รหัสผ่าน แต่ Employee ไม่มีคอลัมน์นี้ จึงเพิ่ม User_Account แทนการแก้ตารางเดิม · ชีทมีงานตรวจนับสต็อก จึงเพิ่ม Check_Stock คู่กับรายละเอียด
รหัสเป็นข้อความ ตามรูปเล่ม เช่น M001, PD001, B0001 ไม่ใช่เลข AUTOINCREMENT แบบในแลป
SQLite ปิด foreign key ไว้ ต้องสั่ง PRAGMA foreign_keys = ON ทุกครั้งที่เชื่อมต่อ ไม่งั้นลบสมาชิกที่มีบิลอ้างถึงได้

05 · helper

DatabaseHelper: ประตูเดียวที่แตะไฟล์ฐานข้อมูล

Lab 8 · ช่วง 3
  • Connection เดียวทั้งแอป สร้างตอนเรียกครั้งแรก (lazy) แล้วใช้ซ้ำ
  • static เรียกผ่านชื่อคลาสได้เลย และปิด constructor กันคน new
  • เปิด foreign key ทันทีหลังเชื่อมต่อ
  • เพิ่มจากแลป: nextId() สร้างรหัสถัดไป เช่น M005 → M006 และ closeQuietly() ให้ DAO ใช้ปิด rs/stmt
helper/DatabaseHelper.javapublic static synchronized Connection getConnect() {
    if (conn == null) {
        try {
            conn = DriverManager.getConnection(URL);
            Statement pragma = null;
            try {
                pragma = conn.createStatement();
                pragma.execute("PRAGMA foreign_keys = ON");
            } finally {
                closeQuietly(null, pragma);
            }
            createTablesIfEmpty();   // ไฟล์ใหม่ = สร้างตาราง + mock data
        } catch (SQLException ex) {
            LOG.log(Level.SEVERE, "เชื่อมต่อไม่สำเร็จ", ex);
        }
    }
    return conn;
}

06 · model

model: กล่องข้อมูล 1 แถว + ตัวแปลงจาก ResultSet

Lab 8 · ช่วง 5

constructor 3 แบบ

  • แบบเต็ม ตอนอ่านจากฐานข้อมูล (มีรหัสแล้ว)
  • ไม่มีรหัส ตอนเพิ่มใหม่ DAO สร้างรหัสให้ตอน save
  • ว่าง ให้ fromRS สร้างก่อนแล้วค่อยเติม

fromRS

อ่าน 1 แถวที่ ResultSet ชี้อยู่ แล้วเติมทีละคอลัมน์ ชื่อคอลัมน์ต้องตรงกับตารางทุกตัว

20 model ในโปรเจกต์ทำแบบเดียวกันหมด

model/Member.javapublic class Member {
    private String memberId;      // ← Member_ID (PK เช่น M001)
    private String memberName;    // ← Member_Name
    private String memberPhoneNo; // ← UNIQUE 10 หลัก
    private String memberAddress; // ← NULL ได้
    private int pointBalance;

    // constructor 3 แบบ + getter/setter (Ctrl+I)

    public static Member fromRS(ResultSet rs) throws SQLException {
        Member m = new Member();
        m.setMemberId(rs.getString("Member_ID"));
        m.setMemberName(rs.getString("Member_Name"));
        m.setMemberPhoneNo(rs.getString("Member_PhoneNo"));
        m.setMemberAddress(rs.getString("Member_Address"));
        m.setPointBalance(rs.getInt("Point_Balance"));
        return m;
    }
}

07 · dao

interface Dao<T>: สัญญาที่ DAO ทุกตารางต้องทำได้

Lab 8 · ช่วง 6
dao/Dao.javapublic interface Dao<T> {

    T get(String id);          // ไม่พบคืน null

    List<T> getAll();

    List<T> getAll(String where, String order);

    T save(T obj);           // สำเร็จคืน obj ที่มีรหัสแล้ว

    T update(T obj);         // ไม่พบแถวคืน null

    int delete(T obj);        // จำนวนแถวที่ลบ, -1 = ผิดพลาด
}
Lab 8โปรเจกต์นี้
เมธอด6 ตัว6 ตัว ชื่อเดียวกัน
ชนิดรหัสint idString id (M001)
รหัสใหม่getInsertedId()nextId()
ผิดพลาดไม่ throw ออก · log แล้วคืน null / -1

ตารางที่ PK มี 2 คอลัมน์ เช่น Bill_Product ไม่ implements Dao เพราะ get รับรหัสตัวเดียว

08 · dao

DAO ทุกเมธอดเขียนแบบ try-catch-finally

Lab 8 · ช่วง 6
1 · ยืม Connection จาก helper ไม่เปิดเอง
2 · PreparedStatement + ? ค่าที่ผู้ใช้พิมพ์ถูก bind แยกจาก SQL กัน SQL injection และภาษาไทยไม่พัง
3 · catch log ข้อผิดพลาดแล้วคืน null ให้ service ตัดสินใจต่อ
4 · finally ปิด ResultSet และ Statement ที่เปิดเอง แต่ ไม่ปิด Connection เพราะคนอื่นใช้ต่อ
dao/MemberDao.java@Override
public Member get(String id) {
    String sql = "SELECT * FROM Member WHERE Member_ID = ?";
    Connection conn = DatabaseHelper.getConnect();
    PreparedStatement stmt = null;
    ResultSet rs = null;
    try {
        stmt = conn.prepareStatement(sql);
        stmt.setString(1, id);
        rs = stmt.executeQuery();
        return rs.next() ? Member.fromRS(rs) : null;
    } catch (SQLException ex) {
        LOG.log(Level.WARNING, "get(" + id + ") ล้มเหลว", ex);
        return null;
    } finally {
        DatabaseHelper.closeQuietly(rs, stmt);
    }
}

09 · service

service: กฎของร้านอยู่ที่เดียว หน้าจอแค่แสดงข้อความ

Lab 8 · ช่วง 7
service/MemberService.java (ย่อ)/** สมัครสมาชิกด่วนหน้าร้าน */
public Member add(Member m) {
    validate(m);                 // ชื่อห้ามว่าง เบอร์ 10 หลัก
    if (memberDao.getByPhoneNo(m.getMemberPhoneNo()) != null) {
        throw new IllegalArgumentException(
            "เบอร์ " + m.getMemberPhoneNo() + " เป็นสมาชิกอยู่แล้ว");
    }
    return memberDao.save(m);
}

/** ค้นในหน่วยความจำ ไม่ต่อข้อความผู้ใช้เข้า SQL */
public List<Member> search(String keyword) {
    return memberDao.getAll().stream()
        .filter(x -> x.getMemberName().contains(keyword)
                  || x.getMemberPhoneNo().contains(keyword))
        .collect(Collectors.toList());
}

ทุก service ใช้ชื่อเมธอดชุดเดียวกัน

getAll() · get(id) · search() · add() · edit() · remove()

คนทำหน้าจอเดาชื่อได้โดยไม่ต้องถามเจ้าของโมดูล

ผิดกฎ = throw พร้อมข้อความไทย

หน้าจอ catch แล้วโชว์ ex.getMessage() ใน JOptionPane ทุกหน้าจึงได้กฎเดียวกัน

remove() ลบแถวที่มีคนอ้างถึงไม่ได้ (ติด foreign key) จะคืน false ให้หน้าจอบอกเหตุผล

10 · ทดสอบ

ทดสอบหลังบ้านด้วย main ก่อนต่อหน้าจอ

Lab 8 · ช่วง 6 · POS Flow Step 5

TestMemberDao

$ Run File: TestMemberDao สมาชิกทั้งหมด: 5 M001: สมชาย ใจดี (0812345678) เพิ่มได้รหัส: M006 กันเบอร์ซ้ำได้: เบอร์ 0911111111 เป็นสมาชิกอยู่แล้ว ลบ M001 (มีบิล): false ลบตัวทดสอบ: true

เจอ SQL ผิดตอนนี้ แก้ง่ายกว่าตอนต่อหน้าจอแล้ว

TestPosService (flow ขายครบวง)

$ Run File: TestPosService เปิดโต๊ะ T02 ได้บิล: B0007 · สถานะโต๊ะ: กำลังใช้งาน กันเปิดโต๊ะซ้ำ: โต๊ะนี้ไม่ว่าง ยอดรวม: 1270.0 เงินไม่พอ rollback: รับเงินไม่พอ ขาดอีก 871.00 บาท ส่วนลด P001: 299.0 · สุทธิ: 971.0 · เงินทอน: 29.0 โต๊ะ T02 หลังจ่าย: ว่าง แต้ม M004: 38 ใบเสร็จมี 3 รายการ

11 · หน้าจอ

ทุกหน้าลากวางใน NetBeans แล้วตั้งชื่อตัวแปรให้ตรงกติกา

Lab 11 · Step 3, 5 · POS Flow Step 6
หน้า CRUD ผู้จัดจำหน่าย ฟอร์มซ้าย ช่องค้นหาและตารางด้านขวา
VendorPanel · JPanel Form ที่วางใน MainFrame
  • คลิกขวา package ui → New → JPanel Form (หน้าใน MainFrame), JFrame Form (Login, Main), JDialog Form (popup)
  • ลากจาก Palette แบบ Free Design (GroupLayout) ห้ามเลือก Absolute Layout
  • คลิกขวา → Change Variable Name: txt txa cmb btn tbl scr + ชื่อ field
  • NetBeans เก็บแบบไว้ใน .form และเขียน initComponents() ในบล็อกสีเทาให้เอง ห้ามแก้ด้วยมือ
  • สีตาม Figma ตั้งหลัง initComponents() ผ่าน Theme เท่านั้น

12 · หน้าจอ

แม่แบบหน้า CRUD ที่ทุกหน้าใช้ซ้ำ

Lab 11 · Step 6, 7
ปุ่ม ดับเบิลคลิกในแท็บ Design (หรือ Events → Action → actionPerformed) ได้ btnSaveActionPerformed(evt)
คลิกแถว JTable ไม่มีเมนู Events → ListSelection ต้องผูกเองใน constructor
ตาราง สร้าง DefaultTableModel ใหม่ทุกครั้งจากข้อมูลสด แล้ว setModel

หน้า CRUD 7 หน้า (สมาชิก สินค้า วัตถุดิบ ผู้จัดจำหน่าย พนักงาน เงินเดือน ค่าใช้จ่าย) ใช้โครงนี้

ui/MemberPanel.java (ส่วนที่เขียนเอง · ย่อ)public MemberPanel() {
    initComponents();
    tblMember.getSelectionModel()
        .addListSelectionListener(this::tblMemberValueChanged);
    loadTable(memberService.getAll());
}

private void loadTable(List<Member> list) {
    DefaultTableModel model = Form.readOnlyModel(
            "รหัส", "ชื่อ-นามสกุล", "เบอร์โทร", "แต้ม");
    for (Member m : list) {
        model.addRow(new Object[]{m.getMemberId(), m.getMemberName(), ...});
    }
    tblMember.setModel(model);
}

private void btnSaveActionPerformed(ActionEvent evt) {
    try {
        if (id == null) memberService.add(m);
        else memberService.edit(m);
        loadTable(memberService.getAll());
    } catch (IllegalArgumentException ex) {
        JOptionPane.showMessageDialog(this, ex.getMessage());
    }
}

13 · ล็อกอิน

ล็อกอินต้องตรงทั้งชื่อ รหัส และสถานะที่กด

Lab 11 · Step 3, 4
หน้าล็อกอินพร้อมปุ่มสถานะผู้จัดการและพนักงาน

Session จำว่าใครล็อกอินอยู่ ทุกหน้าเรียก Session.isManager() ได้โดยไม่ต้องส่งต่อทาง constructor

service/LoginService.javapublic UserAccount login(String username,
        String password, String role) {
    UserAccount user = userAccountDao.getByUsername(username);
    if (user == null
            || !user.getPassword().equals(password)
            || !user.getRole().equals(role)) {
        return null;
    }
    Employee employee = employeeDao.get(user.getEmpId());
    Session.login(user, employee);
    return user;
}

ui/LoginFrame.java (ย่อ)private void btnLoginActionPerformed(ActionEvent evt) {
    String role = tglManager.isSelected()
            ? UserAccount.ROLE_MANAGER : UserAccount.ROLE_EMPLOYEE;
    UserAccount user = loginService.login(...);
    if (user == null) {   // แจ้งเตือนแล้วล้างช่องรหัส
        return;
    }
    new MainFrame().setVisible(true);
    dispose();
}

14 · เมนูหลัก

MainFrame: JFrame ตัวเดียว เปลี่ยนหน้าด้วย CardLayout

Lab 11 · Step 6 (เชื่อมสองจอ)
MainFrame แถบเมนูซ้ายสีแดง ปุ่มเมนูหลักถูกเลือกเป็นสีชมพูแดง
ปุ่มที่เลือกเป็นสีชมพูแดง เหมือนใน Figma
  • ทั้งแอปมี JFrame แค่ 2 ตัว: LoginFrame กับ MainFrame หน้าอื่นเป็น JPanel
  • pnlContent ตั้ง Layout เป็น Card Layout ในแท็บ Design แล้ว add(page, ชื่อการ์ด)
  • พนักงานไม่เห็นปุ่มหลังบ้าน และหน้าเหล่านั้นไม่ถูกสร้างเลย
private void showCard(String card, JButton active) {
    Component page = pages.get(card);
    if (page instanceof Refreshable) {
        ((Refreshable) page).refresh(); // โหลดข้อมูลสด
    }
    cards.show(pnlContent, card);
}

15 · ขายหน้าร้าน

flow ขายหนึ่งรอบ: บิลมีชีวิต โต๊ะมีสถานะ

POS Flow · ภาพรวม + Step 7, 9, 11

1เปิดโต๊ะ

  1. กด + บนการ์ดเมนูครั้งแรก
  2. OpenTableDialog เลือกโต๊ะ + จำนวนคน
  3. PosService.openTable() เช็กโต๊ะว่าง และคนไม่เกินที่นั่ง
  4. BillDao.save() สร้างบิล B0007
  5. DiningTableDao.updateStatus()

ว่าง → กำลังใช้งาน

→

2สั่งอาหาร

  1. กด + ซ้ำได้หลายรอบ
  2. PosService.addProduct() เมนูเดิมบวกจำนวนในแถวเดิม
  3. OrderLinePanel ปุ่ม − / + / ลบ
  4. รวมเงินคำนวณใหม่ทุกครั้งจากฐานข้อมูล

โต๊ะที่มีบิลค้าง เปิดใหม่ = สั่งต่อในบิลเดิม

→

3ชำระเงิน

  1. PaymentDialog ค้นสมาชิกด้วยเบอร์ เลือกโปร
  2. พิมพ์เงินรับ เห็นเงินทอนทันที
  3. PosService.checkout() ใน transaction เดียว
  4. เปิด BillDetailDialog พิมพ์ใบเสร็จ

กำลังใช้งาน → ว่าง

16 · ชำระเงิน

ชำระเงิน 4 อย่างต้องสำเร็จพร้อมกัน ไม่งั้นย้อนทั้งหมด

POS Flow · แบบฝึกหัดข้อ 3

ในแลป ถ้าปิดบิลสำเร็จแต่คืนโต๊ะพัง โต๊ะจะค้าง ทางแก้คือรวมเป็น ธุรกรรมเดียว

  1. บันทึกยอดบิล ส่วนลด เงินรับ
  2. ตัดวัตถุดิบตามสูตร BOM
  3. บวกแต้มสมาชิก
  4. คืนโต๊ะเป็น "ว่าง"
DAO กลืน error แล้วคืน null service จึงต้องเช็กค่าที่คืนด้วย require() แล้ว throw เอง ไม่งั้น rollback ไม่ทำงาน
service/PosService.java (ย่อ)public Bill checkout(...) throws SQLException {
    Connection conn = DatabaseHelper.getConnect();
    conn.setAutoCommit(false);
    try {
        double discount = promotionService.calcDiscount(...);
        double net = round2(total - discount);
        if (received < net) {
            throw new IllegalArgumentException("รับเงินไม่พอ ...");
        }
        require(billDao.update(bill) != null, "บันทึกบิลไม่สำเร็จ");
        stockItemService.deductByBom(orderId);
        memberService.addPoints(memberId, net);
        diningTableDao.updateStatus(tableId, DiningTable.STATUS_VACANT);
        conn.commit();
        return bill;
    } catch (RuntimeException | SQLException ex) {
        conn.rollback();
        throw ex;
    } finally {
        conn.setAutoCommit(true);
    }
}

17 · กฎร้าน

โปรโมชั่น แต้มสมาชิก และตัดสต็อก

TOR + ข้อตกลงของทีม

P001 · มา 4 จ่าย 3

ลดค่าบุฟเฟต์ 1 ท่าน (ใบที่ถูกที่สุด)

P002 · สมาชิกลด 10%

ต้องค้นหาสมาชิกก่อน ไม่งั้นกดยืนยันไม่ได้

P003 · มา 2 ฟรีซุปพิเศษ

ลดราคาน้ำซุปพิเศษ 1 หม้อ

  • 1 บิลใช้ได้ 1 โปร · เช็กช่วงวันที่และจำนวนคนขั้นต่ำ
  • ตาราง Promotion ไม่มีคอลัมน์วิธีคิดส่วนลด จึงเขียนตามรหัสโปรใน PromotionService
  • แต้มสมาชิก: ยอดสุทธิทุก 25 บาท = 1 แต้ม
dao/StockItemDao.java · ตัดสต็อกด้วย SQL คำสั่งเดียวUPDATE Stock_Item
SET Current_Stock = Current_Stock - (
    SELECT SUM(bp.Unit * bom.Quantity)
    FROM Bill_Product bp
    JOIN BOM bom ON bom.Product_ID = bp.Product_ID
    WHERE bp.Order_ID = ?
      AND bom.Stock_Item_ID = Stock_Item.Stock_Item_ID)
WHERE Stock_Item_ID IN (...วัตถุดิบที่บิลนี้ใช้...)

18 · Dashboard

Dashboard: SQL สรุปยอด + กราฟ JFreeChart

Figma Dashboard + ไลบรารี jfreechart 1.5.5
Dashboard ยอดบิล ลูกค้า รายได้ กราฟรายเดือน โดนัทตามหมวด กราฟแท่งรายวัน และสัดส่วนการชำระเงิน
dao/ReportDao.java · ยอดขายรายเดือนSELECT strftime('%m', Payment_Date) AS label,
       SUM(Total_Price - Discount_Amount) AS value
FROM Bill
WHERE Payment_Date IS NOT NULL
  AND strftime('%Y', Payment_Date) = ?
GROUP BY label ORDER BY label
  • กล่อง Dine-In / Takeaway ใน Figma ไม่มีในฐานข้อมูล จึงเปลี่ยนเป็นสัดส่วนเงินสด / QR และแสดงเป็นบาท
  • JFreeChart ไม่ใช้ฟอนต์ที่ตั้งไว้ ต้องตั้งธีมกราฟเอง ไม่งั้นตัวไทยเป็นสี่เหลี่ยม

19 · สรุปที่มา

แต่ละส่วนของโค้ด มาจากแลปไหน

ส่วนของโปรเจกต์มาจากไฟล์ในโปรเจกต์
แบ่ง 4 ชั้นLab 8 · หัวข้อ 0.1ui/ service/ dao/ helper/ model/
ฐานข้อมูล + foreign keyLab 8 · ช่วง 2sql/bonus_suki_sqlite.sql, bonus_suki_mock_data.sql
Connection กลางLab 8 · ช่วง 3DatabaseHelper
model + fromRSLab 8 · ช่วง 520 คลาสใน model/
Dao<T> + try-catch-finallyLab 8 · ช่วง 620 คลาสใน dao/
service + ทดสอบLab 8 · ช่วง 7 · POS Flow Step 515 คลาสใน service/, TestMemberDao, TestPosService
หน้าจอลากวาง + CRUDLab 11 · Step 3–718 หน้าใน ui/ (มี .form ทุกหน้า)
Login + สิทธิ์Lab 11 · Step 3, 4LoginFrame, LoginService, Session
เปิดโต๊ะ / สั่ง / เช็คบิลPOS Flow · Step 1–11PosPanel, OpenTableDialog, PaymentDialog, PosService
transaction ตอนจ่ายPOS Flow · แบบฝึกหัดข้อ 3PosService.checkout()

20 · ทีม

ใครทำส่วนไหน

ต
เต้
ฐานข้อมูล · โครงโปรเจกต์

bonus_suki.db, DatabaseHelper, Dao, Theme, ThaiFont · StockItemPanel, CheckStockPanel

ป
เปรม
Login · เมนูหลัก · ค่าใช้จ่าย

LoginService, Session, LoginFrame, MainFrame, HomePanel · Expense ครบชุด

อ
โอ๊ต
POS

PosService, PromotionService, DiningTable · ProductItemPanel, OrderLinePanel, OpenTableDialog, PaymentDialog

น
นุ
บิล / ใบเสร็จ · ทดสอบ

Bill, BillProduct, BillService · BillPanel, BillDetailDialog · ทดสอบทุกหน้า

บ
บาส
สินค้า · สต็อก

Product, StockItem, CheckStock ครบชุด DAO/Service · ProductPanel

น
นิว
User · Supplier · POS UI

Employee, UserAccount, Vendor ครบชุด · EmployeePanel, VendorPanel, PosPanel

ต
ต๊ะ
สมาชิก · เงินเดือน

Member (โมดูลแม่แบบ), Payroll ครบชุด · MemberPanel, PayrollPanel

บ
บิ๊ก
ลงเวลา · Dashboard

WorkTime ครบชุด, ReportDao, ReportService · WorkTimePanel, DashboardPanel · Scenarios, ER

21 · สรุป

สรุป

  • โครงสร้างตาม Lab 8: 4 ชั้น, Dao<T>, try-catch-finally, ทดสอบด้วย main
  • หน้าจอตาม Lab 11: ลากวางใน NetBeans, event ผ่าน designer, model ตารางใหม่ทุกครั้ง
  • flow ขายตาม POS Flow: เปิดโต๊ะ → สั่ง → เช็คบิล และทำแบบฝึกหัด transaction จริง
  • ปรับให้ตรงรูปเล่มและ Figma: รหัสแบบข้อความ, เพิ่ม 3 ตาราง, สีแบรนด์ผ่าน Theme

Q&A

ลองใช้งานจริง

รัน BonusSuki.java ใน NetBeans (F6)

manager / 1234 · กดสถานะ "ผู้จัดการ"

← → เปลี่ยนหน้า · F เต็มจอ