ข้ามไปเนื้อหา
← เครื่องมือทั้งหมด
เอกสารพร้อมใช้

OCR Read - Tiktok Shop

อ่านเอกสาร ใบเสร็จรับเงิน/ใบกำกับภาษี เพื่อจัดทำรายงานสรุปค่าใช้จ่ายประจำเดือน

วิธีตั้งค่า Google และใช้งาน Apps Script

เครื่องมือนี้อ่าน PDF ใบเสร็จ/ใบกำกับภาษีค่าบริการ Tiktok Shop ใน Google Drive แล้วบันทึกลิงก์ข้อมูลลง Sheets เพื่อจัดทำรายงานสรุปค่าใช้จ่ายค่าบริการและค่าขนส่งในสิ้นเดือน

เตรียมก่อนเริ่ม

  • ใช้บัญชี Google ที่เข้าถึงโฟลเดอร์ต้นทางและแก้ไขชีตปลายทางได้
  • สร้างโฟลเดอร์เฉพาะสำหรับ PDF และชีตใหม่สำหรับทดลอง
  • ดาวน์โหลดไฟล์เอกสารค่าใช้จ่ายจาก Tiktok เพื่อนำมาทำรายงานประจำเดือน ref : วิธีการดาวน์โหลดใบกำกับภาษี

1. สร้าง Google Sheets

เปิด Google Sheets แล้วสร้างไฟล์ใหม่สำหรับรายงานค่าบริการ Shopee เริ่มจากไฟล์เปล่าเพื่อทดลองกับ PDF 1–2 ไฟล์ก่อนใช้กับข้อมูลจริง

จุดที่ดูในภาพ: ชื่อ Untitled spreadsheet ด้านซ้ายบน

2. ตั้งชื่อไฟล์และแท็บปลายทาง

ตั้งชื่อไฟล์ตามต้องการ เช่น Report_Tiktok Shop แล้วเปลี่ยนชื่อแท็บด้านล่างเป็น E_TIKTOK ให้ตรงตัวพิมพ์และขีดล่าง จากนั้นเพิ่มหัวตารางในแถว 1 ก่อนรัน เพราะโค้ดเริ่มอ่านรายการเดิมจากแถว 2

จุดที่ดูในภาพ: ชื่อไฟล์ด้านซ้ายบน และแท็บ E-TIKTOK ด้านล่าง

คอลัมน์หัวตารางแถว 1
Aลิงก์ PDF
Bเลขที่เอกสาร
Cวันที่
Dระยะเวลา
Eมูลค่าก่อน VAT
FVAT
Gยอดรวม

3. เปิด Apps Script

ใน Google Sheets เลือก Extensions → Apps Script หรือ ส่วนขยาย → Apps Script จะเปิดหน้าแก้ไขโค้ด ตั้งชื่อโปรเจกต์ให้จำได้ เช่น Tiktok PDF Reader

จุดที่กดในภาพ: เมนู Extensions แล้วเลือก Apps Script

4. คัดลอก Sheet ID

ดู URL ของไฟล์ Google Sheets แล้วคัดลอกเฉพาะข้อความระหว่าง /d/ และ /edit ไม่คัดลอก URL ทั้งเส้น

plain texthttps://docs.google.com/spreadsheets/d/SHEET_ID/edit

จุดที่ดูในภาพ: แถบ URL ด้านบน รหัสตรงกลางคือ Sheet ID

5. สร้างโฟลเดอร์ PDF และคัดลอก Folder ID

เปิด Google Drive สร้างหรือเลือกโฟลเดอร์ที่จะเก็บ PDF ค่าบริการ Shopee แล้วคัดลอกข้อความหลัง /folders/ หากมี ? ให้หยุดก่อนเครื่องหมายนี้ วาง PDF ไว้ในโฟลเดอร์นั้นโดยตรง เพราะโค้ดไม่ค้นโฟลเดอร์ย่อย ถ้าจะใช้โฟลเดอร์ outputs ต้องเปิด outputs แล้วใช้ ID ของโฟลเดอร์นั้น

plain texthttps://drive.google.com/drive/folders/FOLDER_ID

จุดที่ดูในภาพ: แถบ URL และชื่อโฟลเดอร์ที่เปิดอยู่

6. วางโค้ดและใส่ ID ของคุณ

เปิด Code.gs ลบโค้ดตัวอย่าง แล้ววางโค้ดที่แนบในหน้านี้ให้ครบ เปลี่ยนเฉพาะค่าภายในเครื่องหมายอัญประกาศสองบรรทัดต่อไปนี้

