Skip to main content

Member Database

ภาพรวม​

Member Database คือคลังข้อมูลสมาชิกที่ผู้ดูแลอัปโหลดขึ้นมาเองจากไฟล์ CSV (เช่นจากระบบ CRM หรือ Excel) เพื่อใช้เป็น "ตารางอ้างอิง" ที่แยกออกมาเป็นเอกเทศ ให้เมนู Form builder นำไปตรวจสอบว่าผู้กรอกฟอร์มเป็นสมาชิกจริงหรือไม่

จุดสำคัญที่แยกเมนูนี้ออกจากเมนู Import Data คือ Member Database ไม่ได้เขียนค่าลงโปรไฟล์ผู้ใช้ LINE เลย — ข้อมูลที่อัปโหลดขึ้นมาอยู่ในตารางของตัวเอง (customer_database + customer_database_row) และไม่มีโมดูลใดใน CMS API ที่นำแถวเหล่านั้นไปเขียนทับตาราง line_user (ต่างจาก Import Data ที่อัปเดตฟิลด์ของเพื่อนตรง ๆ)

บทบาทที่ใช้งาน — ผู้ดูแล CMS ที่มีรายชื่อสมาชิกเดิมอยู่แล้วในระบบอื่น และต้องการใช้รายชื่อนั้นเป็นเงื่อนไขยืนยันตัวตนบนฟอร์ม

เส้นทางผู้ใช้งาน​

1. ดูรายการฐานข้อมูล​

  1. เปิดเมนู Member Database ตารางแสดง ชื่อ (พร้อมคำอธิบายใต้ชื่อ), จำนวนฟิลด์, จำนวนแถว, การเชื่อมโยง, วันที่สร้าง และปุ่มแก้ไข/ลบ
  2. คลิกตัวเลขในคอลัมน์ ฟิลด์ เพื่อเปิด popover ดูรายชื่อคอลัมน์ทั้งหมดเป็นแท็ก
  3. คอลัมน์ การเชื่อมโยง แสดงชื่อฟอร์มทุกฟอร์มที่ผูกฐานข้อมูลนี้ไว้ หรือแท็ก "ยังไม่เชื่อมโยง" ถ้าไม่มี
  4. ช่องค้นหาด้านบนค้นทั้งชื่อและคำอธิบาย รายการเรียงจากใหม่ไปเก่าตามวันที่สร้างเสมอ

2. อัปโหลดฐานข้อมูลใหม่​

  1. กดปุ่ม อัปโหลดฐานข้อมูล เข้าสู่ wizard 3 ขั้น
  2. ขั้นที่ 1 — อัปโหลด CSV ลากไฟล์ .csv มาวาง (ไฟล์เดียว) ระบบส่งไปให้ backend อ่านเป็นพรีวิวก่อน ยังไม่บันทึกอะไรลงฐานข้อมูล
  3. ขั้นที่ 2 — ดูตัวอย่าง เห็นสรุป "n ฟิลด์, m แถว" รายชื่อคอลัมน์เป็นแท็ก และตารางตัวอย่าง 5 แถวแรก — ถ้าไฟล์มีข้อผิดพลาด กล่องแดงจะไล่รายการข้อผิดพลาดให้ครบทุกข้อ และปุ่มถัดไปถูกปิด ต้องย้อนกลับไปเลือกไฟล์ใหม่
  4. ขั้นที่ 3 — รายละเอียด กรอกชื่อ (บังคับ, ไม่เกิน 255 ตัวอักษร) และคำอธิบาย (ไม่บังคับ, ไม่เกิน 1,000 ตัวอักษร) แล้วกด สร้าง

3. แก้ไขหรือแทนที่ไฟล์​

  1. กดปุ่มแก้ไขในตาราง — ถ้าฐานข้อมูลนี้มีฟอร์มผูกอยู่แล้ว จะมีกล่องเตือนสีเหลืองบอกจำนวนและชื่อฟอร์มทั้งหมด ว่าการแทนที่ไฟล์จะกระทบฟอร์มเหล่านั้น
  2. แก้แค่ชื่อ/คำอธิบายโดยไม่แนบไฟล์ใหม่ก็ได้ — ข้อมูลแถวเดิมไม่ถูกแตะต้อง
  3. หรือแนบไฟล์ CSV ใหม่เพื่อ แทนที่คอลัมน์และแถวทั้งหมด (พรีวิวตรวจสอบเหมือนตอนสร้าง และปุ่มบันทึกถูกปิดถ้าพรีวิวมีข้อผิดพลาด)

