#api#process#report
API รายงาน (report/run, report/pdf, ai-report-*)
รายงานทุกตัวของระบบวิ่งผ่าน 2 route คงที่ + 2 action ของ workflow — ไม่มี endpoint แยกรายรายงาน; รายงานแต่ละตัวถูกเลือกด้วย menuKey ในบอดี้. ภาพรวม prefix, envelope และการตรวจสิทธิ์อยู่ที่ API เครื่องยนต์ process /process/* — เส้นทาง สิทธิ์ ขนาดบอดี้ (Process engine run API); ตัวเครื่องยนต์รายงานอยู่ที่ เครื่องมือรายงานฝั่ง Backend (Report engine / RunReport).
ตาราง action
| action | หน้าที่ | input สำคัญ | ตาราง | path:line |
|---|---|---|---|---|
report/run |
ประมวลผลรายงานตาม menuKey คืนคอลัมน์ แถว ยอดรวม การ์ดสรุป SQL ที่รันจริง และเทมเพลตที่บันทึกไว้ |
menuKey, mode, dateFrom, dateTo, fromCode, toCode, orderBy, keyword, viewDetail, options{}, customSql, customColumns[], groupByColumn, templateAction |
erp_report_template, ic_inventory, erp_option, ic_warehouse, ic_shelf, erp_user_group_wh_shelf, erp_user_group_detail (ตารางข้อมูลอื่นแล้วแต่รายงาน) | handler.go:223 → report.go:246 |
report/pdf |
รัน RunReport ด้วยเงื่อนไขเดียวกับหน้าจอ แล้วเรนเดอร์เป็นไฟล์ PDF |
RunReportInput ชุดเดียวกัน + paperSize, orientation, fontSizePt, colorMode, printTheme, density, gridStyle, showZebra, showRowNumber, showCompanyHeader, showSignatureBlock, signatureLabels[], footerNote, customTitle, customSubtitle, reportLang |
ตารางเดียวกับ report/run + erp_user, ic_trans (หัวบริษัท/ข้อมูลประกอบ) | handler.go:270 → report_pdf.go:111 |
workflow/ai-report-sql |
แปลงคำสั่งภาษาไทย/อังกฤษเป็น SELECT แบบอ่านอย่างเดียว แล้วลองรันใน transaction read-only เพื่อนับแถวและคืนชื่อคอลัมน์ | prompt, menuKey, dateFrom, dateTo |
ic_opposite_unit, ic_trans_detail, ic_inventory, ic_trans, ar_customer, ap_supplier, gl_journal_detail | workflow_handler.go:124 → ai_copilot.go:259 |
workflow/ai-report-analyze |
วิเคราะห์แถว/ยอดรวมของรายงานที่แสดงอยู่ คืนหัวข้อสรุป จุดเด่น ความผิดปกติ และการ์ดตัวเลข (คำนวณด้วย *big.Rat) |
menuKey, title, rows[], totals{}, summaryCards[] |
(ไม่แตะ DB — คำนวณจากบอดี้ที่ส่งมา) | workflow_handler.go:131 → ai_copilot.go:341 |
ทั้ง 4 action ผูกกับ claims ใน JWT: ProviderCode, DatabaseName, Username ถูกเติมจาก token เสมอ (json:"-" ใน struct) — ค่าในบอดี้ไม่มีผล (handler.go:235-237, workflow_handler.go:128,135).
report/run
- เมธอด + path:
POST /api/v1/process/report/runและPOST /process/report/run(ติดตั้งทั้งสอง prefix, router.go:165,206) - Auth:
Authorization: Bearer <token>; สิทธิ์ขั้นต่ำRead(permission.go:25); ถ้าบอดี้มีcustomSqlไม่ว่าง ต้องเป็น Admin (permission.go:60-66) - ขนาดบอดี้: 16 MB (
maxJSONBody, handler.go:231); รันรายงานมี timeout 45 วินาทีใน service (report.go:253) - Request:
{
"menuKey": "<menuKey>",
"mode": "<mode>",
"dateFrom": "2026-01-01",
"dateTo": "2026-12-31",
"fromCode": "<codeFrom>",
"toCode": "<codeTo>",
"orderBy": "<orderByKey>",
"keyword": "<text>",
"viewDetail": false,
"options": { "<conditionKey>": "<value>" }
}
- ฟิลด์ที่ service รับ (report.go:26-100): ชื่อ snake_case ที่ใช้แทนกันได้ คือ
report_code/reportCode(แทน menuKey),doc_date_from/doc_date_to(แทน dateFrom/dateTo),doc_no_from/doc_no_to(แทน fromCode/toCode) —normalizeRunReportInputรวมเข้าชื่อหลัก. วันที่ว่างใช้ค่าเริ่มต้น "ย้อนหลัง 1 ปี ถึงวันนี้" (report.go:271-272). ถ้าmenuKeyไม่อยู่ในmenuReportsและไม่ใช่ menu ของตัวออกแบบ จะลองรันแบบ legacy ตามmode; ไม่เจอ = 400 "ไม่พบรายงาน <menuKey>" (report.go:288-296) - Custom SQL / เทมเพลต:
customSqlรัน SQL อ่านอย่างเดียวแทนคิวรีมาตรฐาน — รายงานที่โชว์ต้นทุนสินค้า (costGuarded) หรือที่ล็อกตามคลัง/ชั้นของผู้ใช้ (permGuarded) จะปฏิเสธ Custom SQL (report.go:120-121);templateAction=save|delete|set_defaultพร้อมtemplateId/templateName/templateDesc/templateIsDefaultเขียนตารางerp_report_templateก่อนรันรายงาน (report.go:260). รายละเอียดเชิงแนวคิด: SQL กำหนดเองและเทมเพลตรายงาน (Custom SQL & report templates), ทะเบียนรายงานรายเมนูและเงื่อนไขกรอง (Menu report registry & conditions) - Response 200:
{"ok":true,"data":{...}}โดยdataคือRunReportResult(report.go:102-):menuKey,yearType,reportTypeCode,reportCategory,subtitle,conditionText,usesDateRange,codeRangeLabel,orderByOptions[],conditions[],supportsDetail,viewDetailActive,columns[],allColumns[],rows[],totals{}(ค่าเงินเป็น string ทศนิยม),hierarchyLevels[],groupNodes[],summaryCards[],defaultSql,executedSql,executionMs,templates[],savedTemplateId. โครงลำดับชั้นกลุ่ม: การจัดกลุ่มหลายระดับและการพิมพ์ PDF รายงาน (Report hierarchy & PDF) - Errors: ไม่มี token = 401; สิทธิ์ไม่พอ = 403 (ข้อความไทยจาก
deniedMessages); บอดี้ JSON เสีย = 400 "invalid request body"; ไม่พบรายงาน / คิวรีรันไม่ได้ = 400 (report.go:295, 770 "ประมวลผลรายงานไม่สำเร็จ: ..."); error อื่น = 500 "internal server error" - Rate limit: ไม่มี middleware จำกัดอัตรา; มีเพดานเวลา 60 วินาทีของ router + 45 วินาทีของ service
- ผู้เรียกฝั่งหน้าเว็บ:
runBackendReport()(query-client.ts:1020) → NextPOST /api/process/report/run(route.ts:151) ซึ่งแปลง alias ของ menuKey (route.ts:12-30) แล้วเติมtotal_vat_amount/total_base_amountในผลลัพธ์จากtotals; Next ยังมีGETที่รับ query string (menuKey,dateFrom,dateTo,fromCode,toCode, ที่เหลือเข้าoptions) แล้วแปลงเป็น POST (route.ts:81-150). หน้าจอที่ใช้:account/report/UniversalReportScreen.tsx:407,490,FastReportDesignerScreen.tsx:1094,1198,1309,1365,FormDesignerScreen.tsx:991,1764— ภาพรวมที่ จอรายงานกลาง (Universal Report Screen)
report/pdf
- เมธอด + path:
POST /api/v1/process/report/pdfและPOST /process/report/pdf(router.go:166,207) - Auth/สิทธิ์/ขนาด: เหมือน
report/runทุกข้อ (Read;customSql= Admin; 16 MB) - Request:
RunReportInputชุดเดียวกับ report/run เพิ่มตัวเลือกการพิมพ์ —paperSize(A4/A3/Letter/Legal/A5/Continuous),orientation(auto/portrait/landscape),fontSizePt(6.5-13.0 ค่าเริ่มต้น 8.5),colorMode,printTheme,density,gridStyle,showZebra/showRowNumber/showCompanyHeader(ค่าเริ่มต้น true),showSignatureBlock(ค่าเริ่มต้น false),signatureLabels[],footerNote,customTitle,customSubtitle,reportLang(enแปลค่าในแถวผ่านพจนานุกรมสองภาษา) (report.go:47-65, report_pdf.go:111-135) - Response 200: ไบต์ไฟล์ PDF ไม่ห่อ JSON —
Content-Type: application/pdf,Content-Disposition: inline; filename="<menuKey>_<yyyyMMdd_HHmm>.pdf"(sml_reportถ้าไม่มี menuKey),Cache-Control: no-store(handler.go:308-310, report_pdf.go:141-146) - Errors: เหมือน report/run (เป็น JSON
{"error":{"message"}}); เรนเดอร์ไม่สำเร็จ = 500 พร้อมข้อความ "สร้างไฟล์ PDF รายงานไม่สำเร็จ: ..." (report_pdf.go:138) - ผู้เรียกฝั่งหน้าเว็บ:
fetchBackendReportPdf()(query-client.ts:1033) → NextPOST /api/process/report/pdf(route.ts:28) ซึ่งแปลง alias ของ menuKey, เติมtitleจากชื่อเมนูถ้าไม่ได้ส่งมา, ส่งไบต์ PDF ต่อเป็น stream; ถ้าล้มเหลวคืนfail(message, status). ใช้จากaccount/report/GlobalReportPrintModal.tsx:2330
ตัวอย่าง (ค่าสมมติ):
POST /api/v1/process/report/pdf (Authorization: Bearer <token>)
{"menuKey":"<menuKey>","dateFrom":"2026-01-01","dateTo":"2026-12-31",
"paperSize":"A4","orientation":"auto","printTheme":"<theme>","reportLang":"th"}
workflow/ai-report-sql
- เมธอด + path:
POST /api/v1/process/workflow/ai-report-sql(และ/process/workflow/ai-report-sql); บอดี้จำกัด 1 MB (workflow_handler.go:25) - สิทธิ์: action นี้ ไม่อยู่ใน
routeRights(permission.go:25-52) จึงตกเป็นต้อง Admin ตามค่าเริ่มต้น (ดู API เครื่องยนต์ process /process/* — เส้นทาง สิทธิ์ ขนาดบอดี้ (Process engine run API)) - Request:
{"prompt":"<คำสั่งภาษาไทย>","menuKey":"<menuKey>","dateFrom":"2026-01-01","dateTo":"2026-12-31"} - การทำงาน: ไม่ได้เรียก LLM ภายนอก —
compileThaiPromptToSQL(ai_copilot.go:91) แปลงข้อความเป็น SQL ด้วยกฎคำสำคัญ,validateReadOnlySelectSQLบังคับ SELECT/WITH เท่านั้น แล้วรันใน transactionReadOnlyที่ rollback ทุกครั้ง (ai_copilot.go:269-285) เพื่อนับแถวและอ่านชื่อคอลัมน์ - Response 200:
{"ok":true,"data":{"prompt","title","explanation","sql","suggestedGroupBy","previewRowCount","columns":[...]}}(ai_copilot.go:59-67).sqlที่ได้ใช้ต่อเป็นcustomSqlของ report/run - Errors:
promptว่าง = 400 (ข้อความไทยให้ระบุคำสั่ง); SQL ที่สร้างไม่ผ่านกฎ read-only = 400; รันไม่ได้ = 400 "คำสั่ง SQL ที่สร้างไม่สามารถรันได้" - ผู้เรียกฝั่งหน้าเว็บ:
GlobalReportPrintModal.tsx:1235ผ่าน Next/api/process/workflow/[action](ACTIONS whitelist มีai-report-sql, route.ts:26)
workflow/ai-report-analyze
- เมธอด + path:
POST /api/v1/process/workflow/ai-report-analyze; บอดี้ 1 MB; สิทธิ์ Admin ตามค่าเริ่มต้นเช่นเดียวกัน - Request:
{"menuKey":"<menuKey>","title":"<ชื่อรายงาน>","rows":[{...}],"totals":{"<คอลัมน์>":"1234.50"},"summaryCards":[...]} - Response 200:
{"ok":true,"data":{"headline","highlights":[...],"anomalies":[...],"metrics":[{"label","value","tone"}]}}—toneเป็น emerald/amber/rose/brand (ai_copilot.go:326-338). ถ้าrowsว่างคืน highlight บอกว่าไม่พบรายการและจบทันที (ai_copilot.go:355-358). ไม่แตะ DB (รับ_ context.Context, ai_copilot.go:341) และไม่เรียก LLM - ผู้เรียกฝั่งหน้าเว็บ:
UniversalReportScreen.tsx:238(ปุ่มai-report-analyze-btn, แผงai-report-insight-panel); whitelist เดียวกัน (route.ts:27). แนวคิด: ผู้ช่วย AI แบบกฎคำนวณ 6 ตัว (AI Copilot ai-*)
ที่มาในโค้ด
backend/internal/adapter/inbound/http/router.go:165-166,206-207(route ทั้งสอง prefix)backend/internal/adapter/inbound/http/process/handler.go:223-266(RunReport),270-313(RenderReportPDF)backend/internal/adapter/inbound/http/process/permission.go:25,60-66(Read / customSql = Admin)backend/internal/adapter/inbound/http/process/workflow_handler.go:18-30,124-138(ai-report-sql/analyze)backend/internal/application/process/report.go:26-100,102-,246-376,390-431backend/internal/application/process/report_pdf.go:111-147backend/internal/application/process/ai_copilot.go:48,59-67,91,259-307,314-338,341-423frontend/src/app/api/process/report/run/route.ts:12-30,32-80,81-150,151-160;frontend/src/app/api/process/report/pdf/route.ts:12-26,28-90frontend/src/lib/query-client.ts:1017-1066(runBackendReport, fetchBackendReportPdf)frontend/account/report/UniversalReportScreen.tsx:238,407,490;GlobalReportPrintModal.tsx:1235,2330