javascriptvar folderId = 'FOLDER_ID_ของคุณ';
var sheetId = 'SHEET_ID_ของคุณ';
javascript// ==========================================
// 🎵  TIKTOK Shop
// ==========================================
function syncPdfToSheet_E_Tiktok() {
  var folderId = 'FOLDER_ID_ของคุณ';
  var sheetId = 'SHEET_ID_ของคุณ';

  var folder = DriveApp.getFolderById(folderId);
  var files = folder.getFilesByType(MimeType.PDF);
  var spreadsheet = SpreadsheetApp.openById(sheetId);
  var sheet = spreadsheet.getSheetByName("E-TIKTOK");

  if (!sheet) { Logger.log("❌ ไม่พบชีตที่ชื่อ 'E-TIKTOK'"); return; }

  var sheetFileIds = new Map(); 
  var data = sheet.getDataRange().getValues();
  for (var i = 1; i < data.length; i++) {
    var pdfUrl = data[i][0];
    if (pdfUrl.includes("drive.google.com/file/d/")) {
      var fileId = pdfUrl.split("/d/")[1].split("/")[0]; 
      if (sheetFileIds.has(fileId)) { sheet.deleteRow(i + 1); i--; } 
      else { sheetFileIds.set(fileId, i + 1); }
    }
  }

  var newFiles = [];
  var existingFiles = new Set();
  while (files.hasNext()) {
    var file = files.next();
    var fileId = file.getId();
    var pdfUrl = "https://drive.google.com/file/d/" + fileId;
    existingFiles.add(fileId);
    if (!sheetFileIds.has(fileId)) { newFiles.push({ id: fileId, url: pdfUrl }); }
  }

  var rowsToDelete = [];
  for (let [fileId, rowIndex] of sheetFileIds) {
    if (!existingFiles.has(fileId)) { rowsToDelete.push(rowIndex); }
  }
  rowsToDelete.sort((a, b) => b - a).forEach(row => sheet.deleteRow(row));

  newFiles.forEach(file => {
    try {
      var docFile = Drive.Files.copy({ mimeType: MimeType.GOOGLE_DOCS }, file.id);
      Utilities.sleep(1000);
      var docId = docFile.id;
      var doc = DocumentApp.openById(docId);
      var text = doc.getBody().getText().trim();
      DriveApp.getFileById(docId).setTrashed(true);

      if (!text) { sheet.appendRow([file.url, "Conversion Failed: Empty Document"]); return; }

      var lines = text.split('\n').map(line => line.trim());
      if (text.includes("Platform Service Fee")) { processPlatformServiceFee(sheet, file.url, lines); } 
      else if (text.includes("Logistics fee")) { processLogisticsFee(sheet, file.url, lines); } 
      else if (text.includes("Creator commission")) { processCreatorCommission(sheet, file.url, lines); } 
      else { sheet.appendRow([file.url, "Unknown document type"]); }
    } catch (e) {
      sheet.appendRow([file.url, 'Conversion Failed: ' + e.message]);
    }
  });
  Logger.log("✅ Tiktok อัปเดตเสร็จสิ้น");
}

// ------------------------------------------------------------------
// 1. ฟังก์ชันจัดการ Platform Service Fee
// ------------------------------------------------------------------
function processPlatformServiceFee(sheet, pdfUrl, lines) {
  var documentNo = "Not Found";
  var date = "Not Found";
  var period = "Not Found";
  var subtotal = "Not Found";
  var vat = "Not Found";
  var total = "Not Found";
  var description = "Platform Service Fee";

  for (var i = 0; i < lines.length; i++) {
    var line = lines[i];
    
    if (line.includes("Invoice number") || line.includes("Invoice date")) {
      var match = line.match(/Invoice number[::]\s*(.*?)\s*Invoice date[::]\s*(.*?)\s*Period[::]\s*(.*)/);
      if (match) {
        documentNo = match[1].trim();
        date = match[2].trim();
        period = match[3].trim();
      }
    }
    
    if (line.includes("Subtotal (excluding VAT)")) {
      var amountMatch = line.match(/[฿B](\d+(?:,\d+)*(?:\.\d+)?)/);
      if (amountMatch) subtotal = amountMatch[1].replace(/,/g, '');
    }
    
    if (line.includes("Total VAT")) {
      var vatMatch = line.match(/[฿B](\d+(?:,\d+)*(?:\.\d+)?)/);
      if (vatMatch) vat = vatMatch[1].replace(/,/g, '');
    }
    
    if (line.includes("Total amount") && line.includes("including VAT")) {
      var totalMatch = line.match(/[฿B](\d+(?:,\d+)*(?:\.\d+)?)/);
      if (totalMatch) total = totalMatch[1].replace(/,/g, '');
    }
  }
  
  // พิมพ์ลงชีตพร้อมแปลงวันที่
  sheet.appendRow([pdfUrl, documentNo, formatDateToDDMMYYYY(date), formatDateToDDMMYYYY(period), description, subtotal, vat, total]);
}