4. ลบฐานข้อมูล​

  1. กดปุ่มลบ ระบบเปิดกล่องยืนยันพร้อมชื่อฐานข้อมูล
  2. ถ้าฐานข้อมูลนี้ยังมีฟอร์มผูกอยู่ การลบจะถูกปฏิเสธ และขึ้นข้อความจากเซิร์ฟเวอร์บอกชื่อฟอร์มที่ผูกไว้ ต้องไปปลดการผูกที่ฟอร์มก่อน

5. นำไปใช้ในเมนู Form builder​

  1. เปิดฟอร์มที่เมนู Form builder เปิดสวิตช์ Profile Mapping แล้วเลือกฐานข้อมูลจากเมนูนี้
  2. ในคำถามชนิดตรวจสอบระเบียน (Database validation) เลือก คอลัมน์ของฐานข้อมูล ที่คำถามนั้นจะไปเทียบค่า
  3. ถ้าเปิดขั้นตอนยืนยันด้วย OTP ให้เลือกคอลัมน์ของ ระเบียนที่จับคู่ได้ เป็นช่องทางส่งรหัส (เช่นคอลัมน์ phone) — คอลัมน์นั้นไม่จำเป็นต้องเป็นคำถามในฟอร์ม
  4. คู่ที่ผูกแล้วจะย้อนกลับมาแสดงในคอลัมน์ การเชื่อมโยง ของตารางในเมนูนี้

รายละเอียดสำคัญที่ตรวจสอบจากซอร์สโค้ดจริง​

