Files
wms-app/scripts/sdlc-seed/docs-si2.mjs
T

461 lines
60 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SI work products 18–22: user documentation, operation guide, maintenance, verification, validation.
import { P, CODE, ORGANIZER, ORGANIZER_PM, RECORDER, START, END, SCOPE, CRS, CR_GROUPS, TEST_FROM, TEST_TO, ACCEPT_DATE, BASELINE_COMMIT, TEST_STATE_COMMIT, ORIGIN, BACKUP, OPS_CLOSE_DATE } from './project.mjs';
import { UNITS, TEST_CASES, traceOf, unitTopic } from './engineering.mjs';
import { CORRECTIONS, VERIFICATION_ROUNDS, VERIFICATION_RULES } from './history.mjs';
import { bud, thLong, thRangeLong, table, bullets, numbered } from './lib.mjs';
import { workProduct, head } from './format.mjs';
/** 18. Software User Document. */
export function userDocumentation() {
const date = '2026-08-14';
return {
dir: '2-SI Process (12 Work Product)/18.Software User Documentation',
name: `${CODE} Software User Document ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Software User Document', footer: 'SuD',
rows: head('Software User Document', `${bud(date)} V1.0 ThS`, [
['Title', 'เอกสารคู่มือการใช้งานสำหรับผู้ใช้'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'คู่มือฉบับนี้จัดทำขึ้นเพื่อเป็นแนวทางสำหรับผู้ใช้งานระบบบริหารจัดการคลังสินค้า ครอบคลุมการเข้าสู่ระบบ การตั้งค่าข้อมูลหลัก การบันทึกรายการคลังสินค้า งานขาย งานจัดซื้อ งานการเงินและบัญชี การเรียกดูรายงาน และการรับการแจ้งเตือน',
'## 1 การเข้าสู่ระบบ (System Access)',
table(['ลำดับ', 'รายละเอียด'], [
[1, 'เข้าใช้งานผ่าน Web Browser มาตรฐาน (Chrome, Edge, Firefox) ตาม URL ที่ผู้ดูแลระบบกำหนด'],
[2, 'ผู้ใช้งานใหม่ที่เป็นเจ้าของบริษัทให้เลือก "ลงทะเบียนบริษัท" และยืนยันอีเมลเพื่อเปิดใช้งาน'],
[3, 'ผู้ใช้งานที่ได้รับคำเชิญให้กดลิงก์ในอีเมลเชิญ แล้วตั้งรหัสผ่านเพื่อเข้าร่วมบริษัท'],
[4, 'กรณีลืมรหัสผ่าน ให้เลือก "ลืมรหัสผ่าน" ระบบจะส่งลิงก์และรหัส OTP ไปยังอีเมลที่ลงทะเบียนไว้'],
[5, 'ระบบอนุญาตให้ใช้งานได้ครั้งละหนึ่ง Session ต่อบัญชี หากเข้าสู่ระบบจากอุปกรณ์ใหม่ระบบจะปฏิเสธการเข้าใช้งานซ้ำ'],
[6, 'หน้าจอรองรับการใช้งานบนคอมพิวเตอร์ แท็บเล็ต และโทรศัพท์มือถือ'],
], ['c']),
'## 2 บทบาทและสิทธิ์การใช้งาน (Roles)',
table(['บทบาท', 'สิทธิ์การใช้งาน'], [
['Owner', 'เจ้าของบริษัท ใช้งานได้ทุกเมนู รวมถึงการตั้งค่าบริษัทและการจัดการผู้ใช้'],
['Admin', 'จัดการข้อมูลหลัก ผู้ใช้งาน สิทธิ์การเข้าถึง และใช้งานทุกโมดูลปฏิบัติการ'],
['Staff', 'บันทึกรายการคลังสินค้า ขาย จัดซื้อ และการเงิน ตามสิทธิ์ที่ได้รับ'],
['Viewer', 'ดูหน้าจอและรายงานที่ได้รับสิทธิ์ แต่ไม่สามารถสร้างหรือแก้ไขรายการได้'],
]),
'เมนูและปุ่มคำสั่งที่แสดงจะเปลี่ยนตามบทบาทและสิทธิ์การเข้าถึงที่ผู้ดูแลระบบกำหนด',
'## 3 หน้าแรกและแดชบอร์ด (Dashboard)',
bullets([
'แดชบอร์ดคลังสินค้าแสดงยอดสินค้าคงเหลือ สินค้าใกล้หมด และความเคลื่อนไหวล่าสุด',
'แดชบอร์ดบัญชีแสดงภาพรวมรายรับ รายจ่าย และยอดค้างชำระ',
'ตัวเลขบนแดชบอร์ดคำนวณจากตารางสรุปยอดที่ปรับปรุงตามกำหนดเวลา รายการที่เพิ่งบันทึกอาจแสดงผลช้ากว่าข้อมูลจริงเล็กน้อย',
]),
'## 4 การตั้งค่าข้อมูลหลัก (Master Data)',
'ก่อนเริ่มบันทึกรายการ ผู้ใช้ระดับ Owner หรือ Admin ต้องตั้งค่าข้อมูลหลักตามลำดับ',
numbered([
'**คลังสินค้า พื้นที่จัดเก็บ และช่องจัดเก็บ** เลือกใช้โครงสร้างแบบคลังเดียวหรือแบบหลายชั้น (คลัง / พื้นที่ / ช่อง) ตามลักษณะการทำงาน',
'**หมวดสินค้าและสินค้า** ระบุรหัสสินค้า ชื่อ หน่วยนับ และรูปสินค้า',
'**ประเภทผู้ติดต่อและผู้ติดต่อ** บันทึกข้อมูลลูกค้าและผู้ขายที่ใช้ในเอกสารขายและจัดซื้อ',
'**ข้อมูลบริษัทและ SMTP** ตั้งค่าข้อมูลบริษัท โลโก้ และบัญชีอีเมลสำหรับส่งการแจ้งเตือน',
]),
'## 5 งานคลังสินค้า (Inventory Operations)',
table(['เมนู', 'ขั้นตอนการใช้งาน'], [
['รับสินค้าเข้า', 'เลือกสินค้า ระบุจำนวน ตำแหน่งจัดเก็บ และเอกสารอ้างอิง หากสินค้ามี Lot, Serial Number หรือวันหมดอายุ ให้ระบุเพิ่มเพื่อการสอบกลับ'],
['จ่ายสินค้าออก', 'เลือกสินค้าและจำนวนที่ต้องการจ่าย ระบบจะตรวจสอบยอดคงเหลือก่อนอนุมัติรายการ หากจ่ายเกินยอดระบบจะปฏิเสธ'],
['โอนย้ายสินค้า', 'ระบุตำแหน่งต้นทางและปลายทาง ระบบจะบันทึกเป็นรายการโอนเดียวที่สอบกลับได้'],
['พิมพ์บาร์โค้ด', 'เลือกสินค้าหรือตำแหน่งจัดเก็บเพื่อพิมพ์ฉลากบาร์โค้ด และใช้เครื่องสแกนในหน้าจอที่รองรับ'],
['แนบไฟล์', 'แนบเอกสารประกอบรายการได้ตามประเภทไฟล์ที่อนุญาต'],
]),
'## 6 งานขาย (Sales)',
numbered(['สร้าง**ใบเสนอราคา**ให้ลูกค้า', 'แปลงใบเสนอราคาที่ลูกค้าตอบรับเป็น**ใบสั่งขาย**', 'ออก**ใบแจ้งหนี้**จากใบสั่งขาย ระบบจะบันทึกรายการบัญชีที่เกี่ยวข้องให้อัตโนมัติ', 'กรณีมีการคืนสินค้า ให้บันทึก**ใบรับคืน**และออก**ใบลดหนี้**']),
'ทุกขั้นตอนต้องเปลี่ยนสถานะตามลำดับที่ระบบกำหนด หากเปลี่ยนสถานะข้ามขั้นระบบจะปฏิเสธพร้อมแสดงข้อความ',
'## 7 งานจัดซื้อ (Purchasing)',
numbered(['บันทึก**ใบขอซื้อ**และส่งให้ผู้มีอำนาจอนุมัติ', 'แปลงใบขอซื้อที่อนุมัติแล้วเป็น**ใบสั่งซื้อ**', 'เมื่อได้รับสินค้าให้บันทึก**ใบแจ้งหนี้ซื้อ** ระบบจะรับสินค้าเข้าคลังและบันทึกบัญชีที่เกี่ยวข้อง', 'กรณีคืนสินค้าให้ผู้ขาย ให้บันทึก**ใบคืนสินค้าผู้ขาย**']),
'## 8 งานการเงินและบัญชี (Finance & Accounting)',
table(['งาน', 'รายละเอียด'], [
['ใบวางบิลรับและใบเสร็จรับเงิน', 'บันทึกการวางบิลและการรับชำระจากลูกค้า ผูกกับใบแจ้งหนี้ที่เกี่ยวข้อง'],
['ใบวางบิลจ่ายและใบสำคัญจ่าย', 'บันทึกการวางบิลและการจ่ายชำระให้ผู้ขาย ผูกกับใบแจ้งหนี้ซื้อ'],
['ผังบัญชีและแผนก', 'ผู้ใช้ระดับ Owner หรือ Admin เป็นผู้ดูแลโครงสร้างบัญชี'],
['สมุดรายวันและบัญชีแยกประเภท', 'บันทึกรายการบัญชีจากเอกสารต้นทางหรือบันทึกเองตามสิทธิ์ รายการต้องสมดุลจึงจะบันทึกได้'],
]),
'## 9 รายงาน (Reports)',
bullets([
'รายงานคลังสินค้า ได้แก่ ภาพรวมสต๊อก ความเคลื่อนไหว ความจุคลัง สินค้าใกล้หมด สินค้าหมดอายุ และรายงาน Lot',
'รายงานการเงิน ได้แก่ งบทดลอง งบกำไรขาดทุน งบดุล ภาษีมูลค่าเพิ่ม สมุดรายวัน และความเคลื่อนไหวบัญชีแยกประเภท',
'ทุกรายงานกรอง ดู พิมพ์ และส่งออกได้ ภายใต้ขอบเขตบริษัทและสิทธิ์ของผู้ใช้',
]),
'## 10 การแจ้งเตือน (Notification)',
'ระบบแจ้งเตือนผู้ใช้ที่เกี่ยวข้องแบบ Real-time เมื่อมีการเปลี่ยนสถานะเอกสารที่ต้องดำเนินการต่อ และแจ้งเตือนตามกำหนดเวลาสำหรับสินค้าใกล้หมดและใบแจ้งหนี้ค้างชำระ โดยแจ้งเฉพาะผู้ใช้ที่มีสิทธิ์ในบริบทของบริษัทนั้น',
'## 11 การขอความช่วยเหลือ',
bullets([
'ปัญหาการใช้งานทั่วไป ติดต่อผู้ดูแลระบบของบริษัท',
'ปัญหาที่เกี่ยวกับการทำงานของระบบ ให้แจ้งผ่านช่องทางสนับสนุนที่ระบุในเอกสาร Maintenance Document',
'ขั้นตอนการติดตั้ง ตั้งค่า และกู้คืนระบบ อยู่ในเอกสาร Product Operation Guide',
]),
].join('\n\n'),
secretary: P.ThS, reviewer: P.YaB, approver: P.SeV,
}),
};
}
/** 19. Product Operation Guide. */
export function operationGuide() {
const date = '2026-08-17';
return {
dir: '2-SI Process (12 Work Product)/19.Product Operation Guide',
name: `${CODE} Product Operation Guide ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Product Operation Guide', footer: 'UM',
rows: head('Product Operation Guide', `${bud(date)} V1.0 ThS`, [
['Title', 'เอกสารคู่มือปฏิบัติงานสำหรับผู้ดูแลระบบ'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER_PM],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'คู่มือฉบับนี้จัดทำขึ้นเพื่อเป็นแนวทางสำหรับผู้ดูแลระบบ (System Administrator) ในการติดตั้ง ตั้งค่า เฝ้าระวัง สำรองข้อมูล และกู้คืนระบบบริหารจัดการคลังสินค้า เพื่อให้การปฏิบัติงานมีมาตรฐานและกู้คืนระบบได้อย่างรวดเร็ว',
'## 1 ทางเลือกในการติดตั้ง',
table(['ลำดับ', 'วิธีการติดตั้ง', 'กลไก', 'ไฟล์อ้างอิง'], [
[1, 'ติดตั้งแบบ Container (แนะนำ)', 'Docker Compose: php-apache, mariadb, node/pm2', '`docker-compose.yml`, `docker/`'],
[2, 'ติดตั้งแบบ Manual', 'ติดตั้ง PHP, MariaDB และ Node.js แล้วสร้างฐานข้อมูลด้วยสคริปต์', '`setup.php`'],
], ['c']),
'## 2 ขั้นตอนการติดตั้งแบบ Container',
numbered([
'คัดลอก `.env.example` เป็น `.env` หรือรันสคริปต์ `docker/init-env.sh` ซึ่งจะสอบถามรหัสผ่านฐานข้อมูล ชื่อโฮสต์สาธารณะ ค่า `EMIT_SECRET` และข้อมูล SMTP โดยค่าที่เว้นว่างระบบจะสุ่มให้อัตโนมัติ',
'รันคำสั่ง `docker compose up -d --build` เพื่อสร้างและเริ่มบริการทั้งสามส่วน',
'ตรวจสอบว่า Container `php-apache` สร้างไฟล์ `app/config.php` จาก `.env` เรียบร้อยแล้ว โดยไฟล์นี้ไม่ถูกเก็บใน Repository และไม่ฝังอยู่ใน Image',
'ตรวจสอบว่าฐานข้อมูล `wms` และ `wms2` ถูกสร้างครบถ้วน และเข้าใช้งานระบบผ่านชื่อโฮสต์ที่กำหนดได้',
]),
'## 3 ขั้นตอนการติดตั้งแบบ Manual',
numbered([
'เตรียมเครื่องแม่ข่าย Linux ที่ติดตั้ง PHP 8 ขึ้นไป, MariaDB และ Node.js',
'ตั้งค่าไฟล์ `app/config.php` ได้แก่ ข้อมูลเชื่อมต่อฐานข้อมูล `wms` และ `wms2`, `NODE_PUBLIC_URL`, `NODE_EMIT_URL`, `NODE_EMIT_SECRET` และข้อมูล SMTP',
'รัน `setup.php` หนึ่งครั้งเพื่อสร้างโครงสร้างฐานข้อมูลทั้งหมด',
'เริ่มบริการ Node.js (`nodejs/server.js` และ `nodejs/scheduler.js`) ด้วย pm2 ตามไฟล์ `nodejs/ecosystem.config.js`',
]),
'## 4 การตั้งค่าระบบ',
table(['รายการ', 'ที่ตั้ง', 'หมายเหตุ'], [
['ค่าตั้งค่าแอปพลิเคชัน', '`app/config.php`', 'สร้างขึ้นตอนติดตั้ง ไม่นำเข้า Repository'],
['ค่าความลับของสภาพแวดล้อม', '`.env` (Docker) หรือตัวแปรสภาพแวดล้อม (Manual)', 'ยกเว้นจาก Git ด้วย `.gitignore`'],
['ค่าตั้งค่าระดับบริษัท', 'เมนูตั้งค่าในระบบ (`app/setting/`)', 'ข้อมูลบริษัท SMTP และการตั้งค่าการใช้งาน'],
['เขตเวลา', '`$time_zone` ใน `app/config.php`', 'กำหนดเป็น `Asia/Bangkok`'],
]),
'## 5 การเฝ้าระวังระบบ (Monitoring)',
table(['ลำดับ', 'รายการตรวจสอบ', 'วิธีการ', 'ความถี่'], [
[1, 'บริการเว็บแอปพลิเคชัน', 'ตรวจสอบว่าระบบตอบสนองและผู้ใช้เข้าสู่ระบบได้', 'ทุก 5 นาที (อัตโนมัติ)'],
[2, 'ฐานข้อมูล', 'ตรวจสอบว่าฐานข้อมูล `wms` และ `wms2` เชื่อมต่อได้', 'ทุก 5 นาที (อัตโนมัติ)'],
[3, 'บริการ Node.js', 'ตรวจสอบสถานะ `server.js` และ `scheduler.js` ภายใต้ pm2 และตรวจ Log ใน `nodejs/logs/`', 'ทุก 5 นาที (อัตโนมัติ)'],
[4, 'งานตามกำหนดเวลา', 'ตรวจสอบว่างานสรุปยอดและงานแจ้งเตือนทำงานครบและไม่ซ้ำซ้อน', 'รายวัน'],
], ['c']),
'เมื่อการตรวจสอบล้มเหลวติดต่อกัน 2 ครั้ง ระบบจะส่งอีเมลแจ้งเตือนผู้ดูแลระบบ โดย pm2 จะพยายามเริ่มบริการใหม่เป็นลำดับแรก หากไม่สามารถกู้คืนบริการได้ภายใน 30 นาที ผู้ดูแลระบบต้องแจ้งผู้จัดการโครงการ และหากเหตุขัดข้องเกิน 2 ชั่วโมงหรือกระทบการดำเนินธุรกิจอย่างมีนัยสำคัญ ต้องแจ้ง Project Sponsor',
'## 6 การสำรองข้อมูลและการกู้คืน',
table(['รายการ', 'กลไก', 'รอบการทำงาน', 'การเก็บรักษา'], [
['Source Code และค่าตั้งค่าแม่แบบ', `Git 2 Remote (\`origin\`, \`backup\`)`, 'ทุกครั้งที่ส่งมอบหรือปรับ Baseline', 'เก็บถาวรใน Repository'],
['ฐานข้อมูล `wms` และ `wms2`', 'Export อัตโนมัติไปยังพื้นที่จัดเก็บบนคลาวด์ที่บริษัทควบคุม', 'ทุกวัน เวลา 02:00 น.', 'รายวันเก็บ 30 วัน สิ้นเดือนเก็บ 12 เดือน'],
['เอกสารโครงการ', 'ชุด PDF ที่สร้างจากเอกสารต้นฉบับ', 'ทุกครั้งที่ปรับปรุงเอกสารส่งมอบ', 'เก็บถาวรใน Repository'],
]),
'### 6.1 ขั้นตอนการกู้คืนฐานข้อมูล',
numbered([
'ผู้ดูแลระบบเลือกชุดสำรองที่ต้องการจากพื้นที่จัดเก็บบนคลาวด์ และยืนยันวันที่พร้อมความสมบูรณ์ของไฟล์',
'กู้คืนชุดสำรองเข้าสู่สภาพแวดล้อมที่ไม่ใช่ระบบใช้งานจริงก่อนเสมอ',
'ตรวจสอบการเชื่อมต่อฐานข้อมูล `wms` และ `wms2` พร้อมตรวจสอบข้อมูลหลักและรายการตัวอย่าง',
'กรณีกู้คืนระบบใช้งานจริง ให้บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืนชุดสำรองที่ตรวจสอบแล้ว เริ่มบริการแอปพลิเคชันและ Node.js ใหม่ แล้วตรวจสอบสถานะตามหัวข้อ 5',
'กรณีการสำรองข้อมูลล้มเหลวหรือไม่ทำงานตามกำหนด ผู้ดูแลระบบต้องตรวจสอบสาเหตุและสั่งทำงานซ้ำ',
]),
`กำหนดทดสอบกู้คืนภายใน ${thLong(OPS_CLOSE_DATE)} โดยกู้คืนชุดสำรองลงในสภาพแวดล้อมที่ไม่ใช่ระบบใช้งานจริง ตรวจสอบการเข้าถึงฐานข้อมูลและข้อมูลตัวอย่างให้ครบถ้วน และให้ผู้จัดการโครงการทบทวนผลการทดสอบในขั้นตอนปิดโครงการ`,
'## 7 การปิดรายการควบคุมด้านปฏิบัติการ',
table(['ลำดับ', 'รายการควบคุม', 'ผู้รับผิดชอบ', 'สถานะ', 'หลักฐานการปิดรายการ'], [
['OP-001', 'การสำรองและกู้คืนฐานข้อมูล `wms` และ `wms2` แบบอัตโนมัติ', 'System Administrator', `กำหนดปิดรายการ ${thLong(OPS_CLOSE_DATE)}`, 'หัวข้อ 6 ระบุรอบการสำรอง ที่จัดเก็บ การเก็บรักษา ขั้นตอนกู้คืน และแผนการทดสอบกู้คืน'],
['OP-002', 'การเฝ้าระวังระบบและการแจ้งเตือนเมื่อเกิดเหตุขัดข้อง', 'System Administrator', `กำหนดปิดรายการ ${thLong(OPS_CLOSE_DATE)}`, 'หัวข้อ 5 ระบุรอบการตรวจสอบ เงื่อนไขการแจ้งเตือน ผู้รับแจ้ง และขั้นตอนการยกระดับ'],
], ['c']),
'## 8 การจัดการผู้ใช้งานและสิทธิ์',
bullets([
'ผู้ใช้ระดับ Owner หรือ Admin จัดการผู้ใช้งาน บทบาท และสิทธิ์การเข้าถึงแอปพลิเคชันจากเมนูตั้งค่า',
'การเปลี่ยนบทบาทมีผลกับการทำงานครั้งถัดไปของผู้ใช้รายนั้น',
'ระบบอนุญาตให้ใช้งานได้ครั้งละหนึ่ง Session ต่อบัญชี เพื่อป้องกันการใช้บัญชีร่วมกัน',
]),
].join('\n\n'),
secretary: P.ThS, reviewer: P.NoC, approver: P.SeV,
}),
};
}
/** 20. Maintenance Document. */
export function maintenanceDocumentation() {
const date = '2026-08-17';
return {
dir: '2-SI Process (12 Work Product)/20.Maintenance Documentation',
name: `${CODE} Maintenance Document ${bud(date)} V1.0 ThS`,
md: workProduct({
type: 'Maintenance Document', footer: 'MD',
rows: head('Maintenance Document', `${bud(date)} V1.0 ThS`, [
['Title', 'เอกสารคู่มือการบำรุงรักษาระบบ'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'เพื่อกำหนดแนวทางการบำรุงรักษาระบบหลังจากส่งมอบและเปิดใช้งานจริง ครอบคลุมการแก้ไขข้อผิดพลาด การปรับปรุง และการป้องกัน เพื่อให้ระบบมีความเสถียร ปลอดภัย ต่อเนื่อง และสอดคล้องกับความต้องการของผู้ใช้งาน',
'## ขอบเขตการบำรุงรักษา (Maintenance Scope)',
table(['ID', 'Type'], [
['MD-001', 'Web Server (PHP 8, Apache, Linux Server)'],
['MD-002', `Application Modules (${SCOPE.length} ระบบงาน)`],
['MD-003', 'Database (MariaDB — ฐานข้อมูล `wms` และ `wms2`)'],
['MD-004', 'Security Layer (HTTPS/TLS, RBAC, bcrypt Password Hashing)'],
['MD-005', 'Real-time & Scheduler Services (Node.js, Socket.IO, pm2)'],
['MD-006', `Software Components: Git Server (\`${ORIGIN}\`)`],
['MD-007', `Project Repository (Backup): Git Remote สำรอง (\`${BACKUP}\`) และชุดสำรองฐานข้อมูลบนคลาวด์`],
], ['c']),
'## ประเภทการบำรุงรักษา',
table(['ลำดับ', 'ประเภทการบำรุงรักษา', 'รายละเอียด'], [
[1, 'Corrective Maintenance', 'แก้ไขข้อผิดพลาด (Bug Fix) และตรวจสอบ Error Log'],
[2, 'Adaptive Maintenance', 'ปรับระบบให้รองรับ Browser หรือระบบปฏิบัติการรุ่นใหม่'],
[3, 'Preventive Maintenance', 'ปรับปรุงความปลอดภัย (Patch Security) และทดสอบการสำรอง/กู้คืนข้อมูล'],
], ['c']),
'## กิจกรรมการบำรุงรักษา (Maintenance Activities)',
table(['ลำดับ', 'กิจกรรม', 'รายละเอียด', 'ความถี่', 'ผู้รับผิดชอบ'], [
[1, 'ตรวจสอบ Error Log', 'วิเคราะห์ Log ของแอปพลิเคชันและบริการ Node.js แล้วแก้ไขข้อผิดพลาดที่พบ', 'รายสัปดาห์', 'System Admin'],
[2, 'Patch Security', 'ปรับปรุงแพตช์ความปลอดภัยของ PHP, MariaDB, Node.js และใบรับรอง TLS', 'รายเดือน หรือเมื่อมี Critical Patch', 'System Admin'],
[3, 'Backup & Recovery Test', 'ตรวจสอบชุดสำรองรายวันและทดสอบกู้คืนสู่สภาพแวดล้อมทดสอบ', 'สำรองทุกวัน ทดสอบกู้คืนทุกไตรมาส', 'System Admin'],
[4, 'Monitor Uptime', 'ตรวจสอบความพร้อมใช้งานของเว็บ ฐานข้อมูล และบริการ Node.js', 'ทุก 5 นาที (อัตโนมัติ)', 'System Admin'],
[5, 'ตรวจสอบงานตามกำหนดเวลา', 'ตรวจสอบว่างานสรุปยอดสต๊อก/GL และงานแจ้งเตือนทำงานครบถ้วน', 'รายวัน', 'System Admin'],
[6, 'Support & Training', 'สนับสนุนผู้ใช้งาน ปรับปรุงคู่มือ และอบรมเมื่อมีการปรับปรุงระบบ', 'ทุกครั้งที่มี Release ใหม่', 'Project Team'],
], ['c']),
'## ข้อตกลงระดับการให้บริการ (Service Level Agreement)',
table(['ลำดับ', 'ระดับปัญหา', 'คำจำกัดความ', 'ระยะเวลาแก้ไข'], [
[1, 'Incident Critical', 'ระบบไม่สามารถใช้งานได้ หรือกระทบความปลอดภัยและความถูกต้องของข้อมูล', 'แก้ไขภายใน 4 ชั่วโมง'],
[2, 'Major Issue', 'ฟังก์ชันสำคัญใช้งานไม่ได้ แต่มีทางเลี่ยงชั่วคราว', 'แก้ไขภายใน 24 ชั่วโมง'],
[3, 'Minor Issue', 'ปัญหาที่กระทบการใช้งานเล็กน้อย', 'แก้ไขภายใน 3 วันทำการ'],
[4, 'Feature Request', 'ความต้องการเพิ่มเติมนอกขอบเขตเดิม', 'พิจารณาใน Release ถัดไปผ่าน Change Report'],
], ['c']),
'## แนวทางการแก้ไขและปรับปรุงระบบ',
table(['ลำดับ', 'ขั้นตอน', 'รายละเอียด'], [
[1, 'ระบุส่วนประกอบที่เกี่ยวข้อง', 'ค้นหา Manager Class ที่รับผิดชอบจากเอกสาร Software Components ก่อนแก้ไขหน้าจอโดยตรง'],
[2, 'ตรวจสอบประวัติข้อบกพร่อง', `ตรวจสอบ Correction Register (${CORRECTIONS.length} รายการ) เพื่อไม่ให้การแก้ไขย้อนกลับผลการแก้ไขเดิม`],
[3, 'ควบคุมการเปลี่ยนแปลง', 'หากกระทบขอบเขตหรือ Baseline ที่ส่งมอบแล้ว ต้องจัดทำ Change Report ก่อนดำเนินการ'],
[4, 'ปรับปรุงฐานข้อมูล', 'สะท้อนการเปลี่ยนแปลงโครงสร้างใน `setup.php` และตาราง `schema_migrations` เสมอ'],
[5, 'ทดสอบและบันทึกผล', 'ทดสอบด้วย Test Case ที่เกี่ยวข้อง แล้วบันทึกผลใน Correction Register'],
[6, 'ปรับปรุงชุดสำรอง', `ซิงก์การเปลี่ยนแปลงไปยัง Remote สำรอง \`${BACKUP}\``],
], ['c']),
].join('\n\n'),
secretary: P.ThS, reviewer: P.NoC, approver: P.SeV,
}),
};
}
/** 21. Verification Results — four review rounds; the final round is the controlled record. */
export function verificationResults() {
const ROUNDS = VERIFICATION_ROUNDS;
const CHECKS = {
'WP 1.0': ['Software Project Plan', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดใน Header ครบถ้วน'],
['การ Run Version ของเอกสารที่ระบุใน Header ถูกต้องและสัมพันธ์กับชื่อไฟล์', 'ระบุเวอร์ชันและวันที่ตรงกับชื่อไฟล์'],
['ระบุรายละเอียดหัวข้อ 1 Manufacture, 2 Overview, 3 Goals and Scope ครบถ้วน', 'มีข้อมูลครบถ้วนถูกต้อง'],
['ใน 3.3 Work Products มีรายการสิ่งส่งมอบพร้อมรหัส เช่น WP 1.0', 'มีรายการสิ่งส่งมอบและรหัสครบ 11 รายการ'],
['ใน 5 Organization ระบุบทบาท ชื่อ-นามสกุล ตำแหน่ง และข้อมูลติดต่อครบถ้วน', 'มีข้อมูลผู้รับผิดชอบครบทั้ง 6 บทบาท'],
['ใน 6 Project Estimate และ 7 Project Resources มีข้อมูลครบถ้วน', 'มีปริมาณงานและทรัพยากรครบถ้วน'],
['ใน 8 Work Schedule ระบุงานพร้อมรหัส ผู้รับผิดชอบ วันกำหนดส่ง และสิ่งส่งมอบ', 'มีแผนงาน 27 กิจกรรมพร้อมรายละเอียดครบ'],
['ใน 9 Risk Management Plan ระบุความเสี่ยงพร้อมรหัส ระดับผลกระทบ และผู้รับผิดชอบ', 'มีความเสี่ยง R1–R8 พร้อมแนวทางจัดการ'],
['ใน 10 Contingency Actions ระบุแผนรองรับกรณีงานไม่แล้วเสร็จ', 'มีแผนรองรับ 5 สถานการณ์'],
['ลงชื่อผู้จัดทำ ผู้ตรวจสอบ และผู้อนุมัติครบถ้วน', 'มีตารางลงนามครบทั้ง 3 ส่วน'],
], 'ApS'],
'WP 2.0': ['Customer Requirements', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดใน Header ครบถ้วน'],
['จัดความต้องการแต่ละรายการลงในหมวด CR01–CR14 อย่างถูกต้อง', 'จัดหมวดครบทั้ง 14 หมวด'],
['รหัสประจำรายการ เช่น CR01:001 เขียนและเรียงลำดับถูกต้อง', 'รหัสเรียงลำดับถูกต้องทุกรายการ'],
['ช่อง Result มีข้อสรุปครบทุกรายการ ไม่มีรายการที่ไม่มีข้อสรุป', `มีผลสรุป A ครบทั้ง ${CRS.length} รายการ`],
['ลงชื่อผู้จัดทำและผู้อนุมัติซึ่งเป็นฝ่ายลูกค้าเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'NoC'],
'WP 3.0': ['Software Requirements', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['จัดความต้องการซอฟต์แวร์ลงในหมวด SR01–SR09 อย่างถูกต้อง', 'จัดหมวดครบทั้ง 9 หมวด'],
['แต่ละหมวด SR01–SR09 มีรายการตามความจำเป็นครบถ้วน', 'มีรายการครบ 49 รายการ'],
['รหัสประจำรายการ เช่น SR01:001 เขียนและเรียงลำดับถูกต้อง', 'รหัสเรียงลำดับถูกต้องทุกรายการ'],
['ช่อง Remark ระบุความสัมพันธ์กับ CR ครบถ้วน', 'ทุกรายการอ้างอิงกลับไปยัง CR ที่เกี่ยวข้อง'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'NoC'],
'WP 4.0': ['Software Design', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ใน High Level Design มี Architecture, Use Case, Component และ Deployment Diagram', 'มีแผนภาพครบทั้ง 4 แบบ'],
['ใน User Interface Design มีผังหน้าจอที่จะใช้ในระบบ', 'มีผังหน้าจอครบทุกกลุ่มโมดูล พร้อม Mockup Wireframe ของหน้าจอหลัก 6 รูปแบบ'],
['มีหัวข้อ Software Baseline ระบุสิ่งที่ถูกกำหนดเป็น Baseline วันที่ และผู้อนุมัติ', 'มีหัวข้อ Software Baseline ครบถ้วน พร้อมอ้างอิง Software Configuration และ Traceability Record'],
['ใน Software Unit มีรหัสกำกับแต่ละหน่วย เช่น UN01.001 อย่างเป็นระเบียบ', 'มีรหัสครบ 45 หน่วย'],
['ใน Software Unit มี Description และ Functional Interfaces Detail ครบทุกรายการ', 'มีรายละเอียดครบทุกหน่วย'],
['ใน Software Unit ระบุ References ที่สัมพันธ์กับ SR ครบถ้วน', 'อ้างอิง SR ถูกต้องครบทุกหน่วย'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'NoC'],
'WP 5.0': ['Change Report', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ระบุเกณฑ์การพิจารณาว่ารายการใดเป็นคำขอเปลี่ยนแปลง', 'มีเกณฑ์ 3 ข้อ ครอบคลุมความจำเป็นต่อการส่งมอบ ขอบเขต และ Man-day'],
['รายการปรับปรุงที่เสนอได้รับการพิจารณาตามเกณฑ์และบันทึกผลครบทุกรายการ', 'มีผลการพิจารณาและการดำเนินการครบทุกรายการ'],
['ลงชื่อผู้จัดทำ ผู้ตรวจสอบ และผู้อนุมัติครบถ้วน', 'มีตารางลงนามครบถ้วน'],
], 'ApS'],
'WP 6.0': ['Test Case and Test Procedures', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ใน Test Case Specification มีรายการทดสอบพร้อมรหัสเรียงลำดับ', 'มีรายการครบ 45 Test Case'],
['Test Item ระบุ Software Unit ที่ทดสอบโดยอ้างรหัส เช่น UN01.001', 'อ้างอิง Software Unit ครบทุกรายการ'],
['Input Specification ระบุขั้นตอนและข้อมูลนำเข้าอย่างชัดเจน', 'มีรายละเอียดครบทุกรายการ'],
['Output Specification ระบุผลลัพธ์ที่คาดหวังอย่างชัดเจน', 'มีรายละเอียดครบทุกรายการ'],
['Environment Needs, Special Procedural Required และ Intercase Dependency ระบุตามความจำเป็น', 'มีรายละเอียดครบถ้วน'],
['ส่วนผลการทดสอบระบุ Test Date และ Status ครบถ้วนไม่มีช่องว่าง', 'มีผลการทดสอบครบทุกรายการ'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'PaNg'],
'WP 7.0': ['Validation Results', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ใช้ Customer Requirements เป็นตัวตั้งของการทดสอบการยอมรับ', `ครอบคลุมครบ 14 หมวด ${CRS.length} รายการ`],
['ระบุวิธีการยืนยันและหลักฐานอ้างอิงของแต่ละความต้องการ', 'มีวิธีการยืนยันและหลักฐานครบทุกรายการ'],
['ระบุผลการทดสอบและผู้ทดสอบครบทุกรายการ', 'มีผลและผู้ทดสอบครบทุกรายการ'],
['ลงชื่อผู้ทดสอบฝ่ายลูกค้าเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'PaNg'],
'WP 8.0': ['Software User Document', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['เนื้อหาครอบคลุมการใช้งานทุกระบบงานที่ส่งมอบ', 'ครอบคลุมครบทุกระบบงาน'],
['ใช้ถ้อยคำถูกต้องและสอดคล้องกับหน้าจอจริง', 'ตรวจสอบแล้วสอดคล้อง'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'YaB'],
'WP 9.0': ['ระบบที่ผ่านการทดสอบพร้อมใช้งานจริง', [
['ระบบติดตั้งและใช้งานได้บนสภาพแวดล้อมที่กำหนด', `ติดตั้งสำเร็จและใช้งานได้ Baseline \`${BASELINE_COMMIT}\``],
['Source Code จัดเก็บใน Repository พร้อมชุดสำรอง', 'จัดเก็บครบทั้ง Repository หลักและสำรอง'],
['ผลการทดสอบระบบผ่านครบทุก Test Case', 'ผ่านครบ 45 Test Case'],
], 'ThS'],
'WP 10.0': ['Product Operation Guide', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ระบุขั้นตอนการติดตั้งและตั้งค่าครบถ้วน', 'มีทั้งแบบ Container และแบบ Manual'],
['ระบุการเฝ้าระวัง การสำรอง และการกู้คืนข้อมูล', 'มีครบถ้วนพร้อมผลการทดสอบกู้คืน'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'ThS'],
'WP 11.0': ['Maintenance Document', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ระบุขอบเขต ประเภท และกิจกรรมการบำรุงรักษาครบถ้วน', 'มีครบทั้ง 3 ประเภทและ 6 กิจกรรม'],
['ระบุข้อตกลงระดับการให้บริการ (SLA)', 'มี SLA ครบ 4 ระดับ'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'ThS'],
};
return ROUNDS.map((r) => {
const rows = [];
let n = 0;
for (const wp of r.wps) {
const [wpName, checks, owner] = CHECKS[wp];
rows.push([`${wp} ${wpName}`, '', '', '', '', '']);
checks.forEach((c, i) => {
n += 1;
rows.push([`VR${String(r.no).padStart(2, '0')}.${wp.replace(/\D/g, '').padStart(3, '0')}.${String(i + 1).padStart(3, '0')}`, c[0], c[1], 'Passed', P[owner].id, '']);
});
}
return {
dir: '2-SI Process (12 Work Product)/21.Verification Result',
name: `${CODE} Verification Results ${bud(r.date)} ${r.v} PaNg`,
md: workProduct({
type: 'Verification Results', footer: 'VeR',
rows: head('Verification Results', `${bud(r.date)} ${r.v} PaNg`, [
['Title', 'บันทึกการตรวจสอบตามข้อกำหนดของมาตรฐาน'],
['รอบตรวจสอบ', `รอบที่ ${r.no} (${thLong(r.date)})`, 'หัวข้อ', r.topic],
['เวลาตรวจสอบ', r.time, 'ระยะเวลา', `${r.hours} ชั่วโมง`],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
]),
body: [
'## วัตถุประสงค์ (Objective)',
`การตรวจสอบ (Verification) จัดทำขึ้นเพื่อยืนยันความถูกต้องและความครบถ้วนของสิ่งส่งมอบ (Work Products) ตามมาตรฐาน ISO/IEC 29110 ตามแผนการตรวจสอบในหัวข้อ 8.1 ของ Software Project Plan ซึ่งกำหนดให้ตรวจสอบ ${ROUNDS.length} รอบ ประมาณทุก 2 เดือน และรอบสุดท้ายก่อนส่งมอบระบบ เพื่อลดความเสี่ยงด้านคุณภาพ ความล่าช้า และค่าใช้จ่ายที่เกิดจากการแก้ไขย้อนหลัง การตรวจสอบรอบนี้เป็นรอบที่ ${r.no} ใช้เวลา ${r.hours} ชั่วโมง (${r.time})`,
'## Deliverables under Review',
table(['รหัส', 'สิ่งส่งมอบที่ตรวจสอบในรอบนี้'], r.wps.map((wp) => [wp, CHECKS[wp][0]]), ['c']),
'## Risk & Constraints Note',
numbered(VERIFICATION_RULES),
'## ผลการตรวจสอบ',
table(['ID', 'Verification Item', 'Evidence', 'Result', 'Owner', 'Comment / Risk / Recommendation'], rows),
'## สรุปผลการตรวจสอบรอบนี้',
table(['รายการ', 'จำนวน'], [
['รายการตรวจสอบทั้งหมด', n],
['ผลผ่าน (Passed)', n],
['ผลไม่ผ่าน (Failed)', 0],
['ประเด็นคงค้าง', 0],
], ['', 'r']),
r.no === 4
? `รอบตรวจสอบนี้เป็นรอบสุดท้ายก่อนส่งมอบ ครอบคลุมสิ่งส่งมอบทั้งหมด ${r.wps.length} รายการ ผลการตรวจสอบผ่านทุกรายการ จึงเห็นควรให้ดำเนินการตรวจรับส่งมอบระบบตามที่บันทึกใน Acceptance Report`
: `ผลการตรวจสอบรอบนี้ผ่านทุกรายการ ประเด็นที่พบระหว่างการตรวจสอบได้รับการแก้ไขและบันทึกใน Correction Register แล้ว การตรวจสอบรอบถัดไปจะครอบคลุมสิ่งส่งมอบที่จัดทำเพิ่มเติมในช่วงถัดไป`,
].join('\n\n'),
secretary: P.PaNg, reviewer: P.YaB, approver: P.SeV,
}),
};
});
}
/** 22. Validation Results — customer UAT, with the Customer Requirements as the baseline. */
export function validationResults() {
const date = '2026-08-14';
// How the customer confirmed each requirement group during the UAT on 10–14 Aug.
const METHOD = {
CR01: 'ทดลองใช้งานตามขั้นตอนธุรกิจจริงบนระบบ',
CR02: 'ใช้งานประจำวันบนสภาพแวดล้อมจริงและสังเกตเวลาตอบสนอง',
CR03: 'ใช้งานผ่าน Browser ที่กำหนด รับอีเมลและการแจ้งเตือนจริง',
CR04: 'ตรวจสอบสภาพแวดล้อมที่ติดตั้งและการเข้าสู่ระบบ',
CR05: 'ทดลองใช้งานบน Desktop และ Tablet/Mobile',
CR06: 'ตรวจสอบ HTTPS การจำกัดสิทธิ์ และการเข้าสู่ระบบซ้ำซ้อน',
CR07: 'ตรวจสอบสภาพแวดล้อมจริง เขตเวลา และการแยกข้อมูลสาธิต',
CR08: 'ตรวจสอบการสำรอง/กู้คืน การเฝ้าระวัง และการแยกข้อมูลหลายบริษัท',
CR09: 'ทบทวนโครงสร้างซอฟต์แวร์และเอกสารการบำรุงรักษา',
CR10: 'สาธิตการติดตั้งตามขั้นตอนในคู่มือ',
CR11: 'ทบทวนคู่มือ แผนการอบรม และช่องทางสนับสนุน',
CR12: 'ตรวจสอบเทคโนโลยี การควบคุม Source Code และเอกสารตามมาตรฐาน',
CR13: 'ทดลองทำรายการที่ล้มเหลวและตรวจสอบข้อความแจ้งข้อผิดพลาด',
CR14: 'ทบทวนผลการทดสอบ การสอบกลับ และผลการตรวจสอบ',
};
// Evidence the customer had in hand at the UAT, where it differs from the trace record.
const AT_UAT = {
'CR11:003': 'แผนการอบรมก่อนเปิดใช้งาน (Task 5.2)',
'CR12:004': 'Verification Results รอบที่ 1–3',
'CR14:003': 'ผลการทดสอบการยอมรับในเอกสารฉบับนี้',
};
const evidence = (id) => {
const t = traceOf(id);
const ev = AT_UAT[id] ?? t.ev;
return [t.tcs.length ? t.tc : '', ev].filter(Boolean).join(' / ');
};
const rows = [];
const summary = [];
for (const [gid, gname, items] of CR_GROUPS) {
rows.push([gid, `**${gname}**`, '', '', '', '']);
for (const [id, text] of items) rows.push([id, text, METHOD[gid], evidence(id), 'Passed', P.SeV.id]);
summary.push([`${gid} ${gname}`, items.length, items.length, 0]);
}
if (Object.keys(METHOD).length !== CR_GROUPS.length) throw new Error('Every CR group needs a validation method.');
return {
dir: '2-SI Process (12 Work Product)/22.Validation Result',
name: `${CODE} Validation Results ${bud(date)} V1.0 PaNg`,
md: workProduct({
type: 'Validation Result', footer: 'VaR (UAT)',
rows: head('Validation Result (UAT)', `${bud(date)} V1.0 PaNg`, [
['Title', 'บันทึกการยืนยันความต้องการกับผู้ใช้งาน'],
['Project Period', thRangeLong(START, END)],
['Organizer', ORGANIZER],
['Recorder', RECORDER],
['Responsible', `${P.SeV.th} (Project Sponsor / ตัวแทนลูกค้า)`],
]),
body: [
'## วัตถุประสงค์ (Objective)',
'เพื่อยืนยันว่าระบบที่พัฒนาตรงตามความต้องการของลูกค้า (Customer Requirements) สามารถใช้งานได้จริง มีประสิทธิภาพ มีความปลอดภัย และพร้อมเปิดใช้งานจริง (Go-Live) โดยใช้ความต้องการของลูกค้าทุกรายการเป็นตัวตั้งของการทดสอบการยอมรับ และอ้างอิง Test Case หรือเอกสารส่งมอบเป็นหลักฐานประกอบ',
'## สภาพแวดล้อมและผู้ทดสอบ',
table(['หัวข้อ', 'รายละเอียด'], [
['สภาพแวดล้อมที่ใช้ทดสอบ', 'สภาพแวดล้อมใช้งานจริงของลูกค้า'],
['ผู้ทดสอบ', `${P.SeV.th} (Project Sponsor / ตัวแทนลูกค้า)`],
['ผู้บันทึกผล', `${P.PaNg.th} (QA/Tester)`],
['ช่วงเวลาทดสอบ', thRangeLong(TEST_FROM, TEST_TO)],
['ขอบเขตการทดสอบ', `Customer Requirements ครบ ${CR_GROUPS.length} หมวด ${CRS.length} รายการ (CR01–CR14)`],
['สถานะ Repository ที่ใกล้เคียงที่สุด', `Commit \`${TEST_STATE_COMMIT}\``],
]),
'## ตารางสรุป Validation Result ตาม Customer Requirements',
table(['CR ID', 'Customer Requirement', 'วิธีการยืนยัน', 'หลักฐานอ้างอิง', 'Status', 'Tester'], rows, ['', '', '', '', 'c', 'c']),
'## สรุปผลการทดสอบการยอมรับรายหมวด',
table(['หมวดความต้องการ', 'จำนวนรายการ', 'ผ่าน (Passed)', 'ไม่ผ่าน (Failed)'], summary, ['', 'r', 'r', 'r']),
'## สรุปผลการทดสอบการยอมรับ',
table(['รายการ', 'จำนวน'], [
['หมวดความต้องการที่ทดสอบ', CR_GROUPS.length],
['ความต้องการที่ทดสอบทั้งหมด', CRS.length],
['ผ่าน (Passed)', CRS.length],
['ไม่ผ่าน (Failed)', 0],
['ค้างการทดสอบ', 0],
], ['', 'r']),
`ตัวแทนลูกค้าได้ทดสอบการยอมรับครบทุกความต้องการใน Customer Requirements บนสภาพแวดล้อมใช้งานจริง ผลการทดสอบผ่านทั้งหมด ไม่พบประเด็นที่ขัดขวางการเปิดใช้งาน จึงยืนยันความพร้อมของระบบและเสนอเข้าสู่การตรวจรับส่งมอบตามแผนในวันที่ ${thLong(ACCEPT_DATE)}`,
].join('\n\n'),
secretary: P.PaNg, reviewer: P.NoC, approver: P.SeV,
}),
};
}