// ------------------------------------------------------------------
// 2. ฟังก์ชันจัดการ Logistics Fee
// ------------------------------------------------------------------
function processLogisticsFee(sheet, pdfUrl, lines) {
  var documentNo = "Not Found";
  var date = "Not Found";
  var period = "Not Found";
  var amount = "Not Found";
  var vat = "0"; 
  var total = "Not Found";
  var description = "Logistics fee";

  for (var i = 0; i < lines.length; i++) {
    var line = lines[i];
    
    if (line.includes("Receipt number") || line.includes("Receipt date")) {
      var match = line.match(/Receipt number[::]\s*(.*?)\s*Receipt date[::]\s*(.*?)\s*Period[::]\s*(.*)/);
      if (match) {
        documentNo = match[1].trim();
        date = match[2].trim();
        period = match[3].trim();
      }
    }
    
    if (line.includes("Total Amount")) {
      var match = line.match(/Total Amount\s*[฿B](\d+(?:,\d+)*(?:\.\d+)?)/);
      if (match) {
        total = match[1].replace(/,/g, '');
        amount = total; 
      }
    }
  }
  
  // พิมพ์ลงชีตพร้อมแปลงวันที่
  sheet.appendRow([pdfUrl, documentNo, formatDateToDDMMYYYY(date), formatDateToDDMMYYYY(period), description, amount, vat, total]);
}

// ------------------------------------------------------------------
// 3. ฟังก์ชันจัดการ Creator Commission
// ------------------------------------------------------------------
function processCreatorCommission(sheet, pdfUrl, lines) {
  try {
    var receiptData = extractCreatorReceiptData(findLineContaining(lines, "Receipt Number :"));
    var periodLine = findLineContaining(lines, "Period :");
    var period = periodLine ? periodLine.split("Period :")[1].trim() : "Not Found";
    var commissionLine = findLineContaining(lines, "Creator commission");
    var commission = commissionLine ? extractNumber(commissionLine) : "Not Found";
    var totalAmountLine = findLineContaining(lines, "Total Amount");
    var totalAmount = totalAmountLine ? extractNumber(totalAmountLine) : "Not Found";
    
    // พิมพ์ลงชีตพร้อมแปลงวันที่
    sheet.appendRow([pdfUrl, receiptData.number || "Not Found", formatDateToDDMMYYYY(receiptData.date) || "Not Found", formatDateToDDMMYYYY(period) || "Not Found", commission || "Creator commission", totalAmount || "Not Found", 0, totalAmount || "Not Found"]);
  } catch (e) { sheet.appendRow([pdfUrl, 'Error processing Creator commission: ' + e.message]); }
}

// ------------------------------------------------------------------
// ฟังก์ชันตัวช่วยต่างๆ 
// ------------------------------------------------------------------

// ฟังก์ชันแปลงวันที่ เดือนภาษาอังกฤษ เป็น DD/MM/YYYY
function formatDateToDDMMYYYY(dateString) {
  if (!dateString || dateString === "Not Found") return dateString;
  
  var months = {
    "Jan": "01", "Feb": "02", "Mar": "03", "Apr": "04", "May": "05", "Jun": "06",
    "Jul": "07", "Aug": "08", "Sep": "09", "Oct": "10", "Nov": "11", "Dec": "12"
  };

  function parseSingleDate(str) {
    // หาเดือนแบบตัวอักษร วันที่ และปี คั่นด้วยเครื่องหมาย , หรือช่องว่าง
    var match = str.trim().match(/([a-zA-Z]+)\s+(\d+),?\s+(\d{4})/);
    if (match) {
      var month = months[match[1].substring(0, 3)] || "00";
      var day = ("0" + match[2]).slice(-2); // เติม 0 ข้างหน้าถ้าเป็นเลขหลักเดียว
      var year = match[3];
      return day + "/" + month + "/" + year;
    }
    return str.trim();
  }

  // แยกรอบบิล (Period) ที่คั่นด้วย - เพื่อแปลงทั้งสองฝั่ง
  if (dateString.includes("-")) {
    var parts = dateString.split("-");
    return parseSingleDate(parts[0]) + " - " + parseSingleDate(parts[1]);
  } else {
    return parseSingleDate(dateString);
  }
}

