บันทึกของไม้ท่อนหนึ่ง
ตอนสร้าง ERP ในบทความก่อน ผมเข้าใจว่างานบางอย่างเป็นงานของระบบที่ยืนด้วยตัวเอง พอการนำเข้าไม้เริ่มขึ้น เรื่องนี้ก็เป็นรูปเป็นร่าง คือการตามวัตถุดิบหลายร้อยชิ้นทีละชิ้น วัดด้วยเครื่องอ่านมือถือ ตามด้วย QR ระบบคลังสินค้าอีกตัวที่ต้องสร้างขึ้นใหม่จากศูนย์ บทความนี้เล่าเส้นทางของไม้ท่อนหนึ่ง ตั้งแต่ลงถึงหน้างานจนขึ้นรถบรรทุกของลูกค้า และการตัดสินใจทางซอฟต์แวร์ที่ผมทำระหว่างทาง
ในบทความก่อน ผมเล่าว่าย้าย ERP ที่รับช่วงมาขึ้น stack ที่ทันสมัยอย่างไร แล้วถักสายการผลิตที่ยังขาดอยู่ที่หน้างานขึ้นใหม่จากศูนย์อย่างไร บทความนี้ถือเป็นภาคต่อของมัน เพราะมีสิ่งหนึ่งที่ผมเข้าใจตอนสร้าง ERP ตัวนั้น คืองานบางอย่างไม่ใช่งานของ ERP โปรเจกต์นี้เกิดจากการรู้ตัวข้อนั้น
”งานนี้คุณยัดลง ERP ไม่ได้หรอก”
ตอนสร้าง ERP ในหัวผมมีคำถามเดิมตลอด งานไหนควรอยู่ข้างใน งานไหนเป็นงานของแอปที่ยืนด้วยตัวเอง เพราะเวลาคุณพยายามกองทุกอย่างไว้ในระบบเดียว ไม่ระบบก็อืดอาด ไม่อย่างนั้นความคล่องในการใช้งานก็พัง ที่หน้างานมีพื้นที่งานที่ยังบริหารด้วยแบบแผน Excel และ WhatsApp อยู่ ผมอึดอัดที่ไม่ได้ไปแตะมัน แต่การอัดทุกอย่างเข้าไปใน ERP ก็ไม่ถูก
แล้วในโรงงานก็มีพื้นที่งานใหม่เกิดขึ้น คือการนำเข้าไม้ ก่อนหน้านี้เราซื้อวัตถุดิบจากผู้ขายหลายเจ้า ด้วยความสัมพันธ์ที่สั่งสมมาหลายปี การนำเข้าไม้เป็นอีกเรื่องหนึ่ง ปริมาณหนัก ของจำนวนไม่น้อย และเป็นงานที่เรายังไม่รู้รายละเอียดส่วนใหญ่ ตู้คอนเทนเนอร์เดินทางมาถึงตุรกีทางเรือ ลงที่คลัง ไม้ดิบข้างในนอนอยู่บนชั้นเป็นวันหรือเป็นเดือน แล้วถูกขายให้ลูกค้าทีละชิ้นหรือทีละพาเลท
ตอนแรกผมคิดจะเพิ่มเป็นโมดูลใน ERP แต่ยิ่งคิดก็ยิ่งยืนยันได้ยาก นี่มันคือ ERP ของใบเสนอราคาและคำสั่งซื้อ ส่วนไม้ในคลังคือวัตถุดิบที่ต้องวัดทีละชิ้นเป็นร้อย ๆ ชิ้น ต้องติดฉลาก และต้องตามเป็นรายชิ้นจนกว่าจะขายออก การไหลของงานที่หนักหน้างานขนาดนี้อยู่ข้างใน ERP จะทำให้ศูนย์กลางของระบบพร่ามัว ฝั่ง stack เองก็ไม่เข้ากัน ERP สร้างบน Blade + Alpine + MySQL ส่วนตรงนี้ผมจะทำด้วย Vue + Inertia + Postgres + PWA อุปกรณ์หน้างาน การทำงานออฟไลน์ การคุยกับเครื่องพิมพ์ฉลากผ่าน TCP โดยตรง พวกนี้เป็นคุณสมบัติของอีกแอปหนึ่ง
การตัดสินใจว่าจะเขียนใหม่จากศูนย์ แยกออกมา และเขียนเองอีกครั้ง จึงเกิดขึ้นง่าย เหตุผลหลักยังคงเป็นต้นทุนซอฟต์แวร์
การพบกับโลกของอุปกรณ์ครั้งแรก
ก่อนการตัดสินใจทางซอฟต์แวร์ มีความกลัวอยู่อย่างหนึ่ง คือเครื่องอ่านมือถือ อุปกรณ์หน้างานเป็นโลกที่ผมไม่เคยรู้จักเลย
ผมสั่งเครื่องมาทดสอบตัวหนึ่ง เป็นเครื่อง Android handheld ของจีนราคาถูก ฝาปิดติดกาวมาเบี้ยว ผมงมอยู่กับมันสองคืน บาร์โค้ดอ่านอย่างไร ระบบตรวจจับเครื่องอย่างไร หน้าจอมีพฤติกรรมแบบไหน ใช้เบราว์เซอร์ตัวไหน แล้วผมก็พบว่ามันเป็นเครื่อง Android มาตรฐานโดยประมาณ แค่มีเคสแข็งแรงหุ้มอยู่ มีปุ่มไก และมีเครื่องอ่านเลเซอร์
เรื่องนี้ทำให้โล่งใจ แต่ในเวลาเดียวกันก็สร้างความกลัวใหม่ ในเมื่อเป็น Android แปลว่าผมต้องเขียนแอปเป็น APK ผมไม่มีประสบการณ์ Android เลย APK คืออะไร เขียนด้วยภาษาอะไร ใช้ฐานข้อมูลอะไร deploy อย่างไร ผมไม่มีความคิดใด ๆ ทั้งสิ้น ช่วงที่ผมคิดว่า “งานนี้ผมจะเอาตัวรอดยังไง” เป็นช่วงที่เกิดขึ้นจริง
ทางออกจริง ๆ อยู่ในฉากหลังของแอปอยู่แล้ว ผมไม่ได้ถูกบังคับให้เขียน APK แบบ native สำหรับแอปตามคลังสินค้า แค่ทำของที่รันในเว็บเบราว์เซอร์ก็พอ Chrome บนเครื่องอ่านมือถือรัน Vue ได้อยู่แล้ว ผมเพิ่ม PWA ลงไปข้างบน (manifest, service worker, offline cache) ความรู้สึกว่าเป็น “แอป” ก็ยังอยู่กับผู้ใช้ เปิดมาเต็มจอ ทำงานออฟไลน์ได้ เพิ่มลงหน้าจอหลักได้
พูดอีกอย่างคือการเลือก PWA ของผมไม่ใช่ “การลองเทคโนโลยีใหม่” มันคือการหลบ ดึงพื้นที่ที่ผมไม่รู้ (Android native) เข้ามาในพื้นที่ที่ผมรู้ (เว็บ) ผู้ใช้จะสังเกตเห็นไหม ไม่เห็น มันให้ข้อได้เปรียบกับผมไหม มหาศาล
อุปกรณ์หน้างานของเราวันนี้คือ Zebra TC21 เป็น Android handheld ระดับมืออาชีพ ฝั่งเครื่องพิมพ์ฉลากก็ลงตัวในสองก้าว ตอนแรกเราลอง Zebra ZPL 320 ตัวเล็กแบบพกพา การเชื่อมต่อมีปัญหา ขนาดฉลากก็ไม่พอ เราเลยเปลี่ยนไปใช้ TSC TE210 (203 dpi, 70×50 มม.) มีประสิทธิภาพ ไม่มีปัญหา และยังใช้อยู่จนถึงตอนนี้ เราคุยกับเครื่องพิมพ์ผ่าน TCP socket ตรงไปที่พอร์ต 9100 คำสั่ง TSPL2 สร้างจากฝั่งเซิร์ฟเวอร์ แล้วพิมพ์อักขระภาษาตุรกีออกมาถูกต้องด้วย code page 1254
พาเลทเต็ม หรือแยกชิ้น
ตอนเริ่มสร้างระบบ มีการตัดสินใจเชิงออกแบบที่ตั้งใจไว้อย่างหนึ่ง คือเราจะไม่แกะพาเลท ลูกค้าซื้อเป็นพาเลท เราไม่ขายปลีก แบบนี้ให้ความสบายในเชิงปฏิบัติการ และทำให้ระบบเรียบง่ายด้วย ไม่ต้องมี state machine เพิ่มเพื่อตามเป็นรายชิ้น
แต่มุมหนึ่งในหัวผมวนกลับมาที่คำถามเดิมตลอด ถ้ามีคนแกะพาเลทแล้วขอเป็นชิ้น ๆ ที่เหลือเราจะตามอย่างไร
คำถามนี้เรียกร้องคำตอบหลังจากนั้นไม่นาน ผมออกแบบระบบให้ตามได้ถึงระดับรายชิ้น QR ของพาเลทยังคงอยู่เหมือนเดิม แต่สถานะของไม้ข้างใน (ในสต็อก / จอง / ขายแล้ว) ถูกพาไปเป็น state ด้วย JSON-data พอเครื่องอ่านมือถือสแกนพาเลท ระบบบอกได้แล้วว่า “พาเลทนี้ถูกแกะแล้ว ไม้ 3 ชิ้นข้างในขายให้ลูกค้ารายนี้ไป เหลืออีก 7 ชิ้น”
ผลลัพธ์ที่เป็นรูปธรรมของการตัดสินใจนี้จะเห็นในหัวข้อถัดไป การไหลของงานหน้างานในการขายรายชิ้นถูกวางตามวิวัฒนาการนี้
สถานการณ์ต่าง ๆ ถูกทดสอบแล้ว ผมยังมองไม่เห็นช่องโหว่ แต่จะมีอะไรที่ผมมองข้ามไปโผล่ขึ้นมาในชีวิตจริงหรือเปล่า ผมไม่รู้ เดี๋ยวเวลาจะบอกเอง
เส้นทางของไม้ท่อนหนึ่ง
งานที่ระบบทำ เล่าผ่านไม้ท่อนเดียวจะเห็นภาพที่สุด
หลายวันก่อนตู้คอนเทนเนอร์จะถึงตุรกี ใบรายการบรรจุจะมาจากผู้จัดหา คือรายการที่บอกว่า “ในตู้นี้มีไม้ขนาดนี้ ชนิดนี้ จำนวนเท่านี้” เราอัปโหลด PDF นี้เข้าระบบ ผู้ใช้ฝ่ายรับสินค้าเปิดจากเครื่องอ่านมือถือแล้วเห็นล่วงหน้าว่าอะไรกำลังจะมา นี่เป็นรายละเอียดเล็ก ๆ แต่สร้างความต่างมากที่หน้างาน ผู้ใช้ประกอบภาพของสินค้าในหัวได้ก่อนตู้จะมาถึง ถ้าไม่มีในระบบ เขาต้องโทรถามออฟฟิศทุกครั้งว่า “ตกลงรอบนี้อะไรจะมา”
หลังตู้ถูกยกลง กระบวนการรับสินค้าก็เริ่ม ผู้ใช้กด “รับสินค้า” จากเครื่องอ่านมือถือ
- เลือกวันที่แล้วยืนยัน
- เลือกผู้จัดหา ตรงนี้มีการตัดสินใจเชิงออกแบบที่ตั้งใจไว้ ระบบไม่ได้แสดงผู้จัดหาทั้งหมดให้เขา แต่แสดงเฉพาะรายที่ ซื้อไว้แล้วและกำลังจะส่งมอบเร็ว ๆ นี้ ถ้าผู้จัดหาหลายสิบรายเรียงอยู่ข้างกันในรายการ ผู้ใช้อาจเผลอเลือกผิดคน ผมลดโอกาสพลาดนี้ให้เป็นศูนย์ด้วยการหุบรายการให้แคบลง
- เลือกสินค้าและคุณภาพ
- ระบบถามคำถามหนึ่งข้อ “ไม้ที่จะกรอกมีความยาวและความหนาเท่ากันหรือไม่” ถ้าไม้ทั้งพาเลทหนา × ยาวเท่ากันหมด ผู้ใช้ตอบ “ใช่” ระบบรับค่าร่วมสองค่านั้นครั้งเดียว แล้วหลังจากนั้นขอแค่ ความกว้าง ของไม้แต่ละชิ้นก็พอ ถ้าไม่ใช่ ต้องกรอกไม้ทีละชิ้นด้วยค่าสามค่า วิธีนี้ลดภาระงานบนเครื่องอ่านมือถือลงเหลือเกือบหนึ่งในสาม และที่สำคัญกว่านั้น มันพิสูจน์ให้ผู้ใช้เห็นว่าเครื่องอ่านมือถือไม่ได้กดทับเขา
- ผู้ใช้วัดไม้ทีละชิ้น 10×20×400, 10×10×300… ระบบคำนวณลูกบาศก์เมตรของแต่ละชิ้นอัตโนมัติ (ทศนิยมหกตำแหน่ง เพราะส่วนต่างของ ม³ ดูเหมือนเล็กแต่พอล็อตใหญ่ขึ้นมันสะสม)
- พอพาเลทเต็มก็กด “ปิดพาเลท” ตอนนั้นระบบสั่งเครื่องพิมพ์ทำงาน QR ของพาเลทบวกกับ QR รายชิ้นของไม้ทุกชิ้นข้างใน ออกจากเครื่องพิมพ์ต่อเนื่องกัน ในครั้งเดียว
- ผู้ใช้ติดฉลากแต่ละใบลงบนไม้ของมัน ติดฉลากพาเลทลงบนพาเลท แล้วพาเลทก็เข้าสต็อก
พาเลทถัดไปก็ไหลแบบเดียวกัน จนกว่าตู้จะหมด
หลายเดือนต่อมามีผู้ซื้อต้องการไม้ ใบสั่งขายถูกเปิดที่ออฟฟิศ การแจ้งเตือนลงไปที่เครื่องอ่านมือถือ ผู้ใช้ที่หน้างานเปิดใบสั่ง แล้วเริ่มยืนยันการจัดส่งจริง
ในการขายพาเลทเต็ม การไหลของงานง่าย สแกน QR ของพาเลท ระบบทำเครื่องหมาย “ขายแล้ว” ให้ไม้ทุกชิ้นในพาเลทนั้นในครั้งเดียว ส่วนการขายรายชิ้น ผมวางการไหลของการยืนยันไว้อย่างประณีต เริ่มจากสแกนพาเลทก่อน ระบบตรวจว่าพาเลทนั้นเป็นพาเลทที่ควรจะถูกขายจริงหรือไม่ ถ้าผิด ระบบแจ้งข้อผิดพลาดกับผู้ใช้ แล้วการสแกนก็เริ่มใหม่จนกว่าจะเจอพาเลทที่ถูก พอเปิดพาเลทที่ถูกต้อง ไม้ข้างในก็ขึ้นมาให้ผู้ใช้เลือกได้ เขาสแกนทีละชิ้นเพื่อทำเครื่องหมายจำนวนที่จะขาย ระบบไม่ปิดการจัดส่งจนกว่าการยืนยันจะครบ
เหตุผลที่ผมวางตรรกะนี้มีข้อเดียว คือลดความเสี่ยงที่จะขายไม้ผิดชิ้นจากพาเลทผิดใบให้เป็นศูนย์ หน้างานมืด คนเหนื่อย สมาธิหลุด ระบบต้องกันความผิดพลาดไว้ให้ได้แม้สมาธิจะหลุด
หัวของคนเขียนโปรแกรมกับหัวของคนหน้างานคือหัวเดียวกัน
ในบทความก่อนผมเขียนว่า “ผมไม่ได้คิดว่าผู้รับผิดชอบงานขายจะทำตัวอย่างไร แต่คิดว่าในฐานะผู้รับผิดชอบงานขาย ตอนนี้ผมต้องการอะไร” ในโปรเจกต์นี้ผมได้เจอเงื่อนไขที่ต้องเอาข้อเสนอนั้นมาลงมือทำจริง
การออกแบบระบบไม่ได้มาจากคนข้างนอก ความคิดเรื่องใบรายการบรรจุ การจำกัดรายชื่อผู้จัดหาตอนรับสินค้า คำถาม “ความยาวกับความหนาเท่ากันหรือไม่” ตรรกะการยืนยันพาเลท ไม่มีอันไหนเลยที่มาจากการ “ไปถามฝ่ายขายกันเถอะ” “ไปถามฝ่ายคลังกันเถอะ” “ไปถามโปรแกรมเมอร์กันเถอะ” ผมหาทั้งหมดนั้นด้วยตัวเอง ด้วยการตั้งสถานการณ์ของตัวเอง ด้วยการให้ประสบการณ์ของตัวเองพูด
ตอนทำแบบนั้นผมออกจะเหมือนคนหลายบุคลิกอยู่เหมือนกัน
ผมลงหน้างาน หยิบเครื่องอ่านมือถือขึ้นมา แล้วรับสินค้าจริง ๆ ผมเอาคอมพิวเตอร์ออกมา ตรวจระบบ เห็นสิ่งที่ขาด ผมวัดไม้อาจจะเป็นสิบ ๆ ครั้ง ผ่านการรับสินค้าตั้งแต่ต้นจนจบด้วยตัวเอง ทุกครั้งที่เจอการไหลของงานที่ทำให้ผู้ใช้ลำบาก ผมก็คิดต่อ แก้ ทดสอบ อุดส่วนที่ขาด วิเคราะห์ข้อผิดพลาด แล้วกลับไปเขียนโค้ดใหม่ จากนั้นก็ลงหน้างานอีก
สิ่งที่ผมอยากเล่าคือ คนที่รับของก็ผม คนที่ตรวจรับก็ผม คนที่ขายก็ผม ผมเขียนโปรแกรมตัวเองให้เป็นทุกชิ้นส่วนของระบบ จนกว่าจะได้ข้อมูลที่ถูกต้อง
นี่เป็นสภาวะหนึ่งของการเขียนซอฟต์แวร์ที่คนพูดถึงกันน้อยแต่ทรงพลังมาก ถ้านักพัฒนาคนอื่นเขียนโปรเจกต์นี้ เขาต้องลงหน้างาน คุยกับผู้ใช้ เรียนรู้วงจร กลับมาที่โต๊ะแล้วพยายามเขียนโค้ด อีกสัปดาห์หนึ่งค่อยกลับมาบอกว่า “อันนี้ไม่เวิร์ก” ส่วนผมเขียนโค้ดตอนที่อยู่ในวงจรนั้น ความหน่วงระหว่างกันเป็นศูนย์
ในโปรเจกต์นี้ผมจึงไม่ได้เจอช่วงเวลาแบบ “ผมคิดด้วยหัวโปรแกรมเมอร์ แต่ที่หน้างานมันไม่ได้เป็นแบบนั้น” ในบทความก่อนผมเจอบ่อย ตรงนั้นการตัดสินใจของคนอื่นถูกวางไว้บนตัวผม ต้องผ่านขั้นตอนของประสบการณ์เอาเอง ส่วนที่นี่ทุกการตัดสินใจโผล่ออกมาจากใต้ความทรงจำของการรับสินค้าครั้งหนึ่ง หรือฉากโทรศัพท์ขายของสักครั้ง
สิ่งที่ผมยังแก้ไม่ได้
ถึงอย่างนั้นก็ยังมีปัญหาเปิดอยู่ข้อหนึ่ง ต้องพูดกันตรง ๆ
ในขั้นตอนรับสินค้า การวัดไม้ทีละชิ้นยังขึ้นอยู่กับแรงคน ผู้ใช้หยิบไม้ขึ้นมาหนึ่งท่อน คลี่ตลับเมตร แล้วกรอกค่าความหนา ความกว้าง ความยาว ลงเครื่องอ่านมือถือทีละค่า พอเป็นไม้หลายร้อยชิ้นมันกลายเป็นงานหลายชั่วโมง แล้วความเหนื่อยก็กลายเป็นความคลาดเคลื่อนในการวัด ระบบกินอาหารจากข้อมูลดิบชุดนี้ทั้งต้นทางและปลายทาง พูดอีกอย่างคือบทเรียน “ถ้าข้อมูลแรกที่กรอกผิด ทั้งสายก็เน่า” จากบทความก่อนก็ทำงานที่นี่ด้วย แต่ตัวจุดชนวนต่างกัน ค่าที่กรอกผิดเพราะความเหนื่อย ทำให้ระบบถือลูกบาศก์เมตรผิดไปตลอดกาล
ทางแก้ที่อยู่ในหัวผมคือการวัดในระดับพาเลท แนวปฏิบัติทั่วโลกไปทางนี้ แทนที่จะวัดไม้ทีละท่อนด้วยตลับเมตร ก็วัดพาเลททั้งใบเป็นก้อนเดียวแล้วคำนวณปริมาตรรวม ผมยังไม่ได้ย้ายไปตรงนั้น แต่ทิศทางมันไปทางนั้น
ปิดท้าย
ในหัวข้อหนึ่งของบทความนี้ผมเขียนประโยคนี้ไว้ “ในอนาคตถ้าของมาจากผู้จัดหาหลายรายในช่วงเวลาเดียวกัน อาจจะสับสนได้ ตอนนี้ยังเป็นเรื่องที่เปิดอยู่” ผมสังเกตเห็นประโยคนี้ตอนกำลังเขียนบทความ ก่อนหน้านั้นไม่เคยนึกถึงเลย
ในตอนปิดท้ายของบทความก่อนผมเขียนว่า “บทความนี้ก็เป็นบันทึกกันตาบอดแบบนั้นอยู่บ้างเหมือนกัน” นี่คือหลักฐานแบบเรียลไทม์ การเขียนเป็นเครื่องมือคิด ไม่ใช่แค่การบันทึก ผมนั่งอยู่ใต้หัวข้อหนึ่ง กำลังเรียงลำดับการไหลของงาน แล้วรอยรั่วของระบบที่ผมยังไม่ทันสังเกตก็โผล่ออกมา ผมจดมันไว้ข้างไฟล์
การเขียนบล็อกอาจจะเป็นเครื่องมือพัฒนาที่หล่อเลี้ยงระบบในระยะยาว ไม่ใช่แค่การถ่ายทอด ผมจะทดสอบเรื่องนี้ต่ออีกสักพัก
ระบบนี้ยังโตอยู่ คำว่า “จบแล้ว” ก็ยังไม่มีอีกเช่นเคย ก้าวถัดไปน่าจะเป็นการย้ายไปวัดในระดับพาเลท ตอนนี้ยังเป็นมือคน ตลับเมตร และหน้าจอของเครื่องอ่านมือถือ