ยืนยันจาก internal/modules/customerdatabase/{controller,service,dto,papaparse,papaparse_config}.go ของ line-management-cms-api-go:

  • ไฟล์ CSV ที่มีคอลัมน์เดียวอัปโหลดไม่ได้เลย — ตัวอ่าน CSV ของโมดูลนี้จำลองพฤติกรรมของ papaparse ไว้ทั้งหมด รวมถึงการเดาตัวคั่นอัตโนมัติที่ยอมรับตัวคั่นได้เฉพาะเมื่อจำนวนฟิลด์เฉลี่ยต่อบรรทัด มากกว่า 1.99 ไฟล์คอลัมน์เดียวจึงไม่มีตัวคั่นใดผ่านเกณฑ์ ระบบบันทึกเป็นข้อผิดพลาด "ไม่สามารถตรวจจับตัวคั่นได้ ใช้ , เป็นค่าเริ่มต้น" และเมื่อมีข้อผิดพลาดใด ๆ ปุ่มถัดไปจะถูกปิดทันที
  • ขีดจำกัดจริงคือ 10 MB และ 50,000 แถว — ทั้งสองค่าถูกตรวจฝั่ง backend และรายงานเป็นข้อผิดพลาดในผลพรีวิว (File exceeds the 10MB size limit, CSV exceeds the 50000 row limit) — สังเกตว่าหน้าเว็บไม่ได้ตรวจขนาดไฟล์ก่อนส่ง จึงต้องอัปโหลดขึ้นไปก่อนแล้วจึงรู้ผล
  • "มีข้อผิดพลาด" ทำให้พรีวิวเปลี่ยนความหมายของตัวเลข — เมื่อไม่มีข้อผิดพลาด rowCount คือจำนวนแถวที่จะถูกบันทึกจริงและมีตัวอย่าง 5 แถว; เมื่อมีข้อผิดพลาด รายการแถวจะถูกล้างเป็นค่าว่าง ตัวอย่างหายไปหมด แต่ rowCount กลับรายงานจำนวนแถวดิบที่อ่านได้ — จึงเป็นเรื่องปกติที่พรีวิวที่ล้มเหลวจะยังโชว์ตัวเลขแถวอยู่แต่ไม่มีตารางตัวอย่างให้ดู
  • หัวคอลัมน์ถูกตรวจสามเรื่องแยกกัน — ต้องมีแถวหัวคอลัมน์ (CSV file has no header row), ห้ามมีเซลล์หัวคอลัมน์ที่ว่าง (CSV header contains empty cell(s)) และห้ามมีชื่อคอลัมน์ซ้ำ (CSV header contains duplicate cell(s): ... โดยไล่ชื่อที่ซ้ำมาให้ตามลำดับที่พบครั้งแรก) — ทั้งสามข้อถูกตรวจกับหัวคอลัมน์ดิบ ไม่ใช่หัวคอลัมน์ที่ผ่านการเปลี่ยนชื่ออัตโนมัติ
  • จำนวนช่องต่อบรรทัดที่ไม่เท่ากันถูกรายงานเป็นรายบรรทัด — เช่น Row 12: Too many fields: expected 5 fields but parsed 6 ทำให้แก้ไฟล์ได้ตรงจุด แต่เพราะข้อผิดพลาดข้อเดียวก็ปิดปุ่มถัดไปแล้ว ไฟล์ที่มีบรรทัดเพี้ยนแค่บรรทัดเดียวจึงอัปโหลดไม่ได้ทั้งไฟล์
  • ทุกค่าถูกเก็บเป็นข้อความล้วน — ตัวอ่านปิด dynamic typing ไว้ ตัวเลขและวันที่จึงถูกเก็บเป็นสตริงตามที่พิมพ์มา ทุกเซลล์ถูก trim และเซลล์ที่ขาดหายกลายเป็นสตริงว่าง "" — ลำดับคอลัมน์ตามไฟล์ CSV ถูกรักษาไว้ ทั้งในคำตอบของ API และในค่า jsonb ที่บันทึกลงฐานข้อมูล
  • ไฟล์ถูกอ่านสองครั้งและพรีวิวไม่ได้ถูกนำมาใช้ซ้ำ — ขั้นพรีวิว (POST /customer-database/preview) แค่แยกวิเคราะห์แล้วโยนผลกลับมา ไม่เก็บอะไรไว้เลย ตอนกดสร้างหน้าเว็บส่งไฟล์เดิมขึ้นไปใหม่และ backend แยกวิเคราะห์อีกรอบ — ถ้าไฟล์ต้นทางถูกแก้ระหว่างนั้น ไม่มีกลไกใดตรวจจับความต่าง
  • การบันทึกแถวทำในทรานแซกชันเดียว แบ่งใส่เป็นก้อนละ 1,000 แถว — ทั้งตอนสร้างและตอนแทนที่ไฟล์ ถ้าล้มกลางทางจะย้อนกลับทั้งหมด ไม่มีสภาพ "อัปโหลดได้ครึ่งไฟล์"
  • การแทนที่ไฟล์คือการลบทิ้งแล้วเขียนใหม่ทั้งหมด — คอลัมน์และจำนวนแถวถูกเขียนทับ แถวเดิมถูกลบออกทั้งชุดก่อนจะใส่ชุดใหม่เข้าไป — ไม่มีการรวมข้อมูลเดิมกับใหม่ ไม่มีการเก็บเวอร์ชันย้อนหลัง
  • แก้ไขฐานข้อมูลที่มีฟอร์มผูกอยู่ "ทำได้" แต่ลบ "ทำไม่ได้" — ขั้นตอนแก้ไขไม่มีการกันไว้เลย (หน้าเว็บเตือนเท่านั้น) ส่วนขั้นตอนลบตรวจจริงที่ backend และตอบ 400 Database is mapped by form(s): <ชื่อฟอร์ม> ถ้ายังมีฟอร์มผูกอยู่
  • การหาว่า "มีฟอร์มใดผูกอยู่" ใช้การอ่านค่า JSON ตรง ๆ — query เทียบ form_builder.profile_mapping ->> 'databaseId' กับรหัสฐานข้อมูลในรูปสตริง และตัดฟอร์มที่สถานะเป็น delete ออก — ฟอร์มที่ถูกลบแล้วจึงไม่กันการลบฐานข้อมูล
  • การลบเป็น soft delete แบบใช้สถานะ ไม่ใช่คอลัมน์วันที่ลบ — เรกคอร์ดถูกเปลี่ยน status เป็น delete และทุก query ของโมดูลนี้กรองด้วย status != 'delete' (ไม่มีเงื่อนไข deleted_date IS NULL เลย) — แถวข้อมูลใน customer_database_row ไม่ได้ถูกลบตามไปด้วย ยังอยู่ในฐานข้อมูลเพียงแต่เข้าถึงผ่าน API ไม่ได้แล้ว
  • มี endpoint ดูรายละเอียดรายตัวอยู่จริงแต่หน้าเว็บไม่เคยเรียก — GET /customer-database/:id คืนข้อมูลชุดเดียวกับในตารางบวกตัวอย่าง 5 แถวแรก (เรียงตาม id ขึ้น) แต่หน้ารายการไม่มีทางเข้าหน้ารายละเอียด — ข้อมูลที่ผู้ใช้เห็นได้จริงจึงมีแค่ในตารางและ popover รายชื่อคอลัมน์
  • พารามิเตอร์ :id ไม่ถูกแปลงเป็นตัวเลขก่อน — controller รับค่าดิบเป็นสตริงส่งเข้า query ตรง ๆ ตามต้นฉบับ ถ้าเรียกด้วย id ที่ไม่ใช่ตัวเลข Postgres จะโยนข้อผิดพลาดออกมาเป็น 500 (ไม่ใช่ 400)
  • การเรียงลำดับและการแบ่งหน้ามีทางเลือกมากกว่าที่หน้าเว็บใช้ — backend รับ orderBy (ค่าเริ่มต้น createdAt) และ order (ค่าเริ่มต้น DESC) พร้อมแปลงชื่อ property เป็นชื่อคอลัมน์จริงให้ และถ้าส่ง limit ที่ไม่มากกว่า 0 มา จะไม่แบ่งหน้าเลย คืนทุกแถว — หน้าเว็บไม่เคยส่ง orderBy/order จึงได้ผลเรียงใหม่→เก่าตลอด
  • เพดานความยาวชื่อ 255 และคำอธิบาย 1,000 ตัวอักษรเป็นข้อจำกัดของหน้าเว็บเท่านั้น — ฝั่ง backend ตรวจแค่ว่าชื่อเป็นสตริงที่ไม่ว่าง (trim แล้ว) และคำอธิบายเป็นสตริงถ้าส่งมา ไม่มีการตรวจความยาวใด ๆ
  • ตอนแก้ไข หน้าเว็บส่งคำอธิบายเป็นสตริงว่างเสมอเมื่อผู้ใช้ล้างช่องนั้น ไม่ได้ส่ง null — ค่าที่บันทึกจึงเป็น "" ไม่ใช่ NULL ซึ่งเป็นเหตุให้คำอธิบายที่ "ลบแล้ว" ยังนับเป็นค่าที่มีอยู่ในการค้นหาด้วย description ILIKE
  • การค้นหาในรายการค้นทั้งชื่อและคำอธิบาย — เงื่อนไข name ILIKE หรือ description ILIKE ถูกครอบเป็นวงเล็บเดียวแล้ว AND กับเงื่อนไขขอบเขต OA และ status != 'delete' เสมอ
  • คำตอบของ API แยกความต่างระหว่าง "ไม่ได้ส่งคำอธิบายมา" กับ "คำอธิบายเป็น null" — ตอนสร้างที่ไม่ได้กรอกคำอธิบาย คีย์ description จะไม่ปรากฏใน JSON เลย ส่วนตอนอ่านจากฐานข้อมูลคีย์จะปรากฏพร้อมค่า null (คงพฤติกรรมตามต้นฉบับไว้ตรงตัว)

