Work 03 · Deep dive

แดชบอร์ดยอดขายและลูกค้า

Sales & customer dashboard

เว็บแอปภายใน · ใช้จริงทุกวันในธุรกิจครอบครัว
Internal web app · used daily in my family's business

เว็บแอปที่อ่านข้อมูลจากโปรแกรมบัญชี (ERP) ของธุรกิจครอบครัวโดยตรง แล้วสรุปออกมาเป็นหน้าเดียวที่ตอบได้ว่าเดือนนี้ขายเป็นยังไง วันนี้ต้องจัดการอะไร และต้องโทรหาลูกค้ารายไหน

A web app that reads the family business's accounting software (ERP) directly and condenses it into one screen: how the month is going, what needs doing today, and which customers to call.

How it works

ระบบทำงานยังไง

How it works

01
ERP ในออฟฟิศERP in the officeไฟล์ข้อมูลขาย ลูกค้า สินค้า และบิลSales, customer, product and invoice files
02
เซิร์ฟเวอร์ในออฟฟิศOffice serverอ่านไฟล์ตรง ไม่ต้อง exportReads the files directly, no exports
03
คำนวณCalculationsยอดเทียบช่วงเดียวกัน คาดการณ์สิ้นเดือน margin หนี้ค้างLike-for-like comparison, month-end forecast, margin, receivables
04
แถบแจ้งเตือนAlert barสินค้าต้องสั่ง ลูกค้าเกินกำหนด ลูกค้าเสี่ยงหายStock to reorder, overdue customers, customers at risk
05
คนใช้งานPeople using itเปิดในออฟฟิศ หรือจากข้างนอกผ่าน VPNIn the office, or remotely over a VPN

ขั้นที่มีคนเป็นผู้ตัดสินใจหรือใช้งานSteps where a person decides or uses it

หน้าแรก: ยอดเดือนนี้ คาดการณ์สิ้นเดือน และแถบแจ้งเตือน (ตัวเลขถูกเบลอ)
หน้าแรก: ยอดเดือนนี้ คาดการณ์สิ้นเดือน และแถบแจ้งเตือน (ตัวเลขถูกเบลอ)
Home: month-to-date sales, month-end forecast and the alert bar (figures blurred)
มุมมองลูกค้าที่ซื้อสูงสุด มีปุ่มสลับมุมมอง At Risk / Loyalty / ลูกค้าใหม่ / Concentration (ชื่อและตัวเลขถูกเบลอ)
มุมมองลูกค้าที่ซื้อสูงสุด มีปุ่มสลับมุมมอง At Risk / Loyalty / ลูกค้าใหม่ / Concentration (ชื่อและตัวเลขถูกเบลอ)
Top customers view, with switches for At Risk / Loyalty / New / Concentration (names and figures blurred)
My calls

ส่วนที่ผมเป็นคนตัดสินใจ

Decisions I made

AI เขียนโค้ดให้ แต่โจทย์ เกณฑ์ และสิ่งที่ยอมไม่ได้ ผมเป็นคนกำหนด

The AI writes the code; the problem, the criteria and the non-negotiables are mine.

หน้าแรกต้องบอกว่าวันนี้ทำอะไรThe home screen must say what to do today

แดชบอร์ดส่วนใหญ่โชว์กราฟแล้วจบ ผมให้หน้าแรกมีแถบแจ้งเตือนที่กดแล้วพาไปที่รายชื่อเลย เช่น ลูกค้า 14 รายเกินกำหนดชำระ กดแล้วเห็นทั้ง 14 รายพร้อมเบอร์โทร

Most dashboards stop at charts. Mine has alerts that jump straight to the list: "14 customers overdue" opens those 14 with phone numbers.

เทียบกับช่วงเดียวกันเท่านั้นCompare like with like

วันที่ 8 ของเดือน ถ้าเอาไปเทียบกับทั้งเดือนก่อน ยอดจะดูตกตลอด ผมเลยให้เทียบกับ 8 วันแรกของเดือนก่อน แล้วโชว์ตัวเลขคาดการณ์สิ้นเดือนแยกไว้

On the 8th of the month, comparing with the whole previous month always looks like a drop. It compares with the first 8 days of last month and shows a separate month-end forecast.

