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

396 lines
37 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.
// 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, 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[0] === 'WP 9.0' ? `${w[2]} (Git baseline \`${BASELINE_COMMIT}\`)` : 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}\``],
['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย 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`'],
['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``],
['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย 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, 'ชุดเอกสารส่งมอบที่ควบคุมแล้ว', 'Git Repository'],
], ['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}\`) และเอกสารส่งมอบใน Git Repository`],
['รอบการสำรอง', 'ซิงก์ทุกครั้งที่มีการส่งมอบหรือปรับปรุง 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', 'ซิงก์เอกสารส่งมอบใน Git Repository ไปยัง Remote สำรองและตรวจสอบการเรียกคืน', P.ThS.id, thLong(END), 'ผ่าน'],
], ['c', '', 'c', 'c', 'c']),
'## ขั้นตอนการกู้คืน',
table(['ลำดับ', 'ขั้นตอน', 'รายละเอียด'], [
[1, 'ยืนยันขอบเขตความเสียหาย', 'ตรวจสอบว่าข้อมูลที่สูญหายเป็น Source Code เอกสาร หรือฐานข้อมูล'],
[2, 'เลือกชุดสำรองที่จะใช้', 'เลือกชุดสำรองล่าสุดที่ผ่านการตรวจสอบและยืนยันวันที่'],
[3, 'กู้คืนสู่สภาพแวดล้อมทดสอบก่อน', 'Clone จาก Remote สำรอง หรือ Restore ฐานข้อมูลสู่เครื่องทดสอบ'],
[4, 'ตรวจสอบความถูกต้อง', 'ตรวจสอบ Baseline และข้อมูลตัวอย่างว่าครบถ้วน'],
[5, 'กู้คืนสู่สภาพแวดล้อมใช้งานจริง', 'บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืน แล้วตรวจสอบสถานะระบบตามคู่มือผู้ดูแลระบบ'],
], ['c']),
].join('\n\n'),
secretary: P.ThS, reviewer: P.ApS, approver: P.SeV,
}),
};
}