เมนูที่เกี่ยวข้องกันในเส้นทางนี้​

  • Form builder — ผู้บริโภคหลักและผู้บริโภคเดียวของเมนูนี้ ความเชื่อมโยงเกิดผ่านค่า profile_mapping.databaseId ในเรกคอร์ดฟอร์ม โดย คอลัมน์ ของฐานข้อมูลถูกใช้สองที่: (1) เป็นตัวเลือก "ฟิลด์ฐานข้อมูล" ของคำถามชนิดตรวจสอบระเบียน (Database validation) และ (2) เป็นช่องทางส่ง OTP ให้ระเบียนที่จับคู่ได้ — ขั้นตอนยืนยันตัวตนจะเปิดใช้ได้เฉพาะเมื่อ Profile Mapping เปิดอยู่ เลือกฐานข้อมูลแล้ว และฐานข้อมูลนั้นมีคอลัมน์อย่างน้อยหนึ่งคอลัมน์
  • Settings > OTP Config — ถ้าจะใช้ขั้นตอนยืนยัน OTP บนฟอร์มที่ผูกฐานข้อมูลนี้ ต้องตั้งค่าช่องทาง SMS หรืออีเมลของ OA ไว้ก่อน ไม่เช่นนั้นตัวเลือกช่องทางจะถูกปิดไว้
  • Import Data — คนละกลไกกันโดยสิ้นเชิง ไม่ใช่แค่คนละเมนู ดูตารางเปรียบเทียบด้านล่าง

Member Database ต่างจาก Import Data อย่างไร​

ความต่างทั้งหมดนี้ตรวจสอบจากซอร์สโค้ดของทั้งสองโมดูลได้จริง:

Member DatabaseImport Data
ตารางปลายทางcustomer_database / customer_database_row (jsonb ต่อแถว)line_user (ฟิลด์โปรไฟล์ + custom_attribute)
วิธีระบุตัวตนไม่มี — เก็บทุกแถวตามที่อยู่ในไฟล์ต้องเลือก match key (LINE UID / เบอร์ / อีเมล / custom attribute) เทียบกับคอลัมน์ใน CSV
ผลต่อโปรไฟล์เพื่อนไม่แตะเลยอัปเดตทับค่าเดิมของเพื่อนที่จับคู่ได้
การจับคู่ฟิลด์ไม่มี — ชื่อคอลัมน์ถูกใช้เป็นชื่อฟิลด์ตรงตัวต้องจับคู่คอลัมน์→ฟิลด์เองทีละคอลัมน์
การประมวลผลทำทันทีในคำขอเดียว (ทรานแซกชัน)เข้าคิว RabbitMQ ให้ worker ทำเบื้องหลัง
แถวที่ใช้ไม่ได้ทำให้ทั้งไฟล์ถูกปฏิเสธถูกข้ามเป็นรายแถวและสรุปไว้ในไฟล์ log
ลบได้หรือไม่ลบได้ (soft delete) เว้นแต่มีฟอร์มผูกอยู่ไม่มีการลบ — เก็บเป็นประวัติงานนำเข้า

พูดสั้นที่สุด: Member Database เพิ่มตารางอ้างอิงไว้ข้าง ๆ โดยไม่แตะข้อมูลเพื่อน ส่วน Import Data เปลี่ยนตัวข้อมูลเพื่อนโดยตรง

จุดที่ควรระวัง​

  • การแทนที่ไฟล์ CSV ด้วยไฟล์ที่ชื่อคอลัมน์ไม่เหมือนเดิม จะทำให้ฟอร์มที่ผูกไว้พังแบบเงียบ ๆ — คำถามชนิดตรวจสอบระเบียนอ้างอิงคอลัมน์ด้วยชื่อ และคอลัมน์ส่ง OTP ก็เช่นกัน ระบบไม่ได้ตรวจย้อนว่าชื่อคอลัมน์ที่ฟอร์มใช้ยังมีอยู่ในไฟล์ใหม่หรือไม่ ก่อนแทนที่ไฟล์ให้เทียบชื่อคอลัมน์ให้ตรงกันก่อนเสมอ
  • ไฟล์ CSV คอลัมน์เดียวใช้กับเมนูนี้ไม่ได้ ต้องมีอย่างน้อยสองคอลัมน์เพื่อให้ระบบเดาตัวคั่นได้
  • ไฟล์ที่ผิดพลาดแค่บรรทัดเดียวก็อัปโหลดไม่ได้ทั้งไฟล์ — พรีวิวจะไล่บอกเป็นรายบรรทัดให้แก้
  • ขนาดไฟล์ไม่ได้ถูกตรวจก่อนส่ง ไฟล์ใหญ่เกิน 10 MB จะถูกอัปโหลดขึ้นไปทั้งก้อนก่อนจะรู้ว่าถูกปฏิเสธ
  • การแทนที่ไฟล์ไม่มีเวอร์ชันย้อนหลังและไม่มีปุ่มยกเลิก — ข้อมูลชุดเดิมหายทันทีที่บันทึกสำเร็จ ควรเก็บไฟล์ต้นฉบับไว้เอง
  • ตัวเลขและวันที่ถูกเก็บเป็นข้อความตามที่พิมพ์มา — การเทียบค่าบนฟอร์มจึงเป็นการเทียบข้อความ ควรทำรูปแบบในไฟล์ให้สม่ำเสมอ (เช่น เบอร์โทรที่มีขีดกับไม่มีขีดถือเป็นค่าต่างกัน)
  • ไม่มีหน้ารายละเอียดแยกและไม่มีฟังก์ชันส่งออกกลับเป็น CSV — ตรวจข้อมูลที่อัปโหลดไปแล้วได้แค่จากจำนวนแถว/ฟิลด์ในตารางและ popover รายชื่อคอลัมน์
  • ลบฐานข้อมูลที่มีฟอร์มผูกอยู่ไม่ได้ ต้องปลดการผูกที่เมนู Form builder ก่อน (ตรวจคอลัมน์ "การเชื่อมโยง" ให้เป็น "ยังไม่เชื่อมโยง" ก่อนลบ)

