Compare commits

12 Commits
Author SHA1 Message Date
Thanakorn 53cd5c36e0 docs(sdlc): expand weak points in audit prep note 2026-10-02 08:23:11 +07:00
Thanakorn 46f334fe96 chore(sdlc): stop tracking sdlc-delivery.zip 2026-10-02 08:22:16 +07:00
Thanakorn 13ac9958f3 docs(sdlc): reword Change Report row in List of Evidence; re-render delivery 2026-10-02 08:19:52 +07:00
Thanakorn 073d8f8f37 docs(sdlc): drop advisor feedback response from audit prep note 2026-10-02 08:12:51 +07:00
Thanakorn 2def1ef921 docs(sdlc): add ISO 29110 audit prep note; keep top-level sdlc notes out of seed and render 2026-10-02 08:05:10 +07:00
Thanakorn 4f1c2a0ad1 docs(sdlc): merge group heading rows across the full table width 2026-09-28 13:53:21 +07:00
Thanakorn 31f2629ddf docs(sdlc): cite only main and commits up to project end; guard enforces it 2026-09-25 15:46:19 +07:00
Thanakorn 7b5cc192d9 docs(sdlc): drop perfective maintenance; say Git Repository, not branch sdlc 2026-09-25 15:34:08 +07:00
Thanakorn fe9c7e612f build(sdlc): convert the rendered PDFs to Word in build-sdlc-delivery.sh
Uses local pdf2docx when present, else a throwaway python container.
2026-09-25 15:21:55 +07:00
ThanakornandClaude Opus 5.5 6c857005c0 docs(sdlc): write each work product as of its own date
- SOW, plan and design no longer cite the Aug baseline commit or the package tag
- Work Schedule status derived from its date; Test Case has no results yet
- build fails when a document cites a later commit, tag, ISS or ID (timeline.mjs)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:52:21 +07:00
Thanakorn be020e332b build(sdlc): seed data, figures and rendered package for 23/09 feedback 2026-09-24 09:19:05 +07:00
Thanakorn 6c1d66d8de docs(sdlc): update work products for 23/09 review feedback 2026-09-24 09:19:05 +07:00
154 changed files with 477 additions and 296 deletions
+3
View File
@@ -16,3 +16,6 @@ lib/zxcvbn-php-master/vendor/sebastian/
notes/
docs/
SESSION.php
# SDLC delivery zip is packaged by hand, outside the repo
sdlc-delivery.zip
+29 -1
View File
@@ -32,7 +32,35 @@ while IFS= read -r -d '' source; do
destination="$STAGING_DIR/${relative%.md}.pdf"
echo "Rendering ${relative%.md}.pdf"
node "$TOOL_DIR/render-sdlc.mjs" "$source" "$destination"
done < <(find "$SOURCE_DIR" -type f -name '*.md' -print0 | sort -z)
done < <(find "$SOURCE_DIR" -mindepth 2 -type f -name '*.md' -print0 | sort -z)
# Every .pdf gets a Word copy beside it, reconstructed from the rendered page.
# The CI header bands need Playwright, so they are rendered here with node and
# handed to the converter, which then only needs Python with pdf2docx: the local
# interpreter ($PYTHON) when it has it, otherwise a throwaway python container.
BAND_DIR="$(mktemp -d)"
trap 'rm -rf "$BAND_DIR"' EXIT
echo "Rendering CI header bands..."
node "$TOOL_DIR/render-ci-band.mjs" "$SOURCE_DIR" "$BAND_DIR"
echo "Converting PDFs to Word..."
PYTHON="${PYTHON:-python3}"
if "$PYTHON" -c 'import pdf2docx, docx' 2>/dev/null; then
"$PYTHON" "$TOOL_DIR/convert-to-docx.py" "$SOURCE_DIR" "$STAGING_DIR" "$BAND_DIR"
elif command -v docker >/dev/null 2>&1; then
PY_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/sdlc-delivery-python"
mkdir -p "$PY_CACHE"
docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp -e PYTHONPATH=/py \
-v "$ROOT_DIR:$ROOT_DIR" -v "$BAND_DIR:$BAND_DIR:ro" -v "$PY_CACHE:/py" \
-w "$ROOT_DIR" python:3.12-slim sh -c '
python -c "import pdf2docx, docx" 2>/dev/null ||
pip install --quiet --disable-pip-version-check --target /py -r "$1"
shift; python "$@"' _ \
"$TOOL_DIR/requirements.txt" "$TOOL_DIR/convert-to-docx.py" "$SOURCE_DIR" "$STAGING_DIR" "$BAND_DIR"
else
echo "pdf2docx is not available: pip install -r $TOOL_DIR/requirements.txt, or install docker." >&2
exit 1
fi
echo "Verifying staged delivery package..."
"$SCRIPT_DIR/verify-sdlc-delivery.sh" "$STAGING_DIR"
@@ -104,6 +104,7 @@ table:not(.document-control) td.a-center { text-align: center; }
taking an equal share of the table width. */
table:not(.document-control) th.tight,
table:not(.document-control) td.tight { white-space: nowrap; width: 1%; }
table:not(.document-control) tr.group td { background: var(--brn-tint); font-weight: 600; }
thead { display: table-header-group; }
tr { break-inside: avoid; }
+4 -3
View File
@@ -18,12 +18,13 @@ const SCALE = 3; // the band is drawn at 3x so it stays crisp in print
const applyTemplate = (template, values) => template.replace(/{{(\w+)}}/g, (_, key) => values[key] ?? '');
const slug = (title) => title.replace(/[^\w-]+/g, '-').replace(/^-|-$/g, '').toLowerCase() || 'band';
async function markdownFiles(directory) {
// Work products sit in the process folders; top-level files in sdlc/ are notes.
async function markdownFiles(directory, depth = 0) {
const entries = await fs.readdir(directory, { withFileTypes: true });
const results = await Promise.all(entries.map(async (entry) => {
const target = path.join(directory, entry.name);
if (entry.isDirectory()) return markdownFiles(target);
return entry.isFile() && entry.name.endsWith('.md') ? [target] : [];
if (entry.isDirectory()) return markdownFiles(target, depth + 1);
return depth > 0 && entry.isFile() && entry.name.endsWith('.md') ? [target] : [];
}));
return results.flat();
}
+9 -2
View File
@@ -71,13 +71,18 @@ function renderTable(lines, className = '') {
const hasSeparator = isSeparator(rows[1] ?? []);
const align = hasSeparator ? columnAlignment(rows[1]) : header.map(() => '');
const data = hasSeparator ? rows.slice(2) : rows.slice(1);
// A row with only its first cell filled is a group heading (CR01, SR06, UN04,
// WP 1.0 …): it spans the full width and does not size the columns. Signature
// tables keep their blank cells for signing.
const isGroup = (row) => className !== 'signature-table' && header.length >= 3
&& Boolean(row[0]) && row.slice(1).every((cell) => !cell);
// A column whose longest cell is short (IDs, dates, counts, statuses) is
// marked "tight" so auto table layout shrinks it to its content instead of
// handing every column an equal share of the page width.
const widest = header.map((_, column) => Math.max(
(header[column] ?? '').length,
...data.map((row) => (row[column] ?? '').replace(/[`*]/g, '').length)
...data.filter((row) => !isGroup(row)).map((row) => (row[column] ?? '').replace(/[`*]/g, '').length)
));
const classFor = (column) => [
align[column] ?? '',
@@ -89,7 +94,9 @@ function renderTable(lines, className = '') {
return `<${tag}${attr ? ` class="${attr}"` : ''}>${renderInline(cell)}</${tag}>`;
}).join('');
const classes = [className, header.length >= 8 ? 'dense' : ''].filter(Boolean).join(' ');
return `<table${classes ? ` class="${classes}"` : ''}><thead><tr>${cells('th', header)}</tr></thead><tbody>${data.map((row) => `<tr>${cells('td', row)}</tr>`).join('')}</tbody></table>`;
return `<table${classes ? ` class="${classes}"` : ''}><thead><tr>${cells('th', header)}</tr></thead><tbody>${data.map((row) => isGroup(row)
? `<tr class="group"><td colspan="${header.length}">${renderInline(row[0])}</td></tr>`
: `<tr>${cells('td', row)}</tr>`).join('')}</tbody></table>`;
}
function renderParagraph(lines) {
+4 -3
View File
@@ -26,12 +26,13 @@ function compact(text) {
return text.normalize('NFC').toLocaleLowerCase().replace(/[^\p{L}\p{N}]/gu, '');
}
async function markdownFiles(directory) {
// Work products sit in the process folders; top-level files in sdlc/ are notes.
async function markdownFiles(directory, depth = 0) {
const entries = await fs.readdir(directory, { withFileTypes: true });
const results = await Promise.all(entries.map(async (entry) => {
const target = path.join(directory, entry.name);
if (entry.isDirectory()) return markdownFiles(target);
return entry.isFile() && entry.name.endsWith('.md') ? [target] : [];
if (entry.isDirectory()) return markdownFiles(target, depth + 1);
return depth > 0 && entry.isFile() && entry.name.endsWith('.md') ? [target] : [];
}));
return results.flat();
}
+27 -1
View File
@@ -18,15 +18,41 @@ Approval signature tables on every document.
| `format.mjs` | The shared work-product scaffold |
| `figures.mjs` | Software Design diagrams and wireframes, drawn as SVG; `scripts/sdlc-delivery/render-figures.mjs` rasterises them into `figures/*.png` |
| `docs-*.mjs` | One generator per work product |
| `timeline.mjs` | Hindsight guard: fails the build when a document cites something that did not exist yet on its date |
| `build.mjs` | Writes all documents and checks no folder is left empty |
## Usage
```bash
node scripts/sdlc-seed/build.mjs # regenerate sdlc/ only
bash scripts/build-sdlc-delivery.sh # seed, render to PDF, then verify
bash scripts/build-sdlc-delivery.sh # seed, render to PDF and Word, then verify
```
The traceability chain is validated at import time: every Customer Requirement
must resolve to existing Software Requirements, Software Units, and a Test Case,
or the build fails.
## Write each document as of its own date
Every work product is dated (`... 25690105 V1.0 ...`) and may only state what was
known on that day. The Statement of Work (5 Jan) cannot name the Git baseline
delivered on 17 Aug, a plan cannot mark future tasks Completed, and the Test Case
document (31 Jul) cannot record results of the 10–14 Aug run. Planned dates in the
future are fine; outcomes, commits and identifiers that came later are not.
`build.mjs` enforces the checkable part through `timeline.mjs` and stops with a
list of offending documents when a work product cites:
- a Git commit created after the document date or after the project end
(`END`), or one that is not on branch `main` (dates read from `git log`);
- any branch other than `main` (the documents describe the delivered software,
which lives on `main` only), or a document dated after `END`;
- the delivery tag `TAG` before the tag was created;
- an `ISS-nnn` before its detection date in the Correction Register;
- CR, SR, Unit or Test Case IDs before the work product that defines them.
Prose is not checked automatically. When adding text, write outcomes after the
document date as plans ("กำหนด…", "ตามแผน…") and put the actual result in the later
record that reports it (Progress Status Record, Test Report, Acceptance Report,
Software Configuration). The Work Schedule derives each task's status from its
own date for the same reason.
+7 -1
View File
@@ -8,6 +8,7 @@ import * as pm2 from './docs-pm2.mjs';
import * as si from './docs-si.mjs';
import * as si2 from './docs-si2.mjs';
import * as other from './docs-other.mjs';
import { hindsightErrors } from './timeline.mjs';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const target = path.join(root, 'sdlc');
@@ -70,7 +71,12 @@ async function main() {
if (!FOLDERS.includes(d.dir)) throw new Error(`Unknown folder for ${d.name}: ${d.dir}`);
}
await fs.rm(target, { recursive: true, force: true });
const hindsight = hindsightErrors(docs, root);
if (hindsight.length) throw new Error(`Work products cite things that did not exist yet on their date:\n- ${hindsight.join('\n- ')}`);
// Only the process folders are generated; files at the top of sdlc/ are
// working notes (e.g. the audit prep) and are neither seeded nor rendered.
for (const folder of FOLDERS) await fs.rm(path.join(target, folder), { recursive: true, force: true });
for (const folder of FOLDERS) await fs.mkdir(path.join(target, folder), { recursive: true });
for (const d of docs) {
await fs.writeFile(path.join(target, d.dir, `${d.name}.md`), d.md, 'utf8');
+6 -6
View File
@@ -1,5 +1,5 @@
// 3-Other Document: Stakeholder Register, Project Charter, Traceability Record Table, Training Report, List of Evidence.
import { P, TEAM, CODE, PROJECT_TH, COMPANY_TH, ORGANIZER_PM, RECORDER, START, END, SCOPE, CRS, TRAINING_DATE, ACCEPT_DATE, TEST_FROM, TEST_TO, BASELINE_COMMIT, ORIGIN, BACKUP, TAG, DEV_START } from './project.mjs';
import { P, TEAM, CODE, PROJECT_TH, COMPANY_TH, ORGANIZER_PM, RECORDER, START, END, SCOPE, CRS, TRAINING_DATE, ACCEPT_DATE, TEST_FROM, TEST_TO, BASELINE_COMMIT, ORIGIN, BACKUP, DEV_START } from './project.mjs';
import { SRS, UNITS, TEST_CASES, traceOf, srTopic, unitTopic } from './engineering.mjs';
import { CORRECTIONS, PERIODS, MEETINGS, TASKS } from './history.mjs';
import { bud, thLong, thRangeLong, table, bullets, numbered, daysBetween } from './lib.mjs';
@@ -104,8 +104,8 @@ export function projectCharter() {
['อนุมัติกฎบัตรโครงการ', thLong('2026-01-23'), 'Project Sponsor อนุมัติขอบเขตและวัตถุประสงค์'],
['ตั้ง Baseline ความต้องการ', thLong('2026-02-18'), 'อนุมัติ Customer Requirements และแผนงาน'],
['Baseline การพัฒนา', thLong('2026-05-29'), 'พัฒนาครบทุกโมดูลตามขอบเขต'],
['ทดสอบระบบและ UAT', thRangeLong(TEST_FROM, TEST_TO), 'ผลการทดสอบผ่านครบทุกรายการ'],
['ตรวจรับส่งมอบ', thLong(ACCEPT_DATE), 'ผลการตรวจรับ Accepted'],
['ทดสอบระบบและ UAT', thRangeLong(TEST_FROM, TEST_TO), 'Test Case และ UAT ผ่านครบทุกรายการ'],
['ตรวจรับส่งมอบ', thLong(ACCEPT_DATE), 'Project Sponsor ลงนามตรวจรับสิ่งส่งมอบ'],
['ปิดโครงการ', thLong(END), 'ปิดโครงการอย่างเป็นทางการ'],
], ['', 'c', '']),
].join('\n\n'),
@@ -234,7 +234,7 @@ export function listOfEvidence(inventory) {
[3, `Progress Status Record (${files('3.Progress Status Record').length} Report)`, multi('3.Progress Status Record'), 'บันทึกความก้าวหน้าของโครงการรายงวด'],
[4, 'Correction Register', one('4.Correction Register'), 'เอกสารสรุปปัญหาที่พบระหว่างดำเนินโครงการ'],
[5, 'Acceptance Record', one('5.Acceptance Report'), 'บันทึกการส่งมอบงาน (Sign off Letter)'],
[6, `Change Requests (${files('6.Change Report').length} ฉบับ)`, multi('6.Change Report'), 'บันทึกขอการเปลี่ยนแปลงความต้องการ'],
[6, 'Change Request (Change Report)', multi('6.Change Report'), 'เกณฑ์และทะเบียนการพิจารณาคำขอเปลี่ยนแปลง (ไม่มีคำขอที่เข้าเกณฑ์ตลอดโครงการ)'],
[7, `Meeting Record (${files('7.Meeting Record').length} MoM Report)`, multi('7.Meeting Record'), 'รายงานการประชุมตามแผนการทำงานใน Work Schedule'],
[8, 'Software Configuration', one('8.Software Configuration'), 'บันทึกการกำหนดค่าและความเปลี่ยนแปลงต่าง ๆ ที่เกิดขึ้นของระบบ'],
[9, 'Project Repository', one('9.Project Repository'), `พื้นที่เก็บข้อมูล เอกสาร และหลักฐานทุกอย่างของโครงการ (Git Server: \`${ORIGIN}\`)`],
@@ -248,7 +248,7 @@ export function listOfEvidence(inventory) {
[14, 'Software Components', one('14.Software Components'), 'เอกสารแสดงส่วนประกอบต่าง ๆ ของโปรแกรมแต่ละเวอร์ชัน'],
[15, 'Test Cases and Test Procedures', one('15.Test Cases'), 'เอกสารแสดงตัวอย่างชุดข้อมูลที่ใช้ทดสอบ'],
[16, 'Test Report', one('16.Test Report'), 'บันทึกผลการทดสอบระบบ'],
[17, 'Software', one('17.Software'), `ซอฟต์แวร์สำหรับส่งมอบให้กับผู้ใช้งาน (Baseline \`${BASELINE_COMMIT}\`, Tag \`${TAG}\`)`],
[17, 'Software', one('17.Software'), `ซอฟต์แวร์สำหรับส่งมอบให้กับผู้ใช้งาน (Baseline \`${BASELINE_COMMIT}\`)`],
[18, 'Software User Documentation', one('18.Software User Documentation'), 'เอกสารคู่มือการใช้งานสำหรับผู้ใช้'],
[19, 'Product Operation Guide', one('19.Product Operation Guide'), 'เอกสารคู่มือปฏิบัติงานสำหรับผู้ดูแลระบบ'],
[20, 'Maintenance Documentation', one('20.Maintenance Documentation'), 'เอกสารคู่มือการบำรุงรักษาระบบ'],
@@ -272,7 +272,7 @@ export function listOfEvidence(inventory) {
['เอกสารที่จัดเตรียมครบถ้วน', inventory.length],
['เอกสารที่ยังไม่ได้จัดเตรียม', 0],
], ['', 'r']),
`เอกสารและหลักฐานทั้งหมดจัดเก็บใน Project Repository และสำรองไว้ตามที่ระบุในเอกสาร Project Repository (Backup) โดยชุดเอกสารส่งมอบควบคุมด้วย Tag \`${TAG}\``,
`เอกสารและหลักฐานทั้งหมดจัดเก็บใน Project Repository และสำรองไว้ตามที่ระบุในเอกสาร Project Repository (Backup) โดยชุดเอกสารส่งมอบควบคุมใน Git Repository`,
].join('\n\n'),
secretary: P.YaB, reviewer: P.ApS, approver: P.SeV,
}),
+16 -13
View File
@@ -71,7 +71,10 @@ export function statementOfWork() {
};
}
/** 2.1 Work Schedule — landscape task table with milestones. */
/** Task status as known on `date`: the plan cannot report work that has not happened yet. */
const statusOn = (date, start, finish) => (finish <= date ? 'Completed' : start <= date ? 'In Progress' : 'Not Started');
/** 2.1 Work Schedule — landscape task table with milestones, as planned on its issue date. */
export function workSchedule() {
const date = '2026-02-13';
return {
@@ -88,7 +91,7 @@ export function workSchedule() {
body: [
'## แผนการดำเนินงาน (Work Schedule)',
table(['No.', 'Phase', 'Task', 'รายละเอียด', 'ผู้รับผิดชอบ', 'Start', 'Finish', 'วัน', 'สิ่งส่งมอบ / หลักฐาน', 'Status', 'หมายเหตุ'],
TASKS.map((t) => [t[0], t[1], t[2], t[3], resp(t[4]), thShort(t[5]), thShort(t[6]), daysBetween(t[5], t[6]), t[7], t[8], t[9]]),
TASKS.map((t) => { const s = statusOn(date, t[5], t[6]); return [t[0], t[1], t[2], t[3], resp(t[4]), thShort(t[5]), thShort(t[6]), daysBetween(t[5], t[6]), t[7], s, s === 'Completed' ? t[9] : '-']; }),
['c', '', '', '', 'c', 'r', 'r', 'r', '', 'c', '']),
'## หลักไมล์ของโครงการ (Milestones)',
table(['หลักไมล์', 'วันที่', 'เกณฑ์การบรรลุ'], [
@@ -96,10 +99,10 @@ export function workSchedule() {
['อนุมัติกฎบัตรโครงการ', thLong('2026-01-23'), 'Project Sponsor อนุมัติ Project Charter'],
['ตั้ง Baseline ความต้องการและแผนงาน', thLong('2026-02-18'), 'อนุมัติ Customer Requirements และ Work Schedule'],
['เริ่มพัฒนาระบบ', thLong(DEV_START), 'เริ่ม Task 3.1 ตามแผน'],
['Baseline การพัฒนา', thLong('2026-05-29'), 'พัฒนาครบทุกโมดูล (Git a0677d6)'],
['ทดสอบระบบและ UAT', thRangeLong(TEST_FROM, TEST_TO), `ทดสอบ 45 Test Case และ UAT ครบ 14 หมวดความต้องการ (${CRS.length} รายการ) ผ่านทั้งหมด`],
['ตรวจรับส่งมอบระบบ', thLong('2026-08-17'), 'ผลการตรวจรับ Accepted'],
['อบรมผู้ใช้งาน', thLong('2026-08-22'), 'อบรมผู้ใช้งาน 6 คน'],
['Baseline การพัฒนา', thLong('2026-05-29'), 'พัฒนาครบทุกโมดูลตามขอบเขต และกำหนด Git Baseline การพัฒนา'],
['ทดสอบระบบและ UAT', thRangeLong(TEST_FROM, TEST_TO), `Test Case ผ่านครบ และ UAT ผ่านครบ 14 หมวดความต้องการ (${CRS.length} รายการ)`],
['ตรวจรับส่งมอบระบบ', thLong('2026-08-17'), 'Project Sponsor ลงนามตรวจรับสิ่งส่งมอบครบทุกรายการ'],
['อบรมผู้ใช้งาน', thLong('2026-08-22'), 'ผู้ใช้งานทุกฝ่ายที่เกี่ยวข้องผ่านการอบรม'],
['ปิดโครงการ', thLong(END), 'ปิดโครงการอย่างเป็นทางการ'],
], ['', 'c', '']),
'## หมายเหตุการติดตามแผนงาน',
@@ -165,15 +168,15 @@ export function softwareProjectPlan() {
'**ด้านคุณภาพของระบบ**',
table(['ลำดับ', 'หัวข้อ', 'เกณฑ์คุณภาพ', 'วิธีประเมิน'], [
['1.1', 'ความถูกต้องของฟังก์ชัน', 'ฟังก์ชันทั้งหมดทำงานตรงตาม Customer Requirements ครบ 100%', 'ทดสอบตาม Test Case และ UAT'],
['1.2', 'ความถูกต้องของข้อมูลสต๊อกและบัญชี', 'ไม่พบยอดสต๊อกติดลบหรือรายการ GL ที่ไม่สมดุล', 'ทดสอบ Transaction และ Rollback (TC-UN08.002)'],
['1.3', 'ความปลอดภัยของระบบ', 'ไม่พบข้อบกพร่องระดับวิกฤตด้านสิทธิ์และการแยกข้อมูล', 'ทดสอบสิทธิ์เชิงลบ (TC-UN12.001, TC-UN12.002)'],
['1.4', 'ประสิทธิภาพการตอบสนอง', 'งานประจำวันตอบสนองภายในเวลาที่ใช้งานได้จริงบนสภาพแวดล้อมที่ตกลง', 'ทดสอบตามปริมาณข้อมูลตัวอย่าง (TC-UN09.001)'],
['1.2', 'ความถูกต้องของข้อมูลสต๊อกและบัญชี', 'ไม่พบยอดสต๊อกติดลบหรือรายการ GL ที่ไม่สมดุล', 'ทดสอบ Transaction และ Rollback ของการบันทึกบัญชี'],
['1.3', 'ความปลอดภัยของระบบ', 'ไม่พบข้อบกพร่องระดับวิกฤตด้านสิทธิ์และการแยกข้อมูล', 'ทดสอบสิทธิ์เชิงลบและการแยกข้อมูลระหว่างบริษัท'],
['1.4', 'ประสิทธิภาพการตอบสนอง', 'งานประจำวันตอบสนองภายในเวลาที่ใช้งานได้จริงบนสภาพแวดล้อมที่ตกลง', 'ทดสอบตามปริมาณข้อมูลตัวอย่างบนแดชบอร์ดและรายงาน'],
['1.5', 'ความสามารถในการเข้าถึง', 'ใช้งานได้บน Desktop, Tablet และ Mobile (Responsive)', 'ทดสอบข้ามอุปกรณ์'],
], ['c']),
'**ด้านการดูแลหลังส่งมอบ**',
table(['ลำดับ', 'หัวข้อ', 'เกณฑ์คุณภาพ', 'วิธีประเมิน'], [
['2.1', 'การตอบสนองเหตุขัดข้อง', 'ตอบรับภายใน 4 ชั่วโมง และแก้ไขตามระดับความรุนแรงที่กำหนดใน SLA', 'บันทึกการให้บริการ'],
['2.2', 'การสำรองข้อมูล', 'มีระบบสำรองข้อมูลอัตโนมัติรายวันและทดสอบกู้คืนได้จริง', 'Backup & Restore Test (TC-UN13.004)'],
['2.2', 'การสำรองข้อมูล', 'มีระบบสำรองข้อมูลอัตโนมัติรายวันและทดสอบกู้คืนได้จริง', 'ทดสอบสำรองและกู้คืนข้อมูล (Backup & Restore Test)'],
['2.3', 'การเฝ้าระวังระบบ', 'ตรวจสอบสถานะบริการทุก 5 นาที และแจ้งเตือนเมื่อล้มเหลว 2 ครั้งติดกัน', 'บันทึกการเฝ้าระวัง'],
], ['c']),
sec(4, 'Software Development Life Cycle Methodology'),
@@ -208,9 +211,9 @@ export function softwareProjectPlan() {
[1, 'เอกสารบริหารโครงการ (PM Process)', '10 รายการ'],
[2, 'เอกสารกระบวนการพัฒนา (SI Process)', '12 รายการ'],
[3, 'ความต้องการลูกค้า (Customer Requirements)', `${CRS.length} รายการ (CR01–CR14)`],
[4, 'ความต้องการซอฟต์แวร์ (SRS)', '49 รายการ (SR01–SR09)'],
[5, 'Software Unit', '45 หน่วย (UN01–UN13)'],
[6, 'Test Case', '45 รายการ'],
[4, 'ความต้องการซอฟต์แวร์ (SRS)', 'ประมาณ 50 รายการ แบ่งตามกลุ่มระบบในขอบเขตงาน'],
[5, 'Software Unit', 'ประมาณ 45 หน่วย ใน 13 กลุ่มโมดูล'],
[6, 'Test Case', 'อย่างน้อย 1 รายการต่อ Software Unit'],
[7, 'ตารางฐานข้อมูล', 'ประมาณ 50 ตาราง ใน 2 ฐานข้อมูล'],
], ['c']),
'### 6.2 Effort Man-Day',
+7 -10
View File
@@ -1,6 +1,6 @@
// 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 { 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';
@@ -115,7 +115,7 @@ export function acceptanceReport() {
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']),
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', 'ผ่าน'],
@@ -134,7 +134,7 @@ export function acceptanceReport() {
['ข้อบกพร่องคงค้าง', 'ไม่มี'],
['ผลการตรวจรับโดยรวม', '**Accepted** — ตรวจรับส่งมอบระบบครบถ้วน'],
]),
`ผู้รับมอบได้ตรวจสอบสิ่งส่งมอบทั้งหมดตามเกณฑ์การตรวจรับที่กำหนดในเอกสาร Statement of Work และ Software Project Plan แล้วเห็นว่าครบถ้วนถูกต้อง จึงมีมติตรวจรับส่งมอบระบบเมื่อวันที่ ${thLong(ACCEPT_DATE)} โดยงานอบรมผู้ใช้งานและการปิดงานควบคุมปฏิบัติการดำเนินการแล้วเสร็จภายในวันที่ ${thLong(END)} ตามแผนปิดโครงการ`,
`ผู้รับมอบได้ตรวจสอบสิ่งส่งมอบทั้งหมดตามเกณฑ์การตรวจรับที่กำหนดในเอกสาร Statement of Work และ Software Project Plan แล้วเห็นว่าครบถ้วนถูกต้อง จึงมีมติตรวจรับส่งมอบระบบเมื่อวันที่ ${thLong(ACCEPT_DATE)} โดยงานอบรมผู้ใช้งานและการปิดงานควบคุมปฏิบัติการจะดำเนินการให้แล้วเสร็จภายในวันที่ ${thLong(END)} ตามแผนปิดโครงการ`,
].join('\n\n'),
secretary: P.ApS, reviewer: P.YaB, approver: P.SeV,
}),
@@ -279,7 +279,6 @@ export function softwareConfiguration() {
['Repository สำรอง (backup)', `\`${BACKUP}\``],
['Branch หลัก', '`main` เป็น Baseline ที่ควบคุม'],
['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``],
['Tag ของชุดเอกสารส่งมอบ', `\`${TAG}\``],
['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย SSH Key ตามบทบาทของสมาชิกโครงการ'],
]),
'## การเชื่อมโยงกับการเปลี่ยนแปลง',
@@ -310,9 +309,7 @@ export function projectRepository() {
['ประเภท', 'Git Server'],
['Repository หลัก (origin)', `\`${ORIGIN}\``],
['Branch หลัก', '`main`'],
['Branch เอกสารส่งมอบ', '`sdlc`'],
['Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\``],
['Tag ของชุดเอกสารส่งมอบ', `\`${TAG}\``],
['สิทธิ์การเข้าถึง', 'ยืนยันตัวตนด้วย SSH Key ตามบทบาทของสมาชิกโครงการ'],
]),
'## โครงสร้างการจัดเก็บ',
@@ -336,7 +333,7 @@ export function projectRepository() {
table(['ลำดับ', 'หลักฐาน', 'รายละเอียด'], [
[1, 'โครงสร้าง Repository และประวัติการเปลี่ยนแปลง', 'ตรวจสอบได้จาก Git Server ตามสิทธิ์ที่ได้รับ'],
[2, 'Baseline ของระบบที่ส่งมอบ', `Commit \`${BASELINE_COMMIT}\` บน branch \`main\``],
[3, 'ชุดเอกสารส่งมอบที่ควบคุมแล้ว', `Branch \`sdlc\` และ Tag \`${TAG}\``],
[3, 'ชุดเอกสารส่งมอบที่ควบคุมแล้ว', 'Git Repository'],
], ['c']),
].join('\n\n'),
secretary: P.ThS, reviewer: P.ApS, approver: P.SeV,
@@ -364,7 +361,7 @@ export function projectRepositoryBackup() {
['วิธีการสำรอง', 'Git Remote สำรอง (Secondary Remote)'],
['ชื่อ Remote สำรอง', '`backup`'],
['ที่อยู่ Remote สำรอง', `\`${BACKUP}\``],
['ขอบเขตการสำรอง', `Branch \`main\` (Baseline \`${BASELINE_COMMIT}\`), Branch \`sdlc\` และ Tag \`${TAG}\``],
['ขอบเขตการสำรอง', `Branch \`main\` (Baseline \`${BASELINE_COMMIT}\`) และเอกสารส่งมอบใน Git Repository`],
['รอบการสำรอง', 'ซิงก์ทุกครั้งที่มีการส่งมอบหรือปรับปรุง Baseline'],
['ผู้รับผิดชอบ', `${P.ThS.th} (Developer)`],
]),
@@ -381,14 +378,14 @@ export function projectRepositoryBackup() {
['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), 'ผ่าน'],
['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, Tag และข้อมูลตัวอย่างว่าครบถ้วน'],
[4, 'ตรวจสอบความถูกต้อง', 'ตรวจสอบ Baseline และข้อมูลตัวอย่างว่าครบถ้วน'],
[5, 'กู้คืนสู่สภาพแวดล้อมใช้งานจริง', 'บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืน แล้วตรวจสอบสถานะระบบตามคู่มือผู้ดูแลระบบ'],
], ['c']),
].join('\n\n'),
+20 -24
View File
@@ -1,9 +1,9 @@
// SI work products 11–22.
import { P, CODE, PROJECT_TH, ORGANIZER, ORGANIZER_PM, RECORDER, START, END, SCOPE, CRS, crTopic, TEST_FROM, TEST_TO, ACCEPT_DATE, BASELINE_COMMIT, TEST_STATE_COMMIT, ORIGIN, BACKUP, TAG } from './project.mjs';
import { P, CODE, PROJECT_TH, ORGANIZER, ORGANIZER_PM, RECORDER, START, END, SCOPE, CRS, crTopic, TEST_FROM, TEST_TO, ACCEPT_DATE, DEV_BASELINE, BASELINE_COMMIT, TEST_STATE_COMMIT, ORIGIN, BACKUP } from './project.mjs';
import { SR_GROUPS, SRS, srTopic, UNIT_GROUPS, UNITS, unitTopic, unitModule, TEST_CASES, traceOf } from './engineering.mjs';
import { CORRECTIONS } from './history.mjs';
import { figure } from './figures.mjs';
import { bud, thLong, thShort, thRangeLong, table, bullets, numbered } from './lib.mjs';
import { bud, thLong, thShort, thRange, thRangeLong, table, bullets, numbered } from './lib.mjs';
import { workProduct, head } from './format.mjs';
const D = '2026-08-17';
@@ -131,20 +131,20 @@ export function softwareDesign() {
figure('wfStockIn'),
figure('wfReport'),
'## Software Baseline',
'หัวข้อนี้ระบุสิ่งที่ถูกกำหนดเป็น Baseline ของซอฟต์แวร์ วันที่กำหนด และผู้อนุมัติ เพื่อให้ตรวจสอบเส้นทาง Baseline → Configuration → Change ได้ในเอกสารชุดเดียวกัน',
'หัวข้อนี้กำหนดสิ่งที่จะควบคุมเป็น Baseline ของซอฟต์แวร์ เวลาที่กำหนด Baseline และผู้อนุมัติ เพื่อให้ตรวจสอบเส้นทาง Baseline → Configuration → Change ได้ Commit ที่เป็น Baseline จริงจะบันทึกใน Software Configuration (WP 8) เมื่อกำหนด Baseline แต่ละครั้ง',
table(['รายการที่กำหนดเป็น Baseline', 'ค่าที่ควบคุม', 'ที่จัดเก็บ'], [
['Source Code ของแอปพลิเคชัน', `Git commit \`${BASELINE_COMMIT}\` บน branch \`main\``, `Repository หลัก \`${ORIGIN}\``],
['Source Code ของแอปพลิเคชัน', 'Git commit บน branch `main` ณ วันที่กำหนด Baseline', `Repository หลัก \`${ORIGIN}\``],
['โครงสร้างฐานข้อมูล', 'Schema ที่สร้างโดย `setup.php` และ `docker/mariadb/init-wms2.sql`', 'ควบคุมใน Repository เดียวกัน'],
['ค่าตั้งค่าและชุดติดตั้ง', '`docker-compose.yml`, `docker/`, `.env.example` (ไม่รวมค่าความลับ)', 'ควบคุมใน Repository เดียวกัน'],
['เอกสาร Work Products', `Branch \`sdlc\` และ Tag \`${TAG}\``, `Repository หลักและ Remote สำรอง \`${BACKUP}\``],
['เอกสาร Work Products', 'Git Repository', `Repository หลักและ Remote สำรอง \`${BACKUP}\``],
]),
table(['หัวข้อ', 'รายละเอียด'], [
['วันที่กำหนด Baseline', `${thLong('2026-08-17')} (Baseline ของระบบที่ส่งมอบ)`],
['เวลาที่กำหนด Baseline', `เมื่อพัฒนาครบทุกโมดูล (Baseline การพัฒนา ตามแผน ${thLong(DEV_BASELINE)}) และเมื่อตรวจรับส่งมอบ (Baseline ของระบบที่ส่งมอบ ตามแผน ${thLong(ACCEPT_DATE)})`],
['ผู้จัดทำ Baseline', `${P.ThS.th} (Developer)`],
['ผู้ตรวจสอบ', `${P.NoC.th} (System Analyst) และ ${P.PaNg.th} (QA/Tester)`],
['ผู้อนุมัติ', `${P.SeV.th} (Project Sponsor) พร้อมการตรวจรับเมื่อ ${thLong(ACCEPT_DATE)}`],
['ผู้อนุมัติ', `${P.SeV.th} (Project Sponsor) โดย Baseline ของระบบที่ส่งมอบอนุมัติพร้อมการตรวจรับ`],
['เอกสารควบคุมที่เกี่ยวข้อง', 'Software Configuration (WP 8) ระบุรายการ Configuration Item และการควบคุมเวอร์ชัน'],
['การสอบกลับ', 'Traceability Record (WP 13) เชื่อมโยงความต้องการกับ Software Unit และ Test Case บน Baseline นี้'],
['การสอบกลับ', 'Traceability Record (WP 13) เชื่อมโยงความต้องการกับ Software Unit และ Test Case ที่จะกำหนดภายหลัง'],
['การเปลี่ยนแปลง Baseline', 'ต้องพิจารณาตามเกณฑ์ใน Change Report และบันทึกผลใน Correction Register เมื่อเป็นการแก้ไขข้อบกพร่อง'],
]),
'## Software Unit',
@@ -162,14 +162,13 @@ export function traceabilityRecord() {
let group = '';
for (const [id, text] of CRS) {
const g = id.slice(0, 4);
if (g !== group) { group = g; const name = SCOPEGROUP(g); rows.push([`${g}: ${name}`, '', '', '', '', '', '', '']); }
if (g !== group) { group = g; const name = SCOPEGROUP(g); rows.push([`${g}: ${name}`, '', '', '', '', '', '']); }
const t = traceOf(id);
rows.push([id, text, t.sr.join(', '), t.sr.map(srTopic).join(' / '), t.un.join(', ') || '-', t.un.map(unitTopic).join(' / ') || '-', t.tc, t.ev || '-']);
rows.push([id, text, t.sr.join(', '), t.sr.map(srTopic).join(' / '), t.un.join(', ') || '-', t.un.map(unitTopic).join(' / ') || '-', t.ev || '-']);
}
const traced = CRS.map(([id]) => traceOf(id));
const withUnits = traced.filter((t) => t.un.length).length;
const withTests = traced.filter((t) => t.tcs.length).length;
const byDocument = traced.filter((t) => !t.tcs.length).length;
const byDocument = traced.filter((t) => !t.un.length).length;
return {
dir: '2-SI Process (12 Work Product)/13.Traceability record',
name: `${CODE} Traceability Record ${bud(date)} V1.0 NoC`,
@@ -183,23 +182,20 @@ export function traceabilityRecord() {
]),
body: [
'## วัตถุประสงค์ (Objective)',
'เอกสารที่เชื่อมโยงทุกขั้นตอนของโครงการ ตั้งแต่ความต้องการของลูกค้า → ความต้องการซอฟต์แวร์ → การออกแบบ → การทดสอบ เพื่อยืนยันความครบถ้วนของ Customer Requirements และลดความเสี่ยงของข้อบกพร่องและความต้องการที่ตกหล่น',
'เอกสารที่เชื่อมโยงความต้องการของลูกค้า → ความต้องการซอฟต์แวร์ → การออกแบบ (Software Unit) ณ วันที่ออกแบบระบบ เพื่อยืนยันความครบถ้วนของ Customer Requirements ก่อนเริ่มพัฒนา และลดความเสี่ยงของความต้องการที่ตกหล่น การสอบกลับไปยัง Test Case และผลการทดสอบจะเพิ่มใน Traceability Record ฉบับส่งมอบ หลังจัดทำ Test Case and Test Procedures',
'## Traceability Matrix',
table(['CR ID', 'CR Topic', 'SRS ID', 'SRS Topic', 'Unit ID', 'Unit Topic', 'Test Case ID', 'หลักฐานอื่น (Document)'], rows),
table(['CR ID', 'CR Topic', 'SRS ID', 'SRS Topic', 'Unit ID', 'Unit Topic', 'หลักฐานอื่น (Document)'], rows),
'หมายเหตุ: ความต้องการที่เป็นเอกสาร การอบรม หรือหลักฐานด้านคุณภาพ สอบกลับไปยังเอกสารส่งมอบที่เป็นหลักฐานในคอลัมน์ "หลักฐานอื่น" แทน Software Unit',
'## สรุปความครอบคลุม',
table(['รายการ', 'จำนวน'], [
['ความต้องการของลูกค้าทั้งหมด (CR01–CR14)', CRS.length],
['เชื่อมโยงกับความต้องการซอฟต์แวร์ (SRS)', CRS.length],
['เชื่อมโยงกับ Software Unit', withUnits],
['เชื่อมโยงกับ Test Case', withTests],
['ยืนยันด้วยเอกสารส่งมอบ (ไม่มี Software Unit)', byDocument],
['มีหลักฐานยืนยันครบ (Test Case หรือเอกสาร)', CRS.length],
['ความต้องการซอฟต์แวร์ทั้งหมด (SR01–SR09)', SRS.length],
['Software Unit ทั้งหมด', UNITS.length],
['Test Case ทั้งหมด', TEST_CASES.length],
], ['', 'r']),
`ความต้องการทุกรายการมีเส้นทางการสอบกลับที่ครบถ้วนตั้งแต่ความต้องการของลูกค้าจนถึง Test Case และผลการทดสอบ โดยผลการทดสอบทั้งหมดผ่านในรอบทดสอบระหว่าง ${thRangeLong(TEST_FROM, TEST_TO)}`,
'ความต้องการทุกรายการเชื่อมโยงกับความต้องการซอฟต์แวร์ และเชื่อมโยงกับ Software Unit หรือเอกสารส่งมอบที่เป็นหลักฐาน Test Case ของแต่ละ Software Unit จะกำหนดใน Test Case and Test Procedures (WP 6.0) ก่อนรอบทดสอบระบบ',
].join('\n\n'),
secretary: P.NoC, reviewer: P.YaB, approver: P.SeV,
}),
@@ -233,7 +229,7 @@ export function softwareComponents() {
[4, 'Deployment Package', 'ชุดติดตั้ง Docker Compose (php-apache, mariadb, node/pm2)', '`docker-compose.yml`, `docker/`', `\`${BASELINE_COMMIT}\``],
[5, 'Configuration Template', 'แม่แบบค่าตั้งค่าและสคริปต์สร้าง `.env`', '`.env.example`, `docker/init-env.sh`', `\`${BASELINE_COMMIT}\``],
[6, 'Demo Data', 'สคริปต์สร้างข้อมูลสาธิตสำหรับตรวจรับ', '`demo_seed*.php`', '`dd48a8b`'],
[7, 'Document Package', 'เอกสาร Work Products และชุด PDF ที่ส่งมอบ', '`sdlc/`, `sdlc-delivery/`', `Tag \`${TAG}\``],
[7, 'Document Package', 'เอกสาร Work Products และชุด PDF ที่ส่งมอบ', '`sdlc/`, `sdlc-delivery/`', 'Git Repository'],
], ['c']),
'## รายการ Software Unit ที่พัฒนาแล้ว',
table(['Module', 'Unit ID', 'Description', 'สถานะ'],
@@ -260,7 +256,7 @@ export function softwareComponents() {
/** 15. Test Cases and Test Procedures. */
export function testCases() {
const date = '2026-07-31';
const rows = TEST_CASES.map((t, i) => [i + 1, t[0], `${t[1]} ${unitTopic(t[1])}`, t[2], t[3], t[4], t[5], t[6], 'Passed', thRangeLong(TEST_FROM, TEST_TO)]);
const rows = TEST_CASES.map((t, i) => [i + 1, t[0], `${t[1]} ${unitTopic(t[1])}`, t[2], t[3], t[4], t[5], t[6], 'Ready', `ตามแผน ${thRange(TEST_FROM, TEST_TO)}`]);
return {
dir: '2-SI Process (12 Work Product)/15.Test Cases and Test Procedures',
name: `${CODE} Test Case and Test Procedures ${bud(date)} V1.0 PaNg`,
@@ -281,14 +277,14 @@ export function testCases() {
['ทดสอบระบบตาม Test Case', 'Internal Testing Server (PHP 8, MariaDB, Node.js)', `${P.PaNg.th} (QA/Tester)`],
['ทดสอบการยอมรับ (UAT)', 'สภาพแวดล้อมใช้งานจริงของลูกค้า', `${P.SeV.th} (Project Sponsor / ตัวแทนลูกค้า)`],
]),
`การทดสอบระบบดำเนินการบน Internal Testing Server จึงสามารถทดสอบกรณีที่มีความเสี่ยงสูง เช่น การจำลองความล้มเหลวและ Rollback (TC-UN08.002) ได้โดยไม่กระทบข้อมูลของลูกค้า สถานะของ Repository ที่ใกล้เคียงที่สุด ณ สิ้นสุดรอบทดสอบคือ Commit \`${TEST_STATE_COMMIT}\``,
`การทดสอบระบบดำเนินการบน Internal Testing Server จึงสามารถทดสอบกรณีที่มีความเสี่ยงสูง เช่น การจำลองความล้มเหลวและ Rollback (TC-UN08.002) ได้โดยไม่กระทบข้อมูลของลูกค้า รอบทดสอบระบบกำหนดไว้ระหว่าง ${thRangeLong(TEST_FROM, TEST_TO)} และผลการทดสอบบันทึกใน Test Report`,
'## ตาราง Test Case',
table(['No', 'Test Case ID', 'Test Item', 'Input Specifications', 'Output Specifications', 'Environment / Needs', 'Special Procedural Required', 'Intercase Dependency', 'Status', 'Test Date'], rows, ['c', 'c']),
'## Summary',
table(['No', 'Topic', 'Detail'], [
[1, 'Test Case', `การทดสอบระบบทั้งหมด ${TEST_CASES.length} Test Case ผ่าน 100%`],
[2, 'Pending', 'ไม่พบข้อผิดพลาด (Fail) หรือรายการค้าง (Pending) ในรอบการทดสอบนี้'],
[3, 'Conclusion', 'ผลการทดสอบสรุปได้ว่าระบบพร้อมสำหรับ UAT และการส่งมอบ เนื่องจากผลการทดสอบผ่านครบ 100% และเป็นไปตาม Project Plan'],
[1, 'Test Case', `กำหนด Test Case ทั้งหมด ${TEST_CASES.length} รายการ ครอบคลุม Software Unit ทุกหน่วย สถานะพร้อมทดสอบ (Ready)`],
[2, 'เกณฑ์ผ่าน', 'ทุก Test Case ต้องได้ผลตาม Output Specifications ข้อบกพร่องที่พบบันทึกใน Correction Register และทดสอบซ้ำจนผ่าน'],
[3, 'เกณฑ์เข้าสู่ UAT', 'Test Case ผ่านครบ 100% และไม่มีข้อบกพร่องระดับสูงคงค้าง ผลการทดสอบบันทึกใน Test Report'],
], ['c']),
].join('\n\n'),
secretary: P.PaNg, reviewer: P.NoC, approver: P.SeV,
+6 -7
View File
@@ -158,11 +158,11 @@ export function operationGuide() {
'กรณีกู้คืนระบบใช้งานจริง ให้บันทึกจุดกู้คืน หยุดบริการที่เกี่ยวข้อง กู้คืนชุดสำรองที่ตรวจสอบแล้ว เริ่มบริการแอปพลิเคชันและ Node.js ใหม่ แล้วตรวจสอบสถานะตามหัวข้อ 5',
'กรณีการสำรองข้อมูลล้มเหลวหรือไม่ทำงานตามกำหนด ผู้ดูแลระบบต้องตรวจสอบสาเหตุและสั่งทำงานซ้ำ',
]),
`การทดสอบกู้คืนดำเนินการเมื่อ ${thLong(OPS_CLOSE_DATE)} โดยกู้คืนชุดสำรองลงในสภาพแวดล้อมที่ไม่ใช่ระบบใช้งานจริงสำเร็จ ตรวจสอบการเข้าถึงฐานข้อมูลและข้อมูลตัวอย่างครบถ้วน และผู้จัดการโครงการได้ทบทวนผลการทดสอบในขั้นตอนปิดโครงการ`,
`กำหนดทดสอบกู้คืนภายใน ${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 ระบุรอบการตรวจสอบ เงื่อนไขการแจ้งเตือน ผู้รับแจ้ง และขั้นตอนการยกระดับ'],
['OP-001', 'การสำรองและกู้คืนฐานข้อมูล `wms` และ `wms2` แบบอัตโนมัติ', 'System Administrator', `กำหนดปิดรายการ ${thLong(OPS_CLOSE_DATE)}`, 'หัวข้อ 6 ระบุรอบการสำรอง ที่จัดเก็บ การเก็บรักษา ขั้นตอนกู้คืน และแผนการทดสอบกู้คืน'],
['OP-002', 'การเฝ้าระวังระบบและการแจ้งเตือนเมื่อเกิดเหตุขัดข้อง', 'System Administrator', `กำหนดปิดรายการ ${thLong(OPS_CLOSE_DATE)}`, 'หัวข้อ 5 ระบุรอบการตรวจสอบ เงื่อนไขการแจ้งเตือน ผู้รับแจ้ง และขั้นตอนการยกระดับ'],
], ['c']),
'## 8 การจัดการผู้ใช้งานและสิทธิ์',
bullets([
@@ -207,8 +207,7 @@ export function maintenanceDocumentation() {
table(['ลำดับ', 'ประเภทการบำรุงรักษา', 'รายละเอียด'], [
[1, 'Corrective Maintenance', 'แก้ไขข้อผิดพลาด (Bug Fix) และตรวจสอบ Error Log'],
[2, 'Adaptive Maintenance', 'ปรับระบบให้รองรับ Browser หรือระบบปฏิบัติการรุ่นใหม่'],
[3, 'Perfective Maintenance', 'ปรับปรุงประสิทธิภาพและเพิ่มความสามารถตามที่ตกลง'],
[4, 'Preventive Maintenance', 'ปรับปรุงความปลอดภัย (Patch Security) และทดสอบการสำรอง/กู้คืนข้อมูล'],
[3, 'Preventive Maintenance', 'ปรับปรุงความปลอดภัย (Patch Security) และทดสอบการสำรอง/กู้คืนข้อมูล'],
], ['c']),
'## กิจกรรมการบำรุงรักษา (Maintenance Activities)',
table(['ลำดับ', 'กิจกรรม', 'รายละเอียด', 'ความถี่', 'ผู้รับผิดชอบ'], [
@@ -324,7 +323,7 @@ export function verificationResults() {
], 'ThS'],
'WP 11.0': ['Maintenance Document', [
['รายละเอียดและความถูกต้องของ Header ของเอกสาร', 'มีรายละเอียดครบถ้วนถูกต้อง'],
['ระบุขอบเขต ประเภท และกิจกรรมการบำรุงรักษาครบถ้วน', 'มีครบทั้ง 4 ประเภทและ 6 กิจกรรม'],
['ระบุขอบเขต ประเภท และกิจกรรมการบำรุงรักษาครบถ้วน', 'มีครบทั้ง 3 ประเภทและ 6 กิจกรรม'],
['ระบุข้อตกลงระดับการให้บริการ (SLA)', 'มี SLA ครบ 4 ระดับ'],
['ลงชื่อผู้จัดทำและผู้อนุมัติเรียบร้อย', 'มีตารางลงนามครบถ้วน'],
], 'ThS'],
@@ -453,7 +452,7 @@ export function validationResults() {
['ไม่ผ่าน (Failed)', 0],
['ค้างการทดสอบ', 0],
], ['', 'r']),
`ตัวแทนลูกค้าได้ทดสอบการยอมรับครบทุกความต้องการใน Customer Requirements บนสภาพแวดล้อมใช้งานจริง ผลการทดสอบผ่านทั้งหมด ไม่พบประเด็นที่ขัดขวางการเปิดใช้งาน จึงยืนยันความพร้อมของระบบสำหรับการตรวจรับส่งมอบ ซึ่งบันทึกไว้ใน Acceptance Report เมื่อ ${thLong(ACCEPT_DATE)}`,
`ตัวแทนลูกค้าได้ทดสอบการยอมรับครบทุกความต้องการใน Customer Requirements บนสภาพแวดล้อมใช้งานจริง ผลการทดสอบผ่านทั้งหมด ไม่พบประเด็นที่ขัดขวางการเปิดใช้งาน จึงยืนยันความพร้อมของระบบและเสนอเข้าสู่การตรวจรับส่งมอบตามแผนในวันที่ ${thLong(ACCEPT_DATE)}`,
].join('\n\n'),
secretary: P.PaNg, reviewer: P.NoC, approver: P.SeV,
}),
+6 -6
View File
@@ -8,10 +8,10 @@ export const TASKS = [
['1.2', 'Project Initiation', 'Identify Stakeholders', 'ระบุผู้มีส่วนได้ส่วนเสียและบทบาทความรับผิดชอบ', ['ApS', 'YaB'], '2026-01-12', '2026-01-16', 'Stakeholder Register', 'Completed', 'จัดทำ Stakeholder Register'],
['1.3', 'Project Initiation', 'Define Vision & Objectives', 'กำหนดวัตถุประสงค์ ขอบเขตระบบ และเกณฑ์ความสำเร็จ', ['ApS', 'NoC'], '2026-01-19', '2026-01-21', 'Project Charter Report', 'Completed', 'อนุมัติวัตถุประสงค์โครงการ'],
['1.4', 'Project Initiation', 'Approve Project Charter', 'ผู้บริหารอนุมัติกฎบัตรโครงการและขอบเขตงาน', ['SeV', 'ApS'], '2026-01-22', '2026-01-23', 'Approved Project Charter', 'Completed', 'อนุมัติโดย Project Sponsor'],
['2.1', 'Project Planning', 'Collect Customer Requirements', 'เก็บและวิเคราะห์ความต้องการลูกค้า จัดหมวด CR01–CR14', ['NoC', 'ApS'], '2026-01-12', '2026-02-06', 'Customer Requirements', 'Completed', `ความต้องการ ${CRS.length} รายการ ผลสรุป A ทั้งหมด`],
['2.1', 'Project Planning', 'Collect Customer Requirements', 'เก็บและวิเคราะห์ความต้องการลูกค้า และจัดหมวดความต้องการ', ['NoC', 'ApS'], '2026-01-12', '2026-02-06', 'Customer Requirements', 'Completed', `ความต้องการ ${CRS.length} รายการ ผลสรุป A ทั้งหมด`],
['2.2', 'Project Planning', 'Develop Software Project Plan', 'จัดทำแผนโครงการ ทรัพยากร ความเสี่ยง และการควบคุม', ['ApS', 'YaB'], '2026-01-26', '2026-02-13', 'Software Project Plan', 'Completed', 'รวม Work Schedule'],
['2.3', 'Project Planning', 'Baseline Requirements & Schedule', 'ทบทวนและตั้ง Baseline ความต้องการและแผนงาน', ['ApS', 'NoC'], '2026-02-16', '2026-02-18', 'Baselined Plan', 'Completed', 'เริ่มพัฒนา 19 ก.พ. 69'],
['3.1', 'Project Execution', 'Software Requirements & Design', 'จัดทำ SRS (SR01–SR09) และเอกสารออกแบบระบบ', ['NoC', 'ThS'], '2026-02-19', '2026-02-25', 'SRS, Software Design', 'Completed', 'Git 9a50080–1843308'],
['3.1', 'Project Execution', 'Software Requirements & Design', 'จัดทำ SRS และเอกสารออกแบบระบบ', ['NoC', 'ThS'], '2026-02-19', '2026-02-25', 'SRS, Software Design', 'Completed', 'Git 9a50080–1843308'],
['3.2', 'Project Execution', 'Security & Database Foundation', 'ป้องกันไฟล์ตั้งค่า แก้ผลตรวจความปลอดภัย และออกแบบฐานข้อมูลสต๊อก', ['ThS'], '2026-03-09', '2026-03-17', 'Database schema, ICS modules', 'Completed', 'Git 93d903c–a4f474b'],
['3.3', 'Project Execution', 'Inventory & Warehouse Modules', 'พัฒนาคลังสินค้า ความจุ สินค้า Lot/Serial/Expiry และรายงาน', ['ThS', 'NoC'], '2026-04-09', '2026-04-29', 'Inventory & report modules', 'Completed', 'Git 3b8f94f–db5c47b'],
['3.4', 'Project Execution', 'Authentication & Onboarding', 'พัฒนาเข้าสู่ระบบ ลงทะเบียน Onboarding และสิทธิ์ตามบทบาท', ['ThS'], '2026-04-28', '2026-05-12', 'Authentication modules', 'Completed', 'Git d8fd30c–79e66bd'],
@@ -135,7 +135,7 @@ export const LESSONS = [
*/
export const PERIODS = [
['2026-01-09', '2026-01-05', '2026-01-09', 1, [['1.1', 'Completed', 100, 'เปิดโครงการอย่างเป็นทางการ ชี้แจงขอบเขตและทีมงานครบทุกบทบาท'], ['1.2', 'In Progress', 40, 'อยู่ระหว่างรวบรวมรายชื่อผู้มีส่วนได้ส่วนเสียและระดับการมีส่วนร่วม']], [], [], { date: '2026-01-16', topic: 'ทบทวนรายชื่อผู้มีส่วนได้ส่วนเสียและเริ่มกำหนดวัตถุประสงค์โครงการ' }],
['2026-01-23', '2026-01-12', '2026-01-23', 2, [['1.2', 'Completed', 100, 'จัดทำ Stakeholder Register ครบ 6 บทบาทหลักและผู้ใช้งานที่เกี่ยวข้อง'], ['1.3', 'Completed', 100, 'กำหนดวัตถุประสงค์ ขอบเขตระบบ 11 ระบบงาน และเกณฑ์ความสำเร็จ'], ['1.4', 'Completed', 100, 'Project Charter ได้รับอนุมัติจาก Project Sponsor'], ['2.1', 'In Progress', 30, 'เริ่มเก็บความต้องการลูกค้าจากฝ่ายคลังสินค้า ขาย และบัญชี']], [], [], { date: '2026-02-06', topic: 'สรุปความต้องการลูกค้า CR01–CR14 และทบทวนร่างแผนโครงการ' }],
['2026-01-23', '2026-01-12', '2026-01-23', 2, [['1.2', 'Completed', 100, 'จัดทำ Stakeholder Register ครบ 6 บทบาทหลักและผู้ใช้งานที่เกี่ยวข้อง'], ['1.3', 'Completed', 100, 'กำหนดวัตถุประสงค์ ขอบเขตระบบ 11 ระบบงาน และเกณฑ์ความสำเร็จ'], ['1.4', 'Completed', 100, 'Project Charter ได้รับอนุมัติจาก Project Sponsor'], ['2.1', 'In Progress', 30, 'เริ่มเก็บความต้องการลูกค้าจากฝ่ายคลังสินค้า ขาย และบัญชี']], [], [], { date: '2026-02-06', topic: 'สรุปความต้องการลูกค้าและทบทวนร่างแผนโครงการ' }],
['2026-02-06', '2026-01-26', '2026-02-06', 3, [['2.1', 'Completed', 100, `สรุปความต้องการลูกค้า ${CRS.length} รายการ จัดหมวด CR01–CR14 และได้ผลสรุป A ทุกรายการ`], ['2.2', 'In Progress', 60, 'อยู่ระหว่างจัดทำ Software Project Plan และ Work Schedule']], [], [], { date: '2026-02-13', topic: 'ทบทวน Software Project Plan, Work Schedule และแผนความเสี่ยง' }],
['2026-02-18', '2026-02-09', '2026-02-18', 4, [['2.2', 'Completed', 100, 'จัดทำแผนโครงการ ทรัพยากร ความเสี่ยง R1–R8 และแผนสำรองงานที่ไม่แล้วเสร็จ'], ['2.3', 'Completed', 100, 'ตั้ง Baseline ความต้องการและแผนงาน อนุมัติเริ่มพัฒนา 19 ก.พ. 69']], [], [], { date: '2026-02-25', topic: 'ติดตามการจัดทำ SRS, เอกสารออกแบบระบบ และการเริ่มพัฒนา' }],
['2026-02-25', '2026-02-19', '2026-02-25', 5, [['3.1', 'Completed', 100, 'จัดทำ SRS (SR01–SR09) และเอกสารออกแบบระบบ เริ่มพัฒนาโครงสร้างแอปพลิเคชัน'], ['4.2', 'In Progress', 20, 'เริ่มควบคุม Source Code บน Git และกำหนด main เป็น Baseline']], [], [], { date: '2026-03-17', topic: 'ติดตามผลการแก้ไขด้านความปลอดภัยและการออกแบบฐานข้อมูลสต๊อก' }],
@@ -148,7 +148,7 @@ export const PERIODS = [
['2026-08-03', '2026-06-01', '2026-08-03', 12, [['4.3', 'In Progress', 70, 'ตรวจสอบเอกสารความต้องการ ออกแบบ และการสอบกลับ รอบที่ 1–3'], ['4.5', 'In Progress', 80, 'จัดทำคู่มือผู้ใช้ คู่มือผู้ดูแลระบบ และคู่มือบำรุงรักษา'], ['4.6', 'Completed', 100, 'แก้ไขการเข้าสู่ระบบและค่าตั้งค่าสภาพแวดล้อมหลัง Baseline']], ['ISS-028'], [], { date: '2026-08-14', topic: 'ติดตามผลการทดสอบระบบ UAT และข้อมูลสาธิต' }],
['2026-08-14', '2026-08-04', '2026-08-14', 13, [['4.4', 'Completed', 100, `ทดสอบระบบ 45 Test Case และ UAT ครบ 14 หมวดความต้องการ (${CRS.length} รายการ) ผ่านทั้งหมดระหว่าง 10–14 ส.ค. 69`], ['4.7', 'In Progress', 60, 'จัดทำข้อมูลสาธิตเสร็จ อยู่ระหว่างปรับแบรนด์และชุดติดตั้ง Docker Compose'], ['5.1', 'In Progress', 70, 'ตรวจสอบความครบถ้วนของ Work Products']], [], [], { date: '2026-08-17', topic: 'สรุปผลการตรวจสอบรอบสุดท้าย และตรวจรับส่งมอบระบบ' }],
['2026-08-17', '2026-08-15', '2026-08-17', 14, [['4.3', 'Completed', 100, 'ตรวจสอบ Work Products รอบที่ 4 ครบทุกรายการ ผลผ่านทั้งหมด'], ['4.5', 'Completed', 100, 'คู่มือผู้ใช้ คู่มือผู้ดูแลระบบ และคู่มือบำรุงรักษาเสร็จสมบูรณ์'], ['4.7', 'Completed', 100, 'ปรับแบรนด์และชุดติดตั้ง Docker Compose เสร็จ Baseline 6c39700'], ['5.1', 'Completed', 100, 'จัดทำ List of Evidence ครบทุก Work Product'], ['5.3', 'In Progress', 80, 'ลูกค้าตรวจรับระบบเมื่อ 17 ส.ค. 69 เหลือการอบรมและปิดโครงการ']], [], [], { date: '2026-08-24', topic: 'สรุปผลการอบรม การปิดงานควบคุมปฏิบัติการ และปิดโครงการ' }],
['2026-08-24', '2026-08-18', '2026-08-24', 15, [['5.2', 'Completed', 100, 'อบรมผู้ใช้งาน 6 คน เมื่อ 22 ส.ค. 69 ผ่านการทดลองใช้งานครบทุกขั้นตอน'], ['5.3', 'Completed', 100, 'ปิดงานควบคุมปฏิบัติการ 23 ส.ค. 69 และปิดโครงการอย่างเป็นทางการ 24 ส.ค. 69'], ['4.1', 'Completed', 100, 'บันทึกความก้าวหน้าและรายงานการประชุมครบตลอดโครงการ'], ['4.2', 'Completed', 100, 'ควบคุมเวอร์ชันและสำรองข้อมูลครบ พร้อม Tag sdlc-v1.0-final']], [], [], null],
['2026-08-24', '2026-08-18', '2026-08-24', 15, [['5.2', 'Completed', 100, 'อบรมผู้ใช้งาน 6 คน เมื่อ 22 ส.ค. 69 ผ่านการทดลองใช้งานครบทุกขั้นตอน'], ['5.3', 'Completed', 100, 'ปิดงานควบคุมปฏิบัติการ 23 ส.ค. 69 และปิดโครงการอย่างเป็นทางการ 24 ส.ค. 69'], ['4.1', 'Completed', 100, 'บันทึกความก้าวหน้าและรายงานการประชุมครบตลอดโครงการ'], ['4.2', 'Completed', 100, 'ควบคุมเวอร์ชันและสำรองข้อมูลครบ']], [], [], null],
];
/**
@@ -171,7 +171,7 @@ export const MEETINGS = [
progress: [['Phase 1: Project Initiation', 'เสร็จสมบูรณ์', 100, 'อนุมัติ Project Charter และขอบเขตงานครบถ้วน'], ['Phase 2: Project Planning', 'กำลังดำเนินการ', 30, 'อยู่ระหว่างเก็บความต้องการลูกค้า'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 12, 'เข้าสู่ขั้นตอนวางแผนโครงการตามกำหนด']],
agenda: [['พิจารณาอนุมัติกฎบัตรโครงการ (Project Charter)', 'ยืนยันวัตถุประสงค์ ขอบเขต และงบประมาณ'], ['ทบทวน Stakeholder Register', 'ยืนยันบทบาทและระดับการมีส่วนร่วม'], ['แผนการเก็บความต้องการลูกค้า', 'กำหนดผู้ให้ข้อมูลและกรอบเวลา']],
discussion: [['อนุมัติกฎบัตรโครงการ', 'Project Sponsor อนุมัติวัตถุประสงค์ ขอบเขต 11 ระบบงาน และเกณฑ์ความสำเร็จ', ['SeV'], 'อนุมัติ'], ['ผู้มีส่วนได้ส่วนเสีย', 'ยืนยันผู้รับผิดชอบตาม RACI ครบทุกบทบาท', ['ApS', 'YaB'], 'ยืนยันแล้ว'], ['การเก็บความต้องการ', 'มอบหมาย System Analyst สัมภาษณ์ฝ่ายคลังสินค้า ขาย จัดซื้อ และบัญชี', ['NoC'], 'ดำเนินการ']],
actions: [['เก็บและสรุปความต้องการลูกค้า CR01–CR14 (Task 2.1)', ['NoC', 'ApS'], '2026-02-06', 'Scheduled'], ['จัดทำร่าง Software Project Plan และ Work Schedule (Task 2.2)', ['ApS'], '2026-02-13', 'Scheduled']],
actions: [['เก็บและสรุปความต้องการลูกค้า (Task 2.1)', ['NoC', 'ApS'], '2026-02-06', 'Scheduled'], ['จัดทำร่าง Software Project Plan และ Work Schedule (Task 2.2)', ['ApS'], '2026-02-13', 'Scheduled']],
next: { date: '2026-02-06', topic: 'สรุปความต้องการลูกค้าและทบทวนร่างแผนโครงการ' },
},
{
@@ -260,7 +260,7 @@ export const MEETINGS = [
attachment: 'Training Report, Product Operation Guide, Project Repository (Backup)',
progress: [['Phase 1: Project Initiation', 'เสร็จสมบูรณ์', 100, 'เปิดโครงการและอนุมัติกฎบัตรเรียบร้อย'], ['Phase 2: Project Planning', 'เสร็จสมบูรณ์', 100, 'อนุมัติแผนโครงการและ Baseline ความต้องการ'], ['Phase 3: Project Execution', 'เสร็จสมบูรณ์', 100, 'พัฒนาระบบครบตามขอบเขตและเปิดใช้งานจริง'], ['Phase 4: Verification & Validation', 'เสร็จสมบูรณ์', 100, 'ตรวจสอบ ทดสอบ และ UAT ผ่านครบทุกรายการ'], ['Phase 5: Project Close', 'เสร็จสมบูรณ์', 100, 'อบรม ปิดงานควบคุมปฏิบัติการ และปิดโครงการเรียบร้อย'], ['รวมความคืบหน้าทั้งโครงการ', 'ตามแผน', 100, 'โครงการเสร็จสมบูรณ์ ปิดโครงการอย่างเป็นทางการ']],
agenda: [['สรุปผลการอบรมผู้ใช้งาน', 'ยืนยันความพร้อมของผู้ใช้ก่อนเปิดใช้งานจริง'], ['สรุปการปิดงานควบคุมการสำรองข้อมูลและการเฝ้าระวัง', 'ยืนยันการปิดรายการ OP-001 และ OP-002'], ['สรุปการสำรอง Repository และเอกสารโครงการ', 'ยืนยันการสำรองและกู้คืนได้จริง'], ['ปิดโครงการอย่างเป็นทางการ', 'ลงมติปิดโครงการและส่งมอบเอกสาร']],
discussion: [['ผลการอบรม', 'อบรมผู้ใช้งาน 6 คน เมื่อ 22 ส.ค. 69 ผู้เข้าอบรมทดลองใช้งานครบทุกขั้นตอนได้สำเร็จ', ['ThS', 'PaNg'], 'เสร็จสิ้น'], ['การควบคุมปฏิบัติการ', 'ปิดรายการสำรองฐานข้อมูลอัตโนมัติ (OP-001) และการเฝ้าระวังพร้อมแจ้งเตือน (OP-002) เมื่อ 23 ส.ค. 69', ['ThS'], 'เสร็จสิ้น'], ['การสำรองข้อมูลโครงการ', 'ซิงก์ Branch และ Tag sdlc-v1.0-final ไปยัง Backup Remote และทดสอบเรียกคืนสำเร็จ', ['ThS', 'YaB'], 'เสร็จสิ้น'], ['การปิดโครงการ', 'ที่ประชุมมีมติปิดโครงการอย่างเป็นทางการเมื่อ 24 ส.ค. 69', ['SeV', 'ApS'], 'ปิดโครงการ']],
discussion: [['ผลการอบรม', 'อบรมผู้ใช้งาน 6 คน เมื่อ 22 ส.ค. 69 ผู้เข้าอบรมทดลองใช้งานครบทุกขั้นตอนได้สำเร็จ', ['ThS', 'PaNg'], 'เสร็จสิ้น'], ['การควบคุมปฏิบัติการ', 'ปิดรายการสำรองฐานข้อมูลอัตโนมัติ (OP-001) และการเฝ้าระวังพร้อมแจ้งเตือน (OP-002) เมื่อ 23 ส.ค. 69', ['ThS'], 'เสร็จสิ้น'], ['การสำรองข้อมูลโครงการ', 'ซิงก์ Branch `main` ไปยัง Backup Remote และทดสอบเรียกคืนสำเร็จ', ['ThS', 'YaB'], 'เสร็จสิ้น'], ['การปิดโครงการ', 'ที่ประชุมมีมติปิดโครงการอย่างเป็นทางการเมื่อ 24 ส.ค. 69', ['SeV', 'ApS'], 'ปิดโครงการ']],
actions: [['จัดเก็บเอกสารโครงการทั้งหมดเข้าระบบควบคุมเอกสาร', ['YaB'], '2026-08-24', 'เสร็จสมบูรณ์'], ['ติดตามการตรวจนับสต๊อกสิ้นเดือนแรกหลังเปิดใช้งานจริง', ['ApS'], '2026-09-30', 'Scheduled']],
next: null,
},
+1 -1
View File
@@ -42,7 +42,7 @@ export const WPS = [
['WP 6.0', `เอกสาร ${CODE} Test Case and Test Procedures`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 7.0', `เอกสาร ${CODE} Validation Results`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 8.0', `เอกสาร ${CODE} Software User Document`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 9.0', 'ระบบ BRN WMS ที่ผ่านการทดสอบพร้อมนำไปใช้งานจริง', `ส่ง Source Code (Git baseline ${BASELINE_COMMIT}) และติดตั้ง`],
['WP 9.0', 'ระบบ BRN WMS ที่ผ่านการทดสอบพร้อมนำไปใช้งานจริง', 'ส่ง Source Code ผ่าน Git Repository และติดตั้ง'],
['WP 10.0', `เอกสาร ${CODE} Product Operation Guide`, 'ส่งเอกสารจำนวน 1 ชุด'],
['WP 11.0', `เอกสาร ${CODE} Maintenance Document`, 'ส่งเอกสารจำนวน 1 ชุด'],
];
+83
View File
@@ -0,0 +1,83 @@
// Hindsight guard: a work product may only cite what existed on its own date.
// A plan dated 13 Feb cannot quote the commit that was delivered on 17 Aug, a test case
// dated 31 Jul cannot record results from the 10–14 Aug test run, and so on.
// Planned dates in the future are fine; facts that only came into being later are not.
import { execFileSync } from 'node:child_process';
import { TAG, END } from './project.mjs';
import { CORRECTIONS } from './history.mjs';
/** Document date from its name: "... 25690817 V1.0 ..." -> 2026-08-17 */
export function docDate(name) {
const m = name.match(/ (25\d\d)(\d\d)(\d\d) V/);
if (!m) throw new Error(`No date in document name: ${name}`);
return `${+m[1] - 543}-${m[2]}-${m[3]}`;
}
function git(root, args) {
try { return execFileSync('git', ['-C', root, ...args], { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); } catch { return null; }
}
/**
* Identifier families and the work product that first defines them. An ID may be cited only
* on or after the date of that work product.
*/
const FAMILIES = [
{ label: 'Customer Requirement', re: /\bCR\d\d(?::\d{3})?\b/g, definedBy: 'Customer Requirements' },
{ label: 'Software Requirement', re: /\bSR\d\d(?::\d{3})?\b/g, definedBy: 'Software Requirements' },
{ label: 'Software Unit', re: /(?<!TC-)\bUN\d\d(?:\.\d{3})?\b/g, definedBy: 'Software Design' },
{ label: 'Test Case', re: /\bTC-UN\d\d\.\d{3}\b/g, definedBy: 'Test Case and Test Procedures' },
];
/** Returns a list of "document: problem" strings; empty when every document is consistent with its date. */
export function hindsightErrors(docs, root) {
const errors = [];
const hasGit = git(root, ['rev-parse', '--git-dir']) !== null;
const commitDates = new Map();
const commitDate = (h) => {
if (!commitDates.has(h)) commitDates.set(h, git(root, ['log', '-1', '--format=%ad', '--date=short', `${h}^{commit}`]) || null);
return commitDates.get(h);
};
const onMain = (h) => git(root, ['merge-base', '--is-ancestor', h, 'main']) !== null;
const tagDate =hasGit ? git(root, ['for-each-ref', `refs/tags/${TAG}`, '--format=%(creatordate:short)']) || null : null;
const detected = new Map(CORRECTIONS.map((c) => [c[0], c[1]]));
const firstDefined = new Map();
for (const f of FAMILIES) {
const dates = docs.filter((d) => d.name.includes(` ${f.definedBy} `)).map((d) => docDate(d.name)).sort();
if (!dates.length) throw new Error(`No work product defines ${f.label} IDs (${f.definedBy})`);
firstDefined.set(f, dates[0]);
}
for (const d of docs) {
const D = docDate(d.name);
const found = new Set();
const flag = (what) => found.add(what);
if (D > END) flag(`dated after the project end ${END}`);
if (hasGit) {
for (const [, h] of d.md.matchAll(/(?<![\w/.-])([0-9a-f]{7,40})(?![\w/.-])/g)) {
const c = commitDate(h);
if (!c) continue;
if (c > D) flag(`commit ${h} (created ${c})`);
if (c > END) flag(`commit ${h} is after the project end ${END}`);
if (!onMain(h)) flag(`commit ${h} is not on branch main`);
}
}
// The documents describe the delivered software, which lives on main only.
for (const [, b] of d.md.matchAll(/\bbranch\s+`?([\w./-]+)`?/gi)) {
if (b !== 'main' && /^[a-z]/.test(b)) flag(`branch ${b} (only main may be named)`);
}
if (d.md.includes(TAG) && !(tagDate && tagDate <= D)) flag(`tag ${TAG} (${tagDate ? `created ${tagDate}` : 'not created yet'})`);
for (const [id] of d.md.matchAll(/\bISS-\d{3}\b/g)) {
const at = detected.get(id);
if (!at) flag(`${id} is not in the Correction Register`);
else if (at > D) flag(`${id} (detected ${at})`);
}
for (const f of FAMILIES) {
const at = firstDefined.get(f);
if (at <= D) continue;
const ids = [...new Set([...d.md.matchAll(f.re)].map((m) => m[0]))];
if (ids.length) flag(`${f.label} IDs ${ids.slice(0, 3).join(', ')}${ids.length > 3 ? ', …' : ''} (defined ${at})`);
}
for (const what of found) errors.push(`${d.name}: ${what}`);
}
return errors;
}
+1 -1
View File
@@ -9,7 +9,7 @@ PACKAGE_DIR="$(realpath -m "${1:-$ROOT_DIR/sdlc-delivery}")"
[[ -d "$PACKAGE_DIR" ]] || { echo "Delivery package not found: $PACKAGE_DIR" >&2; exit 2; }
expected="$(find "$SOURCE_DIR" -type f -name '*.md' | wc -l | tr -d ' ')"
expected="$(find "$SOURCE_DIR" -mindepth 2 -type f -name '*.md' | wc -l | tr -d ' ')"
produced="$(find "$PACKAGE_DIR" -type f -name '*.pdf' | wc -l | tr -d ' ')"
[[ "$expected" == "$produced" ]] || { echo "Expected $expected PDFs, found $produced." >&2; exit 1; }
BIN
View File
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More