function extractCreatorReceiptData(receiptLine) {
  if (!receiptLine) return { number: null, date: null };
  var result = { number: null, date: null };
  try {
    var numberPart = receiptLine.split("Receipt Number :")[1] || "";
    result.number = numberPart.split("Receipt Date :")[0].trim();
    var datePart = receiptLine.split("Receipt Date :")[1] || "";
    result.date = datePart.trim();
    if (result.number.includes("Creator Username:")) {
      result.number = result.number.split("Creator Username:")[1].trim();
    }
  } catch (e) {}
  return result;
}

function findLineContaining(lines, searchText) {
  var foundLine = lines.find(line => line.includes(searchText));
  return foundLine ? foundLine.trim() : null;
}

function extractNumber(line) {
  if (!line) return null;
  try {
    var bahtIndex = line.indexOf("฿");
    if (bahtIndex !== -1) return parseFloat(line.substring(bahtIndex + 1).trim().replace(/,/g, ''));
    var matches = line.match(/(\d+\.?\d*)/);
    if (matches && matches[0]) return parseFloat(matches[0]);
  } catch (e) {}
  return null;
}

คงชื่อฟังก์ชัน syncPdfToSheet_E_Tiktok() และ getSheetByName("E_Tiktok") ให้ตรงกับคู่มือ

จุดที่แก้ในภาพ: บรรทัด var folderId และ var sheetId

7. บันทึกโค้ด

กด Save รูปแผ่นดิสก์ในแถบเครื่องมือ หรือ Ctrl+S / Command+S แล้วรอให้ Unsaved changes หายไป หากช่องฟังก์ชันยังขึ้น No functions ให้ตรวจว่าโค้ดครบ ไม่มีข้อผิดพลาดไวยากรณ์ และบันทึกแล้ว

จุดที่กดในภาพ: ปุ่ม Save รูปแผ่นดิสก์ด้านบน

8. เพิ่มบริการ Drive

ที่แถบซ้ายกดเครื่องหมาย + ข้าง Services เพื่อเปิดหน้าต่าง Add a service ระวังอย่ากด + ข้าง Libraries

จุดที่กดในภาพ: + ทางขวาของคำว่า Services

10. เลือก Drive API และเวอร์ชัน

เลื่อนหา Drive API เลือก Version = v2 และ Identifier = Drive แล้วกด Add ตามภาพ เมื่อเสร็จต้องเห็น Drive ใต้ Services คู่มือนี้ใช้ v2 ตามภาพและโค้ดต้นฉบับ หากจะใช้ v3 ต้องปรับวิธีแปลง PDF และทดสอบแยก

สำหรับโปรเจกต์ Google Cloud แบบ default ที่ Apps Script สร้างให้ การเพิ่มบริการจะเปิด API ให้ด้วย หากเชื่อม standard Cloud project ไว้เอง ให้เปิด Google Drive API ใน Cloud Console ของโปรเจกต์นั้นเพิ่มเติม โค้ดใช้ DriveApp, DocumentApp และ SpreadsheetApp ที่เป็นบริการในตัว จึงไม่ต้องเพิ่ม Docs API หรือ Sheets API สำหรับการเรียกใช้เหล่านี้

จุดที่กดในภาพ: Drive API → Version v2 → Identifier Drive → Add

11. อัปโหลด PDF และรันครั้งแรก

อัปโหลด PDF ตัวอย่าง 1–2 ไฟล์เข้าโฟลเดอร์ที่กำหนดใน folderId โดยตรง กลับ Apps Script กด Save เลือกฟังก์ชัน syncPdfToSheet_E_Tiktok() แล้วกด Run เมื่อขึ้น Authorization required ให้กด Review permissions และเลือกบัญชี Google ที่เข้าถึงโฟลเดอร์และแก้ไขชีตได้

จุดที่กดในภาพ: Review permissions

12. ตรวจคำเตือนของ Google

หากพบ Google hasn’t verified this app ให้ตรวจว่าเป็นโปรเจกต์ที่คุณสร้างและโค้ดที่คุณตรวจแล้ว หากยังไม่มีลิงก์ไปต่อ ให้กด Advanced แล้วเลือก Go to [ชื่อโปรเจกต์] (unsafe) ภาพนี้เปิด Advanced แล้วจึงเห็นลิงก์ไปต่อ หากองค์กรบล็อกการอนุญาต ให้ติดต่อผู้ดูแล Google Workspace