ข้อมูลต้องตรงกับบัญชีเป๊ะNumbers must match the books exactly

ทุกตัวเลขตัดบิลที่ถูกยกเลิกออกเสมอ และใช้ช่องข้อมูลที่ตรงความหมายจริง เพราะตัวเลขที่ผิดแม้แต่นิดเดียวจะทำให้คนเลิกเชื่อแดชบอร์ดทั้งหน้า

Cancelled invoices are always excluded and each field is used for what it actually means, because one wrong number makes people stop trusting the whole dashboard.

Code

โค้ดจริงบางส่วน

Real code excerpts

โค้ดทั้งหมดเขียนโดย Claude Code ตามที่ผมสั่ง ผมอ่านแล้วอธิบายได้ว่าแต่ละท่อนทำอะไร และทำไมต้องเขียนแบบนี้ (ข้อมูลลับอย่างรหัสผ่านและที่อยู่เครื่องถูกตัดออก)

All code was written by Claude Code under my direction. I can explain what each part does and why it's written that way. (Secrets such as keys and machine paths are removed; code comments are in Thai.)

แจ้งเตือนลูกค้าที่กำลังจะหาย

Alerting on customers about to lapse

ไล่ดูบิลทุกใบ จำวันที่ซื้อล่าสุดและจำนวนบิลของลูกค้าแต่ละราย ลูกค้าที่เคยซื้อตั้งแต่ 3 บิลขึ้นไปแต่หายไป 60–180 วัน จะนับเป็น At Risk แล้วขึ้นเป็นแถบแจ้งเตือนบนหน้าแรก กดแล้วไปที่รายชื่อได้ทันที

Walk through every invoice, remembering each customer's last purchase date and invoice count. Customers with 3+ invoices who have been silent for 60–180 days count as At Risk and appear as an alert on the home screen that links straight to the list.

// At Risk customers
const lastByCus = {}, billsByCus = {};
for (const inv of DB.invoices) {
  const cod = inv.CUSCOD;
  const dt = inv.DOCDAT || '';
  if (!dt) continue;
  if (!lastByCus[cod] || dt > lastByCus[cod]) lastByCus[cod] = dt;
  billsByCus[cod] = (billsByCus[cod] || 0) + 1;
}
let atRiskCount = 0;
for (const [cod, last] of Object.entries(lastByCus)) {
  if ((billsByCus[cod] || 0) < 3) continue;          // ซื้อน้อยกว่า 3 บิล = ยังสรุปไม่ได้
  const days = daysSince(last);
  if (days >= 60 && days < 180) atRiskCount++;
}
if (atRiskCount > 0) alerts.push({ id: 'at-risk', level: 'info',
  text: `ลูกค้า At Risk ${atRiskCount} ราย — เคยซื้อสม่ำเสมอแต่หายไป 60-180 วัน`,
  tab: 'customers', view: 'atrisk' });
server.jsserver.js · เขียนโดย Claude Codewritten by Claude Code
Problems solved

ปัญหาที่เจอ และวิธีแก้

Problems and fixes

ปัญหาProblem

ยอดขายไม่ตรงกับในโปรแกรมบัญชี

Sales didn't match the accounting software

แก้ยังไงFix

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

Exclude cancelled invoices and use the right quantity field (two fields have similar names; the wrong one skews totals)

ปัญหาProblem

เซิร์ฟเวอร์เปิดโฟลเดอร์ชื่อไทยไม่ได้

The server couldn't open a folder with a Thai name

แก้ยังไงFix

ย้ายไปไว้ในโฟลเดอร์ที่ชื่อเป็นภาษาอังกฤษ

Moved it to a folder with an English name

ปัญหาProblem

ต้องเปิดใช้ทั้งในออฟฟิศและนอกออฟฟิศ

Needed both in-office and remote access

แก้ยังไงFix

ตั้งค่าแยกตามเครื่อง และให้เข้าจากข้างนอกผ่านเครือข่ายส่วนตัว (VPN) ไม่เปิดสู่อินเทอร์เน็ตสาธารณะ

Per-machine settings, and remote access via a private network (VPN), never exposed to the public internet

← animclipanimclipแบ่งกลุ่มลูกค้าตามพฤติกรรมการซื้อBehavior-based customer segmentation →