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. ดูรายการฐานข้อมูล
- เปิดเมนู Member Database ตารางแสดง ชื่อ (พร้อมคำอธิบายใต้ชื่อ), จำนวนฟิลด์, จำนวนแถว, การเชื่อมโยง, วันที่สร้าง และปุ่มแก้ไข/ลบ
- คลิกตัวเลขในคอลัมน์ ฟิลด์ เพื่อเปิด popover ดูรายชื่อคอลัมน์ทั้งหมดเป็นแท็ก
- คอลัมน์ การเชื่อมโยง แสดงชื่อฟอร์มทุกฟอร์มที่ผูกฐานข้อมูลนี้ไว้ หรือแท็ก "ยังไม่เชื่อมโยง" ถ้าไม่มี
- ช่องค้นหาด้านบนค้นทั้งชื่อและคำอธิบาย รายการเรียงจากใหม่ไปเก่าตามวันที่สร้างเสมอ
2. อัปโหลดฐานข้อมูลใหม่
- กดปุ่ม อัปโหลดฐานข้อมูล เข้าสู่ wizard 3 ขั้น
- ขั้นที่ 1 — อัปโหลด CSV ลากไฟล์
.csvมาวาง (ไฟล์เดียว) ระบบส่งไปให้ backend อ่านเป็นพรีวิวก่อน ยังไม่บันทึกอะไรลงฐานข้อมูล - ขั้นที่ 2 — ดูตัวอย่าง เห็นสรุป "n ฟิลด์, m แถว" รายชื่อคอลัมน์เป็นแท็ก และตารางตัวอย่าง 5 แถวแรก — ถ้าไฟล์มีข้อผิดพลาด กล่องแดงจะไล่รายการข้อผิดพลาดให้ครบทุกข้อ และปุ่มถัดไปถูกปิด ต้องย้อนกลับไปเลือกไฟล์ใหม่
- ขั้นที่ 3 — รายละเอียด กรอกชื่อ (บังคับ, ไม่เกิน 255 ตัวอักษร) และคำอธิบาย (ไม่บังคับ, ไม่เกิน 1,000 ตัวอักษร) แล้วกด สร้าง
3. แก้ไขหรือแทนที่ไฟล์
- กดปุ่มแก้ไขในตาราง — ถ้าฐานข้อมูลนี้มีฟอร์มผูกอยู่แล้ว จะมีกล่องเตือนสีเหลืองบอกจำนวนและชื่อฟอร์มทั้งหมด ว่าการแทนที่ไฟล์จะกระทบฟอร์มเหล่านั้น
- แก้แค่ชื่อ/คำอธิบายโดยไม่แนบไฟล์ใหม่ก็ได้ — ข้อมูลแถวเดิมไม่ถูกแตะต้อง
- หรือแนบไฟล์ CSV ใหม่เพื่อ แทนที่คอลัมน์และแถวทั้งหมด (พรีวิวตรวจสอบเหมือนตอนสร้าง และปุ่มบันทึกถูกปิดถ้าพรีวิวมีข้อผิดพลาด)
4. ลบฐานข้อมูล
- กดปุ่มลบ ระบบเปิดกล่องยืนยันพร้อมชื่อฐานข้อมูล
- ถ้าฐานข้อมูลนี้ยังมีฟอร์มผูกอยู่ การลบจะถูกปฏิเสธ และขึ้นข้อความจากเซิร์ฟเวอร์บอกชื่อฟอร์มที่ผูกไว้ ต้องไปปลดการผูกที่ฟอร์มก่อน
5. นำไปใช้ในเมนู Form builder
- เปิดฟอร์มที่เมนู Form builder เปิดสวิตช์ Profile Mapping แล้วเลือกฐานข้อมูลจากเมนูนี้
- ในคำถามชนิดตรวจสอบระเบียน (Database validation) เลือก คอลัมน์ของฐานข้อมูล ที่คำถามนั้นจะไปเทียบค่า
- ถ้าเปิดขั้นตอนยืนยันด้วย OTP ให้เลือกคอลัมน์ของ ระเบียนที่จับคู่ได้ เป็นช่องทางส่งรหัส (เช่นคอลัมน์
phone) — คอลัมน์นั้นไม่จำเป็นต้องเป็นคำถามในฟอร์ม - คู่ที่ผูกแล้วจะย้อนกลับมาแสดงในคอลัมน์ การเชื่อมโยง ของตารางในเมนูนี้
รายละเอียดสำคัญที่ตรวจสอบจากซอร์สโค้ดจริง
ยืนยันจาก 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 Database | Import 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ตรวจ actionVIEWของ subjectmember-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— จุดเข้าและPermissionGuardsrc/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")และการอ่าน multipartinternal/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)