// PM work products 3–10: Progress Status Record, Correction Register, Acceptance Report, // Change Report, Meeting Record, Software Configuration, Project Repository (+ Backup). import { P, CRS, CODE, PROJECT_TH, COMPANY_TH, ORGANIZER, ORGANIZER_PM, RECORDER, START, END, WPS, TEAM, ORIGIN, BACKUP, TAG, BASELINE_COMMIT, TEST_FROM, TEST_TO, ACCEPT_DATE, OPS_CLOSE_DATE } from './project.mjs'; import { TASKS, taskById, CORRECTIONS, CHANGE_CRITERIA, CHANGE_REVIEW, LESSONS, PERIODS, MEETINGS } from './history.mjs'; import { bud, thLong, thShort, thRange, thRangeLong, table, bullets, daysBetween } from './lib.mjs'; import { workProduct, head } from './format.mjs'; const ids = (arr) => arr.map((i) => P[i].id).join(', '); const dir3 = '1-PM Process (10 Work Product)/3.Progress Status Record'; /** 3. Progress Status Record — one per reporting period. */ export function progressStatusRecords() { return PERIODS.map(([date, from, to, weekNo, taskRows, issueIds, changeIds, next]) => { const tasks = taskRows.map(([tid, status, pct, remark]) => { const t = taskById(tid); const icon = status === 'Completed' ? 'Completed' : status === 'In Progress' ? 'In Progress' : 'Planned'; return [tid, t[2], thRange(t[5], t[6]), ids(t[4]), icon, pct, remark]; }); // The reference package records risks in five columns: number, issue, // detail, owner and status. const issues = issueIds.map((iid, n) => { const c = CORRECTIONS.find((x) => x[0] === iid); return [n + 1, `${c[0]} : ${c[4]}`, `${c[5]} จึงดำเนินการ${c[6]} แล้วเสร็จ ${thShort(c[2])}`, P[c[7]].id, 'เสร็จสิ้น']; }); if (changeIds.length) throw new Error(`Progress record ${date} lists change requests, but the project has none.`); const changes = []; return { dir: dir3, name: `${CODE} Progress Status Record ${bud(date)} V1.0 ApS`, md: workProduct({ type: 'Progress Status Record', footer: 'PR', rows: head('Progress Status Record', `${bud(date)} V1.0 ApS`, [ ['Title', `รายงานสรุปงวดที่ ${weekNo} : ${thLong(date)}`], ['Reporting Period', thRangeLong(from, to)], ['Organizer', ORGANIZER_PM], ['Recorder', RECORDER], ]), body: [ '## สรุปสถานะโครงการ', table(['Task ID', 'Task Name', 'Period', 'Responsible', 'Status', 'Progress (%)', 'Remarks'], tasks, ['c', '', 'c', 'c', 'c', 'r', '']), '## ความเสี่ยง/อุปสรรค', table(['ลำดับ', 'ประเด็น / ความเสี่ยง', 'รายละเอียด', 'ผู้รับผิดชอบ', 'สถานะ'], issues.length ? issues : [['-', 'ไม่พบประเด็นความเสี่ยงหรืออุปสรรคในงวดนี้', '-', '-', '-']], ['c', '', '', 'c', 'c']), '## สรุป CR/Change (ถ้ามี)', changes.length ? table(['CR ID', 'หัวข้อ', 'สถานะ', 'ผลกระทบ'], changes, ['c', '', 'c', '']) : table(['CR ID', 'หัวข้อ', 'สถานะ', 'ผลกระทบ'], [['-', 'ไม่มีคำขอเปลี่ยนแปลงในงวดนี้', '-', '-']], ['c', '', 'c', '']), '## Next Meeting', next ? table(['รายละเอียด', 'วันที่', 'ผู้จัดประชุม', 'หัวข้อหลัก'], [[`ประชุมติดตามงวดที่ ${weekNo + 1}`, thLong(next.date), `${P.ApS.th} (${P.ApS.id})`, next.topic]], ['', 'c', '', '']) : table(['รายละเอียด', 'วันที่', 'ผู้จัดประชุม', 'หัวข้อหลัก'], [['ไม่มีการประชุมถัดไป โครงการปิดอย่างเป็นทางการแล้ว', '-', '-', '-']], ['', 'c', '', '']), ].join('\n\n'), secretary: P.YaB, reviewer: P.ApS, approver: P.SeV, }), }; }); } /** 4. Correction Register — all issues found during the project. */ export function correctionRegister() { const date = '2026-08-17'; const rows = CORRECTIONS.map((c, i) => [i + 1, c[0], thShort(c[1]), c[4], c[5], c[6], P[c[7]].id, thShort(c[2]), 'เสร็จสิ้น']); const bySev = (s) => CORRECTIONS.filter((c) => c[3] === s).length; return { dir: '1-PM Process (10 Work Product)/4.Correction Register', name: `${CODE} Correction Register ${bud(date)} V1.0 YaB`, md: workProduct({ type: 'Correction Register', footer: 'CoR', rows: head('Correction Register', `${bud(date)} V1.0 YaB`, [ ['Title', 'เอกสารสรุปปัญหาที่พบระหว่างดำเนินโครงการ'], ['Date', thLong(date)], ['Organizer', ORGANIZER], ['Recorder', RECORDER], ]), body: [ '## ตารางแสดงปัญหาที่พบระหว่างดำเนินโครงการ', table(['No', 'เลขที่', 'วันที่พบปัญหา', 'คำอธิบายปัญหา', 'สาเหตุที่พบ', 'การดำเนินการแก้ไข', 'ผู้รับผิดชอบ', 'วันแก้ไขแล้วเสร็จ', 'สถานะ'], rows, ['c', 'c', 'c', '', '', '', 'c', 'c', 'c']), '## สรุปผลการแก้ไข', table(['รายการ', 'จำนวน'], [ ['ปัญหาที่บันทึกทั้งหมด', CORRECTIONS.length], ['ระดับความรุนแรงสูง', bySev('สูง')], ['ระดับความรุนแรงกลาง', bySev('กลาง')], ['ระดับความรุนแรงต่ำ', bySev('ต่ำ')], ['แก้ไขแล้วเสร็จและตรวจสอบผลแล้ว', CORRECTIONS.length], ['คงค้าง', 0], ], ['', 'r']), `ปัญหาทุกรายการได้รับการแก้ไขและตรวจสอบผลด้วย Test Case ที่เกี่ยวข้อง โดยผลการทดสอบผ่านทั้งหมดในรอบทดสอบระหว่าง ${thRangeLong(TEST_FROM, TEST_TO)} ไม่มีข้อบกพร่องระดับวิกฤตคงค้าง ณ วันตรวจรับ`, '## บทเรียนและแนวทางป้องกันสำหรับโครงการถัดไป (Lessons Learned)', 'เมื่อปิดโครงการ ทีมงานทบทวนปัญหาทั้งหมดในทะเบียนนี้ จัดกลุ่มตามสาเหตุ และกำหนดแนวทางป้องกันเพื่อไม่ให้ปัญหาลักษณะเดียวกันเกิดซ้ำในโครงการถัดไป', table(['ลำดับ', 'กลุ่มปัญหา', 'เลขที่ปัญหา', 'จำนวน', 'สิ่งที่เกิดขึ้น', 'แนวทางป้องกัน (Preventive Action)'], LESSONS.map(([theme, issueIds, what, action], i) => [i + 1, theme, issueIds.join(', '), issueIds.length, what, action]), ['c', '', '', 'r', '', '']), '## การเชื่อมโยงกับหลักฐานการทดสอบ', table(['เลขที่', 'การแก้ไขอ้างอิง (Commit)', 'Test Case ที่ใช้ตรวจสอบ', 'ผลการทดสอบ'], CORRECTIONS.map((c) => [c[0], `\`${c[8]}\``, c[9], 'Passed']), ['c', '', 'c', 'c']), ].join('\n\n'), secretary: P.YaB, reviewer: P.ApS, approver: P.SeV, }), }; } /** 5. Acceptance Report — the sign-off record. */ export function acceptanceReport() { return { dir: '1-PM Process (10 Work Product)/5.Acceptance Report', name: `${CODE} Acceptance Report ${bud(ACCEPT_DATE)} V1.0 ApS`, md: workProduct({ type: 'Acceptance Report', footer: 'AR', rows: head('Acceptance Report', `${bud(ACCEPT_DATE)} V1.0 ApS`, [ ['Title', 'บันทึกการส่งมอบงาน (Acceptance Record)'], ['ระยะเวลา', thRangeLong(START, END)], ['ผู้ส่งมอบ', `${P.ApS.th} (Project Manager)`], ['ผู้รับมอบ', `${P.SeV.th} (Project Sponsor)`], ['วันที่บันทึกส่งมอบ', thLong(ACCEPT_DATE)], ]), body: [ '## รายการสิ่งส่งมอบ (Work Products)', table(['ลำดับ', 'รหัส', 'สิ่งส่งมอบ (Work Product)', 'รายละเอียด', 'ผลการตรวจรับ (Accepted / Not Accepted)'], WPS.map((w, i) => [i + 1, w[0], w[1], w[2], 'Accepted']), ['c', 'c', '', '', 'c']), '## ผลการตรวจสอบตามเกณฑ์การตรวจรับ', table(['ลำดับ', 'เกณฑ์การตรวจรับ', 'หลักฐานอ้างอิง', 'ผลการตรวจสอบ'], [ [1, 'ฟังก์ชันในขอบเขตทำงานตรงตาม Customer Requirements ครบทุกรายการ', 'Traceability Record, Test Report', 'ผ่าน'], [2, 'ทดสอบระบบตาม Test Case ครบถ้วน', `Test Report 45 Test Case ผ่านทั้งหมด (${thRangeLong(TEST_FROM, TEST_TO)})`, 'ผ่าน'], [3, 'ทดสอบการยอมรับโดยผู้ใช้ (UAT)', `Validation Results ครบ 14 หมวดความต้องการ (${CRS.length} รายการ) ผ่านทั้งหมด`, 'ผ่าน'], [4, 'ไม่มีข้อบกพร่องระดับวิกฤตคงค้าง', `Correction Register ${CORRECTIONS.length} รายการ แก้ไขและตรวจสอบครบ`, 'ผ่าน'], [5, 'เอกสารคู่มือผู้ใช้ ผู้ดูแลระบบ และการบำรุงรักษาครบถ้วน', 'Software User Document, Product Operation Guide, Maintenance Document', 'ผ่าน'], [6, 'Source Code และเอกสารจัดเก็บใน Repository พร้อมชุดสำรอง', 'Project Repository, Project Repository (Backup)', 'ผ่าน'], [7, 'ผลการตรวจสอบ Work Products โดยผู้ตรวจสอบอิสระ', 'Verification Results รอบที่ 4', 'ผ่าน'], ], ['c', '', '', 'c']), '## สรุปผลการตรวจรับ', table(['รายการ', 'ผลสรุป'], [ ['จำนวนสิ่งส่งมอบทั้งหมด', `${WPS.length} รายการ`], ['ตรวจรับแล้ว (Accepted)', `${WPS.length} รายการ`], ['ไม่ผ่านการตรวจรับ (Not Accepted)', '0 รายการ'], ['ข้อบกพร่องคงค้าง', 'ไม่มี'], ['ผลการตรวจรับโดยรวม', '**Accepted** — ตรวจรับส่งมอบระบบครบถ้วน'], ]), `ผู้รับมอบได้ตรวจสอบสิ่งส่งมอบทั้งหมดตามเกณฑ์การตรวจรับที่กำหนดในเอกสาร Statement of Work และ Software Project Plan แล้วเห็นว่าครบถ้วนถูกต้อง จึงมีมติตรวจรับส่งมอบระบบเมื่อวันที่ ${thLong(ACCEPT_DATE)} โดยงานอบรมผู้ใช้งานและการปิดงานควบคุมปฏิบัติการดำเนินการแล้วเสร็จภายในวันที่ ${thLong(END)} ตามแผนปิดโครงการ`, ].join('\n\n'), secretary: P.ApS, reviewer: P.YaB, approver: P.SeV, }), }; } /** 6. Change Report — the change-control register. No adjustment in this project met the criteria. */ export function changeReports() { const date = ACCEPT_DATE; return [{ dir: '1-PM Process (10 Work Product)/6.Change Report', name: `${CODE} Change Report ${bud(date)} V1.0 ApS`, md: workProduct({ type: 'Change Report', footer: 'CR', rows: head('Change Report', `${bud(date)} V1.0 ApS`, [ ['Title', 'ทะเบียนการควบคุมการเปลี่ยนแปลง (Change Control Register)'], ['Project Period', thRangeLong(START, END)], ['Organizer', ORGANIZER_PM], ['Recorder', RECORDER], ]), body: [ '## วัตถุประสงค์ (Objective)', 'เอกสารนี้บันทึกการควบคุมการเปลี่ยนแปลงของโครงการ ตั้งแต่ตั้ง Baseline ความต้องการเมื่อ 18 กุมภาพันธ์ 2569 จนถึงการตรวจรับส่งมอบ โดยพิจารณาทุกรายการปรับปรุงที่มีการเสนอตามเกณฑ์ในส่วนที่ 1 รายการที่เข้าเกณฑ์ต้องจัดทำคำขอเปลี่ยนแปลง ประเมินผลกระทบ และขออนุมัติก่อนดำเนินการ ส่วนรายการที่ไม่เข้าเกณฑ์ดำเนินการเป็นมติที่ประชุมหรืองานในแผน', '## ส่วนที่ 1 : เกณฑ์การพิจารณาคำขอเปลี่ยนแปลง (Change Criteria)', 'รายการใดจะถือเป็นคำขอเปลี่ยนแปลงเมื่อเข้าเกณฑ์ทั้ง 3 ข้อ หากตัดรายการนั้นออกแล้วยังส่งมอบงานได้ตามที่ตกลงไว้ จะไม่ถือเป็นคำขอเปลี่ยนแปลง', table(['ลำดับ', 'เกณฑ์', 'คำอธิบาย'], CHANGE_CRITERIA.map(([name, detail], i) => [i + 1, name, detail]), ['c']), '## ส่วนที่ 2 : ทะเบียนคำขอเปลี่ยนแปลง (Change Request Log)', table(['Change ID', 'หัวข้อ', 'ผู้ร้องขอ', 'ผลกระทบ', 'สถานะ'], [['-', 'ไม่มีคำขอเปลี่ยนแปลงที่เข้าเกณฑ์ตลอดโครงการ', '-', '-', '-']], ['c', '', 'c', '', 'c']), '## ส่วนที่ 3 : รายการปรับปรุงที่พิจารณาแล้วไม่เข้าเกณฑ์', table(['ลำดับ', 'วันที่พิจารณา', 'รายการ', 'ผู้เสนอ', 'ผลการพิจารณา', 'การดำเนินการ', 'อ้างอิง'], CHANGE_REVIEW.map(([when, topic, who, assessment, handled, ref], i) => [i + 1, thShort(when), topic, P[who].id, `ไม่เข้าเกณฑ์ — ${assessment}`, handled, ref]), ['c', 'c', '', 'c', '', '', '']), '## ส่วนที่ 4 : สรุปผลการควบคุมการเปลี่ยนแปลง', table(['รายการ', 'จำนวน'], [ ['รายการปรับปรุงที่เสนอและพิจารณา', CHANGE_REVIEW.length], ['คำขอเปลี่ยนแปลงที่เข้าเกณฑ์', 0], ['คำขอเปลี่ยนแปลงที่อนุมัติ', 0], ['ผลกระทบต่อ Man-day กำหนดส่งมอบ และค่าใช้จ่าย', 'ไม่มี'], ], ['', 'r']), 'ขอบเขตงาน สิ่งส่งมอบ และ Customer Requirements ที่ตั้ง Baseline ไม่มีการเปลี่ยนแปลงตลอดโครงการ รายการปรับปรุงที่พิจารณาทั้งหมดดำเนินการภายในแผนงานเดิมโดยไม่กระทบ Man-day และกำหนดส่งมอบ', '## ภาคผนวก A : เกณฑ์การประเมินระดับความรุนแรงของการเปลี่ยนแปลง', 'ใช้ประเมินระดับความรุนแรง (Priority Level) เมื่อมีคำขอเปลี่ยนแปลงที่เข้าเกณฑ์ในส่วนที่ 1', table(['ระดับ (Level)', 'คำจำกัดความ (Definition)', 'ตัวอย่างสถานการณ์ (Examples)'], [ ['High', 'กระทบระบบหลัก งบประมาณ หรือแผนงานอย่างมีนัยสำคัญ', 'เพิ่มโมดูลใหม่ เปลี่ยนโครงสร้างฐานข้อมูลหลัก'], ['Medium', 'กระทบฟังก์ชันย่อยหรือส่วนติดต่อผู้ใช้งานบางส่วน และกระทบ Man-day', 'รองรับ 2 ภาษา เพิ่มรายงานใหม่'], ['Low', 'กระทบเล็กน้อยต่อ Man-day ไม่เปลี่ยนการทำงานหลัก', 'เพิ่มฟิลด์ในเอกสาร'], ]), ].join('\n\n'), secretary: P.ApS, reviewer: P.YaB, approver: P.SeV, }), }]; } /** 7. Meeting Record — Minutes of Meeting per checkpoint. */ export function meetingRecords() { return MEETINGS.map((m) => { const t = taskById(m.taskId); const participants = TEAM.map((p) => [p.th, p.id, p.role, '✔']); return { dir: '1-PM Process (10 Work Product)/7.Meeting Record', name: `${CODE} Minutes Of Meeting ${bud(m.date)} V1.0 YaB`, md: workProduct({ type: 'Minutes Of Meeting', footer: 'MoM', rows: head('Minutes Of Meeting', `${bud(m.date)} V1.0 YaB`, [ ['Phase', m.phase], ['Task ID / Task Name', `${m.taskId} ${m.taskName}`], ['Work Period of Task', thRangeLong(t[5], t[6])], ['Meeting Date', thLong(m.date), 'Time', m.time], ['สิ่งแนบ', m.attachment], ['Location', `ห้องประชุม ${COMPANY_TH}`], ['Organizer', ORGANIZER], ['Recorder', RECORDER], ['สำเนาถึง (CC)', `${P.SeV.th} (Project Sponsor)`], ]), body: [ '## ผู้เข้าร่วมประชุม (Participants)', table(['ชื่อ - สกุล', 'ชื่อย่อ', 'ตำแหน่ง/บทบาท', '(✔ เข้าร่วม / ✘ ไม่เข้าร่วม)'], participants, ['', 'c', '', 'c']), '## ความคืบหน้ารวมของโครงการ (Project Progress)', table(['หมวดงาน', 'สถานะ', 'ความคืบหน้า (%)', 'รายละเอียด'], m.progress, ['', 'c', 'r', '']), '## หัวข้อการประชุม (Agenda)', table(['ลำดับ', 'หัวข้อการประชุม', 'วัตถุประสงค์'], m.agenda.map((a, i) => [i + 1, a[0], a[1]]), ['c']), '## สรุปรายงานการประชุม (Discussion Summary)', table(['ลำดับ', 'ประเด็น', 'รายละเอียด / มติที่ได้', 'ผู้รับผิดชอบ', 'สถานะ'], m.discussion.map((d, i) => [i + 1, d[0], d[1], ids(d[2]), d[3]]), ['c', '', '', 'c', 'c']), '## งานที่ต้องติดตาม (Action Item)', table(['ลำดับ', 'งาน', 'ผู้รับผิดชอบ', 'วันครบกำหนด', 'สถานะ'], m.actions.map((a, i) => [i + 1, a[0], ids(a[1]), thLong(a[2]), a[3]]), ['c', '', 'c', 'c', 'c']), '## งานประชุมครั้งถัดไป (Next Meeting)', m.next ? table(['หัวข้อการประชุมถัดไป', 'วันที่', 'ผู้จัดประชุม', 'วัตถุประสงค์'], [[m.next.topic, thLong(m.next.date), `${P.ApS.th} (${P.ApS.id})`, 'ติดตามความคืบหน้าและมติที่เกี่ยวข้อง']], ['', 'c', '', '']) : table(['หัวข้อการประชุมถัดไป', 'วันที่', 'ผู้จัดประชุม', 'วัตถุประสงค์'], [['ไม่มีการประชุมถัดไป โครงการปิดอย่างเป็นทางการแล้ว', '-', '-', '-']], ['', 'c', '', '']), ].join('\n\n'), secretary: P.YaB, reviewer: P.ApS, approver: P.SeV, }), }; }); } /** 8. Software Configuration — controlled items and their versions. */ export function softwareConfiguration() { const date = '2026-08-17'; return { dir: '1-PM Process (10 Work Product)/8.Software Configuration', name: `${CODE} Software Configuration ${bud(date)} V1.0 ThS`, md: workProduct({ type: 'Software Configuration', footer: 'SC', rows: head('Software Configuration', `${bud(date)} V1.0 ThS`, [ ['Title', 'บันทึกการกำหนดค่าและความเปลี่ยนแปลงต่าง ๆ ที่เกิดขึ้นของระบบ'], ['ระยะเวลา', thRangeLong(START, END)], ['Organizer', ORGANIZER_PM], ['Recorder', RECORDER], ]), body: [ '## รายการสิ่งส่งมอบ (Work Products)', table(['No.', 'Work Product', 'Version'], [ ['**เอกสารส่งมอบครั้งที่ 1 (วางแผนโครงการ)**', '', ''], ['WP 1.0', `เอกสาร ${CODE} Software Project Plan (พร้อม Work Schedule)`, '1.0'], ['WP 2.0', `เอกสาร ${CODE} Customer Requirements`, '1.0'], ['**เอกสารส่งมอบครั้งที่ 2 (พัฒนาและทดสอบ)**', '', ''], ['WP 3.0', `เอกสาร ${CODE} Software Requirements Specification`, '1.0'], ['WP 4.0', `เอกสาร ${CODE} Software Design`, '1.0'], ['WP 5.0', `เอกสาร ${CODE} Change Report`, '1.0'], ['WP 6.0', `เอกสาร ${CODE} Test Case and Test Procedures`, '1.0'], ['WP 7.0', `เอกสาร ${CODE} Validation Results`, '1.0'], ['WP 8.0', `เอกสาร ${CODE} Software User Document`, '1.0'], ['WP 9.0', `ระบบ BRN WMS ที่ผ่านการทดสอบ (Git baseline \`${BASELINE_COMMIT}\`)`, '1.0'], ['**เอกสารส่งมอบครั้งที่ 3 (ส่งมอบและปิดโครงการ)**', '', ''], ['WP 10.0', `เอกสาร ${CODE} Product Operation Guide`, '1.0'], ['WP 11.0', `เอกสาร ${CODE} Maintenance Document`, '1.0'], ['—', `เอกสาร ${CODE} Acceptance Report`, '1.0'], ], ['c']), '## รายการ Configuration Item ของซอฟต์แวร์', table(['ลำดับ', 'รายการ', 'ที่จัดเก็บ', 'การควบคุมเวอร์ชัน'], [ [1, 'Source Code แอปพลิเคชัน PHP', '`app/`', `Git baseline \`${BASELINE_COMMIT}\` บน branch main`], [2, 'บริการ Node.js (แจ้งเตือนและงานตามกำหนดเวลา)', '`nodejs/`', 'Git พร้อม package.json และ package-lock.json'], [3, 'สคริปต์ติดตั้งฐานข้อมูล', '`setup.php`, `docker/mariadb/init-wms2.sql`', 'Git'], [4, 'ชุดติดตั้ง Docker Compose', '`docker-compose.yml`, `docker/`, `.env.example`', 'Git (ไม่รวมค่าความลับ)'], [5, 'ค่าตั้งค่าและความลับของสภาพแวดล้อม', '`.env`, `app/config.php` บนเครื่องที่ติดตั้ง', 'ไม่ควบคุมใน Git สร้างขึ้นตอนติดตั้งด้วย `docker/init-env.sh`'], [6, 'เอกสาร Work Products', '`sdlc/`', 'Git (Markdown ต้นฉบับ)'], [7, 'ชุดเอกสารส่งมอบ (PDF)', '`sdlc-delivery/`', 'Git สร้างจาก `sdlc/` ด้วย `scripts/build-sdlc-delivery.sh`'], ], ['c']), '## การควบคุมเวอร์ชันและการเข้าถึง', table(['หัวข้อ', 'รายละเอียด'], [ ['Repository หลัก (origin)', `\`${ORIGIN}\``], ['Repository สำรอง (backup)', `\`${BACKUP}\``], ['Branch หลัก', '`main` เป็น Baseline ที่ควบคุม'], ['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``], ['Tag ของชุดเอกสารส่งมอบ', `\`${TAG}\``], ['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย SSH Key ตามบทบาทของสมาชิกโครงการ'], ]), '## การเชื่อมโยงกับการเปลี่ยนแปลง', `การเปลี่ยนแปลง Configuration Item ต้องพิจารณาตามเกณฑ์ใน Change Report และการแก้ไขข้อบกพร่องต้องบันทึกใน Correction Register (${CORRECTIONS[0][0]}–${CORRECTIONS[CORRECTIONS.length - 1][0]}) โดยทุกรายการเชื่อมโยงกับ Commit ที่ตรวจสอบได้ใน Repository`, ].join('\n\n'), secretary: P.ThS, reviewer: P.ApS, approver: P.SeV, }), }; } /** 9. Project Repository. */ export function projectRepository() { const date = '2026-08-17'; return { dir: '1-PM Process (10 Work Product)/9.Project Repository', name: `${CODE} Project Repository ${bud(date)} V1.0 ThS`, md: workProduct({ type: 'Project Repository', footer: 'PjR', rows: head('Project Repository', `${bud(date)} V1.0 ThS`, [ ['Title', 'พื้นที่เก็บข้อมูล เอกสาร และหลักฐานทุกอย่างของโครงการ'], ['ระยะเวลา', thRangeLong(START, END)], ['Organizer', ORGANIZER_PM], ['Recorder', RECORDER], ]), body: [ '## ข้อมูล Repository', table(['หัวข้อ', 'รายละเอียด'], [ ['ประเภท', 'Git Server'], ['Repository หลัก (origin)', `\`${ORIGIN}\``], ['Branch หลัก', '`main`'], ['Branch เอกสารส่งมอบ', '`sdlc`'], ['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``], ['Tag ของชุดเอกสารส่งมอบ', `\`${TAG}\``], ['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย SSH Key ตามบทบาทของสมาชิกโครงการ'], ]), '## โครงสร้างการจัดเก็บ', table(['ลำดับ', 'ส่วนงาน', 'ที่จัดเก็บ', 'เนื้อหา'], [ [1, 'Source Code แอปพลิเคชัน', '`app/`', 'เข้าสู่ระบบ, คลังสินค้า, ขาย, จัดซื้อ, การเงิน, บัญชี, รายงาน, บาร์โค้ด'], [2, 'บริการ Real-time และงานตามกำหนดเวลา', '`nodejs/`', 'Socket.IO และงานสรุปยอด/แจ้งเตือน'], [3, 'การติดตั้งและตั้งค่า', '`docker/`, `docker-compose.yml`, `setup.php`, `.env.example`', 'ชุดติดตั้ง Container และสคริปต์สร้างฐานข้อมูล'], [4, 'ข้อมูลสาธิต', '`demo_seed*.php`', 'ชุดข้อมูลตัวอย่างสำหรับตรวจรับและสาธิต'], [5, 'เอกสาร Work Products', '`sdlc/`', 'เอกสาร ISO/IEC 29110 กระบวนการ PM และ SI'], [6, 'ชุดเอกสารส่งมอบ', '`sdlc-delivery/`', 'ไฟล์ PDF ที่สร้างจาก `sdlc/`'], [7, 'สคริปต์สนับสนุนโครงการ', '`scripts/`', 'สคริปต์สร้างและตรวจสอบชุดเอกสารส่งมอบ'], ], ['c']), '## การควบคุมการเข้าถึงและการจัดเก็บ', bullets([ 'สมาชิกโครงการทุกคนเข้าถึง Repository ได้ผ่าน Git Server ตามสิทธิ์ของบทบาท', 'Project Manager และ System Analyst มีสิทธิ์อนุมัติการ Merge เข้าสู่ Branch หลัก', 'ไฟล์ที่เกี่ยวข้องกับโครงการทั้งหมดต้องจัดเก็บใน Repository นี้', 'ค่าตั้งค่าและความลับของสภาพแวดล้อมต้องไม่ถูกนำเข้า Repository', ]), '## หลักฐานการจัดเก็บ', table(['ลำดับ', 'หลักฐาน', 'รายละเอียด'], [ [1, 'โครงสร้าง Repository และประวัติการเปลี่ยนแปลง', 'ตรวจสอบได้จาก Git Server ตามสิทธิ์ที่ได้รับ'], [2, 'Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\` บน branch \`main\``], [3, 'ชุดเอกสารส่งมอบที่ควบคุมแล้ว', `Branch \`sdlc\` และ Tag \`${TAG}\``], ], ['c']), ].join('\n\n'), secretary: P.ThS, reviewer: P.ApS, approver: P.SeV, }), }; } /** 10. Project Repository (Backup). */ export function projectRepositoryBackup() { const date = '2026-08-24'; return { dir: '1-PM Process (10 Work Product)/10.Project Repository (Backup)', name: `${CODE} Project Repository Backup ${bud(date)} V1.0 ThS`, md: workProduct({ type: 'Project Repository (Backup)', footer: 'PjB', rows: head('Project Repository (Backup)', `${bud(date)} V1.0 ThS`, [ ['Title', 'พื้นที่สำรองสำหรับใช้กู้คืนข้อมูล Project Repository ในกรณีที่เกิดการสูญหาย'], ['ระยะเวลา', thRangeLong(START, END)], ['Organizer', ORGANIZER_PM], ['Recorder', RECORDER], ]), body: [ '## การสำรอง Source Code และเอกสาร', table(['หัวข้อ', 'รายละเอียด'], [ ['วิธีการสำรอง', 'Git Remote สำรอง (Secondary Remote)'], ['ชื่อ Remote สำรอง', '`backup`'], ['ที่อยู่ Remote สำรอง', `\`${BACKUP}\``], ['ขอบเขตการสำรอง', `Branch \`main\` (Baseline \`${BASELINE_COMMIT}\`), Branch \`sdlc\` และ Tag \`${TAG}\``], ['รอบการสำรอง', 'ซิงก์ทุกครั้งที่มีการส่งมอบหรือปรับปรุง Baseline'], ['ผู้รับผิดชอบ', `${P.ThS.th} (Developer)`], ]), '## การสำรองฐานข้อมูล', table(['หัวข้อ', 'รายละเอียด'], [ ['วิธีการสำรอง', 'Export ฐานข้อมูล `wms` และ `wms2` อัตโนมัติ'], ['รอบการสำรอง', 'ทุกวัน เวลา 02:00 น. (Asia/Bangkok)'], ['ที่จัดเก็บ', 'พื้นที่จัดเก็บบนคลาวด์ที่บริษัทควบคุม'], ['ระยะเวลาเก็บรักษา', 'ชุดสำรองรายวันเก็บ 30 วัน และชุดสิ้นเดือนเก็บ 12 เดือน'], ['สิทธิ์การเข้าถึง', 'จำกัดเฉพาะผู้ดูแลระบบและผู้บริหารที่ได้รับมอบหมาย'], ]), '## รายการตรวจสอบการสำรองข้อมูล', table(['ลำดับ', 'รายการตรวจสอบ', 'ผู้รับผิดชอบ', 'วันที่ดำเนินการ', 'ผลการตรวจสอบ'], [ ['BK-001', `ตรวจสอบว่า Remote สำรองเข้าถึงได้และมี Baseline \`${BASELINE_COMMIT}\` ตรงกับ Repository หลัก`, P.ThS.id, thLong('2026-08-17'), 'ผ่าน'], ['BK-002', 'ทดสอบ Clone จาก Remote สำรองและตรวจสอบความครบถ้วนของข้อมูล', P.ThS.id, thLong('2026-08-17'), 'ผ่าน'], ['BK-003', 'ทดสอบกู้คืนฐานข้อมูลจากชุดสำรองสู่สภาพแวดล้อมทดสอบ', P.ThS.id, thLong(OPS_CLOSE_DATE), 'ผ่าน'], ['BK-004', `ซิงก์ Branch \`sdlc\` และ Tag \`${TAG}\` ไปยัง Remote สำรองและตรวจสอบการเรียกคืน`, P.ThS.id, thLong(END), 'ผ่าน'], ], ['c', '', 'c', 'c', 'c']), '## ขั้นตอนการกู้คืน', table(['ลำดับ', 'ขั้นตอน', 'รายละเอียด'], [ [1, 'ยืนยันขอบเขตความเสียหาย', 'ตรวจสอบว่าข้อมูลที่สูญหายเป็น Source Code เอกสาร หรือฐานข้อมูล'], [2, 'เลือกชุดสำรองที่จะใช้', 'เลือกชุดสำรองล่าสุดที่ผ่านการตรวจสอบและยืนยันวันที่'], [3, 'กู้คืนสู่สภาพแวดล้อมทดสอบก่อน', 'Clone จาก Remote สำรอง หรือ Restore ฐานข้อมูลสู่เครื่องทดสอบ'], [4, 'ตรวจสอบความถูกต้อง', 'ตรวจสอบ Baseline, Tag และข้อมูลตัวอย่างว่าครบถ้วน'], [5, 'กู้คืนสู่สภาพแวดล้อมใช้งานจริง', 'บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืน แล้วตรวจสอบสถานะระบบตามคู่มือผู้ดูแลระบบ'], ], ['c']), ].join('\n\n'), secretary: P.ThS, reviewer: P.ApS, approver: P.SeV, }), }; }