จุดที่ดูและกดในภาพ: หัวคำเตือน และ Go to Untitled project (unsafe)

13. ตรวจรายการสิทธิ์และยืนยัน

อ่านรายการสิทธิ์ก่อนกด Continue โค้ดต้องอ่านไฟล์ใน Drive สร้าง Google Docs ชั่วคราวแล้วนำไปถังขยะ และเขียนหรือลบแถวใน Sheets หน้าต่างอนุญาตอาจต่างกันตามบัญชีและภาษา ใช้บัญชีที่เข้าถึงทั้งโฟลเดอร์และชีตได้

จุดที่ดูและกดในภาพ: รายการสิทธิ์ Drive / Docs / Sheets และ Continue ด้านล่าง

14. ตรวจผลการรันและข้อมูลในชีต

เมื่ออนุญาตเสร็จ หากสคริปต์ยังไม่เริ่มทำงานให้กด Run อีกครั้ง รอ Execution log แสดง Execution completed แล้วกลับแท็บ E_TIKTOK ตรวจข้อมูลแถว 2 เป็นต้นไป เปิด PDF ต้นฉบับเทียบเลขที่เอกสาร วันที่ ยอดก่อน VAT VAT และยอดรวม

ภาพนี้ยืนยันเฉพาะการจบการรัน ไม่ได้ยืนยันว่า OCR อ่านข้อมูลครบและถูกต้อง เพราะโค้ดจับข้อผิดพลาดแล้วเขียนเป็นแถว error ได้ด้วย สำหรับครั้งต่อไปให้อัปโหลด PDF ใหม่เข้าโฟลเดอร์เดิมแล้วกด Run สคริปต์จะข้าม File ID ที่อยู่ในคอลัมน์ A แล้ว และไม่มี Trigger อัตโนมัติในโค้ดเดิม

จุดที่ดูในภาพ: Execution log และ Execution completed

ปัญหาที่พบบ่อย

  • ไม่พบชีต E_Tiktok : ตรวจชื่อแท็บให้ตรง และ sheetId ชี้ไฟล์ที่ถูกต้อง
  • Drive is not defined: เพิ่ม Drive API ใน Services และตั้ง Identifier เป็น Drive
  • เปิดโฟลเดอร์หรือชีตไม่ได้: ใช้เฉพาะ ID ไม่ใช่ URL ทั้งเส้น และตรวจสิทธิ์ของบัญชีที่อนุญาต
  • รันสำเร็จแต่ไม่มีแถว: ตรวจว่ามี PDF ในโฟลเดอร์โดยตรงและ File ID ยังไม่เคยบันทึก
  • Error: No Text / Conversion Failed: ตรวจคอลัมน์ B ทดลองเปิด PDF ด้วย Google Docs และตรวจการแปลง PDF กับเวอร์ชัน API
  • บางช่องว่างหรือยอดผิด: ตรวจ PDF ต้นฉบับ เพราะ OCR อาจแบ่งบรรทัดไม่ตรงกับข้อความที่โค้ดค้นหา
  • แก้ PDF แล้วข้อมูลไม่เปลี่ยน: โค้ดข้าม File ID ที่มีในชีตแล้ว หากต้องการลองใหม่ให้สำรองชีตก่อน ลบเฉพาะแถว error ของไฟล์นั้น แล้วรันซ้ำ
  • แถวหายหลังย้าย PDF: โค้ดเดิมลบแถวเมื่อไม่พบไฟล์ในโฟลเดอร์ต้นทาง ควรคง PDF ไว้หรือปรับนโยบาย sync ก่อนใช้เป็นทะเบียนถาวร

ทางเลือก: รันอัตโนมัติภายหลัง

หลังแก้ปัญหาลบแถวและทดลองรันด้วยตนเองผ่านแล้ว เปิด Triggers รูปนาฬิกาที่แถบซ้าย → Add Trigger → เลือก syncPdfToSheet_E_Tiktok() → deployment Head → Event source Time-driven → เลือกช่วงเวลา เช่น ทุก 1 ชั่วโมง → Save บัญชีผู้สร้าง Trigger ต้องเข้าถึงโฟลเดอร์และชีตได้ ส่วนนี้ไม่มีภาพหน้าต่าง Trigger ในภาพต้นฉบับ

บทความดูเครื่องมือ