สิทธิ์การเข้าถึง (ตรวจสอบจากซอร์สโค้ดจริง)​

  • ฝั่งหน้าเว็บ — ถูกครอบด้วย PermissionGuard ตรวจ action VIEW ของ subject member-database
  • ฝั่งเซิร์ฟเวอร์ — ทั้ง 6 เส้นทางถูกครอบด้วย ModuleGate(d, "customer-database") ที่บังคับใช้จริง ทั้ง POST /customer-database/preview, POST /customer-database, GET /customer-database, GET /customer-database/:id, PUT /customer-database/:id, DELETE /customer-database/:id — ถ้าผู้ดูแลระดับแพลตฟอร์มปิดโมดูลนี้ให้องค์กรใด ผู้ใช้ประเภท customer ในองค์กรนั้นจะได้ 403 ทันทีแม้เรียก API ตรง (ผู้ใช้ประเภท onemoby ข้ามการกั้นนี้เสมอ)
  • ชื่อโมดูลฝั่ง backend กับ subject สิทธิ์ฝั่งหน้าเว็บ "ไม่ใช่ชื่อเดียวกัน" — หน้าเว็บใช้ member-database ส่วน ModuleGate ใช้ customer-database (ตรงกับ system_module.name และชื่อไดเรกทอรี internal/modules/customerdatabase/) ทั้งสองชื่อหมายถึงฟีเจอร์เดียวกัน แต่ต้องตั้งค่าคนละที่ — เวลาตรวจว่า "ทำไมเข้าไม่ได้" ต้องดูทั้งสองชื่อ
  • ทุกคำขอถูกจำกัดขอบเขตด้วย line_oa_id จาก context — ฐานข้อมูลของ OA อื่นมองไม่เห็นและแก้ไม่ได้ แม้จะรู้รหัสก็ตาม (จะได้ 404 "Customer database not found")
  • policy annotation ระดับการกระทำ (create/read/readAll/update/delete) มีประกาศครบทุกเส้นทางแต่วิ่งผ่าน middleware ที่เป็น no-op เหมือนโมดูลอื่น — การบังคับจริงอยู่ที่ ModuleGate ระดับโมดูล
  • ไม่มีเส้นทางใดในโมดูลนี้ที่เป็น public และไม่มีเส้นทาง hard delete — การลบทั้งหมดเป็น soft delete

ไฟล์อ้างอิงจากซอร์สโค้ด​

Frontend (cms-web-2026)

  • src/app/member-database/page.tsx — จุดเข้าและ PermissionGuard
  • src/components/member-database/member-database.container.tsx — การดึงรายการ, การแบ่งหน้า, กล่องยืนยันการลบ
  • src/components/member-database/member-database-table.tsx — คอลัมน์ทั้งหมด, popover รายชื่อคอลัมน์, แท็ก "การเชื่อมโยง"
  • src/components/member-database/upload-database-modal.tsx — wizard 3 ขั้น และเงื่อนไขปิดปุ่มถัดไปเมื่อพรีวิวมีข้อผิดพลาด
  • src/components/member-database/edit-database-modal.tsx — กล่องเตือนฟอร์มที่ผูกอยู่ และการส่งคำอธิบายเป็นสตริงว่าง
  • src/components/member-database/csv-preview-detail.tsx — การแสดงผลพรีวิวและรายการข้อผิดพลาด
  • src/components/form-builder/form/components/SpecialFields.tsx, src/components/form-builder/form/verification/verification-form.tsx — จุดที่ คอลัมน์ ของฐานข้อมูลถูกนำไปใช้ในฟอร์ม

Backend (line-management-cms-api-go)

  • internal/modules/customerdatabase/controller.go — 6 เส้นทาง + ModuleGate("customer-database") และการอ่าน multipart
  • internal/modules/customerdatabase/service.go — ขีดจำกัด 10 MB / 50,000 แถว, การบันทึกเป็นก้อนละ 1,000, การตรวจฟอร์มที่ผูกอยู่, soft delete แบบใช้สถานะ
  • internal/modules/customerdatabase/papaparse.go, papaparse_config.go — การเดาตัวคั่น (เกณฑ์ > 1.99) และการตรวจหัวคอลัมน์/จำนวนช่องต่อบรรทัด
  • internal/modules/customerdatabase/dto.go — กฎตรวจข้อมูลขาเข้าของทั้งสามเส้นทางที่รับไฟล์
  • internal/modules/customerdatabase/orderedrow.go, orderedmap.go — การรักษาลำดับคอลัมน์เดิมของไฟล์ CSV

ดูรายละเอียดเชิงเทคนิคเพิ่มเติมที่ ฐานข้อมูลสมาชิก (Member Database)